@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,89 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCombobox } from "downshift";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Controller } from "react-hook-form";
|
|
5
|
+
import { EditorIcon } from "../../../EditorIcon.js";
|
|
6
|
+
import styles from "../../../styles/ui.module.css.js";
|
|
7
|
+
const MAX_SUGGESTIONS = 20;
|
|
8
|
+
const DownshiftAutoComplete = (props) => {
|
|
9
|
+
if (props.suggestions.length > 0) {
|
|
10
|
+
return /* @__PURE__ */ jsx(DownshiftAutoCompleteWithSuggestions, { ...props });
|
|
11
|
+
} else {
|
|
12
|
+
return /* @__PURE__ */ jsx("input", { className: styles.textInput, size: 40, autoFocus: true, ...props.register(props.inputName) });
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const DownshiftAutoCompleteWithSuggestions = ({
|
|
16
|
+
autofocus,
|
|
17
|
+
suggestions,
|
|
18
|
+
control,
|
|
19
|
+
inputName,
|
|
20
|
+
placeholder,
|
|
21
|
+
initialInputValue,
|
|
22
|
+
setValue
|
|
23
|
+
}) => {
|
|
24
|
+
const [items, setItems] = React.useState(suggestions.slice(0, MAX_SUGGESTIONS));
|
|
25
|
+
const enableAutoComplete = suggestions.length > 0;
|
|
26
|
+
const { isOpen, getToggleButtonProps, getMenuProps, getInputProps, highlightedIndex, getItemProps, selectedItem } = useCombobox({
|
|
27
|
+
initialInputValue,
|
|
28
|
+
onInputValueChange({ inputValue = "" }) {
|
|
29
|
+
setValue(inputName, inputValue);
|
|
30
|
+
inputValue = inputValue.toLowerCase() || "";
|
|
31
|
+
const matchingItems = [];
|
|
32
|
+
for (const suggestion of suggestions) {
|
|
33
|
+
if (suggestion.toLowerCase().includes(inputValue)) {
|
|
34
|
+
matchingItems.push(suggestion);
|
|
35
|
+
if (matchingItems.length >= MAX_SUGGESTIONS) {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
setItems(matchingItems);
|
|
41
|
+
},
|
|
42
|
+
items,
|
|
43
|
+
itemToString(item) {
|
|
44
|
+
return item ?? "";
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const dropdownIsVisible = isOpen && items.length > 0;
|
|
48
|
+
return /* @__PURE__ */ jsxs("div", { className: styles.downshiftAutocompleteContainer, children: [
|
|
49
|
+
/* @__PURE__ */ jsxs("div", { "data-visible-dropdown": dropdownIsVisible, className: styles.downshiftInputWrapper, children: [
|
|
50
|
+
/* @__PURE__ */ jsx(
|
|
51
|
+
Controller,
|
|
52
|
+
{
|
|
53
|
+
name: inputName,
|
|
54
|
+
control,
|
|
55
|
+
render: ({ field }) => {
|
|
56
|
+
const downshiftSrcProps = getInputProps();
|
|
57
|
+
return /* @__PURE__ */ jsx(
|
|
58
|
+
"input",
|
|
59
|
+
{
|
|
60
|
+
...downshiftSrcProps,
|
|
61
|
+
name: field.name,
|
|
62
|
+
placeholder,
|
|
63
|
+
className: styles.downshiftInput,
|
|
64
|
+
size: 30,
|
|
65
|
+
"data-editor-dialog": true,
|
|
66
|
+
autoFocus: autofocus
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
),
|
|
72
|
+
enableAutoComplete && /* @__PURE__ */ jsx("button", { "aria-label": "toggle menu", type: "button", ...getToggleButtonProps(), children: /* @__PURE__ */ jsx(EditorIcon, { name: "chevron-down" }) })
|
|
73
|
+
] }),
|
|
74
|
+
/* @__PURE__ */ jsx("div", { className: styles.downshiftAutocompleteContainer, children: /* @__PURE__ */ jsx("ul", { ...getMenuProps(), "data-visible": dropdownIsVisible, children: items.map((item, index) => /* @__PURE__ */ jsx(
|
|
75
|
+
"li",
|
|
76
|
+
{
|
|
77
|
+
"data-selected": selectedItem === item,
|
|
78
|
+
"data-highlighted": highlightedIndex === index,
|
|
79
|
+
...getItemProps({ item, index }),
|
|
80
|
+
children: item
|
|
81
|
+
},
|
|
82
|
+
`${item}${index}`
|
|
83
|
+
)) }) })
|
|
84
|
+
] });
|
|
85
|
+
};
|
|
86
|
+
export {
|
|
87
|
+
DownshiftAutoComplete,
|
|
88
|
+
DownshiftAutoCompleteWithSuggestions
|
|
89
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import * as RadixPopover from "@radix-ui/react-popover";
|
|
4
|
+
import { editorRootElementRef$ } from "../index.js";
|
|
5
|
+
import styles from "../../../styles/ui.module.css.js";
|
|
6
|
+
import { useCellValue } from "@mdxeditor/gurx";
|
|
7
|
+
const PopoverPortal = (props) => {
|
|
8
|
+
const editorRootElementRef = useCellValue(editorRootElementRef$);
|
|
9
|
+
return /* @__PURE__ */ jsx(RadixPopover.Portal, { ...props, container: editorRootElementRef?.current });
|
|
10
|
+
};
|
|
11
|
+
const PopoverContent = React.forwardRef(
|
|
12
|
+
(props, ref) => {
|
|
13
|
+
return /* @__PURE__ */ jsxs(RadixPopover.Content, { ...props, className: styles.popoverContent, sideOffset: 5, side: "top", ref, children: [
|
|
14
|
+
/* @__PURE__ */ jsx("span", { className: styles.popoverArrow, children: /* @__PURE__ */ jsx(RadixPopover.Arrow, {}) }),
|
|
15
|
+
props.children
|
|
16
|
+
] });
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
export {
|
|
20
|
+
PopoverContent,
|
|
21
|
+
PopoverPortal
|
|
22
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { DiffViewer } from "./DiffViewer.js";
|
|
3
|
+
import { SourceEditor } from "./SourceEditor.js";
|
|
4
|
+
import { markdownProcessingError$, viewMode$ } from "../core/index.js";
|
|
5
|
+
import styles from "../../styles/ui.module.css.js";
|
|
6
|
+
import { useCellValues } from "@mdxeditor/gurx";
|
|
7
|
+
const DiffSourceWrapper = ({ children }) => {
|
|
8
|
+
const [error, viewMode] = useCellValues(markdownProcessingError$, viewMode$);
|
|
9
|
+
return /* @__PURE__ */ jsxs("div", { className: "mdxeditor-diff-source-wrapper", children: [
|
|
10
|
+
error ? /* @__PURE__ */ jsxs("div", { className: styles.markdownParseError, children: [
|
|
11
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
12
|
+
error.error,
|
|
13
|
+
"."
|
|
14
|
+
] }),
|
|
15
|
+
/* @__PURE__ */ jsx("p", { children: "You can fix the errors in source mode and switch to rich text mode when you are ready." })
|
|
16
|
+
] }) : null,
|
|
17
|
+
/* @__PURE__ */ jsx("div", { className: "mdxeditor-rich-text-editor", style: { display: viewMode === "rich-text" && error == null ? "block" : "none" }, children }),
|
|
18
|
+
viewMode === "diff" ? /* @__PURE__ */ jsx(DiffViewer, {}) : null,
|
|
19
|
+
viewMode === "source" ? /* @__PURE__ */ jsx(SourceEditor, {}) : null
|
|
20
|
+
] });
|
|
21
|
+
};
|
|
22
|
+
export {
|
|
23
|
+
DiffSourceWrapper
|
|
24
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { diffMarkdown$, readOnlyDiff$, cmExtensions$ } from "./index.js";
|
|
4
|
+
import { markdown$, readOnly$, markdownSourceEditorValue$, onBlur$ } from "../core/index.js";
|
|
5
|
+
import { MergeView } from "@codemirror/merge";
|
|
6
|
+
import { EditorState } from "@codemirror/state";
|
|
7
|
+
import { EditorView } from "@codemirror/view";
|
|
8
|
+
import { COMMON_STATE_CONFIG_EXTENSIONS } from "./SourceEditor.js";
|
|
9
|
+
import { useRealm, useCellValues, usePublisher, useCellValue } from "@mdxeditor/gurx";
|
|
10
|
+
function setContent(view, content) {
|
|
11
|
+
if (view !== void 0) {
|
|
12
|
+
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: content } });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const DiffViewer = () => {
|
|
16
|
+
const realm = useRealm();
|
|
17
|
+
const [newMarkdown, oldMarkdown, readOnly, readOnlyDiff] = useCellValues(markdown$, diffMarkdown$, readOnly$, readOnlyDiff$);
|
|
18
|
+
const onUpdate = usePublisher(markdownSourceEditorValue$);
|
|
19
|
+
const elRef = React.useRef(null);
|
|
20
|
+
const cmMergeViewRef = React.useRef(null);
|
|
21
|
+
const cmExtensions = useCellValue(cmExtensions$);
|
|
22
|
+
const triggerOnBlur = usePublisher(onBlur$);
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
return realm.sub(diffMarkdown$, (newDiffMarkdown) => {
|
|
25
|
+
setContent(cmMergeViewRef.current?.a, newDiffMarkdown);
|
|
26
|
+
});
|
|
27
|
+
}, [realm]);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
return realm.sub(markdown$, (newMarkdown2) => {
|
|
30
|
+
setContent(cmMergeViewRef.current?.b, newMarkdown2);
|
|
31
|
+
});
|
|
32
|
+
}, [realm]);
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
const isReadOnly = readOnly || readOnlyDiff;
|
|
35
|
+
const revertParams = isReadOnly ? {
|
|
36
|
+
renderRevertControl: void 0,
|
|
37
|
+
revertControls: void 0
|
|
38
|
+
} : {
|
|
39
|
+
renderRevertControl: () => {
|
|
40
|
+
const el = document.createElement("button");
|
|
41
|
+
el.classList.add("cm-merge-revert");
|
|
42
|
+
el.appendChild(document.createTextNode("⮕"));
|
|
43
|
+
return el;
|
|
44
|
+
},
|
|
45
|
+
revertControls: "a-to-b"
|
|
46
|
+
};
|
|
47
|
+
cmMergeViewRef.current = new MergeView({
|
|
48
|
+
...revertParams,
|
|
49
|
+
parent: elRef.current,
|
|
50
|
+
orientation: "a-b",
|
|
51
|
+
gutter: true,
|
|
52
|
+
a: {
|
|
53
|
+
doc: oldMarkdown,
|
|
54
|
+
extensions: [...cmExtensions, ...COMMON_STATE_CONFIG_EXTENSIONS, EditorState.readOnly.of(true)]
|
|
55
|
+
},
|
|
56
|
+
b: {
|
|
57
|
+
doc: newMarkdown,
|
|
58
|
+
extensions: [
|
|
59
|
+
...cmExtensions,
|
|
60
|
+
...COMMON_STATE_CONFIG_EXTENSIONS,
|
|
61
|
+
EditorState.readOnly.of(isReadOnly),
|
|
62
|
+
EditorView.updateListener.of(({ state }) => {
|
|
63
|
+
const md = state.doc.toString();
|
|
64
|
+
onUpdate(md);
|
|
65
|
+
}),
|
|
66
|
+
EditorView.focusChangeEffect.of((_, focused) => {
|
|
67
|
+
if (!focused) {
|
|
68
|
+
triggerOnBlur(new FocusEvent("blur"));
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
})
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return () => {
|
|
76
|
+
cmMergeViewRef.current?.destroy();
|
|
77
|
+
cmMergeViewRef.current = null;
|
|
78
|
+
};
|
|
79
|
+
}, [onUpdate, cmExtensions]);
|
|
80
|
+
return /* @__PURE__ */ jsx("div", { ref: elRef, className: "mdxeditor-diff-editor" });
|
|
81
|
+
};
|
|
82
|
+
export {
|
|
83
|
+
DiffViewer
|
|
84
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { markdown } from "@codemirror/lang-markdown";
|
|
3
|
+
import { EditorState } from "@codemirror/state";
|
|
4
|
+
import { EditorView, lineNumbers } from "@codemirror/view";
|
|
5
|
+
import { basicLight } from "cm6-theme-basic-light";
|
|
6
|
+
import { basicSetup } from "codemirror";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { cmExtensions$ } from "./index.js";
|
|
9
|
+
import { markdown$, readOnly$, markdownSourceEditorValue$, onBlur$ } from "../core/index.js";
|
|
10
|
+
import { useCellValues, usePublisher } from "@mdxeditor/gurx";
|
|
11
|
+
const COMMON_STATE_CONFIG_EXTENSIONS = [
|
|
12
|
+
basicSetup,
|
|
13
|
+
basicLight,
|
|
14
|
+
markdown(),
|
|
15
|
+
lineNumbers(),
|
|
16
|
+
EditorView.lineWrapping
|
|
17
|
+
];
|
|
18
|
+
const SourceEditor = () => {
|
|
19
|
+
const [markdown2, readOnly, cmExtensions] = useCellValues(markdown$, readOnly$, cmExtensions$);
|
|
20
|
+
const updateMarkdown = usePublisher(markdownSourceEditorValue$);
|
|
21
|
+
const triggerOnBlur = usePublisher(onBlur$);
|
|
22
|
+
const editorViewRef = React.useRef(null);
|
|
23
|
+
const ref = React.useCallback(
|
|
24
|
+
(el) => {
|
|
25
|
+
if (el !== null) {
|
|
26
|
+
const extensions = [
|
|
27
|
+
// custom extensions should come first so that you can override the default extensions
|
|
28
|
+
...cmExtensions,
|
|
29
|
+
...COMMON_STATE_CONFIG_EXTENSIONS,
|
|
30
|
+
EditorView.updateListener.of(({ state }) => {
|
|
31
|
+
updateMarkdown(state.doc.toString());
|
|
32
|
+
}),
|
|
33
|
+
EditorView.focusChangeEffect.of((_, focused) => {
|
|
34
|
+
if (!focused) {
|
|
35
|
+
triggerOnBlur(new FocusEvent("blur"));
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
})
|
|
39
|
+
];
|
|
40
|
+
if (readOnly) {
|
|
41
|
+
extensions.push(EditorState.readOnly.of(true));
|
|
42
|
+
}
|
|
43
|
+
el.innerHTML = "";
|
|
44
|
+
editorViewRef.current = new EditorView({
|
|
45
|
+
parent: el,
|
|
46
|
+
state: EditorState.create({ doc: markdown2, extensions })
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
editorViewRef.current?.destroy();
|
|
50
|
+
editorViewRef.current = null;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
[markdown2, readOnly, updateMarkdown, cmExtensions, triggerOnBlur]
|
|
54
|
+
);
|
|
55
|
+
return /* @__PURE__ */ jsx("div", { ref, className: "cm-sourceView mdxeditor-source-editor" });
|
|
56
|
+
};
|
|
57
|
+
export {
|
|
58
|
+
COMMON_STATE_CONFIG_EXTENSIONS,
|
|
59
|
+
SourceEditor
|
|
60
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { viewMode$, addEditorWrapper$ } from "../core/index.js";
|
|
2
|
+
import { DiffSourceWrapper } from "./DiffSourceWrapper.js";
|
|
3
|
+
import { Cell } from "@mdxeditor/gurx";
|
|
4
|
+
import { realmPlugin } from "../../RealmWithPlugins.js";
|
|
5
|
+
const diffMarkdown$ = Cell("");
|
|
6
|
+
const cmExtensions$ = Cell([]);
|
|
7
|
+
const readOnlyDiff$ = Cell(false);
|
|
8
|
+
const diffSourcePlugin = realmPlugin({
|
|
9
|
+
update: (r, params) => {
|
|
10
|
+
r.pub(diffMarkdown$, params?.diffMarkdown ?? "");
|
|
11
|
+
},
|
|
12
|
+
init(r, params) {
|
|
13
|
+
r.pubIn({
|
|
14
|
+
[diffMarkdown$]: params?.diffMarkdown ?? "",
|
|
15
|
+
[cmExtensions$]: params?.codeMirrorExtensions ?? [],
|
|
16
|
+
[addEditorWrapper$]: DiffSourceWrapper,
|
|
17
|
+
[readOnlyDiff$]: params?.readOnlyDiff ?? false,
|
|
18
|
+
[viewMode$]: params?.viewMode ?? "rich-text"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
export {
|
|
23
|
+
cmExtensions$,
|
|
24
|
+
diffMarkdown$,
|
|
25
|
+
diffSourcePlugin,
|
|
26
|
+
readOnlyDiff$
|
|
27
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { DecoratorNode } from "lexical";
|
|
3
|
+
import { NestedEditorsContext } from "../core/NestedLexicalEditor.js";
|
|
4
|
+
import { voidEmitter } from "../../utils/voidEmitter.js";
|
|
5
|
+
import { useCellValues } from "@mdxeditor/gurx";
|
|
6
|
+
import { directiveDescriptors$ } from "../core/index.js";
|
|
7
|
+
class DirectiveNode extends DecoratorNode {
|
|
8
|
+
/** @internal */
|
|
9
|
+
__mdastNode;
|
|
10
|
+
/** @internal */
|
|
11
|
+
__focusEmitter = voidEmitter();
|
|
12
|
+
/** @internal */
|
|
13
|
+
static getType() {
|
|
14
|
+
return "directive";
|
|
15
|
+
}
|
|
16
|
+
/** @internal */
|
|
17
|
+
static clone(node) {
|
|
18
|
+
return new DirectiveNode(structuredClone(node.__mdastNode), node.__key);
|
|
19
|
+
}
|
|
20
|
+
/** @internal */
|
|
21
|
+
static importJSON(serializedNode) {
|
|
22
|
+
return $createDirectiveNode(serializedNode.mdastNode);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Constructs a new {@link DirectiveNode} with the specified MDAST directive node as the object to edit.
|
|
26
|
+
*/
|
|
27
|
+
constructor(mdastNode, key) {
|
|
28
|
+
super(key);
|
|
29
|
+
this.__mdastNode = mdastNode;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns the MDAST node that is being edited.
|
|
33
|
+
*/
|
|
34
|
+
getMdastNode() {
|
|
35
|
+
return this.__mdastNode;
|
|
36
|
+
}
|
|
37
|
+
/** @internal */
|
|
38
|
+
exportJSON() {
|
|
39
|
+
return {
|
|
40
|
+
mdastNode: structuredClone(this.__mdastNode),
|
|
41
|
+
type: "directive",
|
|
42
|
+
version: 1
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** @internal */
|
|
46
|
+
createDOM() {
|
|
47
|
+
return document.createElement(this.__mdastNode.type === "textDirective" ? "span" : "div");
|
|
48
|
+
}
|
|
49
|
+
/** @internal */
|
|
50
|
+
updateDOM() {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Sets a new MDAST node to edit.
|
|
55
|
+
*/
|
|
56
|
+
setMdastNode(mdastNode) {
|
|
57
|
+
this.getWritable().__mdastNode = mdastNode;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Focuses the direcitive editor.
|
|
61
|
+
*/
|
|
62
|
+
select = () => {
|
|
63
|
+
this.__focusEmitter.publish();
|
|
64
|
+
};
|
|
65
|
+
/** @internal */
|
|
66
|
+
decorate(parentEditor, config) {
|
|
67
|
+
return /* @__PURE__ */ jsx(
|
|
68
|
+
DirectiveEditorContainer,
|
|
69
|
+
{
|
|
70
|
+
lexicalNode: this,
|
|
71
|
+
mdastNode: this.getMdastNode(),
|
|
72
|
+
parentEditor,
|
|
73
|
+
config,
|
|
74
|
+
focusEmitter: this.__focusEmitter
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
/** @internal */
|
|
79
|
+
isInline() {
|
|
80
|
+
return this.__mdastNode.type === "textDirective";
|
|
81
|
+
}
|
|
82
|
+
/** @internal */
|
|
83
|
+
isKeyboardSelectable() {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const DirectiveEditorContainer = (props) => {
|
|
88
|
+
const { mdastNode } = props;
|
|
89
|
+
const [directiveDescriptors] = useCellValues(directiveDescriptors$);
|
|
90
|
+
const descriptor = directiveDescriptors.find((descriptor2) => descriptor2.testNode(mdastNode));
|
|
91
|
+
if (!descriptor) {
|
|
92
|
+
throw new Error(`No descriptor found for directive ${mdastNode.name}`);
|
|
93
|
+
}
|
|
94
|
+
const Editor = descriptor.Editor;
|
|
95
|
+
return /* @__PURE__ */ jsx(NestedEditorsContext.Provider, { value: props, children: /* @__PURE__ */ jsx(Editor, { descriptor, mdastNode, lexicalNode: props.lexicalNode, parentEditor: props.parentEditor }) });
|
|
96
|
+
};
|
|
97
|
+
function $createDirectiveNode(mdastNode, key) {
|
|
98
|
+
return new DirectiveNode(mdastNode, key);
|
|
99
|
+
}
|
|
100
|
+
function $isDirectiveNode(node) {
|
|
101
|
+
return node instanceof DirectiveNode;
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
$createDirectiveNode,
|
|
105
|
+
$isDirectiveNode,
|
|
106
|
+
DirectiveNode
|
|
107
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { $isDirectiveNode } from "./DirectiveNode.js";
|
|
2
|
+
const DirectiveVisitor = {
|
|
3
|
+
testLexicalNode: $isDirectiveNode,
|
|
4
|
+
visitLexicalNode({ actions, mdastParent, lexicalNode }) {
|
|
5
|
+
actions.appendToParent(mdastParent, lexicalNode.getMdastNode());
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
export {
|
|
9
|
+
DirectiveVisitor
|
|
10
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { $createTextNode } from "lexical";
|
|
2
|
+
import { $createDirectiveNode } from "./DirectiveNode.js";
|
|
3
|
+
const DIRECTIVE_TYPES = ["leafDirective", "containerDirective", "textDirective"];
|
|
4
|
+
function isMdastDirectivesNode(node) {
|
|
5
|
+
return DIRECTIVE_TYPES.includes(node.type);
|
|
6
|
+
}
|
|
7
|
+
const MdastDirectiveVisitor = (escapeUnknownTextDirectives) => ({
|
|
8
|
+
testNode: (node, { directiveDescriptors }) => {
|
|
9
|
+
if (isMdastDirectivesNode(node)) {
|
|
10
|
+
const descriptor = directiveDescriptors.find((descriptor2) => descriptor2.testNode(node));
|
|
11
|
+
if (escapeUnknownTextDirectives && !descriptor && node.type === "textDirective") {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return descriptor !== void 0;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
},
|
|
18
|
+
visitNode({ lexicalParent, mdastNode, descriptors }) {
|
|
19
|
+
const isKnown = !escapeUnknownTextDirectives || descriptors.directiveDescriptors.some((d) => d.testNode(mdastNode));
|
|
20
|
+
if (isKnown) {
|
|
21
|
+
lexicalParent.append($createDirectiveNode(mdastNode));
|
|
22
|
+
} else {
|
|
23
|
+
lexicalParent.append($createTextNode(`:${mdastNode.name}`));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
export {
|
|
28
|
+
MdastDirectiveVisitor,
|
|
29
|
+
isMdastDirectivesNode
|
|
30
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { realmPlugin } from "../../RealmWithPlugins.js";
|
|
2
|
+
import { insertDecoratorNode$, addToMarkdownExtension$, addExportVisitor$, addLexicalNode$, addImportVisitor$, addSyntaxExtension$, addMdastExtension$, directiveDescriptors$ } from "../core/index.js";
|
|
3
|
+
import { Signal, map } from "@mdxeditor/gurx";
|
|
4
|
+
import { directiveToMarkdown, directiveFromMarkdown } from "mdast-util-directive";
|
|
5
|
+
import { directive } from "micromark-extension-directive";
|
|
6
|
+
import { $createDirectiveNode, DirectiveNode } from "./DirectiveNode.js";
|
|
7
|
+
import { $isDirectiveNode } from "./DirectiveNode.js";
|
|
8
|
+
import { DirectiveVisitor } from "./DirectiveVisitor.js";
|
|
9
|
+
import { MdastDirectiveVisitor } from "./MdastDirectiveVisitor.js";
|
|
10
|
+
const insertDirective$ = Signal((r) => {
|
|
11
|
+
r.link(
|
|
12
|
+
r.pipe(
|
|
13
|
+
insertDirective$,
|
|
14
|
+
map((payload) => {
|
|
15
|
+
return () => $createDirectiveNode({ children: [], ...payload });
|
|
16
|
+
})
|
|
17
|
+
),
|
|
18
|
+
insertDecoratorNode$
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
const directivesPlugin = realmPlugin({
|
|
22
|
+
update: (realm, params) => {
|
|
23
|
+
realm.pub(directiveDescriptors$, params?.directiveDescriptors ?? []);
|
|
24
|
+
},
|
|
25
|
+
init: (realm, params) => {
|
|
26
|
+
realm.pubIn({
|
|
27
|
+
[directiveDescriptors$]: params?.directiveDescriptors ?? [],
|
|
28
|
+
// import
|
|
29
|
+
[addMdastExtension$]: directiveFromMarkdown(),
|
|
30
|
+
[addSyntaxExtension$]: directive(),
|
|
31
|
+
[addImportVisitor$]: MdastDirectiveVisitor(params?.escapeUnknownTextDirectives),
|
|
32
|
+
// export
|
|
33
|
+
[addLexicalNode$]: DirectiveNode,
|
|
34
|
+
[addExportVisitor$]: DirectiveVisitor,
|
|
35
|
+
[addToMarkdownExtension$]: directiveToMarkdown()
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
export {
|
|
40
|
+
$createDirectiveNode,
|
|
41
|
+
$isDirectiveNode,
|
|
42
|
+
DirectiveNode,
|
|
43
|
+
directivesPlugin,
|
|
44
|
+
insertDirective$
|
|
45
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { jsx, Fragment, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as Dialog from "@radix-ui/react-dialog";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import YamlParser from "js-yaml";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { useForm, useFieldArray } from "react-hook-form";
|
|
7
|
+
import { frontmatterDialogOpen$, removeFrontmatter$ } from "./index.js";
|
|
8
|
+
import styles from "../../styles/ui.module.css.js";
|
|
9
|
+
import { readOnly$, editorRootElementRef$, iconComponentFor$, useTranslation } from "../core/index.js";
|
|
10
|
+
import { useCellValues, usePublisher } from "@mdxeditor/gurx";
|
|
11
|
+
const FrontmatterEditor = ({ yaml, onChange }) => {
|
|
12
|
+
const [readOnly, editorRootElementRef, iconComponentFor, frontmatterDialogOpen] = useCellValues(
|
|
13
|
+
readOnly$,
|
|
14
|
+
editorRootElementRef$,
|
|
15
|
+
iconComponentFor$,
|
|
16
|
+
frontmatterDialogOpen$
|
|
17
|
+
);
|
|
18
|
+
const t = useTranslation();
|
|
19
|
+
const setFrontmatterDialogOpen = usePublisher(frontmatterDialogOpen$);
|
|
20
|
+
const removeFrontmatter = usePublisher(removeFrontmatter$);
|
|
21
|
+
const yamlConfig = React.useMemo(() => {
|
|
22
|
+
if (!yaml) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
return Object.entries(YamlParser.load(yaml)).map(([key, value]) => ({ key, value }));
|
|
26
|
+
}, [yaml]);
|
|
27
|
+
const { register, control, handleSubmit } = useForm({
|
|
28
|
+
defaultValues: {
|
|
29
|
+
yamlConfig
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const { fields, append, remove } = useFieldArray({
|
|
33
|
+
control,
|
|
34
|
+
name: "yamlConfig"
|
|
35
|
+
});
|
|
36
|
+
const onSubmit = React.useCallback(
|
|
37
|
+
({ yamlConfig: yamlConfig2 }) => {
|
|
38
|
+
if (yamlConfig2.length === 0) {
|
|
39
|
+
removeFrontmatter();
|
|
40
|
+
setFrontmatterDialogOpen(false);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const yaml2 = yamlConfig2.reduce((acc, { key, value }) => {
|
|
44
|
+
if (key && value) {
|
|
45
|
+
acc[key] = value;
|
|
46
|
+
}
|
|
47
|
+
return acc;
|
|
48
|
+
}, {});
|
|
49
|
+
onChange(YamlParser.dump(yaml2).trim());
|
|
50
|
+
setFrontmatterDialogOpen(false);
|
|
51
|
+
},
|
|
52
|
+
[onChange, setFrontmatterDialogOpen, removeFrontmatter]
|
|
53
|
+
);
|
|
54
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
|
|
55
|
+
Dialog.Root,
|
|
56
|
+
{
|
|
57
|
+
open: frontmatterDialogOpen,
|
|
58
|
+
onOpenChange: (open) => {
|
|
59
|
+
setFrontmatterDialogOpen(open);
|
|
60
|
+
},
|
|
61
|
+
children: /* @__PURE__ */ jsxs(Dialog.Portal, { container: editorRootElementRef?.current, children: [
|
|
62
|
+
/* @__PURE__ */ jsx(Dialog.Overlay, { className: styles.dialogOverlay }),
|
|
63
|
+
/* @__PURE__ */ jsxs(Dialog.Content, { className: styles.largeDialogContent, "data-editor-type": "frontmatter", children: [
|
|
64
|
+
/* @__PURE__ */ jsx(Dialog.Title, { className: styles.dialogTitle, children: t("frontmatterEditor.title", "Edit document frontmatter") }),
|
|
65
|
+
/* @__PURE__ */ jsxs(
|
|
66
|
+
"form",
|
|
67
|
+
{
|
|
68
|
+
onSubmit: (e) => {
|
|
69
|
+
void handleSubmit(onSubmit)(e);
|
|
70
|
+
e.stopPropagation();
|
|
71
|
+
},
|
|
72
|
+
onReset: (e) => {
|
|
73
|
+
e.stopPropagation();
|
|
74
|
+
setFrontmatterDialogOpen(false);
|
|
75
|
+
},
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ jsxs("table", { className: styles.propertyEditorTable, children: [
|
|
78
|
+
/* @__PURE__ */ jsxs("colgroup", { children: [
|
|
79
|
+
/* @__PURE__ */ jsx("col", {}),
|
|
80
|
+
/* @__PURE__ */ jsx("col", {}),
|
|
81
|
+
/* @__PURE__ */ jsx("col", {})
|
|
82
|
+
] }),
|
|
83
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
84
|
+
/* @__PURE__ */ jsx("th", { children: t("frontmatterEditor.key", "Key") }),
|
|
85
|
+
/* @__PURE__ */ jsx("th", { children: t("frontmatterEditor.value", "Value") }),
|
|
86
|
+
/* @__PURE__ */ jsx("th", {})
|
|
87
|
+
] }) }),
|
|
88
|
+
/* @__PURE__ */ jsx("tbody", { children: fields.map((item, index) => {
|
|
89
|
+
return /* @__PURE__ */ jsxs("tr", { children: [
|
|
90
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(TableInput, { ...register(`yamlConfig.${index}.key`, { required: true }), autofocusIfEmpty: true, readOnly }) }),
|
|
91
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(TableInput, { ...register(`yamlConfig.${index}.value`, { required: true }), readOnly }) }),
|
|
92
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(
|
|
93
|
+
"button",
|
|
94
|
+
{
|
|
95
|
+
type: "button",
|
|
96
|
+
onClick: () => {
|
|
97
|
+
remove(index);
|
|
98
|
+
},
|
|
99
|
+
className: styles.iconButton,
|
|
100
|
+
disabled: readOnly,
|
|
101
|
+
children: iconComponentFor("delete_big")
|
|
102
|
+
}
|
|
103
|
+
) })
|
|
104
|
+
] }, item.id);
|
|
105
|
+
}) }),
|
|
106
|
+
/* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(
|
|
107
|
+
"button",
|
|
108
|
+
{
|
|
109
|
+
disabled: readOnly,
|
|
110
|
+
className: classNames(styles.primaryButton, styles.smallButton),
|
|
111
|
+
type: "button",
|
|
112
|
+
onClick: () => {
|
|
113
|
+
append({ key: "", value: "" });
|
|
114
|
+
},
|
|
115
|
+
children: t("frontmatterEditor.addEntry", "Add entry")
|
|
116
|
+
}
|
|
117
|
+
) }) }) })
|
|
118
|
+
] }),
|
|
119
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: "var(--spacing-2)" }, children: [
|
|
120
|
+
/* @__PURE__ */ jsx("button", { type: "submit", className: styles.primaryButton, children: t("dialogControls.save", "Save") }),
|
|
121
|
+
/* @__PURE__ */ jsx("button", { type: "reset", className: styles.secondaryButton, children: t("dialogControls.cancel", "Cancel") })
|
|
122
|
+
] })
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
),
|
|
126
|
+
/* @__PURE__ */ jsx(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { className: styles.dialogCloseButton, "aria-label": t("dialogControls.cancel", "Cancel"), children: iconComponentFor("close") }) })
|
|
127
|
+
] })
|
|
128
|
+
] })
|
|
129
|
+
}
|
|
130
|
+
) });
|
|
131
|
+
};
|
|
132
|
+
const TableInput = React.forwardRef(({ className, autofocusIfEmpty: _, ...props }, ref) => {
|
|
133
|
+
return /* @__PURE__ */ jsx("input", { className: classNames(styles.propertyEditorInput, className), ...props, ref });
|
|
134
|
+
});
|
|
135
|
+
export {
|
|
136
|
+
FrontmatterEditor
|
|
137
|
+
};
|