@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/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
- // * When the API is unavailable, the bar degrades to the legacy
36
- // window.find() calls (no count, still functional).
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 — rendered page only"
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" || document.body === null) return ranges;
245
- var nodes = collectTextNodes(document.body, []);
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
- /** Cheap proxy for "how much history is rendered" — used to notice a page landing. */
274
- function domSignature() {
275
- if (typeof document === "undefined" || document.body === null) return 0;
276
- return collectTextNodes(document.body, []).length;
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
- /** Resolve once the rendered history differs from `before`, or the page times out. */
280
- function waitForChange(before) {
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 (domSignature() !== before) { resolve(true); return; }
285
- if (Date.now() - started >= PAGE_SETTLE_MS) { resolve(false); return; }
286
- setTimeout(poll, PAGE_POLL_MS);
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.ranges = collectRanges(state.query);
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
- if (state.index >= 0 && !highlightsSupported()) legacyFind(state.query, false);
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
- if (highlightsSupported()) {
400
- state.index = ((state.index + delta) % n + n) % n;
401
- paint();
402
- updateCount();
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 (state.bar) runSearch(true);
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(document.documentElement, { childList: true, subtree: true, characterData: true });
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 resolved = resolveFace(state.ctx);
458
- if (!resolved) {
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 = resolved.id;
463
- if (faceHasMore(resolved.face) === false) {
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(resolved.face); },
474
- loadOlder: function () { return Promise.resolve(resolved.face.loadOlder()); },
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 === "stalled" || result.reason === "error") setStatus(L.stalled, false);
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
- /** Drop everything paged for the previous session when the user switches. */
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 (!isOpen()) return;
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.stalled, false);
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
- injectCss();
643
- var ops = {
644
- open: open,
645
- close: close,
646
- goTo: goTo,
647
- isOpen: isOpen,
648
- isBarInput: function (target) { return target === state.input; }
649
- };
650
- window.addEventListener("keydown", createHandlers(ops), true);
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