@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,262 @@
|
|
|
1
|
+
import { $isElementNode } from "lexical";
|
|
2
|
+
import { toMarkdown } from "mdast-util-to-markdown";
|
|
3
|
+
import { isMdastHTMLNode } from "./plugins/core/MdastHTMLNode.js";
|
|
4
|
+
import { mergeStyleAttributes } from "./utils/mergeStyleAttributes.js";
|
|
5
|
+
function isParent(node) {
|
|
6
|
+
return node.children instanceof Array;
|
|
7
|
+
}
|
|
8
|
+
function exportLexicalTreeToMdast({
|
|
9
|
+
root,
|
|
10
|
+
visitors,
|
|
11
|
+
jsxComponentDescriptors,
|
|
12
|
+
jsxIsAvailable,
|
|
13
|
+
addImportStatements = true
|
|
14
|
+
}) {
|
|
15
|
+
let unistRoot = null;
|
|
16
|
+
const referredComponents = /* @__PURE__ */ new Set();
|
|
17
|
+
const knownImportSources = /* @__PURE__ */ new Map();
|
|
18
|
+
visitors = visitors.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
19
|
+
visit(root, null);
|
|
20
|
+
function registerReferredComponent(componentName, importStatement) {
|
|
21
|
+
referredComponents.add(componentName);
|
|
22
|
+
if (importStatement) {
|
|
23
|
+
knownImportSources.set(componentName, { ...importStatement });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function appendToParent(parentNode, node) {
|
|
27
|
+
if (unistRoot === null) {
|
|
28
|
+
unistRoot = node;
|
|
29
|
+
return unistRoot;
|
|
30
|
+
}
|
|
31
|
+
if (!isParent(parentNode)) {
|
|
32
|
+
throw new Error("Attempting to append children to a non-parent");
|
|
33
|
+
}
|
|
34
|
+
const siblings = parentNode.children;
|
|
35
|
+
const prevSibling = siblings.at(-1);
|
|
36
|
+
if (prevSibling) {
|
|
37
|
+
const joinVisitor = visitors.find((visitor) => visitor.shouldJoin?.(prevSibling, node));
|
|
38
|
+
if (joinVisitor) {
|
|
39
|
+
const joinedNode = joinVisitor.join(prevSibling, node);
|
|
40
|
+
siblings.splice(siblings.length - 1, 1, joinedNode);
|
|
41
|
+
return joinedNode;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
siblings.push(node);
|
|
45
|
+
return node;
|
|
46
|
+
}
|
|
47
|
+
function visitChildren(lexicalNode, parentNode) {
|
|
48
|
+
lexicalNode.getChildren().forEach((lexicalChild) => {
|
|
49
|
+
visit(lexicalChild, parentNode);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function visit(lexicalNode, mdastParent, usedVisitors = null) {
|
|
53
|
+
const visitor = visitors.find((visitor2, index) => {
|
|
54
|
+
if (usedVisitors?.has(index)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return visitor2.testLexicalNode?.(lexicalNode);
|
|
58
|
+
});
|
|
59
|
+
if (!visitor) {
|
|
60
|
+
throw new Error(`no lexical visitor found for ${lexicalNode.getType()}`, {
|
|
61
|
+
cause: lexicalNode
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
visitor.visitLexicalNode?.({
|
|
65
|
+
lexicalNode,
|
|
66
|
+
mdastParent,
|
|
67
|
+
actions: {
|
|
68
|
+
addAndStepInto(type, props = {}, hasChildren = true) {
|
|
69
|
+
const newNode = {
|
|
70
|
+
type,
|
|
71
|
+
...props,
|
|
72
|
+
...hasChildren ? { children: [] } : {}
|
|
73
|
+
};
|
|
74
|
+
appendToParent(mdastParent, newNode);
|
|
75
|
+
if ($isElementNode(lexicalNode) && hasChildren) {
|
|
76
|
+
visitChildren(lexicalNode, newNode);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
appendToParent,
|
|
80
|
+
visitChildren,
|
|
81
|
+
visit,
|
|
82
|
+
registerReferredComponent,
|
|
83
|
+
nextVisitor() {
|
|
84
|
+
visit(lexicalNode, mdastParent, (usedVisitors ?? /* @__PURE__ */ new Set()).add(visitors.indexOf(visitor)));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (unistRoot === null) {
|
|
91
|
+
throw new Error("traversal ended with no root element");
|
|
92
|
+
}
|
|
93
|
+
const importsMap = /* @__PURE__ */ new Map();
|
|
94
|
+
const defaultImportsMap = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const componentName of referredComponents) {
|
|
96
|
+
const descriptor = jsxComponentDescriptors.find((descriptor2) => descriptor2.name === componentName) ?? knownImportSources.get(componentName) ?? jsxComponentDescriptors.find((descriptor2) => descriptor2.name === "*");
|
|
97
|
+
if (!descriptor) {
|
|
98
|
+
throw new Error(`Component ${componentName} is used but not imported`);
|
|
99
|
+
}
|
|
100
|
+
if (!descriptor.source) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (descriptor.defaultExport) {
|
|
104
|
+
defaultImportsMap.set(componentName, descriptor.source);
|
|
105
|
+
} else {
|
|
106
|
+
const { source } = descriptor;
|
|
107
|
+
const existing = importsMap.get(source);
|
|
108
|
+
if (existing) {
|
|
109
|
+
existing.push(componentName);
|
|
110
|
+
} else {
|
|
111
|
+
importsMap.set(source, [componentName]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!addImportStatements) {
|
|
116
|
+
for (const [path, names] of importsMap.entries()) {
|
|
117
|
+
const cleaned = names.filter((n) => knownImportSources.has(n));
|
|
118
|
+
if (cleaned.length > 0) {
|
|
119
|
+
importsMap.set(path, cleaned);
|
|
120
|
+
} else {
|
|
121
|
+
importsMap.delete(path);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const key of defaultImportsMap.keys()) {
|
|
125
|
+
if (!knownImportSources.has(key)) {
|
|
126
|
+
defaultImportsMap.delete(key);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const imports = Array.from(importsMap).map(([source, componentNames]) => {
|
|
131
|
+
return {
|
|
132
|
+
type: "mdxjsEsm",
|
|
133
|
+
value: `import { ${componentNames.join(", ")} } from '${source}'`
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
imports.push(
|
|
137
|
+
...Array.from(defaultImportsMap).map(([componentName, source]) => {
|
|
138
|
+
return {
|
|
139
|
+
type: "mdxjsEsm",
|
|
140
|
+
value: `import ${componentName} from '${source}'`
|
|
141
|
+
};
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
const typedRoot = unistRoot;
|
|
145
|
+
const frontmatter = typedRoot.children.find((child) => child.type === "yaml");
|
|
146
|
+
if (frontmatter) {
|
|
147
|
+
typedRoot.children.splice(typedRoot.children.indexOf(frontmatter) + 1, 0, ...imports);
|
|
148
|
+
} else {
|
|
149
|
+
typedRoot.children.unshift(...imports);
|
|
150
|
+
}
|
|
151
|
+
fixWrappingWhitespace(typedRoot, []);
|
|
152
|
+
collapseNestedHtmlTags(typedRoot);
|
|
153
|
+
if (!jsxIsAvailable) {
|
|
154
|
+
convertUnderlineJsxToHtml(typedRoot);
|
|
155
|
+
}
|
|
156
|
+
return typedRoot;
|
|
157
|
+
}
|
|
158
|
+
function collapseNestedHtmlTags(node) {
|
|
159
|
+
if ("children" in node && node.children.length > 0) {
|
|
160
|
+
if (isMdastHTMLNode(node) && node.children.length === 1) {
|
|
161
|
+
const onlyChild = node.children[0];
|
|
162
|
+
if (onlyChild.type === "mdxJsxTextElement" && onlyChild.name === "span") {
|
|
163
|
+
onlyChild.attributes.forEach((attribute) => {
|
|
164
|
+
if (attribute.type === "mdxJsxAttribute") {
|
|
165
|
+
const parentAttribute = node.attributes.find((attr) => attr.type === "mdxJsxAttribute" && attr.name === attribute.name);
|
|
166
|
+
if (parentAttribute) {
|
|
167
|
+
if (attribute.name === "className") {
|
|
168
|
+
const mergedClassesSet = /* @__PURE__ */ new Set([
|
|
169
|
+
...parentAttribute.value.split(" "),
|
|
170
|
+
...attribute.value.split(" ")
|
|
171
|
+
]);
|
|
172
|
+
parentAttribute.value = Array.from(mergedClassesSet).join(" ");
|
|
173
|
+
} else if (attribute.name === "style") {
|
|
174
|
+
parentAttribute.value = mergeStyleAttributes(parentAttribute.value, attribute.value);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
node.attributes.push(attribute);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
node.children = onlyChild.children;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
node.children.forEach((child) => {
|
|
185
|
+
collapseNestedHtmlTags(child);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function convertUnderlineJsxToHtml(node) {
|
|
190
|
+
if (Object.hasOwn(node, "children")) {
|
|
191
|
+
const nodeAsParent = node;
|
|
192
|
+
const newChildren = [];
|
|
193
|
+
nodeAsParent.children.forEach((child) => {
|
|
194
|
+
if (child.type === "mdxJsxTextElement" && child.name === "u") {
|
|
195
|
+
newChildren.push(...[{ type: "html", value: "<u>" }, ...child.children, { type: "html", value: "</u>" }]);
|
|
196
|
+
} else {
|
|
197
|
+
newChildren.push(child);
|
|
198
|
+
convertUnderlineJsxToHtml(child);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
nodeAsParent.children = newChildren;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const TRAILING_WHITESPACE_REGEXP = /\s+$/;
|
|
205
|
+
const LEADING_WHITESPACE_REGEXP = /^\s+/;
|
|
206
|
+
function fixWrappingWhitespace(node, parentChain) {
|
|
207
|
+
if (node.type === "strong" || node.type === "emphasis") {
|
|
208
|
+
const lastChild = node.children.at(-1);
|
|
209
|
+
if (lastChild?.type === "text") {
|
|
210
|
+
const trailingWhitespace = TRAILING_WHITESPACE_REGEXP.exec(lastChild.value);
|
|
211
|
+
if (trailingWhitespace) {
|
|
212
|
+
lastChild.value = lastChild.value.replace(TRAILING_WHITESPACE_REGEXP, "");
|
|
213
|
+
const parent = parentChain.at(-1);
|
|
214
|
+
if (parent) {
|
|
215
|
+
parent.children.splice(parent.children.indexOf(node) + 1, 0, {
|
|
216
|
+
type: "text",
|
|
217
|
+
value: trailingWhitespace[0]
|
|
218
|
+
});
|
|
219
|
+
fixWrappingWhitespace(parent, parentChain.slice(0, -1));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const firstChild = node.children.at(0);
|
|
224
|
+
if (firstChild?.type === "text") {
|
|
225
|
+
const leadingWhitespace = LEADING_WHITESPACE_REGEXP.exec(firstChild.value);
|
|
226
|
+
if (leadingWhitespace) {
|
|
227
|
+
firstChild.value = firstChild.value.replace(LEADING_WHITESPACE_REGEXP, "");
|
|
228
|
+
const parent = parentChain.at(-1);
|
|
229
|
+
if (parent) {
|
|
230
|
+
parent.children.splice(parent.children.indexOf(node), 0, {
|
|
231
|
+
type: "text",
|
|
232
|
+
value: leadingWhitespace[0]
|
|
233
|
+
});
|
|
234
|
+
fixWrappingWhitespace(parent, parentChain.slice(0, -1));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if ("children" in node && node.children.length > 0) {
|
|
240
|
+
const nodeAsParent = node;
|
|
241
|
+
nodeAsParent.children.forEach((child) => {
|
|
242
|
+
fixWrappingWhitespace(child, [...parentChain, nodeAsParent]);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function exportMarkdownFromLexical({
|
|
247
|
+
root,
|
|
248
|
+
toMarkdownOptions,
|
|
249
|
+
toMarkdownExtensions,
|
|
250
|
+
visitors,
|
|
251
|
+
jsxComponentDescriptors,
|
|
252
|
+
jsxIsAvailable
|
|
253
|
+
}) {
|
|
254
|
+
return toMarkdown(exportLexicalTreeToMdast({ root, visitors, jsxComponentDescriptors, jsxIsAvailable }), {
|
|
255
|
+
extensions: toMarkdownExtensions,
|
|
256
|
+
...toMarkdownOptions
|
|
257
|
+
}) + "\n";
|
|
258
|
+
}
|
|
259
|
+
export {
|
|
260
|
+
exportLexicalTreeToMdast,
|
|
261
|
+
exportMarkdownFromLexical
|
|
262
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { $createHorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
|
|
2
|
+
import { KEY_ENTER_COMMAND, $getSelection, $isRangeSelection, $isTextNode, $isParagraphNode, $isRootOrShadowRoot, COMMAND_PRIORITY_HIGH } from "lexical";
|
|
3
|
+
import { createRootEditorSubscription$ } from "./plugins/core/index.js";
|
|
4
|
+
import { realmPlugin } from "./RealmWithPlugins.js";
|
|
5
|
+
const registerHorizontalRuleOnEnter = (editor) => editor.registerCommand(
|
|
6
|
+
KEY_ENTER_COMMAND,
|
|
7
|
+
() => {
|
|
8
|
+
const selection = $getSelection();
|
|
9
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const textNode = selection.anchor.getNode();
|
|
13
|
+
const paragraph = textNode.getParent();
|
|
14
|
+
if (!$isTextNode(textNode) || selection.anchor.offset !== 3 || textNode.getTextContent() !== "---" || !$isParagraphNode(paragraph) || paragraph.getChildrenSize() !== 1 || !$isRootOrShadowRoot(paragraph.getParent())) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const horizontalRule = $createHorizontalRuleNode();
|
|
18
|
+
paragraph.clear();
|
|
19
|
+
if (paragraph.getNextSibling()) {
|
|
20
|
+
paragraph.replace(horizontalRule);
|
|
21
|
+
} else {
|
|
22
|
+
paragraph.insertBefore(horizontalRule);
|
|
23
|
+
}
|
|
24
|
+
horizontalRule.selectNext();
|
|
25
|
+
return true;
|
|
26
|
+
},
|
|
27
|
+
COMMAND_PRIORITY_HIGH
|
|
28
|
+
);
|
|
29
|
+
const horizontalRuleOnEnterPlugin = realmPlugin({
|
|
30
|
+
init(realm) {
|
|
31
|
+
realm.pub(createRootEditorSubscription$, registerHorizontalRuleOnEnter);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
export {
|
|
35
|
+
horizontalRuleOnEnterPlugin,
|
|
36
|
+
registerHorizontalRuleOnEnter
|
|
37
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
2
|
+
import { toMarkdown } from "mdast-util-to-markdown";
|
|
3
|
+
function isParent(node) {
|
|
4
|
+
return node.children instanceof Array;
|
|
5
|
+
}
|
|
6
|
+
class MarkdownParseError extends Error {
|
|
7
|
+
constructor(message, cause) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "MarkdownParseError";
|
|
10
|
+
this.cause = cause;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
class UnrecognizedMarkdownConstructError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "UnrecognizedMarkdownConstructError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function gatherMetadata(mdastNode) {
|
|
20
|
+
const importsMap = /* @__PURE__ */ new Map();
|
|
21
|
+
if (mdastNode.type !== "root") {
|
|
22
|
+
return {
|
|
23
|
+
importDeclarations: {}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const importStatements = mdastNode.children.filter((n) => n.type === "mdxjsEsm").filter((n) => n.value.startsWith("import "));
|
|
27
|
+
importStatements.forEach((imp) => {
|
|
28
|
+
(imp.data?.estree?.body ?? []).forEach((declaration) => {
|
|
29
|
+
if (declaration.type !== "ImportDeclaration") {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
declaration.specifiers.forEach((specifier) => {
|
|
33
|
+
importsMap.set(specifier.local.name, {
|
|
34
|
+
source: `${declaration.source.value}`,
|
|
35
|
+
defaultExport: specifier.type === "ImportDefaultSpecifier"
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
importDeclarations: Object.fromEntries(importsMap.entries())
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function importMarkdownToLexical({
|
|
45
|
+
root,
|
|
46
|
+
markdown,
|
|
47
|
+
visitors,
|
|
48
|
+
syntaxExtensions,
|
|
49
|
+
mdastExtensions,
|
|
50
|
+
...descriptors
|
|
51
|
+
}) {
|
|
52
|
+
let mdastRoot;
|
|
53
|
+
try {
|
|
54
|
+
mdastRoot = fromMarkdown(markdown, {
|
|
55
|
+
extensions: syntaxExtensions,
|
|
56
|
+
mdastExtensions
|
|
57
|
+
});
|
|
58
|
+
} catch (e) {
|
|
59
|
+
if (e instanceof Error) {
|
|
60
|
+
throw new MarkdownParseError(`Error parsing markdown: ${e.message}`, e);
|
|
61
|
+
} else {
|
|
62
|
+
throw new MarkdownParseError(`Error parsing markdown: ${e}`, e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (mdastRoot.children.length === 0) {
|
|
66
|
+
mdastRoot.children.push({ type: "paragraph", children: [] });
|
|
67
|
+
}
|
|
68
|
+
if (mdastRoot.children.at(-1)?.type !== "paragraph") {
|
|
69
|
+
mdastRoot.children.push({ type: "paragraph", children: [] });
|
|
70
|
+
}
|
|
71
|
+
importMdastTreeToLexical({ root, mdastRoot, visitors, ...descriptors });
|
|
72
|
+
}
|
|
73
|
+
function importMdastTreeToLexical({ root, mdastRoot, visitors, ...descriptors }) {
|
|
74
|
+
const formattingMap = /* @__PURE__ */ new WeakMap();
|
|
75
|
+
const styleMap = /* @__PURE__ */ new WeakMap();
|
|
76
|
+
const metaData = gatherMetadata(mdastRoot);
|
|
77
|
+
visitors = visitors.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
78
|
+
function visitChildren(mdastNode, lexicalParent) {
|
|
79
|
+
if (!isParent(mdastNode)) {
|
|
80
|
+
throw new Error("Attempting to visit children of a non-parent");
|
|
81
|
+
}
|
|
82
|
+
mdastNode.children.forEach((child) => {
|
|
83
|
+
visit(child, lexicalParent, mdastNode);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function visit(mdastNode, lexicalParent, mdastParent, skipVisitors = null) {
|
|
87
|
+
const visitor = visitors.find((visitor2, index) => {
|
|
88
|
+
if (skipVisitors?.has(index)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (typeof visitor2.testNode === "string") {
|
|
92
|
+
return visitor2.testNode === mdastNode.type;
|
|
93
|
+
}
|
|
94
|
+
return visitor2.testNode(mdastNode, descriptors);
|
|
95
|
+
});
|
|
96
|
+
if (!visitor) {
|
|
97
|
+
try {
|
|
98
|
+
throw new UnrecognizedMarkdownConstructError(`Unsupported markdown syntax: ${toMarkdown(mdastNode)}`);
|
|
99
|
+
} catch (_e) {
|
|
100
|
+
throw new UnrecognizedMarkdownConstructError(
|
|
101
|
+
`Parsing of the following markdown structure failed: ${JSON.stringify({
|
|
102
|
+
type: mdastNode.type,
|
|
103
|
+
name: "name" in mdastNode ? mdastNode.name : "N/A"
|
|
104
|
+
})}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
visitor.visitNode({
|
|
109
|
+
//@ts-expect-error root type is glitching
|
|
110
|
+
mdastNode,
|
|
111
|
+
lexicalParent,
|
|
112
|
+
mdastParent,
|
|
113
|
+
descriptors,
|
|
114
|
+
metaData,
|
|
115
|
+
actions: {
|
|
116
|
+
visitChildren,
|
|
117
|
+
nextVisitor() {
|
|
118
|
+
visit(mdastNode, lexicalParent, mdastParent, (skipVisitors ?? /* @__PURE__ */ new Set()).add(visitors.indexOf(visitor)));
|
|
119
|
+
},
|
|
120
|
+
addAndStepInto(lexicalNode) {
|
|
121
|
+
lexicalParent.append(lexicalNode);
|
|
122
|
+
if (isParent(mdastNode)) {
|
|
123
|
+
visitChildren(mdastNode, lexicalNode);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
addFormatting(format, node) {
|
|
127
|
+
if (!node) {
|
|
128
|
+
if (isParent(mdastNode)) {
|
|
129
|
+
node = mdastNode;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (node) {
|
|
133
|
+
formattingMap.set(node, format | (formattingMap.get(mdastParent) ?? 0));
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
removeFormatting(format, node) {
|
|
137
|
+
if (!node) {
|
|
138
|
+
if (isParent(mdastNode)) {
|
|
139
|
+
node = mdastNode;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (node) {
|
|
143
|
+
formattingMap.set(node, format ^ (formattingMap.get(mdastParent) ?? 0));
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
getParentFormatting() {
|
|
147
|
+
return formattingMap.get(mdastParent) ?? 0;
|
|
148
|
+
},
|
|
149
|
+
addStyle(style, node) {
|
|
150
|
+
if (!node) {
|
|
151
|
+
if (isParent(mdastNode)) {
|
|
152
|
+
node = mdastNode;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (node) {
|
|
156
|
+
styleMap.set(node, style);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
getParentStyle() {
|
|
160
|
+
return styleMap.get(mdastParent) ?? "";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
visit(mdastRoot, root, null);
|
|
166
|
+
}
|
|
167
|
+
export {
|
|
168
|
+
MarkdownParseError,
|
|
169
|
+
UnrecognizedMarkdownConstructError,
|
|
170
|
+
importMarkdownToLexical,
|
|
171
|
+
importMdastTreeToLexical
|
|
172
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { FocusEvent as FocusEvent_2 } from 'react';
|
|
2
|
+
import { ForwardRefExoticComponent } from 'react';
|
|
3
|
+
import { KeyboardEvent as KeyboardEvent_2 } from 'react';
|
|
4
|
+
import { LexicalEditor } from 'lexical';
|
|
5
|
+
import { Options } from 'mdast-util-to-markdown';
|
|
6
|
+
import { ReactNode } from 'react';
|
|
7
|
+
import { Realm } from '@mdxeditor/gurx';
|
|
8
|
+
import { RefAttributes } from 'react';
|
|
9
|
+
|
|
10
|
+
export declare const horizontalRuleOnEnterPlugin: (params?: unknown) => RealmPlugin;
|
|
11
|
+
|
|
12
|
+
export declare const MarkdownEditor: ForwardRefExoticComponent<MarkdownEditorProps & RefAttributes<MarkdownEditorHandle>>;
|
|
13
|
+
|
|
14
|
+
export declare type MarkdownEditorColorScheme = 'dark' | 'inherit' | 'light' | 'system';
|
|
15
|
+
|
|
16
|
+
export declare type MarkdownEditorDensity = 'compact' | 'document';
|
|
17
|
+
|
|
18
|
+
export declare type MarkdownEditorHandle = {
|
|
19
|
+
focus: (options?: {
|
|
20
|
+
defaultSelection?: 'rootStart' | 'rootEnd';
|
|
21
|
+
preventScroll?: boolean;
|
|
22
|
+
}) => void;
|
|
23
|
+
getMarkdown: () => string;
|
|
24
|
+
getSelectionMarkdown: () => string;
|
|
25
|
+
insertMarkdown: (markdown: string) => void;
|
|
26
|
+
setMarkdown: (markdown: string) => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export declare type MarkdownEditorProps = {
|
|
30
|
+
additionalPlugins?: RealmPlugin[];
|
|
31
|
+
ariaLabel?: string;
|
|
32
|
+
autoFocus?: boolean | {
|
|
33
|
+
defaultSelection?: 'rootStart' | 'rootEnd';
|
|
34
|
+
preventScroll?: boolean;
|
|
35
|
+
};
|
|
36
|
+
className?: string;
|
|
37
|
+
colorScheme?: MarkdownEditorColorScheme;
|
|
38
|
+
contentClassName?: string;
|
|
39
|
+
defaultValue?: string;
|
|
40
|
+
density?: MarkdownEditorDensity;
|
|
41
|
+
minHeight?: number | string;
|
|
42
|
+
onBlur?: (event: FocusEvent) => void;
|
|
43
|
+
onChange?: (markdown: string) => void;
|
|
44
|
+
onError?: (error: Error) => void;
|
|
45
|
+
onFocus?: (event: FocusEvent_2<HTMLDivElement>) => void;
|
|
46
|
+
onHeightChange?: (height: number) => void;
|
|
47
|
+
onKeyDown?: (event: KeyboardEvent_2<HTMLDivElement>) => void;
|
|
48
|
+
onNavigate?: (target: string) => void;
|
|
49
|
+
onOpenExternalLink?: (href: string) => void;
|
|
50
|
+
overlayContainer?: HTMLElement | null;
|
|
51
|
+
placeholder?: ReactNode;
|
|
52
|
+
readOnly?: boolean;
|
|
53
|
+
resolveLink?: (href: string) => string | null;
|
|
54
|
+
spellCheck?: boolean;
|
|
55
|
+
toMarkdownOptions?: ToMarkdownOptions;
|
|
56
|
+
value?: string;
|
|
57
|
+
variant?: MarkdownEditorVariant;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export declare type MarkdownEditorVariant = 'card' | 'embedded' | 'plain';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A plugin for the editor.
|
|
64
|
+
* @group Core
|
|
65
|
+
*/
|
|
66
|
+
declare interface RealmPlugin {
|
|
67
|
+
init?: (realm: Realm) => void;
|
|
68
|
+
update?: (realm: Realm) => void;
|
|
69
|
+
postInit?: (realm: Realm) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export declare const registerHorizontalRuleOnEnter: (editor: LexicalEditor) => () => void;
|
|
73
|
+
|
|
74
|
+
declare interface ToMarkdownOptions extends Options {
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { }
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
declare module 'micromark-util-types' {
|
|
81
|
+
interface TokenTypeMap {
|
|
82
|
+
comment: 'comment';
|
|
83
|
+
commentEnd: 'commentEnd';
|
|
84
|
+
data: 'data';
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/* empty css */
|
|
2
|
+
import { MarkdownEditor } from "./MarkdownEditor.js";
|
|
3
|
+
import { horizontalRuleOnEnterPlugin, registerHorizontalRuleOnEnter } from "./horizontalRuleShortcut.js";
|
|
4
|
+
export {
|
|
5
|
+
MarkdownEditor,
|
|
6
|
+
horizontalRuleOnEnterPlugin,
|
|
7
|
+
registerHorizontalRuleOnEnter
|
|
8
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { useMdastNodeUpdater, NestedLexicalEditor } from "../plugins/core/NestedLexicalEditor.js";
|
|
4
|
+
import { PropertyPopover } from "../plugins/core/PropertyPopover.js";
|
|
5
|
+
import styles from "../styles/ui.module.css.js";
|
|
6
|
+
const isExpressionValue = (value) => {
|
|
7
|
+
if (value !== null && typeof value === "object" && "type" in value && "value" in value && typeof value.value === "string") {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
return false;
|
|
11
|
+
};
|
|
12
|
+
const isStringValue = (value) => typeof value === "string";
|
|
13
|
+
const isMdxJsxAttribute = (value) => {
|
|
14
|
+
if (value.type === "mdxJsxAttribute" && typeof value.name === "string") {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
19
|
+
const GenericJsxEditor = ({ mdastNode, descriptor, PropertyEditor }) => {
|
|
20
|
+
const updateMdastNode = useMdastNodeUpdater();
|
|
21
|
+
const properties = React.useMemo(
|
|
22
|
+
() => descriptor.props.reduce((acc, { name }) => {
|
|
23
|
+
const attribute = mdastNode.attributes.find((attr) => isMdxJsxAttribute(attr) ? attr.name === name : false);
|
|
24
|
+
if (attribute) {
|
|
25
|
+
if (isExpressionValue(attribute.value)) {
|
|
26
|
+
acc[name] = attribute.value.value;
|
|
27
|
+
return acc;
|
|
28
|
+
}
|
|
29
|
+
if (isStringValue(attribute.value)) {
|
|
30
|
+
acc[name] = attribute.value;
|
|
31
|
+
return acc;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
acc[name] = "";
|
|
35
|
+
return acc;
|
|
36
|
+
}, {}),
|
|
37
|
+
[mdastNode, descriptor]
|
|
38
|
+
);
|
|
39
|
+
const onChange = React.useCallback(
|
|
40
|
+
(values) => {
|
|
41
|
+
const updatedAttributes = Object.entries(values).reduce((acc, [name, value]) => {
|
|
42
|
+
if (value === "") {
|
|
43
|
+
return acc;
|
|
44
|
+
}
|
|
45
|
+
const property = descriptor.props.find((prop) => prop.name === name);
|
|
46
|
+
if (property?.type === "expression") {
|
|
47
|
+
acc.push({
|
|
48
|
+
type: "mdxJsxAttribute",
|
|
49
|
+
name,
|
|
50
|
+
value: { type: "mdxJsxAttributeValueExpression", value }
|
|
51
|
+
});
|
|
52
|
+
return acc;
|
|
53
|
+
}
|
|
54
|
+
acc.push({
|
|
55
|
+
type: "mdxJsxAttribute",
|
|
56
|
+
name,
|
|
57
|
+
value
|
|
58
|
+
});
|
|
59
|
+
return acc;
|
|
60
|
+
}, []);
|
|
61
|
+
updateMdastNode({ attributes: updatedAttributes });
|
|
62
|
+
},
|
|
63
|
+
[mdastNode, updateMdastNode, descriptor]
|
|
64
|
+
);
|
|
65
|
+
const PropertyEditorComponent = PropertyEditor ?? PropertyPopover;
|
|
66
|
+
const shouldRenderComponentName = descriptor.props.length == 0 && descriptor.hasChildren && descriptor.kind === "flow";
|
|
67
|
+
return /* @__PURE__ */ jsxs("div", { className: descriptor.kind === "text" ? styles.inlineEditor : styles.blockEditor, children: [
|
|
68
|
+
shouldRenderComponentName ? /* @__PURE__ */ jsx("span", { className: styles.genericComponentName, children: mdastNode.name ?? "Fragment" }) : null,
|
|
69
|
+
descriptor.props.length > 0 ? /* @__PURE__ */ jsx(PropertyEditorComponent, { properties, title: mdastNode.name ?? "", onChange }) : null,
|
|
70
|
+
descriptor.hasChildren ? /* @__PURE__ */ jsx(
|
|
71
|
+
NestedLexicalEditor,
|
|
72
|
+
{
|
|
73
|
+
block: descriptor.kind === "flow",
|
|
74
|
+
getContent: (node) => node.children,
|
|
75
|
+
getUpdatedMdastNode: (mdastNode2, children) => {
|
|
76
|
+
return { ...mdastNode2, children };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
) : /* @__PURE__ */ jsx("span", { className: styles.genericComponentName, children: mdastNode.name })
|
|
80
|
+
] });
|
|
81
|
+
};
|
|
82
|
+
export {
|
|
83
|
+
GenericJsxEditor
|
|
84
|
+
};
|