@mdxeditor/editor 4.0.4 → 4.1.1
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/index.js +6 -6
- package/dist/mdastUtilHtmlComment.js +2 -1
- package/dist/plugins/codeblock/CodeBlockNode.js +4 -5
- package/dist/plugins/codeblock/index.js +1 -1
- package/dist/plugins/codemirror/CodeMirrorEditor.js +1 -1
- package/dist/plugins/core/GenericHTMLNode.js +2 -5
- package/dist/plugins/core/NestedLexicalEditor.js +58 -38
- package/dist/plugins/core/index.js +26 -30
- package/dist/plugins/core/lexicalExtensions.js +143 -0
- package/dist/plugins/diff-source/SourceEditor.js +1 -1
- package/dist/plugins/directives/DirectiveNode.js +1 -4
- package/dist/plugins/frontmatter/FrontmatterNode.js +1 -4
- package/dist/plugins/frontmatter/index.js +1 -1
- package/dist/plugins/headings/index.js +2 -1
- package/dist/plugins/image/ImageDialog.js +1 -2
- package/dist/plugins/image/ImageEditor.js +1 -2
- package/dist/plugins/image/ImageNode.js +1 -4
- package/dist/plugins/image/index.js +4 -6
- package/dist/plugins/jsx/LexicalJsxNode.js +1 -4
- package/dist/plugins/jsx/LexicalMdxExpressionNode.js +1 -4
- package/dist/plugins/link-dialog/LinkDialog.js +3 -3
- package/dist/plugins/link-dialog/index.js +16 -4
- package/dist/plugins/lists/index.js +3 -3
- package/dist/plugins/markdown-shortcut/index.js +1 -1
- package/dist/plugins/search/index.js +259 -179
- package/dist/plugins/table/TableEditor.js +38 -24
- package/dist/plugins/table/TableNode.js +1 -4
- package/dist/plugins/thematic-break/LexicalThematicBreakVisitor.js +2 -2
- package/dist/plugins/toolbar/components/KitchenSinkToolbar.js +1 -1
- package/dist/plugins/toolbar/primitives/toolbar.js +1 -1
- package/dist/realmSession.js +38 -0
- package/dist/style.css +1511 -1525
- package/dist/utils/lexicalHelpers.js +53 -74
- package/package.json +23 -12
- package/dist/plugins/core/SharedHistoryPlugin.js +0 -10
- package/dist/styles/globals.css.js +0 -1
|
@@ -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 { $getRoot, $getNodeByKey, $isTextNode, $createRangeSelection, HISTORY_PUSH_TAG } 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,96 @@ 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) break;
|
|
80
|
+
const sourceUnit = String.fromCodePoint(codePoint);
|
|
81
|
+
const normalizedUnit = sourceUnit.normalize("NFKD");
|
|
82
|
+
const mapsOneToOne = normalizedUnit === sourceUnit;
|
|
83
|
+
for (let normalizedOffset = 0; normalizedOffset < normalizedUnit.length; normalizedOffset++) {
|
|
84
|
+
const startOffset = mapsOneToOne ? sourceOffset + normalizedOffset : sourceOffset;
|
|
85
|
+
const endOffset = mapsOneToOne ? startOffset + 1 : sourceOffset + sourceUnit.length;
|
|
86
|
+
units.push({ key: node.getKey(), startOffset, endOffset });
|
|
87
|
+
text += normalizedUnit[normalizedOffset] ?? "";
|
|
88
|
+
}
|
|
89
|
+
sourceOffset += sourceUnit.length;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { editor, text, units };
|
|
93
|
+
}
|
|
94
|
+
function findStateMatches(snapshot, searchQuery) {
|
|
95
|
+
const matches = [];
|
|
96
|
+
for (const [startIndex, endIndex] of searchText(snapshot.text, searchQuery)) {
|
|
97
|
+
const startUnit = snapshot.units[startIndex];
|
|
98
|
+
const endUnit = snapshot.units[endIndex];
|
|
99
|
+
if (!startUnit || !endUnit) continue;
|
|
100
|
+
matches.push({
|
|
101
|
+
editor: snapshot.editor,
|
|
102
|
+
start: { key: startUnit.key, offset: startUnit.startOffset },
|
|
103
|
+
end: { key: endUnit.key, offset: endUnit.endOffset }
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return matches;
|
|
107
|
+
}
|
|
108
|
+
function projectSnapshot(snapshot, matches) {
|
|
109
|
+
const nodeIndex = [];
|
|
110
|
+
const offsetIndex = [];
|
|
111
|
+
const domTextNodes = /* @__PURE__ */ new Map();
|
|
112
|
+
const projectedMatches = [];
|
|
113
|
+
let projectionComplete = true;
|
|
114
|
+
snapshot.editor.getEditorState().read(() => {
|
|
115
|
+
for (const unit of snapshot.units) {
|
|
116
|
+
let domTextNode = domTextNodes.get(unit.key);
|
|
117
|
+
if (!domTextNode) {
|
|
118
|
+
const lexicalNode = $getNodeByKey(unit.key);
|
|
119
|
+
if (!$isTextNode(lexicalNode)) {
|
|
120
|
+
projectionComplete = false;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
const nodeRange = createDOMRange(snapshot.editor, lexicalNode, 0, lexicalNode, lexicalNode.getTextContentSize());
|
|
124
|
+
if (!nodeRange || nodeRange.startContainer !== nodeRange.endContainer) {
|
|
125
|
+
projectionComplete = false;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
domTextNode = nodeRange.startContainer;
|
|
129
|
+
domTextNodes.set(unit.key, domTextNode);
|
|
130
|
+
}
|
|
131
|
+
nodeIndex.push(domTextNode);
|
|
132
|
+
offsetIndex.push(unit.startOffset);
|
|
133
|
+
}
|
|
134
|
+
for (const match of matches) {
|
|
135
|
+
const startNode = $getNodeByKey(match.start.key);
|
|
136
|
+
const endNode = $getNodeByKey(match.end.key);
|
|
137
|
+
if (!$isTextNode(startNode) || !$isTextNode(endNode)) continue;
|
|
138
|
+
const range = createDOMRange(snapshot.editor, startNode, match.start.offset, endNode, match.end.offset);
|
|
139
|
+
if (range) projectedMatches.push({ match, range });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
index: projectionComplete ? { allText: snapshot.text, nodeIndex, offsetIndex } : EmptyTextNodeIndex,
|
|
144
|
+
projectedMatches
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function supportsHighlights() {
|
|
148
|
+
return typeof CSS !== "undefined" && typeof CSS.highlights !== "undefined" && typeof Highlight !== "undefined";
|
|
149
|
+
}
|
|
101
150
|
const focusHighlightRange = (range) => {
|
|
151
|
+
if (!supportsHighlights()) return;
|
|
102
152
|
CSS.highlights.delete(MDX_FOCUS_SEARCH_NAME);
|
|
103
|
-
if (range)
|
|
104
|
-
CSS.highlights.set(MDX_FOCUS_SEARCH_NAME, new Highlight(range));
|
|
153
|
+
if (range) CSS.highlights.set(MDX_FOCUS_SEARCH_NAME, new Highlight(range));
|
|
105
154
|
};
|
|
106
155
|
const highlightRanges = (ranges) => {
|
|
156
|
+
if (!supportsHighlights()) return;
|
|
107
157
|
CSS.highlights.set(MDX_SEARCH_NAME, new Highlight(...ranges));
|
|
108
158
|
};
|
|
109
159
|
const resetHighlights = () => {
|
|
160
|
+
if (!supportsHighlights()) return;
|
|
110
161
|
CSS.highlights.delete(MDX_SEARCH_NAME);
|
|
111
162
|
CSS.highlights.delete(MDX_FOCUS_SEARCH_NAME);
|
|
112
163
|
};
|
|
@@ -130,56 +181,114 @@ const scrollToRange = (range, contentEditable, options) => {
|
|
|
130
181
|
const rangeBottom = bottomRelativeToContainer + contentEditable.scrollTop;
|
|
131
182
|
const visibleTop = contentEditable.scrollTop;
|
|
132
183
|
const visibleBottom = visibleTop + contentEditable.clientHeight;
|
|
133
|
-
|
|
134
|
-
if (inView)
|
|
135
|
-
return;
|
|
184
|
+
if (rangeTop >= visibleTop && rangeBottom <= visibleBottom) return;
|
|
136
185
|
}
|
|
137
186
|
const top = topRelativeToContainer + contentEditable.scrollTop - first.height;
|
|
138
187
|
contentEditable.scrollTo({ top, behavior });
|
|
139
188
|
};
|
|
140
|
-
function
|
|
141
|
-
return
|
|
189
|
+
function isSameStart(first, second) {
|
|
190
|
+
return (first == null ? void 0 : first.editor) === second.editor && first.start.key === second.start.key && first.start.offset === second.start.offset;
|
|
191
|
+
}
|
|
192
|
+
function clearSearchResults(realm) {
|
|
193
|
+
realm.pubIn({
|
|
194
|
+
[editorSearchStateMatches$]: [],
|
|
195
|
+
[editorSearchRanges$]: [],
|
|
196
|
+
[editorSearchCursor$]: 0
|
|
197
|
+
});
|
|
198
|
+
resetHighlights();
|
|
142
199
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
200
|
+
function refreshSearch(realm, editor, advanceFrom) {
|
|
201
|
+
if (realm.getValue(editorSearchActiveEditor$) !== editor) return;
|
|
202
|
+
let snapshot;
|
|
203
|
+
editor.getEditorState().read(() => {
|
|
204
|
+
snapshot = createSearchSnapshot(editor);
|
|
205
|
+
});
|
|
206
|
+
const searchOpen = realm.getValue(searchOpen$);
|
|
207
|
+
const searchQuery = realm.getValue(editorSearchTerm$);
|
|
208
|
+
const stateMatches = searchOpen ? findStateMatches(snapshot, searchQuery) : [];
|
|
209
|
+
const { index, projectedMatches } = projectSnapshot(snapshot, stateMatches);
|
|
210
|
+
if (searchOpen) {
|
|
211
|
+
realm.pub(editorSearchTextNodeIndex$, index);
|
|
212
|
+
} else {
|
|
213
|
+
realm.pub(debouncedIndexer$, index);
|
|
214
|
+
}
|
|
215
|
+
if (!searchOpen || !searchQuery || projectedMatches.length === 0) {
|
|
216
|
+
clearSearchResults(realm);
|
|
151
217
|
return;
|
|
152
218
|
}
|
|
219
|
+
const matches = projectedMatches.map(({ match }) => match);
|
|
220
|
+
const ranges = projectedMatches.map(({ range }) => range);
|
|
221
|
+
const previousCursor = realm.getValue(editorSearchCursor$);
|
|
222
|
+
let cursor = Math.min(Math.max(previousCursor, 1), ranges.length);
|
|
223
|
+
if (advanceFrom && isSameStart(matches[advanceFrom.cursor - 1], advanceFrom.match)) {
|
|
224
|
+
cursor = advanceFrom.cursor % ranges.length + 1;
|
|
225
|
+
}
|
|
226
|
+
realm.pubIn({
|
|
227
|
+
[editorSearchStateMatches$]: matches,
|
|
228
|
+
[editorSearchRanges$]: ranges,
|
|
229
|
+
[editorSearchCursor$]: cursor
|
|
230
|
+
});
|
|
231
|
+
highlightRanges(ranges);
|
|
232
|
+
focusHighlightRange(ranges[cursor - 1]);
|
|
233
|
+
}
|
|
234
|
+
function resolveMatch(match) {
|
|
235
|
+
const startNode = $getNodeByKey(match.start.key);
|
|
236
|
+
const endNode = $getNodeByKey(match.end.key);
|
|
237
|
+
if (!$isTextNode(startNode) || !$isTextNode(endNode)) return null;
|
|
238
|
+
if (match.start.offset < 0 || match.start.offset > startNode.getTextContentSize()) return null;
|
|
239
|
+
if (match.end.offset < 0 || match.end.offset > endNode.getTextContentSize()) return null;
|
|
240
|
+
return { startNode, endNode };
|
|
241
|
+
}
|
|
242
|
+
function replaceStateMatches(editor, matches, replacement, onUpdate) {
|
|
243
|
+
if (matches.length === 0 || matches.some((match) => match.editor !== editor)) return;
|
|
153
244
|
editor.update(
|
|
154
245
|
() => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
246
|
+
if (matches.some((match) => resolveMatch(match) === null)) return;
|
|
247
|
+
for (let index = matches.length - 1; index >= 0; index--) {
|
|
248
|
+
const match = matches[index];
|
|
249
|
+
if (!match) continue;
|
|
250
|
+
const resolved = resolveMatch(match);
|
|
251
|
+
if (!resolved) {
|
|
252
|
+
throw new Error("Search match became stale during replacement.");
|
|
253
|
+
}
|
|
254
|
+
if (resolved.startNode.is(resolved.endNode)) {
|
|
255
|
+
const replacedNode = resolved.startNode.spliceText(match.start.offset, match.end.offset - match.start.offset, replacement, true);
|
|
256
|
+
if (replacedNode.getTextContentSize() === 0) {
|
|
257
|
+
replacedNode.remove();
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
169
260
|
}
|
|
261
|
+
const selection = $createRangeSelection();
|
|
262
|
+
selection.anchor.set(resolved.startNode.getKey(), match.start.offset, "text");
|
|
263
|
+
selection.focus.set(resolved.endNode.getKey(), match.end.offset, "text");
|
|
264
|
+
selection.insertText(replacement);
|
|
170
265
|
}
|
|
171
266
|
},
|
|
172
267
|
{
|
|
268
|
+
tag: HISTORY_PUSH_TAG,
|
|
173
269
|
onUpdate
|
|
174
270
|
}
|
|
175
271
|
);
|
|
176
272
|
}
|
|
273
|
+
function ensureHistoryBaseline(realm, editor) {
|
|
274
|
+
var _a;
|
|
275
|
+
const historyState = realm.getValue(historyState$);
|
|
276
|
+
if (!editorUsesHistoryState(editor, historyState)) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (((_a = historyState.current) == null ? void 0 : _a.editor) !== editor) {
|
|
280
|
+
if (historyState.current) {
|
|
281
|
+
historyState.undoStack.push(historyState.current);
|
|
282
|
+
}
|
|
283
|
+
historyState.current = { editor, editorState: editor.getEditorState() };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
177
286
|
function useEditorSearch() {
|
|
178
287
|
const realm = useRealm();
|
|
179
288
|
const ranges = useCellValue(editorSearchRanges$);
|
|
180
289
|
const cursor = useCellValue(editorSearchCursor$);
|
|
181
290
|
const search = useCellValue(editorSearchTerm$);
|
|
182
|
-
const currentRange = ranges
|
|
291
|
+
const currentRange = cursor > 0 ? ranges.at(cursor - 1) ?? null : null;
|
|
183
292
|
const contentEditable = useCellValue(editorSearchScrollableContent$);
|
|
184
293
|
const [isSearchOpen, setIsSearchOpen] = useCell(searchOpen$);
|
|
185
294
|
const openSearch = () => {
|
|
@@ -191,13 +300,12 @@ function useEditorSearch() {
|
|
|
191
300
|
const toggleSearch = () => {
|
|
192
301
|
setIsSearchOpen(!isSearchOpen);
|
|
193
302
|
};
|
|
194
|
-
const rangeCount = ranges.length;
|
|
195
303
|
const scrollToRangeOrIndex = (range, options) => {
|
|
196
304
|
const scrollRange = typeof range === "number" ? ranges[range - 1] : range;
|
|
197
305
|
if (!scrollRange) {
|
|
198
306
|
throw new Error("Error scrolling to range, range does not exist");
|
|
199
307
|
}
|
|
200
|
-
scrollToRange(scrollRange, contentEditable, options);
|
|
308
|
+
scrollToRange(scrollRange, contentEditable ?? void 0, options);
|
|
201
309
|
};
|
|
202
310
|
const setSearch = (term) => {
|
|
203
311
|
if ((term ?? "") !== search) {
|
|
@@ -206,64 +314,42 @@ function useEditorSearch() {
|
|
|
206
314
|
realm.pub(editorSearchTermDebounced$, term ?? "");
|
|
207
315
|
};
|
|
208
316
|
const next = () => {
|
|
209
|
-
if (!ranges.length)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
realm.pub(editorSearchCursor$, newVal);
|
|
317
|
+
if (!ranges.length) return;
|
|
318
|
+
const newCursor = cursor % ranges.length + 1;
|
|
319
|
+
scrollToRangeOrIndex(newCursor);
|
|
320
|
+
realm.pub(editorSearchCursor$, newCursor);
|
|
214
321
|
};
|
|
215
322
|
const prev = () => {
|
|
216
|
-
if (!ranges.length)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
realm.pub(editorSearchCursor$, newVal);
|
|
323
|
+
if (!ranges.length) return;
|
|
324
|
+
const newCursor = cursor <= 1 ? ranges.length : cursor - 1;
|
|
325
|
+
scrollToRangeOrIndex(newCursor);
|
|
326
|
+
realm.pub(editorSearchCursor$, newCursor);
|
|
221
327
|
};
|
|
222
|
-
const replace = (
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
});
|
|
328
|
+
const replace = (replacement, onUpdate) => {
|
|
329
|
+
const editor = realm.getValue(editorSearchActiveEditor$);
|
|
330
|
+
const matches = realm.getValue(editorSearchStateMatches$);
|
|
331
|
+
const match = matches[cursor - 1];
|
|
332
|
+
if (!editor || !match) return;
|
|
333
|
+
ensureHistoryBaseline(realm, editor);
|
|
334
|
+
replaceStateMatches(editor, [match], replacement, () => {
|
|
335
|
+
refreshSearch(realm, editor, { cursor, match });
|
|
238
336
|
onUpdate == null ? void 0 : onUpdate();
|
|
239
337
|
});
|
|
240
338
|
};
|
|
241
|
-
const replaceAll = (
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
}
|
|
339
|
+
const replaceAll = (replacement, onUpdate) => {
|
|
340
|
+
const editor = realm.getValue(editorSearchActiveEditor$);
|
|
341
|
+
const matches = realm.getValue(editorSearchStateMatches$);
|
|
342
|
+
if (!editor || matches.length === 0) return;
|
|
343
|
+
ensureHistoryBaseline(realm, editor);
|
|
344
|
+
replaceStateMatches(editor, matches, replacement, () => {
|
|
345
|
+
refreshSearch(realm, editor);
|
|
346
|
+
onUpdate == null ? void 0 : onUpdate();
|
|
347
|
+
});
|
|
262
348
|
};
|
|
263
349
|
return {
|
|
264
350
|
next,
|
|
265
351
|
prev,
|
|
266
|
-
total:
|
|
352
|
+
total: ranges.length,
|
|
267
353
|
cursor,
|
|
268
354
|
setSearch,
|
|
269
355
|
search,
|
|
@@ -280,73 +366,67 @@ function useEditorSearch() {
|
|
|
280
366
|
};
|
|
281
367
|
}
|
|
282
368
|
const searchPlugin = realmPlugin({
|
|
283
|
-
//TODO: ensure proper event cleanup
|
|
284
369
|
init(realm) {
|
|
285
|
-
if (
|
|
286
|
-
console.warn("CSS.highlights is not supported in this browser. Search
|
|
287
|
-
return;
|
|
370
|
+
if (!supportsHighlights()) {
|
|
371
|
+
console.warn("CSS.highlights is not supported in this browser. Search highlighting will be unavailable.");
|
|
288
372
|
}
|
|
289
373
|
realm.sub(editorSearchCursor$, (cursor) => {
|
|
290
|
-
|
|
291
|
-
|
|
374
|
+
if (realm.getValue(searchOpen$)) {
|
|
375
|
+
focusHighlightRange(realm.getValue(editorSearchRanges$)[cursor - 1]);
|
|
376
|
+
}
|
|
292
377
|
});
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
378
|
+
realm.sub(editorSearchTerm$, () => {
|
|
379
|
+
const editor = realm.getValue(editorSearchActiveEditor$);
|
|
380
|
+
if (editor) refreshSearch(realm, editor);
|
|
381
|
+
});
|
|
382
|
+
realm.sub(searchOpen$, (searchOpen) => {
|
|
383
|
+
if (!searchOpen) {
|
|
384
|
+
clearSearchResults(realm);
|
|
298
385
|
return;
|
|
299
386
|
}
|
|
300
|
-
const
|
|
301
|
-
|
|
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);
|
|
387
|
+
const editor = realm.getValue(editorSearchActiveEditor$);
|
|
388
|
+
if (editor) refreshSearch(realm, editor);
|
|
320
389
|
});
|
|
321
|
-
realm.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
childList: true,
|
|
344
|
-
subtree: true,
|
|
345
|
-
characterData: true
|
|
346
|
-
});
|
|
347
|
-
return () => observer == null ? void 0 : observer.disconnect();
|
|
390
|
+
realm.pub(createActiveEditorSubscription$, (editor) => {
|
|
391
|
+
var _a;
|
|
392
|
+
realm.pubIn({
|
|
393
|
+
[editorSearchActiveEditor$]: editor,
|
|
394
|
+
[editorSearchTextNodeIndex$]: EmptyTextNodeIndex,
|
|
395
|
+
[editorSearchScrollableContent$]: ((_a = editor.getRootElement()) == null ? void 0 : _a.parentElement) ?? null
|
|
396
|
+
});
|
|
397
|
+
clearSearchResults(realm);
|
|
398
|
+
refreshSearch(realm, editor);
|
|
399
|
+
const unregisterUpdate = editor.registerUpdateListener(({ dirtyElements, dirtyLeaves }) => {
|
|
400
|
+
if (dirtyElements.size > 0 || dirtyLeaves.size > 0) refreshSearch(realm, editor);
|
|
401
|
+
});
|
|
402
|
+
const unregisterRoot = editor.registerRootListener((rootElement) => {
|
|
403
|
+
if (!rootElement) {
|
|
404
|
+
if (realm.getValue(editorSearchActiveEditor$) === editor) {
|
|
405
|
+
realm.pubIn({
|
|
406
|
+
[editorSearchTextNodeIndex$]: EmptyTextNodeIndex,
|
|
407
|
+
[editorSearchScrollableContent$]: null
|
|
408
|
+
});
|
|
409
|
+
clearSearchResults(realm);
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
348
412
|
}
|
|
413
|
+
realm.pub(editorSearchScrollableContent$, rootElement.parentElement);
|
|
414
|
+
refreshSearch(realm, editor);
|
|
349
415
|
});
|
|
416
|
+
return () => {
|
|
417
|
+
unregisterUpdate();
|
|
418
|
+
unregisterRoot();
|
|
419
|
+
if (realm.getValue(editorSearchActiveEditor$) === editor) {
|
|
420
|
+
realm.pub(editorSearchActiveEditor$, null);
|
|
421
|
+
clearSearchResults(realm);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
realm.sub(activeEditor$, (editor) => {
|
|
426
|
+
if (!editor) {
|
|
427
|
+
realm.pub(editorSearchScrollableContent$, null);
|
|
428
|
+
clearSearchResults(realm);
|
|
429
|
+
}
|
|
350
430
|
});
|
|
351
431
|
}
|
|
352
432
|
});
|