dsh-tiddlywiki 0.26.5 → 0.26.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +2 -1
- package/cordis.patch.yml +13 -13
- package/lib/index.js +45 -14
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/markdown-editor.ts +128 -128
- package/src/client/state.ts +39 -39
- package/src/client/toast.ts +22 -22
- package/src/host/tools.ts +56 -15
- package/docs/code-review-2026-09-17.md +0 -151
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 dsh-tiddlywiki contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-tiddlywiki contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ dsh plugin --profile web add link:/path/to/dsh-tiddlywiki
|
|
|
101
101
|
| `tiddlywiki_get` | 读单个 tiddler 全文(`modified` 以 ISO 返回,可直接用作 `expectedModified`) |
|
|
102
102
|
| `tiddlywiki_put` | 写/覆盖;**新建**未指定类型时默认 `text/markdown`(`$:/` 系统条目除外),**覆盖既有条目时保留原 `type`/tags/自定义字段**(v0.20.1,改类型要显式 `fields.type`);`expectedModified` + `force` 提供乐观并发保护 |
|
|
103
103
|
| `tiddlywiki_batch_put` | 批量写入(`overwrite=false` 跳过已存在;单条失败不影响其余、逐条报错;4 路并发但结果保持入参顺序) |
|
|
104
|
-
| `tiddlywiki_append` | **增量追加**:`mode=append\|prepend`、`heading=某标题`
|
|
104
|
+
| `tiddlywiki_append` | **增量追加**:`mode=append\|prepend`、`heading=某标题` 定位写入(落点 = 该标题之后、**下一个任意级别标题之前**;CRLF 行尾的笔记同样有效),写日志/批注无需读全文;标题不存在时**不报错但也不再装成功**——追加到文末并返回 `headingMatched=false` + 回执明说;与 `put` 同一套写策略(保留原 `type`/tags/自定义字段,新条目才默认 markdown),支持 `fields` 显式覆盖 |
|
|
105
105
|
| `tiddlywiki_rename` | 重命名 + 尽量同步其他条目里的引用;旧标题删除失败时如实回报「两份副本都在」 |
|
|
106
106
|
| `tiddlywiki_delete` | 删除(幂等)。默认**软删除**进 `$:/dsh-tiddlywiki/trash/`,`permanent=true` 才真删;支持 `expectedModified`/`expectedRevision`/`force` 乐观并发(读后被改动则拒绝,不把人类的新改动丢进回收站) |
|
|
107
107
|
| `tiddlywiki_trash` | 回收站:`action=list\|restore\|empty`(索引读不到或损坏时显式报错,绝不把回收站当空的重建) |
|
|
@@ -419,6 +419,7 @@ lib/ # 预构建产物(发布含 lib/**,提交入库;
|
|
|
419
419
|
|
|
420
420
|
> 最近几个主要版本的一句话记录(完整变更见 [Releases](https://github.com/bbqisbbq/dsh-tiddlywiki/releases) / git log)。
|
|
421
421
|
|
|
422
|
+
- **v0.26.6**(2026-09-26):**修 `tiddlywiki_append` 的 `heading` 在 CRLF 笔记上完全失效、且静默回退到文末**(作者本机知识库的一篇 25KB / 454 行长笔记上连续踩两次后定位到源码)。现象:对这篇笔记连做两次带 `heading` 的 append,内容**全部**落到了文末,而回执照样打印「段落「X」」——看起来完全成功。**根因**:`insertIntoSection` 用 `base.split('\n')`,而 CRLF 正文的**每一行结尾都留着 `\r`**;JS 里 `(.*)` **不匹配 `\r`**、`$` 也**不匹配 `\r` 之前的位置**,于是 `/^#{1,6}\s+(.*)$/` 对**每一个**标题都匹配失败 ⇒ `start < 0` ⇒ 走「追加到文末」的回退分支。**只在 CRLF 笔记上复现**(纯 LF 笔记一切正常),所以它看起来像偶发;落盘文件的「**376 个 CRLF + 78 个裸 LF**」混合行尾正是走过该分支的签名(`lines.join('\n')` 保留每行原有 `\r`、只把新块用 `\n` 接上)。**修复三处**:① 标题匹配前**只**剥掉行尾 `\r`(原文行尾一个字节都不动;**没有**改成 `split(/\r?\n/)`——那会让每次 append 都把整篇重写成 LF,炸出全文件 diff);② 新块按**文档自己的行尾**写入(`base.includes('\r\n')` 判定),CRLF 笔记不再越写越乱;③ `insertIntoSection` 改为返回 `{ text, matched }`,`tiddlywiki_append` 把它作为 **`headingMatched`** 一并返回,未命中时回执改为「⚠️ 未找到标题「X」,已改为追加到文末」——**不再把「定位失败」伪装成「写进了那一节」**(Agent 因此完全无法自检,这是本 bug 最坑的一点)。顺带把 `heading` 的真实落点语义写进 schema:实为「该标题之后、**下一个任意级别标题之前**」,标题后紧跟子标题时落在两者之间(=章节开头),并非 schema 原先暗示的「整节末尾」。守门:`verify-tools` +4 条 E2E(CRLF 正文的笔记必须 `headingMatched=true` 且标记落在目标段内、段内不得混入裸 LF;未命中必须 `headingMatched=false` 且回执含「未找到标题」、不得再宣称写进该段落;命中时的反向保护;wikitext `!` 标题同样可定位),**已反向验证**(撤掉 lib 里的行尾 CR 剥离即红 1 条)。
|
|
422
423
|
- **v0.26.5**(2026-09-23):**修 P0:`fields` 参数被按字符拆成几十个垃圾字段**(用户实测复现两次后报障)。现象:`tiddlywiki_put` / `batch_put` / `append` 传 `fields: {"review-after":"2026-12-22"}` 后,条目上出现 `0={`、`1="`、`2=r`… 几十个**单字符字段**,真正的自定义字段**一个都没写进去**;又因为 `fields.type` 是**改内容类型的唯一入口**,这期间也无法把条目写成 wikitext / `text/css`。**根因两层**:① 参数 schema 声明为 `{type:'json'}`,而 DSH 的 schema 里 `type:'json'` 是**纯注解**——编译后连 `type` 都没有(`src/sdk.ts` 的 `compileValue`),模型收不到「这是个对象」的信息,于是把 JSON 当**字符串**发来;② 新建笔记 + 自动工作区标记走 `withWorkspaceMark()` 的 `{ ...(fields ?? {}) }`,而**展开字符串是按字符展开**的(`{...'{"r"'}` → `{0:'{',1:'"',2:'r'…}`),这些索引键随后被当成自定义字段写入;旧 `applyCustomFields()` 对非对象又是**静默 return**,所以全程没有任何报错。**修复(三处)**:① 三个写工具的 `fields` schema 改成真正的 `object` + `additionalProperties: true`(模型能直接看到类型);② 新增 `normalizeFieldsArg()`(`write-policy.ts`)——对象原样返回、`undefined`/`null`/空串视同未给(保留基底)、**JSON 字符串解析成对象**(兼容仍按字符串发参的模型)、数组/数字/解析不出对象的字符串**明确抛错**;`defineTool` 相应新增 `normalizeArgs` 钩子,让归一化跑在 schema 预校验**之前**(放到之后会被「must be an object」先拦掉,兼容层就没机会生效);③ `applyCustomFields()` 遇到非对象**抛错**而不是静默忽略,`withWorkspaceMark()` 只为已归一化的对象做展开。守门:`verify-write-policy` +5 条纯函数(字符串解析 / 对象原样 / 三种坏输入拒绝 / 字符串在 `buildWriteTiddler` 处被拦 / 归一化后无索引键),`verify-tools` +4 条 E2E(三个工具的 schema 必须 `type:object`、按字符串发参仍写对字段且不产生 `0=` 垃圾、坏输入明确报错且不留半成品条目、`append`/`batch_put` 走同一套归一化)。**注意**:本次修复只影响写入端;已被写进条目的垃圾字段需要重建该条目(`fields` 只能覆盖、不能删除单个字段),本机知识库已扫描确认无残留。
|
|
423
424
|
- **v0.26.4**(2026-09-23):**修「发送给 Agent」因配置被浏览器自动填充而 404 + 设置页文本框防自动填充**。实测:设置页「TW 端请求基址」(`ui.sendToAgent.endpoint`)被浏览器自动完成当成 URL/账号字段填进 `/dsh-tiddlywiki/tw/root`,于是 send-to-agent 把请求拼成 `/dsh-tiddlywiki/tw/root/agent/sessions`,命中 TW 代理子路径 → 404「获取会话列表失败」。**两层加固**:① 设置页 `textField` 一律 `autocomplete=off`(`tokenField` 早已如此,URL 类文本框 `endpoint`/`git.remote`/`wechat.dsn` 等不再被自动填充污染);② send-to-agent bundle `baseEndpoint()` 校验自定义 endpoint——一旦含 `/tw/`、`/api/`、`/recipes/`、`/files/` 等 TW 子路径或非 origin/根绝对路径(判为浏览器误填),**回退到 `location.origin + "/dsh-tiddlywiki"` 自动推导**,按钮不再因误配 endpoint 而 404。另修 `scripts/gen-seed-send-to-agent.mjs` 模板字符串里一处未转义反引号(`ok:false`)导致再生成 seed 必崩的 bug。bundle send-to-agent 0.3.5→0.3.6。
|
|
424
425
|
- **v0.26.3**(2026-09-23):**修正 starter-docs 教程里一处技术性错误说法**:「方法一」原来写「点标签后 TW 会自动生成该标签的页面,列出所有带此标签的笔记」——这不准确。实测(TiddlyWiki 5.4.1 内核,已核对 `$:/config/ViewTemplateBodyFilters.multids` 与 `$:/core/ui/TagTemplate`):内核**没有**"标签页"这种自动页面,点标签丸/侧边栏标签弹出的是**下拉列表**(`list-tagged-draggable`,全量不分页),直接打开以标签命名的条目只会看到条目本身(缺失则提示),不会自动列出成员笔记。教程现在如实描述弹层行为与它的局限(不分页、不可分享链接),并指出想要「一个页面 + 可翻页」的标签列表应自行写汇总页(笔记多时套分页)。纯 seed 文案修正,不影响任何运行时行为;新装 wiki 的「示例与文档」seed 应用新文案,已有 wiki 不受影响(seed 是 ONE-SHOT)。
|
package/cordis.patch.yml
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
# dsh-tiddlywiki bundle patch: inserts the plugin row into the web profile
|
|
2
|
-
# roster. Applied as a profile bundle layer (the `dsh.bundle.patch` manifest
|
|
3
|
-
# field) over dsh-base; activate with
|
|
4
|
-
# `dsh plugin --profile web add link:<path-to-this-package>` and restart dsh web.
|
|
5
|
-
#
|
|
6
|
-
# The row is a bare plugin by package name: the node half (exports ".") runs
|
|
7
|
-
# in the host process (WikiServer, routes, tiddlywiki_* tools, prompt section,
|
|
8
|
-
# auto-commit), and the `dsh.client` declaration in package.json makes the
|
|
9
|
-
# browser half (exports "./client", served at /plugins/dsh-tiddlywiki/client.js)
|
|
10
|
-
# load in the web GUI.
|
|
11
|
-
- insert:
|
|
12
|
-
- id: dsh-tiddlywiki
|
|
13
|
-
name: dsh-tiddlywiki
|
|
1
|
+
# dsh-tiddlywiki bundle patch: inserts the plugin row into the web profile
|
|
2
|
+
# roster. Applied as a profile bundle layer (the `dsh.bundle.patch` manifest
|
|
3
|
+
# field) over dsh-base; activate with
|
|
4
|
+
# `dsh plugin --profile web add link:<path-to-this-package>` and restart dsh web.
|
|
5
|
+
#
|
|
6
|
+
# The row is a bare plugin by package name: the node half (exports ".") runs
|
|
7
|
+
# in the host process (WikiServer, routes, tiddlywiki_* tools, prompt section,
|
|
8
|
+
# auto-commit), and the `dsh.client` declaration in package.json makes the
|
|
9
|
+
# browser half (exports "./client", served at /plugins/dsh-tiddlywiki/client.js)
|
|
10
|
+
# load in the web GUI.
|
|
11
|
+
- insert:
|
|
12
|
+
- id: dsh-tiddlywiki
|
|
13
|
+
name: dsh-tiddlywiki
|
package/lib/index.js
CHANGED
|
@@ -10316,22 +10316,44 @@ function rewriteRefs(text, oldTitle, newTitle) {
|
|
|
10316
10316
|
};
|
|
10317
10317
|
}
|
|
10318
10318
|
/**
|
|
10319
|
-
* Insert `addition`
|
|
10320
|
-
*
|
|
10321
|
-
*
|
|
10322
|
-
*
|
|
10319
|
+
* Insert `addition` into the section introduced by `heading` (Markdown `#`/`##`…
|
|
10320
|
+
* or wikitext `!`/`!!`…). Used by tiddlywiki_append so an agent can add to one
|
|
10321
|
+
* section of a long note without rewriting the whole file.
|
|
10322
|
+
*
|
|
10323
|
+
* **落点语义**:插在「该标题之后、**下一个任意级别的标题**之前」。所以当标题后面
|
|
10324
|
+
* 紧跟子标题时(`## 4. 定时任务` + `### 槽位表`),内容落在两者**之间**,也就是
|
|
10325
|
+
* 「章节开头」而不是「整节末尾」——这一点以前只在 schema 里写成「段落的末尾」,
|
|
10326
|
+
* 容易让调用方误会(v0.26.6 起描述与回执都写清楚了)。
|
|
10327
|
+
*
|
|
10328
|
+
* **返回 `matched`**:`false` = 没找到该标题,文本被追加到**文末**。调用方**必须**
|
|
10329
|
+
* 把它透出去(v0.26.6 之前这个回退是静默的,回执照样打「段落「X」」,
|
|
10330
|
+
* 于是一次定位失败看起来像成功——Agent 没有任何办法自检)。
|
|
10331
|
+
*
|
|
10332
|
+
* **CRLF(v0.26.6 修复)**:tiddler 正文可能是 CRLF(Windows 上写的/同步来的笔记),
|
|
10333
|
+
* 而 `split('\n')` 会让每行结尾都留着 `\r`。JS 里 `(.*)` 不匹配 `\r`、`$` 也不匹配
|
|
10334
|
+
* `\r` 之前的位置,于是 `/^#{1,6}\s+(.*)$/` 对**每一个**标题都匹配失败 ⇒ 所有带
|
|
10335
|
+
* `heading` 的 append 都静默落到了文末。**只在 CRLF 笔记上复现**(纯 LF 笔记一切正常),
|
|
10336
|
+
* 这就是它像「偶发」的原因。现在只在**匹配时**剥掉行尾 CR,原文行尾一个字节都不动;
|
|
10337
|
+
* 并且新块用**文档自己的行尾**写入,避免 CRLF 笔记被越写越乱(混入 LF 块)。
|
|
10323
10338
|
*/
|
|
10324
10339
|
function insertIntoSection(base, heading, addition) {
|
|
10325
10340
|
const lines = base.split("\n");
|
|
10326
10341
|
const headingText = (line) => {
|
|
10327
|
-
const
|
|
10342
|
+
const s = line.replace(/\r$/, "");
|
|
10343
|
+
const md = /^#{1,6}\s+(.*)$/.exec(s);
|
|
10328
10344
|
if (md !== null) return (md[1] ?? "").trim();
|
|
10329
|
-
const tw = /^!{1,6}\s*(.*)$/.exec(
|
|
10345
|
+
const tw = /^!{1,6}\s*(.*)$/.exec(s);
|
|
10330
10346
|
if (tw !== null) return (tw[1] ?? "").trim();
|
|
10331
10347
|
return null;
|
|
10332
10348
|
};
|
|
10333
10349
|
const start = lines.findIndex((line) => headingText(line) === heading);
|
|
10334
|
-
|
|
10350
|
+
const eol = base.includes("\r\n") ? "\r\n" : "\n";
|
|
10351
|
+
const adapt = (s) => eol === "\n" ? s.replace(/\r\n/g, "\n") : s.replace(/\r\n/g, "\n").split("\n").join("\r\n");
|
|
10352
|
+
const block = adapt(addition);
|
|
10353
|
+
if (start < 0) return {
|
|
10354
|
+
text: base.trim().length === 0 ? block : `${base.replace(/\s+$/, "")}${eol}${eol}${block}`,
|
|
10355
|
+
matched: false
|
|
10356
|
+
};
|
|
10335
10357
|
let end = lines.length;
|
|
10336
10358
|
for (let i = start + 1; i < lines.length; i++) if (headingText(lines[i] ?? "") !== null) {
|
|
10337
10359
|
end = i;
|
|
@@ -10339,8 +10361,11 @@ function insertIntoSection(base, heading, addition) {
|
|
|
10339
10361
|
}
|
|
10340
10362
|
const before = lines.slice(0, end).join("\n").replace(/\s+$/, "");
|
|
10341
10363
|
const after = lines.slice(end).join("\n");
|
|
10342
|
-
const head = `${before}
|
|
10343
|
-
return
|
|
10364
|
+
const head = `${before}${eol}${eol}${block}`;
|
|
10365
|
+
return {
|
|
10366
|
+
text: after.trim().length === 0 ? head : `${head}${eol}${eol}${after.replace(/^\s+/, "")}`,
|
|
10367
|
+
matched: true
|
|
10368
|
+
};
|
|
10344
10369
|
}
|
|
10345
10370
|
function registerTiddlywikiTools(ctx, deps) {
|
|
10346
10371
|
const disposers = [];
|
|
@@ -11110,7 +11135,7 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
11110
11135
|
}));
|
|
11111
11136
|
register(defineTool({
|
|
11112
11137
|
name: "tiddlywiki_append",
|
|
11113
|
-
description: "向已有 tiddler 追加/前插文本,或写入指定标题段落的末尾(无需先读全文、不会整篇覆盖)——适合日志、批注、清单的增量写入。条目不存在时默认新建(createIfMissing=false 则报错)。**写入既有条目走与 tiddlywiki_put 同一套写策略**:原有 tags、自定义字段与**内容类型**全部保留(不会把 Markdown 笔记改成 wikitext、也不会把 CSS 改成 Markdown);可用 fields 显式覆盖字段/类型。新建条目未指定内容类型时才默认 text/markdown(并像 tiddlywiki_put 一样自动补 agent-written 与 `ws/<项目名>` 工作区标记)。⚠️ 本工具内部是「读全文 → 整篇回写」,属于覆盖写入:改前请先 `tiddlywiki_get`,并把读到的 `modified` 作为 `expectedModified` 传回(v0.25.0 起支持),否则人类在 TW
|
|
11138
|
+
description: "向已有 tiddler 追加/前插文本,或写入指定标题段落的末尾(无需先读全文、不会整篇覆盖)——适合日志、批注、清单的增量写入。条目不存在时默认新建(createIfMissing=false 则报错)。**写入既有条目走与 tiddlywiki_put 同一套写策略**:原有 tags、自定义字段与**内容类型**全部保留(不会把 Markdown 笔记改成 wikitext、也不会把 CSS 改成 Markdown);可用 fields 显式覆盖字段/类型。新建条目未指定内容类型时才默认 text/markdown(并像 tiddlywiki_put 一样自动补 agent-written 与 `ws/<项目名>` 工作区标记)。⚠️ 本工具内部是「读全文 → 整篇回写」,属于覆盖写入:改前请先 `tiddlywiki_get`,并把读到的 `modified` 作为 `expectedModified` 传回(v0.25.0 起支持),否则人类在 TW 编辑器里的并发修改会被静默回滚。带 `heading` 时返回 `headingMatched`:`false` = 没找到该标题、文本已追加到**文末**(CRLF 行尾的笔记也能正确定位)。",
|
|
11114
11139
|
parameters: {
|
|
11115
11140
|
title: {
|
|
11116
11141
|
type: "string",
|
|
@@ -11129,7 +11154,7 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
11129
11154
|
},
|
|
11130
11155
|
heading: {
|
|
11131
11156
|
type: "string",
|
|
11132
|
-
description: "可选:append
|
|
11157
|
+
description: "可选:append 时改为写入指定标题的位置(Markdown # 或 wikitext ! 标题,按标题文本匹配、忽略标题级别)。**落点 = 该标题之后、下一个任意级别标题之前**:若该标题后紧跟子标题,内容插在两者之间(即章节开头),**不是整节末尾**。标题不存在时不报错,改为追加到文末,并在回执与返回的 headingMatched=false 里明确说明(别把「没命中」当「写进去了」)"
|
|
11133
11158
|
},
|
|
11134
11159
|
createIfMissing: {
|
|
11135
11160
|
type: "boolean",
|
|
@@ -11160,7 +11185,8 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
11160
11185
|
},
|
|
11161
11186
|
normalizeArgs: normalizeFieldsInArgs,
|
|
11162
11187
|
output: { render: (_args, value) => {
|
|
11163
|
-
const
|
|
11188
|
+
const headingNote = value.heading === null ? "" : value.headingMatched === false ? ` · ⚠️ 未找到标题「${value.heading}」,已改为追加到文末` : ` · 段落「${value.heading}」`;
|
|
11189
|
+
const lines = [`${value.created ? "已新建并写入" : "已增量写入"} tiddler「${value.title}」(${value.mode}${headingNote}):新增 ${value.added} 字符,现共 ${value.total} 字符。`];
|
|
11164
11190
|
if (value.type !== null) lines.push(`类型: ${value.type}${value.typeDefaulted === true ? "(新建且未指定,已默认 markdown)" : ""}`);
|
|
11165
11191
|
if (value.typeChanged !== void 0) lines.push(`⚠️ 内容类型已从 ${value.typeChanged.from} 改为 ${value.typeChanged.to}`);
|
|
11166
11192
|
if (value.workspace !== void 0) lines.push(`已自动标记工作区: ws/${value.workspace}(字段 ${WORKSPACE_FIELD})`);
|
|
@@ -11184,8 +11210,12 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
11184
11210
|
const base = existing?.text ?? "";
|
|
11185
11211
|
const addition = args.text;
|
|
11186
11212
|
let next;
|
|
11187
|
-
|
|
11188
|
-
|
|
11213
|
+
let headingMatched;
|
|
11214
|
+
if (mode === "append" && typeof args.heading === "string" && args.heading.trim().length > 0) {
|
|
11215
|
+
const placed = insertIntoSection(base, args.heading.trim(), addition);
|
|
11216
|
+
next = placed.text;
|
|
11217
|
+
headingMatched = placed.matched;
|
|
11218
|
+
} else if (mode === "prepend") next = base.trim().length === 0 ? addition : `${addition}\n\n${base}`;
|
|
11189
11219
|
else next = base.trim().length === 0 ? addition : `${base.replace(/\s+$/, "")}\n\n${addition}`;
|
|
11190
11220
|
const appendMarked = existing === void 0 && !title.startsWith("$:/") ? withWorkspaceMark(workspaceMarkFor(deps, exec), normalizeTagArg(args.tags), args.fields) : {
|
|
11191
11221
|
tags: normalizeTagArg(args.tags),
|
|
@@ -11203,6 +11233,7 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
11203
11233
|
title,
|
|
11204
11234
|
mode,
|
|
11205
11235
|
heading: mode === "append" && typeof args.heading === "string" && args.heading.trim().length > 0 ? args.heading.trim() : null,
|
|
11236
|
+
...headingMatched !== void 0 ? { headingMatched } : {},
|
|
11206
11237
|
created: existing === void 0,
|
|
11207
11238
|
added: addition.length,
|
|
11208
11239
|
total: next.length,
|