@mdxeditor/editor 4.0.4 → 4.1.0

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.
@@ -1,7 +1,9 @@
1
1
  import { Cell, debounceTime, useRealm, useCellValue, useCell } from "@mdxeditor/gurx";
2
- import { getNearestEditorFromDOMNode, $getNearestNodeFromDOMNode, $isTextNode, $createRangeSelection } from "lexical";
2
+ import { createDOMRange } from "@lexical/selection";
3
+ import { $createRangeSelection, HISTORY_PUSH_TAG, $getRoot, $getNodeByKey, $isTextNode } from "lexical";
3
4
  import { realmPlugin } from "../../RealmWithPlugins.js";
4
- import { contentEditableRef$, createRootEditorSubscription$ } from "../core/index.js";
5
+ import { contentEditableRef$, createActiveEditorSubscription$, activeEditor$, historyState$ } from "../core/index.js";
6
+ import { editorUsesHistoryState } from "../core/lexicalExtensions.js";
5
7
  const EmptyTextNodeIndex = {
6
8
  allText: "",
7
9
  nodeIndex: [],
@@ -19,7 +21,7 @@ const editorSearchScrollableContent$ = Cell(
19
21
  null,
20
22
  (r) => r.sub(contentEditableRef$, (cref) => {
21
23
  var _a;
22
- r.pub(editorSearchScrollableContent$, ((_a = cref == null ? void 0 : cref.current) == null ? void 0 : _a.parentNode) ?? null);
24
+ r.pub(editorSearchScrollableContent$, ((_a = cref == null ? void 0 : cref.current) == null ? void 0 : _a.parentElement) ?? null);
23
25
  })
24
26
  );
25
27
  const MDX_SEARCH_NAME = "MdxSearch";
@@ -27,6 +29,8 @@ const MDX_FOCUS_SEARCH_NAME = "MdxFocusSearch";
27
29
  const debouncedIndexer$ = Cell(EmptyTextNodeIndex, (realm) => {
28
30
  realm.link(debouncedIndexer$, realm.pipe(editorSearchTextNodeIndex$, realm.transformer(debounceTime(250))));
29
31
  });
32
+ const editorSearchActiveEditor$ = Cell(null);
33
+ const editorSearchStateMatches$ = Cell([]);
30
34
  function* searchText(allText, searchQuery) {
31
35
  if (!searchQuery) {
32
36
  return;
@@ -34,8 +38,7 @@ function* searchText(allText, searchQuery) {
34
38
  let regex;
35
39
  try {
36
40
  regex = new RegExp(searchQuery, "gi");
37
- } catch (e) {
38
- console.error("Invalid search pattern:", e);
41
+ } catch {
39
42
  return;
40
43
  }
41
44
  let match;
@@ -46,50 +49,17 @@ function* searchText(allText, searchQuery) {
46
49
  }
47
50
  continue;
48
51
  }
49
- const start = match.index;
50
- const end = start + match[0].length - 1;
51
- yield [start, end];
52
+ yield [match.index, match.index + match[0].length - 1];
52
53
  }
53
54
  }
54
- function indexAllTextNodes(root) {
55
- var _a;
56
- let allText = "";
57
- const nodeIndex = [];
58
- const offsetIndex = [];
59
- if (!root) {
60
- return { allText: "", nodeIndex, offsetIndex };
61
- }
62
- const contentSelector = "p, h1, h2, h3, h4, h5, h6, li, code, pre";
63
- const treeWalker = document.createTreeWalker(
64
- root,
65
- NodeFilter.SHOW_TEXT,
66
- // The corrected heuristic: accept any text node that is a descendant of a valid content container.
67
- (node) => {
68
- var _a2;
69
- if ((_a2 = node.parentElement) == null ? void 0 : _a2.closest(contentSelector)) {
70
- return NodeFilter.FILTER_ACCEPT;
71
- }
72
- return NodeFilter.FILTER_REJECT;
73
- }
74
- );
75
- let currentNode;
76
- while (currentNode = treeWalker.nextNode()) {
77
- const nodeContent = ((_a = currentNode.textContent) == null ? void 0 : _a.normalize("NFKD")) ?? currentNode.textContent ?? "";
78
- for (let i = 0; i < nodeContent.length; i++) {
79
- nodeIndex.push(currentNode);
80
- offsetIndex.push(i);
81
- allText += nodeContent[i] ?? "";
82
- }
83
- }
84
- return { allText, nodeIndex, offsetIndex };
85
- }
86
55
  function* rangeSearchScan(searchQuery, { allText, offsetIndex, nodeIndex }) {
87
56
  for (const [start, end] of searchText(allText, searchQuery)) {
88
57
  const startOffset = offsetIndex[start];
89
58
  const endOffset = offsetIndex[end];
90
59
  const startNode = nodeIndex[start];
91
60
  const endNode = nodeIndex[end];
92
- const range = new Range();
61
+ const ownerDocument = (startNode == null ? void 0 : startNode.ownerDocument) ?? document;
62
+ const range = ownerDocument.createRange();
93
63
  if (startNode === void 0 || endNode === void 0 || startOffset === void 0 || endOffset === void 0) {
94
64
  throw new Error("Invalid range: startNode, endNode, startOffset, or endOffset is undefined.");
95
65
  }
@@ -98,15 +68,104 @@ function* rangeSearchScan(searchQuery, { allText, offsetIndex, nodeIndex }) {
98
68
  yield range;
99
69
  }
100
70
  }
71
+ function createSearchSnapshot(editor) {
72
+ let text = "";
73
+ const units = [];
74
+ for (const node of $getRoot().getAllTextNodes()) {
75
+ const nodeText = node.getTextContent();
76
+ let sourceOffset = 0;
77
+ while (sourceOffset < nodeText.length) {
78
+ const codePoint = nodeText.codePointAt(sourceOffset);
79
+ if (codePoint === void 0)
80
+ break;
81
+ const sourceUnit = String.fromCodePoint(codePoint);
82
+ const normalizedUnit = sourceUnit.normalize("NFKD");
83
+ const mapsOneToOne = normalizedUnit === sourceUnit;
84
+ for (let normalizedOffset = 0; normalizedOffset < normalizedUnit.length; normalizedOffset++) {
85
+ const startOffset = mapsOneToOne ? sourceOffset + normalizedOffset : sourceOffset;
86
+ const endOffset = mapsOneToOne ? startOffset + 1 : sourceOffset + sourceUnit.length;
87
+ units.push({ key: node.getKey(), startOffset, endOffset });
88
+ text += normalizedUnit[normalizedOffset] ?? "";
89
+ }
90
+ sourceOffset += sourceUnit.length;
91
+ }
92
+ }
93
+ return { editor, text, units };
94
+ }
95
+ function findStateMatches(snapshot, searchQuery) {
96
+ const matches = [];
97
+ for (const [startIndex, endIndex] of searchText(snapshot.text, searchQuery)) {
98
+ const startUnit = snapshot.units[startIndex];
99
+ const endUnit = snapshot.units[endIndex];
100
+ if (!startUnit || !endUnit)
101
+ continue;
102
+ matches.push({
103
+ editor: snapshot.editor,
104
+ start: { key: startUnit.key, offset: startUnit.startOffset },
105
+ end: { key: endUnit.key, offset: endUnit.endOffset }
106
+ });
107
+ }
108
+ return matches;
109
+ }
110
+ function projectSnapshot(snapshot, matches) {
111
+ const nodeIndex = [];
112
+ const offsetIndex = [];
113
+ const domTextNodes = /* @__PURE__ */ new Map();
114
+ const projectedMatches = [];
115
+ let projectionComplete = true;
116
+ snapshot.editor.getEditorState().read(() => {
117
+ for (const unit of snapshot.units) {
118
+ let domTextNode = domTextNodes.get(unit.key);
119
+ if (!domTextNode) {
120
+ const lexicalNode = $getNodeByKey(unit.key);
121
+ if (!$isTextNode(lexicalNode)) {
122
+ projectionComplete = false;
123
+ break;
124
+ }
125
+ const nodeRange = createDOMRange(snapshot.editor, lexicalNode, 0, lexicalNode, lexicalNode.getTextContentSize());
126
+ if (!nodeRange || nodeRange.startContainer !== nodeRange.endContainer) {
127
+ projectionComplete = false;
128
+ break;
129
+ }
130
+ domTextNode = nodeRange.startContainer;
131
+ domTextNodes.set(unit.key, domTextNode);
132
+ }
133
+ nodeIndex.push(domTextNode);
134
+ offsetIndex.push(unit.startOffset);
135
+ }
136
+ for (const match of matches) {
137
+ const startNode = $getNodeByKey(match.start.key);
138
+ const endNode = $getNodeByKey(match.end.key);
139
+ if (!$isTextNode(startNode) || !$isTextNode(endNode))
140
+ continue;
141
+ const range = createDOMRange(snapshot.editor, startNode, match.start.offset, endNode, match.end.offset);
142
+ if (range)
143
+ projectedMatches.push({ match, range });
144
+ }
145
+ });
146
+ return {
147
+ index: projectionComplete ? { allText: snapshot.text, nodeIndex, offsetIndex } : EmptyTextNodeIndex,
148
+ projectedMatches
149
+ };
150
+ }
151
+ function supportsHighlights() {
152
+ return typeof CSS !== "undefined" && typeof CSS.highlights !== "undefined" && typeof Highlight !== "undefined";
153
+ }
101
154
  const focusHighlightRange = (range) => {
155
+ if (!supportsHighlights())
156
+ return;
102
157
  CSS.highlights.delete(MDX_FOCUS_SEARCH_NAME);
103
158
  if (range)
104
159
  CSS.highlights.set(MDX_FOCUS_SEARCH_NAME, new Highlight(range));
105
160
  };
106
161
  const highlightRanges = (ranges) => {
162
+ if (!supportsHighlights())
163
+ return;
107
164
  CSS.highlights.set(MDX_SEARCH_NAME, new Highlight(...ranges));
108
165
  };
109
166
  const resetHighlights = () => {
167
+ if (!supportsHighlights())
168
+ return;
110
169
  CSS.highlights.delete(MDX_SEARCH_NAME);
111
170
  CSS.highlights.delete(MDX_FOCUS_SEARCH_NAME);
112
171
  };
@@ -130,56 +189,122 @@ const scrollToRange = (range, contentEditable, options) => {
130
189
  const rangeBottom = bottomRelativeToContainer + contentEditable.scrollTop;
131
190
  const visibleTop = contentEditable.scrollTop;
132
191
  const visibleBottom = visibleTop + contentEditable.clientHeight;
133
- const inView = rangeTop >= visibleTop && rangeBottom <= visibleBottom;
134
- if (inView)
192
+ if (rangeTop >= visibleTop && rangeBottom <= visibleBottom)
135
193
  return;
136
194
  }
137
195
  const top = topRelativeToContainer + contentEditable.scrollTop - first.height;
138
196
  contentEditable.scrollTo({ top, behavior });
139
197
  };
140
- function isSimilarRange(range1, range2) {
141
- return range1.startContainer === range2.startContainer && range1.startOffset === range2.startOffset;
198
+ function isSameStart(first, second) {
199
+ return (first == null ? void 0 : first.editor) === second.editor && first.start.key === second.start.key && first.start.offset === second.start.offset;
142
200
  }
143
- function replaceTextInRange(range, str, onUpdate) {
144
- const startDomNode = range.startContainer;
145
- const endDomNode = range.endContainer;
146
- const startOffset = range.startOffset;
147
- const endOffset = range.endOffset;
148
- const editor = getNearestEditorFromDOMNode(startDomNode);
149
- if (!editor) {
150
- console.warn("No editor found for the provided DOM node.");
201
+ function clearSearchResults(realm) {
202
+ realm.pubIn({
203
+ [editorSearchStateMatches$]: [],
204
+ [editorSearchRanges$]: [],
205
+ [editorSearchCursor$]: 0
206
+ });
207
+ resetHighlights();
208
+ }
209
+ function refreshSearch(realm, editor, advanceFrom) {
210
+ if (realm.getValue(editorSearchActiveEditor$) !== editor)
211
+ return;
212
+ let snapshot;
213
+ editor.getEditorState().read(() => {
214
+ snapshot = createSearchSnapshot(editor);
215
+ });
216
+ const searchOpen = realm.getValue(searchOpen$);
217
+ const searchQuery = realm.getValue(editorSearchTerm$);
218
+ const stateMatches = searchOpen ? findStateMatches(snapshot, searchQuery) : [];
219
+ const { index, projectedMatches } = projectSnapshot(snapshot, stateMatches);
220
+ if (searchOpen) {
221
+ realm.pub(editorSearchTextNodeIndex$, index);
222
+ } else {
223
+ realm.pub(debouncedIndexer$, index);
224
+ }
225
+ if (!searchOpen || !searchQuery || projectedMatches.length === 0) {
226
+ clearSearchResults(realm);
151
227
  return;
152
228
  }
229
+ const matches = projectedMatches.map(({ match }) => match);
230
+ const ranges = projectedMatches.map(({ range }) => range);
231
+ const previousCursor = realm.getValue(editorSearchCursor$);
232
+ let cursor = Math.min(Math.max(previousCursor, 1), ranges.length);
233
+ if (advanceFrom && isSameStart(matches[advanceFrom.cursor - 1], advanceFrom.match)) {
234
+ cursor = advanceFrom.cursor % ranges.length + 1;
235
+ }
236
+ realm.pubIn({
237
+ [editorSearchStateMatches$]: matches,
238
+ [editorSearchRanges$]: ranges,
239
+ [editorSearchCursor$]: cursor
240
+ });
241
+ highlightRanges(ranges);
242
+ focusHighlightRange(ranges[cursor - 1]);
243
+ }
244
+ function resolveMatch(match) {
245
+ const startNode = $getNodeByKey(match.start.key);
246
+ const endNode = $getNodeByKey(match.end.key);
247
+ if (!$isTextNode(startNode) || !$isTextNode(endNode))
248
+ return null;
249
+ if (match.start.offset < 0 || match.start.offset > startNode.getTextContentSize())
250
+ return null;
251
+ if (match.end.offset < 0 || match.end.offset > endNode.getTextContentSize())
252
+ return null;
253
+ return { startNode, endNode };
254
+ }
255
+ function replaceStateMatches(editor, matches, replacement, onUpdate) {
256
+ if (matches.length === 0 || matches.some((match) => match.editor !== editor))
257
+ return;
153
258
  editor.update(
154
259
  () => {
155
- const startLexicalNode = $getNearestNodeFromDOMNode(startDomNode);
156
- const endLexicalNode = $getNearestNodeFromDOMNode(endDomNode);
157
- if (!$isTextNode(startLexicalNode) || !$isTextNode(endLexicalNode)) {
260
+ if (matches.some((match) => resolveMatch(match) === null))
158
261
  return;
159
- }
160
- try {
161
- const selection = $createRangeSelection();
162
- selection.anchor.set(startLexicalNode.getKey(), startOffset, "text");
163
- selection.focus.set(endLexicalNode.getKey(), endOffset, "text");
164
- selection.insertText(str);
165
- } catch (e) {
166
- console.warn("Error replacing text in the editor:", e);
167
- if (onUpdate) {
168
- onUpdate();
262
+ for (let index = matches.length - 1; index >= 0; index--) {
263
+ const match = matches[index];
264
+ if (!match)
265
+ continue;
266
+ const resolved = resolveMatch(match);
267
+ if (!resolved) {
268
+ throw new Error("Search match became stale during replacement.");
169
269
  }
270
+ if (resolved.startNode.is(resolved.endNode)) {
271
+ const replacedNode = resolved.startNode.spliceText(match.start.offset, match.end.offset - match.start.offset, replacement, true);
272
+ if (replacedNode.getTextContentSize() === 0) {
273
+ replacedNode.remove();
274
+ }
275
+ continue;
276
+ }
277
+ const selection = $createRangeSelection();
278
+ selection.anchor.set(resolved.startNode.getKey(), match.start.offset, "text");
279
+ selection.focus.set(resolved.endNode.getKey(), match.end.offset, "text");
280
+ selection.insertText(replacement);
170
281
  }
171
282
  },
172
283
  {
284
+ tag: HISTORY_PUSH_TAG,
173
285
  onUpdate
174
286
  }
175
287
  );
176
288
  }
289
+ function ensureHistoryBaseline(realm, editor) {
290
+ var _a;
291
+ const historyState = realm.getValue(historyState$);
292
+ if (!editorUsesHistoryState(editor, historyState)) {
293
+ return;
294
+ }
295
+ if (((_a = historyState.current) == null ? void 0 : _a.editor) !== editor) {
296
+ if (historyState.current) {
297
+ historyState.undoStack.push(historyState.current);
298
+ }
299
+ historyState.current = { editor, editorState: editor.getEditorState() };
300
+ }
301
+ }
177
302
  function useEditorSearch() {
178
303
  const realm = useRealm();
179
304
  const ranges = useCellValue(editorSearchRanges$);
180
305
  const cursor = useCellValue(editorSearchCursor$);
181
306
  const search = useCellValue(editorSearchTerm$);
182
- const currentRange = ranges[cursor - 1] ?? null;
307
+ const currentRange = cursor > 0 ? ranges.at(cursor - 1) ?? null : null;
183
308
  const contentEditable = useCellValue(editorSearchScrollableContent$);
184
309
  const [isSearchOpen, setIsSearchOpen] = useCell(searchOpen$);
185
310
  const openSearch = () => {
@@ -191,13 +316,12 @@ function useEditorSearch() {
191
316
  const toggleSearch = () => {
192
317
  setIsSearchOpen(!isSearchOpen);
193
318
  };
194
- const rangeCount = ranges.length;
195
319
  const scrollToRangeOrIndex = (range, options) => {
196
320
  const scrollRange = typeof range === "number" ? ranges[range - 1] : range;
197
321
  if (!scrollRange) {
198
322
  throw new Error("Error scrolling to range, range does not exist");
199
323
  }
200
- scrollToRange(scrollRange, contentEditable, options);
324
+ scrollToRange(scrollRange, contentEditable ?? void 0, options);
201
325
  };
202
326
  const setSearch = (term) => {
203
327
  if ((term ?? "") !== search) {
@@ -208,62 +332,44 @@ function useEditorSearch() {
208
332
  const next = () => {
209
333
  if (!ranges.length)
210
334
  return;
211
- const newVal = cursor % ranges.length + 1;
212
- scrollToRangeOrIndex(newVal);
213
- realm.pub(editorSearchCursor$, newVal);
335
+ const newCursor = cursor % ranges.length + 1;
336
+ scrollToRangeOrIndex(newCursor);
337
+ realm.pub(editorSearchCursor$, newCursor);
214
338
  };
215
339
  const prev = () => {
216
340
  if (!ranges.length)
217
341
  return;
218
- const newVal = cursor <= 1 ? ranges.length : cursor - 1;
219
- scrollToRangeOrIndex(newVal);
220
- realm.pub(editorSearchCursor$, newVal);
342
+ const newCursor = cursor <= 1 ? ranges.length : cursor - 1;
343
+ scrollToRangeOrIndex(newCursor);
344
+ realm.pub(editorSearchCursor$, newCursor);
221
345
  };
222
- const replace = (str, onUpdate) => {
223
- const currentRange2 = ranges[cursor - 1];
224
- if (!currentRange2) {
346
+ const replace = (replacement, onUpdate) => {
347
+ const editor = realm.getValue(editorSearchActiveEditor$);
348
+ const matches = realm.getValue(editorSearchStateMatches$);
349
+ const match = matches[cursor - 1];
350
+ if (!editor || !match)
225
351
  return;
226
- }
227
- const { startContainer, startOffset } = currentRange2 ?? {};
228
- replaceTextInRange(currentRange2, str, () => {
229
- const unsub = realm.sub(editorSearchRanges$, (newRanges) => {
230
- unsub();
231
- if (isSimilarRange(newRanges[cursor - 1] ?? {}, {
232
- startOffset,
233
- startContainer
234
- })) {
235
- realm.pub(editorSearchCursor$, (cursor + 1) % (newRanges.length + 1) || 1);
236
- }
237
- });
352
+ ensureHistoryBaseline(realm, editor);
353
+ replaceStateMatches(editor, [match], replacement, () => {
354
+ refreshSearch(realm, editor, { cursor, match });
238
355
  onUpdate == null ? void 0 : onUpdate();
239
356
  });
240
357
  };
241
- const replaceAll = (str, onUpdate) => {
242
- const runReplaceAll = () => {
243
- let ticks = 0;
244
- for (let i = ranges.length - 1; i >= 0; i--) {
245
- const textReplaceRange = ranges[i];
246
- if (!textReplaceRange) {
247
- throw new Error("error replacing all text range does not exist");
248
- }
249
- replaceTextInRange(textReplaceRange, str, () => {
250
- ticks++;
251
- if (ticks >= ranges.length) {
252
- onUpdate == null ? void 0 : onUpdate();
253
- }
254
- });
255
- }
256
- };
257
- if (typeof requestIdleCallback === "function") {
258
- requestIdleCallback(runReplaceAll);
259
- } else {
260
- setTimeout(runReplaceAll, 0);
261
- }
358
+ const replaceAll = (replacement, onUpdate) => {
359
+ const editor = realm.getValue(editorSearchActiveEditor$);
360
+ const matches = realm.getValue(editorSearchStateMatches$);
361
+ if (!editor || matches.length === 0)
362
+ return;
363
+ ensureHistoryBaseline(realm, editor);
364
+ replaceStateMatches(editor, matches, replacement, () => {
365
+ refreshSearch(realm, editor);
366
+ onUpdate == null ? void 0 : onUpdate();
367
+ });
262
368
  };
263
369
  return {
264
370
  next,
265
371
  prev,
266
- total: rangeCount,
372
+ total: ranges.length,
267
373
  cursor,
268
374
  setSearch,
269
375
  search,
@@ -280,73 +386,70 @@ function useEditorSearch() {
280
386
  };
281
387
  }
282
388
  const searchPlugin = realmPlugin({
283
- //TODO: ensure proper event cleanup
284
389
  init(realm) {
285
- if (typeof CSS.highlights === "undefined") {
286
- console.warn("CSS.highlights is not supported in this browser. Search functionality will be limited.");
287
- return;
390
+ if (!supportsHighlights()) {
391
+ console.warn("CSS.highlights is not supported in this browser. Search highlighting will be unavailable.");
288
392
  }
289
393
  realm.sub(editorSearchCursor$, (cursor) => {
290
- const ranges = realm.getValue(editorSearchRanges$);
291
- focusHighlightRange(ranges[cursor - 1]);
394
+ if (realm.getValue(searchOpen$)) {
395
+ focusHighlightRange(realm.getValue(editorSearchRanges$)[cursor - 1]);
396
+ }
397
+ });
398
+ realm.sub(editorSearchTerm$, () => {
399
+ const editor = realm.getValue(editorSearchActiveEditor$);
400
+ if (editor)
401
+ refreshSearch(realm, editor);
292
402
  });
293
- const updateHighlights = (searchQuery, textNodeIndex) => {
294
- if (!searchQuery) {
295
- realm.pub(editorSearchCursor$, 0);
296
- realm.pub(editorSearchRanges$, []);
297
- resetHighlights();
403
+ realm.sub(searchOpen$, (searchOpen) => {
404
+ if (!searchOpen) {
405
+ clearSearchResults(realm);
298
406
  return;
299
407
  }
300
- const ranges = Array.from(rangeSearchScan(searchQuery, textNodeIndex));
301
- realm.pub(editorSearchRanges$, ranges);
302
- highlightRanges(ranges);
303
- if (ranges.length) {
304
- const currentCursor = realm.getValue(editorSearchCursor$) || 1;
305
- focusHighlightRange(ranges[currentCursor - 1]);
306
- realm.pub(editorSearchCursor$, currentCursor);
307
- const scrollRange = ranges[currentCursor - 1];
308
- if (!scrollRange)
309
- throw new Error("error updating highlights, scroll range does not exist");
310
- const contentEditable = realm.getValue(editorSearchScrollableContent$);
311
- scrollToRange(scrollRange, contentEditable, {
312
- ignoreIfInView: true
313
- });
314
- } else {
315
- resetHighlights();
316
- }
317
- };
318
- realm.sub(editorSearchTextNodeIndex$, (textNodeIndex) => {
319
- updateHighlights(realm.getValue(editorSearchTerm$), textNodeIndex);
320
- });
321
- realm.sub(editorSearchTerm$, (searchQuery) => {
322
- updateHighlights(searchQuery, realm.getValue(editorSearchTextNodeIndex$));
408
+ const editor = realm.getValue(editorSearchActiveEditor$);
409
+ if (editor)
410
+ refreshSearch(realm, editor);
323
411
  });
324
- realm.pub(createRootEditorSubscription$, (editor) => {
325
- let observer = null;
326
- return editor.registerRootListener((rootElement) => {
327
- if (observer) {
328
- observer.disconnect();
329
- observer = null;
330
- }
331
- if (rootElement) {
332
- const initialIndex = indexAllTextNodes(rootElement);
333
- realm.pub(editorSearchTextNodeIndex$, initialIndex);
334
- observer = new MutationObserver(() => {
335
- const newIndex = indexAllTextNodes(rootElement);
336
- if (realm.getValue(searchOpen$)) {
337
- realm.pub(editorSearchTextNodeIndex$, newIndex);
338
- } else {
339
- realm.pub(debouncedIndexer$, newIndex);
340
- }
341
- });
342
- observer.observe(rootElement, {
343
- childList: true,
344
- subtree: true,
345
- characterData: true
346
- });
347
- return () => observer == null ? void 0 : observer.disconnect();
412
+ realm.pub(createActiveEditorSubscription$, (editor) => {
413
+ var _a;
414
+ realm.pubIn({
415
+ [editorSearchActiveEditor$]: editor,
416
+ [editorSearchTextNodeIndex$]: EmptyTextNodeIndex,
417
+ [editorSearchScrollableContent$]: ((_a = editor.getRootElement()) == null ? void 0 : _a.parentElement) ?? null
418
+ });
419
+ clearSearchResults(realm);
420
+ refreshSearch(realm, editor);
421
+ const unregisterUpdate = editor.registerUpdateListener(({ dirtyElements, dirtyLeaves }) => {
422
+ if (dirtyElements.size > 0 || dirtyLeaves.size > 0)
423
+ refreshSearch(realm, editor);
424
+ });
425
+ const unregisterRoot = editor.registerRootListener((rootElement) => {
426
+ if (!rootElement) {
427
+ if (realm.getValue(editorSearchActiveEditor$) === editor) {
428
+ realm.pubIn({
429
+ [editorSearchTextNodeIndex$]: EmptyTextNodeIndex,
430
+ [editorSearchScrollableContent$]: null
431
+ });
432
+ clearSearchResults(realm);
433
+ }
434
+ return;
348
435
  }
436
+ realm.pub(editorSearchScrollableContent$, rootElement.parentElement);
437
+ refreshSearch(realm, editor);
349
438
  });
439
+ return () => {
440
+ unregisterUpdate();
441
+ unregisterRoot();
442
+ if (realm.getValue(editorSearchActiveEditor$) === editor) {
443
+ realm.pub(editorSearchActiveEditor$, null);
444
+ clearSearchResults(realm);
445
+ }
446
+ };
447
+ });
448
+ realm.sub(activeEditor$, (editor) => {
449
+ if (!editor) {
450
+ realm.pub(editorSearchScrollableContent$, null);
451
+ clearSearchResults(realm);
452
+ }
350
453
  });
351
454
  }
352
455
  });