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 b6c7a640c..a2f4432ef 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/av/sort.go b/kernel/av/sort.go index 37c36aea8..bef537865 100644 --- a/kernel/av/sort.go +++ b/kernel/av/sort.go @@ -220,59 +220,62 @@ func (value *Value) Compare(other *Value, attrView *AttributeView) int { } case KeyTypeRelation: if nil != value.Relation && nil != other.Relation { + if 1 < len(value.Relation.Contents) && 1 < len(other.Relation.Contents) && KeyTypeNumber == value.Relation.Contents[0].Type && KeyTypeNumber == other.Relation.Contents[0].Type { + v1, ok1 := util.Convert2Float(value.Relation.Contents[0].String(false)) + v2, ok2 := util.Convert2Float(other.Relation.Contents[0].String(false)) + if ok1 && ok2 { + if v1 > v2 { + return 1 + } + if v1 < v2 { + return -1 + } + return 0 + } + } + vContentBuf := bytes.Buffer{} for _, c := range value.Relation.Contents { - vContentBuf.WriteString(c.String()) + vContentBuf.WriteString(c.String(true)) vContentBuf.WriteByte(' ') } vContent := strings.TrimSpace(vContentBuf.String()) oContentBuf := bytes.Buffer{} for _, c := range other.Relation.Contents { - oContentBuf.WriteString(c.String()) + oContentBuf.WriteString(c.String(true)) oContentBuf.WriteByte(' ') } oContent := strings.TrimSpace(oContentBuf.String()) - - v1, ok1 := util.Convert2Float(vContent) - v2, ok2 := util.Convert2Float(oContent) - if ok1 && ok2 { - if v1 > v2 { - return 1 - } - - if v1 < v2 { - return -1 - } - return 0 - } return strings.Compare(vContent, oContent) } case KeyTypeRollup: if nil != value.Rollup && nil != other.Rollup { + if 1 < len(value.Rollup.Contents) && 1 < len(other.Rollup.Contents) && KeyTypeNumber == value.Rollup.Contents[0].Type && KeyTypeNumber == other.Rollup.Contents[0].Type { + v1, ok1 := util.Convert2Float(value.Rollup.Contents[0].String(false)) + v2, ok2 := util.Convert2Float(other.Rollup.Contents[0].String(false)) + if ok1 && ok2 { + if v1 > v2 { + return 1 + } + if v1 < v2 { + return -1 + } + return 0 + } + } + vContentBuf := bytes.Buffer{} for _, c := range value.Rollup.Contents { - vContentBuf.WriteString(c.String()) + vContentBuf.WriteString(c.String(true)) vContentBuf.WriteByte(' ') } vContent := strings.TrimSpace(vContentBuf.String()) oContentBuf := bytes.Buffer{} for _, c := range other.Rollup.Contents { - oContentBuf.WriteString(c.String()) + oContentBuf.WriteString(c.String(true)) oContentBuf.WriteByte(' ') } oContent := strings.TrimSpace(oContentBuf.String()) - - v1, ok1 := util.Convert2Float(vContent) - v2, ok2 := util.Convert2Float(oContent) - if ok1 && ok2 { - if v1 > v2 { - return 1 - } - if v1 < v2 { - return -1 - } - return 0 - } return strings.Compare(vContent, oContent) } } diff --git a/kernel/av/table.go b/kernel/av/table.go index ec7264fcb..fb7afc849 100644 --- a/kernel/av/table.go +++ b/kernel/av/table.go @@ -1586,8 +1586,8 @@ func (table *Table) calcColRollup(col *TableColumn, colIndex int) { for _, row := range table.Rows { if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup { for _, content := range row.Cells[colIndex].Value.Rollup.Contents { - if !uniqueValues[content.String()] { - uniqueValues[content.String()] = true + if !uniqueValues[content.String(true)] { + uniqueValues[content.String(true)] = true countUniqueValues++ } } @@ -1635,7 +1635,7 @@ func (table *Table) calcColRollup(col *TableColumn, colIndex int) { for _, row := range table.Rows { if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup && 0 < len(row.Cells[colIndex].Value.Rollup.Contents) { for _, content := range row.Cells[colIndex].Value.Rollup.Contents { - val, _ := util.Convert2Float(content.String()) + val, _ := util.Convert2Float(content.String(false)) sum += val } } @@ -1647,7 +1647,7 @@ func (table *Table) calcColRollup(col *TableColumn, colIndex int) { for _, row := range table.Rows { if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup && 0 < len(row.Cells[colIndex].Value.Rollup.Contents) { for _, content := range row.Cells[colIndex].Value.Rollup.Contents { - val, _ := util.Convert2Float(content.String()) + val, _ := util.Convert2Float(content.String(false)) sum += val count++ } @@ -1661,7 +1661,7 @@ func (table *Table) calcColRollup(col *TableColumn, colIndex int) { for _, row := range table.Rows { if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup && 0 < len(row.Cells[colIndex].Value.Rollup.Contents) { for _, content := range row.Cells[colIndex].Value.Rollup.Contents { - val, _ := util.Convert2Float(content.String()) + val, _ := util.Convert2Float(content.String(false)) values = append(values, val) } } @@ -1679,7 +1679,7 @@ func (table *Table) calcColRollup(col *TableColumn, colIndex int) { for _, row := range table.Rows { if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup && 0 < len(row.Cells[colIndex].Value.Rollup.Contents) { for _, content := range row.Cells[colIndex].Value.Rollup.Contents { - val, _ := util.Convert2Float(content.String()) + val, _ := util.Convert2Float(content.String(false)) if val < minVal { minVal = val } @@ -1694,7 +1694,7 @@ func (table *Table) calcColRollup(col *TableColumn, colIndex int) { for _, row := range table.Rows { if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup && 0 < len(row.Cells[colIndex].Value.Rollup.Contents) { for _, content := range row.Cells[colIndex].Value.Rollup.Contents { - val, _ := util.Convert2Float(content.String()) + val, _ := util.Convert2Float(content.String(false)) if val > maxVal { maxVal = val } @@ -1710,7 +1710,7 @@ func (table *Table) calcColRollup(col *TableColumn, colIndex int) { for _, row := range table.Rows { if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup && 0 < len(row.Cells[colIndex].Value.Rollup.Contents) { for _, content := range row.Cells[colIndex].Value.Rollup.Contents { - val, _ := util.Convert2Float(content.String()) + val, _ := util.Convert2Float(content.String(false)) if val < minVal { minVal = val } diff --git a/kernel/av/value.go b/kernel/av/value.go index 0a2728d5c..5b188eedd 100644 --- a/kernel/av/value.go +++ b/kernel/av/value.go @@ -64,7 +64,7 @@ func (value *Value) SetUpdatedAt(mills int64) { } } -func (value *Value) String() string { +func (value *Value) String(format bool) string { if nil == value { return "" } @@ -84,7 +84,10 @@ func (value *Value) String() string { if nil == value.Number { return "" } - return value.Number.FormattedContent + if format { + return value.Number.FormattedContent + } + return fmt.Sprintf("%f", value.Number.Content) case KeyTypeDate: if nil == value.Date { return "" @@ -158,7 +161,7 @@ func (value *Value) String() string { } var ret []string for _, v := range value.Relation.Contents { - ret = append(ret, v.String()) + ret = append(ret, v.String(format)) } return strings.TrimSpace(strings.Join(ret, ", ")) case KeyTypeRollup: @@ -167,7 +170,7 @@ func (value *Value) String() string { } var ret []string for _, v := range value.Rollup.Contents { - ret = append(ret, v.String()) + ret = append(ret, v.String(format)) } return strings.TrimSpace(strings.Join(ret, ", ")) default: @@ -679,8 +682,8 @@ func (r *ValueRollup) RenderContents(calc *RollupCalc, destKey *Key) { countUniqueValues := 0 uniqueValues := map[string]bool{} for _, v := range r.Contents { - if _, ok := uniqueValues[v.String()]; !ok { - uniqueValues[v.String()] = true + if _, ok := uniqueValues[v.String(true)]; !ok { + uniqueValues[v.String(true)] = true countUniqueValues++ } } @@ -688,7 +691,7 @@ func (r *ValueRollup) RenderContents(calc *RollupCalc, destKey *Key) { case CalcOperatorCountEmpty: countEmpty := 0 for _, v := range r.Contents { - if "" == v.String() { + if "" == v.String(true) { countEmpty++ } } @@ -696,7 +699,7 @@ func (r *ValueRollup) RenderContents(calc *RollupCalc, destKey *Key) { case CalcOperatorCountNotEmpty: countNonEmpty := 0 for _, v := range r.Contents { - if "" != v.String() { + if "" != v.String(true) { countNonEmpty++ } } @@ -704,7 +707,7 @@ func (r *ValueRollup) RenderContents(calc *RollupCalc, destKey *Key) { case CalcOperatorPercentEmpty: countEmpty := 0 for _, v := range r.Contents { - if "" == v.String() { + if "" == v.String(true) { countEmpty++ } } @@ -714,7 +717,7 @@ func (r *ValueRollup) RenderContents(calc *RollupCalc, destKey *Key) { case CalcOperatorPercentNotEmpty: countNonEmpty := 0 for _, v := range r.Contents { - if "" != v.String() { + if "" != v.String(true) { countNonEmpty++ } } diff --git a/kernel/bazaar/icon.go b/kernel/bazaar/icon.go index 94c669d51..3e1cab908 100644 --- a/kernel/bazaar/icon.go +++ b/kernel/bazaar/icon.go @@ -17,7 +17,6 @@ package bazaar import ( - "errors" "os" "path/filepath" "sort" @@ -184,14 +183,9 @@ func InstallIcon(repoURL, repoHash, installPath string, systemID string) error { if nil != err { return err } - return installPackage(data, installPath) + return installPackage(data, installPath, repoURLHash) } func UninstallIcon(installPath string) error { - if err := os.RemoveAll(installPath); nil != err { - logging.LogErrorf("remove icon [%s] failed: %s", installPath, err) - return errors.New("remove community icon failed") - } - //logging.Logger.Infof("uninstalled icon [%s]", installPath) - return nil + return uninstallPackage(installPath) } diff --git a/kernel/bazaar/package.go b/kernel/bazaar/package.go index f96161d4b..dafd7b889 100644 --- a/kernel/bazaar/package.go +++ b/kernel/bazaar/package.go @@ -19,6 +19,7 @@ package bazaar import ( "bytes" "errors" + "fmt" "os" "path/filepath" "strings" @@ -588,7 +589,26 @@ func incPackageDownloads(repoURLHash, systemID string) { }).Post(u) } -func installPackage(data []byte, installPath string) (err error) { +func uninstallPackage(installPath string) (err error) { + if err = os.RemoveAll(installPath); nil != err { + logging.LogErrorf("remove [%s] failed: %s", installPath, err) + return fmt.Errorf("remove community package [%s] failed", filepath.Base(installPath)) + } + packageCache.Flush() + return +} + +func installPackage(data []byte, installPath, repoURLHash string) (err error) { + err = installPackage0(data, installPath) + if nil != err { + return + } + + packageCache.Delete(strings.TrimPrefix(repoURLHash, "https://github.com/")) + return +} + +func installPackage0(data []byte, installPath string) (err error) { tmpPackage := filepath.Join(util.TempDir, "bazaar", "package") if err = os.MkdirAll(tmpPackage, 0755); nil != err { return diff --git a/kernel/bazaar/plugin.go b/kernel/bazaar/plugin.go index 0b70157ef..0b460e518 100644 --- a/kernel/bazaar/plugin.go +++ b/kernel/bazaar/plugin.go @@ -17,7 +17,6 @@ package bazaar import ( - "errors" "os" "path/filepath" "runtime" @@ -27,7 +26,6 @@ import ( "github.com/dustin/go-humanize" ants "github.com/panjf2000/ants/v2" - "github.com/siyuan-note/filelock" "github.com/siyuan-note/httpclient" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/util" @@ -220,16 +218,11 @@ func InstallPlugin(repoURL, repoHash, installPath string, systemID string) error if nil != err { return err } - return installPackage(data, installPath) + return installPackage(data, installPath, repoURLHash) } func UninstallPlugin(installPath string) error { - if err := filelock.Remove(installPath); nil != err { - logging.LogErrorf("remove plugin [%s] failed: %s", installPath, err) - return errors.New("remove community plugin failed") - } - //logging.Logger.Infof("uninstalled plugin [%s]", installPath) - return nil + return uninstallPackage(installPath) } func isIncompatiblePlugin(plugin *Plugin, currentFrontend string) bool { diff --git a/kernel/bazaar/template.go b/kernel/bazaar/template.go index ec5b130d8..e08f260d3 100644 --- a/kernel/bazaar/template.go +++ b/kernel/bazaar/template.go @@ -17,7 +17,6 @@ package bazaar import ( - "errors" "os" "path/filepath" "sort" @@ -27,7 +26,6 @@ import ( "github.com/dustin/go-humanize" "github.com/panjf2000/ants/v2" - "github.com/siyuan-note/filelock" "github.com/siyuan-note/httpclient" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/util" @@ -182,15 +180,11 @@ func InstallTemplate(repoURL, repoHash, installPath string, systemID string) err if nil != err { return err } - return installPackage(data, installPath) + return installPackage(data, installPath, repoURLHash) } func UninstallTemplate(installPath string) error { - if err := filelock.Remove(installPath); nil != err { - logging.LogErrorf("remove template [%s] failed: %s", installPath, err) - return errors.New("remove community template failed") - } - return nil + return uninstallPackage(installPath) } func filterLegacyTemplates(templates []*Template) (ret []*Template) { diff --git a/kernel/bazaar/theme.go b/kernel/bazaar/theme.go index 1a32efdaa..af1ec06c0 100644 --- a/kernel/bazaar/theme.go +++ b/kernel/bazaar/theme.go @@ -17,7 +17,6 @@ package bazaar import ( - "errors" "os" "path/filepath" "sort" @@ -186,14 +185,9 @@ func InstallTheme(repoURL, repoHash, installPath string, systemID string) error if nil != err { return err } - return installPackage(data, installPath) + return installPackage(data, installPath, repoURLHash) } func UninstallTheme(installPath string) error { - if err := os.RemoveAll(installPath); nil != err { - logging.LogErrorf("remove theme [%s] failed: %s", installPath, err) - return errors.New("remove community theme failed") - } - //logging.Logger.Infof("uninstalled theme [%s]", installPath) - return nil + return uninstallPackage(installPath) } diff --git a/kernel/bazaar/widget.go b/kernel/bazaar/widget.go index 1d6d64e5d..901f9c739 100644 --- a/kernel/bazaar/widget.go +++ b/kernel/bazaar/widget.go @@ -17,7 +17,6 @@ package bazaar import ( - "errors" "os" "path/filepath" "sort" @@ -26,7 +25,6 @@ import ( "github.com/dustin/go-humanize" ants "github.com/panjf2000/ants/v2" - "github.com/siyuan-note/filelock" "github.com/siyuan-note/httpclient" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/util" @@ -180,14 +178,9 @@ func InstallWidget(repoURL, repoHash, installPath string, systemID string) error if nil != err { return err } - return installPackage(data, installPath) + return installPackage(data, installPath, repoURLHash) } func UninstallWidget(installPath string) error { - if err := filelock.Remove(installPath); nil != err { - logging.LogErrorf("remove widget [%s] failed: %s", installPath, err) - return errors.New("remove community widget failed") - } - //logging.Logger.Infof("uninstalled widget [%s]", installPath) - return nil + return uninstallPackage(installPath) } diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index d7bcd6918..4db2283f8 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -89,7 +89,7 @@ func GetAttributeViewPrimaryKeyValues(avID, keyword string, page, pageSize int) } keyValues.Values = []*av.Value{} for _, v := range tmp { - if strings.Contains(strings.ToLower(v.String()), strings.ToLower(keyword)) { + if strings.Contains(strings.ToLower(v.String(true)), strings.ToLower(keyword)) { keyValues.Values = append(keyValues.Values, v) } } @@ -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) } @@ -905,14 +905,33 @@ func renderTemplateCol(ial map[string]string, flashcard *Flashcard, rowValues [] dataModel[rowValue.Key.Name] = time.UnixMilli(v.Date.Content) } } else if av.KeyTypeRollup == v.Type { - if 0 < len(v.Rollup.Contents) && av.KeyTypeNumber == v.Rollup.Contents[0].Type { - // 模板使用汇总时支持数字计算 - // Template supports numerical calculations when using rollup https://github.com/siyuan-note/siyuan/issues/10810 - // 汇总数字时仅取第一个数字填充模板 - dataModel[rowValue.Key.Name] = v.Rollup.Contents[0].Number.Content + if 0 < len(v.Rollup.Contents) { + var numbers []float64 + var contents []string + for _, content := range v.Rollup.Contents { + if av.KeyTypeNumber == content.Type { + numbers = append(numbers, content.Number.Content) + } else { + contents = append(contents, content.String(true)) + } + } + + if 0 < len(numbers) { + dataModel[rowValue.Key.Name] = numbers + } else { + dataModel[rowValue.Key.Name] = contents + } + } + } else if av.KeyTypeRelation == v.Type { + if 0 < len(v.Relation.Contents) { + var contents []string + for _, content := range v.Relation.Contents { + contents = append(contents, content.String(true)) + } + dataModel[rowValue.Key.Name] = contents } } else { - dataModel[rowValue.Key.Name] = v.String() + dataModel[rowValue.Key.Name] = v.String(true) } } } @@ -1218,7 +1237,7 @@ func renderAttributeViewTable(attrView *av.AttributeView, view *av.View, query s hit := false for _, cell := range row.Cells { for _, keyword := range keywords { - if strings.Contains(strings.ToLower(cell.Value.String()), strings.ToLower(keyword)) { + if strings.Contains(strings.ToLower(cell.Value.String(true)), strings.ToLower(keyword)) { hit = true break } @@ -2635,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 { @@ -2747,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) diff --git a/kernel/model/bazzar.go b/kernel/model/bazzar.go index 432369ca2..dd77cdc42 100644 --- a/kernel/model/bazzar.go +++ b/kernel/model/bazzar.go @@ -19,16 +19,17 @@ package model import ( "errors" "fmt" - "github.com/88250/gulu" - "github.com/siyuan-note/logging" - "github.com/siyuan-note/siyuan/kernel/util" "path" "path/filepath" "strings" "sync" "time" + "github.com/88250/gulu" + "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/bazaar" + "github.com/siyuan-note/siyuan/kernel/util" + "golang.org/x/mod/semver" ) func BatchUpdateBazaarPackages(frontend string) { @@ -202,9 +203,7 @@ func BazaarPlugins(frontend, keyword string) (plugins []*bazaar.Plugin) { plugin.Installed = util.IsPathRegularDirOrSymlinkDir(filepath.Join(util.DataDir, "plugins", plugin.Name)) if plugin.Installed { if pluginConf, err := bazaar.PluginJSON(plugin.Name); nil == err && nil != plugin { - if plugin.Version != pluginConf.Version { - plugin.Outdated = true - } + plugin.Outdated = 0 > semver.Compare("v"+pluginConf.Version, "v"+plugin.Version) } } } @@ -273,9 +272,7 @@ func BazaarWidgets(keyword string) (widgets []*bazaar.Widget) { widget.Installed = util.IsPathRegularDirOrSymlinkDir(filepath.Join(util.DataDir, "widgets", widget.Name)) if widget.Installed { if widgetConf, err := bazaar.WidgetJSON(widget.Name); nil == err && nil != widget { - if widget.Version != widgetConf.Version { - widget.Outdated = true - } + widget.Outdated = 0 > semver.Compare("v"+widgetConf.Version, "v"+widget.Version) } } } @@ -324,10 +321,8 @@ func BazaarIcons(keyword string) (icons []*bazaar.Icon) { for _, icon := range icons { if installed == icon.Name { icon.Installed = true - if themeConf, err := bazaar.IconJSON(icon.Name); nil == err { - if icon.Version != themeConf.Version { - icon.Outdated = true - } + if iconConf, err := bazaar.IconJSON(icon.Name); nil == err { + icon.Outdated = 0 > semver.Compare("v"+iconConf.Version, "v"+icon.Version) } } icon.Current = icon.Name == Conf.Appearance.Icon @@ -389,7 +384,7 @@ func BazaarThemes(keyword string) (ret []*bazaar.Theme) { if installed == theme.Name { theme.Installed = true if themeConf, err := bazaar.ThemeJSON(theme.Name); nil == err { - theme.Outdated = theme.Version != themeConf.Version + theme.Outdated = 0 > semver.Compare("v"+themeConf.Version, "v"+theme.Version) } theme.Current = theme.Name == Conf.Appearance.ThemeDark || theme.Name == Conf.Appearance.ThemeLight } @@ -462,10 +457,8 @@ func BazaarTemplates(keyword string) (templates []*bazaar.Template) { for _, template := range templates { template.Installed = util.IsPathRegularDirOrSymlinkDir(filepath.Join(util.DataDir, "templates", template.Name)) if template.Installed { - if themeConf, err := bazaar.TemplateJSON(template.Name); nil == err && nil != themeConf { - if template.Version != themeConf.Version { - template.Outdated = true - } + if templateConf, err := bazaar.TemplateJSON(template.Name); nil == err && nil != templateConf { + template.Outdated = 0 > semver.Compare("v"+templateConf.Version, "v"+template.Version) } } } diff --git a/kernel/model/box.go b/kernel/model/box.go index 450d9014b..8b2ad4d45 100644 --- a/kernel/model/box.go +++ b/kernel/model/box.go @@ -506,14 +506,15 @@ func FullReindex() { } func fullReindex() { - util.PushMsg(Conf.Language(35), 7*1000) + util.PushEndlessProgress(Conf.language(35)) + defer util.PushClearProgress() + WaitForWritingFiles() if err := sql.InitDatabase(true); nil != err { os.Exit(logging.ExitCodeReadOnlyDatabase) return } - treenode.InitBlockTree(true) sql.IndexIgnoreCached = false openedBoxes := Conf.GetOpenedBoxes() diff --git a/kernel/model/export.go b/kernel/model/export.go index f3012adfe..c86f9165b 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -148,7 +148,7 @@ func ExportAv2CSV(avID, blockID string) (zipPath string, err error) { } } - val = cell.Value.String() + val = cell.Value.String(true) } rowVal = append(rowVal, val) @@ -2335,7 +2335,7 @@ func exportTree(tree *parse.Tree, wysiwyg, expandKaTexMacros, keepFold bool, } } - val = cell.Value.String() + val = cell.Value.String(true) } mdTableCell.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: []byte(val)}) } diff --git a/kernel/model/tree.go b/kernel/model/tree.go index db9d62d1d..a83e09a06 100644 --- a/kernel/model/tree.go +++ b/kernel/model/tree.go @@ -162,7 +162,7 @@ func LoadTreeByBlockIDWithReindex(id string) (ret *parse.Tree, err error) { bt := treenode.GetBlockTree(id) if nil == bt { - if task.Contain(task.DatabaseIndex, task.DatabaseIndexFull) { + if task.ContainIndexTask() { err = ErrIndexing return } @@ -187,7 +187,7 @@ func LoadTreeByBlockID(id string) (ret *parse.Tree, err error) { bt := treenode.GetBlockTree(id) if nil == bt { - if task.Contain(task.DatabaseIndex, task.DatabaseIndexFull) { + if task.ContainIndexTask() { err = ErrIndexing return } diff --git a/kernel/task/queue.go b/kernel/task/queue.go index 4eba696ba..2baf64609 100644 --- a/kernel/task/queue.go +++ b/kernel/task/queue.go @@ -115,17 +115,13 @@ var uniqueActions = []string{ AssetContentDatabaseIndexCommit, } -func Contain(action string, moreActions ...string) bool { - actions := append(moreActions, action) - actions = gulu.Str.RemoveDuplicatedElem(actions) - - queueLock.Lock() - for _, task := range taskQueue { - if gulu.Str.Contains(task.Action, actions) { +func ContainIndexTask() bool { + actions := getCurrentActions() + for _, action := range actions { + if gulu.Str.Contains(action, []string{DatabaseIndexFull, DatabaseIndex}) { return true } } - queueLock.Unlock() return false } diff --git a/kernel/treenode/blocktree.go b/kernel/treenode/blocktree.go index 536a141c5..823036acc 100644 --- a/kernel/treenode/blocktree.go +++ b/kernel/treenode/blocktree.go @@ -31,6 +31,7 @@ import ( "github.com/panjf2000/ants/v2" util2 "github.com/siyuan-note/dejavu/util" "github.com/siyuan-note/logging" + "github.com/siyuan-note/siyuan/kernel/task" "github.com/siyuan-note/siyuan/kernel/util" "github.com/vmihailenco/msgpack/v5" ) @@ -504,6 +505,12 @@ func SaveBlockTree(force bool) { blockTreeLock.Lock() defer blockTreeLock.Unlock() + if task.ContainIndexTask() { + //logging.LogInfof("skip saving block tree because indexing") + return + } + //logging.LogInfof("saving block tree") + start := time.Now() if err := os.MkdirAll(util.BlockTreePath, 0755); nil != err { logging.LogErrorf("create block tree dir [%s] failed: %s", util.BlockTreePath, err) diff --git a/kernel/treenode/node.go b/kernel/treenode/node.go index 91b31af85..db65da824 100644 --- a/kernel/treenode/node.go +++ b/kernel/treenode/node.go @@ -598,7 +598,7 @@ func getAttributeViewContent(avID string) (content string) { if nil == cell.Value { continue } - buf.WriteString(cell.Value.String()) + buf.WriteString(cell.Value.String(true)) buf.WriteByte(' ') } } @@ -1050,12 +1050,33 @@ func renderTemplateCol(ial map[string]string, rowValues []*av.KeyValues, tplCont dataModel[rowValue.Key.Name] = time.UnixMilli(v.Date.Content) } } else if av.KeyTypeRollup == v.Type { - if 0 < len(v.Rollup.Contents) && av.KeyTypeNumber == v.Rollup.Contents[0].Type { - // 汇总数字时仅取第一个数字填充模板 - dataModel[rowValue.Key.Name] = v.Rollup.Contents[0].Number.Content + if 0 < len(v.Rollup.Contents) { + var numbers []float64 + var contents []string + for _, content := range v.Rollup.Contents { + if av.KeyTypeNumber == content.Type { + numbers = append(numbers, content.Number.Content) + } else { + contents = append(contents, content.String(true)) + } + } + + if 0 < len(numbers) { + dataModel[rowValue.Key.Name] = numbers + } else { + dataModel[rowValue.Key.Name] = contents + } + } + } else if av.KeyTypeRelation == v.Type { + if 0 < len(v.Relation.Contents) { + var contents []string + for _, content := range v.Relation.Contents { + contents = append(contents, content.String(true)) + } + dataModel[rowValue.Key.Name] = contents } } else { - dataModel[rowValue.Key.Name] = v.String() + dataModel[rowValue.Key.Name] = v.String(true) } } }