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 +17 -14
- package/README.zh.md +17 -14
- package/lib/client.js +71 -24
- package/package.json +4 -4
- package/src/client.js +71 -24
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
|
-

|
|
6
6
|
|
|
7
|
-
[](https://github.com/revive123456/dsh-obsidian-
|
|
7
|
+
[](https://github.com/revive123456/dsh-obsidian-notes/actions/workflows/ci.yml)
|
|
8
|
+
[](https://github.com/revive123456/dsh-obsidian-notes/blob/main/LICENSE)
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
English | [中文](https://github.com/revive123456/dsh-obsidian-
|
|
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
|
-
|  |  |
|
|
44
44
|
|
|
45
45
|
| Real tables and fenced code | Edit with a line-numbered source view |
|
|
46
46
|
| --- | --- |
|
|
47
|
-
|  |  |
|
|
48
48
|
|
|
49
|
-

|
|
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 (
|
|
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-
|
|
83
|
-
cd dsh-obsidian-
|
|
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-
|
|
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-
|
|
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-
|
|
283
|
-
[docs/troubleshooting.md](https://github.com/revive123456/dsh-obsidian-
|
|
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
|
-

|
|
6
6
|
|
|
7
|
-
[](https://github.com/revive123456/dsh-obsidian-
|
|
7
|
+
[](https://github.com/revive123456/dsh-obsidian-notes/actions/workflows/ci.yml)
|
|
8
|
+
[](https://github.com/revive123456/dsh-obsidian-notes/blob/main/LICENSE)
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
[English](https://github.com/revive123456/dsh-obsidian-
|
|
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
|
-
|  |  |
|
|
32
32
|
|
|
33
33
|
| 真表格与围栏代码 | 带行号的源码编辑视图 |
|
|
34
34
|
| --- | --- |
|
|
35
|
-
|  |  |
|
|
36
36
|
|
|
37
|
-

|
|
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
|
|
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-
|
|
66
|
-
cd dsh-obsidian-
|
|
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-
|
|
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-
|
|
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-
|
|
198
|
-
[docs/troubleshooting.zh.md](https://github.com/revive123456/dsh-obsidian-
|
|
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
|
-
/** 生成「@路径 + 行数 + > 选中内容 +
|
|
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 ?
|
|
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(`>
|
|
1667
|
+
lines.push(`> ${t("promptLong", { n: span.text.length, a: lineA, b: lineB })}`);
|
|
1654
1668
|
}
|
|
1655
|
-
lines.push(
|
|
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 =
|
|
2729
|
-
.filter((sid) => !accounted.has(sid) && visibleSession(sid))
|
|
2730
|
-
|
|
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
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
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(
|
|
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("
|
|
4068
|
-
createElement("span", { className: "dsh-obs-asklen" },
|
|
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.
|
|
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-
|
|
29
|
+
"url": "git+https://github.com/revive123456/dsh-obsidian-notes.git"
|
|
30
30
|
},
|
|
31
|
-
"homepage": "https://github.com/revive123456/dsh-obsidian-
|
|
31
|
+
"homepage": "https://github.com/revive123456/dsh-obsidian-notes#readme",
|
|
32
32
|
"bugs": {
|
|
33
|
-
"url": "https://github.com/revive123456/dsh-obsidian-
|
|
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
|
-
/** 生成「@路径 + 行数 + > 选中内容 +
|
|
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 ?
|
|
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(`>
|
|
570
|
+
lines.push(`> ${t("promptLong", { n: span.text.length, a: lineA, b: lineB })}`);
|
|
557
571
|
}
|
|
558
|
-
lines.push(
|
|
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 =
|
|
1632
|
-
.filter((sid) => !accounted.has(sid) && visibleSession(sid))
|
|
1633
|
-
|
|
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
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
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(
|
|
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("
|
|
2971
|
-
createElement("span", { className: "dsh-obs-asklen" },
|
|
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
|
);
|