@mdxeditor/editor 3.49.2 → 3.50.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/dist/MDXEditor.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { usePublisher, useCellValue, useCellValues, useRealm } from "@mdxeditor/gurx";
2
2
  import React__default from "react";
3
- import { corePlugin, editorRootElementRef$, rootEditor$, useTranslation, contentEditableRef$, contentEditableWrapperElement$, contentEditableClassName$, spellCheck$, composerChildren$, topAreaChildren$, editorWrappers$, placeholder$, bottomAreaChildren$, insertMarkdown$, setMarkdown$, viewMode$, markdownSourceEditorValue$, markdown$ } from "./plugins/core/index.js";
3
+ import { corePlugin, editorRootElementRef$, editorWrapperElementRef$, rootEditor$, useTranslation, contentEditableRef$, contentEditableWrapperElement$, contentEditableClassName$, spellCheck$, composerChildren$, topAreaChildren$, editorWrappers$, placeholder$, bottomAreaChildren$, viewMode$, activeEditor$, exportVisitors$, toMarkdownExtensions$, toMarkdownOptions$, jsxComponentDescriptors$, jsxIsAvailable$, insertMarkdown$, setMarkdown$, markdownSourceEditorValue$, markdown$ } from "./plugins/core/index.js";
4
4
  import { RealmWithPlugins } from "./RealmWithPlugins.js";
5
5
  import { createLexicalComposerContext, LexicalComposerContext } from "@lexical/react/LexicalComposerContext";
6
6
  import { ContentEditable } from "@lexical/react/LexicalContentEditable";
@@ -11,6 +11,7 @@ import { defaultSvgIcons } from "./defaultSvgIcons.js";
11
11
  import { lexicalTheme } from "./styles/lexicalTheme.js";
12
12
  import styles from "./styles/ui.module.css.js";
13
13
  import { noop } from "./utils/fp.js";
