From 8a354da50a4e2c67b9b924e2255cf8f78396d142 Mon Sep 17 00:00:00 2001 From: Silent Lee Date: Mon, 15 Apr 2024 00:49:48 +0800 Subject: [PATCH] Add database `lineNumber` field type (#11008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :art: add database lineNumber type * :art: fix https://github.com/siyuan-note/siyuan/issues/10896 * :art: Improve mobile app appearance language https://github.com/siyuan-note/siyuan/issues/11009 * :art: Improve database template field calc https://github.com/siyuan-note/siyuan/issues/11011 * :arrow_up: Upgrade kernel deps * :arrow_up: Upgrade kernel deps * :art: fix https://github.com/siyuan-note/siyuan/issues/10896 * :art: fix https://github.com/siyuan-note/siyuan/issues/10896 * :bug: https://github.com/siyuan-note/siyuan/issues/11015 * :bug: https://github.com/siyuan-note/siyuan/issues/11018 * :bug: https://github.com/siyuan-note/siyuan/issues/11018 * :art: 刚创建时无 id,更新需和 oldValue 保持一致 * :bug: Database date field between filter calculation error Fix https://github.com/siyuan-note/siyuan/issues/10979 * :bug: Primary key value unexpectedly updated when database adds row https://github.com/siyuan-note/siyuan/issues/11018 * :art: https://github.com/siyuan-note/siyuan/issues/11013 * :art: 搜索可汇总字段时排除行号类型字段 https://github.com/siyuan-note/siyuan/pull/11008 * :art: lineNumber no need to join the calc op and remove useless todo tag --------- Co-authored-by: Vanessa Co-authored-by: Daniel <845765@qq.com> --- .gitignore | 1 + app/appearance/langs/en_US.json | 1 + app/appearance/langs/es_ES.json | 1 + app/appearance/langs/fr_FR.json | 1 + app/appearance/langs/zh_CHT.json | 1 + app/appearance/langs/zh_CN.json | 1 + app/src/protyle/render/av/action.ts | 3 +- app/src/protyle/render/av/cell.ts | 10 +- app/src/protyle/render/av/col.ts | 195 +++++++++++++-------- app/src/protyle/render/av/filter.ts | 3 +- app/src/protyle/render/av/openMenuPanel.ts | 40 +++++ app/src/protyle/render/av/render.ts | 16 +- app/src/protyle/render/av/sort.ts | 19 +- app/src/types/index.d.ts | 1 + kernel/av/av.go | 33 ++-- kernel/model/attribute_view.go | 6 +- 16 files changed, 224 insertions(+), 108 deletions(-) diff --git a/.gitignore b/.gitignore index 4cdcb33b3..beff8f1d9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ electron/dist # IDE .idea/ +.vscode/ # Log logs diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index fa5a50889..62612e2ec 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -119,6 +119,7 @@ "goToEditTabPrev": "Go to previous edited tab", "createdTime": "Created time", "updatedTime": "Updated time", + "lineNumber": "Line number", "removeBookmark": "Remove bookmark from ${x}?", "defaultMargin": "Default", "noneMargin": "None", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index 620fa4b3c..703991e49 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -119,6 +119,7 @@ "goToEditTabPrev": "Ir a la pestaña editada anteriormente", "createdTime": "Hora de creación", "updatedTime": "Hora actualizada", + "lineNumber": "Número de línea", "removeBookmark": "¿Eliminar marcador de ${x}?", "lockEdit": "Hacer que el documento sea de sólo lectura", "unlockEdit": "Hacer que el documento sea escribible", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index adbf84220..dd28a4723 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -119,6 +119,7 @@ "goToEditTabPrev": "Aller à l'onglet modifié précédent", "createdTime": "Heure de création", "updatedTime": "Heure mise à jour", + "lineNumber": "Numéro de ligne", "removeBookmark": "Supprimer le signet de ${x} ?", "lockEdit": "Rendre le document en lecture seule", "unlockEdit": "Rendre le document accessible en écriture", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index d21ac9f4c..d88415cfb 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -119,6 +119,7 @@ "goToEditTabPrev": "跳到上一個編輯頁籤", "createdTime": "建立時間", "updatedTime": "更新時間", + "lineNumber": "行號", "removeBookmark": "移除 ${x} 中的書籤?", "lockEdit": "鎖定編輯", "unlockEdit": "解除鎖定", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index 5325449e4..c418a93b2 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -119,6 +119,7 @@ "goToEditTabPrev": "跳转到上一个编辑页签", "createdTime": "创建时间", "updatedTime": "更新时间", + "lineNumber": "行号", "removeBookmark": "移除 ${x} 中的书签?", "lockEdit": "锁定编辑", "unlockEdit": "解除锁定", diff --git a/app/src/protyle/render/av/action.ts b/app/src/protyle/render/av/action.ts index 073614c62..595784b50 100644 --- a/app/src/protyle/render/av/action.ts +++ b/app/src/protyle/render/av/action.ts @@ -251,7 +251,8 @@ export const avClick = (protyle: IProtyle, event: MouseEvent & { target: HTMLEle return; } const type = getTypeByCellElement(target); - if (type === "updated" || type === "created" || (type === "block" && !target.getAttribute("data-detached"))) { + // TODO 点击单元格的时候, lineNumber 选中整行 + if (type === "updated" || type === "created" || type === "lineNumber" || (type === "block" && !target.getAttribute("data-detached"))) { selectRow(rowElement.querySelector(".av__firstcol"), "toggle"); } else { scrollElement.querySelectorAll(".av__row--select").forEach(item => { diff --git a/app/src/protyle/render/av/cell.ts b/app/src/protyle/render/av/cell.ts index c909c551e..18e1f5317 100644 --- a/app/src/protyle/render/av/cell.ts +++ b/app/src/protyle/render/av/cell.ts @@ -644,7 +644,7 @@ export const renderCellAttr = (cellElement: Element, value: IAVCellValue) => { } }; -export const renderCell = (cellValue: IAVCellValue) => { +export const renderCell = (cellValue: IAVCellValue, rowIndex = 0) => { let text = ""; if (["text", "template"].includes(cellValue.type)) { text = `${cellValue ? (cellValue[cellValue.type as "text"].content || "") : ""}`; @@ -683,6 +683,9 @@ export const renderCell = (cellValue: IAVCellValue) => { text += dayjs(dataValue.content).format("YYYY-MM-DD HH:mm"); } text += ""; + } else if (["lineNumber"].includes(cellValue.type)) { + // 渲染行号 + text = `${rowIndex + 1}`; } else if (cellValue.type === "mAsset") { cellValue?.mAsset?.forEach((item) => { if (item.type === "image") { @@ -713,8 +716,9 @@ export const renderCell = (cellValue: IAVCellValue) => { text = text.substring(0, text.length - 2); } } - if (["text", "template", "url", "email", "phone", "number", "date", "created", "updated"].includes(cellValue.type) && - cellValue && cellValue[cellValue.type as "url"].content) { + + if (["text", "template", "url", "email", "phone", "number", "date", "created", "updated", "lineNumber"].includes(cellValue.type) && + ( cellValue.type === "lineNumber" || (cellValue && cellValue[cellValue.type as "url"].content))) { text += ``; } return text; diff --git a/app/src/protyle/render/av/col.ts b/app/src/protyle/render/av/col.ts index 86bbae70b..2dd6e6b9d 100644 --- a/app/src/protyle/render/av/col.ts +++ b/app/src/protyle/render/av/col.ts @@ -252,6 +252,7 @@ export const getEditHTML = (options: { ${genUpdateColItem("template", colData.type)} ${genUpdateColItem("relation", colData.type)} ${genUpdateColItem("rollup", colData.type)} + ${genUpdateColItem("lineNumber", colData.type)} ${genUpdateColItem("created", colData.type)} ${genUpdateColItem("updated", colData.type)} `; @@ -482,6 +483,8 @@ export const getColNameByType = (type: TAVCol) => { return window.siyuan.languages.checkbox; case "block": return window.siyuan.languages["_attrView"].key; + case "lineNumber": + return window.siyuan.languages.lineNumber; } }; @@ -518,6 +521,8 @@ export const getColIconByType = (type: TAVCol) => { return "iconMath"; case "checkbox": return "iconCheck"; + case "lineNumber": + return "iconSpreadOdd"; } }; @@ -694,90 +699,94 @@ export const showColMenu = (protyle: IProtyle, blockElement: Element, cellElemen } }); menu.addSeparator(); - menu.addItem({ - icon: "iconUp", - label: window.siyuan.languages.asc, - click() { - fetchPost("/api/av/renderAttributeView", { - id: avID, - }, (response) => { - transaction(protyle, [{ - action: "setAttrViewSorts", - avID: response.data.id, - data: [{ - column: colId, - order: "ASC" - }], - blockID - }], [{ - action: "setAttrViewSorts", - avID: response.data.id, - data: response.data.view.sorts, - blockID - }]); - }); - } - }); - menu.addItem({ - icon: "iconDown", - label: window.siyuan.languages.desc, - click() { - fetchPost("/api/av/renderAttributeView", { - id: avID, - }, (response) => { - transaction(protyle, [{ - action: "setAttrViewSorts", - avID: response.data.id, - data: [{ - column: colId, - order: "DESC" - }], - blockID - }], [{ - action: "setAttrViewSorts", - avID: response.data.id, - data: response.data.view.sorts, - blockID - }]); - }); - } - }); - if (type !== "mAsset") { + + // 行号 类型不参与 排序和筛选 + if (type !== "lineNumber") { menu.addItem({ - icon: "iconFilter", - label: window.siyuan.languages.filter, + icon: "iconUp", + label: window.siyuan.languages.asc, click() { fetchPost("/api/av/renderAttributeView", { id: avID, }, (response) => { - const avData = response.data as IAV; - let filter: IAVFilter; - avData.view.filters.find((item) => { - if (item.column === colId && item.value.type === type) { - filter = item; - return true; - } - }); - if (!filter) { - filter = { + transaction(protyle, [{ + action: "setAttrViewSorts", + avID: response.data.id, + data: [{ column: colId, - operator: getDefaultOperatorByType(type), - value: genCellValue(type, ""), - }; - avData.view.filters.push(filter); - } - setFilter({ - filter, - protyle, - data: avData, - blockElement: blockElement, - target: blockElement.querySelector(`.av__row--header .av__cell[data-col-id="${colId}"]`), - }); + order: "ASC" + }], + blockID + }], [{ + action: "setAttrViewSorts", + avID: response.data.id, + data: response.data.view.sorts, + blockID + }]); }); } }); + menu.addItem({ + icon: "iconDown", + label: window.siyuan.languages.desc, + click() { + fetchPost("/api/av/renderAttributeView", { + id: avID, + }, (response) => { + transaction(protyle, [{ + action: "setAttrViewSorts", + avID: response.data.id, + data: [{ + column: colId, + order: "DESC" + }], + blockID + }], [{ + action: "setAttrViewSorts", + avID: response.data.id, + data: response.data.view.sorts, + blockID + }]); + }); + } + }); + if (type !== "mAsset") { + menu.addItem({ + icon: "iconFilter", + label: window.siyuan.languages.filter, + click() { + fetchPost("/api/av/renderAttributeView", { + id: avID, + }, (response) => { + const avData = response.data as IAV; + let filter: IAVFilter; + avData.view.filters.find((item) => { + if (item.column === colId && item.value.type === type) { + filter = item; + return true; + } + }); + if (!filter) { + filter = { + column: colId, + operator: getDefaultOperatorByType(type), + value: genCellValue(type, ""), + }; + avData.view.filters.push(filter); + } + setFilter({ + filter, + protyle, + data: avData, + blockElement: blockElement, + target: blockElement.querySelector(`.av__row--header .av__cell[data-col-id="${colId}"]`), + }); + }); + } + }); + } + menu.addSeparator(); } - menu.addSeparator(); menu.addItem({ icon: "iconInsertLeft", @@ -1429,6 +1438,44 @@ export const addCol = (protyle: IProtyle, blockElement: Element, previousID?: st blockElement.setAttribute("updated", newUpdated); } }); + // 在创建时间前插入 lineNumber + menu.addItem({ + icon: "iconSpreadOdd", + label: window.siyuan.languages.lineNumber, + click() { + const id = Lute.NewNodeID(); + const newUpdated = dayjs().format("YYYYMMDDHHmmss"); + transaction(protyle, [{ + action: "addAttrViewCol", + name: window.siyuan.languages.lineNumber, + avID, + type: "lineNumber", + id, + previousID + }, { + action: "doUpdateUpdated", + id: blockId, + data: newUpdated, + }], [{ + action: "removeAttrViewCol", + id, + avID, + }, { + action: "doUpdateUpdated", + id: blockId, + data: blockElement.getAttribute("updated") + }]); + addAttrViewColAnimation({ + blockElement: blockElement, + protyle: protyle, + type: "lineNumber", + name: window.siyuan.languages.lineNumber, + id, + previousID + }); + blockElement.setAttribute("updated", newUpdated); + } + }); menu.addItem({ icon: "iconClock", label: window.siyuan.languages.createdTime, diff --git a/app/src/protyle/render/av/filter.ts b/app/src/protyle/render/av/filter.ts index 929cfad73..520e04c04 100644 --- a/app/src/protyle/render/av/filter.ts +++ b/app/src/protyle/render/av/filter.ts @@ -566,7 +566,8 @@ export const addFilter = (options: { return true; } }); - if (!filter && column.type !== "mAsset") { + // 该列是行号类型列,则不允许添加到过滤器 + if (!filter && column.type !== "mAsset" && column.type !== "lineNumber") { menu.addItem({ label: column.name, iconHTML: column.icon ? unicode2Emoji(column.icon, "b3-menu__icon", true) : ``, diff --git a/app/src/protyle/render/av/openMenuPanel.ts b/app/src/protyle/render/av/openMenuPanel.ts index 819382aa9..76f9eefad 100644 --- a/app/src/protyle/render/av/openMenuPanel.ts +++ b/app/src/protyle/render/av/openMenuPanel.ts @@ -854,6 +854,46 @@ export const openMenuPanel = (options: { name, type: target.dataset.oldType as TAVCol, }]); + + // 需要取消 lineNumber 列的排序和过滤 + if (target.dataset.newType === "lineNumber") { + const sortExist = data.view.sorts.find((sort) => sort.column === options.colId); + if (sortExist) { + const oldSorts = Object.assign([], data.view.sorts); + const newSorts = data.view.sorts.filter((sort) => sort.column !== options.colId); + + transaction(options.protyle, [{ + action: "setAttrViewSorts", + avID: data.id, + data: newSorts, + blockID, + }], [{ + action: "setAttrViewSorts", + avID: data.id, + data: oldSorts, + blockID, + }]); + } + + const filterExist = data.view.filters.find((filter) => filter.column === options.colId); + if (filterExist) { + const oldFilters = JSON.parse(JSON.stringify(data.view.filters)); + const newFilters = data.view.filters.filter((filter) => filter.column !== options.colId); + + transaction(options.protyle, [{ + action: "setAttrViewFilters", + avID: data.id, + data: newFilters, + blockID + }], [{ + action: "setAttrViewFilters", + avID: data.id, + data: oldFilters, + blockID + }]); + } + + } } avPanelElement.remove(); event.preventDefault(); diff --git a/app/src/protyle/render/av/render.ts b/app/src/protyle/render/av/render.ts index 7b826f530..4d6dbe36d 100644 --- a/app/src/protyle/render/av/render.ts +++ b/app/src/protyle/render/av/render.ts @@ -129,8 +129,16 @@ style="width: ${column.width || "200px"};"> if (pinIndex === index) { tableHTML += ""; } - calcHTML += `
${getCalcValue(column) || '' + window.siyuan.languages.calc}
`; + + // lineNumber type 不参与计算操作 + if (column.type === "lineNumber") { + calcHTML += `
 
`; + } else { + calcHTML += `
${getCalcValue(column) || '' + window.siyuan.languages.calc}
`; + } + if (pinIndex === index) { calcHTML += ""; } @@ -142,7 +150,7 @@ style="width: ${index === 0 ? ((parseInt(column.width || "200") + 24) + "px") : `; // body - data.rows.forEach((row: IAVRow) => { + data.rows.forEach((row: IAVRow, rowIndex: number) => { tableHTML += `
`; if (pinIndex > -1) { tableHTML += '
'; @@ -165,7 +173,7 @@ ${cell.value?.isDetached ? ' data-detached="true"' : ""} style="width: ${data.columns[index].width || "200px"}; ${cell.valueType === "number" ? "text-align: right;" : ""} ${cell.bgColor ? `background-color:${cell.bgColor};` : ""} -${cell.color ? `color:${cell.color};` : ""}">${renderCell(cell.value)}
`; +${cell.color ? `color:${cell.color};` : ""}">${renderCell(cell.value, rowIndex)}
`; if (pinIndex === index) { tableHTML += ""; diff --git a/app/src/protyle/render/av/sort.ts b/app/src/protyle/render/av/sort.ts index af727019b..e56936c11 100644 --- a/app/src/protyle/render/av/sort.ts +++ b/app/src/protyle/render/av/sort.ts @@ -16,12 +16,19 @@ export const addSort = (options: { const menu = new Menu("av-add-sort"); options.data.view.columns.forEach((column) => { let hasSort = false; - options.data.view.sorts.find((sort) => { - if (sort.column === column.id) { - hasSort = true; - return true; - } - }); + + // 如果该列是行号类型列,不允许添加排序 + if (column.type === "lineNumber") { + hasSort = true; + } else { + options.data.view.sorts.find((sort) => { + if (sort.column === column.id) { + hasSort = true; + return true; + } + }); + } + if (!hasSort) { menu.addItem({ label: column.name, diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index f927350c2..b06ba0815 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -82,6 +82,7 @@ type TAVCol = | "created" | "updated" | "checkbox" + | "lineNumber" type THintSource = "search" | "av" | "hint"; type TAVFilterOperator = "=" diff --git a/kernel/av/av.go b/kernel/av/av.go index 9713035d4..a9242f4f6 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -64,22 +64,23 @@ func (kValues *KeyValues) GetValue(blockID string) (ret *Value) { type KeyType string const ( - KeyTypeBlock KeyType = "block" - KeyTypeText KeyType = "text" - KeyTypeNumber KeyType = "number" - KeyTypeDate KeyType = "date" - KeyTypeSelect KeyType = "select" - KeyTypeMSelect KeyType = "mSelect" - KeyTypeURL KeyType = "url" - KeyTypeEmail KeyType = "email" - KeyTypePhone KeyType = "phone" - KeyTypeMAsset KeyType = "mAsset" - KeyTypeTemplate KeyType = "template" - KeyTypeCreated KeyType = "created" - KeyTypeUpdated KeyType = "updated" - KeyTypeCheckbox KeyType = "checkbox" - KeyTypeRelation KeyType = "relation" - KeyTypeRollup KeyType = "rollup" + KeyTypeBlock KeyType = "block" + KeyTypeText KeyType = "text" + KeyTypeNumber KeyType = "number" + KeyTypeDate KeyType = "date" + KeyTypeSelect KeyType = "select" + KeyTypeMSelect KeyType = "mSelect" + KeyTypeURL KeyType = "url" + KeyTypeEmail KeyType = "email" + KeyTypePhone KeyType = "phone" + KeyTypeMAsset KeyType = "mAsset" + KeyTypeTemplate KeyType = "template" + KeyTypeCreated KeyType = "created" + KeyTypeUpdated KeyType = "updated" + KeyTypeCheckbox KeyType = "checkbox" + KeyTypeRelation KeyType = "relation" + KeyTypeRollup KeyType = "rollup" + KeyTypeLineNumber KeyType = "lineNumber" ) // Key 描述了属性视图属性列的基础结构。 diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 17051cb59..4db2283f8 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -145,7 +145,7 @@ func SearchAttributeViewNonRelationKey(avID, keyword string) (ret []*av.Key) { } for _, keyValues := range attrView.KeyValues { - if av.KeyTypeRelation != keyValues.Key.Type && av.KeyTypeRollup != keyValues.Key.Type && av.KeyTypeTemplate != keyValues.Key.Type && av.KeyTypeCreated != keyValues.Key.Type && av.KeyTypeUpdated != keyValues.Key.Type { + if av.KeyTypeRelation != keyValues.Key.Type && av.KeyTypeRollup != keyValues.Key.Type && av.KeyTypeTemplate != keyValues.Key.Type && av.KeyTypeCreated != keyValues.Key.Type && av.KeyTypeUpdated != keyValues.Key.Type && av.KeyTypeLineNumber != keyValues.Key.Type { if strings.Contains(strings.ToLower(keyValues.Key.Name), strings.ToLower(keyword)) { ret = append(ret, keyValues.Key) } @@ -2654,7 +2654,7 @@ func AddAttributeViewKey(avID, keyID, keyName, keyType, keyIcon, previousKeyID s switch keyTyp { case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate, av.KeyTypeCreated, av.KeyTypeUpdated, av.KeyTypeCheckbox, - av.KeyTypeRelation, av.KeyTypeRollup: + av.KeyTypeRelation, av.KeyTypeRollup, av.KeyTypeLineNumber: key := av.NewKey(keyID, keyName, keyIcon, keyTyp) if av.KeyTypeRollup == keyTyp { @@ -2766,7 +2766,7 @@ func updateAttributeViewColumn(operation *Operation) (err error) { switch colType { case av.KeyTypeBlock, av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate, av.KeyTypeCreated, av.KeyTypeUpdated, av.KeyTypeCheckbox, - av.KeyTypeRelation, av.KeyTypeRollup: + av.KeyTypeRelation, av.KeyTypeRollup, av.KeyTypeLineNumber: for _, keyValues := range attrView.KeyValues { if keyValues.Key.ID == operation.ID { keyValues.Key.Name = strings.TrimSpace(operation.Name)