dsh-obsidian-notes 0.5.0 → 0.5.2

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/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  **A local Obsidian-style notes pane for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) web UI.** Point it at a folder of Markdown files, then read, edit, search and cite them next to the conversation — without opening Obsidian, without importing anything, and without your notes leaving the machine.
4
4
 
5
- ![The notes pane in action: open the vault tree, browse callouts, edit a note, save it, ask about a selected line](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/hero.gif)
5
+ ![The notes pane in action: open the vault tree, read a callout note, select a passage, ask about it — the composed prompt lands in the DSH composer, gets sent, and the answer comes back in the chat](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/hero.gif?v=2)
6
6
 
7
- [![CI](https://github.com/revive123456/dsh-obsidian-vault/actions/workflows/ci.yml/badge.svg)](https://github.com/revive123456/dsh-obsidian-vault/actions/workflows/ci.yml)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/revive123456/dsh-obsidian-vault/blob/main/LICENSE)
7
+ [![CI](https://github.com/revive123456/dsh-obsidian-notes/actions/workflows/ci.yml/badge.svg)](https://github.com/revive123456/dsh-obsidian-notes/actions/workflows/ci.yml)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/revive123456/dsh-obsidian-notes/blob/main/LICENSE)
9
9
  ![DSH 0.2.1+](https://img.shields.io/badge/DSH-0.2.1%2B-blueviolet)
10
10
 
11
- English | [中文](https://github.com/revive123456/dsh-obsidian-vault/blob/main/README.zh.md)
11
+ English | [中文](https://github.com/revive123456/dsh-obsidian-notes/blob/main/README.zh.md)
12
12
 
13
13
  ---
14
14
 
@@ -40,13 +40,13 @@ to a chat:
40
40
 
41
41
  | Notes tree + docked preview | Callouts, line numbers, badges |
42
42
  | --- | --- |
43
- | ![Sidebar notes tree with the preview dock open](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/screenshot-sidebar.png) | ![Coloured Obsidian callouts with one source line per rendered line](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/screenshot-callouts.png) |
43
+ | ![Sidebar notes tree with the preview dock open](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/screenshot-sidebar.png) | ![Coloured Obsidian callouts with one source line per rendered line](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/screenshot-callouts.png) |
44
44
 
45
45
  | Real tables and fenced code | Edit with a line-numbered source view |
46
46
  | --- | --- |
47
- | ![Table with column alignment and a TypeScript code fence](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/screenshot-markdown.png) | ![Editor with line numbers and Save button](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/screenshot-editing.png) |
47
+ | ![Table with column alignment and a TypeScript code fence](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/screenshot-markdown.png) | ![Editor with line numbers and Save button](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/screenshot-editing.png) |
48
48
 
49
- ![Select a passage and ask about it: path, line numbers and quote are sent with the question](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/screenshot-ask.png)
49
+ ![Select a passage and ask about it: path, line numbers and quote are sent with the question](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/screenshot-ask.png)
50
50
 
51
51
  ## Install
52
52
 
@@ -64,7 +64,7 @@ not enough.
64
64
  2. Search for **`dsh-obsidian-notes`** and install it.
65
65
  3. Restart DeepSeek Harness.
66
66
 
67
- > This path requires the package to be published on npm (target release 0.5.0).
67
+ > This path requires the package to be published on npm (current release **0.5.2**).
68
68
  > If search shows nothing, use one of the two paths below.
69
69
 
70
70
  ### 2. CLI (after the npm release)
@@ -79,8 +79,8 @@ dsh web
79
79
  ### 3. From source (works before the npm release)
80
80
 
81
81
  ```bash
82
- git clone https://github.com/revive123456/dsh-obsidian-vault.git
83
- cd dsh-obsidian-vault
82
+ git clone https://github.com/revive123456/dsh-obsidian-notes.git
83
+ cd dsh-obsidian-notes
84
84
  npm i # runs build.mjs via prepare → lib/client.js
85
85
  dsh plugin --profile web add "$(pwd)" # PowerShell: "$PWD"
86
86
  # restart `dsh web`
@@ -97,7 +97,7 @@ boot.
97
97
 
98
98
  **Uninstall:** `dsh plugin --profile web remove dsh-obsidian-notes`, then restart.
99
99
  If you installed it the old way (manual `cordis.patch.yml` row), see
100
- [docs/legacy-install.md](https://github.com/revive123456/dsh-obsidian-vault/blob/main/docs/legacy-install.md).
100
+ [docs/legacy-install.md](https://github.com/revive123456/dsh-obsidian-notes/blob/main/docs/legacy-install.md).
101
101
 
102
102
  ### First run
103
103
 
@@ -271,13 +271,16 @@ Repository layout:
271
271
  | `src/session-store.js` | Session log discovery and deletion |
272
272
  | `lib/client.js` | Build artifact (gitignored; produced by `npm run build`) |
273
273
  | `cordis.patch.yml` | The bundle layer inserted by `dsh plugin add` |
274
- | `tools/demo/` | Isolated demo environment + headless-Chrome asset pipeline (see [tools/demo/README.md](https://github.com/revive123456/dsh-obsidian-vault/blob/main/tools/demo/README.md)) |
274
+ | `tools/demo/` | Isolated demo environment + headless-Chrome asset pipeline (see [tools/demo/README.md](https://github.com/revive123456/dsh-obsidian-notes/blob/main/tools/demo/README.md)) |
275
275
  | `docs/assets/` | The screenshots and GIF used above |
276
276
 
277
277
  The plugin never modifies the DSH installation: it only contributes a bundle
278
278
  layer plus host/client plugin modules.
279
279
 
280
+ Contributions are welcome through pull requests — `main` is protected and only
281
+ the maintainer can push to it. See [CONTRIBUTING.md](CONTRIBUTING.md).
282
+
280
283
  ## License
281
284
 
282
- [MIT](https://github.com/revive123456/dsh-obsidian-vault/blob/main/LICENSE). See [README.zh.md](https://github.com/revive123456/dsh-obsidian-vault/blob/main/README.zh.md) for the Chinese version and
283
- [docs/troubleshooting.md](https://github.com/revive123456/dsh-obsidian-vault/blob/main/docs/troubleshooting.md) for troubleshooting.
285
+ [MIT](https://github.com/revive123456/dsh-obsidian-notes/blob/main/LICENSE). See [README.zh.md](https://github.com/revive123456/dsh-obsidian-notes/blob/main/README.zh.md) for the Chinese version and
286
+ [docs/troubleshooting.md](https://github.com/revive123456/dsh-obsidian-notes/blob/main/docs/troubleshooting.md) for troubleshooting.
package/README.zh.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  **DeepSeek Harness Web 的本地 Obsidian 风格笔记面板。** 指向一个 Markdown 文件夹,就能在对话旁边浏览、编辑、搜索、引用笔记 —— 不用装 Obsidian、不用导入,笔记也不会离开这台机器。
4
4
 
5
- ![笔记面板演示:打开目录树、浏览 callout、编辑保存、选中一段文字提问](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/hero.gif)
5
+ ![笔记面板演示:打开目录树、阅读 callout 笔记、选中一段文字提问 —— 组合好的 prompt 进入 DSH 输入框、发送后回答出现在对话里](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/hero.gif?v=2)
6
6
 
7
- [![CI](https://github.com/revive123456/dsh-obsidian-vault/actions/workflows/ci.yml/badge.svg)](https://github.com/revive123456/dsh-obsidian-vault/actions/workflows/ci.yml)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/revive123456/dsh-obsidian-vault/blob/main/LICENSE)
7
+ [![CI](https://github.com/revive123456/dsh-obsidian-notes/actions/workflows/ci.yml/badge.svg)](https://github.com/revive123456/dsh-obsidian-notes/actions/workflows/ci.yml)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/revive123456/dsh-obsidian-notes/blob/main/LICENSE)
9
9
  ![DSH 0.2.1+](https://img.shields.io/badge/DSH-0.2.1%2B-blueviolet)
10
10
 
11
- [English](https://github.com/revive123456/dsh-obsidian-vault/blob/main/README.md) | 中文
11
+ [English](https://github.com/revive123456/dsh-obsidian-notes/blob/main/README.md) | 中文
12
12
 
13
13
  ---
14
14
 
@@ -28,13 +28,13 @@ DSH 生态里已经有一个 Obsidian 方向的插件
28
28
 
29
29
  | 侧边栏笔记树 + 右侧预览 | Callout、行号、类型徽标 |
30
30
  | --- | --- |
31
- | ![侧边栏笔记树与右侧预览](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/screenshot-sidebar.png) | ![彩色 Obsidian callout,一行源码对应一行渲染](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/screenshot-callouts.png) |
31
+ | ![侧边栏笔记树与右侧预览](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/screenshot-sidebar.png) | ![彩色 Obsidian callout,一行源码对应一行渲染](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/screenshot-callouts.png) |
32
32
 
33
33
  | 真表格与围栏代码 | 带行号的源码编辑视图 |
34
34
  | --- | --- |
35
- | ![带列对齐的表格与 TypeScript 代码块](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/screenshot-markdown.png) | ![带行号的编辑器与保存按钮](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/screenshot-editing.png) |
35
+ | ![带列对齐的表格与 TypeScript 代码块](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/screenshot-markdown.png) | ![带行号的编辑器与保存按钮](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/screenshot-editing.png) |
36
36
 
37
- ![选中一段文字提问:路径、行号与原文会随问题一起注入](https://raw.githubusercontent.com/revive123456/dsh-obsidian-vault/main/docs/assets/screenshot-ask.png)
37
+ ![选中一段文字提问:路径、行号与原文会随问题一起注入](https://raw.githubusercontent.com/revive123456/dsh-obsidian-notes/main/docs/assets/screenshot-ask.png)
38
38
 
39
39
  ## 安装
40
40
 
@@ -48,7 +48,7 @@ DSH 生态里已经有一个 Obsidian 方向的插件
48
48
  2. 搜索 **`dsh-obsidian-notes`** 并安装;
49
49
  3. 重启 DeepSeek Harness。
50
50
 
51
- > 这条路径要求 npm 上已有该包(目标版本 0.5.0)。搜不到时用下面两种方式。
51
+ > 这条路径要求 npm 上已有该包(当前版本 **0.5.2**)。搜不到时用下面两种方式。
52
52
 
53
53
  ### 2. CLI(npm 发布后)
54
54
 
@@ -62,8 +62,8 @@ dsh web
62
62
  ### 3. 从源码安装(npm 发布前也能用)
63
63
 
64
64
  ```bash
65
- git clone https://github.com/revive123456/dsh-obsidian-vault.git
66
- cd dsh-obsidian-vault
65
+ git clone https://github.com/revive123456/dsh-obsidian-notes.git
66
+ cd dsh-obsidian-notes
67
67
  npm i # 通过 prepare 跑 build.mjs → 生成 lib/client.js
68
68
  dsh plugin --profile web add "$(pwd)" # PowerShell 用 "$PWD"
69
69
  # 重启 `dsh web`
@@ -77,7 +77,7 @@ dsh plugin --profile web add "$(pwd)" # PowerShell 用 "$PWD"
77
77
  > clone + `npm i` + 安装本地路径。
78
78
 
79
79
  **卸载:** `dsh plugin --profile web remove dsh-obsidian-notes`,然后重启。若当年是手工改
80
- `cordis.patch.yml` 装的,见 [docs/legacy-install.md](https://github.com/revive123456/dsh-obsidian-vault/blob/main/docs/legacy-install.md)。
80
+ `cordis.patch.yml` 装的,见 [docs/legacy-install.md](https://github.com/revive123456/dsh-obsidian-notes/blob/main/docs/legacy-install.md)。
81
81
 
82
82
  ### 首次使用
83
83
 
@@ -187,12 +187,15 @@ npm run check:bundle # bundle 清单一致性(patch 文件、module id、fil
187
187
  | `src/session-store.js` | 会话日志发现与删除 |
188
188
  | `lib/client.js` | 构建产物(gitignore;由 `npm run build` 生成) |
189
189
  | `cordis.patch.yml` | `dsh plugin add` 安装后生效的 bundle 层 |
190
- | `tools/demo/` | 隔离 demo 环境 + 无头 Chrome 资产生成流程(见 [tools/demo/README.md](https://github.com/revive123456/dsh-obsidian-vault/blob/main/tools/demo/README.md)) |
190
+ | `tools/demo/` | 隔离 demo 环境 + 无头 Chrome 资产生成流程(见 [tools/demo/README.md](https://github.com/revive123456/dsh-obsidian-notes/blob/main/tools/demo/README.md)) |
191
191
  | `docs/assets/` | 上面用到的截图与 GIF |
192
192
 
193
193
  插件不修改 DSH 安装本身:只贡献一个 bundle 层和 host/client 插件模块。
194
194
 
195
+ 欢迎通过 PR 贡献 —— `main` 已开启分支保护,只有维护者能直接 push。见
196
+ [CONTRIBUTING.md](CONTRIBUTING.md)。
197
+
195
198
  ## 许可
196
199
 
197
- [MIT](https://github.com/revive123456/dsh-obsidian-vault/blob/main/LICENSE)。英文版见 [README.md](https://github.com/revive123456/dsh-obsidian-vault/blob/main/README.md),排障见
198
- [docs/troubleshooting.zh.md](https://github.com/revive123456/dsh-obsidian-vault/blob/main/docs/troubleshooting.zh.md)。
200
+ [MIT](https://github.com/revive123456/dsh-obsidian-notes/blob/main/LICENSE)。英文版见 [README.md](https://github.com/revive123456/dsh-obsidian-notes/blob/main/README.md),排障见
201
+ [docs/troubleshooting.zh.md](https://github.com/revive123456/dsh-obsidian-notes/blob/main/docs/troubleshooting.zh.md)。
package/lib/client.js CHANGED
@@ -1218,6 +1218,13 @@ window.__ModuleLoader__.load({
1218
1218
  askSend: "发送",
1219
1219
  askCancel: "Esc 取消",
1220
1220
  askCap: "针对选中内容提问…({n} 字符)",
1221
+ askChars: "{n} 字符",
1222
+ askCited: "已引用选中片段({n} 字符)",
1223
+ promptLine: "第 {a} 行",
1224
+ promptLines: "第 {a}–{b} 行",
1225
+ promptDefaultQ: "请解释这段内容",
1226
+ promptLong: "(片段较长,共 {n} 字符,请阅读 @文件 第 {a}–{b} 行)",
1227
+ promptQuestion: "【问题】",
1221
1228
  rename: "重命名",
1222
1229
  delete: "删除",
1223
1230
  deleteConfirm: "确认删除该笔记?此操作不可恢复。",
@@ -1348,6 +1355,13 @@ window.__ModuleLoader__.load({
1348
1355
  askSend: "Send",
1349
1356
  askCancel: "Esc to cancel",
1350
1357
  askCap: "Ask about the selection…({n} chars)",
1358
+ askChars: "{n} chars",
1359
+ askCited: "Cited the selected passage ({n} chars)",
1360
+ promptLine: "Line {a}",
1361
+ promptLines: "Lines {a}–{b}",
1362
+ promptDefaultQ: "Please explain this passage",
1363
+ promptLong: "(long selection: {n} chars; read @file lines {a}–{b})",
1364
+ promptQuestion: "Question:",
1351
1365
  rename: "Rename",
1352
1366
  delete: "Delete",
1353
1367
  deleteConfirm: "Delete this note? This cannot be undone.",
@@ -1640,19 +1654,19 @@ window.__ModuleLoader__.load({
1640
1654
  return line;
1641
1655
  }
1642
1656
 
1643
- /** 生成「@路径 + 行数 + > 选中内容 + 【问题】」的注入 prompt(无多余空行) */
1644
- function buildPrompt(absPath, content, span, question) {
1657
+ /** 生成「@路径 + 行数 + > 选中内容 + 问题标签」的注入 prompt(无多余空行)。 */
1658
+ function buildPrompt(t, absPath, content, span, question) {
1645
1659
  const lineA = offsetToLine(content, span.start);
1646
1660
  const lineB = offsetToLine(content, span.end - 1);
1647
- const range = lineA === lineB ? `第 ${lineA} 行` : `第 ${lineA}–${lineB} 行`;
1648
- const q = (question || "").trim() || "请解释这段内容";
1661
+ const range = lineA === lineB ? t("promptLine", { a: lineA }) : t("promptLines", { a: lineA, b: lineB });
1662
+ const q = (question || "").trim() || t("promptDefaultQ");
1649
1663
  const lines = [`@${absPath}`, range];
1650
1664
  if (span.text.length <= SEL_CHAR_LIMIT) {
1651
1665
  for (const l of span.text.split("\n")) lines.push(`> ${l}`);
1652
1666
  } else {
1653
- lines.push(`> (片段较长,共 ${span.text.length} 字符,请阅读 @文件 第 ${lineA}–${lineB} 行)`);
1667
+ lines.push(`> ${t("promptLong", { n: span.text.length, a: lineA, b: lineB })}`);
1654
1668
  }
1655
- lines.push(`【问题】${q}`);
1669
+ lines.push(`${t("promptQuestion")} ${q}`);
1656
1670
  return lines.join("\n");
1657
1671
  }
1658
1672
 
@@ -2029,6 +2043,38 @@ window.__ModuleLoader__.load({
2029
2043
  return `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, "0")}/${String(d.getDate()).padStart(2, "0")}`;
2030
2044
  }
2031
2045
 
2046
+ /**
2047
+ * 会话行次序:与 DSH 内置侧边栏的**默认**排序(`orderBy: 'updated'`,
2048
+ * 「最近更新」)一致。
2049
+ *
2050
+ * 不能直接用 Workspace 的 `sessionIds` 次序:那是**挂载次序**(host 侧
2051
+ * `attachSession` 把新会话 prepend 到数组头),一个很久以前创建、现在仍在
2052
+ * 用的会话会停在旧位置(表现为「当前会话排在倒数第二」)。DSH 默认视图
2053
+ * 的做法是:按会话的 `updatedAt` 降序;时间相同时按 id 升序 —— 官方
2054
+ * `compareSessionRecency` 就是这两个比较,id 作稳定 tie-break 是为了同一份
2055
+ * 数据每次渲染次序稳定,不随 Map 迭代顺序抖动。
2056
+ * @param a - 会话 id。
2057
+ * @param b - 会话 id。
2058
+ * @param byId - 会话 id → 会话摘要(`updatedAt` 为 epoch ms)。
2059
+ * @returns 排序比较值(负 = a 更靠前)。
2060
+ */
2061
+ function compareSessionRecency(a, b, byId) {
2062
+ const ta = byId[a]?.updatedAt ?? Number.NEGATIVE_INFINITY;
2063
+ const tb = byId[b]?.updatedAt ?? Number.NEGATIVE_INFINITY;
2064
+ if (ta !== tb) return tb - ta;
2065
+ return a < b ? -1 : 1;
2066
+ }
2067
+
2068
+ /**
2069
+ * 按「最近更新」排会话 id 列表(不修改入参)。
2070
+ * @param sessionIds - 待排的会话 id。
2071
+ * @param byId - 会话 id → 会话摘要。
2072
+ * @returns 新的已排序数组。
2073
+ */
2074
+ function sortedSessionIds(sessionIds, byId) {
2075
+ return [...sessionIds].sort((a, b) => compareSessionRecency(a, b, byId));
2076
+ }
2077
+
2032
2078
  /**
2033
2079
  * 一个会话及其全部子代理后代(闭包,含自身)。
2034
2080
  * 删父会话必须连子代理日志一起删:父级日志消失后,子会话会变成无法归属的
@@ -2704,7 +2750,9 @@ window.__ModuleLoader__.load({
2704
2750
  return true;
2705
2751
  };
2706
2752
 
2707
- // 分组:工作区 sessionIds 账户为主,cwd === path 兜底;其余入「未分组」
2753
+ // 分组:工作区 sessionIds 账户为主,cwd === path 兜底;其余入「未分组」。
2754
+ // 组内次序**不**沿用 Workspace 的 sessionIds:那是挂载次序(新会话 prepend),
2755
+ // 会让人气正旺的旧会话沉底。统一按「最近更新」排,与 DSH 内置侧边栏默认一致。
2708
2756
  const groups = useMemo(() => {
2709
2757
  const accounted = new Set();
2710
2758
  const list = [];
@@ -2723,11 +2771,12 @@ window.__ModuleLoader__.load({
2723
2771
  accounted.add(sid);
2724
2772
  }
2725
2773
  }
2726
- list.push({ key: w.workspaceId, workspace: w, sessionIds: wsIds });
2774
+ list.push({ key: w.workspaceId, workspace: w, sessionIds: sortedSessionIds(wsIds, byId) });
2727
2775
  }
2728
- const ungrouped = ids
2729
- .filter((sid) => !accounted.has(sid) && visibleSession(sid))
2730
- .sort((a, b) => (byId[b].updatedAt ?? 0) - (byId[a].updatedAt ?? 0));
2776
+ const ungrouped = sortedSessionIds(
2777
+ ids.filter((sid) => !accounted.has(sid) && visibleSession(sid)),
2778
+ byId,
2779
+ );
2731
2780
  return { list, ungrouped };
2732
2781
  }, [items, ids, byId, archived, current, purged]); // eslint-disable-line react-hooks/exhaustive-deps
2733
2782
 
@@ -2782,15 +2831,13 @@ window.__ModuleLoader__.load({
2782
2831
  const q = query.trim().toLowerCase();
2783
2832
  const matched = useMemo(() => {
2784
2833
  if (q === "") return [];
2785
- return ids
2786
- .filter((sid) => {
2787
- if (!visibleSession(sid)) return false;
2788
- const s = byId[sid];
2789
- const title = (s.title ?? "").toLowerCase();
2790
- const display = (s.displayTitle ?? "").toLowerCase();
2791
- return title.includes(q) || display.includes(q);
2792
- })
2793
- .sort((a, b) => (byId[b].updatedAt ?? 0) - (byId[a].updatedAt ?? 0));
2834
+ return sortedSessionIds(ids.filter((sid) => {
2835
+ if (!visibleSession(sid)) return false;
2836
+ const s = byId[sid];
2837
+ const title = (s.title ?? "").toLowerCase();
2838
+ const display = (s.displayTitle ?? "").toLowerCase();
2839
+ return title.includes(q) || display.includes(q);
2840
+ }), byId);
2794
2841
  }, [ids, byId, archived, q, current, purged]);
2795
2842
 
2796
2843
  // ── 批量操作 ──────────────────────────────────────────────────────────────
@@ -3759,9 +3806,9 @@ window.__ModuleLoader__.load({
3759
3806
 
3760
3807
  const submitAsk = useCallback((question) => {
3761
3808
  if (!ask) return;
3762
- const prompt = buildPrompt(ask.basePath, ask.content, ask.span, question);
3809
+ const prompt = buildPrompt(t, ask.basePath, ask.content, ask.span, question);
3763
3810
  if (injectPrompt(prompt)) {
3764
- showToast(`${t("cited")}:已引用选中片段(${ask.span.text.length} 字符)`);
3811
+ showToast(t("askCited", { n: ask.span.text.length }));
3765
3812
  } else {
3766
3813
  try {
3767
3814
  if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
@@ -4064,8 +4111,8 @@ window.__ModuleLoader__.load({
4064
4111
  )
4065
4112
  : createElement("div", { className: "dsh-obs-askbar" },
4066
4113
  createElement("button", { onMouseDown: (e) => e.preventDefault(), onClick: () => { setAskOpen(true); setDraftQ(""); } }, `💬 ${t("ask")}`),
4067
- createElement("button", { onMouseDown: (e) => e.preventDefault(), onClick: () => submitAsk("请解释这段内容") }, `⚡ ${t("explain")}`),
4068
- createElement("span", { className: "dsh-obs-asklen" }, `${ask.span.text.length} 字符`),
4114
+ createElement("button", { onMouseDown: (e) => e.preventDefault(), onClick: () => submitAsk(t("promptDefaultQ")) }, `⚡ ${t("explain")}`),
4115
+ createElement("span", { className: "dsh-obs-asklen" }, t("askChars", { n: ask.span.text.length })),
4069
4116
  ),
4070
4117
  ),
4071
4118
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-obsidian-notes",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Obsidian-style notes panel for DeepSeek Harness Web: vault directory tree, Markdown preview/edit/save, cite-to-chat, batch archive/delete of sessions and workspaces. DSH 内嵌 Obsidian 风格笔记面板:目录树 + Markdown 预览/编辑/保存 + 引用到对话 + 会话/工作区批量操作。",
5
5
  "engines": {
6
6
  "node": "^22.19 || >=24"
@@ -26,11 +26,11 @@
26
26
  ],
27
27
  "repository": {
28
28
  "type": "git",
29
- "url": "git+https://github.com/revive123456/dsh-obsidian-vault.git"
29
+ "url": "git+https://github.com/revive123456/dsh-obsidian-notes.git"
30
30
  },
31
- "homepage": "https://github.com/revive123456/dsh-obsidian-vault#readme",
31
+ "homepage": "https://github.com/revive123456/dsh-obsidian-notes#readme",
32
32
  "bugs": {
33
- "url": "https://github.com/revive123456/dsh-obsidian-vault/issues"
33
+ "url": "https://github.com/revive123456/dsh-obsidian-notes/issues"
34
34
  },
35
35
  "dsh": {
36
36
  "bundle": {
package/src/client.js CHANGED
@@ -121,6 +121,13 @@
121
121
  askSend: "发送",
122
122
  askCancel: "Esc 取消",
123
123
  askCap: "针对选中内容提问…({n} 字符)",
124
+ askChars: "{n} 字符",
125
+ askCited: "已引用选中片段({n} 字符)",
126
+ promptLine: "第 {a} 行",
127
+ promptLines: "第 {a}–{b} 行",
128
+ promptDefaultQ: "请解释这段内容",
129
+ promptLong: "(片段较长,共 {n} 字符,请阅读 @文件 第 {a}–{b} 行)",
130
+ promptQuestion: "【问题】",
124
131
  rename: "重命名",
125
132
  delete: "删除",
126
133
  deleteConfirm: "确认删除该笔记?此操作不可恢复。",
@@ -251,6 +258,13 @@
251
258
  askSend: "Send",
252
259
  askCancel: "Esc to cancel",
253
260
  askCap: "Ask about the selection…({n} chars)",
261
+ askChars: "{n} chars",
262
+ askCited: "Cited the selected passage ({n} chars)",
263
+ promptLine: "Line {a}",
264
+ promptLines: "Lines {a}–{b}",
265
+ promptDefaultQ: "Please explain this passage",
266
+ promptLong: "(long selection: {n} chars; read @file lines {a}–{b})",
267
+ promptQuestion: "Question:",
254
268
  rename: "Rename",
255
269
  delete: "Delete",
256
270
  deleteConfirm: "Delete this note? This cannot be undone.",
@@ -543,19 +557,19 @@
543
557
  return line;
544
558
  }
545
559
 
546
- /** 生成「@路径 + 行数 + > 选中内容 + 【问题】」的注入 prompt(无多余空行) */
547
- function buildPrompt(absPath, content, span, question) {
560
+ /** 生成「@路径 + 行数 + > 选中内容 + 问题标签」的注入 prompt(无多余空行)。 */
561
+ function buildPrompt(t, absPath, content, span, question) {
548
562
  const lineA = offsetToLine(content, span.start);
549
563
  const lineB = offsetToLine(content, span.end - 1);
550
- const range = lineA === lineB ? `第 ${lineA} 行` : `第 ${lineA}–${lineB} 行`;
551
- const q = (question || "").trim() || "请解释这段内容";
564
+ const range = lineA === lineB ? t("promptLine", { a: lineA }) : t("promptLines", { a: lineA, b: lineB });
565
+ const q = (question || "").trim() || t("promptDefaultQ");
552
566
  const lines = [`@${absPath}`, range];
553
567
  if (span.text.length <= SEL_CHAR_LIMIT) {
554
568
  for (const l of span.text.split("\n")) lines.push(`> ${l}`);
555
569
  } else {
556
- lines.push(`> (片段较长,共 ${span.text.length} 字符,请阅读 @文件 第 ${lineA}–${lineB} 行)`);
570
+ lines.push(`> ${t("promptLong", { n: span.text.length, a: lineA, b: lineB })}`);
557
571
  }
558
- lines.push(`【问题】${q}`);
572
+ lines.push(`${t("promptQuestion")} ${q}`);
559
573
  return lines.join("\n");
560
574
  }
561
575
 
@@ -932,6 +946,38 @@
932
946
  return `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, "0")}/${String(d.getDate()).padStart(2, "0")}`;
933
947
  }
934
948
 
949
+ /**
950
+ * 会话行次序:与 DSH 内置侧边栏的**默认**排序(`orderBy: 'updated'`,
951
+ * 「最近更新」)一致。
952
+ *
953
+ * 不能直接用 Workspace 的 `sessionIds` 次序:那是**挂载次序**(host 侧
954
+ * `attachSession` 把新会话 prepend 到数组头),一个很久以前创建、现在仍在
955
+ * 用的会话会停在旧位置(表现为「当前会话排在倒数第二」)。DSH 默认视图
956
+ * 的做法是:按会话的 `updatedAt` 降序;时间相同时按 id 升序 —— 官方
957
+ * `compareSessionRecency` 就是这两个比较,id 作稳定 tie-break 是为了同一份
958
+ * 数据每次渲染次序稳定,不随 Map 迭代顺序抖动。
959
+ * @param a - 会话 id。
960
+ * @param b - 会话 id。
961
+ * @param byId - 会话 id → 会话摘要(`updatedAt` 为 epoch ms)。
962
+ * @returns 排序比较值(负 = a 更靠前)。
963
+ */
964
+ function compareSessionRecency(a, b, byId) {
965
+ const ta = byId[a]?.updatedAt ?? Number.NEGATIVE_INFINITY;
966
+ const tb = byId[b]?.updatedAt ?? Number.NEGATIVE_INFINITY;
967
+ if (ta !== tb) return tb - ta;
968
+ return a < b ? -1 : 1;
969
+ }
970
+
971
+ /**
972
+ * 按「最近更新」排会话 id 列表(不修改入参)。
973
+ * @param sessionIds - 待排的会话 id。
974
+ * @param byId - 会话 id → 会话摘要。
975
+ * @returns 新的已排序数组。
976
+ */
977
+ function sortedSessionIds(sessionIds, byId) {
978
+ return [...sessionIds].sort((a, b) => compareSessionRecency(a, b, byId));
979
+ }
980
+
935
981
  /**
936
982
  * 一个会话及其全部子代理后代(闭包,含自身)。
937
983
  * 删父会话必须连子代理日志一起删:父级日志消失后,子会话会变成无法归属的
@@ -1607,7 +1653,9 @@
1607
1653
  return true;
1608
1654
  };
1609
1655
 
1610
- // 分组:工作区 sessionIds 账户为主,cwd === path 兜底;其余入「未分组」
1656
+ // 分组:工作区 sessionIds 账户为主,cwd === path 兜底;其余入「未分组」。
1657
+ // 组内次序**不**沿用 Workspace 的 sessionIds:那是挂载次序(新会话 prepend),
1658
+ // 会让人气正旺的旧会话沉底。统一按「最近更新」排,与 DSH 内置侧边栏默认一致。
1611
1659
  const groups = useMemo(() => {
1612
1660
  const accounted = new Set();
1613
1661
  const list = [];
@@ -1626,11 +1674,12 @@
1626
1674
  accounted.add(sid);
1627
1675
  }
1628
1676
  }
1629
- list.push({ key: w.workspaceId, workspace: w, sessionIds: wsIds });
1677
+ list.push({ key: w.workspaceId, workspace: w, sessionIds: sortedSessionIds(wsIds, byId) });
1630
1678
  }
1631
- const ungrouped = ids
1632
- .filter((sid) => !accounted.has(sid) && visibleSession(sid))
1633
- .sort((a, b) => (byId[b].updatedAt ?? 0) - (byId[a].updatedAt ?? 0));
1679
+ const ungrouped = sortedSessionIds(
1680
+ ids.filter((sid) => !accounted.has(sid) && visibleSession(sid)),
1681
+ byId,
1682
+ );
1634
1683
  return { list, ungrouped };
1635
1684
  }, [items, ids, byId, archived, current, purged]); // eslint-disable-line react-hooks/exhaustive-deps
1636
1685
 
@@ -1685,15 +1734,13 @@
1685
1734
  const q = query.trim().toLowerCase();
1686
1735
  const matched = useMemo(() => {
1687
1736
  if (q === "") return [];
1688
- return ids
1689
- .filter((sid) => {
1690
- if (!visibleSession(sid)) return false;
1691
- const s = byId[sid];
1692
- const title = (s.title ?? "").toLowerCase();
1693
- const display = (s.displayTitle ?? "").toLowerCase();
1694
- return title.includes(q) || display.includes(q);
1695
- })
1696
- .sort((a, b) => (byId[b].updatedAt ?? 0) - (byId[a].updatedAt ?? 0));
1737
+ return sortedSessionIds(ids.filter((sid) => {
1738
+ if (!visibleSession(sid)) return false;
1739
+ const s = byId[sid];
1740
+ const title = (s.title ?? "").toLowerCase();
1741
+ const display = (s.displayTitle ?? "").toLowerCase();
1742
+ return title.includes(q) || display.includes(q);
1743
+ }), byId);
1697
1744
  }, [ids, byId, archived, q, current, purged]);
1698
1745
 
1699
1746
  // ── 批量操作 ──────────────────────────────────────────────────────────────
@@ -2662,9 +2709,9 @@
2662
2709
 
2663
2710
  const submitAsk = useCallback((question) => {
2664
2711
  if (!ask) return;
2665
- const prompt = buildPrompt(ask.basePath, ask.content, ask.span, question);
2712
+ const prompt = buildPrompt(t, ask.basePath, ask.content, ask.span, question);
2666
2713
  if (injectPrompt(prompt)) {
2667
- showToast(`${t("cited")}:已引用选中片段(${ask.span.text.length} 字符)`);
2714
+ showToast(t("askCited", { n: ask.span.text.length }));
2668
2715
  } else {
2669
2716
  try {
2670
2717
  if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
@@ -2967,8 +3014,8 @@
2967
3014
  )
2968
3015
  : createElement("div", { className: "dsh-obs-askbar" },
2969
3016
  createElement("button", { onMouseDown: (e) => e.preventDefault(), onClick: () => { setAskOpen(true); setDraftQ(""); } }, `💬 ${t("ask")}`),
2970
- createElement("button", { onMouseDown: (e) => e.preventDefault(), onClick: () => submitAsk("请解释这段内容") }, `⚡ ${t("explain")}`),
2971
- createElement("span", { className: "dsh-obs-asklen" }, `${ask.span.text.length} 字符`),
3017
+ createElement("button", { onMouseDown: (e) => e.preventDefault(), onClick: () => submitAsk(t("promptDefaultQ")) }, `⚡ ${t("explain")}`),
3018
+ createElement("span", { className: "dsh-obs-asklen" }, t("askChars", { n: ask.span.text.length })),
2972
3019
  ),
2973
3020
  ),
2974
3021
  );