@nkzw/mdx-editor 0.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/LICENSE +21 -0
- package/README.md +86 -0
- package/UPSTREAM.md +21 -0
- package/dist/EditorIcon.js +75 -0
- package/dist/FormatConstants.js +20 -0
- package/dist/MDXEditor.js +189 -0
- package/dist/MarkdownEditor.js +281 -0
- package/dist/PersistentMarkdownEditor.js +358 -0
- package/dist/RealmWithPlugins.js +35 -0
- package/dist/core.d.ts +3232 -0
- package/dist/core.js +354 -0
- package/dist/defaultSvgIcons.js +371 -0
- package/dist/directive-editors/AdmonitionDirectiveDescriptor.js +28 -0
- package/dist/directive-editors/GenericDirectiveEditor.js +37 -0
- package/dist/exportMarkdownFromLexical.js +262 -0
- package/dist/horizontalRuleShortcut.js +37 -0
- package/dist/importMarkdownToLexical.js +172 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +8 -0
- package/dist/jsx-editors/GenericJsxEditor.js +84 -0
- package/dist/mdastUtilHtmlComment.js +125 -0
- package/dist/persistence.d.ts +128 -0
- package/dist/persistence.js +4 -0
- package/dist/plugins/codeblock/CodeBlockNode.js +183 -0
- package/dist/plugins/codeblock/CodeBlockVisitor.js +14 -0
- package/dist/plugins/codeblock/MdastCodeVisitor.js +23 -0
- package/dist/plugins/codeblock/findCodeBlockDescriptor.js +8 -0
- package/dist/plugins/codeblock/index.js +46 -0
- package/dist/plugins/codemirror/CodeMirrorEditor.js +145 -0
- package/dist/plugins/codemirror/index.js +115 -0
- package/dist/plugins/codemirror/useCodeMirrorRef.js +101 -0
- package/dist/plugins/core/GenericHTMLNode.js +118 -0
- package/dist/plugins/core/LexicalGenericHTMLNodeVisitor.js +15 -0
- package/dist/plugins/core/LexicalLinebreakVisitor.js +10 -0
- package/dist/plugins/core/LexicalParagraphVisitor.js +10 -0
- package/dist/plugins/core/LexicalRootVisitor.js +10 -0
- package/dist/plugins/core/LexicalTextVisitor.js +160 -0
- package/dist/plugins/core/MdastBreakVisitor.js +10 -0
- package/dist/plugins/core/MdastFormattingVisitor.js +81 -0
- package/dist/plugins/core/MdastHTMLNode.js +120 -0
- package/dist/plugins/core/MdastHTMLVisitor.js +17 -0
- package/dist/plugins/core/MdastParagraphVisitor.js +23 -0
- package/dist/plugins/core/MdastRootVisitor.js +9 -0
- package/dist/plugins/core/MdastTextVisitor.js +16 -0
- package/dist/plugins/core/NestedLexicalEditor.js +221 -0
- package/dist/plugins/core/PropertyPopover.js +75 -0
- package/dist/plugins/core/SharedHistoryPlugin.js +10 -0
- package/dist/plugins/core/index.js +692 -0
- package/dist/plugins/core/ui/DownshiftAutoComplete.js +89 -0
- package/dist/plugins/core/ui/PopoverUtils.js +22 -0
- package/dist/plugins/diff-source/DiffSourceWrapper.js +24 -0
- package/dist/plugins/diff-source/DiffViewer.js +84 -0
- package/dist/plugins/diff-source/SourceEditor.js +60 -0
- package/dist/plugins/diff-source/index.js +27 -0
- package/dist/plugins/directives/DirectiveNode.js +107 -0
- package/dist/plugins/directives/DirectiveVisitor.js +10 -0
- package/dist/plugins/directives/MdastDirectiveVisitor.js +30 -0
- package/dist/plugins/directives/index.js +45 -0
- package/dist/plugins/frontmatter/FrontmatterEditor.js +137 -0
- package/dist/plugins/frontmatter/FrontmatterNode.js +70 -0
- package/dist/plugins/frontmatter/LexicalFrontmatterVisitor.js +10 -0
- package/dist/plugins/frontmatter/MdastFrontmatterVisitor.js +10 -0
- package/dist/plugins/frontmatter/index.js +113 -0
- package/dist/plugins/headings/LexicalHeadingVisitor.js +11 -0
- package/dist/plugins/headings/MdastHeadingVisitor.js +10 -0
- package/dist/plugins/headings/index.js +63 -0
- package/dist/plugins/image/EditImageToolbar.js +58 -0
- package/dist/plugins/image/ImageDialog.js +132 -0
- package/dist/plugins/image/ImageEditor.js +279 -0
- package/dist/plugins/image/ImageNode.js +187 -0
- package/dist/plugins/image/ImagePlaceholder.js +9 -0
- package/dist/plugins/image/ImageResizer.js +223 -0
- package/dist/plugins/image/LexicalImageVisitor.js +42 -0
- package/dist/plugins/image/MdastImageVisitor.js +91 -0
- package/dist/plugins/image/index.js +364 -0
- package/dist/plugins/jsx/LexicalJsxNode.js +103 -0
- package/dist/plugins/jsx/LexicalJsxVisitor.js +27 -0
- package/dist/plugins/jsx/LexicalMdxExpressionNode.js +130 -0
- package/dist/plugins/jsx/LexicalMdxExpressionVisitor.js +14 -0
- package/dist/plugins/jsx/MdastMdxExpressionVisitor.js +11 -0
- package/dist/plugins/jsx/MdastMdxJsEsmVisitor.js +8 -0
- package/dist/plugins/jsx/MdastMdxJsxElementVisitor.js +28 -0
- package/dist/plugins/jsx/index.js +97 -0
- package/dist/plugins/jsx/jsxTagName.js +7 -0
- package/dist/plugins/link/AutoLinkPlugin.js +18 -0
- package/dist/plugins/link/LexicalLinkVisitor.js +10 -0
- package/dist/plugins/link/MdastLinkVisitor.js +14 -0
- package/dist/plugins/link/index.js +34 -0
- package/dist/plugins/link-dialog/LinkDialog.js +262 -0
- package/dist/plugins/link-dialog/index.js +304 -0
- package/dist/plugins/lists/CheckListPlugin.js +270 -0
- package/dist/plugins/lists/LexicalListItemVisitor.js +41 -0
- package/dist/plugins/lists/LexicalListVisitor.js +13 -0
- package/dist/plugins/lists/MdastListItemVisitor.js +11 -0
- package/dist/plugins/lists/MdastListVisitor.js +19 -0
- package/dist/plugins/lists/NotesListItemNode.js +22 -0
- package/dist/plugins/lists/index.js +111 -0
- package/dist/plugins/markdown-shortcut/index.js +114 -0
- package/dist/plugins/maxlength/index.js +36 -0
- package/dist/plugins/quote/LexicalQuoteVisitor.js +10 -0
- package/dist/plugins/quote/MdastBlockQuoteVisitor.js +10 -0
- package/dist/plugins/quote/index.js +18 -0
- package/dist/plugins/remote/index.js +52 -0
- package/dist/plugins/search/index.js +360 -0
- package/dist/plugins/table/LexicalTableVisitor.js +10 -0
- package/dist/plugins/table/MdastTableVisitor.js +10 -0
- package/dist/plugins/table/TableEditor.js +527 -0
- package/dist/plugins/table/TableNode.js +208 -0
- package/dist/plugins/table/index.js +66 -0
- package/dist/plugins/thematic-break/LexicalThematicBreakVisitor.js +10 -0
- package/dist/plugins/thematic-break/MdastThematicBreakVisitor.js +10 -0
- package/dist/plugins/thematic-break/index.js +27 -0
- package/dist/plugins/toolbar/components/BlockTypeSelect.js +62 -0
- package/dist/plugins/toolbar/components/BoldItalicUnderlineToggles.js +98 -0
- package/dist/plugins/toolbar/components/ChangeAdmonitionType.js +43 -0
- package/dist/plugins/toolbar/components/ChangeCodeMirrorLanguage.js +42 -0
- package/dist/plugins/toolbar/components/CodeToggle.js +21 -0
- package/dist/plugins/toolbar/components/CreateLink.js +24 -0
- package/dist/plugins/toolbar/components/DiffSourceToggleWrapper.js +42 -0
- package/dist/plugins/toolbar/components/HighlightToggle.js +28 -0
- package/dist/plugins/toolbar/components/InsertAdmonition.js +34 -0
- package/dist/plugins/toolbar/components/InsertCodeBlock.js +23 -0
- package/dist/plugins/toolbar/components/InsertFrontmatter.js +28 -0
- package/dist/plugins/toolbar/components/InsertImage.js +29 -0
- package/dist/plugins/toolbar/components/InsertTable.js +25 -0
- package/dist/plugins/toolbar/components/InsertThematicBreak.js +23 -0
- package/dist/plugins/toolbar/components/KitchenSinkToolbar.js +82 -0
- package/dist/plugins/toolbar/components/ListsToggle.js +29 -0
- package/dist/plugins/toolbar/components/UndoRedo.js +60 -0
- package/dist/plugins/toolbar/index.js +32 -0
- package/dist/plugins/toolbar/primitives/DialogButton.js +130 -0
- package/dist/plugins/toolbar/primitives/TooltipWrap.js +17 -0
- package/dist/plugins/toolbar/primitives/select.js +76 -0
- package/dist/plugins/toolbar/primitives/toolbar.js +144 -0
- package/dist/registerCodeBoundaryEscape.js +40 -0
- package/dist/styles/lexical-theme.module.css.js +62 -0
- package/dist/styles/lexicalTheme.js +32 -0
- package/dist/styles/ui.module.css.js +296 -0
- package/dist/styles.css +2838 -0
- package/dist/utils/detectMac.js +16 -0
- package/dist/utils/fp.js +44 -0
- package/dist/utils/isPartOftheEditorUI.js +12 -0
- package/dist/utils/lexicalHelpers.js +185 -0
- package/dist/utils/makeHslTransparent.js +6 -0
- package/dist/utils/mergeStyleAttributes.js +22 -0
- package/dist/utils/uuid4.js +10 -0
- package/dist/utils/voidEmitter.js +15 -0
- package/package.json +133 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { Cell, debounceTime, useRealm, useCellValue, useCell } from "@mdxeditor/gurx";
|
|
2
|
+
import { getNearestEditorFromDOMNode, $getNearestNodeFromDOMNode, $isTextNode, $createRangeSelection } from "lexical";
|
|
3
|
+
import { realmPlugin } from "../../RealmWithPlugins.js";
|
|
4
|
+
import { contentEditableRef$, createRootEditorSubscription$ } from "../core/index.js";
|
|
5
|
+
const EmptyTextNodeIndex = {
|
|
6
|
+
allText: "",
|
|
7
|
+
nodeIndex: [],
|
|
8
|
+
offsetIndex: []
|
|
9
|
+
};
|
|
10
|
+
const editorSearchTerm$ = Cell("");
|
|
11
|
+
const editorSearchRanges$ = Cell([]);
|
|
12
|
+
const editorSearchCursor$ = Cell(0);
|
|
13
|
+
const editorSearchTextNodeIndex$ = Cell(EmptyTextNodeIndex);
|
|
14
|
+
const searchOpen$ = Cell(false);
|
|
15
|
+
const editorSearchTermDebounced$ = Cell("", (realm) => {
|
|
16
|
+
realm.link(editorSearchTermDebounced$, realm.pipe(editorSearchTerm$, realm.transformer(debounceTime(250))));
|
|
17
|
+
});
|
|
18
|
+
const editorSearchScrollableContent$ = Cell(
|
|
19
|
+
null,
|
|
20
|
+
(r) => r.sub(contentEditableRef$, (cref) => {
|
|
21
|
+
r.pub(editorSearchScrollableContent$, cref?.current?.parentNode ?? null);
|
|
22
|
+
})
|
|
23
|
+
);
|
|
24
|
+
const MDX_SEARCH_NAME = "MdxSearch";
|
|
25
|
+
const MDX_FOCUS_SEARCH_NAME = "MdxFocusSearch";
|
|
26
|
+
const debouncedIndexer$ = Cell(EmptyTextNodeIndex, (realm) => {
|
|
27
|
+
realm.link(debouncedIndexer$, realm.pipe(editorSearchTextNodeIndex$, realm.transformer(debounceTime(250))));
|
|
28
|
+
});
|
|
29
|
+
function* searchText(allText, searchQuery) {
|
|
30
|
+
if (!searchQuery) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
let regex;
|
|
34
|
+
try {
|
|
35
|
+
regex = new RegExp(searchQuery, "gi");
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error("Invalid search pattern:", e);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let match;
|
|
41
|
+
while ((match = regex.exec(allText)) !== null) {
|
|
42
|
+
if (match[0].length === 0) {
|
|
43
|
+
if (regex.lastIndex === match.index) {
|
|
44
|
+
regex.lastIndex++;
|
|
45
|
+
}
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const start = match.index;
|
|
49
|
+
const end = start + match[0].length - 1;
|
|
50
|
+
yield [start, end];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function indexAllTextNodes(root) {
|
|
54
|
+
let allText = "";
|
|
55
|
+
const nodeIndex = [];
|
|
56
|
+
const offsetIndex = [];
|
|
57
|
+
if (!root) {
|
|
58
|
+
return { allText: "", nodeIndex, offsetIndex };
|
|
59
|
+
}
|
|
60
|
+
const contentSelector = "p, h1, h2, h3, h4, h5, h6, li, code, pre";
|
|
61
|
+
const treeWalker = document.createTreeWalker(
|
|
62
|
+
root,
|
|
63
|
+
NodeFilter.SHOW_TEXT,
|
|
64
|
+
// The corrected heuristic: accept any text node that is a descendant of a valid content container.
|
|
65
|
+
(node) => {
|
|
66
|
+
if (node.parentElement?.closest(contentSelector)) {
|
|
67
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
68
|
+
}
|
|
69
|
+
return NodeFilter.FILTER_REJECT;
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
let currentNode;
|
|
73
|
+
while (currentNode = treeWalker.nextNode()) {
|
|
74
|
+
const nodeContent = currentNode.textContent?.normalize("NFKD") ?? currentNode.textContent ?? "";
|
|
75
|
+
for (let i = 0; i < nodeContent.length; i++) {
|
|
76
|
+
nodeIndex.push(currentNode);
|
|
77
|
+
offsetIndex.push(i);
|
|
78
|
+
allText += nodeContent[i] ?? "";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { allText, nodeIndex, offsetIndex };
|
|
82
|
+
}
|
|
83
|
+
function* rangeSearchScan(searchQuery, { allText, offsetIndex, nodeIndex }) {
|
|
84
|
+
for (const [start, end] of searchText(allText, searchQuery)) {
|
|
85
|
+
const startOffset = offsetIndex[start];
|
|
86
|
+
const endOffset = offsetIndex[end];
|
|
87
|
+
const startNode = nodeIndex[start];
|
|
88
|
+
const endNode = nodeIndex[end];
|
|
89
|
+
const range = new Range();
|
|
90
|
+
if (startNode === void 0 || endNode === void 0 || startOffset === void 0 || endOffset === void 0) {
|
|
91
|
+
throw new Error("Invalid range: startNode, endNode, startOffset, or endOffset is undefined.");
|
|
92
|
+
}
|
|
93
|
+
range.setStart(startNode, startOffset);
|
|
94
|
+
range.setEnd(endNode, endOffset + 1);
|
|
95
|
+
yield range;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const focusHighlightRange = (range) => {
|
|
99
|
+
CSS.highlights.delete(MDX_FOCUS_SEARCH_NAME);
|
|
100
|
+
if (range) CSS.highlights.set(MDX_FOCUS_SEARCH_NAME, new Highlight(range));
|
|
101
|
+
};
|
|
102
|
+
const highlightRanges = (ranges) => {
|
|
103
|
+
CSS.highlights.set(MDX_SEARCH_NAME, new Highlight(...ranges));
|
|
104
|
+
};
|
|
105
|
+
const resetHighlights = () => {
|
|
106
|
+
CSS.highlights.delete(MDX_SEARCH_NAME);
|
|
107
|
+
CSS.highlights.delete(MDX_FOCUS_SEARCH_NAME);
|
|
108
|
+
};
|
|
109
|
+
const scrollToRange = (range, contentEditable, options) => {
|
|
110
|
+
const ignoreIfInView = options?.ignoreIfInView ?? true;
|
|
111
|
+
const behavior = options?.behavior ?? "smooth";
|
|
112
|
+
const [first] = range.getClientRects();
|
|
113
|
+
if (!contentEditable) {
|
|
114
|
+
console.warn("No content-editable element found for scrolling.");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!first) {
|
|
118
|
+
console.warn("No client rect found for the range, cannot scroll.");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const containerRect = contentEditable.getBoundingClientRect();
|
|
122
|
+
const topRelativeToContainer = first.top - containerRect.top;
|
|
123
|
+
const bottomRelativeToContainer = first.bottom - containerRect.top;
|
|
124
|
+
if (ignoreIfInView) {
|
|
125
|
+
const rangeTop = topRelativeToContainer + contentEditable.scrollTop;
|
|
126
|
+
const rangeBottom = bottomRelativeToContainer + contentEditable.scrollTop;
|
|
127
|
+
const visibleTop = contentEditable.scrollTop;
|
|
128
|
+
const visibleBottom = visibleTop + contentEditable.clientHeight;
|
|
129
|
+
const inView = rangeTop >= visibleTop && rangeBottom <= visibleBottom;
|
|
130
|
+
if (inView) return;
|
|
131
|
+
}
|
|
132
|
+
const top = topRelativeToContainer + contentEditable.scrollTop - first.height;
|
|
133
|
+
contentEditable.scrollTo({ top, behavior });
|
|
134
|
+
};
|
|
135
|
+
function isSimilarRange(range1, range2) {
|
|
136
|
+
return range1.startContainer === range2.startContainer && range1.startOffset === range2.startOffset;
|
|
137
|
+
}
|
|
138
|
+
function replaceTextInRange(range, str, onUpdate) {
|
|
139
|
+
const startDomNode = range.startContainer;
|
|
140
|
+
const endDomNode = range.endContainer;
|
|
141
|
+
const startOffset = range.startOffset;
|
|
142
|
+
const endOffset = range.endOffset;
|
|
143
|
+
const editor = getNearestEditorFromDOMNode(startDomNode);
|
|
144
|
+
if (!editor) {
|
|
145
|
+
console.warn("No editor found for the provided DOM node.");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
editor.update(
|
|
149
|
+
() => {
|
|
150
|
+
const startLexicalNode = $getNearestNodeFromDOMNode(startDomNode);
|
|
151
|
+
const endLexicalNode = $getNearestNodeFromDOMNode(endDomNode);
|
|
152
|
+
if (!$isTextNode(startLexicalNode) || !$isTextNode(endLexicalNode)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const selection = $createRangeSelection();
|
|
157
|
+
selection.anchor.set(startLexicalNode.getKey(), startOffset, "text");
|
|
158
|
+
selection.focus.set(endLexicalNode.getKey(), endOffset, "text");
|
|
159
|
+
selection.insertText(str);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.warn("Error replacing text in the editor:", e);
|
|
162
|
+
if (onUpdate) {
|
|
163
|
+
onUpdate();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
onUpdate
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
function useEditorSearch() {
|
|
173
|
+
const realm = useRealm();
|
|
174
|
+
const ranges = useCellValue(editorSearchRanges$);
|
|
175
|
+
const cursor = useCellValue(editorSearchCursor$);
|
|
176
|
+
const search = useCellValue(editorSearchTerm$);
|
|
177
|
+
const currentRange = ranges[cursor - 1] ?? null;
|
|
178
|
+
const contentEditable = useCellValue(editorSearchScrollableContent$);
|
|
179
|
+
const [isSearchOpen, setIsSearchOpen] = useCell(searchOpen$);
|
|
180
|
+
const openSearch = () => {
|
|
181
|
+
setIsSearchOpen(true);
|
|
182
|
+
};
|
|
183
|
+
const closeSearch = () => {
|
|
184
|
+
setIsSearchOpen(false);
|
|
185
|
+
};
|
|
186
|
+
const toggleSearch = () => {
|
|
187
|
+
setIsSearchOpen(!isSearchOpen);
|
|
188
|
+
};
|
|
189
|
+
const rangeCount = ranges.length;
|
|
190
|
+
const scrollToRangeOrIndex = (range, options) => {
|
|
191
|
+
const scrollRange = typeof range === "number" ? ranges[range - 1] : range;
|
|
192
|
+
if (!scrollRange) {
|
|
193
|
+
throw new Error("Error scrolling to range, range does not exist");
|
|
194
|
+
}
|
|
195
|
+
scrollToRange(scrollRange, contentEditable, options);
|
|
196
|
+
};
|
|
197
|
+
const setSearch = (term) => {
|
|
198
|
+
if ((term ?? "") !== search) {
|
|
199
|
+
realm.pub(editorSearchCursor$, 0);
|
|
200
|
+
}
|
|
201
|
+
realm.pub(editorSearchTermDebounced$, term ?? "");
|
|
202
|
+
};
|
|
203
|
+
const next = () => {
|
|
204
|
+
if (!ranges.length) return;
|
|
205
|
+
const newVal = cursor % ranges.length + 1;
|
|
206
|
+
scrollToRangeOrIndex(newVal);
|
|
207
|
+
realm.pub(editorSearchCursor$, newVal);
|
|
208
|
+
};
|
|
209
|
+
const prev = () => {
|
|
210
|
+
if (!ranges.length) return;
|
|
211
|
+
const newVal = cursor <= 1 ? ranges.length : cursor - 1;
|
|
212
|
+
scrollToRangeOrIndex(newVal);
|
|
213
|
+
realm.pub(editorSearchCursor$, newVal);
|
|
214
|
+
};
|
|
215
|
+
const replace = (str, onUpdate) => {
|
|
216
|
+
const currentRange2 = ranges[cursor - 1];
|
|
217
|
+
if (!currentRange2) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const { startContainer, startOffset } = currentRange2 ?? {};
|
|
221
|
+
replaceTextInRange(currentRange2, str, () => {
|
|
222
|
+
const unsub = realm.sub(editorSearchRanges$, (newRanges) => {
|
|
223
|
+
unsub();
|
|
224
|
+
if (isSimilarRange(newRanges[cursor - 1] ?? {}, {
|
|
225
|
+
startOffset,
|
|
226
|
+
startContainer
|
|
227
|
+
})) {
|
|
228
|
+
realm.pub(editorSearchCursor$, (cursor + 1) % (newRanges.length + 1) || 1);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
onUpdate?.();
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
const replaceAll = (str, onUpdate) => {
|
|
235
|
+
const runReplaceAll = () => {
|
|
236
|
+
let ticks = 0;
|
|
237
|
+
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
238
|
+
const textReplaceRange = ranges[i];
|
|
239
|
+
if (!textReplaceRange) {
|
|
240
|
+
throw new Error("error replacing all text range does not exist");
|
|
241
|
+
}
|
|
242
|
+
replaceTextInRange(textReplaceRange, str, () => {
|
|
243
|
+
ticks++;
|
|
244
|
+
if (ticks >= ranges.length) {
|
|
245
|
+
onUpdate?.();
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
if (typeof requestIdleCallback === "function") {
|
|
251
|
+
requestIdleCallback(runReplaceAll);
|
|
252
|
+
} else {
|
|
253
|
+
setTimeout(runReplaceAll, 0);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
next,
|
|
258
|
+
prev,
|
|
259
|
+
total: rangeCount,
|
|
260
|
+
cursor,
|
|
261
|
+
setSearch,
|
|
262
|
+
search,
|
|
263
|
+
currentRange,
|
|
264
|
+
isSearchOpen,
|
|
265
|
+
setIsSearchOpen,
|
|
266
|
+
openSearch,
|
|
267
|
+
closeSearch,
|
|
268
|
+
toggleSearch,
|
|
269
|
+
ranges,
|
|
270
|
+
scrollToRangeOrIndex,
|
|
271
|
+
replace,
|
|
272
|
+
replaceAll
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const searchPlugin = realmPlugin({
|
|
276
|
+
//TODO: ensure proper event cleanup
|
|
277
|
+
init(realm) {
|
|
278
|
+
if (typeof CSS.highlights === "undefined") {
|
|
279
|
+
console.warn("CSS.highlights is not supported in this browser. Search functionality will be limited.");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
realm.sub(editorSearchCursor$, (cursor) => {
|
|
283
|
+
const ranges = realm.getValue(editorSearchRanges$);
|
|
284
|
+
focusHighlightRange(ranges[cursor - 1]);
|
|
285
|
+
});
|
|
286
|
+
const updateHighlights = (searchQuery, textNodeIndex) => {
|
|
287
|
+
if (!searchQuery) {
|
|
288
|
+
realm.pub(editorSearchCursor$, 0);
|
|
289
|
+
realm.pub(editorSearchRanges$, []);
|
|
290
|
+
resetHighlights();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const ranges = Array.from(rangeSearchScan(searchQuery, textNodeIndex));
|
|
294
|
+
realm.pub(editorSearchRanges$, ranges);
|
|
295
|
+
highlightRanges(ranges);
|
|
296
|
+
if (ranges.length) {
|
|
297
|
+
const currentCursor = realm.getValue(editorSearchCursor$) || 1;
|
|
298
|
+
focusHighlightRange(ranges[currentCursor - 1]);
|
|
299
|
+
realm.pub(editorSearchCursor$, currentCursor);
|
|
300
|
+
const scrollRange = ranges[currentCursor - 1];
|
|
301
|
+
if (!scrollRange) throw new Error("error updating highlights, scroll range does not exist");
|
|
302
|
+
const contentEditable = realm.getValue(editorSearchScrollableContent$);
|
|
303
|
+
scrollToRange(scrollRange, contentEditable, {
|
|
304
|
+
ignoreIfInView: true
|
|
305
|
+
});
|
|
306
|
+
} else {
|
|
307
|
+
resetHighlights();
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
realm.sub(editorSearchTextNodeIndex$, (textNodeIndex) => {
|
|
311
|
+
updateHighlights(realm.getValue(editorSearchTerm$), textNodeIndex);
|
|
312
|
+
});
|
|
313
|
+
realm.sub(editorSearchTerm$, (searchQuery) => {
|
|
314
|
+
updateHighlights(searchQuery, realm.getValue(editorSearchTextNodeIndex$));
|
|
315
|
+
});
|
|
316
|
+
realm.pub(createRootEditorSubscription$, (editor) => {
|
|
317
|
+
let observer = null;
|
|
318
|
+
return editor.registerRootListener((rootElement) => {
|
|
319
|
+
if (observer) {
|
|
320
|
+
observer.disconnect();
|
|
321
|
+
observer = null;
|
|
322
|
+
}
|
|
323
|
+
if (rootElement) {
|
|
324
|
+
const initialIndex = indexAllTextNodes(rootElement);
|
|
325
|
+
realm.pub(editorSearchTextNodeIndex$, initialIndex);
|
|
326
|
+
observer = new MutationObserver(() => {
|
|
327
|
+
const newIndex = indexAllTextNodes(rootElement);
|
|
328
|
+
if (realm.getValue(searchOpen$)) {
|
|
329
|
+
realm.pub(editorSearchTextNodeIndex$, newIndex);
|
|
330
|
+
} else {
|
|
331
|
+
realm.pub(debouncedIndexer$, newIndex);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
observer.observe(rootElement, {
|
|
335
|
+
childList: true,
|
|
336
|
+
subtree: true,
|
|
337
|
+
characterData: true
|
|
338
|
+
});
|
|
339
|
+
return () => observer?.disconnect();
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
export {
|
|
346
|
+
EmptyTextNodeIndex,
|
|
347
|
+
MDX_FOCUS_SEARCH_NAME,
|
|
348
|
+
MDX_SEARCH_NAME,
|
|
349
|
+
debouncedIndexer$,
|
|
350
|
+
editorSearchCursor$,
|
|
351
|
+
editorSearchRanges$,
|
|
352
|
+
editorSearchScrollableContent$,
|
|
353
|
+
editorSearchTerm$,
|
|
354
|
+
editorSearchTermDebounced$,
|
|
355
|
+
editorSearchTextNodeIndex$,
|
|
356
|
+
rangeSearchScan,
|
|
357
|
+
searchOpen$,
|
|
358
|
+
searchPlugin,
|
|
359
|
+
useEditorSearch
|
|
360
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { $isTableNode } from "./TableNode.js";
|
|
2
|
+
const LexicalTableVisitor = {
|
|
3
|
+
testLexicalNode: $isTableNode,
|
|
4
|
+
visitLexicalNode({ actions, mdastParent, lexicalNode }) {
|
|
5
|
+
actions.appendToParent(mdastParent, lexicalNode.getMdastNode());
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
export {
|
|
9
|
+
LexicalTableVisitor
|
|
10
|
+
};
|