@mdxeditor/editor 3.49.3 → 3.51.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
@@ -1122,9 +1122,10 @@ export declare interface EditingImageDialogState {
1122
1122
  export declare interface EditLinkDialog {
1123
1123
  type: 'edit';
1124
1124
  initialUrl: string;
1125
- hasInitialText: boolean;
1126
1125
  url: string;
1127
1126
  title: string;
1127
+ text: string;
1128
+ withAnchorText: boolean;
1128
1129
  linkNodeKey: string;
1129
1130
  rectangle: RectData;
1130
1131
  }
@@ -1169,6 +1170,11 @@ export declare const editorSearchTextNodeIndex$: NodeRef<TextNodeIndex>;
1169
1170
  */
1170
1171
  export declare type EditorSubscription = (activeEditor: LexicalEditor) => () => void;
1171
1172
 
1173
+ /**
1174
+ * @group Core
1175
+ */
1176
+ export declare const editorWrapperElementRef$: NodeRef<default_2.RefObject<HTMLDivElement> | null>;
1177
+
1172
1178
  /* Excluded from this release type: editorWrappers$ */
1173
1179
 
1174
1180
  export declare const EmptyTextNodeIndex: TextNodeIndex;
@@ -1314,6 +1320,15 @@ export declare interface GenericJsxEditorProps extends JsxEditorProps {
1314
1320
  */
1315
1321
  export declare function getSelectedNode(selection: RangeSelection): TextNode | ElementNode | null;
1316
1322
 
1323
+ /**
1324
+ * Gets the markdown representation of the current selection in the Lexical editor.
1325
+ * Returns an empty string if there is no selection or if the selection is collapsed.
1326
+ * Converts selected nodes to markdown by recursively processing them and preserving formatting.
1327
+ * Note: Selects entire nodes, not partial selections within nodes.
1328
+ * @group Utils
1329
+ */
1330
+ export declare function getSelectionAsMarkdown(editor: LexicalEditor, _exportParams: Omit<ExportMarkdownFromLexicalOptions, 'root'>): string;
1331
+
1317
1332
  /**
1318
1333
  * Gets the coordinates of the selection in the Lexical editor.
1319
1334
  * @group Utils
@@ -2226,6 +2241,15 @@ export declare interface MDXEditorMethods {
2226
2241
  defaultSelection?: 'rootStart' | 'rootEnd';
2227
2242
  preventScroll?: boolean;
2228
2243
  }) => void;
2244
+ /**
2245
+ * gets the underlying Lexical contentEditable HTML content
2246
+ */
2247
+ getContentEditableHTML: () => string;
2248
+ /**
2249
+ * Gets the markdown representation of the current selection.
2250
+ * Returns an empty string if there is no selection, if selection is collapsed, or if editor is in source/diff mode.
2251
+ */
2252
+ getSelectionMarkdown: () => string;
2229
2253
  }
2230
2254
 
2231
2255
  /**
@@ -2843,7 +2867,7 @@ export declare type SerializedTableNode = Spread<{
2843
2867
  */
2844
2868
  export declare const setMarkdown$: NodeRef<string>;
2845
2869
 
2846
- /* Excluded from this release type: showLinkTitleField$ */
2870
+ export declare const showLinkTitleField$: NodeRef<boolean>;
2847
2871
 
2848
2872
  /**
2849
2873
  * A component that displays the focused live code block's name.
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$,
@@ -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$,
@@ -13,6 +13,7 @@ createCommand();
13
13
  function LinkEditForm({
14
14
  url,
15
15
  title,
16
+ text,
16
17
  onSubmit,
17
18
  onCancel,
18
19
  linkAutocompleteSuggestions,
@@ -29,8 +30,7 @@ function LinkEditForm({
29
30
  values: {
30
31
  url,
31
32
  title,
32
- text: ""
33
- // text is only for freshly created links with no content, it always starts empty
33
+ text
34
34
  }
35
35
  });
36
36
  const t = useTranslation();
@@ -46,6 +46,12 @@ function LinkEditForm({
46
46
  e.stopPropagation();
47
47
  onCancel();
48
48
  },
49
+ onKeyDown: (e) => {
50
+ if (e.key === "Escape") {
51
+ e.stopPropagation();
52
+ onCancel();
53
+ }
54
+ },
49
55
  className: classNames(styles.multiFieldForm, styles.linkDialogEditForm)
50
56
  },
51
57
  /* @__PURE__ */ React__default.createElement("div", { className: styles.formField }, /* @__PURE__ */ React__default.createElement("label", { htmlFor: "link-url" }, t("createLink.url", "URL")), /* @__PURE__ */ React__default.createElement(
@@ -153,11 +159,12 @@ const LinkDialog = () => {
153
159
  {
154
160
  url: linkDialogState.url,
155
161
  title: linkDialogState.title,
162
+ text: linkDialogState.text,
156
163
  onSubmit: updateLink,
157
164
  onCancel: cancelLinkEdit.bind(null),
158
165
  linkAutocompleteSuggestions,
159
166
  showLinkTitleField,
160
- showAnchorTextField: !linkDialogState.hasInitialText
167
+ showAnchorTextField: linkDialogState.withAnchorText
161
168
  }
162
169
  ),
163
170
  linkDialogState.type === "preview" && /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(
@@ -1,6 +1,6 @@
1
- import { $createLinkNode, TOGGLE_LINK_COMMAND, $isLinkNode, LinkNode } from "@lexical/link";
1
+ import { $isLinkNode, $createLinkNode, TOGGLE_LINK_COMMAND, LinkNode } from "@lexical/link";
2
2
  import { Signal, Cell, Action, withLatestFrom, map, filter } from "@mdxeditor/gurx";
3
- import { KEY_ESCAPE_COMMAND, COMMAND_PRIORITY_LOW, KEY_MODIFIER_COMMAND, $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, $createTextNode, $insertNodes, $getNearestNodeFromDOMNode } from "lexical";
3
+ import { KEY_ESCAPE_COMMAND, COMMAND_PRIORITY_LOW, KEY_MODIFIER_COMMAND, $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, $getNodeByKey, $createTextNode, $insertNodes, $isTextNode, $getNearestNodeFromDOMNode } from "lexical";
4
4
  import { realmPlugin } from "../../RealmWithPlugins.js";
5
5
  import { IS_APPLE } from "../../utils/detectMac.js";
6
6
  import { getSelectionRectangle, getSelectedNode } from "../../utils/lexicalHelpers.js";
@@ -64,28 +64,29 @@ const linkDialogState$ = Cell({ type: "inactive" }, (r) => {
64
64
  COMMAND_PRIORITY_HIGH
65
65
  );
66
66
  });
67
- r.link(
68
- r.pipe(
69
- switchFromPreviewToLinkEdit$,
70
- withLatestFrom(linkDialogState$),
71
- map(([, state]) => {
72
- if (state.type === "preview") {
73
- return {
67
+ r.sub(r.pipe(switchFromPreviewToLinkEdit$, withLatestFrom(linkDialogState$, activeEditor$)), ([, state, editor]) => {
68
+ if (state.type === "preview") {
69
+ setTimeout(() => {
70
+ editor == null ? void 0 : editor.getEditorState().read(() => {
71
+ const node = $getNodeByKey(state.linkNodeKey);
72
+ const withAnchorText = $isLinkNode(node) ? node.getTextContent().length > 0 && node.getChildrenSize() <= 1 : false;
73
+ const text = withAnchorText && node ? node.getTextContent() : "";
74
+ r.pub(linkDialogState$, {
74
75
  type: "edit",
75
76
  initialUrl: state.url,
76
- hasInitialText: true,
77
77
  url: state.url,
78
78
  title: state.title,
79
+ text,
80
+ withAnchorText,
79
81
  linkNodeKey: state.linkNodeKey,
80
82
  rectangle: state.rectangle
81
- };
82
- } else {
83
- throw new Error("Cannot switch to edit mode when not in preview mode");
84
- }
85
- })
86
- ),
87
- linkDialogState$
88
- );
83
+ });
84
+ });
85
+ });
86
+ } else {
87
+ throw new Error("Cannot switch to edit mode when not in preview mode");
88
+ }
89
+ });
89
90
  r.sub(r.pipe(updateLink$, withLatestFrom(activeEditor$, linkDialogState$, currentSelection$)), ([payload, editor, state, selection]) => {
90
91
  var _a, _b, _c;
91
92
  const text = ((_a = payload.text) == null ? void 0 : _a.trim()) ?? "";
@@ -105,11 +106,15 @@ const linkDialogState$ = Cell({ type: "inactive" }, (r) => {
105
106
  } else {
106
107
  linkNode.setURL(url);
107
108
  linkNode.setTitle(title);
109
+ updateLinkText(linkNode.getFirstChild(), text);
108
110
  }
109
111
  },
110
112
  { discrete: true }
111
113
  );
112
114
  } else {
115
+ editor == null ? void 0 : editor.update(() => {
116
+ updateLinkText(selection == null ? void 0 : selection.anchor.getNode(), text);
117
+ });
113
118
  editor == null ? void 0 : editor.dispatchCommand(TOGGLE_LINK_COMMAND, { url, title });
114
119
  }
115
120
  r.pub(linkDialogState$, {
@@ -204,29 +209,24 @@ const openLinkEditDialog$ = Action((r) => {
204
209
  editor == null ? void 0 : editor.focus(() => {
205
210
  setTimeout(() => {
206
211
  editor.getEditorState().read(() => {
207
- const node = getLinkNodeInSelection(selection);
212
+ const linkNode = getLinkNodeInSelection(selection);
208
213
  const rectangle = getSelectionRectangle(editor);
209
- if (node) {
210
- r.pub(linkDialogState$, {
211
- type: "edit",
212
- initialUrl: node.getURL(),
213
- hasInitialText: true,
214
- url: node.getURL(),
215
- title: node.getTitle() ?? "",
216
- linkNodeKey: node.getKey(),
217
- rectangle
218
- });
219
- } else {
220
- r.pub(linkDialogState$, {
221
- type: "edit",
222
- initialUrl: "",
223
- hasInitialText: !(selection == null ? void 0 : selection.isCollapsed()),
224
- title: "",
225
- url: "",
226
- linkNodeKey: "",
227
- rectangle
228
- });
229
- }
214
+ const initialUrl = (linkNode == null ? void 0 : linkNode.getURL()) ?? "";
215
+ const url = (linkNode == null ? void 0 : linkNode.getURL()) ?? "";
216
+ const title = (linkNode == null ? void 0 : linkNode.getTitle()) ?? "";
217
+ const linkNodeKey = (linkNode == null ? void 0 : linkNode.getKey()) ?? "";
218
+ const withAnchorText = linkNode ? linkNode.getTextContent().length > 0 && linkNode.getChildrenSize() <= 1 : Boolean(selection == null ? void 0 : selection.isCollapsed());
219
+ const text = withAnchorText && linkNode ? linkNode.getTextContent() : "";
220
+ r.pub(linkDialogState$, {
221
+ type: "edit",
222
+ initialUrl,
223
+ url,
224
+ title,
225
+ text,
226
+ withAnchorText,
227
+ linkNodeKey,
228
+ rectangle
229
+ });
230
230
  });
231
231
  });
232
232
  });
@@ -262,6 +262,12 @@ const onReadOnlyClickLinkCallback$ = Cell(null, (r) => {
262
262
  });
263
263
  });
264
264
  });
265
+ function updateLinkText(node, text) {
266
+ if ($isTextNode(node) && text) {
267
+ node.setTextContent(text);
268
+ node.selectStart();
269
+ }
270
+ }
265
271
  const showLinkTitleField$ = Cell(true);
266
272
  const linkDialogPlugin = realmPlugin({
267
273
  init(r, params) {
@@ -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.3",
14
+ "version": "3.51.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"
@@ -145,7 +146,7 @@
145
146
  "prettier": "^3.1.1",
146
147
  "react": "^19.1.0",
147
148
  "react-dom": "^19.1.0",
148
- "semantic-release": "^21.0.5",
149
+ "semantic-release": "^25.0.2",
149
150
  "svgo": "^3.3.2",
150
151
  "terser": "^5.26.0",
151
152
  "typedoc": "^0.25.13",
@@ -161,7 +162,8 @@
161
162
  "vitest": "^1.1.1"
162
163
  },
163
164
  "publishConfig": {
164
- "access": "public"
165
+ "access": "public",
166
+ "provenance": true
165
167
  },
166
168
  "engines": {
167
169
  "node": ">=16"