From c8ea33c5ce2af04afbcd895a9cd70cd5b67a0976 Mon Sep 17 00:00:00 2001 From: Vanessa Date: Mon, 20 Nov 2023 11:12:02 +0800 Subject: [PATCH] :art: https://github.com/siyuan-note/siyuan/issues/9667 --- app/appearance/langs/en_US.json | 5 + app/appearance/langs/es_ES.json | 5 + app/appearance/langs/fr_FR.json | 5 + app/appearance/langs/zh_CHT.json | 5 + app/appearance/langs/zh_CN.json | 5 + app/src/assets/scss/business/_av.scss | 41 +++-- app/src/protyle/render/av/action.ts | 3 +- app/src/protyle/render/av/calc.ts | 256 ++++++++++++++++++++++++++ app/src/protyle/render/av/cell.ts | 251 +++---------------------- app/src/protyle/render/av/col.ts | 29 ++- app/src/protyle/render/av/render.ts | 6 +- app/src/protyle/wysiwyg/index.ts | 2 +- app/src/types/index.d.ts | 4 + 13 files changed, 372 insertions(+), 245 deletions(-) create mode 100644 app/src/protyle/render/av/calc.ts diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index f92fcfd04..8ccd454a8 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -1,4 +1,9 @@ { + "checked": "Checked", + "unchecked": "Unchecked", + "percentChecked": "Percent checked", + "percentUnchecked": "Percent unchecked", + "checkbox": "Checkbox", "copyInline": "Copy inline element", "unsplit": "Unsplit", "unsplitAll": "Unsplit All", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index e9df8511a..9b0161695 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1,4 +1,9 @@ { + "checked": "marcado", + "unchecked": "desmarcado", + "percentChecked": "Porcentaje comprobado", + "percentUnchecked": "Porcentaje no marcado", + "checkbox": "Casilla de verificación", "copyInline": "Copiar elemento en línea", "unsplit": "Desdividir", "unsplitAll": "Desdividir Todo", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index 0727c71d1..1266a7358 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -1,4 +1,9 @@ { + "checked": "Coché", + "unchecked": "Décoché", + "percentChecked": "Pourcentage vérifié", + "percentUnchecked": "Pourcentage non coché", + "checkbox": "case à cocher", "copyInline": "Copier l'élément en ligne", "unsplit": "Unsplit", "unsplitAll": "Tout dédiviser", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index 106cd4a2e..b15fa250a 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -1,4 +1,9 @@ { + "checked": "已完成", + "unchecked": "未完成", + "percentChecked": "已完成佔比", + "percentUnchecked": "未完成佔比", + "checkbox": "勾選方塊", "copyInline": "複製行級元素", "unsplit": "取消分割畫面", "unsplitAll": "取消全部分螢幕", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index c4e2f03a8..8050e14e0 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -1,4 +1,9 @@ { + "checked": "已完成", + "unchecked": "未完成", + "percentChecked": "已完成占比", + "percentUnchecked": "未完成占比", + "checkbox": "勾选框", "copyInline": "复制行级元素", "unsplit": "取消分屏", "unsplitAll": "取消全部分屏", diff --git a/app/src/assets/scss/business/_av.scss b/app/src/assets/scss/business/_av.scss index db2349ee1..ca2a751fa 100644 --- a/app/src/assets/scss/business/_av.scss +++ b/app/src/assets/scss/business/_av.scss @@ -113,7 +113,8 @@ } &--select { - &:not(.av__row--header) .av__cell { + &:not(.av__row--header) .av__cell, + &:not(.av__row--header) .av__firstcol { background-color: var(--b3-av-background-hl); } @@ -231,7 +232,7 @@ position: absolute; right: 5px; font-size: 85%; - top: 8px; + top: 5.5px; } &.dragover__right { @@ -267,15 +268,15 @@ align-items: center; flex: 1; overflow: hidden; + } - & > .av__cellicon { - height: 1em; - width: 1em; - color: var(--b3-theme-on-surface); - margin: 0 5px 0 0; - flex-shrink: 0; - line-height: 1em; - } + &__cellheadericon { + height: 1em; + width: 1em; + color: var(--b3-theme-on-surface); + margin: 0 5px 0 0; + flex-shrink: 0; + line-height: 1em; } &__celltext { @@ -292,15 +293,23 @@ } } + &__checkbox { + color: var(--b3-theme-on-surface); + height: 14px; + width: 14px; + float: left; + padding: 4.5px 0; + + &:hover { + color: var(--b3-theme-on-background); + } + } + &__firstcol { svg { - color: var(--b3-theme-on-surface); - height: 33px; - width: 24px; + @extend .av__checkbox; opacity: 0; - padding: 5px; - box-sizing: border-box; - float: left; + padding: 9.5px 5px; } &:hover svg { diff --git a/app/src/protyle/render/av/action.ts b/app/src/protyle/render/av/action.ts index 50ea29fdd..9f938290f 100644 --- a/app/src/protyle/render/av/action.ts +++ b/app/src/protyle/render/av/action.ts @@ -3,7 +3,7 @@ import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName} from "../ import {transaction} from "../../wysiwyg/transaction"; import {openEditorTab} from "../../../menus/util"; import {copySubMenu} from "../../../menus/commonMenuItem"; -import {getTypeByCellElement, openCalcMenu, popTextCell} from "./cell"; +import {getTypeByCellElement, popTextCell} from "./cell"; import {getColIconByType, showColMenu} from "./col"; import {insertAttrViewBlockAnimation, updateHeader} from "./row"; import {emitOpenMenu} from "../../../plugin/EventBus"; @@ -23,6 +23,7 @@ import {getSearch} from "../../../util/functions"; import {unicode2Emoji} from "../../../emoji"; import {selectRow} from "./row"; import * as dayjs from "dayjs"; +import {openCalcMenu} from "./calc"; export const avClick = (protyle: IProtyle, event: MouseEvent & { target: HTMLElement }) => { const blockElement = hasClosestBlock(event.target); diff --git a/app/src/protyle/render/av/calc.ts b/app/src/protyle/render/av/calc.ts new file mode 100644 index 000000000..809fe1170 --- /dev/null +++ b/app/src/protyle/render/av/calc.ts @@ -0,0 +1,256 @@ +import {Menu} from "../../../plugin/Menu"; +import {transaction} from "../../wysiwyg/transaction"; +import {hasClosestBlock, hasClosestByClassName} from "../../util/hasClosest"; + +const calcItem = (options: { + menu: Menu, + protyle: IProtyle, + label: string, + operator: string, + oldOperator: string, + colId: string, + avId: string +}) => { + options.menu.addItem({ + iconHTML: "", + label: options.label, + click() { + transaction(options.protyle, [{ + action: "setAttrViewColCalc", + avID: options.avId, + id: options.colId, + data: { + operator: options.operator + } + }], [{ + action: "setAttrViewColCalc", + avID: options.avId, + id: options.colId, + data: { + operator: options.oldOperator + } + }]); + } + }); +}; + +export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { + const blockElement = hasClosestBlock(calcElement); + if (!blockElement) { + return; + } + const rowElement = hasClosestByClassName(calcElement, "av__row--footer"); + if (!rowElement) { + return; + } + rowElement.classList.add("av__row--show"); + const menu = new Menu("av-calc", () => { + rowElement.classList.remove("av__row--show"); + }); + if (menu.isOpen) { + return; + } + const type = calcElement.dataset.dtype as TAVCol; + const colId = calcElement.dataset.colId; + const avId = blockElement.dataset.avId; + const oldOperator = calcElement.dataset.operator; + if (type !== "checkbox") { + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "", + label: window.siyuan.languages.calcOperatorNone + }); + } + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Count all", + label: window.siyuan.languages.calcOperatorCountAll + }); + if (type !== "checkbox") { + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Count values", + label: window.siyuan.languages.calcOperatorCountValues + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Count unique values", + label: window.siyuan.languages.calcOperatorCountUniqueValues + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Count empty", + label: window.siyuan.languages.calcOperatorCountEmpty + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Count not empty", + label: window.siyuan.languages.calcOperatorCountNotEmpty + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Percent empty", + label: window.siyuan.languages.calcOperatorPercentEmpty + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Percent not empty", + label: window.siyuan.languages.calcOperatorPercentNotEmpty + }); + } else { + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Checked", + label: window.siyuan.languages.checked + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Unchecked", + label: window.siyuan.languages.unchecked + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Percent checked", + label: window.siyuan.languages.percentChecked + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Percent unchecked", + label: window.siyuan.languages.percentUnchecked + }); + } + if (["number", "template"].includes(type)) { + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Sum", + label: window.siyuan.languages.calcOperatorSum + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Average", + label: window.siyuan.languages.calcOperatorAverage + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Median", + label: window.siyuan.languages.calcOperatorMedian + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Min", + label: window.siyuan.languages.calcOperatorMin + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Max", + label: window.siyuan.languages.calcOperatorMax + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Range", + label: window.siyuan.languages.calcOperatorRange + }); + } else if (["date", "created", "updated"].includes(type)) { + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Earliest", + label: window.siyuan.languages.calcOperatorEarliest + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Latest", + label: window.siyuan.languages.calcOperatorLatest + }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Range", + label: window.siyuan.languages.calcOperatorRange + }); + } + const calcRect = calcElement.getBoundingClientRect(); + menu.open({x: calcRect.left, y: calcRect.bottom, h: calcRect.height}); +}; diff --git a/app/src/protyle/render/av/cell.ts b/app/src/protyle/render/av/cell.ts index c2dd5b81f..084fc7925 100644 --- a/app/src/protyle/render/av/cell.ts +++ b/app/src/protyle/render/av/cell.ts @@ -133,223 +133,18 @@ export const genCellValue = (colType: TAVCol, value: string | any) => { type: colType, mAsset: value as IAVCellAssetValue[] }; + } else if (colType === "checkbox") { + cellValue = { + type: colType, + checkbox: { + checked: value ? true : false + } + }; } } return cellValue; }; -const calcItem = (options: { - menu: Menu, - protyle: IProtyle, - label: string, - operator: string, - oldOperator: string, - colId: string, - avId: string -}) => { - options.menu.addItem({ - iconHTML: "", - label: options.label, - click() { - transaction(options.protyle, [{ - action: "setAttrViewColCalc", - avID: options.avId, - id: options.colId, - data: { - operator: options.operator - } - }], [{ - action: "setAttrViewColCalc", - avID: options.avId, - id: options.colId, - data: { - operator: options.oldOperator - } - }]); - } - }); -}; - -export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { - const blockElement = hasClosestBlock(calcElement); - if (!blockElement) { - return; - } - const rowElement = hasClosestByClassName(calcElement, "av__row--footer"); - if (!rowElement) { - return; - } - rowElement.classList.add("av__row--show"); - const menu = new Menu("av-calc", () => { - rowElement.classList.remove("av__row--show"); - }); - if (menu.isOpen) { - return; - } - const type = calcElement.dataset.dtype as TAVCol; - const colId = calcElement.dataset.colId; - const avId = blockElement.dataset.avId; - const oldOperator = calcElement.dataset.operator; - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "", - label: window.siyuan.languages.calcOperatorNone - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Count all", - label: window.siyuan.languages.calcOperatorCountAll - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Count values", - label: window.siyuan.languages.calcOperatorCountValues - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Count unique values", - label: window.siyuan.languages.calcOperatorCountUniqueValues - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Count empty", - label: window.siyuan.languages.calcOperatorCountEmpty - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Count not empty", - label: window.siyuan.languages.calcOperatorCountNotEmpty - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Percent empty", - label: window.siyuan.languages.calcOperatorPercentEmpty - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Percent not empty", - label: window.siyuan.languages.calcOperatorPercentNotEmpty - }); - if (["number", "template"].includes(type)) { - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Sum", - label: window.siyuan.languages.calcOperatorSum - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Average", - label: window.siyuan.languages.calcOperatorAverage - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Median", - label: window.siyuan.languages.calcOperatorMedian - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Min", - label: window.siyuan.languages.calcOperatorMin - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Max", - label: window.siyuan.languages.calcOperatorMax - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Range", - label: window.siyuan.languages.calcOperatorRange - }); - } else if (["date", "created", "updated"].includes(type)) { - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Earliest", - label: window.siyuan.languages.calcOperatorEarliest - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Latest", - label: window.siyuan.languages.calcOperatorLatest - }); - calcItem({ - menu, - protyle, - colId, - avId, - oldOperator, - operator: "Range", - label: window.siyuan.languages.calcOperatorRange - }); - } - const calcRect = calcElement.getBoundingClientRect(); - menu.open({x: calcRect.left, y: calcRect.bottom, h: calcRect.height}); -}; - export const cellScrollIntoView = (blockElement: HTMLElement, cellElement: Element, onlyHeight = true) => { const cellRect = cellElement.getBoundingClientRect(); if (!onlyHeight) { @@ -427,7 +222,7 @@ export const popTextCell = (protyle: IProtyle, cellElements: HTMLElement[], type } cellRect = cellElements[0].getBoundingClientRect(); let html = ""; - const style = `style="position:absolute;left: ${cellRect.left}px;top: ${cellRect.top}px;width:${Math.max(cellRect.width, 58)}px;height: ${cellRect.height}px"`; + const style = `style="position:absolute;left: ${cellRect.left}px;top: ${cellRect.top}px;width:${Math.max(cellRect.width, 25)}px;height: ${cellRect.height}px"`; if (["text", "url", "email", "phone", "block", "template"].includes(type)) { html = ``; } else if (type === "number") { @@ -439,6 +234,8 @@ export const popTextCell = (protyle: IProtyle, cellElements: HTMLElement[], type openMenuPanel({protyle, blockElement, type: "asset", cellElements}); } else if (type === "date") { openMenuPanel({protyle, blockElement, type: "date", cellElements}); + } else if (type === "checkbox") { + updateCellValue(protyle, type, cellElements); } if (!hasClosestByClassName(cellElements[0], "custom-attr")) { cellElements[0].classList.add("av__cell--select"); @@ -548,22 +345,28 @@ const updateCellValue = (protyle: IProtyle, type: TAVCol, cellElements: HTMLElem const cellId = item.getAttribute("data-id"); const colId = item.getAttribute("data-col-id"); const inputValue: { - content: string | number, - isNotEmpty?: boolean - } = { - content: (avMaskElement.querySelector(".b3-text-field") as HTMLInputElement).value - }; + content?: string | number, + isNotEmpty?: boolean, + checked?: boolean, + } = {}; const oldValue: { - content: string | number, - isNotEmpty?: boolean - } = { - content: type === "block" ? item.firstElementChild.textContent.trim() : item.textContent.trim() - }; + content?: string | number, + isNotEmpty?: boolean, + checked?: boolean, + } = {}; if (type === "number") { oldValue.content = parseFloat(oldValue.content as string); oldValue.isNotEmpty = typeof oldValue.content === "number" && !isNaN(oldValue.content); inputValue.content = parseFloat(inputValue.content as string); inputValue.isNotEmpty = typeof inputValue.content === "number" && !isNaN(inputValue.content); + } else if (type === "checkbox") { + const useElement = item.querySelector("use") + inputValue.checked = useElement.getAttribute("xlink:href") === "#iconUncheck" + oldValue.checked = !inputValue.checked + useElement.setAttribute("xlink:href", inputValue.checked ? "#iconCheck" : "#iconUncheck") + } else { + inputValue.content = (avMaskElement.querySelector(".b3-text-field") as HTMLInputElement).value + oldValue.content = type === "block" ? item.firstElementChild.textContent.trim() : item.textContent.trim() } if (objEquals(inputValue, oldValue)) { return; @@ -607,6 +410,6 @@ const updateCellValue = (protyle: IProtyle, type: TAVCol, cellElements: HTMLElem focusBlock(blockElement); } setTimeout(() => { - avMaskElement.remove(); + avMaskElement?.remove(); }); }; diff --git a/app/src/protyle/render/av/col.ts b/app/src/protyle/render/av/col.ts index 30554c2ba..953fad778 100644 --- a/app/src/protyle/render/av/col.ts +++ b/app/src/protyle/render/av/col.ts @@ -353,6 +353,8 @@ export const getColIconByType = (type: TAVCol) => { return "iconPhone"; case "template": return "iconMath"; + case "checkbox": + return "iconCheck"; } }; @@ -379,7 +381,7 @@ export const addAttrViewColAnimation = (options: { if (index === 0) { html = `
- ${options.icon ? unicode2Emoji(options.icon, "av__cellicon", true) : ``} + ${options.icon ? unicode2Emoji(options.icon, "av__cellheadericon", true) : ``} ${options.name}
@@ -825,6 +827,31 @@ export const addCol = (protyle: IProtyle, blockElement: Element) => { }); } }); + menu.addItem({ + icon: "iconCheck", + label: window.siyuan.languages.checkbox, + click() { + const id = Lute.NewNodeID(); + transaction(protyle, [{ + action: "addAttrViewCol", + name: window.siyuan.languages.checkbox, + avID, + type: "checkbox", + id + }], [{ + action: "removeAttrViewCol", + id, + avID, + }]); + addAttrViewColAnimation({ + blockElement: blockElement, + protyle: protyle, + type: "checkbox", + name: window.siyuan.languages.checkbox, + id + }); + } + }); menu.addItem({ icon: "iconLink", label: window.siyuan.languages.link, diff --git a/app/src/protyle/render/av/render.ts b/app/src/protyle/render/av/render.ts index 18cc671c0..60ca2c23c 100644 --- a/app/src/protyle/render/av/render.ts +++ b/app/src/protyle/render/av/render.ts @@ -85,9 +85,9 @@ data-icon="${column.icon}" data-dtype="${column.type}" data-pin="${column.pin}" style="width: ${column.width || "200px"}; ${column.wrap ? "" : "white-space: nowrap;"}">
- ${column.icon ? unicode2Emoji(column.icon, "av__cellicon", true) : ``} + ${column.icon ? unicode2Emoji(column.icon, "av__cellheadericon", true) : ``} ${column.name} - ${column.pin ? '
' : ""} + ${column.pin ? '
' : ""}
`; @@ -172,6 +172,8 @@ style="width: ${column.width || "200px"}">${getCalcValue(column) || '${item.name}`; } }); + } else if (cell.valueType === "checkbox") { + text += ``; } if (["text", "template", "url", "email", "phone", "number", "date", "created", "updated"].includes(cell.valueType) && cell.value && cell.value[cell.valueType as "url"].content) { diff --git a/app/src/protyle/wysiwyg/index.ts b/app/src/protyle/wysiwyg/index.ts index c274ea303..70da892ff 100644 --- a/app/src/protyle/wysiwyg/index.ts +++ b/app/src/protyle/wysiwyg/index.ts @@ -385,7 +385,7 @@ export class WYSIWYG { const scrollElement = nodeElement.querySelector(".av__scroll"); const contentRect = protyle.contentElement.getBoundingClientRect(); documentSelf.onmousemove = (moveEvent: MouseEvent) => { - newWidth = Math.max(oldWidth + (moveEvent.clientX - event.clientX), 58) + "px"; + newWidth = Math.max(oldWidth + (moveEvent.clientX - event.clientX), 25) + "px"; scrollElement.querySelectorAll(".av__row, .av__row--footer").forEach(item => { (item.querySelector(`[data-col-id="${dragColId}"]`) as HTMLElement).style.width = newWidth; }); diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index 7a8f16300..2c5e7de8d 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -71,6 +71,7 @@ type TAVCol = | "template" | "created" | "updated" + | "checkbox" type THintSource = "search" | "av" | "hint"; type TAVFilterOperator = "=" @@ -1094,6 +1095,9 @@ interface IAVCellValue { } template?: { content: string + }, + checkbox?: { + checked: boolean } date?: IAVCellDateValue created?: IAVCellDateValue