From 1a92dd7dc0d03a052e49431f4efe1d889cc97874 Mon Sep 17 00:00:00 2001 From: Yi Lok Enoch Lam Date: Tue, 4 Aug 2026 12:11:14 +0200 Subject: [PATCH 1/7] feat: add daily note target database i18n strings Co-authored-by: Orca --- app/appearance/langs/en.json | 3 +++ app/appearance/langs/zh-CN.json | 3 +++ app/appearance/langs/zh-TW.json | 3 +++ 3 files changed, 9 insertions(+) diff --git a/app/appearance/langs/en.json b/app/appearance/langs/en.json index d977aef196f..63823773ded 100644 --- a/app/appearance/langs/en.json +++ b/app/appearance/langs/en.json @@ -1791,6 +1791,9 @@ "fileTree13": "For example, /folder1/{{now | date \"20060102150405\"}}, starting with / uses the selected notebook's root path, not starting with / uses the current document's relative path", "fileTree14": "Save path (support date format template variables, such as /Daily Notes/{{now | date \"2006/01\"}}/{{now | date \"2006-01-02\"}})", "fileTree15": "Daily note template path (for example, dailynote.md; leave blank to apply no template; the file needs to be placed under workspace/data/templates/)", + "dailyNoteDatabaseId": "Target database (Attribute View)", + "dailyNoteDatabaseIdHint": "Newly created daily notes will be automatically added as rows to the selected database. Leave empty to disable.", + "dailyNoteDatabasePick": "Select database…", "fileTree16": "Maximum number to list", "fileTree17": "If there are too many sub-docs/tags, this restriction can be used to improve performance", "fileTree18": "Allows creation of sub-documents deeper than 7 levels", diff --git a/app/appearance/langs/zh-CN.json b/app/appearance/langs/zh-CN.json index 39a79c7186e..e70279bcd39 100644 --- a/app/appearance/langs/zh-CN.json +++ b/app/appearance/langs/zh-CN.json @@ -1791,6 +1791,9 @@ "fileTree13": "例如 /folder1/{{now | date \"20060102150405\"}},以 / 开头则使用选中笔记本的根路径,不以 / 开头则使用当前文档相对路径", "fileTree14": "存放路径(可使用日期格式模板变量,例如 /日记/{{now | date \"2006/01\"}}/{{now | date \"2006-01-02\"}})", "fileTree15": "日记模板路径(例如 dailynote.md,留空时不应用模板,需要将该文件放置于工作空间/data/templates/ 下)", + "dailyNoteDatabaseId": "目标数据库(属性视图)", + "dailyNoteDatabaseIdHint": "新建的日记会自动作为行添加到所选数据库(属性视图)。留空则不启用。", + "dailyNoteDatabasePick": "选择数据库…", "fileTree16": "最大列出数量", "fileTree17": "如果子文档/标签非常多,可通过该项限制以提升性能", "fileTree18": "允许创建深度大于 7 层的子文档", diff --git a/app/appearance/langs/zh-TW.json b/app/appearance/langs/zh-TW.json index 1c12349801b..4e6da429591 100644 --- a/app/appearance/langs/zh-TW.json +++ b/app/appearance/langs/zh-TW.json @@ -1791,6 +1791,9 @@ "fileTree13": "例如 /folder1/{{now | date \"20060102150405\"}},以 / 開頭則使用選中筆記本的根路徑,不以 / 開頭則使用當前文檔相對路徑", "fileTree14": "存放路徑(可使用日期格式範本變數,例如 /日記/{{now | date \"2006/01\"}}/{{now | date \"2006-01-02\"}})", "fileTree15": "日記範本路徑(例如 dailynote.md,留空時不套用範本,需要將該檔放置於工作空間/data/templates/ 下)", + "dailyNoteDatabaseId": "目標資料庫(屬性檢視)", + "dailyNoteDatabaseIdHint": "新建立的日記會自動以列的形式加入所選資料庫(屬性檢視)。留空則不啟用。", + "dailyNoteDatabasePick": "選擇資料庫…", "fileTree16": "最大列出數量", "fileTree17": "如果子文檔/標籤非常多,可通過該項限制以提升性能", "fileTree18": "允許建立深度大於 7 層的子文檔", From 58a7eaca9711c16af4076135a45f862fce5441ce Mon Sep 17 00:00:00 2001 From: Yi Lok Enoch Lam Date: Tue, 4 Aug 2026 12:12:14 +0200 Subject: [PATCH 2/7] feat: add dailyNoteDatabaseID to notebook conf Co-authored-by: Orca --- kernel/conf/box.go | 2 ++ kernel/model/import.go | 1 + 2 files changed, 3 insertions(+) diff --git a/kernel/conf/box.go b/kernel/conf/box.go index 0612cc1d6e4..7d4524cd5b7 100644 --- a/kernel/conf/box.go +++ b/kernel/conf/box.go @@ -31,6 +31,7 @@ type BoxConf struct { DocCreateTemplatePath string `json:"docCreateTemplatePath"` // 新建文档使用的模板路径 DailyNoteSavePath string `json:"dailyNoteSavePath"` // 新建日记存储路径 DailyNoteTemplatePath string `json:"dailyNoteTemplatePath"` // 新建日记使用的模板路径 + DailyNoteDatabaseID string `json:"dailyNoteDatabaseID"` // 新建日记自动添加到的目标数据库(属性视图)块 ID SortMode int `json:"sortMode"` // 排序方式 Encrypted bool `json:"encrypted"` // 是否为加密笔记本 BoxCrypt *BoxEncryption `json:"boxCrypt"` // 笔记本加密参数,仅 Encrypted=true 时有值 @@ -51,6 +52,7 @@ func NewBoxConf() *BoxConf { Closed: true, DailyNoteSavePath: "/daily note/{{now | date \"2006/01\"}}/{{now | date \"2006-01-02\"}}", DailyNoteTemplatePath: "", + DailyNoteDatabaseID: "", SortMode: util.SortModeFileTree, Encrypted: false, } diff --git a/kernel/model/import.go b/kernel/model/import.go index 6c5c99d186a..e4d16a977b4 100644 --- a/kernel/model/import.go +++ b/kernel/model/import.go @@ -273,6 +273,7 @@ func importSY(zipPath, boxID, toPath string, createNotebook, autoDetect bool) (c boxConf.DocCreateTemplatePath = importedBoxConf.DocCreateTemplatePath boxConf.DailyNoteSavePath = importedBoxConf.DailyNoteSavePath boxConf.DailyNoteTemplatePath = importedBoxConf.DailyNoteTemplatePath + boxConf.DailyNoteDatabaseID = importedBoxConf.DailyNoteDatabaseID boxConf.SortMode = importedBoxConf.SortMode if err = box.SaveConf(boxConf); err != nil { return createdBoxID, err From 80dedb0f5caf8f3272aafe6c879cf203f51dae9e Mon Sep 17 00:00:00 2001 From: Yi Lok Enoch Lam Date: Tue, 4 Aug 2026 12:14:40 +0200 Subject: [PATCH 3/7] feat: auto-add new daily note to configured target database Co-authored-by: Orca --- kernel/model/file.go | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/kernel/model/file.go b/kernel/model/file.go index 5d19f3e50fa..ab68c7c3428 100644 --- a/kernel/model/file.go +++ b/kernel/model/file.go @@ -1482,9 +1482,54 @@ func CreateDailyNote(boxID string) (p string, existed bool, err error) { sql.FlushQueue() } + addDailyNoteToDatabase(boxConf.DailyNoteDatabaseID, id) + return } +// addDailyNoteToDatabase 将新建的日记文档作为行添加到笔记本配置的目标数据库(属性视图)中。 +// 该功能为尽力而为:配置无效或插入失败时仅记录警告,不影响日记创建。 +func addDailyNoteToDatabase(dbBlockID, docID string) { + if "" == dbBlockID { + return + } + + bt := treenode.GetBlockTree(dbBlockID) + if nil == bt { + logging.LogWarnf("daily note database block [%s] not found, skip adding daily note [%s] to database", dbBlockID, docID) + return + } + + tree, err := LoadTreeByBlockID(bt.RootID) + if err != nil { + logging.LogWarnf("load tree of daily note database block [%s] failed: %s", bt.RootID, err) + return + } + node := treenode.GetNodeInTree(tree, dbBlockID) + if nil == node || ast.NodeAttributeView != node.Type { + logging.LogWarnf("daily note database block [%s] is not an attribute view block, skip adding daily note [%s] to database", dbBlockID, docID) + return + } + avID := node.AttributeViewID + if "" == avID { + logging.LogWarnf("daily note database block [%s] has no attribute view id, skip adding daily note [%s] to database", dbBlockID, docID) + return + } + + // 幂等:文档已经绑定到该数据库时不再重复添加 + if itemID := GetAttributeViewItemIDs(avID, []string{docID})[docID]; "" != itemID { + return + } + + srcs := []map[string]any{{"id": docID, "isDetached": false}} + if err = AddAttributeViewBlock(nil, srcs, avID, dbBlockID, "", "", "", true); err != nil { + logging.LogWarnf("add daily note [%s] to database [%s] failed: %s", docID, avID, err) + return + } + ReloadAttrView(avID) + logging.LogInfof("daily note [%s] added to database [%s]", docID, avID) +} + func GetHPathByPath(boxID, p string) (hPath string, err error) { if "/" == p { hPath = "/" From 6c54a6d37fec5b5d6c4006b4007ab002e21c745f Mon Sep 17 00:00:00 2001 From: Yi Lok Enoch Lam Date: Tue, 4 Aug 2026 12:15:52 +0200 Subject: [PATCH 4/7] feat: validate and persist dailyNoteDatabaseID in setNotebookConf Co-authored-by: Orca --- kernel/api/notebook.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/api/notebook.go b/kernel/api/notebook.go index 16f22064b78..58249ba2c19 100644 --- a/kernel/api/notebook.go +++ b/kernel/api/notebook.go @@ -452,6 +452,9 @@ func setNotebookConf(c *gin.Context) { boxConf.DailyNoteTemplatePath = util.NormalizeTemplatePath(boxConf.DailyNoteTemplatePath) + // 目标数据库(属性视图)块 ID,仅去除首尾空白,允许为空,不在此处校验块是否存在 + boxConf.DailyNoteDatabaseID = strings.TrimSpace(boxConf.DailyNoteDatabaseID) + if err := box.SaveConf(boxConf); err != nil { ret.Code = -1 ret.Msg = err.Error() From 533ac84dff55c1e741bbe565d40f9efa323c641a Mon Sep 17 00:00:00 2001 From: Yi Lok Enoch Lam Date: Tue, 4 Aug 2026 12:19:36 +0200 Subject: [PATCH 5/7] feat: add target database block picker to notebook settings Co-authored-by: Orca --- app/src/menus/onGetnotebookconf.ts | 82 +++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/app/src/menus/onGetnotebookconf.ts b/app/src/menus/onGetnotebookconf.ts index 8924fefaec0..13f46f4af4d 100644 --- a/app/src/menus/onGetnotebookconf.ts +++ b/app/src/menus/onGetnotebookconf.ts @@ -1,5 +1,5 @@ import {Dialog} from "../dialog"; -import {fetchPost} from "../util/fetch"; +import {fetchPost, fetchSyncPost} from "../util/fetch"; import {isMobile} from "../util/functions"; import {escapeHtml} from "../util/escape"; import {writeText} from "../protyle/util/compatibility"; @@ -18,6 +18,7 @@ declare interface INotebookConf { docCreateSaveBox: string; docCreateTemplatePath: string; dailyNoteTemplatePath: string + dailyNoteDatabaseID: string } } @@ -86,6 +87,13 @@ export const onGetnotebookconf = (data: INotebookConf) => {
${window.siyuan.languages.fileTree15}
+
+
${window.siyuan.languages.dailyNoteDatabaseIdHint}
+
+
+ + +
`; if (isMobile()) { openModel({ @@ -122,19 +130,69 @@ const bindSettingEvent = (contentElement: Element, data: INotebookConf) => { refCreateSavePathElement.value = data.conf.refCreateSavePath; const dailyNoteTemplatePathElement = contentElement.querySelector("#dailyNoteTemplatePath") as HTMLInputElement; dailyNoteTemplatePathElement.value = data.conf.dailyNoteTemplatePath; + const dailyNoteDatabaseIdElement = contentElement.querySelector("#dailyNoteDatabaseId") as HTMLInputElement; + dailyNoteDatabaseIdElement.dataset.id = data.conf.dailyNoteDatabaseID || ""; + if (data.conf.dailyNoteDatabaseID) { + fetchSyncPost("/api/av/searchAttributeView", {keyword: "", avID: "", blockID: "", excludes: []}).then((response) => { + const results = (response?.data?.results || []) as Array<{avID: string, avName: string, blockID: string, hPath: string}>; + const result = results.find((item) => item.blockID === data.conf.dailyNoteDatabaseID); + if (result) { + dailyNoteDatabaseIdElement.value = `${result.avName}${result.hPath ? " · " + result.hPath : ""}`; + } else { + dailyNoteDatabaseIdElement.value = data.conf.dailyNoteDatabaseID; + } + }); + } + const dailyNoteDatabasePickElement = contentElement.querySelector("#dailyNoteDatabasePick") as HTMLButtonElement; + const saveConf = () => { + fetchPost("/api/notebook/setNotebookConf", { + notebook: data.box, + conf: { + refCreateSavePath: refCreateSavePathElement.value, + refCreateSaveBox: (contentElement.querySelector("#refCreateSaveBox") as HTMLInputElement).value, + docCreateSaveBox: (contentElement.querySelector("#docCreateSaveBox") as HTMLInputElement).value, + docCreateSavePath: docCreateSavePathElement.value, + docCreateTemplatePath: docCreateTemplatePathElement.value, + dailyNoteSavePath: dailyNoteSavePathElement.value, + dailyNoteTemplatePath: dailyNoteTemplatePathElement.value, + dailyNoteDatabaseID: dailyNoteDatabaseIdElement.dataset.id || "", + } + }); + }; contentElement.querySelectorAll("input, select").forEach((item) => { item.addEventListener("change", () => { - fetchPost("/api/notebook/setNotebookConf", { - notebook: data.box, - conf: { - refCreateSavePath: refCreateSavePathElement.value, - refCreateSaveBox: (contentElement.querySelector("#refCreateSaveBox") as HTMLInputElement).value, - docCreateSaveBox: (contentElement.querySelector("#docCreateSaveBox") as HTMLInputElement).value, - docCreateSavePath: docCreateSavePathElement.value, - docCreateTemplatePath: docCreateTemplatePathElement.value, - dailyNoteSavePath: dailyNoteSavePathElement.value, - dailyNoteTemplatePath: dailyNoteTemplatePathElement.value, - } + saveConf(); + }); + }); + dailyNoteDatabasePickElement.addEventListener("click", () => { + fetchSyncPost("/api/av/searchAttributeView", {keyword: "", avID: "", blockID: "", excludes: []}).then((response) => { + const results = (response?.data?.results || []) as Array<{avID: string, avName: string, blockID: string, hPath: string}>; + const nameById = new Map(results.map(item => [item.blockID, item.avName])); + const listHTML = results.map((item) => { + const location = item.hPath ? `${escapeHtml(item.hPath)}` : ""; + return `
+ + ${escapeHtml(item.avName)}${location} +
`; + }).join(""); + const picker = new Dialog({ + title: window.siyuan.languages.dailyNoteDatabaseId, + content: `
+
+ + ${window.siyuan.languages.clear} +
${listHTML}
`, + width: isMobile() ? "92vw" : "640px", + }); + picker.element.setAttribute("data-key", "dialog-dailynote-database"); + picker.element.querySelectorAll(".b3-list-item").forEach((item) => { + item.addEventListener("click", () => { + const blockID = item.getAttribute("data-id") || ""; + dailyNoteDatabaseIdElement.dataset.id = blockID; + dailyNoteDatabaseIdElement.value = blockID ? (nameById.get(blockID) || blockID) : ""; + saveConf(); + picker.destroy(); + }); }); }); }); From c6bb2cb66656e6e61db39d33840ab3c1ccc021b8 Mon Sep 17 00:00:00 2001 From: Yi Lok Enoch Lam Date: Tue, 4 Aug 2026 12:28:02 +0200 Subject: [PATCH 6/7] test: dailynote database auto-row smoke test Co-authored-by: Orca --- scripts/dailynote-database-smoke.mjs | 251 +++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 scripts/dailynote-database-smoke.mjs diff --git a/scripts/dailynote-database-smoke.mjs b/scripts/dailynote-database-smoke.mjs new file mode 100644 index 00000000000..909749d67d2 --- /dev/null +++ b/scripts/dailynote-database-smoke.mjs @@ -0,0 +1,251 @@ +#!/usr/bin/env node +// Smoke test: dailyNoteDatabaseID feature. +// 1. create notebook A + doc with an Attribute View block, materialize the AV +// 2. set notebook A conf dailyNoteDatabaseID = av block id +// 3. createDailyNote twice (same day): first adds exactly one row bound to the note, second is idempotent +// 4. notebook B without the setting behaves as before +import {spawn} from "node:child_process"; +import fs from "node:fs"; +import net from "node:net"; +import os from "node:os"; +import path from "node:path"; +import process from "node:process"; +import {fileURLToPath} from "node:url"; + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(scriptDir, ".."); +const appDir = path.join(root, "app"); +const kernelBinary = path.join(appDir, "kernel", "SiYuan-Kernel"); +const expectedKernelVersion = JSON.parse(fs.readFileSync(path.join(appDir, "package.json"), "utf8")).version; + +const fail = (message) => { + throw new Error(message); +}; + +const nodeID = () => { + const now = new Date(); + const pad = (value, size = 2) => String(value).padStart(size, "0"); + const stamp = [ + now.getFullYear(), + pad(now.getMonth() + 1), + pad(now.getDate()), + pad(now.getHours()), + pad(now.getMinutes()), + pad(now.getSeconds()), + ].join(""); + const random = Math.random().toString(36).slice(2, 9).padEnd(7, "0"); + return `${stamp}-${random}`; +}; + +const getFreePort = () => new Promise((resolve, reject) => { + const server = net.createServer(); + server.once("error", reject); + server.listen(0, "127.0.0.1", () => { + const address = server.address(); + server.close(() => resolve(address.port)); + }); +}); + +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +const postJSON = async (baseURL, endpoint, body = {}) => { + const response = await fetch(`${baseURL}${endpoint}`, { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify(body), + }); + if (!response.ok) { + fail(`${endpoint} returned HTTP ${response.status}`); + } + const data = await response.json(); + if (data.code !== 0) { + fail(`${endpoint} failed: ${data.msg || JSON.stringify(data)}`); + } + return data.data; +}; + +const waitForBoot = async (baseURL) => { + let lastError = ""; + for (let i = 0; i < 120; i++) { + try { + const version = await postJSON(baseURL, "/api/system/version"); + const progress = await postJSON(baseURL, "/api/system/bootProgress"); + if (version === expectedKernelVersion && progress?.progress >= 100) { + return; + } + lastError = `version=${version} progress=${progress?.progress}`; + } catch (error) { + lastError = error.message; + } + await sleep(500); + } + fail(`kernel did not finish booting: ${lastError}`); +}; + +const main = async () => { + const workspace = fs.mkdtempSync(path.join(os.tmpdir(), "siyuan-dailynote-db-smoke-workspace-")); + const port = await getFreePort(); + const baseURL = `http://127.0.0.1:${port}`; + let kernel; + + try { + kernel = spawn(kernelBinary, [ + "serve", + "--port", String(port), + "--wd", appDir, + "--workspace", workspace, + "--mode", "dev", + "--lang", "zh-TW", + ], { + cwd: root, + stdio: ["ignore", "pipe", "pipe"], + }); + kernel.stdout.on("data", (chunk) => process.stdout.write(chunk)); + kernel.stderr.on("data", (chunk) => process.stderr.write(chunk)); + + await waitForBoot(baseURL); + + // Notebook A: target for daily notes + const notebookAData = await postJSON(baseURL, "/api/notebook/createNotebook", {name: `Dailynote DB Smoke ${Date.now()}`}); + const notebookA = notebookAData.notebook.id || notebookAData.notebook.ID; + if (!notebookA) { + fail(`createNotebook returned no notebook ID: ${JSON.stringify(notebookAData)}`); + } + + // Doc holding the database block + const docID = nodeID(); + const docData = await postJSON(baseURL, "/api/filetree/createDocWithMd", { + notebook: notebookA, + path: "/Dailynote DB", + markdown: "# Dailynote DB\n", + id: docID, + }); + const docIDResult = typeof docData === "string" ? docData : (docData.id || docData.ID); + if (!docIDResult || docIDResult !== docID) { + fail(`createDocWithMd returned unexpected doc id: ${JSON.stringify(docData)}`); + } + + // Attribute View block (table layout) + const avID = nodeID(); + const avBlockID = nodeID(); + await postJSON(baseURL, "/api/block/appendBlock", { + parentID: docID, + dataType: "dom", + data: `
`, + }); + + // Materialize the AV + const initialView = await postJSON(baseURL, "/api/av/renderAttributeView", { + id: avID, + blockID: avBlockID, + pageSize: -1, + createIfNotExist: true, + }); + if (initialView.viewType !== "table") { + fail(`expected table view, got ${initialView.viewType}`); + } + const initialRowCount = initialView.view?.rowCount ?? 0; + if (initialRowCount !== 0) { + fail(`expected 0 rows initially, got ${initialRowCount}`); + } + console.log(`[ok] initial AV rowCount = ${initialRowCount}`); + + // Configure notebook A: target database = av block id + const confData = await postJSON(baseURL, "/api/notebook/setNotebookConf", { + notebook: notebookA, + conf: {dailyNoteDatabaseID: avBlockID}, + }); + if (confData.dailyNoteDatabaseID !== avBlockID) { + fail(`setNotebookConf did not persist dailyNoteDatabaseID: ${JSON.stringify(confData)}`); + } + console.log(`[ok] dailyNoteDatabaseID persisted as ${confData.dailyNoteDatabaseID}`); + + // First daily note creation -> should add exactly one row bound to the note + const firstData = await postJSON(baseURL, "/api/filetree/createDailyNote", {notebook: notebookA}); + const firstDocID = firstData.id; + if (!firstDocID) { + fail(`createDailyNote returned no doc id: ${JSON.stringify(firstData)}`); + } + console.log(`[ok] first daily note created: ${firstDocID}`); + + let view = await postJSON(baseURL, "/api/av/renderAttributeView", { + id: avID, + blockID: avBlockID, + pageSize: -1, + createIfNotExist: false, + }); + let rowCount = view.view?.rowCount ?? 0; + if (rowCount !== 1) { + fail(`expected 1 row after first daily note, got ${rowCount}`); + } + let bound = await postJSON(baseURL, "/api/av/getAttributeViewItemIDsByBoundIDs", { + avID, + blockIDs: [firstDocID], + }); + if (!bound[firstDocID]) { + fail(`daily note not bound as a row: ${JSON.stringify(bound)}`); + } + const firstItemID = bound[firstDocID]; + console.log(`[ok] exactly 1 row after first create, itemID=${firstItemID}`); + + // Second daily note creation (same day, existed) -> must NOT add another row + const secondData = await postJSON(baseURL, "/api/filetree/createDailyNote", {notebook: notebookA}); + if (secondData.id !== firstDocID) { + fail(`second createDailyNote returned a different doc: ${secondData.id} vs ${firstDocID}`); + } + view = await postJSON(baseURL, "/api/av/renderAttributeView", { + id: avID, + blockID: avBlockID, + pageSize: -1, + createIfNotExist: false, + }); + rowCount = view.view?.rowCount ?? 0; + console.log(`[debug] render2 rowCount=${rowCount} rows=${view.view?.rows?.length} viewID=${view.view?.id}`); + console.log(`[debug] render2 view keys: ${Object.keys(view.view || {}).join(",")}`); + const avFile = path.join(workspace, "data", "storage", "av", `${avID}.json`); + if (fs.existsSync(avFile)) { + const avJson = JSON.parse(fs.readFileSync(avFile, "utf8")); + const blockKv = (avJson.keyValues || []).find((kv) => kv.key?.type === "block"); + console.log(`[debug] av json block values: ${JSON.stringify((blockKv?.values || []).map((v) => ({id: v.blockID, bound: v.block?.id, detached: v.isDetached})))}`); + console.log(`[debug] av json views itemIds: ${JSON.stringify((avJson.views || []).map((v) => ({id: v.id, itemIds: v.itemIDs}))) }`); + } else { + console.log(`[debug] av file missing: ${avFile}`); + } + if (rowCount !== 1) { + fail(`expected still 1 row after second create, got ${rowCount}`); + } + bound = await postJSON(baseURL, "/api/av/getAttributeViewItemIDsByBoundIDs", { + avID, + blockIDs: [firstDocID], + }); + if (bound[firstDocID] !== firstItemID) { + fail(`item id changed on re-create: ${bound[firstDocID]} vs ${firstItemID}`); + } + console.log(`[ok] second create idempotent: still 1 row, same itemID`); + + // Notebook B without the setting -> unchanged behaviour + const notebookBData = await postJSON(baseURL, "/api/notebook/createNotebook", {name: `Dailynote No DB ${Date.now()}`}); + const notebookB = notebookBData.notebook.id || notebookBData.notebook.ID; + const plainData = await postJSON(baseURL, "/api/filetree/createDailyNote", {notebook: notebookB}); + if (!plainData.id) { + fail(`createDailyNote without setting failed: ${JSON.stringify(plainData)}`); + } + console.log(`[ok] unset setting: daily note created normally: ${plainData.id}`); + + console.log("SMOKE_TEST_OK: dailyNoteDatabaseID feature verified"); + } finally { + if (kernel) { + kernel.kill("SIGTERM"); + await sleep(500); + if (kernel.exitCode === null) { + kernel.kill("SIGKILL"); + } + } + fs.rmSync(workspace, {recursive: true, force: true}); + } +}; + +main().catch((error) => { + console.error(`SMOKE_TEST_FAILED: ${error.message}`); + process.exit(1); +}); From 76a3dd2f8e473776d3fd5cd57c4e1e745baf2aad Mon Sep 17 00:00:00 2001 From: Yi Lok Enoch Lam Date: Tue, 4 Aug 2026 21:38:30 +0200 Subject: [PATCH 7/7] i18n: simplify daily note target database labels Co-authored-by: Orca --- app/appearance/langs/en.json | 4 ++-- app/appearance/langs/zh-CN.json | 4 ++-- app/appearance/langs/zh-TW.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/appearance/langs/en.json b/app/appearance/langs/en.json index 63823773ded..56d02ab03f2 100644 --- a/app/appearance/langs/en.json +++ b/app/appearance/langs/en.json @@ -1791,8 +1791,8 @@ "fileTree13": "For example, /folder1/{{now | date \"20060102150405\"}}, starting with / uses the selected notebook's root path, not starting with / uses the current document's relative path", "fileTree14": "Save path (support date format template variables, such as /Daily Notes/{{now | date \"2006/01\"}}/{{now | date \"2006-01-02\"}})", "fileTree15": "Daily note template path (for example, dailynote.md; leave blank to apply no template; the file needs to be placed under workspace/data/templates/)", - "dailyNoteDatabaseId": "Target database (Attribute View)", - "dailyNoteDatabaseIdHint": "Newly created daily notes will be automatically added as rows to the selected database. Leave empty to disable.", + "dailyNoteDatabaseId": "Target database", + "dailyNoteDatabaseIdHint": "Storage database", "dailyNoteDatabasePick": "Select database…", "fileTree16": "Maximum number to list", "fileTree17": "If there are too many sub-docs/tags, this restriction can be used to improve performance", diff --git a/app/appearance/langs/zh-CN.json b/app/appearance/langs/zh-CN.json index e70279bcd39..3c58e520b37 100644 --- a/app/appearance/langs/zh-CN.json +++ b/app/appearance/langs/zh-CN.json @@ -1791,8 +1791,8 @@ "fileTree13": "例如 /folder1/{{now | date \"20060102150405\"}},以 / 开头则使用选中笔记本的根路径,不以 / 开头则使用当前文档相对路径", "fileTree14": "存放路径(可使用日期格式模板变量,例如 /日记/{{now | date \"2006/01\"}}/{{now | date \"2006-01-02\"}})", "fileTree15": "日记模板路径(例如 dailynote.md,留空时不应用模板,需要将该文件放置于工作空间/data/templates/ 下)", - "dailyNoteDatabaseId": "目标数据库(属性视图)", - "dailyNoteDatabaseIdHint": "新建的日记会自动作为行添加到所选数据库(属性视图)。留空则不启用。", + "dailyNoteDatabaseId": "目标数据库", + "dailyNoteDatabaseIdHint": "存放数据库", "dailyNoteDatabasePick": "选择数据库…", "fileTree16": "最大列出数量", "fileTree17": "如果子文档/标签非常多,可通过该项限制以提升性能", diff --git a/app/appearance/langs/zh-TW.json b/app/appearance/langs/zh-TW.json index 4e6da429591..ee3afcd9ff2 100644 --- a/app/appearance/langs/zh-TW.json +++ b/app/appearance/langs/zh-TW.json @@ -1791,8 +1791,8 @@ "fileTree13": "例如 /folder1/{{now | date \"20060102150405\"}},以 / 開頭則使用選中筆記本的根路徑,不以 / 開頭則使用當前文檔相對路徑", "fileTree14": "存放路徑(可使用日期格式範本變數,例如 /日記/{{now | date \"2006/01\"}}/{{now | date \"2006-01-02\"}})", "fileTree15": "日記範本路徑(例如 dailynote.md,留空時不套用範本,需要將該檔放置於工作空間/data/templates/ 下)", - "dailyNoteDatabaseId": "目標資料庫(屬性檢視)", - "dailyNoteDatabaseIdHint": "新建立的日記會自動以列的形式加入所選資料庫(屬性檢視)。留空則不啟用。", + "dailyNoteDatabaseId": "目標資料庫", + "dailyNoteDatabaseIdHint": "存放資料庫", "dailyNoteDatabasePick": "選擇資料庫…", "fileTree16": "最大列出數量", "fileTree17": "如果子文檔/標籤非常多,可通過該項限制以提升性能",