@mdxeditor/editor 4.0.3 → 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.
- package/dist/MDXEditor.js +138 -53
- package/dist/RealmWithPlugins.js +27 -9
- package/dist/exportMarkdownFromLexical.js +1 -1
- package/dist/index.d.ts +12 -10
- package/dist/mdastUtilHtmlComment.js +2 -1
- package/dist/plugins/codeblock/CodeBlockNode.js +3 -1
- package/dist/plugins/core/NestedLexicalEditor.js +58 -38
- package/dist/plugins/core/index.js +25 -29
- package/dist/plugins/core/lexicalExtensions.js +143 -0
- package/dist/plugins/link-dialog/LinkDialog.js +2 -1
- package/dist/plugins/link-dialog/index.js +15 -1
- package/dist/plugins/search/index.js +273 -170
- package/dist/plugins/table/TableEditor.js +37 -23
- package/dist/plugins/thematic-break/LexicalThematicBreakVisitor.js +2 -2
- package/dist/realmSession.js +38 -0
- package/dist/utils/lexicalHelpers.js +53 -74
- package/package.json +23 -12
- package/dist/plugins/core/SharedHistoryPlugin.js +0 -10
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Cell, debounceTime, useRealm, useCellValue, useCell } from "@mdxeditor/gurx";
|
|
2
|
-
import {
|
|
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$,
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
141
|
-
return
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
156
|
-
const endLexicalNode = $getNearestNodeFromDOMNode(endDomNode);
|
|
157
|
-
if (!$isTextNode(startLexicalNode) || !$isTextNode(endLexicalNode)) {
|
|
260
|
+
if (matches.some((match) => resolveMatch(match) === null))
|
|
158
261
|
return;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
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
|
|
212
|
-
scrollToRangeOrIndex(
|
|
213
|
-
realm.pub(editorSearchCursor$,
|
|
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
|
|
219
|
-
scrollToRangeOrIndex(
|
|
220
|
-
realm.pub(editorSearchCursor$,
|
|
342
|
+
const newCursor = cursor <= 1 ? ranges.length : cursor - 1;
|
|
343
|
+
scrollToRangeOrIndex(newCursor);
|
|
344
|
+
realm.pub(editorSearchCursor$, newCursor);
|
|
221
345
|
};
|
|
222
|
-
const replace = (
|
|
223
|
-
const
|
|
224
|
-
|
|
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
|
-
|
|
228
|
-
|
|
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 = (
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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:
|
|
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 (
|
|
286
|
-
console.warn("CSS.highlights is not supported in this browser. Search
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
|
|
294
|
-
if (!
|
|
295
|
-
realm
|
|
296
|
-
realm.pub(editorSearchRanges$, []);
|
|
297
|
-
resetHighlights();
|
|
403
|
+
realm.sub(searchOpen$, (searchOpen) => {
|
|
404
|
+
if (!searchOpen) {
|
|
405
|
+
clearSearchResults(realm);
|
|
298
406
|
return;
|
|
299
407
|
}
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
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(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
});
|