@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,130 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { DecoratorNode, $applyNodeReplacement } from "lexical";
|
|
3
|
+
import lexicalThemeStyles from "../../styles/lexical-theme.module.css.js";
|
|
4
|
+
import styles from "../../styles/ui.module.css.js";
|
|
5
|
+
class LexicalMdxExpressionNode extends DecoratorNode {
|
|
6
|
+
/** @internal */
|
|
7
|
+
__value;
|
|
8
|
+
/** @internal */
|
|
9
|
+
__mdastType;
|
|
10
|
+
/** @internal */
|
|
11
|
+
static getType() {
|
|
12
|
+
return "mdx-expression";
|
|
13
|
+
}
|
|
14
|
+
/** @internal */
|
|
15
|
+
static clone(node) {
|
|
16
|
+
return new LexicalMdxExpressionNode(node.__value, node.__mdastType, node.__key);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Constructs a new {@link GenericHTMLNode} with the specified MDAST HTML node as the object to edit.
|
|
20
|
+
*/
|
|
21
|
+
constructor(value, mdastType, key) {
|
|
22
|
+
super(key);
|
|
23
|
+
this.__value = value;
|
|
24
|
+
this.__mdastType = mdastType;
|
|
25
|
+
}
|
|
26
|
+
getValue() {
|
|
27
|
+
return this.__value;
|
|
28
|
+
}
|
|
29
|
+
getMdastType() {
|
|
30
|
+
return this.__mdastType;
|
|
31
|
+
}
|
|
32
|
+
// View
|
|
33
|
+
createDOM() {
|
|
34
|
+
const element = document.createElement("span");
|
|
35
|
+
element.classList.add(lexicalThemeStyles.mdxExpression);
|
|
36
|
+
return element;
|
|
37
|
+
}
|
|
38
|
+
updateDOM() {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
static importDOM() {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
exportDOM(editor) {
|
|
45
|
+
const { element } = super.exportDOM(editor);
|
|
46
|
+
return {
|
|
47
|
+
element
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
static importJSON(serializedNode) {
|
|
51
|
+
return $createLexicalMdxExpressionNode(serializedNode.value, serializedNode.mdastType);
|
|
52
|
+
}
|
|
53
|
+
exportJSON() {
|
|
54
|
+
return {
|
|
55
|
+
...super.exportJSON(),
|
|
56
|
+
value: this.getValue(),
|
|
57
|
+
mdastType: this.getMdastType(),
|
|
58
|
+
type: "mdx-expression",
|
|
59
|
+
version: 1
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/*
|
|
63
|
+
// Mutation
|
|
64
|
+
insertNewAfter(selection?: RangeSelection, restoreSelection = true): ParagraphNode | GenericHTMLNode {
|
|
65
|
+
const anchorOffset = selection ? selection.anchor.offset : 0
|
|
66
|
+
const newElement =
|
|
67
|
+
anchorOffset > 0 && anchorOffset < this.getTextContentSize() ? $createHeadingNode(this.getTag()) : $createParagraphNode()
|
|
68
|
+
const direction = this.getDirection()
|
|
69
|
+
newElement.setDirection(direction)
|
|
70
|
+
this.insertAfter(newElement, restoreSelection)
|
|
71
|
+
return newElement
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
collapseAtStart(): true {
|
|
75
|
+
const newElement = !this.isEmpty() ? $createHeadingNode(this.getTag()) : $createParagraphNode()
|
|
76
|
+
const children = this.getChildren()
|
|
77
|
+
children.forEach((child) => newElement.append(child))
|
|
78
|
+
this.replace(newElement)
|
|
79
|
+
return true
|
|
80
|
+
}*/
|
|
81
|
+
extractWithChild() {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
isInline() {
|
|
85
|
+
return this.__mdastType === "mdxTextExpression";
|
|
86
|
+
}
|
|
87
|
+
decorate(editor) {
|
|
88
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
89
|
+
"{",
|
|
90
|
+
/* @__PURE__ */ jsx("span", { className: styles.inputSizer, "data-value": this.getValue(), children: /* @__PURE__ */ jsx(
|
|
91
|
+
"input",
|
|
92
|
+
{
|
|
93
|
+
size: 1,
|
|
94
|
+
onKeyDown: (e) => {
|
|
95
|
+
const value = e.target.value;
|
|
96
|
+
if (value === "" && e.key === "Backspace" || e.key === "Delete") {
|
|
97
|
+
e.stopPropagation();
|
|
98
|
+
e.nativeEvent.stopImmediatePropagation();
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
editor.update(() => {
|
|
101
|
+
this.selectPrevious();
|
|
102
|
+
this.remove();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
onChange: (e) => {
|
|
107
|
+
e.target.parentElement.dataset.value = e.target.value;
|
|
108
|
+
editor.update(() => {
|
|
109
|
+
this.getWritable().__value = e.target.value;
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
type: "text",
|
|
113
|
+
value: this.getValue()
|
|
114
|
+
}
|
|
115
|
+
) }),
|
|
116
|
+
"}"
|
|
117
|
+
] });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function $createLexicalMdxExpressionNode(value, type) {
|
|
121
|
+
return $applyNodeReplacement(new LexicalMdxExpressionNode(value, type));
|
|
122
|
+
}
|
|
123
|
+
function $isLexicalMdxExpressionNode(node) {
|
|
124
|
+
return node instanceof LexicalMdxExpressionNode;
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
$createLexicalMdxExpressionNode,
|
|
128
|
+
$isLexicalMdxExpressionNode,
|
|
129
|
+
LexicalMdxExpressionNode
|
|
130
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { $isLexicalMdxExpressionNode } from "./LexicalMdxExpressionNode.js";
|
|
2
|
+
const LexicalMdxExpressionVisitor = {
|
|
3
|
+
testLexicalNode: $isLexicalMdxExpressionNode,
|
|
4
|
+
visitLexicalNode({ actions, mdastParent, lexicalNode }) {
|
|
5
|
+
const mdastNode = {
|
|
6
|
+
type: lexicalNode.getMdastType(),
|
|
7
|
+
value: lexicalNode.getValue()
|
|
8
|
+
};
|
|
9
|
+
actions.appendToParent(mdastParent, mdastNode);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export {
|
|
13
|
+
LexicalMdxExpressionVisitor
|
|
14
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { $createLexicalMdxExpressionNode } from "./LexicalMdxExpressionNode.js";
|
|
2
|
+
const MdastMdxExpressionVisitor = {
|
|
3
|
+
testNode: (node) => node.type === "mdxTextExpression" || node.type === "mdxFlowExpression",
|
|
4
|
+
visitNode({ lexicalParent, mdastNode }) {
|
|
5
|
+
lexicalParent.append($createLexicalMdxExpressionNode(mdastNode.value, mdastNode.type));
|
|
6
|
+
},
|
|
7
|
+
priority: -200
|
|
8
|
+
};
|
|
9
|
+
export {
|
|
10
|
+
MdastMdxExpressionVisitor
|
|
11
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { $createParagraphNode } from "lexical";
|
|
2
|
+
import { $createLexicalJsxNode } from "./LexicalJsxNode.js";
|
|
3
|
+
const MdastMdxJsxElementVisitor = {
|
|
4
|
+
testNode: (node, { jsxComponentDescriptors }) => {
|
|
5
|
+
if (node.type === "mdxJsxTextElement" || node.type === "mdxJsxFlowElement") {
|
|
6
|
+
const descriptor = jsxComponentDescriptors.find((descriptor2) => descriptor2.name === node.name) ?? jsxComponentDescriptors.find((descriptor2) => descriptor2.name === "*");
|
|
7
|
+
return descriptor !== void 0;
|
|
8
|
+
}
|
|
9
|
+
return false;
|
|
10
|
+
},
|
|
11
|
+
visitNode({ lexicalParent, mdastNode, descriptors: { jsxComponentDescriptors }, metaData }) {
|
|
12
|
+
const descriptor = jsxComponentDescriptors.find((descriptor2) => descriptor2.name === mdastNode.name) ?? jsxComponentDescriptors.find((descriptor2) => descriptor2.name === "*");
|
|
13
|
+
if (descriptor?.kind === "text" && mdastNode.type === "mdxJsxFlowElement") {
|
|
14
|
+
const patchedNode = { ...mdastNode, type: "mdxJsxTextElement" };
|
|
15
|
+
const paragraph = $createParagraphNode();
|
|
16
|
+
paragraph.append($createLexicalJsxNode(patchedNode, mdastNode.name ? metaData.importDeclarations[mdastNode.name] : void 0));
|
|
17
|
+
lexicalParent.append(paragraph);
|
|
18
|
+
} else {
|
|
19
|
+
lexicalParent.append(
|
|
20
|
+
$createLexicalJsxNode(mdastNode, mdastNode.name ? metaData.importDeclarations[mdastNode.name] : void 0)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
priority: -200
|
|
25
|
+
};
|
|
26
|
+
export {
|
|
27
|
+
MdastMdxJsxElementVisitor
|
|
28
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { mdxToMarkdown, mdxFromMarkdown } from "mdast-util-mdx";
|
|
2
|
+
import { mdxjs } from "micromark-extension-mdxjs";
|
|
3
|
+
import { insertDecoratorNode$, jsxComponentDescriptors$, addToMarkdownExtension$, addExportVisitor$, addLexicalNode$, addImportVisitor$, addSyntaxExtension$, addMdastExtension$, jsxIsAvailable$ } from "../core/index.js";
|
|
4
|
+
import { $createLexicalJsxNode, LexicalJsxNode } from "./LexicalJsxNode.js";
|
|
5
|
+
import { LexicalJsxVisitor } from "./LexicalJsxVisitor.js";
|
|
6
|
+
import { MdastMdxJsEsmVisitor } from "./MdastMdxJsEsmVisitor.js";
|
|
7
|
+
import { MdastMdxJsxElementVisitor } from "./MdastMdxJsxElementVisitor.js";
|
|
8
|
+
import { Signal, map } from "@mdxeditor/gurx";
|
|
9
|
+
import { realmPlugin } from "../../RealmWithPlugins.js";
|
|
10
|
+
import { MdastMdxExpressionVisitor } from "./MdastMdxExpressionVisitor.js";
|
|
11
|
+
import { LexicalMdxExpressionNode } from "./LexicalMdxExpressionNode.js";
|
|
12
|
+
import { LexicalMdxExpressionVisitor } from "./LexicalMdxExpressionVisitor.js";
|
|
13
|
+
import { GenericJsxEditor } from "../../jsx-editors/GenericJsxEditor.js";
|
|
14
|
+
function isMdastJsxNode(node) {
|
|
15
|
+
return node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement";
|
|
16
|
+
}
|
|
17
|
+
const isExpressionValue = (value) => {
|
|
18
|
+
if (value !== null && typeof value === "object" && "type" in value && "value" in value && typeof value.value === "string") {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
const toMdastJsxAttributes = (attributes) => Object.entries(attributes).map(
|
|
24
|
+
([name, value]) => ({
|
|
25
|
+
type: "mdxJsxAttribute",
|
|
26
|
+
name,
|
|
27
|
+
value: isExpressionValue(value) ? { type: "mdxJsxAttributeValueExpression", value: value.value } : value
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
const insertJsx$ = Signal((r) => {
|
|
31
|
+
r.link(
|
|
32
|
+
r.pipe(
|
|
33
|
+
insertJsx$,
|
|
34
|
+
map(({ kind, name, children, props }) => {
|
|
35
|
+
return () => {
|
|
36
|
+
const attributes = toMdastJsxAttributes(props);
|
|
37
|
+
if (kind === "flow") {
|
|
38
|
+
return $createLexicalJsxNode({
|
|
39
|
+
type: "mdxJsxFlowElement",
|
|
40
|
+
name,
|
|
41
|
+
children: children ?? [],
|
|
42
|
+
attributes
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
return $createLexicalJsxNode({
|
|
46
|
+
type: "mdxJsxTextElement",
|
|
47
|
+
name,
|
|
48
|
+
children: children ?? [],
|
|
49
|
+
attributes
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
})
|
|
54
|
+
),
|
|
55
|
+
insertDecoratorNode$
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
const fragmentDescriptor = {
|
|
59
|
+
name: null,
|
|
60
|
+
kind: "flow",
|
|
61
|
+
props: [],
|
|
62
|
+
hasChildren: true,
|
|
63
|
+
Editor: GenericJsxEditor
|
|
64
|
+
};
|
|
65
|
+
const getDescriptors = (params) => {
|
|
66
|
+
if (params) {
|
|
67
|
+
if (params.allowFragment ?? true) {
|
|
68
|
+
return [fragmentDescriptor, ...params.jsxComponentDescriptors];
|
|
69
|
+
}
|
|
70
|
+
return params.jsxComponentDescriptors;
|
|
71
|
+
}
|
|
72
|
+
return [fragmentDescriptor];
|
|
73
|
+
};
|
|
74
|
+
const jsxPlugin = realmPlugin({
|
|
75
|
+
init: (realm, params) => {
|
|
76
|
+
realm.pubIn({
|
|
77
|
+
// import
|
|
78
|
+
[jsxIsAvailable$]: true,
|
|
79
|
+
[addMdastExtension$]: mdxFromMarkdown(),
|
|
80
|
+
[addSyntaxExtension$]: mdxjs(),
|
|
81
|
+
[addImportVisitor$]: [MdastMdxJsxElementVisitor, MdastMdxJsEsmVisitor, MdastMdxExpressionVisitor],
|
|
82
|
+
// export
|
|
83
|
+
[addLexicalNode$]: [LexicalJsxNode, LexicalMdxExpressionNode],
|
|
84
|
+
[addExportVisitor$]: [LexicalJsxVisitor, LexicalMdxExpressionVisitor],
|
|
85
|
+
[addToMarkdownExtension$]: mdxToMarkdown(),
|
|
86
|
+
[jsxComponentDescriptors$]: getDescriptors(params)
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
update(realm, params) {
|
|
90
|
+
realm.pub(jsxComponentDescriptors$, getDescriptors(params));
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
export {
|
|
94
|
+
insertJsx$,
|
|
95
|
+
isMdastJsxNode,
|
|
96
|
+
jsxPlugin
|
|
97
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { AutoLinkPlugin, createLinkMatcherWithRegExp } from "@lexical/react/LexicalAutoLinkPlugin";
|
|
3
|
+
const URL_REGEX = /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
|
|
4
|
+
const EMAIL_REGEX = /(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;
|
|
5
|
+
const MATCHERS = [
|
|
6
|
+
createLinkMatcherWithRegExp(URL_REGEX, (text) => {
|
|
7
|
+
return text.startsWith("http") ? text : `https://${text}`;
|
|
8
|
+
}),
|
|
9
|
+
createLinkMatcherWithRegExp(EMAIL_REGEX, (text) => {
|
|
10
|
+
return `mailto:${text}`;
|
|
11
|
+
})
|
|
12
|
+
];
|
|
13
|
+
const LexicalAutoLinkPlugin = () => {
|
|
14
|
+
return /* @__PURE__ */ jsx(AutoLinkPlugin, { matchers: MATCHERS });
|
|
15
|
+
};
|
|
16
|
+
export {
|
|
17
|
+
LexicalAutoLinkPlugin
|
|
18
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { $isLinkNode } from "@lexical/link";
|
|
2
|
+
const LexicalLinkVisitor = {
|
|
3
|
+
testLexicalNode: $isLinkNode,
|
|
4
|
+
visitLexicalNode: ({ lexicalNode, actions }) => {
|
|
5
|
+
actions.addAndStepInto("link", { url: lexicalNode.getURL(), title: lexicalNode.getTitle() });
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
export {
|
|
9
|
+
LexicalLinkVisitor
|
|
10
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { $createLinkNode } from "@lexical/link";
|
|
2
|
+
const MdastLinkVisitor = {
|
|
3
|
+
testNode: "link",
|
|
4
|
+
visitNode({ mdastNode, actions }) {
|
|
5
|
+
actions.addAndStepInto(
|
|
6
|
+
$createLinkNode(mdastNode.url, {
|
|
7
|
+
title: mdastNode.title
|
|
8
|
+
})
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export {
|
|
13
|
+
MdastLinkVisitor
|
|
14
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { MdastLinkVisitor } from "./MdastLinkVisitor.js";
|
|
3
|
+
import { LexicalLinkVisitor } from "./LexicalLinkVisitor.js";
|
|
4
|
+
import { LinkNode, AutoLinkNode } from "@lexical/link";
|
|
5
|
+
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
|
6
|
+
import { LexicalAutoLinkPlugin } from "./AutoLinkPlugin.js";
|
|
7
|
+
import { Cell } from "@mdxeditor/gurx";
|
|
8
|
+
import { realmPlugin } from "../../RealmWithPlugins.js";
|
|
9
|
+
import { addComposerChild$, addTableCellEditorChild$, addNestedEditorChild$, addExportVisitor$, addLexicalNode$, addImportVisitor$, addActivePlugin$ } from "../core/index.js";
|
|
10
|
+
const disableAutoLink$ = Cell(false);
|
|
11
|
+
const linkPlugin = realmPlugin({
|
|
12
|
+
init(realm, params) {
|
|
13
|
+
const disableAutoLink = Boolean(params?.disableAutoLink);
|
|
14
|
+
const linkPluginProps = params?.validateUrl ? { validateUrl: params.validateUrl } : {};
|
|
15
|
+
const EditorChild = () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
16
|
+
/* @__PURE__ */ jsx(LinkPlugin, { ...linkPluginProps }),
|
|
17
|
+
disableAutoLink ? null : /* @__PURE__ */ jsx(LexicalAutoLinkPlugin, {})
|
|
18
|
+
] });
|
|
19
|
+
realm.pubIn({
|
|
20
|
+
[addActivePlugin$]: "link",
|
|
21
|
+
[addImportVisitor$]: MdastLinkVisitor,
|
|
22
|
+
[addLexicalNode$]: [LinkNode, AutoLinkNode],
|
|
23
|
+
[addExportVisitor$]: LexicalLinkVisitor,
|
|
24
|
+
[disableAutoLink$]: disableAutoLink,
|
|
25
|
+
[addNestedEditorChild$]: EditorChild,
|
|
26
|
+
[addTableCellEditorChild$]: EditorChild,
|
|
27
|
+
[addComposerChild$]: EditorChild
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
export {
|
|
32
|
+
disableAutoLink$,
|
|
33
|
+
linkPlugin
|
|
34
|
+
};
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import * as RadixPopover from "@radix-ui/react-popover";
|
|
3
|
+
import * as Tooltip from "@radix-ui/react-tooltip";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { EditorIcon } from "../../EditorIcon.js";
|
|
6
|
+
import { editorRootElementRef$, activeEditor$, useTranslation } from "../core/index.js";
|
|
7
|
+
import { DownshiftAutoComplete } from "../core/ui/DownshiftAutoComplete.js";
|
|
8
|
+
import styles from "../../styles/ui.module.css.js";
|
|
9
|
+
import classNames from "classnames";
|
|
10
|
+
import { createCommand } from "lexical";
|
|
11
|
+
import { useForm } from "react-hook-form";
|
|
12
|
+
import { linkDialogState$, linkAutocompleteSuggestions$, onClickLinkCallback$, showLinkTitleField$, onWindowChange$, updateLink$, cancelLinkEdit$, switchFromPreviewToLinkEdit$, removeLink$ } from "./index.js";
|
|
13
|
+
import { useCellValues, usePublisher } from "@mdxeditor/gurx";
|
|
14
|
+
createCommand();
|
|
15
|
+
function LinkEditForm({
|
|
16
|
+
url,
|
|
17
|
+
title,
|
|
18
|
+
text,
|
|
19
|
+
onSubmit,
|
|
20
|
+
onCancel,
|
|
21
|
+
linkAutocompleteSuggestions,
|
|
22
|
+
showLinkTitleField,
|
|
23
|
+
showAnchorTextField
|
|
24
|
+
}) {
|
|
25
|
+
const {
|
|
26
|
+
register,
|
|
27
|
+
handleSubmit,
|
|
28
|
+
control,
|
|
29
|
+
setValue,
|
|
30
|
+
reset: _
|
|
31
|
+
} = useForm({
|
|
32
|
+
values: {
|
|
33
|
+
url,
|
|
34
|
+
title,
|
|
35
|
+
text
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const t = useTranslation();
|
|
39
|
+
return /* @__PURE__ */ jsxs(
|
|
40
|
+
"form",
|
|
41
|
+
{
|
|
42
|
+
onSubmit: (e) => {
|
|
43
|
+
void handleSubmit(onSubmit)(e);
|
|
44
|
+
e.stopPropagation();
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
},
|
|
47
|
+
onReset: (e) => {
|
|
48
|
+
e.stopPropagation();
|
|
49
|
+
onCancel();
|
|
50
|
+
},
|
|
51
|
+
onKeyDown: (e) => {
|
|
52
|
+
if (e.key === "Escape") {
|
|
53
|
+
e.stopPropagation();
|
|
54
|
+
onCancel();
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
className: classNames(styles.multiFieldForm, styles.linkDialogEditForm),
|
|
58
|
+
children: [
|
|
59
|
+
/* @__PURE__ */ jsxs("div", { className: styles.formField, children: [
|
|
60
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "link-url", children: t("createLink.url", "URL") }),
|
|
61
|
+
/* @__PURE__ */ jsx(
|
|
62
|
+
DownshiftAutoComplete,
|
|
63
|
+
{
|
|
64
|
+
register,
|
|
65
|
+
initialInputValue: url,
|
|
66
|
+
inputName: "url",
|
|
67
|
+
suggestions: linkAutocompleteSuggestions,
|
|
68
|
+
setValue,
|
|
69
|
+
control,
|
|
70
|
+
placeholder: t("createLink.urlPlaceholder", "Select or paste an URL"),
|
|
71
|
+
autofocus: true
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
] }),
|
|
75
|
+
showAnchorTextField ? /* @__PURE__ */ jsxs("div", { className: styles.formField, children: [
|
|
76
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "link-text", title: t("createLink.textTooltip", "The text to be displayed for the link"), children: t("createLink.text", "Anchor text") }),
|
|
77
|
+
/* @__PURE__ */ jsx("input", { id: "link-text", className: styles.textInput, size: 40, ...register("text") })
|
|
78
|
+
] }) : null,
|
|
79
|
+
showLinkTitleField ? /* @__PURE__ */ jsxs("div", { className: styles.formField, children: [
|
|
80
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "link-title", title: t("createLink.titleTooltip", "The link's title attribute, shown on hover"), children: t("createLink.title", "Link title") }),
|
|
81
|
+
/* @__PURE__ */ jsx("input", { id: "link-title", className: styles.textInput, size: 40, ...register("title") })
|
|
82
|
+
] }) : null,
|
|
83
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: "var(--spacing-2)" }, children: [
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
"button",
|
|
86
|
+
{
|
|
87
|
+
type: "submit",
|
|
88
|
+
title: t("createLink.saveTooltip", "Set URL"),
|
|
89
|
+
"aria-label": t("createLink.saveTooltip", "Set URL"),
|
|
90
|
+
className: classNames(styles.primaryButton),
|
|
91
|
+
children: t("dialogControls.save", "Save")
|
|
92
|
+
}
|
|
93
|
+
),
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
"button",
|
|
96
|
+
{
|
|
97
|
+
type: "reset",
|
|
98
|
+
title: t("createLink.cancelTooltip", "Cancel change"),
|
|
99
|
+
"aria-label": t("createLink.cancelTooltip", "Cancel change"),
|
|
100
|
+
className: classNames(styles.secondaryButton),
|
|
101
|
+
children: t("dialogControls.cancel", "Cancel")
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] })
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
const LinkDialog = () => {
|
|
110
|
+
const [
|
|
111
|
+
editorRootElementRef,
|
|
112
|
+
activeEditor,
|
|
113
|
+
linkDialogState,
|
|
114
|
+
linkAutocompleteSuggestions,
|
|
115
|
+
onClickLinkCallback,
|
|
116
|
+
showLinkTitleField
|
|
117
|
+
] = useCellValues(
|
|
118
|
+
editorRootElementRef$,
|
|
119
|
+
activeEditor$,
|
|
120
|
+
linkDialogState$,
|
|
121
|
+
linkAutocompleteSuggestions$,
|
|
122
|
+
onClickLinkCallback$,
|
|
123
|
+
showLinkTitleField$
|
|
124
|
+
);
|
|
125
|
+
const publishWindowChange = usePublisher(onWindowChange$);
|
|
126
|
+
const updateLink = usePublisher(updateLink$);
|
|
127
|
+
const cancelLinkEdit = usePublisher(cancelLinkEdit$);
|
|
128
|
+
const switchFromPreviewToLinkEdit = usePublisher(switchFromPreviewToLinkEdit$);
|
|
129
|
+
const removeLink = usePublisher(removeLink$);
|
|
130
|
+
React.useEffect(() => {
|
|
131
|
+
const update = () => {
|
|
132
|
+
activeEditor?.getEditorState().read(() => {
|
|
133
|
+
publishWindowChange(true);
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
window.addEventListener("resize", update);
|
|
137
|
+
window.addEventListener("scroll", update);
|
|
138
|
+
return () => {
|
|
139
|
+
window.removeEventListener("resize", update);
|
|
140
|
+
window.removeEventListener("scroll", update);
|
|
141
|
+
};
|
|
142
|
+
}, [activeEditor, publishWindowChange]);
|
|
143
|
+
const [copyUrlTooltipOpen, setCopyUrlTooltipOpen] = React.useState(false);
|
|
144
|
+
const t = useTranslation();
|
|
145
|
+
if (linkDialogState.type === "inactive") return null;
|
|
146
|
+
const theRect = linkDialogState.rectangle;
|
|
147
|
+
const urlIsExternal = linkDialogState.type === "preview" && linkDialogState.url.startsWith("http");
|
|
148
|
+
return /* @__PURE__ */ jsxs(RadixPopover.Root, { open: true, children: [
|
|
149
|
+
/* @__PURE__ */ jsx(
|
|
150
|
+
RadixPopover.Anchor,
|
|
151
|
+
{
|
|
152
|
+
"data-visible": linkDialogState.type === "edit",
|
|
153
|
+
className: styles.linkDialogAnchor,
|
|
154
|
+
style: {
|
|
155
|
+
top: `${theRect.top}px`,
|
|
156
|
+
left: `${theRect.left}px`,
|
|
157
|
+
width: `${theRect.width}px`,
|
|
158
|
+
height: `${theRect.height}px`
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
),
|
|
162
|
+
/* @__PURE__ */ jsx(RadixPopover.Portal, { container: editorRootElementRef?.current, children: /* @__PURE__ */ jsxs(
|
|
163
|
+
RadixPopover.Content,
|
|
164
|
+
{
|
|
165
|
+
className: classNames(styles.linkDialogPopoverContent),
|
|
166
|
+
sideOffset: 5,
|
|
167
|
+
onOpenAutoFocus: (e) => {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
},
|
|
170
|
+
children: [
|
|
171
|
+
linkDialogState.type === "edit" && /* @__PURE__ */ jsx(
|
|
172
|
+
LinkEditForm,
|
|
173
|
+
{
|
|
174
|
+
url: linkDialogState.url,
|
|
175
|
+
title: linkDialogState.title,
|
|
176
|
+
text: linkDialogState.text,
|
|
177
|
+
onSubmit: updateLink,
|
|
178
|
+
onCancel: cancelLinkEdit.bind(null),
|
|
179
|
+
linkAutocompleteSuggestions,
|
|
180
|
+
showLinkTitleField,
|
|
181
|
+
showAnchorTextField: linkDialogState.withAnchorText
|
|
182
|
+
}
|
|
183
|
+
),
|
|
184
|
+
linkDialogState.type === "preview" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
185
|
+
/* @__PURE__ */ jsxs(
|
|
186
|
+
"a",
|
|
187
|
+
{
|
|
188
|
+
className: styles.linkDialogPreviewAnchor,
|
|
189
|
+
href: linkDialogState.url,
|
|
190
|
+
...urlIsExternal ? { target: "_blank", rel: "noreferrer" } : {},
|
|
191
|
+
onClick: (e) => {
|
|
192
|
+
if (onClickLinkCallback !== null) {
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
onClickLinkCallback(linkDialogState.url);
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
title: urlIsExternal ? t("linkPreview.open", `Open {{url}} in new window`, { url: linkDialogState.url }) : linkDialogState.url,
|
|
198
|
+
children: [
|
|
199
|
+
/* @__PURE__ */ jsx("span", { children: linkDialogState.url }),
|
|
200
|
+
urlIsExternal && /* @__PURE__ */ jsx(EditorIcon, { name: "external" })
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
/* @__PURE__ */ jsx(
|
|
205
|
+
ActionButton,
|
|
206
|
+
{
|
|
207
|
+
onClick: () => {
|
|
208
|
+
switchFromPreviewToLinkEdit();
|
|
209
|
+
},
|
|
210
|
+
title: t("linkPreview.edit", "Edit link URL"),
|
|
211
|
+
"aria-label": t("linkPreview.edit", "Edit link URL"),
|
|
212
|
+
children: /* @__PURE__ */ jsx(EditorIcon, { name: "edit" })
|
|
213
|
+
}
|
|
214
|
+
),
|
|
215
|
+
/* @__PURE__ */ jsx(Tooltip.Provider, { children: /* @__PURE__ */ jsxs(Tooltip.Root, { open: copyUrlTooltipOpen, children: [
|
|
216
|
+
/* @__PURE__ */ jsx(Tooltip.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
217
|
+
ActionButton,
|
|
218
|
+
{
|
|
219
|
+
title: t("linkPreview.copyToClipboard", "Copy to clipboard"),
|
|
220
|
+
"aria-label": t("linkPreview.copyToClipboard", "Copy to clipboard"),
|
|
221
|
+
onClick: () => {
|
|
222
|
+
void window.navigator.clipboard.writeText(linkDialogState.url).then(() => {
|
|
223
|
+
setCopyUrlTooltipOpen(true);
|
|
224
|
+
setTimeout(() => {
|
|
225
|
+
setCopyUrlTooltipOpen(false);
|
|
226
|
+
}, 1e3);
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
children: /* @__PURE__ */ jsx(EditorIcon, { name: copyUrlTooltipOpen ? "check" : "copy" })
|
|
230
|
+
}
|
|
231
|
+
) }),
|
|
232
|
+
/* @__PURE__ */ jsx(Tooltip.Portal, { container: editorRootElementRef?.current, children: /* @__PURE__ */ jsxs(Tooltip.Content, { className: classNames(styles.tooltipContent), sideOffset: 5, children: [
|
|
233
|
+
t("linkPreview.copied", "Copied!"),
|
|
234
|
+
/* @__PURE__ */ jsx(Tooltip.Arrow, {})
|
|
235
|
+
] }) })
|
|
236
|
+
] }) }),
|
|
237
|
+
/* @__PURE__ */ jsx(
|
|
238
|
+
ActionButton,
|
|
239
|
+
{
|
|
240
|
+
title: t("linkPreview.remove", "Remove link"),
|
|
241
|
+
"aria-label": t("linkPreview.remove", "Remove link"),
|
|
242
|
+
onClick: () => {
|
|
243
|
+
removeLink();
|
|
244
|
+
},
|
|
245
|
+
children: /* @__PURE__ */ jsx(EditorIcon, { name: "unlink" })
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
] }),
|
|
249
|
+
/* @__PURE__ */ jsx(RadixPopover.Arrow, { className: styles.popoverArrow })
|
|
250
|
+
]
|
|
251
|
+
},
|
|
252
|
+
linkDialogState.linkNodeKey
|
|
253
|
+
) })
|
|
254
|
+
] });
|
|
255
|
+
};
|
|
256
|
+
const ActionButton = React.forwardRef(({ className, ...props }, ref) => {
|
|
257
|
+
return /* @__PURE__ */ jsx("button", { className: classNames(styles.linkDialogActionButton, className), ref, ...props });
|
|
258
|
+
});
|
|
259
|
+
export {
|
|
260
|
+
LinkDialog,
|
|
261
|
+
LinkEditForm
|
|
262
|
+
};
|