@ryuu-64/dsh-find-all 0.1.6 → 0.1.8
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 +18 -0
- package/README.md +16 -27
- package/docs/compatibility.md +38 -6
- package/lib/client.js +796 -147
- package/package.json +3 -2
package/lib/client.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
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
|
|
@@ -72,43 +72,65 @@ window.__ModuleLoader__.load({
|
|
|
72
72
|
|
|
73
73
|
var ZH = typeof navigator !== "undefined" && /^zh/i.test(navigator.language || "");
|
|
74
74
|
var L = ZH ? {
|
|
75
|
-
placeholder: "
|
|
75
|
+
placeholder: "在此会话中查找…",
|
|
76
76
|
prev: "上一个匹配",
|
|
77
77
|
next: "下一个匹配",
|
|
78
78
|
close: "关闭查找",
|
|
79
|
-
cap: "
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
cap: "仅显示前 {n} 处匹配",
|
|
80
|
+
resultCount: "{n} 个结果",
|
|
81
|
+
scopeWhole: "包含更早内容",
|
|
82
|
+
scopePage: "已加载内容",
|
|
83
|
+
scopeToPage: "搜索当前会话中已加载的内容,并继续加载更早内容。点击改为仅搜索已加载内容",
|
|
84
|
+
scopeToWhole: "仅搜索当前会话中已加载的内容,不再加载更早内容。点击改为包含更早内容",
|
|
84
85
|
loading: "正在加载更早的历史… 已 {n} 页",
|
|
85
86
|
available: "已搜索当前可用历史;宿主未提供更早页,无法确认历史完整性",
|
|
86
|
-
capped: "
|
|
87
|
+
capped: "搜索未完成:已到更早内容的加载上限({n} 页),已保留找到的结果",
|
|
87
88
|
stopTip: "点击停止加载",
|
|
88
|
-
noService: "
|
|
89
|
-
noTarget: "
|
|
89
|
+
noService: "搜索未完成:会话接口不可用,已保留找到的结果",
|
|
90
|
+
noTarget: "当前主会话没有可搜索的聊天正文",
|
|
90
91
|
noRoot: "当前会话的搜索范围不可用",
|
|
91
|
-
incomplete: "
|
|
92
|
-
find: "查找会话"
|
|
92
|
+
incomplete: "搜索未完成,已保留找到的结果",
|
|
93
|
+
find: "查找会话",
|
|
94
|
+
returnPosition: "返回搜索前位置", returning: "正在返回…", returned: "已返回搜索前位置",
|
|
95
|
+
returnAvailable: "已跳到匹配;可返回搜索前位置",
|
|
96
|
+
matchRevealFailed: "已找到匹配,但宿主未能展开并显示它。可手动展开后重试。",
|
|
97
|
+
jumpHint: "找到 {n} 个结果;按 Enter 或向下跳到第一个,按向上跳到最后一个。",
|
|
98
|
+
originUnavailable: "未能记录当前阅读位置;仍可查找,但无法返回",
|
|
99
|
+
returnCancelled: "已取消返回", returnFailed: "未能返回原位置。",
|
|
100
|
+
return_removed: "原内容已不再可用。", return_render: "原内容已加载,但宿主未能显示原段落。可展开原内容后重试。",
|
|
101
|
+
return_unavailable: "宿主无法提供所需内容或定位能力。", return_capped: "已达到历史加载上限,可重试。",
|
|
102
|
+
return_stalled: "等待内容时没有进展,可重试。", return_error: "读取原内容失败,可重试。",
|
|
103
|
+
return_position: "宿主未保持原段落可见。可先在正文内滚动,再重试。"
|
|
93
104
|
} : {
|
|
94
|
-
placeholder: "Find in conversation…",
|
|
105
|
+
placeholder: "Find in this conversation…",
|
|
95
106
|
prev: "Previous match",
|
|
96
107
|
next: "Next match",
|
|
97
108
|
close: "Close find",
|
|
98
|
-
cap: "
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
cap: "Showing only the first {n} matches",
|
|
110
|
+
resultCount: "{n} results",
|
|
111
|
+
scopeWhole: "Include earlier content",
|
|
112
|
+
scopePage: "Loaded content",
|
|
113
|
+
scopeToPage: "Search loaded content in this conversation and load earlier content. Click to search loaded content only",
|
|
114
|
+
scopeToWhole: "Search only content already loaded in this conversation, without loading more. Click to include earlier content",
|
|
103
115
|
loading: "Loading earlier history… {n} pages",
|
|
104
116
|
available: "Searched currently available history; the host offers no earlier pages, so completeness cannot be confirmed",
|
|
105
|
-
capped: "History search incomplete:
|
|
117
|
+
capped: "History search incomplete: loading limit reached ({n} pages); partial results retained",
|
|
106
118
|
stopTip: "Click to stop loading",
|
|
107
|
-
noService: "
|
|
108
|
-
noTarget: "
|
|
119
|
+
noService: "History search incomplete: session service unavailable; partial results retained",
|
|
120
|
+
noTarget: "No searchable chat content in the main conversation",
|
|
109
121
|
noRoot: "Search scope unavailable for this conversation",
|
|
110
122
|
incomplete: "History search incomplete; partial results retained",
|
|
111
|
-
find: "Find in conversation"
|
|
123
|
+
find: "Find in conversation",
|
|
124
|
+
returnPosition: "Return to reading position", returning: "Returning…", returned: "Returned to reading position",
|
|
125
|
+
returnAvailable: "Moved to a match; return to the reading position is available",
|
|
126
|
+
matchRevealFailed: "A match was found, but the host could not reveal and display it. Expand the section and retry.",
|
|
127
|
+
jumpHint: "{n} matches found; Enter or Next goes to the first, Previous goes to the last.",
|
|
128
|
+
originUnavailable: "The current reading position could not be saved; search remains available, but return is unavailable",
|
|
129
|
+
returnCancelled: "Return cancelled", returnFailed: "Could not return to the reading position.",
|
|
130
|
+
return_removed: "The original content is no longer available.", return_render: "The content is loaded, but the host could not display the original passage. Expand it and retry.",
|
|
131
|
+
return_unavailable: "The host cannot provide the required content or positioning capability.", return_capped: "History page limit reached. You can retry.",
|
|
132
|
+
return_stalled: "No progress while waiting for content. You can retry.", return_error: "Loading the original content failed. You can retry.",
|
|
133
|
+
return_position: "The host did not keep the original passage visible. Try scrolling in the conversation, then retrying."
|
|
112
134
|
};
|
|
113
135
|
|
|
114
136
|
//#region pure helpers (exported for tests)
|
|
@@ -140,6 +162,8 @@ window.__ModuleLoader__.load({
|
|
|
140
162
|
// Keyboard routing, isolated so tests can drive it with spies.
|
|
141
163
|
function createHandlers(ops) {
|
|
142
164
|
return function onKeydown(event) {
|
|
165
|
+
// Let actual modals and editors with their own find UI own their keys.
|
|
166
|
+
if (event.defaultPrevented || event.isComposing || (ops.ownsKeys && !ops.ownsKeys(event))) return;
|
|
143
167
|
var meta = event.metaKey || event.ctrlKey;
|
|
144
168
|
var key = event.key;
|
|
145
169
|
if (meta && (key === "f" || key === "F")) {
|
|
@@ -210,16 +234,23 @@ window.__ModuleLoader__.load({
|
|
|
210
234
|
|
|
211
235
|
function cssText() {
|
|
212
236
|
return "" +
|
|
213
|
-
"#" + BAR_ID + "{position:fixed;top:
|
|
214
|
-
"#" + BAR_ID + "
|
|
237
|
+
"#" + BAR_ID + "{--dsh-find-all-anchor-gap:4px;--dsh-find-all-panel-inset:16px;--dsh-find-all-max-width:760px;position:fixed;top:calc(var(--dsh-find-all-header-bottom) + var(--dsh-find-all-anchor-gap));right:calc(var(--dsh-find-all-panel-right-space) + var(--dsh-find-all-panel-inset));z-index:2147483000;visibility:hidden;isolation:isolate;box-sizing:border-box;align-items:stretch;gap:4px;padding:6px 8px;display:flex;flex-direction:column;width:min(var(--dsh-find-all-max-width),calc(var(--dsh-find-all-panel-width) - var(--dsh-find-all-panel-inset) - var(--dsh-find-all-panel-inset)));max-width:calc(100vw - var(--dsh-find-all-panel-inset) - var(--dsh-find-all-panel-inset));background:transparent;--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);border:0;border-radius:var(--dsw-radius-md,8px);box-shadow:var(--dsw-elevation-panel,var(--dsw-shadow-lv2))}" +
|
|
238
|
+
"#" + BAR_ID + "[data-positioned]{visibility:visible}" +
|
|
239
|
+
"#" + BAR_ID + ":before{content:\"\";position:absolute;inset:0;z-index:-1;pointer-events:none;border-radius:inherit;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2));-webkit-backdrop-filter:var(--dsw-menu-backdrop-filter,blur(40px) saturate(150%));backdrop-filter:var(--dsw-menu-backdrop-filter,blur(40px) saturate(150%))}" +
|
|
240
|
+
"#" + BAR_ID + " .controls{display:flex;flex-wrap:wrap;align-items:center;gap:6px;min-width:0}" +
|
|
241
|
+
"#" + BAR_ID + " input{flex:1 1 210px;min-width:120px;width:auto;height:28px;color:var(--dsw-alias-label-primary);font:13px/1 var(--dsw-font-family);background:var(--dsw-alias-bg-base);border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm,6px);outline:none;padding:0 8px}" +
|
|
215
242
|
"#" + BAR_ID + " input:focus-visible{border-color:var(--dsw-alias-state-business-primary)}" +
|
|
216
243
|
"#" + BAR_ID + " .count{min-width:52px;color:var(--dsw-alias-label-tertiary);font:12px/1 var(--dsw-font-family);font-variant-numeric:tabular-nums;text-align:center}" +
|
|
217
|
-
"#" + BAR_ID + " .status{
|
|
244
|
+
"#" + BAR_ID + " .status{width:100%;min-height:15px;color:var(--dsw-alias-label-tertiary);font:12px/1.25 var(--dsw-font-family);text-align:left;white-space:normal;overflow-wrap:anywhere}" +
|
|
218
245
|
"#" + BAR_ID + " .status[data-busy]{cursor:pointer;color:var(--dsw-alias-state-business-primary)}" +
|
|
219
246
|
"#" + BAR_ID + " .scope{width:auto;padding:0 9px;color:var(--dsw-alias-label-secondary);font:12px/1 var(--dsw-font-family);background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:999px}" +
|
|
220
247
|
"#" + BAR_ID + " .scope[data-whole]{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-state-business-primary)}" +
|
|
221
248
|
"#" + BAR_ID + " button{height:26px;min-width:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;align-items:center;justify-content:center;display:inline-flex}" +
|
|
222
249
|
"#" + BAR_ID + " button:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}" +
|
|
250
|
+
"#" + BAR_ID + " button:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}" +
|
|
251
|
+
"#" + BAR_ID + " button:disabled{opacity:.5;cursor:default}" +
|
|
252
|
+
"#" + BAR_ID + " [data-find-all-return][hidden]{display:none}" +
|
|
253
|
+
"#" + BAR_ID + " [data-find-all-return]{padding:0 8px;white-space:nowrap}" +
|
|
223
254
|
"#" + BAR_ID + " svg{pointer-events:none}" +
|
|
224
255
|
"::highlight(" + HL_ALL + "){background-color:rgba(245,197,24,.42)}" +
|
|
225
256
|
"::highlight(" + HL_CUR + "){background-color:#f5a623;color:#141414}";
|
|
@@ -266,11 +297,68 @@ window.__ModuleLoader__.load({
|
|
|
266
297
|
return into;
|
|
267
298
|
}
|
|
268
299
|
|
|
300
|
+
// MessageIconActions is host chrome: copy/branch controls, Turn usage and
|
|
301
|
+
// the local clock. It must never change the conversation result set merely
|
|
302
|
+
// because hover/focus changes its opacity. Current hosts expose data-clock
|
|
303
|
+
// on that row. The structural fallback is limited to the four admitted
|
|
304
|
+
// pre-data-clock hosts, whose official MessageItem/TurnTailNodeView sources
|
|
305
|
+
// render the same icon-only copy action in the verified positions below.
|
|
306
|
+
function legacyMessageActionRows(root) {
|
|
307
|
+
var rows = new Set();
|
|
308
|
+
if (!root) return rows;
|
|
309
|
+
function includeRoot(selector, into) {
|
|
310
|
+
if (root.nodeType === 1 && root.matches && root.matches(selector)) into.push(root);
|
|
311
|
+
var descendants = root.querySelectorAll ? root.querySelectorAll(selector) : [];
|
|
312
|
+
for (var i = 0; i < descendants.length; i++) into.push(descendants[i]);
|
|
313
|
+
return into;
|
|
314
|
+
}
|
|
315
|
+
var markedSelector = '[data-clock="start"],[data-clock="end"]';
|
|
316
|
+
if ((root.nodeType === 1 && root.matches && root.matches(markedSelector)) ||
|
|
317
|
+
(root.querySelector && root.querySelector(markedSelector))) return rows;
|
|
318
|
+
|
|
319
|
+
function containsIconOnlyAction(element) {
|
|
320
|
+
if (!element || !element.querySelectorAll) return false;
|
|
321
|
+
var buttons = element.querySelectorAll('button[type="button"][aria-label]');
|
|
322
|
+
for (var j = 0; j < buttons.length; j++) {
|
|
323
|
+
if (buttons[j].querySelector("svg") && !(buttons[j].textContent || "").trim()) return true;
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
var tails = includeRoot("[data-turn-tail]", []);
|
|
329
|
+
for (var i = 0; i < tails.length; i++) {
|
|
330
|
+
var tailAction = tails[i].lastElementChild;
|
|
331
|
+
if (containsIconOnlyAction(tailAction)) rows.add(tailAction);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
var flows = includeRoot('[data-chat-flow-kind="user"],[data-chat-flow-kind="steering"]', []);
|
|
335
|
+
for (i = 0; i < flows.length; i++) {
|
|
336
|
+
var buttons = flows[i].querySelectorAll('button[type="button"][aria-label]');
|
|
337
|
+
for (var j = 0; j < buttons.length; j++) {
|
|
338
|
+
if (!buttons[j].querySelector("svg") || (buttons[j].textContent || "").trim()) continue;
|
|
339
|
+
var candidate = null;
|
|
340
|
+
for (var node = buttons[j].parentElement; node && node !== flows[i]; node = node.parentElement) {
|
|
341
|
+
var parent = node.parentElement;
|
|
342
|
+
if (parent && parent !== flows[i] && parent.children.length === 2 &&
|
|
343
|
+
node === parent.lastElementChild && node.previousElementSibling) candidate = node;
|
|
344
|
+
}
|
|
345
|
+
if (candidate) { rows.add(candidate); break; }
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return rows;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function messageActionRow(node, legacyRows) {
|
|
352
|
+
var clock = node.getAttribute && node.getAttribute("data-clock");
|
|
353
|
+
return clock === "start" || clock === "end" || legacyRows.has(node);
|
|
354
|
+
}
|
|
355
|
+
|
|
269
356
|
// Join inline Text nodes only within one structural block and DOM root.
|
|
270
357
|
// These snapshots live for one scan. Preserve every original code unit,
|
|
271
358
|
// including whitespace-only nodes and code newlines; never trim or pad.
|
|
272
359
|
function collectTextBlocks(root) {
|
|
273
360
|
var blocks = [];
|
|
361
|
+
var legacyActions = legacyMessageActionRows(root);
|
|
274
362
|
var text = "";
|
|
275
363
|
var segments = [];
|
|
276
364
|
function flush() {
|
|
@@ -287,7 +375,7 @@ window.__ModuleLoader__.load({
|
|
|
287
375
|
return;
|
|
288
376
|
}
|
|
289
377
|
if (node.nodeType !== 1 && node.nodeType !== 11) return;
|
|
290
|
-
if (SKIP_TAGS[node.tagName] || node.id === BAR_ID || node.tagName === "BR") {
|
|
378
|
+
if (SKIP_TAGS[node.tagName] || node.id === BAR_ID || messageActionRow(node, legacyActions) || node.tagName === "BR") {
|
|
291
379
|
// Excluded subtrees and br are hard boundaries, not empty text.
|
|
292
380
|
flush();
|
|
293
381
|
return;
|
|
@@ -336,9 +424,11 @@ window.__ModuleLoader__.load({
|
|
|
336
424
|
}
|
|
337
425
|
|
|
338
426
|
function scrollToRange(range) {
|
|
427
|
+
var viewport = readingViewport(state.target);
|
|
428
|
+
if (viewport && rangeRect(range, viewport)) return;
|
|
339
429
|
var el = range.startContainer.parentElement;
|
|
340
430
|
if (el && el.scrollIntoView) {
|
|
341
|
-
try { el.scrollIntoView({ block: "center", behavior: "
|
|
431
|
+
try { el.scrollIntoView({ block: "center", behavior: "instant" }); } catch (error) {}
|
|
342
432
|
}
|
|
343
433
|
}
|
|
344
434
|
|
|
@@ -369,6 +459,11 @@ window.__ModuleLoader__.load({
|
|
|
369
459
|
return null;
|
|
370
460
|
}
|
|
371
461
|
|
|
462
|
+
function nestedProcessFlow(flow, root) {
|
|
463
|
+
return flow.hasAttribute('data-step-process-content') && flow.closest('[data-chat-group-key]') &&
|
|
464
|
+
flow.parentElement.closest('[data-chat-flow]') && (!root || root.contains(flow));
|
|
465
|
+
}
|
|
466
|
+
|
|
372
467
|
function resolveScope(anchor, sessionId) {
|
|
373
468
|
if (!anchor || typeof sessionId !== "string" || !sessionId || !visible(anchor)) return null;
|
|
374
469
|
var panel = conversationPanel(anchor);
|
|
@@ -381,120 +476,76 @@ window.__ModuleLoader__.load({
|
|
|
381
476
|
// 0.1.6-alpha.2 has the content wrapper but no identity attribute.
|
|
382
477
|
// Identity still comes from the unique session-scoped header anchor.
|
|
383
478
|
if (content && content.hasAttribute('data-conversation-session') && content.getAttribute('data-conversation-session') !== sessionId) return null;
|
|
384
|
-
var flows = scrolls[0].querySelectorAll('[data-chat-flow]')
|
|
479
|
+
var flows = Array.prototype.filter.call(scrolls[0].querySelectorAll('[data-chat-flow]'), function (flow) {
|
|
480
|
+
return !nestedProcessFlow(flow);
|
|
481
|
+
});
|
|
385
482
|
if (flows.length !== 1 || !visible(flows[0])) return null;
|
|
386
483
|
return { panel: panel, root: flows[0] };
|
|
387
484
|
}
|
|
388
485
|
|
|
389
486
|
function createHostAdapter(ctx, changed) {
|
|
390
487
|
var entries = new Map();
|
|
488
|
+
var mainFrame = null;
|
|
391
489
|
var scopeUnavailable = false;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
var selectionState = "pristine";
|
|
395
|
-
function candidates() {
|
|
396
|
-
var out = [];
|
|
397
|
-
entries.forEach(function (id, anchor) {
|
|
398
|
-
// Keep unsupported/invisible instances in the identity roster. Dropping
|
|
399
|
-
// them before routing could silently choose a different valid session.
|
|
400
|
-
out.push({ id: id, anchor: anchor, panel: conversationPanel(anchor), scope: resolveScope(anchor, id) });
|
|
401
|
-
});
|
|
402
|
-
return out;
|
|
490
|
+
function shown(entry) {
|
|
491
|
+
return visible(entry.anchor) || visible(conversationPanel(entry.anchor) || entry.anchor.closest('[data-phase], [data-conversation-content]'));
|
|
403
492
|
}
|
|
404
493
|
function choose(entry) {
|
|
405
|
-
if (
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
return { id: entry.id, anchor: entry.anchor, panel:
|
|
494
|
+
if (entry) mainFrame = { anchor: entry.anchor, panel: conversationPanel(entry.anchor) };
|
|
495
|
+
var scope = entry && resolveScope(entry.anchor, entry.id);
|
|
496
|
+
scopeUnavailable = !!entry && !scope;
|
|
497
|
+
return scope ? { id: entry.id, anchor: entry.anchor, panel: scope.panel,
|
|
498
|
+
root: scope.root, registration: entry } : null;
|
|
409
499
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
// An empty resident Hero is not a session choice. A known active
|
|
422
|
-
// or identified but unregistered view is unavailable, not pristine.
|
|
423
|
-
if (nodes.some(function (node) { return node.getAttribute('data-phase') === 'active' || node.getAttribute('data-content-phase') === 'active' || node.hasAttribute('data-conversation-session') || node.hasAttribute('data-chat-flow'); })) {
|
|
424
|
-
selectionState = "rejected"; scopeUnavailable = true;
|
|
425
|
-
}
|
|
426
|
-
return null;
|
|
500
|
+
function current(previous) {
|
|
501
|
+
// Header utilities are mounted by the main conversation frame. Embedded
|
|
502
|
+
// sidebars have no such utility: focus and page-wide container counts
|
|
503
|
+
// cannot turn them into a search target or poison the next shortcut.
|
|
504
|
+
previous = previous || mainFrame;
|
|
505
|
+
var same = previous && entries.get(previous.anchor);
|
|
506
|
+
if (!same && previous && visible(previous.panel)) {
|
|
507
|
+
var replacements = Array.from(entries.values()).filter(function (entry) {
|
|
508
|
+
return conversationPanel(entry.anchor) === previous.panel && shown(entry);
|
|
509
|
+
});
|
|
510
|
+
if (replacements.length === 1) same = replacements[0];
|
|
427
511
|
}
|
|
428
|
-
if (
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
var
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
// sidebar, makes neutral discovery ambiguous even if unsupported.
|
|
436
|
-
var other = nodes.some(function (node) {
|
|
437
|
-
if (node.hasAttribute('data-conversation-content') && node !== content) return true;
|
|
438
|
-
if (node.hasAttribute('data-conversation-scroll') && node !== scroll) return true;
|
|
439
|
-
if (node.hasAttribute('data-chat-flow') && node !== scope.root) return true;
|
|
440
|
-
return node.hasAttribute('data-phase') && conversationPanel(node) === node && node !== scope.panel;
|
|
441
|
-
});
|
|
442
|
-
if (other) { selectionState = "rejected"; return null; }
|
|
443
|
-
return choose(entry);
|
|
512
|
+
if (same && shown(same)) return choose(same);
|
|
513
|
+
// A surviving main frame with a missing utility is unavailable, not an
|
|
514
|
+
// invitation to select another frame. A real navigation may replace it.
|
|
515
|
+
if (previous && visible(previous.panel)) { scopeUnavailable = true; return null; }
|
|
516
|
+
var all = Array.from(entries.values()).filter(shown);
|
|
517
|
+
scopeUnavailable = all.length > 0;
|
|
518
|
+
return all.length === 1 ? choose(all[0]) : null;
|
|
444
519
|
}
|
|
445
|
-
|
|
446
|
-
function refresh(entry) {
|
|
447
|
-
if (!entry) return null;
|
|
448
|
-
// Passive checks and repeated shortcuts may only retain this exact
|
|
449
|
-
// registered instance and identity. They never discover a substitute.
|
|
450
|
-
if (entries.get(entry.anchor) !== entry.id) { selectionState = "rejected"; scopeUnavailable = true; return null; }
|
|
451
|
-
scopeUnavailable = false;
|
|
452
|
-
return choose({ id: entry.id, anchor: entry.anchor, scope: resolveScope(entry.anchor, entry.id) });
|
|
453
|
-
}
|
|
454
|
-
|
|
455
520
|
return {
|
|
456
|
-
mount: function (anchor, id) {
|
|
457
|
-
|
|
521
|
+
mount: function (anchor, id, nodes) {
|
|
522
|
+
// A new effect/store generation invalidates old async work even when
|
|
523
|
+
// the session id, DOM button and flow happen to be reused.
|
|
524
|
+
var entry = { anchor: anchor, id: id, nodes: nodes || null };
|
|
525
|
+
entries.set(anchor, entry);
|
|
458
526
|
changed();
|
|
459
|
-
return function () {
|
|
527
|
+
return function () {
|
|
528
|
+
if (entries.get(anchor) === entry) entries.delete(anchor);
|
|
529
|
+
changed();
|
|
530
|
+
};
|
|
460
531
|
},
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (direct.length === 1) return choose(direct[0]);
|
|
473
|
-
var content = target && target.closest ? target.closest('[data-conversation-content]') : null;
|
|
474
|
-
var panel = conversationPanel(target) || content;
|
|
475
|
-
var hit = all.filter(function (entry) { return panel && entry.panel === panel; });
|
|
476
|
-
if (hit.length === 1) {
|
|
477
|
-
if (content && hit[0].scope && hit[0].scope.root.closest('[data-conversation-content]') !== content) {
|
|
478
|
-
selectionState = "rejected"; scopeUnavailable = true; return null;
|
|
479
|
-
}
|
|
480
|
-
return choose(hit[0]);
|
|
481
|
-
}
|
|
482
|
-
if (panel) {
|
|
483
|
-
// Before any session is selected, focus in the resident unbound
|
|
484
|
-
// Hero/settling composer is not a rejected conversation choice.
|
|
485
|
-
var phase = panel.getAttribute('data-phase') || panel.getAttribute('data-content-phase');
|
|
486
|
-
var unbound = !panel.hasAttribute('data-conversation-session') && !panel.querySelector('[data-conversation-session]');
|
|
487
|
-
if (selectionState === "pristine" && !previous && unbound && !all.some(visibleEntry) && (phase === "hero" || phase === "settling")) return null;
|
|
488
|
-
selectionState = "rejected"; scopeUnavailable = true; return null;
|
|
489
|
-
}
|
|
490
|
-
if (previous) selectionState = "rejected";
|
|
491
|
-
return null;
|
|
532
|
+
nodes: function (entry) { return entry && entry.registration.nodes; },
|
|
533
|
+
refresh: current,
|
|
534
|
+
select: function (target, previous) {
|
|
535
|
+
// Explicit registered main-frame interaction disambiguates future
|
|
536
|
+
// hosts with multiple main utilities. Unregistered embedded content
|
|
537
|
+
// and unrelated UI never replace the current main frame.
|
|
538
|
+
var direct = Array.from(entries.values()).filter(function (entry) {
|
|
539
|
+
var panel = conversationPanel(entry.anchor);
|
|
540
|
+
return target && (entry.anchor.contains(target) || (panel && conversationPanel(target) === panel));
|
|
541
|
+
});
|
|
542
|
+
return direct.length === 1 ? choose(direct[0]) : current(previous);
|
|
492
543
|
},
|
|
493
544
|
scopeUnavailable: function () { return scopeUnavailable; },
|
|
494
545
|
valid: function (entry) {
|
|
495
|
-
if (!entry || entries.get(entry.anchor) !== entry.
|
|
546
|
+
if (!entry || entries.get(entry.anchor) !== entry.registration) return false;
|
|
496
547
|
var scope = resolveScope(entry.anchor, entry.id);
|
|
497
|
-
return !!scope && scope.root === entry.root;
|
|
548
|
+
return !!scope && scope.panel === entry.panel && scope.root === entry.root;
|
|
498
549
|
},
|
|
499
550
|
face: function (entry) {
|
|
500
551
|
try {
|
|
@@ -504,10 +555,50 @@ window.__ModuleLoader__.load({
|
|
|
504
555
|
return face && typeof face.loadOlder === "function" ? face : null;
|
|
505
556
|
} catch (error) { return null; }
|
|
506
557
|
},
|
|
507
|
-
clear: function () { entries.clear(); }
|
|
558
|
+
clear: function () { entries.clear(); mainFrame = null; }
|
|
508
559
|
};
|
|
509
560
|
}
|
|
510
561
|
|
|
562
|
+
// RC2's rendered Turn controls call its navigation owner, including
|
|
563
|
+
// cancelling a retained paging anchor. Use that actual loaded UI action;
|
|
564
|
+
// never synthesize wheel/beforematch or import a private Chat controller.
|
|
565
|
+
function hostNavigationReady(target, face) {
|
|
566
|
+
var status = face && facePageState(face);
|
|
567
|
+
if (face && (!status || status.reason || pendingLoads.has(face))) return false;
|
|
568
|
+
// The snapshot can settle before React commits Chat's loadingOlder
|
|
569
|
+
// input. Its direct history control must also stop being disabled.
|
|
570
|
+
return !target.root.querySelector(':scope > div:not([data-chat-anchor-key]):not([data-chat-group-key]) > button:disabled');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function activateLoadedTurn(target, store, preferredTurn) {
|
|
574
|
+
if (!store) return false;
|
|
575
|
+
var list = target.root.parentElement, chat = list && list.parentElement, frame = chat && chat.parentElement;
|
|
576
|
+
if (!frame || !target.panel.contains(frame)) return false;
|
|
577
|
+
var navs = Array.prototype.filter.call(frame.querySelectorAll('nav'), function (nav) {
|
|
578
|
+
var label = nav.getAttribute('aria-label');
|
|
579
|
+
return !target.root.contains(nav) && (label === 'Turn navigation' || label === '轮次导航');
|
|
580
|
+
});
|
|
581
|
+
if (navs.length !== 1 || !visible(navs[0])) return false;
|
|
582
|
+
var loaded = new Set();
|
|
583
|
+
Array.prototype.forEach.call(target.root.querySelectorAll('[data-chat-anchor-key][data-chat-turn]'), function (row) {
|
|
584
|
+
var key = row.getAttribute('data-chat-node-key') || row.getAttribute('data-chat-anchor-key');
|
|
585
|
+
if (store.get(key)) loaded.add(row.getAttribute('data-chat-turn'));
|
|
586
|
+
});
|
|
587
|
+
var candidates = [];
|
|
588
|
+
Array.prototype.forEach.call(navs[0].querySelectorAll('button[data-index]'), function (button) {
|
|
589
|
+
// These exact loaded labels are locale-owned by the inspected host.
|
|
590
|
+
// The distinct "Load and jump" labels must never be activated here.
|
|
591
|
+
var match = /^(?:Jump to turn (\d+)|跳转到第 (\d+) 轮)$/.exec(button.getAttribute('aria-label') || '');
|
|
592
|
+
var turn = match && (match[1] || match[2]);
|
|
593
|
+
if (!turn || !loaded.has(turn) || button.disabled || button.getAttribute('aria-busy') === 'true' || !visible(button)) return;
|
|
594
|
+
candidates.push({ button: button, rank: turn === preferredTurn ? 0 : button.getAttribute('aria-current') === 'true' ? 1 : 2 });
|
|
595
|
+
});
|
|
596
|
+
candidates.sort(function (a, b) { return a.rank - b.rank; });
|
|
597
|
+
if (!candidates.length) return false;
|
|
598
|
+
candidates[0].button.click();
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
|
|
511
602
|
function domSignature(root) {
|
|
512
603
|
return root ? collectTextNodes(root, []).length : 0;
|
|
513
604
|
}
|
|
@@ -571,6 +662,412 @@ window.__ModuleLoader__.load({
|
|
|
571
662
|
}
|
|
572
663
|
//#endregion
|
|
573
664
|
|
|
665
|
+
//#region one find session's reading origin
|
|
666
|
+
function readingViewport(target) {
|
|
667
|
+
var scroll = target && target.root.closest('[data-conversation-scroll]');
|
|
668
|
+
if (!scroll) return null;
|
|
669
|
+
var rect = scroll.getBoundingClientRect();
|
|
670
|
+
var bottom = Math.min(rect.bottom, window.innerHeight);
|
|
671
|
+
var composer = scroll.querySelector('[data-composer-seat]');
|
|
672
|
+
if (composer) {
|
|
673
|
+
var composerTop = composer.getBoundingClientRect().top;
|
|
674
|
+
if (composerTop > rect.top) bottom = Math.min(bottom, composerTop);
|
|
675
|
+
}
|
|
676
|
+
var top = Math.max(0, rect.top);
|
|
677
|
+
return bottom > top && rect.width > 0 ? { scroll: scroll, top: top, bottom: bottom, left: Math.max(0, rect.left), right: Math.min(window.innerWidth, rect.right) } : null;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function readingText(row) {
|
|
681
|
+
var text = '', segments = [];
|
|
682
|
+
var walker = document.createTreeWalker(row, NodeFilter.SHOW_TEXT), node;
|
|
683
|
+
while ((node = walker.nextNode()) !== null) {
|
|
684
|
+
var parent = node.parentElement;
|
|
685
|
+
if (!parent || parent.closest('script, style, noscript, button, input, textarea, select, [data-code-block-banner]')) continue;
|
|
686
|
+
if (parent.closest('[data-chat-anchor-key]') !== row) continue;
|
|
687
|
+
segments.push({ node: node, start: text.length, end: text.length + node.data.length });
|
|
688
|
+
text += node.data;
|
|
689
|
+
}
|
|
690
|
+
return { text: text, segments: segments };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function readingRange(snapshot, offset) {
|
|
694
|
+
for (var i = 0; i < snapshot.segments.length; i++) {
|
|
695
|
+
var segment = snapshot.segments[i];
|
|
696
|
+
if (offset < segment.start || offset >= segment.end) continue;
|
|
697
|
+
var start = offset - segment.start;
|
|
698
|
+
var range = document.createRange();
|
|
699
|
+
range.setStart(segment.node, start);
|
|
700
|
+
range.setEnd(segment.node, Math.min(segment.node.data.length, start + (segment.node.data.codePointAt(start) > 65535 ? 2 : 1)));
|
|
701
|
+
return range;
|
|
702
|
+
}
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function clippedViewport(element, viewport) {
|
|
707
|
+
var top = viewport.top, bottom = viewport.bottom, left = viewport.left, right = viewport.right;
|
|
708
|
+
for (var parent = element; parent && parent !== viewport.scroll; parent = parent.parentElement) {
|
|
709
|
+
var style = window.getComputedStyle(parent);
|
|
710
|
+
var clipsY = /^(auto|scroll|hidden|clip)$/.test(style.overflowY), clipsX = /^(auto|scroll|hidden|clip)$/.test(style.overflowX);
|
|
711
|
+
if (!clipsX && !clipsY) continue;
|
|
712
|
+
var box = parent.getBoundingClientRect();
|
|
713
|
+
if (clipsY) { top = Math.max(top, box.top); bottom = Math.min(bottom, box.bottom); }
|
|
714
|
+
if (clipsX) { left = Math.max(left, box.left); right = Math.min(right, box.right); }
|
|
715
|
+
}
|
|
716
|
+
return { top: top, bottom: bottom, left: left, right: right };
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function rangeRect(range, viewport) {
|
|
720
|
+
if (!range || !range.startContainer.isConnected || typeof range.getClientRects !== 'function') return null;
|
|
721
|
+
var clip = viewport && clippedViewport(range.startContainer.parentElement, viewport);
|
|
722
|
+
var rects = range.getClientRects();
|
|
723
|
+
for (var i = 0; i < rects.length; i++) {
|
|
724
|
+
var rect = rects[i];
|
|
725
|
+
if (rect.height > 0 && rect.width > 0 && (!viewport || (clip.bottom > clip.top && clip.right > clip.left && rect.bottom > clip.top && rect.top < clip.bottom && rect.right > clip.left && rect.left < clip.right))) return rect;
|
|
726
|
+
}
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function captureReadingOrigin() {
|
|
731
|
+
var target = state.target, viewport = readingViewport(target);
|
|
732
|
+
var store = state.adapter && state.adapter.nodes(target);
|
|
733
|
+
if (!viewport || !store) return null;
|
|
734
|
+
var rows = target.root.querySelectorAll('[data-chat-anchor-key]');
|
|
735
|
+
var best = null;
|
|
736
|
+
for (var i = 0; i < rows.length; i++) {
|
|
737
|
+
var row = rows[i];
|
|
738
|
+
if (!visible(row) || row.hasAttribute('data-chat-group-key')) continue;
|
|
739
|
+
var anchorKey = row.getAttribute('data-chat-anchor-key');
|
|
740
|
+
var nodeKey = row.getAttribute('data-chat-node-key') || anchorKey;
|
|
741
|
+
var node = store.get(nodeKey);
|
|
742
|
+
if (!node || !Number.isFinite(node.anchorSeq)) continue;
|
|
743
|
+
var snapshot = readingText(row);
|
|
744
|
+
for (var j = 0; j < snapshot.segments.length; j++) {
|
|
745
|
+
var segment = snapshot.segments[j];
|
|
746
|
+
var whole = document.createRange(); whole.selectNodeContents(segment.node);
|
|
747
|
+
if (!rangeRect(whole, viewport)) continue;
|
|
748
|
+
// Text order follows the rendered line order inside one text node.
|
|
749
|
+
// The final geometry check rejects unsupported or non-visible runs.
|
|
750
|
+
var clipTop = clippedViewport(segment.node.parentElement, viewport).top;
|
|
751
|
+
var lo = segment.start, hi = segment.end - 1;
|
|
752
|
+
while (lo < hi) {
|
|
753
|
+
var mid = Math.floor((lo + hi) / 2);
|
|
754
|
+
var probe = rangeRect(readingRange(snapshot, mid));
|
|
755
|
+
if (probe && probe.bottom <= clipTop + 1) lo = mid + 1;
|
|
756
|
+
else hi = mid;
|
|
757
|
+
}
|
|
758
|
+
var rect = rangeRect(readingRange(snapshot, lo), viewport);
|
|
759
|
+
if (!rect) continue;
|
|
760
|
+
var distance = Math.abs(rect.top - viewport.top);
|
|
761
|
+
if (best && distance >= best.distance) continue;
|
|
762
|
+
var body = row.closest('[data-step-process-body]');
|
|
763
|
+
var group = body && body.closest('[data-chat-group-key]');
|
|
764
|
+
best = {
|
|
765
|
+
inner: group ? { key: group.getAttribute('data-chat-group-key'), top: rect.top - body.getBoundingClientRect().top } : null,
|
|
766
|
+
target: target, store: store, face: state.adapter.face(target), nodeKey: nodeKey, anchorKey: anchorKey,
|
|
767
|
+
seq: node.anchorSeq, text: snapshot.text, offset: lo, top: rect.top - viewport.top,
|
|
768
|
+
focus: document.activeElement, distance: distance, navigated: false
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return best;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function originValid(origin) {
|
|
776
|
+
return !!origin && isOpen() && state.origin === origin && state.target === origin.target &&
|
|
777
|
+
state.adapter && state.adapter.valid(origin.target) && state.adapter.nodes(origin.target) === origin.store &&
|
|
778
|
+
state.adapter.face(origin.target) === origin.face;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function readingRow(origin) {
|
|
782
|
+
var rows = origin.target.root.querySelectorAll('[data-chat-anchor-key]'), found = null;
|
|
783
|
+
for (var i = 0; i < rows.length; i++) {
|
|
784
|
+
if (rows[i].getAttribute('data-chat-anchor-key') !== origin.anchorKey ||
|
|
785
|
+
(rows[i].getAttribute('data-chat-node-key') || origin.anchorKey) !== origin.nodeKey) continue;
|
|
786
|
+
if (found) return null;
|
|
787
|
+
found = rows[i];
|
|
788
|
+
}
|
|
789
|
+
return found;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function locateReading(origin, row) {
|
|
793
|
+
if (!row || !visible(row)) return null;
|
|
794
|
+
var snapshot = readingText(row), offset = origin.offset;
|
|
795
|
+
if (snapshot.text !== origin.text && !snapshot.text.startsWith(origin.text)) {
|
|
796
|
+
var from = Math.max(0, origin.offset - 64);
|
|
797
|
+
var quote = origin.text.slice(from, origin.offset + 65);
|
|
798
|
+
if (origin.text.indexOf(quote) !== from || origin.text.indexOf(quote, from + 1) >= 0) return null;
|
|
799
|
+
var index = snapshot.text.indexOf(quote);
|
|
800
|
+
if (index < 0 || snapshot.text.indexOf(quote, index + 1) >= 0) return null;
|
|
801
|
+
offset = index + origin.offset - from;
|
|
802
|
+
}
|
|
803
|
+
return readingRange(snapshot, offset);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function updateReturnButton() {
|
|
807
|
+
if (!state.returnBtn) return;
|
|
808
|
+
var available = originValid(state.origin) && state.origin.navigated;
|
|
809
|
+
state.returnBtn.hidden = !state.restoring && !available;
|
|
810
|
+
state.returnBtn.disabled = !!state.restoring;
|
|
811
|
+
state.returnBtn.textContent = state.restoring ? L.returning : L.returnPosition;
|
|
812
|
+
state.returnBtn.setAttribute('aria-label', state.returnBtn.textContent);
|
|
813
|
+
state.returnBtn.title = state.returnBtn.textContent;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function cancelNavigation() {
|
|
817
|
+
clearTimeout(state.navigationTimer);
|
|
818
|
+
state.navigationTimer = 0;
|
|
819
|
+
state.navigationToken += 1;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function cancelReturn(message) {
|
|
823
|
+
if (!state.restoring) return;
|
|
824
|
+
state.restoring = null;
|
|
825
|
+
if (state.settleCancel) state.settleCancel();
|
|
826
|
+
if (message) setStatus(message, false);
|
|
827
|
+
updateReturnButton();
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function discardOrigin() {
|
|
831
|
+
cancelReturn();
|
|
832
|
+
cancelNavigation();
|
|
833
|
+
state.origin = null;
|
|
834
|
+
updateReturnButton();
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function navigateToRange(range) {
|
|
838
|
+
cancelReturn(L.returnCancelled);
|
|
839
|
+
cancelNavigation();
|
|
840
|
+
var target = state.target, face = state.adapter && state.adapter.face(target);
|
|
841
|
+
if (target && state.adapter.valid(target) && hostNavigationReady(target, face)) {
|
|
842
|
+
var row = range.startContainer.parentElement.closest('[data-chat-turn]');
|
|
843
|
+
activateLoadedTurn(target, state.adapter.nodes(target), row && row.getAttribute('data-chat-turn'));
|
|
844
|
+
if (state.target !== target || !state.adapter.valid(target) || state.adapter.face(target) !== face) return;
|
|
845
|
+
}
|
|
846
|
+
var origin = state.origin, token = state.navigationToken, started = Date.now();
|
|
847
|
+
var activated = new Set(), scrolledRange = null;
|
|
848
|
+
function check() {
|
|
849
|
+
state.navigationTimer = 0;
|
|
850
|
+
if (token !== state.navigationToken || !originValid(origin)) return;
|
|
851
|
+
var viewport = readingViewport(origin.target);
|
|
852
|
+
var current = state.ranges[state.index];
|
|
853
|
+
var geometry = current && origin.target.root.contains(current.startContainer) && rangeRect(current);
|
|
854
|
+
if (current && !geometry) revealMatchRange(origin.target, current, activated);
|
|
855
|
+
if (geometry && scrolledRange !== current) {
|
|
856
|
+
scrollToRange(current);
|
|
857
|
+
scrolledRange = current;
|
|
858
|
+
}
|
|
859
|
+
if (viewport && current && origin.target.root.contains(current.startContainer) && rangeRect(current, viewport)) {
|
|
860
|
+
var firstNavigation = !origin.navigated;
|
|
861
|
+
origin.navigated = true;
|
|
862
|
+
updateReturnButton();
|
|
863
|
+
if (!firstNavigation && state.status && state.status.textContent === L.matchRevealFailed) setStatus("", false);
|
|
864
|
+
if (firstNavigation) {
|
|
865
|
+
if (state.jumpStatus !== null) {
|
|
866
|
+
var priorStatus = state.jumpStatus;
|
|
867
|
+
setStatus(priorStatus, false);
|
|
868
|
+
} else setStatus(L.returnAvailable, false);
|
|
869
|
+
if (state.announcement) state.announcement.textContent = L.returnAvailable;
|
|
870
|
+
}
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (Date.now() - started < 1500) {
|
|
874
|
+
state.navigationTimer = setTimeout(check, 100);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
setStatus(L.matchRevealFailed, false);
|
|
878
|
+
if (state.announcement) state.announcement.textContent = L.matchRevealFailed;
|
|
879
|
+
}
|
|
880
|
+
check();
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function focusWithoutScroll(element) {
|
|
884
|
+
if (!element || !element.isConnected || typeof element.focus !== "function") return;
|
|
885
|
+
try { element.focus({ preventScroll: true }); } catch (error) { element.focus(); }
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function activateDisclosure(button, activated) {
|
|
889
|
+
if (!button || button.disabled || !visible(button) || activated.has(button)) return false;
|
|
890
|
+
activated.add(button);
|
|
891
|
+
// DSH's disclosure handlers focus their button. Pre-focus without
|
|
892
|
+
// scrolling, then restore the find control that initiated navigation.
|
|
893
|
+
var previous = document.activeElement;
|
|
894
|
+
focusWithoutScroll(button);
|
|
895
|
+
button.click();
|
|
896
|
+
if (previous !== button) focusWithoutScroll(previous);
|
|
897
|
+
return true;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function revealMatchRange(target, range, activated) {
|
|
901
|
+
if (!target || !range || !target.root.contains(range.startContainer)) return false;
|
|
902
|
+
var element = range.startContainer.nodeType === 1 ? range.startContainer : range.startContainer.parentElement;
|
|
903
|
+
if (!element) return false;
|
|
904
|
+
var changed = false;
|
|
905
|
+
// Native details participates in the platform ancestor-revealing
|
|
906
|
+
// algorithm. Opening it does not bypass application-owned state.
|
|
907
|
+
for (var node = element; node && target.root.contains(node); node = node.parentElement) {
|
|
908
|
+
if (node.tagName === "DETAILS" && !node.open) {
|
|
909
|
+
node.open = true;
|
|
910
|
+
changed = true;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
var ancestors = [];
|
|
914
|
+
for (node = element; node && target.root.contains(node); node = node.parentElement) {
|
|
915
|
+
ancestors.push(node);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// DSH retains folded process content with hidden=until-found. Use its
|
|
919
|
+
// rendered disclosure controls so React and the navigation owner remain
|
|
920
|
+
// synchronized; never edit the hidden attribute or synthesize beforematch.
|
|
921
|
+
var row = element.closest('[data-chat-turn]');
|
|
922
|
+
var turn = row && row.getAttribute('data-chat-turn');
|
|
923
|
+
var buttons = target.root.querySelectorAll('button[data-turn-process][aria-expanded="false"]');
|
|
924
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
925
|
+
var owner = buttons[i].closest('[data-chat-turn]');
|
|
926
|
+
if (turn && owner && owner.getAttribute('data-chat-turn') === turn && activateDisclosure(buttons[i], activated)) return true;
|
|
927
|
+
}
|
|
928
|
+
// Prefer the disclosure's semantic aria-controls relationship for
|
|
929
|
+
// nested groups. Walk outermost first because an inner control can itself
|
|
930
|
+
// still be inside a folded outer Turn.
|
|
931
|
+
buttons = target.root.querySelectorAll('button[aria-controls][aria-expanded="false"]');
|
|
932
|
+
for (var h = ancestors.length - 1; h >= 0; h--) {
|
|
933
|
+
if (!ancestors[h].id) continue;
|
|
934
|
+
for (i = 0; i < buttons.length; i++) {
|
|
935
|
+
if (buttons[i].getAttribute('aria-controls') === ancestors[h].id && activateDisclosure(buttons[i], activated)) return true;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
// Older supported hosts expose the same group disclosure without
|
|
939
|
+
// aria-controls, but keep its stable group/activity markers.
|
|
940
|
+
var group = row && row.closest('[data-chat-group-key]');
|
|
941
|
+
var button = group && group.querySelector('button[data-process-activity][aria-expanded="false"]');
|
|
942
|
+
if (activateDisclosure(button, activated)) return true;
|
|
943
|
+
return changed;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function returnFailure(reason) {
|
|
947
|
+
return L.returnFailed + ' ' + (L['return_' + reason] || L.return_unavailable);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
async function returnToOrigin() {
|
|
951
|
+
var origin = state.origin;
|
|
952
|
+
if (state.restoring || !originValid(origin) || !origin.navigated) return;
|
|
953
|
+
cancelPageIn(); cancelNavigation();
|
|
954
|
+
var task = { origin: origin };
|
|
955
|
+
state.restoring = task;
|
|
956
|
+
setStatus(L.returning, false); updateReturnButton();
|
|
957
|
+
function cancelled() { return state.restoring !== task || !originValid(origin); }
|
|
958
|
+
function finishFailure(reason) {
|
|
959
|
+
if (cancelled()) return;
|
|
960
|
+
state.restoring = null;
|
|
961
|
+
if (reason === 'removed') state.origin = null;
|
|
962
|
+
setStatus(returnFailure(reason), false); updateReturnButton();
|
|
963
|
+
}
|
|
964
|
+
function nodeState() {
|
|
965
|
+
if (origin.face && origin.face.getSnapshot().removed) return 'removed';
|
|
966
|
+
if (origin.store.get(origin.nodeKey)) return 'loaded';
|
|
967
|
+
var nodes = origin.store.values();
|
|
968
|
+
var first = nodes.reduce(function (min, node) { return Math.min(min, node.anchorSeq); }, Infinity);
|
|
969
|
+
// Materialized Chat nodes do not expose the event window's lower bound.
|
|
970
|
+
// Earlier missing content permits a bounded load attempt, not a deletion claim.
|
|
971
|
+
return first > origin.seq ? 'missing' : 'unavailable';
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
var pages = 0;
|
|
975
|
+
while (!cancelled() && nodeState() === 'missing') {
|
|
976
|
+
if (!origin.face) { finishFailure('unavailable'); return; }
|
|
977
|
+
if (pages >= MAX_PAGES) { finishFailure('capped'); return; }
|
|
978
|
+
var ready = await waitForPage(function () {
|
|
979
|
+
if (pendingLoads.has(origin.face)) return null;
|
|
980
|
+
return facePageState(origin.face);
|
|
981
|
+
}, cancelled);
|
|
982
|
+
if (cancelled()) return;
|
|
983
|
+
if (ready.reason) { finishFailure(ready.reason); return; }
|
|
984
|
+
if (nodeState() !== 'missing') break;
|
|
985
|
+
if (ready.hasMore !== true) { finishFailure('unavailable'); return; }
|
|
986
|
+
var before = origin.store.values().map(function (node) { return node.key + ':' + node.anchorSeq; }).join('|');
|
|
987
|
+
var loaded = await loadPage(origin.face, cancelled);
|
|
988
|
+
if (cancelled()) return;
|
|
989
|
+
if (loaded.reason) { finishFailure(loaded.reason); return; }
|
|
990
|
+
var progress = await waitForPage(function () {
|
|
991
|
+
if (nodeState() !== 'missing') return {};
|
|
992
|
+
var after = origin.store.values().map(function (node) { return node.key + ':' + node.anchorSeq; }).join('|');
|
|
993
|
+
return before !== after ? {} : null;
|
|
994
|
+
}, cancelled);
|
|
995
|
+
if (cancelled()) return;
|
|
996
|
+
if (progress.reason) { finishFailure(progress.reason); return; }
|
|
997
|
+
pages += 1;
|
|
998
|
+
}
|
|
999
|
+
if (cancelled()) return;
|
|
1000
|
+
if (nodeState() !== 'loaded') { finishFailure(nodeState()); return; }
|
|
1001
|
+
var navigationReady = await waitForPage(function () {
|
|
1002
|
+
var status = origin.face && facePageState(origin.face);
|
|
1003
|
+
if (status && status.reason) return status;
|
|
1004
|
+
return hostNavigationReady(origin.target, origin.face) ? {} : null;
|
|
1005
|
+
}, cancelled);
|
|
1006
|
+
if (cancelled()) return;
|
|
1007
|
+
if (navigationReady.reason) { finishFailure(navigationReady.reason); return; }
|
|
1008
|
+
var originalRow = readingRow(origin);
|
|
1009
|
+
activateLoadedTurn(origin.target, origin.store, originalRow && originalRow.getAttribute('data-chat-turn'));
|
|
1010
|
+
if (cancelled()) return;
|
|
1011
|
+
var activated = new Set();
|
|
1012
|
+
var rendered = await waitForPage(function () {
|
|
1013
|
+
var row = readingRow(origin);
|
|
1014
|
+
var range = locateReading(origin, row);
|
|
1015
|
+
if (range && !rangeRect(range)) revealMatchRange(origin.target, range, activated);
|
|
1016
|
+
return range && rangeRect(range) ? { row: row, range: range } : null;
|
|
1017
|
+
}, cancelled);
|
|
1018
|
+
if (cancelled()) return;
|
|
1019
|
+
if (rendered.reason) { finishFailure('render'); return; }
|
|
1020
|
+
var inner = rendered.row.closest('[data-step-process-body]');
|
|
1021
|
+
if (inner && inner.scrollHeight > inner.clientHeight) {
|
|
1022
|
+
var group = inner.closest('[data-chat-group-key]');
|
|
1023
|
+
if (!group) { finishFailure('render'); return; }
|
|
1024
|
+
var innerRect = rangeRect(rendered.range), innerBox = inner.getBoundingClientRect();
|
|
1025
|
+
var sameGroup = origin.inner && group.getAttribute('data-chat-group-key') === origin.inner.key;
|
|
1026
|
+
var innerOffset = Math.max(1 - innerRect.height, Math.min(sameGroup ? origin.inner.top : 0, inner.clientHeight - 1));
|
|
1027
|
+
if (cancelled()) return;
|
|
1028
|
+
inner.scrollTop += innerRect.top - innerBox.top - innerOffset;
|
|
1029
|
+
}
|
|
1030
|
+
var viewport = readingViewport(origin.target), rect = rangeRect(rendered.range);
|
|
1031
|
+
if (!viewport || !rect || cancelled()) { finishFailure('unavailable'); return; }
|
|
1032
|
+
var offset = Math.max(1 - rect.height, Math.min(origin.top, viewport.bottom - viewport.top - 1));
|
|
1033
|
+
var desired = viewport.scroll.scrollTop + rect.top - viewport.top - offset;
|
|
1034
|
+
var top = Math.max(0, Math.min(viewport.scroll.scrollHeight - viewport.scroll.clientHeight, desired));
|
|
1035
|
+
var expectedOffset = offset + desired - top;
|
|
1036
|
+
var initialHeight = viewport.scroll.scrollHeight, initialWidth = viewport.scroll.getBoundingClientRect().width;
|
|
1037
|
+
// One instant landing, then observation. Never fight the host's own
|
|
1038
|
+
// retained paging anchor with repeated scroll writes.
|
|
1039
|
+
viewport.scroll.scrollTo({ top: top, behavior: 'instant' });
|
|
1040
|
+
var started = Date.now(), stable = null;
|
|
1041
|
+
var settled = await waitForPage(function () {
|
|
1042
|
+
var live = locateReading(origin, readingRow(origin));
|
|
1043
|
+
var bounds = readingViewport(origin.target), box = bounds && rangeRect(live, bounds);
|
|
1044
|
+
if (!box) { stable = null; return Date.now() - started >= 1000 ? { reason: 'position' } : null; }
|
|
1045
|
+
var current = box.top - bounds.top;
|
|
1046
|
+
var layoutChanged = bounds.scroll.scrollHeight !== initialHeight || bounds.scroll.getBoundingClientRect().width !== initialWidth;
|
|
1047
|
+
if (!layoutChanged && Math.abs(current - expectedOffset) > 3) return Date.now() - started >= 1000 ? { reason: 'position' } : null;
|
|
1048
|
+
if (!stable || Math.abs(stable.top - current) > 2) stable = { top: current, since: Date.now() };
|
|
1049
|
+
if (Date.now() - stable.since < 600) return null;
|
|
1050
|
+
return { row: readingRow(origin), range: live };
|
|
1051
|
+
}, cancelled);
|
|
1052
|
+
if (cancelled()) return;
|
|
1053
|
+
if (settled.reason) { finishFailure(settled.reason); return; }
|
|
1054
|
+
if (!rangeRect(settled.range, readingViewport(origin.target))) { finishFailure('position'); return; }
|
|
1055
|
+
state.restoring = null;
|
|
1056
|
+
origin.navigated = false;
|
|
1057
|
+
if (state.input) state.input.focus({ preventScroll: true });
|
|
1058
|
+
setStatus(L.returned, false);
|
|
1059
|
+
updateReturnButton();
|
|
1060
|
+
if (state.announcement) state.announcement.textContent = L.returned;
|
|
1061
|
+
} catch (error) { finishFailure('error'); }
|
|
1062
|
+
finally {
|
|
1063
|
+
if (state.restoring === task && !originValid(origin)) {
|
|
1064
|
+
state.restoring = null; state.origin = null; updateReturnButton();
|
|
1065
|
+
if (isOpen()) setStatus(returnFailure('unavailable'), false);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
//#endregion
|
|
1070
|
+
|
|
574
1071
|
//#region bar UI + state
|
|
575
1072
|
|
|
576
1073
|
var state = {
|
|
@@ -597,9 +1094,71 @@ window.__ModuleLoader__.load({
|
|
|
597
1094
|
pageToken: 0,
|
|
598
1095
|
pages: 0,
|
|
599
1096
|
sessionId: null,
|
|
600
|
-
sessionTimer: 0
|
|
1097
|
+
sessionTimer: 0,
|
|
1098
|
+
origin: null, restoring: null, returnBtn: null, announcement: null, navigationTimer: 0, navigationToken: 0,
|
|
1099
|
+
jumpStatus: null,
|
|
1100
|
+
positionObserver: null,
|
|
1101
|
+
positionFrame: 0
|
|
601
1102
|
};
|
|
602
1103
|
|
|
1104
|
+
function barAnchor(target) {
|
|
1105
|
+
if (!target || !target.anchor || !target.panel || !target.anchor.closest) return null;
|
|
1106
|
+
var header = target.anchor.closest('[data-conversation-header], header');
|
|
1107
|
+
if (header && target.panel.contains(header)) return header;
|
|
1108
|
+
// Older supported hosts may not expose the header data attribute. The
|
|
1109
|
+
// registered utility still lives in the header's direct child of the panel.
|
|
1110
|
+
var child = target.anchor;
|
|
1111
|
+
while (child && child.parentElement && child.parentElement !== target.panel) child = child.parentElement;
|
|
1112
|
+
return child && child.parentElement === target.panel ? child : null;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function positionBar() {
|
|
1116
|
+
if (!isOpen() || !state.bar) return false;
|
|
1117
|
+
if (!state.target) { state.bar.removeAttribute("data-positioned"); return false; }
|
|
1118
|
+
var header = barAnchor(state.target);
|
|
1119
|
+
if (!header || !header.isConnected || !state.target.panel.isConnected) {
|
|
1120
|
+
state.bar.removeAttribute("data-positioned");
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
var headerRect = header.getBoundingClientRect();
|
|
1124
|
+
var panelRect = state.target.panel.getBoundingClientRect();
|
|
1125
|
+
var viewportWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
1126
|
+
var panelLeft = Math.max(0, panelRect.left);
|
|
1127
|
+
var panelRight = Math.min(viewportWidth, panelRect.right);
|
|
1128
|
+
var dpr = window.devicePixelRatio || 1;
|
|
1129
|
+
function cssPixel(value) { return Math.round(value * dpr) / dpr + "px"; }
|
|
1130
|
+
state.bar.style.setProperty("--dsh-find-all-header-bottom", cssPixel(headerRect.bottom));
|
|
1131
|
+
state.bar.style.setProperty("--dsh-find-all-panel-right-space", cssPixel(Math.max(0, viewportWidth - panelRight)));
|
|
1132
|
+
state.bar.style.setProperty("--dsh-find-all-panel-width", cssPixel(Math.max(0, panelRight - panelLeft)));
|
|
1133
|
+
state.bar.setAttribute("data-positioned", "");
|
|
1134
|
+
return true;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function scheduleBarPosition() {
|
|
1138
|
+
if (!isOpen() || state.positionFrame) return;
|
|
1139
|
+
if (typeof requestAnimationFrame !== "function") { positionBar(); return; }
|
|
1140
|
+
state.positionFrame = requestAnimationFrame(function () {
|
|
1141
|
+
state.positionFrame = 0;
|
|
1142
|
+
positionBar();
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function stopBarPositioning() {
|
|
1147
|
+
if (state.positionObserver) { state.positionObserver.disconnect(); state.positionObserver = null; }
|
|
1148
|
+
if (state.positionFrame && typeof cancelAnimationFrame === "function") cancelAnimationFrame(state.positionFrame);
|
|
1149
|
+
state.positionFrame = 0;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function startBarPositioning() {
|
|
1153
|
+
stopBarPositioning();
|
|
1154
|
+
positionBar();
|
|
1155
|
+
var header = barAnchor(state.target);
|
|
1156
|
+
if (!header || typeof ResizeObserver === "undefined") return;
|
|
1157
|
+
state.positionObserver = new ResizeObserver(scheduleBarPosition);
|
|
1158
|
+
state.positionObserver.observe(header);
|
|
1159
|
+
if (state.target.panel !== header) state.positionObserver.observe(state.target.panel);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
603
1162
|
function paint() {
|
|
604
1163
|
if (!highlightsSupported()) return;
|
|
605
1164
|
CSS.highlights.delete(HL_ALL);
|
|
@@ -617,11 +1176,17 @@ window.__ModuleLoader__.load({
|
|
|
617
1176
|
var n = state.ranges.length;
|
|
618
1177
|
if (!state.query) state.count.textContent = "";
|
|
619
1178
|
else if (n === 0) state.count.textContent = state.paging ? "…" : "0/0";
|
|
1179
|
+
else if (state.index < 0) state.count.textContent = L.resultCount.replace("{n}", String(n) + (state.capped ? "+" : ""));
|
|
620
1180
|
else state.count.textContent = (state.index + 1) + "/" + n + (state.capped ? "+" : "");
|
|
1181
|
+
var cap = state.query && n > 0 && state.capped ? L.cap.replace("{n}", String(MAX_MATCHES)) : "";
|
|
1182
|
+
state.count.title = cap;
|
|
1183
|
+
if (cap) state.count.setAttribute("aria-label", state.count.textContent + "; " + cap);
|
|
1184
|
+
else state.count.removeAttribute("aria-label");
|
|
621
1185
|
}
|
|
622
1186
|
|
|
623
1187
|
function setStatus(text, busy) {
|
|
624
1188
|
if (!state.status) return;
|
|
1189
|
+
state.jumpStatus = null;
|
|
625
1190
|
state.status.textContent = text || "";
|
|
626
1191
|
state.status.title = busy ? L.stopTip : (text || "");
|
|
627
1192
|
if (busy) state.status.setAttribute("data-busy", "1");
|
|
@@ -629,6 +1194,11 @@ window.__ModuleLoader__.load({
|
|
|
629
1194
|
updateCount();
|
|
630
1195
|
}
|
|
631
1196
|
|
|
1197
|
+
function setJumpStatus(base) {
|
|
1198
|
+
setStatus(L.jumpHint.replace("{n}", String(state.ranges.length)) + (base ? " " + base : ""), false);
|
|
1199
|
+
state.jumpStatus = base || "";
|
|
1200
|
+
}
|
|
1201
|
+
|
|
632
1202
|
function updateScopeButton() {
|
|
633
1203
|
if (!state.scopeBtn) return;
|
|
634
1204
|
var whole = state.scope === "whole";
|
|
@@ -738,6 +1308,7 @@ window.__ModuleLoader__.load({
|
|
|
738
1308
|
var retained = keepIndex && valid ? retainedIndex(state.selection) : -1;
|
|
739
1309
|
if (!state.ranges.length) state.index = -1;
|
|
740
1310
|
else if (!keepIndex) state.index = 0;
|
|
1311
|
+
else if (state.index < 0) state.index = -1;
|
|
741
1312
|
else state.index = retained >= 0 ? retained : Math.max(0, Math.min(state.index, state.ranges.length - 1));
|
|
742
1313
|
rememberSelection();
|
|
743
1314
|
paint();
|
|
@@ -746,14 +1317,15 @@ window.__ModuleLoader__.load({
|
|
|
746
1317
|
}
|
|
747
1318
|
|
|
748
1319
|
function goTo(delta) {
|
|
1320
|
+
cancelReturn(L.returnCancelled);
|
|
749
1321
|
if (!state.adapter || !state.adapter.valid(state.target)) { runSearch(false); return; }
|
|
750
1322
|
var n = state.ranges.length;
|
|
751
1323
|
if (n === 0) return;
|
|
752
|
-
state.index = ((state.index + delta) % n + n) % n;
|
|
1324
|
+
state.index = state.index < 0 ? (delta < 0 ? n - 1 : 0) : ((state.index + delta) % n + n) % n;
|
|
753
1325
|
rememberSelection();
|
|
754
1326
|
paint();
|
|
755
1327
|
updateCount();
|
|
756
|
-
|
|
1328
|
+
navigateToRange(state.ranges[state.index]);
|
|
757
1329
|
}
|
|
758
1330
|
|
|
759
1331
|
function scheduleRescan() {
|
|
@@ -854,26 +1426,34 @@ window.__ModuleLoader__.load({
|
|
|
854
1426
|
if (result.reason === "done" && (!terminal || terminal.reason || terminal.hasMore !== false || pendingLoads.has(face))) result.reason = "stalled";
|
|
855
1427
|
state.paging = false;
|
|
856
1428
|
state.pages = result.pages;
|
|
857
|
-
if (result.reason === "capped") setStatus(L.capped.replace("{n}", String(result.pages)), false);
|
|
858
|
-
else if (result.reason !== "done") setStatus(L.incomplete, false);
|
|
859
|
-
else setStatus(L.available, false);
|
|
860
1429
|
runSearch(true);
|
|
1430
|
+
if (!state.adapter || !state.adapter.valid(target)) return;
|
|
1431
|
+
var message = result.reason === "capped" ? L.capped.replace("{n}", String(result.pages)) :
|
|
1432
|
+
result.reason !== "done" ? L.incomplete : L.available;
|
|
1433
|
+
if (state.ranges.length > 0 && originValid(state.origin) && !state.origin.navigated) setJumpStatus(message);
|
|
1434
|
+
else setStatus(message, false);
|
|
861
1435
|
});
|
|
862
1436
|
}
|
|
863
1437
|
|
|
864
|
-
function syncTarget(target, passive
|
|
1438
|
+
function syncTarget(target, passive) {
|
|
865
1439
|
if (!state.adapter) return;
|
|
866
|
-
var selected = passive ? state.adapter.refresh(state.target) : state.adapter.select(target || document.activeElement, state.target
|
|
867
|
-
if (selected && state.target && selected.id === state.target.id && selected.anchor === state.target.anchor && selected.root === state.target.root)
|
|
1440
|
+
var selected = passive ? state.adapter.refresh(state.target) : state.adapter.select(target || document.activeElement, state.target);
|
|
1441
|
+
if (selected && state.target && selected.id === state.target.id && selected.anchor === state.target.anchor && selected.root === state.target.root && selected.panel === state.target.panel && selected.registration === state.target.registration) {
|
|
1442
|
+
if (state.origin && !originValid(state.origin)) { discardOrigin(); setStatus(returnFailure('unavailable'), false); }
|
|
1443
|
+
scheduleBarPosition();
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
868
1446
|
if (!selected && !state.target) {
|
|
869
1447
|
if (isOpen()) runSearch(false);
|
|
870
1448
|
return;
|
|
871
1449
|
}
|
|
1450
|
+
discardOrigin();
|
|
872
1451
|
cancelPageIn();
|
|
873
1452
|
stopObserver();
|
|
874
1453
|
state.target = selected;
|
|
875
1454
|
state.sessionId = selected ? selected.id : null;
|
|
876
1455
|
if (!isOpen()) return;
|
|
1456
|
+
startBarPositioning();
|
|
877
1457
|
setStatus(selected ? "" : unavailableStatus(), false);
|
|
878
1458
|
runSearch(false);
|
|
879
1459
|
startObserver();
|
|
@@ -916,15 +1496,17 @@ window.__ModuleLoader__.load({
|
|
|
916
1496
|
}
|
|
917
1497
|
|
|
918
1498
|
function onQueryInput(input) {
|
|
1499
|
+
cancelReturn(L.returnCancelled); cancelNavigation();
|
|
919
1500
|
state.query = input.value;
|
|
920
1501
|
cancelPageIn();
|
|
921
1502
|
setStatus("", false);
|
|
922
1503
|
runSearch(false);
|
|
923
|
-
if (state.ranges.length > 0 && highlightsSupported())
|
|
1504
|
+
if (state.ranges.length > 0 && highlightsSupported()) navigateToRange(state.ranges[state.index]);
|
|
924
1505
|
if (state.scope === "whole" && state.query) schedulePageIn();
|
|
925
1506
|
}
|
|
926
1507
|
|
|
927
1508
|
function toggleScope() {
|
|
1509
|
+
cancelReturn(L.returnCancelled);
|
|
928
1510
|
state.scope = state.scope === "whole" ? "page" : "whole";
|
|
929
1511
|
updateScopeButton();
|
|
930
1512
|
if (state.scope === "page") {
|
|
@@ -940,6 +1522,8 @@ window.__ModuleLoader__.load({
|
|
|
940
1522
|
var bar = document.createElement("div");
|
|
941
1523
|
bar.id = BAR_ID;
|
|
942
1524
|
bar.setAttribute("role", "search");
|
|
1525
|
+
var controls = document.createElement("div");
|
|
1526
|
+
controls.className = "controls";
|
|
943
1527
|
|
|
944
1528
|
var input = document.createElement("input");
|
|
945
1529
|
input.type = "text";
|
|
@@ -954,10 +1538,15 @@ window.__ModuleLoader__.load({
|
|
|
954
1538
|
|
|
955
1539
|
var count = document.createElement("span");
|
|
956
1540
|
count.className = "count";
|
|
1541
|
+
count.setAttribute("role", "status");
|
|
957
1542
|
count.setAttribute("aria-live", "polite");
|
|
1543
|
+
count.setAttribute("aria-atomic", "true");
|
|
958
1544
|
|
|
959
1545
|
var status = document.createElement("span");
|
|
960
1546
|
status.className = "status";
|
|
1547
|
+
status.setAttribute("role", "status");
|
|
1548
|
+
status.setAttribute("aria-live", "polite");
|
|
1549
|
+
status.setAttribute("aria-atomic", "true");
|
|
961
1550
|
status.title = L.stopTip;
|
|
962
1551
|
status.addEventListener("click", function () {
|
|
963
1552
|
if (!state.paging) return;
|
|
@@ -965,13 +1554,20 @@ window.__ModuleLoader__.load({
|
|
|
965
1554
|
setStatus(L.incomplete, false);
|
|
966
1555
|
});
|
|
967
1556
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1557
|
+
controls.appendChild(input);
|
|
1558
|
+
controls.appendChild(scopeBtn);
|
|
1559
|
+
controls.appendChild(count);
|
|
1560
|
+
controls.appendChild(button(L.prev, "m18 15-6-6-6 6", function () { goTo(-1); }));
|
|
1561
|
+
controls.appendChild(button(L.next, "m6 9 6 6 6-6", function () { goTo(1); }));
|
|
1562
|
+
var returnBtn = document.createElement('button');
|
|
1563
|
+
returnBtn.type = 'button'; returnBtn.setAttribute('data-find-all-return', '');
|
|
1564
|
+
returnBtn.hidden = true;
|
|
1565
|
+
returnBtn.addEventListener('click', returnToOrigin);
|
|
1566
|
+
state.returnBtn = returnBtn;
|
|
1567
|
+
controls.appendChild(returnBtn);
|
|
1568
|
+
controls.appendChild(button(L.close, "M18 6 6 18M6 6l12 12", close));
|
|
1569
|
+
bar.appendChild(controls);
|
|
973
1570
|
bar.appendChild(status);
|
|
974
|
-
bar.appendChild(button(L.close, "M18 6 6 18M6 6l12 12", close));
|
|
975
1571
|
|
|
976
1572
|
state.bar = bar;
|
|
977
1573
|
state.input = input;
|
|
@@ -980,13 +1576,24 @@ window.__ModuleLoader__.load({
|
|
|
980
1576
|
state.scopeBtn = scopeBtn;
|
|
981
1577
|
updateScopeButton();
|
|
982
1578
|
document.body.appendChild(bar);
|
|
1579
|
+
var announcement = document.createElement('span');
|
|
1580
|
+
announcement.setAttribute('role', 'status'); announcement.setAttribute('aria-live', 'polite');
|
|
1581
|
+
announcement.setAttribute('aria-atomic', 'true');
|
|
1582
|
+
announcement.style.cssText = 'position:fixed;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)';
|
|
1583
|
+
state.announcement = announcement;
|
|
1584
|
+
document.body.appendChild(announcement);
|
|
983
1585
|
}
|
|
984
1586
|
|
|
985
1587
|
function open(target) {
|
|
986
1588
|
if (typeof document === "undefined" || document.body === null) return;
|
|
987
|
-
|
|
1589
|
+
var wasOpen = isOpen();
|
|
1590
|
+
syncTarget(target || document.activeElement, false);
|
|
1591
|
+
if (!wasOpen) state.origin = captureReadingOrigin();
|
|
988
1592
|
if (!state.bar) buildBar();
|
|
989
1593
|
state.bar.style.display = "flex";
|
|
1594
|
+
startBarPositioning();
|
|
1595
|
+
updateReturnButton();
|
|
1596
|
+
if (state.announcement) state.announcement.textContent = "";
|
|
990
1597
|
var selection = "";
|
|
991
1598
|
try { selection = String(window.getSelection()).trim(); } catch (error) {}
|
|
992
1599
|
if (state.target && selection && selection.length <= 200 && selection !== state.query) {
|
|
@@ -996,15 +1603,20 @@ window.__ModuleLoader__.load({
|
|
|
996
1603
|
state.input.focus();
|
|
997
1604
|
state.input.select();
|
|
998
1605
|
runSearch(false);
|
|
1606
|
+
if (!state.query && state.target && state.adapter && state.adapter.valid(state.target) && !state.origin) {
|
|
1607
|
+
setStatus(L.originUnavailable, false);
|
|
1608
|
+
}
|
|
999
1609
|
startObserver();
|
|
1000
1610
|
watchSession();
|
|
1001
1611
|
if (state.scope === "whole" && state.query) schedulePageIn();
|
|
1002
1612
|
}
|
|
1003
1613
|
|
|
1004
1614
|
function close() {
|
|
1615
|
+
discardOrigin();
|
|
1005
1616
|
cancelPageIn();
|
|
1006
1617
|
stopWatchingSession();
|
|
1007
|
-
|
|
1618
|
+
stopBarPositioning();
|
|
1619
|
+
if (state.bar) { state.bar.style.display = "none"; state.bar.removeAttribute("data-positioned"); }
|
|
1008
1620
|
state.ranges = [];
|
|
1009
1621
|
state.index = -1;
|
|
1010
1622
|
state.selection = null;
|
|
@@ -1020,21 +1632,29 @@ window.__ModuleLoader__.load({
|
|
|
1020
1632
|
|
|
1021
1633
|
function apply(ctx) {
|
|
1022
1634
|
var react = require("react");
|
|
1635
|
+
var ui = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
1636
|
+
var SearchIcon = ui.IconSearchOutlineRegular || ui.IconSearchOutline16;
|
|
1023
1637
|
state.ctx = ctx;
|
|
1024
1638
|
state.adapter = createHostAdapter(ctx, function () {
|
|
1025
|
-
syncTarget(null, true);
|
|
1639
|
+
if (isOpen()) syncTarget(null, true);
|
|
1026
1640
|
});
|
|
1027
1641
|
function SessionFind(props) {
|
|
1028
1642
|
var anchor = react.useRef(null);
|
|
1643
|
+
var nodes = typeof props.useChat === 'function' ? props.useChat(function (snapshot) { return snapshot.nodes; }) : null;
|
|
1029
1644
|
react.useEffect(function () {
|
|
1030
1645
|
if (!anchor.current || !props.sessionId) return;
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1646
|
+
// Older supported Button exports do not forward refs. Always
|
|
1647
|
+
// register the real button rather than the sizing-neutral wrapper.
|
|
1648
|
+
var button = anchor.current.querySelector("button[data-find-all-session]");
|
|
1649
|
+
if (button) return state.adapter.mount(button, props.sessionId, nodes);
|
|
1650
|
+
}, [props.sessionId, nodes]);
|
|
1651
|
+
return react.createElement("span", { ref: anchor, style: { display: "inline-flex", flex: "none" } },
|
|
1652
|
+
react.createElement(ui.Button, {
|
|
1653
|
+
type: "button", size: "sm", variant: "ghost", title: L.find, "aria-label": L.find,
|
|
1654
|
+
style: { flex: "none", width: 28, padding: 0, color: "var(--dsw-alias-label-secondary)" },
|
|
1035
1655
|
"data-find-all-session": props.sessionId,
|
|
1036
1656
|
onClick: function (event) { open(event.currentTarget); }
|
|
1037
|
-
},
|
|
1657
|
+
}, react.createElement(SearchIcon, { size: 15 })));
|
|
1038
1658
|
}
|
|
1039
1659
|
ctx.slots.inject("conversation.session.header.utilities", function () {
|
|
1040
1660
|
return ctx.slots.register({ name: "conversation.session.header.utilities", id: "dsh-find-all" }, SessionFind);
|
|
@@ -1043,21 +1663,50 @@ window.__ModuleLoader__.load({
|
|
|
1043
1663
|
injectCss();
|
|
1044
1664
|
var handler = createHandlers({
|
|
1045
1665
|
open: open, close: close, goTo: goTo, isOpen: isOpen,
|
|
1666
|
+
ownsKeys: function (event) {
|
|
1667
|
+
var path = typeof event.composedPath === 'function' ? event.composedPath() : [event.target];
|
|
1668
|
+
return !path.some(function (node) {
|
|
1669
|
+
return node && node.closest && node.closest('dialog[open], [aria-modal="true"], .monaco-editor, .cm-editor, .CodeMirror');
|
|
1670
|
+
});
|
|
1671
|
+
},
|
|
1046
1672
|
isBarInput: function (target) { return target === state.input; }
|
|
1047
1673
|
});
|
|
1048
1674
|
function focus(event) {
|
|
1049
1675
|
if (!state.bar || !state.bar.contains(event.target)) syncTarget(event.target);
|
|
1050
1676
|
}
|
|
1677
|
+
function readingFocus(event) {
|
|
1678
|
+
// Reader activity may refresh the registered main frame, but cannot
|
|
1679
|
+
// select an embedded sidebar or change the search scope.
|
|
1680
|
+
if (event.isTrusted && event.target && event.target.closest && event.target.closest('[data-conversation-scroll]')) focus(event);
|
|
1681
|
+
}
|
|
1682
|
+
function interruptReading(event) {
|
|
1683
|
+
if (!state.restoring || !state.target) return;
|
|
1684
|
+
var viewport = readingViewport(state.target);
|
|
1685
|
+
if (!viewport || !viewport.scroll.contains(event.target)) return;
|
|
1686
|
+
if (event.type === 'keydown' && !/^(ArrowUp|ArrowDown|PageUp|PageDown|Home|End| )$/.test(event.key)) return;
|
|
1687
|
+
if (event.target.closest('input, textarea, [contenteditable="true"]')) return;
|
|
1688
|
+
cancelReturn(L.returnCancelled);
|
|
1689
|
+
}
|
|
1690
|
+
['wheel', 'touchstart', 'pointerdown', 'keydown'].forEach(function (type) { window.addEventListener(type, interruptReading, true); });
|
|
1051
1691
|
window.addEventListener("keydown", handler, true);
|
|
1052
1692
|
window.addEventListener("focusin", focus, true);
|
|
1053
1693
|
window.addEventListener("pointerdown", focus, true);
|
|
1694
|
+
window.addEventListener("wheel", readingFocus, true);
|
|
1695
|
+
window.addEventListener("resize", scheduleBarPosition);
|
|
1696
|
+
window.addEventListener("scroll", scheduleBarPosition, true);
|
|
1054
1697
|
return function () {
|
|
1698
|
+
['wheel', 'touchstart', 'pointerdown', 'keydown'].forEach(function (type) { window.removeEventListener(type, interruptReading, true); });
|
|
1055
1699
|
window.removeEventListener("keydown", handler, true);
|
|
1056
1700
|
window.removeEventListener("focusin", focus, true);
|
|
1057
1701
|
window.removeEventListener("pointerdown", focus, true);
|
|
1702
|
+
window.removeEventListener("wheel", readingFocus, true);
|
|
1703
|
+
window.removeEventListener("resize", scheduleBarPosition);
|
|
1704
|
+
window.removeEventListener("scroll", scheduleBarPosition, true);
|
|
1058
1705
|
close();
|
|
1059
1706
|
state.query = "";
|
|
1060
1707
|
if (state.bar) state.bar.remove();
|
|
1708
|
+
if (state.announcement) state.announcement.remove();
|
|
1709
|
+
state.announcement = state.returnBtn = null;
|
|
1061
1710
|
var style = document.querySelector('style[data-plugin-css="dsh-find-all/bar.css"]');
|
|
1062
1711
|
if (style) style.remove();
|
|
1063
1712
|
state.bar = state.input = state.count = state.status = state.scopeBtn = null;
|