@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.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/UPSTREAM.md +21 -0
  4. package/dist/EditorIcon.js +75 -0
  5. package/dist/FormatConstants.js +20 -0
  6. package/dist/MDXEditor.js +189 -0
  7. package/dist/MarkdownEditor.js +281 -0
  8. package/dist/PersistentMarkdownEditor.js +358 -0
  9. package/dist/RealmWithPlugins.js +35 -0
  10. package/dist/core.d.ts +3232 -0
  11. package/dist/core.js +354 -0
  12. package/dist/defaultSvgIcons.js +371 -0
  13. package/dist/directive-editors/AdmonitionDirectiveDescriptor.js +28 -0
  14. package/dist/directive-editors/GenericDirectiveEditor.js +37 -0
  15. package/dist/exportMarkdownFromLexical.js +262 -0
  16. package/dist/horizontalRuleShortcut.js +37 -0
  17. package/dist/importMarkdownToLexical.js +172 -0
  18. package/dist/index.d.ts +86 -0
  19. package/dist/index.js +8 -0
  20. package/dist/jsx-editors/GenericJsxEditor.js +84 -0
  21. package/dist/mdastUtilHtmlComment.js +125 -0
  22. package/dist/persistence.d.ts +128 -0
  23. package/dist/persistence.js +4 -0
  24. package/dist/plugins/codeblock/CodeBlockNode.js +183 -0
  25. package/dist/plugins/codeblock/CodeBlockVisitor.js +14 -0
  26. package/dist/plugins/codeblock/MdastCodeVisitor.js +23 -0
  27. package/dist/plugins/codeblock/findCodeBlockDescriptor.js +8 -0
  28. package/dist/plugins/codeblock/index.js +46 -0
  29. package/dist/plugins/codemirror/CodeMirrorEditor.js +145 -0
  30. package/dist/plugins/codemirror/index.js +115 -0
  31. package/dist/plugins/codemirror/useCodeMirrorRef.js +101 -0
  32. package/dist/plugins/core/GenericHTMLNode.js +118 -0
  33. package/dist/plugins/core/LexicalGenericHTMLNodeVisitor.js +15 -0
  34. package/dist/plugins/core/LexicalLinebreakVisitor.js +10 -0
  35. package/dist/plugins/core/LexicalParagraphVisitor.js +10 -0
  36. package/dist/plugins/core/LexicalRootVisitor.js +10 -0
  37. package/dist/plugins/core/LexicalTextVisitor.js +160 -0
  38. package/dist/plugins/core/MdastBreakVisitor.js +10 -0
  39. package/dist/plugins/core/MdastFormattingVisitor.js +81 -0
  40. package/dist/plugins/core/MdastHTMLNode.js +120 -0
  41. package/dist/plugins/core/MdastHTMLVisitor.js +17 -0
  42. package/dist/plugins/core/MdastParagraphVisitor.js +23 -0
  43. package/dist/plugins/core/MdastRootVisitor.js +9 -0
  44. package/dist/plugins/core/MdastTextVisitor.js +16 -0
  45. package/dist/plugins/core/NestedLexicalEditor.js +221 -0
  46. package/dist/plugins/core/PropertyPopover.js +75 -0
  47. package/dist/plugins/core/SharedHistoryPlugin.js +10 -0
  48. package/dist/plugins/core/index.js +692 -0
  49. package/dist/plugins/core/ui/DownshiftAutoComplete.js +89 -0
  50. package/dist/plugins/core/ui/PopoverUtils.js +22 -0
  51. package/dist/plugins/diff-source/DiffSourceWrapper.js +24 -0
  52. package/dist/plugins/diff-source/DiffViewer.js +84 -0
  53. package/dist/plugins/diff-source/SourceEditor.js +60 -0
  54. package/dist/plugins/diff-source/index.js +27 -0
  55. package/dist/plugins/directives/DirectiveNode.js +107 -0
  56. package/dist/plugins/directives/DirectiveVisitor.js +10 -0
  57. package/dist/plugins/directives/MdastDirectiveVisitor.js +30 -0
  58. package/dist/plugins/directives/index.js +45 -0
  59. package/dist/plugins/frontmatter/FrontmatterEditor.js +137 -0
  60. package/dist/plugins/frontmatter/FrontmatterNode.js +70 -0
  61. package/dist/plugins/frontmatter/LexicalFrontmatterVisitor.js +10 -0
  62. package/dist/plugins/frontmatter/MdastFrontmatterVisitor.js +10 -0
  63. package/dist/plugins/frontmatter/index.js +113 -0
  64. package/dist/plugins/headings/LexicalHeadingVisitor.js +11 -0
  65. package/dist/plugins/headings/MdastHeadingVisitor.js +10 -0
  66. package/dist/plugins/headings/index.js +63 -0
  67. package/dist/plugins/image/EditImageToolbar.js +58 -0
  68. package/dist/plugins/image/ImageDialog.js +132 -0
  69. package/dist/plugins/image/ImageEditor.js +279 -0
  70. package/dist/plugins/image/ImageNode.js +187 -0
  71. package/dist/plugins/image/ImagePlaceholder.js +9 -0
  72. package/dist/plugins/image/ImageResizer.js +223 -0
  73. package/dist/plugins/image/LexicalImageVisitor.js +42 -0
  74. package/dist/plugins/image/MdastImageVisitor.js +91 -0
  75. package/dist/plugins/image/index.js +364 -0
  76. package/dist/plugins/jsx/LexicalJsxNode.js +103 -0
  77. package/dist/plugins/jsx/LexicalJsxVisitor.js +27 -0
  78. package/dist/plugins/jsx/LexicalMdxExpressionNode.js +130 -0
  79. package/dist/plugins/jsx/LexicalMdxExpressionVisitor.js +14 -0
  80. package/dist/plugins/jsx/MdastMdxExpressionVisitor.js +11 -0
  81. package/dist/plugins/jsx/MdastMdxJsEsmVisitor.js +8 -0
  82. package/dist/plugins/jsx/MdastMdxJsxElementVisitor.js +28 -0
  83. package/dist/plugins/jsx/index.js +97 -0
  84. package/dist/plugins/jsx/jsxTagName.js +7 -0
  85. package/dist/plugins/link/AutoLinkPlugin.js +18 -0
  86. package/dist/plugins/link/LexicalLinkVisitor.js +10 -0
  87. package/dist/plugins/link/MdastLinkVisitor.js +14 -0
  88. package/dist/plugins/link/index.js +34 -0
  89. package/dist/plugins/link-dialog/LinkDialog.js +262 -0
  90. package/dist/plugins/link-dialog/index.js +304 -0
  91. package/dist/plugins/lists/CheckListPlugin.js +270 -0
  92. package/dist/plugins/lists/LexicalListItemVisitor.js +41 -0
  93. package/dist/plugins/lists/LexicalListVisitor.js +13 -0
  94. package/dist/plugins/lists/MdastListItemVisitor.js +11 -0
  95. package/dist/plugins/lists/MdastListVisitor.js +19 -0
  96. package/dist/plugins/lists/NotesListItemNode.js +22 -0
  97. package/dist/plugins/lists/index.js +111 -0
  98. package/dist/plugins/markdown-shortcut/index.js +114 -0
  99. package/dist/plugins/maxlength/index.js +36 -0
  100. package/dist/plugins/quote/LexicalQuoteVisitor.js +10 -0
  101. package/dist/plugins/quote/MdastBlockQuoteVisitor.js +10 -0
  102. package/dist/plugins/quote/index.js +18 -0
  103. package/dist/plugins/remote/index.js +52 -0
  104. package/dist/plugins/search/index.js +360 -0
  105. package/dist/plugins/table/LexicalTableVisitor.js +10 -0
  106. package/dist/plugins/table/MdastTableVisitor.js +10 -0
  107. package/dist/plugins/table/TableEditor.js +527 -0
  108. package/dist/plugins/table/TableNode.js +208 -0
  109. package/dist/plugins/table/index.js +66 -0
  110. package/dist/plugins/thematic-break/LexicalThematicBreakVisitor.js +10 -0
  111. package/dist/plugins/thematic-break/MdastThematicBreakVisitor.js +10 -0
  112. package/dist/plugins/thematic-break/index.js +27 -0
  113. package/dist/plugins/toolbar/components/BlockTypeSelect.js +62 -0
  114. package/dist/plugins/toolbar/components/BoldItalicUnderlineToggles.js +98 -0
  115. package/dist/plugins/toolbar/components/ChangeAdmonitionType.js +43 -0
  116. package/dist/plugins/toolbar/components/ChangeCodeMirrorLanguage.js +42 -0
  117. package/dist/plugins/toolbar/components/CodeToggle.js +21 -0
  118. package/dist/plugins/toolbar/components/CreateLink.js +24 -0
  119. package/dist/plugins/toolbar/components/DiffSourceToggleWrapper.js +42 -0
  120. package/dist/plugins/toolbar/components/HighlightToggle.js +28 -0
  121. package/dist/plugins/toolbar/components/InsertAdmonition.js +34 -0
  122. package/dist/plugins/toolbar/components/InsertCodeBlock.js +23 -0
  123. package/dist/plugins/toolbar/components/InsertFrontmatter.js +28 -0
  124. package/dist/plugins/toolbar/components/InsertImage.js +29 -0
  125. package/dist/plugins/toolbar/components/InsertTable.js +25 -0
  126. package/dist/plugins/toolbar/components/InsertThematicBreak.js +23 -0
  127. package/dist/plugins/toolbar/components/KitchenSinkToolbar.js +82 -0
  128. package/dist/plugins/toolbar/components/ListsToggle.js +29 -0
  129. package/dist/plugins/toolbar/components/UndoRedo.js +60 -0
  130. package/dist/plugins/toolbar/index.js +32 -0
  131. package/dist/plugins/toolbar/primitives/DialogButton.js +130 -0
  132. package/dist/plugins/toolbar/primitives/TooltipWrap.js +17 -0
  133. package/dist/plugins/toolbar/primitives/select.js +76 -0
  134. package/dist/plugins/toolbar/primitives/toolbar.js +144 -0
  135. package/dist/registerCodeBoundaryEscape.js +40 -0
  136. package/dist/styles/lexical-theme.module.css.js +62 -0
  137. package/dist/styles/lexicalTheme.js +32 -0
  138. package/dist/styles/ui.module.css.js +296 -0
  139. package/dist/styles.css +2838 -0
  140. package/dist/utils/detectMac.js +16 -0
  141. package/dist/utils/fp.js +44 -0
  142. package/dist/utils/isPartOftheEditorUI.js +12 -0
  143. package/dist/utils/lexicalHelpers.js +185 -0
  144. package/dist/utils/makeHslTransparent.js +6 -0
  145. package/dist/utils/mergeStyleAttributes.js +22 -0
  146. package/dist/utils/uuid4.js +10 -0
  147. package/dist/utils/voidEmitter.js +15 -0
  148. 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,8 @@
1
+ const MdastMdxJsEsmVisitor = {
2
+ testNode: "mdxjsEsm",
3
+ visitNode() {
4
+ }
5
+ };
6
+ export {
7
+ MdastMdxJsEsmVisitor
8
+ };
@@ -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,7 @@
1
+ import { htmlTags } from "../core/MdastHTMLNode.js";
2
+ function isHtmlTagName(name) {
3
+ return htmlTags.includes(name);
4
+ }
5
+ export {
6
+ isHtmlTagName
7
+ };
@@ -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
+ };