dsh-tiddlywiki 0.34.0 → 0.34.1
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/docs/CHANGELOG.md +8 -2
- package/docs/features.md +1 -1
- package/docs/usage.md +1 -1
- package/lib/index.js +28 -14
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/host/tools-read.ts +17 -8
- package/src/host/tools-support.ts +1 -1
- package/src/host/tw-api.ts +25 -6
package/docs/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
## 最近版本(详细)
|
|
8
8
|
|
|
9
|
+
- **v0.34.1**(2026-11-06):**修 `tiddlywiki_search` 的「纯标签筛选」**(issue #1,用户实测报告)。
|
|
10
|
+
- **症状**:只传 `tags` / `tag`、不传 `query` 时**三层坏**,一层修完才露出下一层 —— ① `Cannot read properties of undefined (reading 'split')`;② 崩溃修掉后**恒返回 0 条**(标签过滤等于没生效);③ 再修报 `tool "tiddlywiki_search" returned invalid output: value is not lossless JSON`。带 `query` 时一切正常,所以「按标签列出」这条日常用法一直没被任何用例走过。
|
|
11
|
+
- **三处根因**:① `TiddlyWikiClient.search()` 签名写死 `query: string` 且首行裸调 `query.split(…)`,而工具 schema 里 `query` 本就是可选的(`snippetAround` 同源,正文 > 160 字时同样炸);② 空查询的短路 `if (terms.length === 0) return { items: [], total: 0 }` 写在 `wantedTags` 过滤**之前** ⇒ 标签判断永远执行不到;③ 回执里 `query: args.query` 把 `undefined` 回给宿主,DSH 的 lossless-JSON 校验因此判整次调用 invalid output。
|
|
12
|
+
- **修法**(原则:空 query 仍不匹配任何东西,但**有其它过滤条件时不短路**):`search(query: string | undefined)` + `(query ?? '')`;短路条件收窄成「terms 为空**且** tags/since/type/field 都没有」;工具层先把 `args.query ?? ''` 归一,回执与 `snippetAround` 都用归一后的值;回执渲染在无关键词时改写成「按条件列出(按修改时间倒序)」,不再出现空的「搜索「」」。
|
|
13
|
+
- **守门**:`scripts/verify-tools.mjs` 新增一组 E2E(`tags` / 单值 `tag` / 多标签 AND / `tags`+`field:value` / `type`+`tag` / 长正文摘要 / 回执可 JSON 无损往返 / 渲染不含「搜索「」」),并把旧的「空白 query + tag 必须 0 条」断言改判成「**光有空查询**才 0 条」。反向验证:把归一去掉 ⇒ 无损往返断言 + schema 反查(`query 应为 string,实际 undefined`)双双变红;把短路条件还原 ⇒ 新用例与旧的多词 AND 用例变红。
|
|
14
|
+
|
|
9
15
|
- **v0.34.0**(2026-10-27):**移除「发布到微信公众号」功能**(作者裁定:没必要)。
|
|
10
16
|
- **删了什么**:三条路由 `/wechat/{ready,publish,publish/status}`;配置 `wechat{enabled,command,adapter,token,dsn,endpoint}` 与设置页「可选功能:微信公众号发布」整块(含提示词预览里随开关变化的草稿字段);注入提示词里的发布前约定(`PUBLISH_RULE`);三个 gated seed `publish-spec` / `wechat-setup` / `wechat-publish`;`WechatPublishRunner` + 契约层(`wechat-publish.ts` / `wechat-publish-contract.ts`,约 900 行);TW 工具栏「发布到公众号」按钮 bundle 及其 `build-*` / `gen-*` / 三个 `verify-wechat-*` 守门;`tools/wechat/` 的 opencli adapter 全家(`wechat-html.js` / `weixin-flow.js` / `publish-note*` / 安装与回填脚本);文档 `docs/wechat-publish-setup.md` 与设计稿。
|
|
11
17
|
- **顺手拆掉的机制**:seed 的 `gate` 机制随最后一个使用者一起删除 —— `runAllSeeds` 不再有 gate 分支,`SeedContext.wechat` 也没了;seed 注册表 **13 → 10 项**(核心 3 + 起步 2 + 可选 5)。`admin-secrets` 不再打码 `wechat.token`,`STRING_CONFIG_PATHS` / `BOOLEAN_CONFIG_PATHS` 去掉五条 `wechat.*`。客户端 i18n 475 → 467 键,`index.ts` 公共面 −33 个名字(289 个)。
|
|
@@ -45,13 +51,13 @@ v0.33.1 修好了 `key` 配对,但用户实测仍是纯文本,且 devtools
|
|
|
45
51
|
|
|
46
52
|
- **v0.32.0**(2026-10-26):**新增 `.tid` / `.tiddler` 文件的侧栏预览**(⑦ 的第三项)。wiki 文件夹本身就是一堆 `.tid`,而 DSH 的文件预览把未知扩展名当纯文本 —— 打开一个 `.tid` 看到的是 `title: …` 字段行加原始 wikitext,`[[链接]]`/`<<宏>>` 几乎不可读。现在注册一个预览实现:元数据用 `ctx.documentPreviews.register({ id, extensions: ['tid','tiddler'], priority, title, loading: 'text-pages' })`,正文以**同一个 id** 注入 keyed、Session 作用域的 `sidebar.right.tab.document`;正文把 wikitext POST 到本插件自己的 `/dsh-tiddlywiki/render`(铁律 #7 的净化管道)换成真 TW 渲染结果,字段行单独排一行摘要。**绝不白屏**是这个功能的硬约束:拿不到内容 → "等待文件内容…";只有字段没有正文 → 明确说明;渲染失败 → `<pre>` 原文 + 一句"渲染失败,下面是原文"。宿主没有 `documentPreviews` 服务时**什么都不做**(老版本 DSH 不受影响),且元数据与正文必须**成对**(拿不到 slots 就把已注册的元数据撤回,单边注册会让面板空白)。契约是从宿主 `app.asar` 的 `dsh-client-ui-sidebar-documentpreview/lib/client.js` 逐行核实后写的(含 `TextPreview` 的完整 props 签名与 `content` 三态),全部记在 `docs/development.md`。**守门**:新增 `scripts/verify-tid-preview.mjs`(进 `verify:unit`,10 条)——纯解析用例(字段/CRLF/没有字段必须整份当正文/只有字段/空文本)+ **成对注册**(两处必须用同一个 `TID_PREVIEW_ID` 常量、正文必须先 `slots.inject` 再 `register`)+ **绝不白屏**(`<pre>` 兜底、三种空态各自的 `t()` 文案)+ 渲染必须走 `RENDER_ENDPOINT` 且失败回 `null` 不抛 + 可选依赖的静默跳过与回滚。纯逻辑拆在 `client/tid-parse.ts`(宿主 barrel 也要导它,而宿主包里不能出现 `react`),`client/tid-preview.ts` 只放 UI。客户端 i18n 473 → 477 键。
|
|
47
53
|
|
|
48
|
-
- **v0.31.1**(2026-10-26):**新增 `tiddlywiki_export`(笔记落盘,配合 `present` 交付)**。「把这条笔记给我」此前只有两条路:正文贴进回复(长笔记会撞回执预算,`get` 还得分页),或者让人类自己去 TW 里复制;DSH 的 `present` 能交付**文件**,但一直缺「把笔记变成文件」这一步。新工具 `export(title, format?, dir?)`:读一篇笔记 → 按格式落盘 → 回 `{path, bytes, format}`,助手紧接着 `present([{path, description}])` 即可。三种格式:`md`(YAML frontmatter + 正文,默认)、`tid`(TiddlyWiki 原生字段行 + 空行 + 正文)、`json`(完整 tiddler)。三条纪律都不是可选项:① **绝不覆盖同名文件**(导出是旁路产物,静默覆盖等于毁掉上次的交付物);② **`$:/plugins/dsh-tiddlywiki/` 系统条目拒绝导出**(可能是密钥,走铁律 #6 的共享原语);③ 读失败 ≠ 不存在(铁律 #3)。自定义字段两处都收(顶层键 + `fields`)—— 只收一处会让「导出丢字段」。守门 `scripts/verify-tools.mjs` 新增一条 7 组断言的 E2E(三格式内容逐项核对、同名拒绝、不存在回 `notFound` 且不写空文件、系统条目拒绝、相对 `dir` 拒绝),并与既有的「真实返回值必须通过 `output.schema`」自动联动。工具数 16 → 17(AGENTS §2、`verify-tool-views` / `verify-prompt` 的计数断言、`TOOL_VIEW_KEYS` + `TOOL_LABELS` + `i18n-card` 的 `card.toolExport` 同步)。
|
|
49
54
|
|
|
50
55
|
|
|
51
|
-
## 历史版本(一句话,
|
|
56
|
+
## 历史版本(一句话,152 条,新的在上)
|
|
52
57
|
|
|
53
58
|
| 版本 | 日期 | 变更 |
|
|
54
59
|
|---|---|---|
|
|
60
|
+
| v0.31.1 | 2026-10-26 | 新增 `tiddlywiki_export`(笔记落盘:md/tid/json,同名拒绝覆盖、系统条目拒绝)+ 工具数 16→17 |
|
|
55
61
|
| v0.31.0 | 2026-10-26 | 对齐 DSH 0.2.1 的改造(选库入口搬到 `conversation.input.left`、`verify-dsh-contract`、`get` 分页、16 个工具补 `output.schema`、skill 走 `ctx.skills.register`、新增 `/note` 命令、提示词加「先查再答」「时效与去重」) |
|
|
56
62
|
| v0.30.65 | 2026-10-09 | 新增 tiddlywiki_replace 工具(笔记正文局部替换) |
|
|
57
63
|
| v0.30.63 | 2026-10-09 | 默认提示词把「笔记内引用」与「聊天引用」拆成两条链接规则;`config.prompt.override` 设置页说明勘误 |
|
package/docs/features.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
| 🌐 **中英双语界面**(v0.30.6) | 插件自己的界面(FAB / 侧边栏 / 快速笔记 / 工具卡片 / 设置页 / toast)与 TW 子进程**共用同一个 `uiLanguage` 设置**;文案按区域分片成 `i18n-*.ts` 的 `zh`/`en` 目录,守门保证两侧键集合一致、且**客户端代码里不再有硬编码中文文案** |
|
|
25
25
|
| 🤖 **Agent 工具** | 15 个 `tiddlywiki_*` 工具:检索(**相关度排序 + 命中处片段 + 字段过滤**)、读写、**增量追加**、批量、重命名、**软删除/回收站**、**反向链接**、**附件入库**、**知识库体检**、git 同步与冲突解决(v0.30.5 起 `git_sync` **一次同步所有可同步的库**、按仓库去重、逐仓库独立成败;**v0.30.58 起「有远端」= 配了 `git.remote` **或**该库所在仓库自己的 `origin`**,配置为空即回落、回执写明远端与来源;v0.19.0;检索/最近仍在**服务端**排除二进制附件,大 wiki 上从 515MB/17s 降到 ~0.4s) |
|
|
26
26
|
| 🛡️ **不会被覆盖的写入** | 所有写入路径(agent 工具 **与** 快速笔记/编辑器路由)都**先读后写**:不传 tags 就保留原有标签、自定义字段与**内容类型**(`text/css`/wikitext 等不会被重置成 Markdown,v0.20.1);`tiddlywiki_put(..., expectedModified/expectedRevision)` 与 `tiddlywiki_delete(..., expectedModified/expectedRevision)` 乐观并发——读取后若有人(在 TW 编辑器里)改过,写入/删除被拒绝(HTTP 409)而不是静默覆盖或丢进回收站;**给了哪个令牌就必须匹配哪个**(v0.23.5:两个令牌是 AND 不是 OR——`revision` 是 TW 的内存计数器、重启会复位,旧逻辑会因此静默放行跨重启的覆盖);`tiddlywiki_attach` 的同名标题**默认拒绝**(要覆盖必须 `force: true`);`tiddlywiki_delete` 默认**软删除进回收站**,`tiddlywiki_trash` 可恢复(v0.19.0 / v0.19.1 / v0.19.5 / v0.23.5)。**v0.25.0**:`tiddlywiki_append` / `tiddlywiki_rename` / `tiddlywiki_batch_put`(逐条目)也支持并发令牌——它们同样是「读全文 → 整篇回写」,此前**没有令牌可传**,而注入的提示词恰恰让模型回传 `expectedModified`;`tags: []` 现在表示**清空全部标签**(此前被当成「未传」而静默保留,模型按 lint 的 junk-tags 建议去清标签时什么都不会发生);`tiddlywiki_rename` 更新引用时若某个引用者在遍历期间被改动过,**跳过它**并在回执里报出数量,而不是覆盖它 |
|
|
27
|
-
| 🏷 **工作区感知的读写** | **新建**笔记自动带 `ws/<项目名>` 标签 + `workspace` 字段(项目名取自会话工作目录,`note.workspaceMark` 可关);`tiddlywiki_search` 因此默认**先在本项目里找、区内 0 条才自动扩大到全库**并在回执里写明实际范围,`query` 按空白切词、**全部词命中**才算(AND)。工作区只是**附加**标记,绝不改写笔记原有的标签/字段,也**只在新建时**打(v0.24.0
|
|
27
|
+
| 🏷 **工作区感知的读写** | **新建**笔记自动带 `ws/<项目名>` 标签 + `workspace` 字段(项目名取自会话工作目录,`note.workspaceMark` 可关);`tiddlywiki_search` 因此默认**先在本项目里找、区内 0 条才自动扩大到全库**并在回执里写明实际范围,`query` 按空白切词、**全部词命中**才算(AND)。工作区只是**附加**标记,绝不改写笔记原有的标签/字段,也**只在新建时**打(v0.24.0)。**只按标签列笔记**也直接可用(v0.34.1):`query` 可省略,只传 `tags`/`tag`/`field` 就是按条件列出(此前这种调用会崩、修掉崩溃后恒返 0 条) |
|
|
28
28
|
| 🕰 **时效性内容只提示不删** | 阶段性笔记(版本记录 / 部署步骤 / 排期 / 临时方案)可声明 `valid-until`(硬过期)或 `review-after`(该复查),被取代时写 `superseded-by` + `superseded` 标签并**保留旧笔记**;`tiddlywiki_lint` 的 `stale` 检查**只报告**(含「版本号或 done 标签 + 长期未改动」的**候选**,明确标注非判定)——**淘汰永远由人决定**,插件不会自动删除或归档任何笔记(v0.24.0) |
|
|
29
29
|
| 🚰 **重启不丢写入** | 停/重启 TW 只有一条路:`drainThenStop()`——**排干 syncer 队列之后才停子进程**,`/restart`、`/admin/restart`、插件/主题变更后的重启、知识库切换、`/sync`、seed、**以及启动自举路径**(v0.29.0:`pluginAdded` 与 `uiLanguage` 两处曾是裸 `server.restart()`,现在全部走 `instance.restart()`)都经它;排干不完整会在回包里如实报 `drained:false`,而不是假装没事(v0.24.1)。唯一的例外是 `dispose()`(卸载路径按设计不排干,且它只从会先排干的 `stopEntry` 到达)|
|
|
30
30
|
| 🕒 **时间戳不再丢** | 插件写入的每条笔记都会带上 TW 的 `created`/`modified`(17 位紧凑 UTC,与 TW 编辑器逐字节一致):新建 = 两者都取当前时刻,覆盖 = **保留原 `created`、刷新 `modified`**。此前这两个字段被当成「TW 服务端会补」而丢弃,而服务端**从不补**——缺 `modified` 的条目会被 TW 的 `sortTiddlers` 当空串,在 `+[!sort[modified]]` 页面上**直接沉到最后一名**(表现为「新日记没被收录」)。`TiddlyWebClient.put()` 还有一道兜底,任何写路径都不会写出无时间戳的条目(v0.22.5) |
|
package/docs/usage.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
| 工具 | 说明 |
|
|
8
8
|
|---|---|
|
|
9
|
-
| `tiddlywiki_search` | 检索(`query` + 可选 `tags[]/tag/since/type/field/value/limit`),**按相关度排序**(标题命中 > 标签 > 正文命中次数),摘要取自**命中处上下文**而不是正文开头。v0.24.0:`query` 按空白切词、**全部词命中**才算(AND);默认**先在工作区内查、区内 0
|
|
9
|
+
| `tiddlywiki_search` | 检索(`query` + 可选 `tags[]/tag/since/type/field/value/limit`),**按相关度排序**(标题命中 > 标签 > 正文命中次数),摘要取自**命中处上下文**而不是正文开头。v0.24.0:`query` 按空白切词、**全部词命中**才算(AND);默认**先在工作区内查、区内 0 条才自动扩大到全库**,回执里写明实际范围。**v0.34.1:`query` 可以省略** —— 只传 `tags` / `tag` / `since` / `type` / `field(+value)` 时就是「按条件列出」(按修改时间倒序),不再报错、也不再恒返 0 条 |
|
|
10
10
|
| `tiddlywiki_recent` | 最近修改的笔记(倒序,支持 `limit`/`since`),开工快速了解动态 |
|
|
11
11
|
| `tiddlywiki_list_tags` | 现有非系统 tag 及计数(已排除只挂在二进制附件上的 tag);默认列使用最多的 200 个(`limit` 可调、上限 1000),截断时返回 `total`/`truncated` |
|
|
12
12
|
| `tiddlywiki_get` | 读单个 tiddler 全文(`modified` 以 ISO 返回,可直接用作 `expectedModified`) |
|
package/lib/index.js
CHANGED
|
@@ -1123,19 +1123,31 @@ var TiddlyWebClient = class TiddlyWebClient {
|
|
|
1123
1123
|
* `书籍 索引` (with the space) and returned 0 hits while each word matched
|
|
1124
1124
|
* dozens — a query shape every model produces naturally, and one that reads
|
|
1125
1125
|
* as "the knowledge base is empty" rather than as a syntax error.
|
|
1126
|
+
*
|
|
1127
|
+
* v0.34.1 — TAGS-ONLY (issue #1): `query` is OPTIONAL in the tool schema, but
|
|
1128
|
+
* this signature said `string` and the body called `query.split(…)` — so
|
|
1129
|
+
* `tiddlywiki_search({ tags: ['x'] })` died with
|
|
1130
|
+
* `Cannot read properties of undefined (reading 'split')`. Worse, even with
|
|
1131
|
+
* the crash gone, the empty-query short-circuit sat ABOVE `wantedTags`, so a
|
|
1132
|
+
* "list by tag" call could never reach the tag filter and returned 0 rows.
|
|
1133
|
+
* The rule now is: an empty query matches nothing **only when it is the only
|
|
1134
|
+
* filter** — with tags / since / type / field present, "list by condition" is
|
|
1135
|
+
* the whole point (and `for (const term of terms)` simply leaves allTerms
|
|
1136
|
+
* true, so ranking falls back to newest-first).
|
|
1126
1137
|
*/
|
|
1127
1138
|
async search(query, options = {}) {
|
|
1128
1139
|
const items = await this.list(void 0, true);
|
|
1129
|
-
const terms = query.split(/\s+/).map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0);
|
|
1130
|
-
if (terms.length === 0) return {
|
|
1131
|
-
items: [],
|
|
1132
|
-
total: 0
|
|
1133
|
-
};
|
|
1140
|
+
const terms = (query ?? "").split(/\s+/).map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0);
|
|
1134
1141
|
const sinceTime = parseSince(options.since);
|
|
1135
1142
|
const wantedTags = [...options.tags ?? [], options.tag].filter((t) => typeof t === "string" && t.trim().length > 0);
|
|
1136
1143
|
const limit = clampLimit(options.limit, 30);
|
|
1144
|
+
const typeFilter = typeof options.type === "string" && options.type.length > 0 ? options.type : void 0;
|
|
1137
1145
|
const fieldName = typeof options.field === "string" && options.field.length > 0 ? options.field : void 0;
|
|
1138
1146
|
const fieldValue = typeof options.value === "string" ? options.value : void 0;
|
|
1147
|
+
if (terms.length === 0 && wantedTags.length === 0 && sinceTime === void 0 && typeFilter === void 0 && fieldName === void 0) return {
|
|
1148
|
+
items: [],
|
|
1149
|
+
total: 0
|
|
1150
|
+
};
|
|
1139
1151
|
const matched = [];
|
|
1140
1152
|
for (const t of items) {
|
|
1141
1153
|
if (t.title.startsWith("$:/")) continue;
|
|
@@ -1161,7 +1173,7 @@ var TiddlyWebClient = class TiddlyWebClient {
|
|
|
1161
1173
|
const modified = parseTiddlerDate(t.modified);
|
|
1162
1174
|
if (modified === void 0 || modified < sinceTime) continue;
|
|
1163
1175
|
}
|
|
1164
|
-
if (
|
|
1176
|
+
if (typeFilter !== void 0 && (t.type ?? "text/vnd.tiddlywiki") !== typeFilter) continue;
|
|
1165
1177
|
if (wantedTags.length > 0) {
|
|
1166
1178
|
if (!wantedTags.every((w) => lowerTags.includes(w.toLowerCase()))) continue;
|
|
1167
1179
|
}
|
|
@@ -1847,7 +1859,7 @@ function withWorkspaceMark(mark, tags, fields) {
|
|
|
1847
1859
|
* literally. Fall back to the earliest individual term before giving up, or a
|
|
1848
1860
|
* perfectly good hit would still render the head of the note.
|
|
1849
1861
|
*/
|
|
1850
|
-
function snippetAround(text, query, max = 160) {
|
|
1862
|
+
function snippetAround(text, query = "", max = 160) {
|
|
1851
1863
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
1852
1864
|
if (flat.length <= max) return flat;
|
|
1853
1865
|
const lower = flat.toLowerCase();
|
|
@@ -2794,11 +2806,11 @@ function searchTool(env) {
|
|
|
2794
2806
|
const { deps, requireWiki } = env;
|
|
2795
2807
|
return defineTool({
|
|
2796
2808
|
name: "tiddlywiki_search",
|
|
2797
|
-
description: "检索 TiddlyWiki 持久知识库:按关键词(可选 tags 数组 / since 修改时间 / type / field+value / limit)搜索非系统 tiddler
|
|
2809
|
+
description: "检索 TiddlyWiki 持久知识库:按关键词(可选 tags 数组 / since 修改时间 / type / field+value / limit)搜索非系统 tiddler,按相关度排序返回标题、标签、修改时间与命中处上下文片段。**query 可省略**——只传 tags/tag/field/since/type 时就是「按条件列出」(v0.34.1),此时按修改时间倒序。二进制 tiddler(图片等附件,正文为 base64)不参与检索。查询按空白切词、**所有词都必须命中**(AND)。若当前会话属于某个工作区,会**先在该工作区内检索、命中为空才自动扩大到全库**,回执会写明用了哪个范围(传了 tags / tag / field / value 时视为调用方已给出明确范围,不再自动收窄)。",
|
|
2798
2810
|
parameters: {
|
|
2799
2811
|
query: {
|
|
2800
2812
|
type: "string",
|
|
2801
|
-
description: "搜索关键词(大小写不敏感,子串匹配;命中标题/标签/正文,标题命中权重最高)。多词按空白切分,**全部词都要命中**才算(AND)——例如「部署
|
|
2813
|
+
description: "搜索关键词(大小写不敏感,子串匹配;命中标题/标签/正文,标题命中权重最高)。多词按空白切分,**全部词都要命中**才算(AND)——例如「部署 步骤」只返回同时含这两词的笔记。⚠️ 省略 query 且给了 tags/tag/field/since/type 时=按这些条件列出(不是空结果)"
|
|
2802
2814
|
},
|
|
2803
2815
|
tags: {
|
|
2804
2816
|
type: "array",
|
|
@@ -2841,7 +2853,8 @@ function searchTool(env) {
|
|
|
2841
2853
|
let scope = "";
|
|
2842
2854
|
if (value.workspace !== null && value.scope === "workspace") scope = ` · 已在工作区 ws/${value.workspace} 内缩小范围`;
|
|
2843
2855
|
else if (value.workspace !== null && value.fellBack) scope = ` · 工作区 ws/${value.workspace} 内 0 条,已扩大到全库`;
|
|
2844
|
-
const
|
|
2856
|
+
const query = value.query ?? "";
|
|
2857
|
+
const head = query.length > 0 ? `TiddlyWiki 搜索「${query}」${filters.length > 0 ? ` (${filters.join(" · ")})` : ""}:命中 ${value.total} 条${scope}(按相关度排序)。` : `TiddlyWiki 按条件列出${filters.length > 0 ? ` (${filters.join(" · ")})` : ""}:命中 ${value.total} 条${scope}(按修改时间倒序)。`;
|
|
2845
2858
|
const lines = [head];
|
|
2846
2859
|
if (value.results.length === 0) lines.push("没有匹配的 tiddler。");
|
|
2847
2860
|
let shown = 0;
|
|
@@ -2867,6 +2880,7 @@ function searchTool(env) {
|
|
|
2867
2880
|
},
|
|
2868
2881
|
execute: async (args, exec) => {
|
|
2869
2882
|
const wiki = requireWiki(sessionIdOf(exec));
|
|
2883
|
+
const query = args.query ?? "";
|
|
2870
2884
|
const options = {
|
|
2871
2885
|
tags: args.tags,
|
|
2872
2886
|
tag: args.tag,
|
|
@@ -2878,14 +2892,14 @@ function searchTool(env) {
|
|
|
2878
2892
|
};
|
|
2879
2893
|
const mark = (args.tags?.length ?? 0) > 0 || args.tag !== void 0 || args.field !== void 0 || args.value !== void 0 ? void 0 : workspaceMarkFor(deps, exec);
|
|
2880
2894
|
let narrowed;
|
|
2881
|
-
if (mark !== void 0) narrowed = await wiki.search(
|
|
2895
|
+
if (mark !== void 0) narrowed = await wiki.search(query, {
|
|
2882
2896
|
...options,
|
|
2883
2897
|
tags: [mark.tag]
|
|
2884
2898
|
});
|
|
2885
2899
|
const usedWorkspace = narrowed !== void 0 && narrowed.total > 0;
|
|
2886
|
-
const { items, total } = usedWorkspace ? narrowed : await wiki.search(
|
|
2900
|
+
const { items, total } = usedWorkspace ? narrowed : await wiki.search(query, options);
|
|
2887
2901
|
return {
|
|
2888
|
-
query
|
|
2902
|
+
query,
|
|
2889
2903
|
tags: args.tags ?? [],
|
|
2890
2904
|
since: args.since ?? null,
|
|
2891
2905
|
type: args.type ?? null,
|
|
@@ -2899,7 +2913,7 @@ function searchTool(env) {
|
|
|
2899
2913
|
title: t.title,
|
|
2900
2914
|
tags: t.tags ?? [],
|
|
2901
2915
|
modified: toIsoDateString(t.modified),
|
|
2902
|
-
snippet: snippetAround(t.text ?? "",
|
|
2916
|
+
snippet: snippetAround(t.text ?? "", query)
|
|
2903
2917
|
}))
|
|
2904
2918
|
};
|
|
2905
2919
|
}
|