14
+ import { getSelectionAsMarkdown } from "./utils/lexicalHelpers.js";
14
15
  const LexicalProvider = ({ children }) => {
15
16
  const rootEditor = useCellValue(rootEditor$);
16
17
  const composerContextValue = React__default.useMemo(() => {
@@ -73,7 +74,9 @@ const RenderRecursiveWrappers = ({ wrappers, children }) => {
73
74
  };
74
75
  const EditorRootElement = ({ children, className, overlayContainer }) => {
75
76
  const editorRootElementRef = React__default.useRef(null);
77
+ const wrapperElementRef = React__default.useRef(null);
76
78
  const setEditorRootElementRef = usePublisher(editorRootElementRef$);
79
+ const setEditorWrapperElementRef = usePublisher(editorWrapperElementRef$);
77
80
  React__default.useEffect(() => {
78
81
  const popupContainer = document.createElement("div");
79
82
  popupContainer.classList.add(
@@ -86,11 +89,12 @@ const EditorRootElement = ({ children, className, overlayContainer }) => {
86
89
  container.appendChild(popupContainer);
87
90
  editorRootElementRef.current = popupContainer;
88
91
  setEditorRootElementRef(editorRootElementRef);
92
+ setEditorWrapperElementRef(wrapperElementRef);
89
93
  return () => {
90
94
  popupContainer.remove();
91
95
  };
92
- }, [className, editorRootElementRef, overlayContainer, setEditorRootElementRef]);
93
- return /* @__PURE__ */ React__default.createElement("div", { className: classNames("mdxeditor", styles.editorRoot, styles.editorWrapper, className) }, children);
96
+ }, [className, editorRootElementRef, overlayContainer, setEditorRootElementRef, setEditorWrapperElementRef]);
97
+ return /* @__PURE__ */ React__default.createElement("div", { className: classNames("mdxeditor", styles.editorRoot, styles.editorWrapper, className), ref: wrapperElementRef }, children);
94
98
  };
95
99
  const Methods = ({ mdxRef }) => {
96
100
  const realm = useRealm();
@@ -114,6 +118,26 @@ const Methods = ({ mdxRef }) => {
114
118
  focus: (callbackFn, opts) => {
115
119
  var _a;
116
120
  (_a = realm.getValue(rootEditor$)) == null ? void 0 : _a.focus(callbackFn, opts);
121
+ },
122
+ getContentEditableHTML: () => {
123
+ var _a, _b;
124
+ return ((_b = (_a = realm.getValue(contentEditableRef$)) == null ? void 0 : _a.current) == null ? void 0 : _b.innerHTML) ?? "";
125
+ },
126
+ getSelectionMarkdown: () => {
127
+ const viewMode = realm.getValue(viewMode$);
128
+ if (viewMode === "source" || viewMode === "diff") {
129
+ return "";
130
+ }
131
+ const activeEditor = realm.getValue(activeEditor$);
132
+ if (!activeEditor) {
133
+ return "";
134
+ }
135
+ realm.getValue(exportVisitors$);
136
+ realm.getValue(toMarkdownExtensions$);
137
+ realm.getValue(toMarkdownOptions$);
138
+ realm.getValue(jsxComponentDescriptors$);
139
+ realm.getValue(jsxIsAvailable$);
140
+ return getSelectionAsMarkdown(activeEditor);
117
141
  }
118
142
  };
119
143
  },
package/dist/index.d.ts CHANGED
@@ -1169,6 +1169,11 @@ export declare const editorSearchTextNodeIndex$: NodeRef<TextNodeIndex>;
1169
1169
  */
1170
1170
  export declare type EditorSubscription = (activeEditor: LexicalEditor) => () => void;
1171
1171
 
1172
+ /**
1173
+ * @group Core
1174
+ */
1175
+ export declare const editorWrapperElementRef$: NodeRef<default_2.RefObject<HTMLDivElement> | null>;
1176
+
1172
1177
  /* Excluded from this release type: editorWrappers$ */
1173
1178
 
1174
1179
  export declare const EmptyTextNodeIndex: TextNodeIndex;
@@ -1314,6 +1319,15 @@ export declare interface GenericJsxEditorProps extends JsxEditorProps {
1314
1319
  */
1315
1320
  export declare function getSelectedNode(selection: RangeSelection): TextNode | ElementNode | null;
1316
1321
 
1322
+ /**
1323
+ * Gets the markdown representation of the current selection in the Lexical editor.
1324
+ * Returns an empty string if there is no selection or if the selection is collapsed.
1325
+ * Converts selected nodes to markdown by recursively processing them and preserving formatting.
1326
+ * Note: Selects entire nodes, not partial selections within nodes.
1327
+ * @group Utils
1328
+ */
1329
+ export declare function getSelectionAsMarkdown(editor: LexicalEditor, _exportParams: Omit<ExportMarkdownFromLexicalOptions, 'root'>): string;
1330
+
1317
1331
  /**
1318
1332
  * Gets the coordinates of the selection in the Lexical editor.
1319
1333
  * @group Utils
@@ -2226,6 +2240,15 @@ export declare interface MDXEditorMethods {
2226
2240
  defaultSelection?: 'rootStart' | 'rootEnd';
2227
2241
  preventScroll?: boolean;
2228
2242
  }) => void;
2243
+ /**
2244
+ * gets the underlying Lexical contentEditable HTML content
2245
+ */
2246
+ getContentEditableHTML: () => string;
2247
+ /**
2248
+ * Gets the markdown representation of the current selection.
2249
+ * Returns an empty string if there is no selection, if selection is collapsed, or if editor is in source/diff mode.
2250
+ */
2251
+ getSelectionMarkdown: () => string;
2229
2252
  }
2230
2253
 
2231
2254
  /**
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { MDXEditor } from "./MDXEditor.js";
4
4
  import { defaultSvgIcons } from "./defaultSvgIcons.js";
5
5
  import { MarkdownParseError, UnrecognizedMarkdownConstructError, importMarkdownToLexical, importMdastTreeToLexical } from "./importMarkdownToLexical.js";
6
6
  import { exportLexicalTreeToMdast, exportMarkdownFromLexical } from "./exportMarkdownFromLexical.js";
7
- import { Appender, NESTED_EDITOR_UPDATED_COMMAND, activeEditor$, activeEditorSubscriptions$, activePlugins$, addActivePlugin$, addBottomAreaChild$, addComposerChild$, addEditorWrapper$, addExportVisitor$, addImportVisitor$, addLexicalNode$, addMdastExtension$, addNestedEditorChild$, addSyntaxExtension$, addToMarkdownExtension$, addTopAreaChild$, applyBlockType$, applyFormat$, autoFocus$, bottomAreaChildren$, codeBlockEditorDescriptors$, composerChildren$, contentEditableClassName$, contentEditableRef$, contentEditableWrapperElement$, convertSelectionToNode$, corePlugin, createActiveEditorSubscription$, createRootEditorSubscription$, currentBlockType$, currentFormat$, currentSelection$, directiveDescriptors$, editorInFocus$, editorRootElementRef$, editorWrappers$, exportVisitors$, historyState$, iconComponentFor$, importVisitors$, inFocus$, initialMarkdown$, initialMarkdownNormalize$, insertDecoratorNode$, insertMarkdown$, jsxComponentDescriptors$, jsxIsAvailable$, lexicalTheme$, markdown$, markdownErrorSignal$, markdownProcessingError$, markdownSourceEditorValue$, mdastExtensions$, muteChange$, nestedEditorChildren$, onBlur$, placeholder$, readOnly$, rootEditor$, rootEditorSubscriptions$, setMarkdown$, spellCheck$, syntaxExtensions$, toMarkdownExtensions$, toMarkdownOptions$, topAreaChildren$, translation$, useTranslation, usedLexicalNodes$, viewMode$ } from "./plugins/core/index.js";
7
+ import { Appender, NESTED_EDITOR_UPDATED_COMMAND, activeEditor$, activeEditorSubscriptions$, activePlugins$, addActivePlugin$, addBottomAreaChild$, addComposerChild$, addEditorWrapper$, addExportVisitor$, addImportVisitor$, addLexicalNode$, addMdastExtension$, addNestedEditorChild$, addSyntaxExtension$, addToMarkdownExtension$, addTopAreaChild$, applyBlockType$, applyFormat$, autoFocus$, bottomAreaChildren$, codeBlockEditorDescriptors$, composerChildren$, contentEditableClassName$, contentEditableRef$, contentEditableWrapperElement$, convertSelectionToNode$, corePlugin, createActiveEditorSubscription$, createRootEditorSubscription$, currentBlockType$, currentFormat$, currentSelection$, directiveDescriptors$, editorInFocus$, editorRootElementRef$, editorWrapperElementRef$, editorWrappers$, exportVisitors$, historyState$, iconComponentFor$, importVisitors$, inFocus$, initialMarkdown$, initialMarkdownNormalize$, insertDecoratorNode$, insertMarkdown$, jsxComponentDescriptors$, jsxIsAvailable$, lexicalTheme$, markdown$, markdownErrorSignal$, markdownProcessingError$, markdownSourceEditorValue$, mdastExtensions$, muteChange$, nestedEditorChildren$, onBlur$, placeholder$, readOnly$, rootEditor$, rootEditorSubscriptions$, setMarkdown$, spellCheck$, syntaxExtensions$, toMarkdownExtensions$, toMarkdownOptions$, topAreaChildren$, translation$, useTranslation, usedLexicalNodes$, viewMode$ } from "./plugins/core/index.js";
8
8
  import { ALL_HEADING_LEVELS, allowedHeadingLevels$, headingsPlugin } from "./plugins/headings/index.js";
9
9
  import { insertThematicBreak$, thematicBreakPlugin } from "./plugins/thematic-break/index.js";
10
10
  import { applyListType$, currentListType$, listsPlugin } from "./plugins/lists/index.js";
@@ -58,7 +58,7 @@ import { RemoteMDXEditorRealmProvider, remoteRealmPlugin, useRemoteMDXEditorReal
58
58
  import { CAN_USE_DOM, IS_APPLE, controlOrMeta } from "./utils/detectMac.js";
59
59
  import { always, call, compose, curry1to0, curry2to1, joinProc, noop, prop, tap, thrush } from "./utils/fp.js";
60
60
  import { isPartOftheEditorUI } from "./utils/isPartOftheEditorUI.js";
61
- import { fromWithinEditorRead, getSelectedNode, getSelectionRectangle, getStateAsMarkdown } from "./utils/lexicalHelpers.js";
61
+ import { fromWithinEditorRead, getSelectedNode, getSelectionAsMarkdown, getSelectionRectangle, getStateAsMarkdown } from "./utils/lexicalHelpers.js";
62
62
  import { makeHslTransparent } from "./utils/makeHslTransparent.js";
63
63
  import { uuidv4 } from "./utils/uuid4.js";
64
64
  import { voidEmitter } from "./utils/voidEmitter.js";
@@ -237,6 +237,7 @@ export {
237
237
  editorSearchTerm$,
238
238
  editorSearchTermDebounced$,
239
239
  editorSearchTextNodeIndex$,
240
+ editorWrapperElementRef$,
240
241
  editorWrappers$,
241
242
  exportLexicalTreeToMdast,
242
243
  exportMarkdownFromLexical,
@@ -245,6 +246,7 @@ export {
245
246
  frontmatterDialogOpen$,
246
247
  frontmatterPlugin,
247
248
  getSelectedNode,
249
+ getSelectionAsMarkdown,
248
250
  getSelectionRectangle,
249
251
  getStateAsMarkdown,
250
252
  hasFrontmatter$,
@@ -84,7 +84,7 @@ const CodeMirrorEditor = ({ language, nodeKey, code, focusEmitter }) => {
84
84
  editorViewRef.current = null;
85
85
  el.removeEventListener("keydown", stopPropagationHandler);
86
86
  };
87
- }, [readOnly, language]);
87
+ }, [readOnly, language, ...codeMirrorExtensions]);
88
88
  return /* @__PURE__ */ React__default.createElement("div", { className: styles.codeMirrorWrapper }, /* @__PURE__ */ React__default.createElement("div", { className: styles.codeMirrorToolbar }, /* @__PURE__ */ React__default.createElement(
89
89
  Select,
90
90
  {
@@ -116,6 +116,7 @@ const jsxComponentDescriptors$ = Cell([]);
116
116
  const directiveDescriptors$ = Cell([]);
117
117
  const codeBlockEditorDescriptors$ = Cell([]);
118
118
  const editorRootElementRef$ = Cell(null);
119
+ const editorWrapperElementRef$ = Cell(null);
119
120
  const contentEditableRef$ = Cell(null);
120
121
  const addLexicalNode$ = Appender(usedLexicalNodes$);
121
122
  const addImportVisitor$ = Appender(importVisitors$);
@@ -636,6 +637,7 @@ export {
636
637
  directiveDescriptors$,
637
638
  editorInFocus$,
638
639
  editorRootElementRef$,
640
+ editorWrapperElementRef$,
639
641
  editorWrappers$,
640
642
  exportVisitors$,
641
643
  historyState$,
@@ -1,4 +1,7 @@
1
- import { $getSelection, $getRoot } from "lexical";
1
+ import { $getSelection, $getRoot, $isRangeSelection, $isElementNode, $isTextNode } from "lexical";
2
+ import { $isLinkNode } from "@lexical/link";
3
+ import { $isHeadingNode } from "@lexical/rich-text";
4
+ import { $isListItemNode, $isListNode } from "@lexical/list";
2
5
  import { $isAtNodeEnd } from "@lexical/selection";
3
6
  import { tap } from "./fp.js";
4
7
  import { exportMarkdownFromLexical } from "../exportMarkdownFromLexical.js";
@@ -72,9 +75,85 @@ function getStateAsMarkdown(editor, exportParams) {
72
75
  });
73
76
  }).markdown;
74
77
  }
78
+ function getSelectionAsMarkdown(editor, _exportParams) {
79
+ let markdown = "";
80
+ editor.getEditorState().read(() => {
81
+ const selection = $getSelection();
82
+ if (!selection || !$isRangeSelection(selection) || selection.isCollapsed()) {
83
+ return;
84
+ }
85
+ const nodes = selection.getNodes();
86
+ if (nodes.length === 0) {
87
+ return;
88
+ }
89
+ const parentNodes = /* @__PURE__ */ new Set();
90
+ nodes.forEach((node) => {
91
+ let current = node;
92
+ while (current) {
93
+ if ($isHeadingNode(current) || $isListItemNode(current) || current.getType() === "paragraph" || current.getType() === "quote") {
94
+ if ($isElementNode(current)) {
95
+ parentNodes.add(current);
96
+ }
97
+ break;
98
+ }
99
+ current = current.getParent();
100
+ }
101
+ });
102
+ const nodesToProcess = parentNodes.size > 0 ? Array.from(parentNodes) : nodes;
103
+ function nodeToMarkdown(node) {
104
+ if ($isHeadingNode(node)) {
105
+ const level = parseInt(node.getTag().replace("h", ""));
106
+ const children = node.getChildren();
107
+ const headingText = children.map((child) => nodeToMarkdown(child)).join("");
108
+ return "#".repeat(level) + " " + headingText + "\n\n";
109
+ } else if ($isListItemNode(node)) {
110
+ const parent = node.getParent();
111
+ const prefix = parent && $isListNode(parent) && parent.getListType() === "number" ? "1. " : "- ";
112
+ const children = node.getChildren();
113
+ const itemText = children.map((child) => nodeToMarkdown(child)).join("");
114
+ return prefix + itemText + "\n";
115
+ } else if ($isListNode(node)) {
116
+ const children = node.getChildren();
117
+ return children.map((child) => nodeToMarkdown(child)).join("") + "\n";
118
+ } else if ($isTextNode(node)) {
119
+ let text = node.getTextContent();
120
+ const format = node.getFormat();
121
+ if (format & 16) {
122
+ return `\`${text}\``;
123
+ }
124
+ if (format & 1) {
125
+ text = `**${text}**`;
126
+ }
127
+ if (format & 2) {
128
+ text = `*${text}*`;
129
+ }
130
+ if (format & 4) {
131
+ text = `~~${text}~~`;
132
+ }
133
+ return text;
134
+ } else if ($isLinkNode(node)) {
135
+ const url = node.getURL();
136
+ const title = node.getTitle();
137
+ const children = node.getChildren();
138
+ const linkText = children.map((child) => nodeToMarkdown(child)).join("");
139
+ if (title) {
140
+ return `[${linkText}](${url} "${title}")`;
141
+ }
142
+ return `[${linkText}](${url})`;
143
+ } else if ($isElementNode(node)) {
144
+ const children = node.getChildren();
145
+ return children.map((child) => nodeToMarkdown(child)).join("");
146
+ }
147
+ return node.getTextContent();
148
+ }
149
+ markdown = nodesToProcess.map((node) => nodeToMarkdown(node)).join("");
150
+ });
151
+ return markdown.trim();
152
+ }
75
153
  export {
76
154
  fromWithinEditorRead,
77
155
  getSelectedNode,
156
+ getSelectionAsMarkdown,
78
157
  getSelectionRectangle,
79
158
  getStateAsMarkdown
80
159
  };
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "*.css"
12
12
  ],
13
13
  "type": "module",
14
- "version": "3.49.2",
14
+ "version": "3.50.0",
15
15
  "description": "React component for rich text markdown editing",
16
16
  "scripts": {
17
17
  "build": "vite build",
@@ -21,6 +21,7 @@
21
21
  "typecheck": "tsc --noEmit",
22
22
  "lint": "eslint src --ext .ts,.tsx",
23
23
  "test": "NODE_ENV=development vitest",
24
+ "test:once": "NODE_ENV=development vitest --run",
24
25
  "semantic-release": "semantic-release",
25
26
  "image-upload-backend": "node ./src/examples/file-backend.js",
26
27
  "export-icons": "node ./scripts/export-figma-icons.mjs"