@ryuu-64/dsh-find-all 0.1.5 → 0.1.7
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/CHANGELOG.md +21 -0
- package/README.md +14 -10
- package/lib/client.js +324 -103
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.7
|
|
4
|
+
|
|
5
|
+
- Use the host-native icon button for the conversation search entry, including theme, hover and keyboard-focus styles.
|
|
6
|
+
- Clarify that search covers loaded conversation content and can continue loading available earlier history; status text no longer implies complete history when the host cannot provide it.
|
|
7
|
+
- Retain the current match during history loading when its identity can still be verified.
|
|
8
|
+
|
|
9
|
+
The runtime code is unchanged from the accepted PR #17 merge (`f945fb9ccd581c808bc7f50128c3a7d7d2fca6da`). The existing 195 checks, release-artifact verification and RC2 Web acceptance passed; the user also confirmed their real-device test passed before this release. The exact supported host-version list is unchanged.
|
|
10
|
+
|
|
11
|
+
## 0.1.6
|
|
12
|
+
|
|
13
|
+
Fix text matching, history feedback and search state after the 0.1.5 compatibility release.
|
|
14
|
+
|
|
15
|
+
- Match phrases across inline text and syntax-highlighting spans within one text block, while keeping separate messages, block elements, `<br>` elements and excluded content apart.
|
|
16
|
+
- Preserve original UTF-16 offsets during case-insensitive Unicode matching, so characters such as `İ` no longer shift later highlights. Matching remains literal, without locale-specific folding or normalization.
|
|
17
|
+
- Report “已搜索当前可用历史” when the host stops offering earlier pages. Recheck readiness and the final results before showing that status; it does not guarantee that the host's history is complete.
|
|
18
|
+
- Keep the last query in the active plugin instance so reopening the bar can restore it. A valid new text selection takes priority; disabling the plugin, refreshing or restarting clears the query.
|
|
19
|
+
- Preserve a verifiable current match when history is prepended or the page rescans, without scrolling automatically. Restore it across replaced text nodes only when the original block text is unchanged; otherwise clamp the previous result index. A new query or conversation selects the first result.
|
|
20
|
+
- Add regression coverage for observer cleanup, conversation and embedded-panel isolation, cross-node text matching, history feedback, query retention and stable selection.
|
|
21
|
+
|
|
22
|
+
The supported host-version list is unchanged. This patch does not add new Web or Desktop host-matrix acceptance evidence; the 0.1.5 compatibility notes and their documented limits still apply.
|
|
23
|
+
|
|
3
24
|
## 0.1.5
|
|
4
25
|
|
|
5
26
|
Fix compatibility with newer DSH hosts while preserving the existing find controls and matching behavior.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-find-all
|
|
2
2
|
|
|
3
|
-
DSH Desktop 的 **⌘F / Ctrl+F
|
|
3
|
+
DSH Desktop 的 **⌘F / Ctrl+F 查找**插件。它有一个和浏览器查找不一样的地方:**会向宿主请求更早的历史,不只搜索当前已经显示出来的部分。**
|
|
4
4
|
|
|
5
5
|
## 为什么需要它
|
|
6
6
|
|
|
@@ -8,17 +8,19 @@ DSH 桌面窗口是 Electron 壳,本身没有浏览器那种查找栏。而会
|
|
|
8
8
|
|
|
9
9
|
所以拿普通查找条搜一个较早的词,结果永远是 0,哪怕这段会话里确实有。
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
这个插件在你输入关键词之后,通过会话服务把更早的历史一页页加载进来,每加载一页就重搜一次。宿主不再提供更早页时,最后重搜并提示“已搜索当前可用历史”。宿主公开接口不能证明历史完整性,插件不会把终止信号当作“全部搜完”。
|
|
12
12
|
|
|
13
13
|
## 功能
|
|
14
14
|
|
|
15
|
-
- **⌘F / Ctrl+F**
|
|
16
|
-
-
|
|
17
|
-
-
|
|
15
|
+
- **⌘F / Ctrl+F** 唤起查找栏,优先带入有效的选中文字;没有新选词时恢复上次关键词。关键词仅保留在当前插件实例内存中,禁用、刷新或重启后清空
|
|
16
|
+
- **范围切换**:`包含更早内容`(默认)↔ `已加载内容`,点那个胶囊按钮切换
|
|
17
|
+
- **已加载内容**:只搜索当前会话中已经加载到页面的内容,包括滚动区域外的内容;不会继续加载历史,也不会移除先前已经加载的内容
|
|
18
|
+
- **边搜边加载**:“包含更早内容”模式下自动分页加载历史,高亮与计数随加载实时更新
|
|
18
19
|
- **进度可见**:显示「正在加载更早的历史… 已 3 页」,**点一下这段文字即可停止加载**
|
|
19
20
|
- **安全上限**:最多加载 400 页(约 2 万条消息);历史不再增长时会自行停止
|
|
20
21
|
- **导航**:Enter / Shift+Enter 上下一个;⌘G(Ctrl+G)/ F3 下一个,加 Shift 反向;Esc 关闭查找栏并清除高亮
|
|
21
|
-
-
|
|
22
|
+
- 切换会话时清除旧结果,并在新会话里重搜同一关键词
|
|
23
|
+
- 后台重扫首次找到结果时选中第一项;前插历史时优先保留可确认的当前命中,不自动滚动。原节点可对应,或同一正文块仍在且原文完全未变时,可在 Text/span 换树后恢复当前项;无法确认时把旧序号夹到新结果范围,换词或换会话则选第一项
|
|
22
24
|
|
|
23
25
|
## 安装
|
|
24
26
|
|
|
@@ -53,7 +55,7 @@ dsh plugin --profile desktop remove dsh-find-bar
|
|
|
53
55
|
| 打开查找栏 | `⌘F` / `Ctrl+F` |
|
|
54
56
|
| 下一个 / 上一个命中 | `Enter` / `Shift+Enter` |
|
|
55
57
|
| 下一个 / 上一个命中(另一套) | `⌘G`(`Ctrl+G`)/ `F3`,加 `Shift` 反向 |
|
|
56
|
-
| 切换范围
|
|
58
|
+
| 切换范围 `包含更早内容` ↔ `已加载内容` | 点查找栏上的胶囊按钮 |
|
|
57
59
|
| 停止加载更早的历史 | 点「正在加载更早的历史…」那行文字 |
|
|
58
60
|
| 关闭并清除高亮 | `Esc` |
|
|
59
61
|
|
|
@@ -62,9 +64,11 @@ dsh plugin --profile desktop remove dsh-find-bar
|
|
|
62
64
|
## 已知限制
|
|
63
65
|
|
|
64
66
|
- 大小写不敏感,没有「区分大小写」开关
|
|
65
|
-
-
|
|
67
|
+
- 单次搜索仅显示前 5000 处匹配
|
|
66
68
|
- 折叠起来的工具调用和思考块,正文不在页面里,搜不到——先展开再搜
|
|
67
|
-
-
|
|
69
|
+
- “包含更早内容”模式要真的翻历史,所以很长的会话首次搜索需要等几秒
|
|
70
|
+
- 宿主尚未就绪、公开报错、无进展或到达上限时,保留已找到的结果并提示未完成;再次打开查找或输入关键词可手动重试。停止、关闭、换词或切换会话会取消插件任务,但不会中断宿主已经发出的请求
|
|
71
|
+
- “已搜索当前可用历史”不保证历史完整。宿主可能在正常末页和不连续页错误时都不再提供更早页;此时重新查找也不保证恢复缺失历史
|
|
68
72
|
|
|
69
73
|
## 卸载 / 回退
|
|
70
74
|
|
|
@@ -79,7 +83,7 @@ dsh plugin --profile desktop add dsh-find-bar # 想换回上游那
|
|
|
79
83
|
|
|
80
84
|
## 关于这个项目
|
|
81
85
|
|
|
82
|
-
本插件 fork 自 [secyborg/dsh-find-bar](https://github.com/secyborg/dsh-find-bar)(作者 secyborg,MIT
|
|
86
|
+
本插件 fork 自 [secyborg/dsh-find-bar](https://github.com/secyborg/dsh-find-bar)(作者 secyborg,MIT),在上游的基础上加了自动加载更早内容的会话搜索。上游原有的查找条界面、高亮、`n/N` 计数和键盘导航都保留了下来。
|
|
83
87
|
|
|
84
88
|
版权与许可为 MIT 双署名:`Copyright (c) 2026 secyborg` 与 `Copyright (c) 2026 Ryuu-64`。上游 README 原样保存在 `README.fork-origin.md`,改动清单与致谢见 `NOTICE.md`。
|
|
85
89
|
|
package/lib/client.js
CHANGED
|
@@ -17,18 +17,18 @@
|
|
|
17
17
|
// nothing, even though the conversation demonstrably contains it.
|
|
18
18
|
//
|
|
19
19
|
// What this plugin adds:
|
|
20
|
-
// * A scope toggle in the bar:
|
|
20
|
+
// * A scope toggle in the bar: 「包含更早内容 / Include earlier content」 (default) and 「已加载内容 / Loaded content」.
|
|
21
21
|
// * In whole-conversation mode a search pages the older history in through
|
|
22
22
|
// the session service (ctx.sessions.binding(id).session.loadOlder()),
|
|
23
23
|
// re-running the match pass after every page — so the count and the
|
|
24
|
-
// highlight set
|
|
24
|
+
// highlight set cover the history the host currently makes available.
|
|
25
25
|
// * Progress is visible ("正在加载更早的历史… 已 3 页"), clickable to stop,
|
|
26
26
|
// capped at MAX_PAGES, and it stops by itself when the history stops
|
|
27
|
-
// growing
|
|
27
|
+
// growing. Host termination does not establish historical completeness.
|
|
28
28
|
//
|
|
29
29
|
// How it works without fighting React (unchanged from the fork):
|
|
30
30
|
// * Matches are collected as DOM Range objects (start/end offsets inside
|
|
31
|
-
// text nodes)
|
|
31
|
+
// text nodes) from block-local text snapshots, including shadow roots.
|
|
32
32
|
// * Highlights use the CSS Custom Highlight API (CSS.highlights + Highlight)
|
|
33
33
|
// which paints ranges WITHOUT mutating the DOM — React keeps full
|
|
34
34
|
// ownership of the tree. Chromium/Electron support this since 105.
|
|
@@ -59,44 +59,52 @@ window.__ModuleLoader__.load({
|
|
|
59
59
|
/** How often the open bar notices that the user switched session. */
|
|
60
60
|
var SESSION_POLL_MS = 600;
|
|
61
61
|
var SKIP_TAGS = { SCRIPT: 1, STYLE: 1, NOSCRIPT: 1 };
|
|
62
|
+
// Structural boundaries, not a computed-style visibility policy. In
|
|
63
|
+
// particular, host message divs and code-content/toolbars stay separate.
|
|
64
|
+
var BLOCK_TAGS = {
|
|
65
|
+
ADDRESS: 1, ARTICLE: 1, ASIDE: 1, BLOCKQUOTE: 1, DD: 1, DETAILS: 1,
|
|
66
|
+
DIALOG: 1, DIV: 1, DL: 1, DT: 1, FIELDSET: 1, FIGCAPTION: 1, FIGURE: 1,
|
|
67
|
+
FOOTER: 1, FORM: 1, H1: 1, H2: 1, H3: 1, H4: 1, H5: 1, H6: 1,
|
|
68
|
+
HEADER: 1, HR: 1, LI: 1, MAIN: 1, NAV: 1, OL: 1, P: 1, PRE: 1,
|
|
69
|
+
SECTION: 1, SUMMARY: 1, TABLE: 1, TBODY: 1, TD: 1, TFOOT: 1,
|
|
70
|
+
TH: 1, THEAD: 1, TR: 1, UL: 1
|
|
71
|
+
};
|
|
62
72
|
|
|
63
73
|
var ZH = typeof navigator !== "undefined" && /^zh/i.test(navigator.language || "");
|
|
64
74
|
var L = ZH ? {
|
|
65
|
-
placeholder: "
|
|
75
|
+
placeholder: "在此会话中查找…",
|
|
66
76
|
prev: "上一个匹配",
|
|
67
77
|
next: "下一个匹配",
|
|
68
78
|
close: "关闭查找",
|
|
69
|
-
cap: "
|
|
70
|
-
scopeWhole: "
|
|
71
|
-
scopePage: "
|
|
72
|
-
scopeToPage: "
|
|
73
|
-
scopeToWhole: "
|
|
79
|
+
cap: "仅显示前 {n} 处匹配",
|
|
80
|
+
scopeWhole: "包含更早内容",
|
|
81
|
+
scopePage: "已加载内容",
|
|
82
|
+
scopeToPage: "搜索当前会话中已加载的内容,并继续加载更早内容。点击改为仅搜索已加载内容",
|
|
83
|
+
scopeToWhole: "仅搜索当前会话中已加载的内容,不再加载更早内容。点击改为包含更早内容",
|
|
74
84
|
loading: "正在加载更早的历史… 已 {n} 页",
|
|
75
|
-
|
|
76
|
-
capped: "
|
|
77
|
-
stalled: "没有更早的历史了",
|
|
85
|
+
available: "已搜索当前可用历史;宿主未提供更早页,无法确认历史完整性",
|
|
86
|
+
capped: "搜索未完成:已到更早内容的加载上限({n} 页),已保留找到的结果",
|
|
78
87
|
stopTip: "点击停止加载",
|
|
79
|
-
noService: "
|
|
88
|
+
noService: "搜索未完成:会话接口不可用,已保留找到的结果",
|
|
80
89
|
noTarget: "请先选择可见的会话,再打开查找",
|
|
81
90
|
noRoot: "当前会话的搜索范围不可用",
|
|
82
|
-
incomplete: "
|
|
91
|
+
incomplete: "搜索未完成,已保留找到的结果",
|
|
83
92
|
find: "查找会话"
|
|
84
93
|
} : {
|
|
85
|
-
placeholder: "Find in conversation…",
|
|
94
|
+
placeholder: "Find in this conversation…",
|
|
86
95
|
prev: "Previous match",
|
|
87
96
|
next: "Next match",
|
|
88
97
|
close: "Close find",
|
|
89
|
-
cap: "
|
|
90
|
-
scopeWhole: "
|
|
91
|
-
scopePage: "
|
|
92
|
-
scopeToPage: "
|
|
93
|
-
scopeToWhole: "
|
|
98
|
+
cap: "Showing only the first {n} matches",
|
|
99
|
+
scopeWhole: "Include earlier content",
|
|
100
|
+
scopePage: "Loaded content",
|
|
101
|
+
scopeToPage: "Search loaded content in this conversation and load earlier content. Click to search loaded content only",
|
|
102
|
+
scopeToWhole: "Search only content already loaded in this conversation, without loading more. Click to include earlier content",
|
|
94
103
|
loading: "Loading earlier history… {n} pages",
|
|
95
|
-
|
|
96
|
-
capped: "
|
|
97
|
-
stalled: "No earlier history",
|
|
104
|
+
available: "Searched currently available history; the host offers no earlier pages, so completeness cannot be confirmed",
|
|
105
|
+
capped: "History search incomplete: loading limit reached ({n} pages); partial results retained",
|
|
98
106
|
stopTip: "Click to stop loading",
|
|
99
|
-
noService: "
|
|
107
|
+
noService: "History search incomplete: session service unavailable; partial results retained",
|
|
100
108
|
noTarget: "Select a visible conversation before opening find",
|
|
101
109
|
noRoot: "Search scope unavailable for this conversation",
|
|
102
110
|
incomplete: "History search incomplete; partial results retained",
|
|
@@ -105,22 +113,30 @@ window.__ModuleLoader__.load({
|
|
|
105
113
|
|
|
106
114
|
//#region pure helpers (exported for tests)
|
|
107
115
|
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
116
|
+
// Literal, locale-independent Unicode simple folding on the ORIGINAL
|
|
117
|
+
// text. RegExp returns UTF-16 offsets, exactly as DOM Range requires.
|
|
118
|
+
// No normalization, full case folding, or transformed-string offsets.
|
|
119
|
+
function findMatches(text, query) {
|
|
111
120
|
if (!query) return [];
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
// This is the entire pattern (not interpolated into another regex).
|
|
122
|
+
// Escape regex syntax only; '-' and '/' are literal in this context.
|
|
123
|
+
// Do not require the newer RegExp.escape() API in supported hosts.
|
|
124
|
+
var literal = String(query).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
125
|
+
if (!literal) return [];
|
|
126
|
+
var haystack = String(text);
|
|
127
|
+
var matcher = new RegExp(literal, "giu");
|
|
114
128
|
var out = [];
|
|
115
|
-
var
|
|
116
|
-
while (
|
|
117
|
-
out.push(
|
|
118
|
-
if (out.length >= MAX_MATCHES) break;
|
|
119
|
-
i = haystack.indexOf(needle, i + needle.length);
|
|
129
|
+
var match;
|
|
130
|
+
while (out.length < MAX_MATCHES && (match = matcher.exec(haystack)) !== null) {
|
|
131
|
+
out.push({ start: match.index, end: match.index + match[0].length });
|
|
120
132
|
}
|
|
121
133
|
return out;
|
|
122
134
|
}
|
|
123
135
|
|
|
136
|
+
function findIndices(text, query) {
|
|
137
|
+
return findMatches(text, query).map(function (match) { return match.start; });
|
|
138
|
+
}
|
|
139
|
+
|
|
124
140
|
// Keyboard routing, isolated so tests can drive it with spies.
|
|
125
141
|
function createHandlers(ops) {
|
|
126
142
|
return function onKeydown(event) {
|
|
@@ -151,41 +167,44 @@ window.__ModuleLoader__.load({
|
|
|
151
167
|
};
|
|
152
168
|
}
|
|
153
169
|
|
|
154
|
-
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* Every dependency is injected, so the loop is testable without a DOM or
|
|
158
|
-
* a host: the caller supplies how to read `hasMore`, how to load a page,
|
|
159
|
-
* how to measure the rendered history, and how to wait for that measure
|
|
160
|
-
* to change.
|
|
161
|
-
*
|
|
162
|
-
* @param opts - { maxPages, isCancelled, hasMore, loadOlder, signature,
|
|
163
|
-
* waitChange, onProgress }.
|
|
164
|
-
* `hasMore` returns true/false/null (null = the host cannot say, so the
|
|
165
|
-
* pager keeps going until a page changes nothing).
|
|
166
|
-
* @returns { pages, reason } where reason is one of done | cancelled |
|
|
167
|
-
* capped | stalled | error.
|
|
170
|
+
/** Page only while the public host state is ready; false is not completeness.
|
|
171
|
+
* Dependencies supply cancellable, bounded readiness/load/DOM waits.
|
|
172
|
+
* @returns { pages, reason } with done, cancelled, capped, stalled, unknown or error.
|
|
168
173
|
*/
|
|
169
174
|
async function runPageIn(opts) {
|
|
170
175
|
var pages = 0;
|
|
171
|
-
|
|
176
|
+
function result(reason) { return { pages: pages, reason: reason }; }
|
|
172
177
|
for (;;) {
|
|
173
|
-
if (opts.isCancelled())
|
|
174
|
-
|
|
175
|
-
if (
|
|
178
|
+
if (opts.isCancelled()) return result("cancelled");
|
|
179
|
+
var ready = await opts.waitReady();
|
|
180
|
+
if (opts.isCancelled()) return result("cancelled");
|
|
181
|
+
if (ready.reason) return result(ready.reason);
|
|
182
|
+
if (ready.hasMore === false) return result("done");
|
|
183
|
+
if (pages >= opts.maxPages) return result("capped");
|
|
176
184
|
var before = opts.signature();
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
185
|
+
var loaded;
|
|
186
|
+
try { loaded = await opts.loadOlder(); }
|
|
187
|
+
catch (error) { loaded = { reason: "error" }; }
|
|
188
|
+
if (opts.isCancelled()) return result("cancelled");
|
|
189
|
+
ready = await opts.waitReady(!!(loaded && loaded.reason));
|
|
190
|
+
if (opts.isCancelled()) return result("cancelled");
|
|
191
|
+
if (loaded && loaded.reason) return result(loaded.reason);
|
|
192
|
+
if (ready.reason) return result(ready.reason);
|
|
193
|
+
if (loaded && loaded.deferred) continue;
|
|
194
|
+
if (ready.hasMore !== false) await opts.waitChange(before);
|
|
195
|
+
if (opts.isCancelled()) return result("cancelled");
|
|
196
|
+
ready = await opts.waitReady();
|
|
197
|
+
if (opts.isCancelled()) return result("cancelled");
|
|
198
|
+
if (ready.reason) return result(ready.reason);
|
|
199
|
+
var changed = opts.signature() !== before;
|
|
200
|
+
if (changed) {
|
|
201
|
+
pages += 1;
|
|
202
|
+
if (opts.onProgress) opts.onProgress(pages);
|
|
182
203
|
}
|
|
183
|
-
if (opts.isCancelled())
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
if (opts.onProgress) opts.onProgress(pages);
|
|
204
|
+
if (opts.isCancelled()) return result("cancelled");
|
|
205
|
+
if (ready.hasMore === false) return result("done");
|
|
206
|
+
if (!changed) return result("stalled");
|
|
187
207
|
}
|
|
188
|
-
return { pages: pages, reason: reason };
|
|
189
208
|
}
|
|
190
209
|
//#endregion
|
|
191
210
|
|
|
@@ -221,8 +240,8 @@ window.__ModuleLoader__.load({
|
|
|
221
240
|
return typeof CSS !== "undefined" && CSS.highlights && typeof Highlight === "function";
|
|
222
241
|
}
|
|
223
242
|
|
|
224
|
-
//
|
|
225
|
-
//
|
|
243
|
+
// Keep the existing pager signature collector unchanged. Matching below
|
|
244
|
+
// uses separate block snapshots; page-settlement semantics are out of scope.
|
|
226
245
|
function collectTextNodes(root, into) {
|
|
227
246
|
if (!root) return into;
|
|
228
247
|
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
@@ -247,17 +266,62 @@ window.__ModuleLoader__.load({
|
|
|
247
266
|
return into;
|
|
248
267
|
}
|
|
249
268
|
|
|
269
|
+
// Join inline Text nodes only within one structural block and DOM root.
|
|
270
|
+
// These snapshots live for one scan. Preserve every original code unit,
|
|
271
|
+
// including whitespace-only nodes and code newlines; never trim or pad.
|
|
272
|
+
function collectTextBlocks(root) {
|
|
273
|
+
var blocks = [];
|
|
274
|
+
var text = "";
|
|
275
|
+
var segments = [];
|
|
276
|
+
function flush() {
|
|
277
|
+
if (segments.length) blocks.push({ text: text, segments: segments });
|
|
278
|
+
text = "";
|
|
279
|
+
segments = [];
|
|
280
|
+
}
|
|
281
|
+
function visit(node) {
|
|
282
|
+
if (node.nodeType === 3) {
|
|
283
|
+
if (node.data.length) {
|
|
284
|
+
segments.push({ node: node, start: text.length, end: text.length + node.data.length });
|
|
285
|
+
text += node.data;
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (node.nodeType !== 1 && node.nodeType !== 11) return;
|
|
290
|
+
if (SKIP_TAGS[node.tagName] || node.id === BAR_ID || node.tagName === "BR") {
|
|
291
|
+
// Excluded subtrees and br are hard boundaries, not empty text.
|
|
292
|
+
flush();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
var boundary = BLOCK_TAGS[node.tagName] || node.nodeType === 11 || node.shadowRoot;
|
|
296
|
+
if (boundary) flush();
|
|
297
|
+
for (var child = node.firstChild; child; child = child.nextSibling) visit(child);
|
|
298
|
+
if (boundary) flush();
|
|
299
|
+
// No Range may span a shadow root and its surrounding light DOM.
|
|
300
|
+
if (node.shadowRoot) visit(node.shadowRoot);
|
|
301
|
+
}
|
|
302
|
+
visit(root);
|
|
303
|
+
flush();
|
|
304
|
+
return blocks;
|
|
305
|
+
}
|
|
306
|
+
|
|
250
307
|
function collectRanges(query, root) {
|
|
251
308
|
var ranges = [];
|
|
252
309
|
if (!query || !root || typeof document === "undefined") return ranges;
|
|
253
|
-
var
|
|
254
|
-
for (var i = 0; i <
|
|
255
|
-
var
|
|
256
|
-
var
|
|
257
|
-
|
|
310
|
+
var blocks = collectTextBlocks(root);
|
|
311
|
+
for (var i = 0; i < blocks.length && ranges.length < MAX_MATCHES; i++) {
|
|
312
|
+
var block = blocks[i];
|
|
313
|
+
var matches = findMatches(block.text, query);
|
|
314
|
+
var start = 0, end = 0;
|
|
315
|
+
for (var j = 0; j < matches.length && ranges.length < MAX_MATCHES; j++) {
|
|
316
|
+
var match = matches[j];
|
|
317
|
+
// At a node junction, start at the following node and end at
|
|
318
|
+
// the preceding node. Non-overlapping matches move forwards.
|
|
319
|
+
while (match.start >= block.segments[start].end) start++;
|
|
320
|
+
while (match.end > block.segments[end].end) end++;
|
|
321
|
+
var first = block.segments[start], last = block.segments[end];
|
|
258
322
|
var range = document.createRange();
|
|
259
|
-
range.setStart(node,
|
|
260
|
-
range.setEnd(node,
|
|
323
|
+
range.setStart(first.node, match.start - first.start);
|
|
324
|
+
range.setEnd(last.node, match.end - last.start);
|
|
261
325
|
ranges.push(range);
|
|
262
326
|
}
|
|
263
327
|
}
|
|
@@ -448,7 +512,7 @@ window.__ModuleLoader__.load({
|
|
|
448
512
|
return root ? collectTextNodes(root, []).length : 0;
|
|
449
513
|
}
|
|
450
514
|
|
|
451
|
-
function
|
|
515
|
+
function waitForPage(check, isCancelled) {
|
|
452
516
|
return new Promise(function (resolve) {
|
|
453
517
|
var started = Date.now();
|
|
454
518
|
function finish(value) {
|
|
@@ -457,24 +521,53 @@ window.__ModuleLoader__.load({
|
|
|
457
521
|
state.settleCancel = null;
|
|
458
522
|
resolve(value);
|
|
459
523
|
}
|
|
460
|
-
state.settleCancel = function () { finish(
|
|
524
|
+
state.settleCancel = function () { finish({ reason: "cancelled" }); };
|
|
461
525
|
(function poll() {
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
if (
|
|
526
|
+
if (isCancelled()) { finish({ reason: "cancelled" }); return; }
|
|
527
|
+
var value = check();
|
|
528
|
+
if (value) { finish(value); return; }
|
|
529
|
+
if (Date.now() - started >= PAGE_SETTLE_MS) { finish({ reason: "stalled" }); return; }
|
|
465
530
|
state.settleTimer = setTimeout(poll, PAGE_POLL_MS);
|
|
466
531
|
})();
|
|
467
532
|
});
|
|
468
533
|
}
|
|
469
534
|
|
|
470
|
-
function
|
|
535
|
+
function facePageState(face) {
|
|
471
536
|
try {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
return
|
|
477
|
-
|
|
537
|
+
var snapshot = typeof face.getSnapshot === "function" && face.getSnapshot();
|
|
538
|
+
if (!snapshot) return { reason: "unknown" };
|
|
539
|
+
if (snapshot.openError != null || snapshot.openState === "error") return { reason: "error" };
|
|
540
|
+
if (snapshot.openState === "cold" || snapshot.openState === "loading") return null;
|
|
541
|
+
if (snapshot.openState !== "open" || snapshot.openError !== null) return { reason: "unknown" };
|
|
542
|
+
if (snapshot.loadingOlder === true) return null;
|
|
543
|
+
if (snapshot.loadingOlder !== false) return { reason: "unknown" };
|
|
544
|
+
return { hasMore: typeof snapshot.hasMore === "boolean" ? snapshot.hasMore : null };
|
|
545
|
+
} catch (error) { return { reason: "unknown" }; }
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// A cancelled UI task cannot abort SessionFace.loadOlder(). Keep its record
|
|
549
|
+
// until settlement so a later query/enable never starts a concurrent request.
|
|
550
|
+
var pendingLoads = new WeakMap();
|
|
551
|
+
async function loadPage(face, isCancelled) {
|
|
552
|
+
var status = facePageState(face);
|
|
553
|
+
if (!status || status.reason || status.hasMore === false || pendingLoads.has(face)) return { deferred: true };
|
|
554
|
+
var pending = { done: false, reason: null };
|
|
555
|
+
pendingLoads.set(face, pending);
|
|
556
|
+
var promise;
|
|
557
|
+
try { promise = Promise.resolve(face.loadOlder()); }
|
|
558
|
+
catch (error) { promise = Promise.reject(error); }
|
|
559
|
+
promise.then(function () {
|
|
560
|
+
pending.done = true;
|
|
561
|
+
pendingLoads.delete(face);
|
|
562
|
+
}, function () {
|
|
563
|
+
pending.done = true;
|
|
564
|
+
pending.reason = "error";
|
|
565
|
+
pendingLoads.delete(face);
|
|
566
|
+
});
|
|
567
|
+
await Promise.resolve();
|
|
568
|
+
return waitForPage(function () {
|
|
569
|
+
return pending.done ? { reason: pending.reason } : null;
|
|
570
|
+
}, isCancelled);
|
|
478
571
|
}
|
|
479
572
|
//#endregion
|
|
480
573
|
|
|
@@ -494,6 +587,7 @@ window.__ModuleLoader__.load({
|
|
|
494
587
|
query: "",
|
|
495
588
|
ranges: [],
|
|
496
589
|
index: -1,
|
|
590
|
+
selection: null,
|
|
497
591
|
capped: false,
|
|
498
592
|
observer: null,
|
|
499
593
|
debounce: 0,
|
|
@@ -524,11 +618,16 @@ window.__ModuleLoader__.load({
|
|
|
524
618
|
if (!state.query) state.count.textContent = "";
|
|
525
619
|
else if (n === 0) state.count.textContent = state.paging ? "…" : "0/0";
|
|
526
620
|
else state.count.textContent = (state.index + 1) + "/" + n + (state.capped ? "+" : "");
|
|
621
|
+
var cap = state.query && n > 0 && state.capped ? L.cap.replace("{n}", String(MAX_MATCHES)) : "";
|
|
622
|
+
state.count.title = cap;
|
|
623
|
+
if (cap) state.count.setAttribute("aria-label", state.count.textContent + "; " + cap);
|
|
624
|
+
else state.count.removeAttribute("aria-label");
|
|
527
625
|
}
|
|
528
626
|
|
|
529
627
|
function setStatus(text, busy) {
|
|
530
628
|
if (!state.status) return;
|
|
531
629
|
state.status.textContent = text || "";
|
|
630
|
+
state.status.title = busy ? L.stopTip : (text || "");
|
|
532
631
|
if (busy) state.status.setAttribute("data-busy", "1");
|
|
533
632
|
else state.status.removeAttribute("data-busy");
|
|
534
633
|
updateCount();
|
|
@@ -547,12 +646,104 @@ window.__ModuleLoader__.load({
|
|
|
547
646
|
return state.target || (state.adapter && state.adapter.scopeUnavailable()) ? L.noRoot : L.noTarget;
|
|
548
647
|
}
|
|
549
648
|
|
|
649
|
+
// The code content wrapper survives the host's plain-pre -> Shiki tree
|
|
650
|
+
// swap (0.2.0-rc.2 CodeBlock). Otherwise use the nearest block boundary.
|
|
651
|
+
function selectionBlock(range, root) {
|
|
652
|
+
var nearest = null;
|
|
653
|
+
for (var node = range.startContainer.parentNode; node; node = node.parentNode) {
|
|
654
|
+
if (node === root || node.nodeType === 11) return nearest || node;
|
|
655
|
+
if (node.hasAttribute && node.hasAttribute("data-code-block-content")) return node;
|
|
656
|
+
if (!nearest && BLOCK_TAGS[node.tagName]) nearest = node;
|
|
657
|
+
}
|
|
658
|
+
return nearest;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function positionInBlocks(range, blocks) {
|
|
662
|
+
for (var i = 0; i < blocks.length; i++) {
|
|
663
|
+
var start = null, end = null;
|
|
664
|
+
for (var j = 0; j < blocks[i].segments.length; j++) {
|
|
665
|
+
var segment = blocks[i].segments[j];
|
|
666
|
+
if (segment.node === range.startContainer) start = segment.start + range.startOffset;
|
|
667
|
+
if (segment.node === range.endContainer) end = segment.start + range.endOffset;
|
|
668
|
+
}
|
|
669
|
+
if (start !== null && end !== null) return { run: i, start: start, end: end };
|
|
670
|
+
}
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function matchNodes(blocks, position) {
|
|
675
|
+
if (!position) return [];
|
|
676
|
+
return blocks[position.run].segments.filter(function (segment) {
|
|
677
|
+
return segment.end > position.start && segment.start < position.end;
|
|
678
|
+
}).map(function (segment) {
|
|
679
|
+
return {
|
|
680
|
+
node: segment.node, text: segment.node.data,
|
|
681
|
+
start: Math.max(position.start, segment.start) - segment.start,
|
|
682
|
+
end: Math.min(position.end, segment.end) - segment.start
|
|
683
|
+
};
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function rememberSelection() {
|
|
688
|
+
var range = state.ranges[state.index];
|
|
689
|
+
state.selection = null;
|
|
690
|
+
if (!range || !state.target) return;
|
|
691
|
+
var block = selectionBlock(range, state.target.root);
|
|
692
|
+
var blocks = block ? collectTextBlocks(block) : [];
|
|
693
|
+
var position = positionInBlocks(range, blocks);
|
|
694
|
+
var nodes = matchNodes(blocks, position);
|
|
695
|
+
state.selection = {
|
|
696
|
+
target: state.target, query: state.query, root: state.target.root,
|
|
697
|
+
startNode: range.startContainer, startOffset: range.startOffset,
|
|
698
|
+
endNode: range.endContainer, endOffset: range.endOffset,
|
|
699
|
+
block: block, position: position, nodes: nodes,
|
|
700
|
+
texts: blocks.map(function (item) { return item.text; })
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function retainedIndex(saved) {
|
|
705
|
+
if (!saved || saved.target !== state.target || saved.query !== state.query || saved.root !== state.target.root) return -1;
|
|
706
|
+
if (!saved.block || !saved.block.isConnected || !saved.position) return -1;
|
|
707
|
+
var blocks = collectTextBlocks(saved.block);
|
|
708
|
+
var i, range;
|
|
709
|
+
for (i = 0; i < state.ranges.length; i++) {
|
|
710
|
+
range = state.ranges[i];
|
|
711
|
+
if (range.startContainer !== saved.startNode || range.startOffset !== saved.startOffset ||
|
|
712
|
+
range.endContainer !== saved.endNode || range.endOffset !== saved.endOffset) continue;
|
|
713
|
+
// Compare the new match's actual ordered participants. Old nodes can
|
|
714
|
+
// remain connected with unchanged text after moving outside this hit.
|
|
715
|
+
var nodes = matchNodes(blocks, positionInBlocks(range, blocks));
|
|
716
|
+
if (nodes.length > 0 && nodes.length === saved.nodes.length && nodes.every(function (item, index) {
|
|
717
|
+
var previous = saved.nodes[index];
|
|
718
|
+
return item.node === previous.node && item.text === previous.text && item.start === previous.start && item.end === previous.end;
|
|
719
|
+
})) return i;
|
|
720
|
+
}
|
|
721
|
+
if (blocks.length !== saved.texts.length || blocks.some(function (item, index) { return item.text !== saved.texts[index]; })) return -1;
|
|
722
|
+
var segments = blocks[saved.position.run].segments;
|
|
723
|
+
var first = null, last = null;
|
|
724
|
+
for (i = 0; i < segments.length; i++) {
|
|
725
|
+
if (saved.position.start >= segments[i].start && saved.position.start < segments[i].end) first = segments[i];
|
|
726
|
+
if (saved.position.end > segments[i].start && saved.position.end <= segments[i].end) last = segments[i];
|
|
727
|
+
}
|
|
728
|
+
if (!first || !last) return -1;
|
|
729
|
+
for (i = 0; i < state.ranges.length; i++) {
|
|
730
|
+
range = state.ranges[i];
|
|
731
|
+
if (range.startContainer === first.node && range.startOffset === saved.position.start - first.start &&
|
|
732
|
+
range.endContainer === last.node && range.endOffset === saved.position.end - last.start) return i;
|
|
733
|
+
}
|
|
734
|
+
return -1;
|
|
735
|
+
}
|
|
736
|
+
|
|
550
737
|
function runSearch(keepIndex) {
|
|
551
738
|
var valid = state.adapter && state.adapter.valid(state.target);
|
|
552
739
|
state.ranges = valid ? collectRanges(state.query, state.target.root) : [];
|
|
553
740
|
if (!valid) setStatus(unavailableStatus(), false);
|
|
554
741
|
state.capped = state.ranges.length >= MAX_MATCHES;
|
|
555
|
-
|
|
742
|
+
var retained = keepIndex && valid ? retainedIndex(state.selection) : -1;
|
|
743
|
+
if (!state.ranges.length) state.index = -1;
|
|
744
|
+
else if (!keepIndex) state.index = 0;
|
|
745
|
+
else state.index = retained >= 0 ? retained : Math.max(0, Math.min(state.index, state.ranges.length - 1));
|
|
746
|
+
rememberSelection();
|
|
556
747
|
paint();
|
|
557
748
|
updateCount();
|
|
558
749
|
|
|
@@ -563,6 +754,7 @@ window.__ModuleLoader__.load({
|
|
|
563
754
|
var n = state.ranges.length;
|
|
564
755
|
if (n === 0) return;
|
|
565
756
|
state.index = ((state.index + delta) % n + n) % n;
|
|
757
|
+
rememberSelection();
|
|
566
758
|
paint();
|
|
567
759
|
updateCount();
|
|
568
760
|
scrollToRange(state.ranges[state.index]);
|
|
@@ -611,7 +803,8 @@ window.__ModuleLoader__.load({
|
|
|
611
803
|
|
|
612
804
|
function startPageIn() {
|
|
613
805
|
if (state.scope !== "whole" || !state.query || !isOpen()) return;
|
|
614
|
-
|
|
806
|
+
cancelPageIn();
|
|
807
|
+
var token = state.pageToken;
|
|
615
808
|
var target = state.target;
|
|
616
809
|
if (!state.adapter || !state.adapter.valid(target)) {
|
|
617
810
|
setStatus(unavailableStatus(), false);
|
|
@@ -623,33 +816,51 @@ window.__ModuleLoader__.load({
|
|
|
623
816
|
return;
|
|
624
817
|
}
|
|
625
818
|
state.sessionId = target.id;
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
819
|
+
function isCancelled() {
|
|
820
|
+
return token !== state.pageToken || !isOpen() || state.target !== target || !state.adapter || state.adapter.face(target) !== face;
|
|
821
|
+
}
|
|
822
|
+
function waitReady(failFast) {
|
|
823
|
+
return waitForPage(function () {
|
|
824
|
+
var status = facePageState(face);
|
|
825
|
+
if (status && status.reason) return status;
|
|
826
|
+
if (pendingLoads.has(face) || !status) return failFast ? { reason: "stalled" } : null;
|
|
827
|
+
return status;
|
|
828
|
+
}, isCancelled);
|
|
629
829
|
}
|
|
630
830
|
state.paging = true;
|
|
631
831
|
state.pages = 0;
|
|
632
832
|
setStatus(L.loading.replace("{n}", "0"), true);
|
|
633
833
|
runPageIn({
|
|
634
834
|
maxPages: MAX_PAGES,
|
|
635
|
-
isCancelled:
|
|
636
|
-
|
|
637
|
-
loadOlder: function () { return
|
|
835
|
+
isCancelled: isCancelled,
|
|
836
|
+
waitReady: waitReady,
|
|
837
|
+
loadOlder: function () { return loadPage(face, isCancelled); },
|
|
638
838
|
signature: function () { return domSignature(target.root); },
|
|
639
|
-
waitChange: function (before) {
|
|
839
|
+
waitChange: function (before) {
|
|
840
|
+
return waitForPage(function () {
|
|
841
|
+
var status = facePageState(face);
|
|
842
|
+
return domSignature(target.root) !== before || !status || status.reason || status.hasMore === false ? {} : null;
|
|
843
|
+
}, isCancelled);
|
|
844
|
+
},
|
|
640
845
|
onProgress: function (pages) {
|
|
641
|
-
if (
|
|
846
|
+
if (isCancelled()) return;
|
|
642
847
|
state.pages = pages;
|
|
643
848
|
setStatus(L.loading.replace("{n}", String(pages)), true);
|
|
644
849
|
runSearch(true);
|
|
645
850
|
}
|
|
646
851
|
}).then(function (result) {
|
|
647
|
-
if (
|
|
852
|
+
if (isCancelled()) {
|
|
853
|
+
// Face replacement cancels this task even if the DOM target survived.
|
|
854
|
+
if (token === state.pageToken) { state.paging = false; setStatus(L.incomplete, false); }
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
var terminal = facePageState(face);
|
|
858
|
+
if (result.reason === "done" && (!terminal || terminal.reason || terminal.hasMore !== false || pendingLoads.has(face))) result.reason = "stalled";
|
|
648
859
|
state.paging = false;
|
|
649
860
|
state.pages = result.pages;
|
|
650
861
|
if (result.reason === "capped") setStatus(L.capped.replace("{n}", String(result.pages)), false);
|
|
651
862
|
else if (result.reason !== "done") setStatus(L.incomplete, false);
|
|
652
|
-
else setStatus(
|
|
863
|
+
else setStatus(L.available, false);
|
|
653
864
|
runSearch(true);
|
|
654
865
|
});
|
|
655
866
|
}
|
|
@@ -747,6 +958,7 @@ window.__ModuleLoader__.load({
|
|
|
747
958
|
|
|
748
959
|
var count = document.createElement("span");
|
|
749
960
|
count.className = "count";
|
|
961
|
+
count.setAttribute("role", "status");
|
|
750
962
|
count.setAttribute("aria-live", "polite");
|
|
751
963
|
|
|
752
964
|
var status = document.createElement("span");
|
|
@@ -783,9 +995,9 @@ window.__ModuleLoader__.load({
|
|
|
783
995
|
var selection = "";
|
|
784
996
|
try { selection = String(window.getSelection()).trim(); } catch (error) {}
|
|
785
997
|
if (state.target && selection && selection.length <= 200 && selection !== state.query) {
|
|
786
|
-
state.input.value = selection;
|
|
787
998
|
state.query = selection;
|
|
788
999
|
}
|
|
1000
|
+
state.input.value = state.query;
|
|
789
1001
|
state.input.focus();
|
|
790
1002
|
state.input.select();
|
|
791
1003
|
runSearch(false);
|
|
@@ -798,10 +1010,9 @@ window.__ModuleLoader__.load({
|
|
|
798
1010
|
cancelPageIn();
|
|
799
1011
|
stopWatchingSession();
|
|
800
1012
|
if (state.bar) state.bar.style.display = "none";
|
|
801
|
-
state.query = "";
|
|
802
|
-
if (state.input) state.input.value = "";
|
|
803
1013
|
state.ranges = [];
|
|
804
1014
|
state.index = -1;
|
|
1015
|
+
state.selection = null;
|
|
805
1016
|
setStatus("", false);
|
|
806
1017
|
paint(); // clears registered highlights
|
|
807
1018
|
updateCount();
|
|
@@ -814,6 +1025,8 @@ window.__ModuleLoader__.load({
|
|
|
814
1025
|
|
|
815
1026
|
function apply(ctx) {
|
|
816
1027
|
var react = require("react");
|
|
1028
|
+
var ui = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
1029
|
+
var SearchIcon = ui.IconSearchOutlineRegular || ui.IconSearchOutline16;
|
|
817
1030
|
state.ctx = ctx;
|
|
818
1031
|
state.adapter = createHostAdapter(ctx, function () {
|
|
819
1032
|
syncTarget(null, true);
|
|
@@ -822,13 +1035,18 @@ window.__ModuleLoader__.load({
|
|
|
822
1035
|
var anchor = react.useRef(null);
|
|
823
1036
|
react.useEffect(function () {
|
|
824
1037
|
if (!anchor.current || !props.sessionId) return;
|
|
825
|
-
|
|
1038
|
+
// Older supported Button exports do not forward refs. Always
|
|
1039
|
+
// register the real button rather than the sizing-neutral wrapper.
|
|
1040
|
+
var button = anchor.current.querySelector("button[data-find-all-session]");
|
|
1041
|
+
if (button) return state.adapter.mount(button, props.sessionId);
|
|
826
1042
|
}, [props.sessionId]);
|
|
827
|
-
return react.createElement("
|
|
828
|
-
|
|
1043
|
+
return react.createElement("span", { ref: anchor, style: { display: "inline-flex", flex: "none" } },
|
|
1044
|
+
react.createElement(ui.Button, {
|
|
1045
|
+
type: "button", size: "sm", variant: "ghost", title: L.find, "aria-label": L.find,
|
|
1046
|
+
style: { flex: "none", width: 28, padding: 0, color: "var(--dsw-alias-label-secondary)" },
|
|
829
1047
|
"data-find-all-session": props.sessionId,
|
|
830
1048
|
onClick: function (event) { open(event.currentTarget); }
|
|
831
|
-
},
|
|
1049
|
+
}, react.createElement(SearchIcon, { size: 15 })));
|
|
832
1050
|
}
|
|
833
1051
|
ctx.slots.inject("conversation.session.header.utilities", function () {
|
|
834
1052
|
return ctx.slots.register({ name: "conversation.session.header.utilities", id: "dsh-find-all" }, SessionFind);
|
|
@@ -850,6 +1068,7 @@ window.__ModuleLoader__.load({
|
|
|
850
1068
|
window.removeEventListener("focusin", focus, true);
|
|
851
1069
|
window.removeEventListener("pointerdown", focus, true);
|
|
852
1070
|
close();
|
|
1071
|
+
state.query = "";
|
|
853
1072
|
if (state.bar) state.bar.remove();
|
|
854
1073
|
var style = document.querySelector('style[data-plugin-css="dsh-find-all/bar.css"]');
|
|
855
1074
|
if (style) style.remove();
|
|
@@ -865,6 +1084,8 @@ window.__ModuleLoader__.load({
|
|
|
865
1084
|
exports.resolveScope = resolveScope;
|
|
866
1085
|
exports.createHostAdapter = createHostAdapter;
|
|
867
1086
|
exports.findIndices = findIndices; // for tests
|
|
1087
|
+
exports.findMatches = findMatches; // for tests
|
|
1088
|
+
exports.collectRanges = collectRanges; // for tests
|
|
868
1089
|
exports.createHandlers = createHandlers; // for tests
|
|
869
1090
|
exports.runPageIn = runPageIn; // for tests
|
|
870
1091
|
return module.exports;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryuu-64/dsh-find-all",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Ctrl+F find bar for the DSH desktop app: it searches the whole conversation, not just the messages already on screen.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@deepseek-ai/dsh-api-session-controller": "0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
|
|
40
40
|
"@deepseek-ai/dsh-client-ui-chat": "0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
|
|
41
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2"
|
|
41
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2",
|
|
42
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2"
|
|
42
43
|
},
|
|
43
44
|
"scripts": {
|
|
44
45
|
"test": "node --test \"test/**/*.test.mjs\"",
|