@ryuu-64/dsh-find-all 0.1.4 → 0.1.5
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 +130 -119
- package/LICENSE +22 -22
- package/README.fork-origin.md +48 -48
- package/README.md +31 -3
- package/docs/compatibility.md +68 -0
- package/lib/client.js +306 -94
- package/package.json +83 -76
package/lib/client.js
CHANGED
|
@@ -32,8 +32,8 @@
|
|
|
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.
|
|
35
|
-
// *
|
|
36
|
-
// window.find()
|
|
35
|
+
// * Without CSS Highlights, navigation still scrolls only scoped ranges.
|
|
36
|
+
// Browser-wide window.find() is deliberately never used.
|
|
37
37
|
//
|
|
38
38
|
// Keyboard:
|
|
39
39
|
// Cmd/Ctrl+F open (prefilled with the current selection)
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
window.__ModuleLoader__.load({
|
|
45
45
|
id: "@ryuu-64/dsh-find-all",
|
|
46
|
-
factory: () => {
|
|
46
|
+
factory: (require) => {
|
|
47
47
|
var module = { exports: {} };
|
|
48
48
|
var exports = module.exports;
|
|
49
49
|
|
|
@@ -76,7 +76,11 @@ window.__ModuleLoader__.load({
|
|
|
76
76
|
capped: "已到加载上限({n} 页),停止加载",
|
|
77
77
|
stalled: "没有更早的历史了",
|
|
78
78
|
stopTip: "点击停止加载",
|
|
79
|
-
noService: "
|
|
79
|
+
noService: "会话接口不可用,仅保留已显示的部分结果",
|
|
80
|
+
noTarget: "请先选择可见的会话,再打开查找",
|
|
81
|
+
noRoot: "当前会话的搜索范围不可用",
|
|
82
|
+
incomplete: "历史尚未搜完,保留部分结果",
|
|
83
|
+
find: "查找会话"
|
|
80
84
|
} : {
|
|
81
85
|
placeholder: "Find in conversation…",
|
|
82
86
|
prev: "Previous match",
|
|
@@ -92,7 +96,11 @@ window.__ModuleLoader__.load({
|
|
|
92
96
|
capped: "Page cap reached ({n}), stopped loading",
|
|
93
97
|
stalled: "No earlier history",
|
|
94
98
|
stopTip: "Click to stop loading",
|
|
95
|
-
noService: "Session service unavailable
|
|
99
|
+
noService: "Session service unavailable; rendered results only",
|
|
100
|
+
noTarget: "Select a visible conversation before opening find",
|
|
101
|
+
noRoot: "Search scope unavailable for this conversation",
|
|
102
|
+
incomplete: "History search incomplete; partial results retained",
|
|
103
|
+
find: "Find in conversation"
|
|
96
104
|
};
|
|
97
105
|
|
|
98
106
|
//#region pure helpers (exported for tests)
|
|
@@ -121,7 +129,7 @@ window.__ModuleLoader__.load({
|
|
|
121
129
|
if (meta && (key === "f" || key === "F")) {
|
|
122
130
|
event.preventDefault();
|
|
123
131
|
event.stopPropagation();
|
|
124
|
-
ops.open();
|
|
132
|
+
ops.open(event.target);
|
|
125
133
|
return;
|
|
126
134
|
}
|
|
127
135
|
if (!ops.isOpen()) return;
|
|
@@ -239,10 +247,10 @@ window.__ModuleLoader__.load({
|
|
|
239
247
|
return into;
|
|
240
248
|
}
|
|
241
249
|
|
|
242
|
-
function collectRanges(query) {
|
|
250
|
+
function collectRanges(query, root) {
|
|
243
251
|
var ranges = [];
|
|
244
|
-
if (!query || typeof document === "undefined"
|
|
245
|
-
var nodes = collectTextNodes(
|
|
252
|
+
if (!query || !root || typeof document === "undefined") return ranges;
|
|
253
|
+
var nodes = collectTextNodes(root, []);
|
|
246
254
|
for (var i = 0; i < nodes.length && ranges.length < MAX_MATCHES; i++) {
|
|
247
255
|
var node = nodes[i];
|
|
248
256
|
var indices = findIndices(node.data, query);
|
|
@@ -270,48 +278,195 @@ window.__ModuleLoader__.load({
|
|
|
270
278
|
}
|
|
271
279
|
}
|
|
272
280
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
281
|
+
//#region host adapter
|
|
282
|
+
// Official session-scoped header utility props supply identity. DOM selectors
|
|
283
|
+
// are intentionally isolated here: see docs/compatibility.md for tag sources.
|
|
284
|
+
// No global current-session state and no page-wide search fallback.
|
|
285
|
+
function visible(element) {
|
|
286
|
+
if (!element || !element.isConnected || !element.getClientRects().length) return false;
|
|
287
|
+
for (var node = element; node; node = node.parentElement) {
|
|
288
|
+
if (node.hidden || node.getAttribute("aria-hidden") === "true") return false;
|
|
289
|
+
var css = window.getComputedStyle(node);
|
|
290
|
+
if (css.display === "none" || css.visibility === "hidden" || css.visibility === "collapse" || css.opacity === "0") return false;
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function conversationPanel(element) {
|
|
296
|
+
// The host composer has its own unrelated data-phase. A conversation
|
|
297
|
+
// root additionally owns the scrollport (official startup fixture).
|
|
298
|
+
for (var node = element; node && node !== document.body; node = node.parentElement) {
|
|
299
|
+
if (!node.hasAttribute || !node.hasAttribute("data-phase")) continue;
|
|
300
|
+
var phase = node.getAttribute("data-phase");
|
|
301
|
+
// Preserve even a broken declared panel boundary; never walk through
|
|
302
|
+
// it to a different ancestor conversation because its scroll is absent.
|
|
303
|
+
if (phase === "active" || phase === "hero" || phase === "settling" || node.querySelector('[data-conversation-scroll]')) return node;
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function resolveScope(anchor, sessionId) {
|
|
309
|
+
if (!anchor || typeof sessionId !== "string" || !sessionId || !visible(anchor)) return null;
|
|
310
|
+
var panel = conversationPanel(anchor);
|
|
311
|
+
if (!panel || panel.getAttribute("data-phase") !== "active" || panel === document.documentElement) return null;
|
|
312
|
+
var anchors = panel.querySelectorAll('[data-find-all-session]');
|
|
313
|
+
if (anchors.length !== 1 || anchors[0] !== anchor) return null;
|
|
314
|
+
var scrolls = panel.querySelectorAll('[data-conversation-scroll]');
|
|
315
|
+
if (scrolls.length !== 1) return null;
|
|
316
|
+
var content = scrolls[0].closest('[data-conversation-content]');
|
|
317
|
+
// 0.1.6-alpha.2 has the content wrapper but no identity attribute.
|
|
318
|
+
// Identity still comes from the unique session-scoped header anchor.
|
|
319
|
+
if (content && content.hasAttribute('data-conversation-session') && content.getAttribute('data-conversation-session') !== sessionId) return null;
|
|
320
|
+
var flows = scrolls[0].querySelectorAll('[data-chat-flow]');
|
|
321
|
+
if (flows.length !== 1 || !visible(flows[0])) return null;
|
|
322
|
+
return { panel: panel, root: flows[0] };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function createHostAdapter(ctx, changed) {
|
|
326
|
+
var entries = new Map();
|
|
327
|
+
var scopeUnavailable = false;
|
|
328
|
+
// Only a never-selected adapter may discover an initial neutral context.
|
|
329
|
+
// Explicit rejection or loss of a selection must never select a substitute.
|
|
330
|
+
var selectionState = "pristine";
|
|
331
|
+
function candidates() {
|
|
332
|
+
var out = [];
|
|
333
|
+
entries.forEach(function (id, anchor) {
|
|
334
|
+
// Keep unsupported/invisible instances in the identity roster. Dropping
|
|
335
|
+
// them before routing could silently choose a different valid session.
|
|
336
|
+
out.push({ id: id, anchor: anchor, panel: conversationPanel(anchor), scope: resolveScope(anchor, id) });
|
|
337
|
+
});
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
function choose(entry) {
|
|
341
|
+
if (!entry) return null;
|
|
342
|
+
if (!entry.scope) { selectionState = "rejected"; scopeUnavailable = true; return null; }
|
|
343
|
+
selectionState = "selected";
|
|
344
|
+
return { id: entry.id, anchor: entry.anchor, panel: entry.scope.panel, root: entry.scope.root };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function visibleEntry(entry) {
|
|
348
|
+
var container = entry.panel || entry.anchor.closest('[data-phase], [data-conversation-content]');
|
|
349
|
+
return visible(entry.anchor) || visible(container);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function discoverInitial(all) {
|
|
353
|
+
var shown = all.filter(visibleEntry);
|
|
354
|
+
var nodes = Array.prototype.filter.call(document.querySelectorAll('[data-phase], [data-conversation-content], [data-conversation-scroll], [data-chat-flow]'), visible);
|
|
355
|
+
scopeUnavailable = false;
|
|
356
|
+
if (shown.length === 0) {
|
|
357
|
+
// An empty resident Hero is not a session choice. A known active
|
|
358
|
+
// or identified but unregistered view is unavailable, not pristine.
|
|
359
|
+
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'); })) {
|
|
360
|
+
selectionState = "rejected"; scopeUnavailable = true;
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
if (shown.length !== 1) { selectionState = "rejected"; return null; }
|
|
365
|
+
var entry = shown[0], scope = entry.scope;
|
|
366
|
+
if (!scope) return choose(entry);
|
|
367
|
+
var content = scope.root.closest('[data-conversation-content]');
|
|
368
|
+
var scroll = scope.root.closest('[data-conversation-scroll]');
|
|
369
|
+
// One main panel and its own content/scroll/flow count as one view.
|
|
370
|
+
// Any other visible known container, including a headerless embedded
|
|
371
|
+
// sidebar, makes neutral discovery ambiguous even if unsupported.
|
|
372
|
+
var other = nodes.some(function (node) {
|
|
373
|
+
if (node.hasAttribute('data-conversation-content') && node !== content) return true;
|
|
374
|
+
if (node.hasAttribute('data-conversation-scroll') && node !== scroll) return true;
|
|
375
|
+
if (node.hasAttribute('data-chat-flow') && node !== scope.root) return true;
|
|
376
|
+
return node.hasAttribute('data-phase') && conversationPanel(node) === node && node !== scope.panel;
|
|
377
|
+
});
|
|
378
|
+
if (other) { selectionState = "rejected"; return null; }
|
|
379
|
+
return choose(entry);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function refresh(entry) {
|
|
383
|
+
if (!entry) return null;
|
|
384
|
+
// Passive checks and repeated shortcuts may only retain this exact
|
|
385
|
+
// registered instance and identity. They never discover a substitute.
|
|
386
|
+
if (entries.get(entry.anchor) !== entry.id) { selectionState = "rejected"; scopeUnavailable = true; return null; }
|
|
387
|
+
scopeUnavailable = false;
|
|
388
|
+
return choose({ id: entry.id, anchor: entry.anchor, scope: resolveScope(entry.anchor, entry.id) });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
mount: function (anchor, id) {
|
|
393
|
+
entries.set(anchor, id);
|
|
394
|
+
changed();
|
|
395
|
+
return function () { entries.delete(anchor); changed(); };
|
|
396
|
+
},
|
|
397
|
+
refresh: refresh,
|
|
398
|
+
select: function (target, previous, retainNeutral) {
|
|
399
|
+
var all = candidates();
|
|
400
|
+
if (!target || !target.nodeType) target = document.activeElement;
|
|
401
|
+
if (target && state.bar && state.bar.contains(target)) return refresh(previous);
|
|
402
|
+
if (retainNeutral && (target === document.body || target === document.documentElement)) {
|
|
403
|
+
if (previous || selectionState !== "pristine") return refresh(previous);
|
|
404
|
+
return discoverInitial(all);
|
|
405
|
+
}
|
|
406
|
+
scopeUnavailable = false;
|
|
407
|
+
var direct = all.filter(function (entry) { return target && entry.anchor.contains(target); });
|
|
408
|
+
if (direct.length === 1) return choose(direct[0]);
|
|
409
|
+
var content = target && target.closest ? target.closest('[data-conversation-content]') : null;
|
|
410
|
+
var panel = conversationPanel(target) || content;
|
|
411
|
+
var hit = all.filter(function (entry) { return panel && entry.panel === panel; });
|
|
412
|
+
if (hit.length === 1) {
|
|
413
|
+
if (content && hit[0].scope && hit[0].scope.root.closest('[data-conversation-content]') !== content) {
|
|
414
|
+
selectionState = "rejected"; scopeUnavailable = true; return null;
|
|
415
|
+
}
|
|
416
|
+
return choose(hit[0]);
|
|
417
|
+
}
|
|
418
|
+
if (panel) {
|
|
419
|
+
// Before any session is selected, focus in the resident unbound
|
|
420
|
+
// Hero/settling composer is not a rejected conversation choice.
|
|
421
|
+
var phase = panel.getAttribute('data-phase') || panel.getAttribute('data-content-phase');
|
|
422
|
+
var unbound = !panel.hasAttribute('data-conversation-session') && !panel.querySelector('[data-conversation-session]');
|
|
423
|
+
if (selectionState === "pristine" && !previous && unbound && !all.some(visibleEntry) && (phase === "hero" || phase === "settling")) return null;
|
|
424
|
+
selectionState = "rejected"; scopeUnavailable = true; return null;
|
|
425
|
+
}
|
|
426
|
+
if (previous) selectionState = "rejected";
|
|
427
|
+
return null;
|
|
428
|
+
},
|
|
429
|
+
scopeUnavailable: function () { return scopeUnavailable; },
|
|
430
|
+
valid: function (entry) {
|
|
431
|
+
if (!entry || entries.get(entry.anchor) !== entry.id) return false;
|
|
432
|
+
var scope = resolveScope(entry.anchor, entry.id);
|
|
433
|
+
return !!scope && scope.root === entry.root;
|
|
434
|
+
},
|
|
435
|
+
face: function (entry) {
|
|
436
|
+
try {
|
|
437
|
+
if (!this.valid(entry) || !ctx.sessions || typeof ctx.sessions.binding !== "function") return null;
|
|
438
|
+
var binding = ctx.sessions.binding(entry.id);
|
|
439
|
+
var face = binding && binding.session;
|
|
440
|
+
return face && typeof face.loadOlder === "function" ? face : null;
|
|
441
|
+
} catch (error) { return null; }
|
|
442
|
+
},
|
|
443
|
+
clear: function () { entries.clear(); }
|
|
444
|
+
};
|
|
277
445
|
}
|
|
278
446
|
|
|
279
|
-
|
|
280
|
-
|
|
447
|
+
function domSignature(root) {
|
|
448
|
+
return root ? collectTextNodes(root, []).length : 0;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function waitForChange(before, root, token) {
|
|
281
452
|
return new Promise(function (resolve) {
|
|
282
453
|
var started = Date.now();
|
|
454
|
+
function finish(value) {
|
|
455
|
+
clearTimeout(state.settleTimer);
|
|
456
|
+
state.settleTimer = 0;
|
|
457
|
+
state.settleCancel = null;
|
|
458
|
+
resolve(value);
|
|
459
|
+
}
|
|
460
|
+
state.settleCancel = function () { finish(false); };
|
|
283
461
|
(function poll() {
|
|
284
|
-
if (
|
|
285
|
-
if (
|
|
286
|
-
|
|
462
|
+
if (token !== state.pageToken || !state.adapter.valid(state.target)) { finish(false); return; }
|
|
463
|
+
if (domSignature(root) !== before) { finish(true); return; }
|
|
464
|
+
if (Date.now() - started >= PAGE_SETTLE_MS) { finish(false); return; }
|
|
465
|
+
state.settleTimer = setTimeout(poll, PAGE_POLL_MS);
|
|
287
466
|
})();
|
|
288
467
|
});
|
|
289
468
|
}
|
|
290
469
|
|
|
291
|
-
//#region session bridge
|
|
292
|
-
//
|
|
293
|
-
// The `sessions` client service is provided by
|
|
294
|
-
// @deepseek-ai/dsh-api-session-controller; the per-session face it binds
|
|
295
|
-
// exposes loadOlder() plus a snapshot carrying hasMore/loadingOlder.
|
|
296
|
-
// Everything here is defensive: a host that changes shape must degrade to
|
|
297
|
-
// page-only search, never break the bar.
|
|
298
|
-
|
|
299
|
-
function resolveFace(ctx) {
|
|
300
|
-
try {
|
|
301
|
-
var sessions = ctx && ctx.sessions;
|
|
302
|
-
if (!sessions || !sessions.list || typeof sessions.list.getSnapshot !== "function") return null;
|
|
303
|
-
var id = sessions.list.getSnapshot().current;
|
|
304
|
-
if (!id) return null;
|
|
305
|
-
if (typeof sessions.binding !== "function") return null;
|
|
306
|
-
var binding = sessions.binding(id);
|
|
307
|
-
var face = binding && binding.session ? binding.session : null;
|
|
308
|
-
if (!face || typeof face.loadOlder !== "function") return null;
|
|
309
|
-
return { id: id, face: face };
|
|
310
|
-
} catch (error) {
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
470
|
function faceHasMore(face) {
|
|
316
471
|
try {
|
|
317
472
|
if (typeof face.getSnapshot !== "function") return null;
|
|
@@ -327,6 +482,10 @@ window.__ModuleLoader__.load({
|
|
|
327
482
|
|
|
328
483
|
var state = {
|
|
329
484
|
ctx: null,
|
|
485
|
+
adapter: null,
|
|
486
|
+
target: null,
|
|
487
|
+
settleTimer: 0,
|
|
488
|
+
settleCancel: null,
|
|
330
489
|
bar: null,
|
|
331
490
|
input: null,
|
|
332
491
|
count: null,
|
|
@@ -384,32 +543,29 @@ window.__ModuleLoader__.load({
|
|
|
384
543
|
else state.scopeBtn.removeAttribute("data-whole");
|
|
385
544
|
}
|
|
386
545
|
|
|
546
|
+
function unavailableStatus() {
|
|
547
|
+
return state.target || (state.adapter && state.adapter.scopeUnavailable()) ? L.noRoot : L.noTarget;
|
|
548
|
+
}
|
|
549
|
+
|
|
387
550
|
function runSearch(keepIndex) {
|
|
388
|
-
state.
|
|
551
|
+
var valid = state.adapter && state.adapter.valid(state.target);
|
|
552
|
+
state.ranges = valid ? collectRanges(state.query, state.target.root) : [];
|
|
553
|
+
if (!valid) setStatus(unavailableStatus(), false);
|
|
389
554
|
state.capped = state.ranges.length >= MAX_MATCHES;
|
|
390
555
|
if (!keepIndex || state.index >= state.ranges.length) state.index = state.ranges.length > 0 ? 0 : -1;
|
|
391
556
|
paint();
|
|
392
557
|
updateCount();
|
|
393
|
-
|
|
558
|
+
|
|
394
559
|
}
|
|
395
560
|
|
|
396
561
|
function goTo(delta) {
|
|
562
|
+
if (!state.adapter || !state.adapter.valid(state.target)) { runSearch(false); return; }
|
|
397
563
|
var n = state.ranges.length;
|
|
398
564
|
if (n === 0) return;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
scrollToRange(state.ranges[state.index]);
|
|
404
|
-
} else {
|
|
405
|
-
legacyFind(state.query, delta < 0);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function legacyFind(query, backwards) {
|
|
410
|
-
if (typeof window.find === "function") {
|
|
411
|
-
try { window.find(query, false, backwards, true); } catch (error) {}
|
|
412
|
-
}
|
|
565
|
+
state.index = ((state.index + delta) % n + n) % n;
|
|
566
|
+
paint();
|
|
567
|
+
updateCount();
|
|
568
|
+
scrollToRange(state.ranges[state.index]);
|
|
413
569
|
}
|
|
414
570
|
|
|
415
571
|
function scheduleRescan() {
|
|
@@ -418,14 +574,14 @@ window.__ModuleLoader__.load({
|
|
|
418
574
|
if (state.paging) return;
|
|
419
575
|
clearTimeout(state.debounce);
|
|
420
576
|
state.debounce = setTimeout(function () {
|
|
421
|
-
if (
|
|
577
|
+
if (isOpen()) runSearch(true);
|
|
422
578
|
}, 250);
|
|
423
579
|
}
|
|
424
580
|
|
|
425
581
|
function startObserver() {
|
|
426
|
-
if (state.observer || typeof MutationObserver === "undefined") return;
|
|
582
|
+
if (state.observer || !state.target || typeof MutationObserver === "undefined") return;
|
|
427
583
|
state.observer = new MutationObserver(scheduleRescan);
|
|
428
|
-
state.observer.observe(
|
|
584
|
+
state.observer.observe(state.target.root, { childList: true, subtree: true, characterData: true });
|
|
429
585
|
}
|
|
430
586
|
|
|
431
587
|
function stopObserver() {
|
|
@@ -438,6 +594,8 @@ window.__ModuleLoader__.load({
|
|
|
438
594
|
|
|
439
595
|
function cancelPageIn() {
|
|
440
596
|
state.pageToken += 1;
|
|
597
|
+
if (state.settleCancel) state.settleCancel();
|
|
598
|
+
clearTimeout(state.settleTimer);
|
|
441
599
|
clearTimeout(state.pageTimer);
|
|
442
600
|
state.pageTimer = 0;
|
|
443
601
|
state.paging = false;
|
|
@@ -454,13 +612,18 @@ window.__ModuleLoader__.load({
|
|
|
454
612
|
function startPageIn() {
|
|
455
613
|
if (state.scope !== "whole" || !state.query || !isOpen()) return;
|
|
456
614
|
var token = ++state.pageToken;
|
|
457
|
-
var
|
|
458
|
-
if (!
|
|
615
|
+
var target = state.target;
|
|
616
|
+
if (!state.adapter || !state.adapter.valid(target)) {
|
|
617
|
+
setStatus(unavailableStatus(), false);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
var face = state.adapter.face(target);
|
|
621
|
+
if (!face) {
|
|
459
622
|
setStatus(L.noService, false);
|
|
460
623
|
return;
|
|
461
624
|
}
|
|
462
|
-
state.sessionId =
|
|
463
|
-
if (faceHasMore(
|
|
625
|
+
state.sessionId = target.id;
|
|
626
|
+
if (faceHasMore(face) === false) {
|
|
464
627
|
setStatus(L.allLoaded, false);
|
|
465
628
|
return;
|
|
466
629
|
}
|
|
@@ -469,11 +632,11 @@ window.__ModuleLoader__.load({
|
|
|
469
632
|
setStatus(L.loading.replace("{n}", "0"), true);
|
|
470
633
|
runPageIn({
|
|
471
634
|
maxPages: MAX_PAGES,
|
|
472
|
-
isCancelled: function () { return token !== state.pageToken || !isOpen(); },
|
|
473
|
-
hasMore: function () { return faceHasMore(
|
|
474
|
-
loadOlder: function () { return Promise.resolve(
|
|
475
|
-
signature: domSignature,
|
|
476
|
-
waitChange: waitForChange,
|
|
635
|
+
isCancelled: function () { return token !== state.pageToken || !isOpen() || !state.adapter.valid(target); },
|
|
636
|
+
hasMore: function () { return faceHasMore(face); },
|
|
637
|
+
loadOlder: function () { return Promise.resolve(face.loadOlder()); },
|
|
638
|
+
signature: function () { return domSignature(target.root); },
|
|
639
|
+
waitChange: function (before) { return waitForChange(before, target.root, token); },
|
|
477
640
|
onProgress: function (pages) {
|
|
478
641
|
if (token !== state.pageToken) return;
|
|
479
642
|
state.pages = pages;
|
|
@@ -485,25 +648,35 @@ window.__ModuleLoader__.load({
|
|
|
485
648
|
state.paging = false;
|
|
486
649
|
state.pages = result.pages;
|
|
487
650
|
if (result.reason === "capped") setStatus(L.capped.replace("{n}", String(result.pages)), false);
|
|
488
|
-
else if (result.reason
|
|
651
|
+
else if (result.reason !== "done") setStatus(L.incomplete, false);
|
|
489
652
|
else setStatus(result.pages > 0 ? L.allLoaded : "", false);
|
|
490
653
|
runSearch(true);
|
|
491
654
|
});
|
|
492
655
|
}
|
|
493
656
|
|
|
494
|
-
|
|
657
|
+
function syncTarget(target, passive, retainNeutral) {
|
|
658
|
+
if (!state.adapter) return;
|
|
659
|
+
var selected = passive ? state.adapter.refresh(state.target) : state.adapter.select(target || document.activeElement, state.target, retainNeutral);
|
|
660
|
+
if (selected && state.target && selected.id === state.target.id && selected.anchor === state.target.anchor && selected.root === state.target.root) return;
|
|
661
|
+
if (!selected && !state.target) {
|
|
662
|
+
if (isOpen()) runSearch(false);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
cancelPageIn();
|
|
666
|
+
stopObserver();
|
|
667
|
+
state.target = selected;
|
|
668
|
+
state.sessionId = selected ? selected.id : null;
|
|
669
|
+
if (!isOpen()) return;
|
|
670
|
+
setStatus(selected ? "" : unavailableStatus(), false);
|
|
671
|
+
runSearch(false);
|
|
672
|
+
startObserver();
|
|
673
|
+
if (selected && state.scope === "whole" && state.query) schedulePageIn();
|
|
674
|
+
}
|
|
675
|
+
|
|
495
676
|
function watchSession() {
|
|
496
677
|
clearInterval(state.sessionTimer);
|
|
497
678
|
state.sessionTimer = setInterval(function () {
|
|
498
|
-
if (
|
|
499
|
-
var resolved = resolveFace(state.ctx);
|
|
500
|
-
var id = resolved ? resolved.id : null;
|
|
501
|
-
if (id === state.sessionId) return;
|
|
502
|
-
state.sessionId = id;
|
|
503
|
-
cancelPageIn();
|
|
504
|
-
setStatus("", false);
|
|
505
|
-
runSearch(false);
|
|
506
|
-
if (state.scope === "whole" && state.query) schedulePageIn();
|
|
679
|
+
if (isOpen()) syncTarget(null, true);
|
|
507
680
|
}, SESSION_POLL_MS);
|
|
508
681
|
}
|
|
509
682
|
|
|
@@ -582,7 +755,7 @@ window.__ModuleLoader__.load({
|
|
|
582
755
|
status.addEventListener("click", function () {
|
|
583
756
|
if (!state.paging) return;
|
|
584
757
|
cancelPageIn();
|
|
585
|
-
setStatus(L.
|
|
758
|
+
setStatus(L.incomplete, false);
|
|
586
759
|
});
|
|
587
760
|
|
|
588
761
|
bar.appendChild(input);
|
|
@@ -602,13 +775,14 @@ window.__ModuleLoader__.load({
|
|
|
602
775
|
document.body.appendChild(bar);
|
|
603
776
|
}
|
|
604
777
|
|
|
605
|
-
function open() {
|
|
778
|
+
function open(target) {
|
|
606
779
|
if (typeof document === "undefined" || document.body === null) return;
|
|
780
|
+
syncTarget(target || document.activeElement, false, true);
|
|
607
781
|
if (!state.bar) buildBar();
|
|
608
782
|
state.bar.style.display = "flex";
|
|
609
783
|
var selection = "";
|
|
610
784
|
try { selection = String(window.getSelection()).trim(); } catch (error) {}
|
|
611
|
-
if (selection && selection.length <= 200 && selection !== state.query) {
|
|
785
|
+
if (state.target && selection && selection.length <= 200 && selection !== state.query) {
|
|
612
786
|
state.input.value = selection;
|
|
613
787
|
state.query = selection;
|
|
614
788
|
}
|
|
@@ -625,6 +799,7 @@ window.__ModuleLoader__.load({
|
|
|
625
799
|
stopWatchingSession();
|
|
626
800
|
if (state.bar) state.bar.style.display = "none";
|
|
627
801
|
state.query = "";
|
|
802
|
+
if (state.input) state.input.value = "";
|
|
628
803
|
state.ranges = [];
|
|
629
804
|
state.index = -1;
|
|
630
805
|
setStatus("", false);
|
|
@@ -638,20 +813,57 @@ window.__ModuleLoader__.load({
|
|
|
638
813
|
}
|
|
639
814
|
|
|
640
815
|
function apply(ctx) {
|
|
816
|
+
var react = require("react");
|
|
641
817
|
state.ctx = ctx;
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
818
|
+
state.adapter = createHostAdapter(ctx, function () {
|
|
819
|
+
syncTarget(null, true);
|
|
820
|
+
});
|
|
821
|
+
function SessionFind(props) {
|
|
822
|
+
var anchor = react.useRef(null);
|
|
823
|
+
react.useEffect(function () {
|
|
824
|
+
if (!anchor.current || !props.sessionId) return;
|
|
825
|
+
return state.adapter.mount(anchor.current, props.sessionId);
|
|
826
|
+
}, [props.sessionId]);
|
|
827
|
+
return react.createElement("button", {
|
|
828
|
+
ref: anchor, type: "button", title: L.find, "aria-label": L.find,
|
|
829
|
+
"data-find-all-session": props.sessionId,
|
|
830
|
+
onClick: function (event) { open(event.currentTarget); }
|
|
831
|
+
}, "⌕");
|
|
832
|
+
}
|
|
833
|
+
ctx.slots.inject("conversation.session.header.utilities", function () {
|
|
834
|
+
return ctx.slots.register({ name: "conversation.session.header.utilities", id: "dsh-find-all" }, SessionFind);
|
|
835
|
+
});
|
|
836
|
+
ctx.effect(function () {
|
|
837
|
+
injectCss();
|
|
838
|
+
var handler = createHandlers({
|
|
839
|
+
open: open, close: close, goTo: goTo, isOpen: isOpen,
|
|
840
|
+
isBarInput: function (target) { return target === state.input; }
|
|
841
|
+
});
|
|
842
|
+
function focus(event) {
|
|
843
|
+
if (!state.bar || !state.bar.contains(event.target)) syncTarget(event.target);
|
|
844
|
+
}
|
|
845
|
+
window.addEventListener("keydown", handler, true);
|
|
846
|
+
window.addEventListener("focusin", focus, true);
|
|
847
|
+
window.addEventListener("pointerdown", focus, true);
|
|
848
|
+
return function () {
|
|
849
|
+
window.removeEventListener("keydown", handler, true);
|
|
850
|
+
window.removeEventListener("focusin", focus, true);
|
|
851
|
+
window.removeEventListener("pointerdown", focus, true);
|
|
852
|
+
close();
|
|
853
|
+
if (state.bar) state.bar.remove();
|
|
854
|
+
var style = document.querySelector('style[data-plugin-css="dsh-find-all/bar.css"]');
|
|
855
|
+
if (style) style.remove();
|
|
856
|
+
state.bar = state.input = state.count = state.status = state.scopeBtn = null;
|
|
857
|
+
state.adapter.clear();
|
|
858
|
+
state.target = state.ctx = null;
|
|
859
|
+
};
|
|
860
|
+
}, "find-all: browser lifecycle");
|
|
651
861
|
}
|
|
652
862
|
|
|
653
863
|
exports.apply = apply;
|
|
654
|
-
exports.inject = ["sessions"];
|
|
864
|
+
exports.inject = ["sessions", "slots"];
|
|
865
|
+
exports.resolveScope = resolveScope;
|
|
866
|
+
exports.createHostAdapter = createHostAdapter;
|
|
655
867
|
exports.findIndices = findIndices; // for tests
|
|
656
868
|
exports.createHandlers = createHandlers; // for tests
|
|
657
869
|
exports.runPageIn = runPageIn; // for tests
|