@lexical/mdast 0.0.0-bootstrap.0 → 0.47.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.
@@ -0,0 +1,635 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type {
10
+ CompiledMdast,
11
+ FromMarkdownExtension,
12
+ MdastExportHandler,
13
+ MdastExportRule,
14
+ MdastImportHandler,
15
+ MdastImportRule,
16
+ MicromarkExtension,
17
+ ToMarkdownExtension,
18
+ } from './types';
19
+ import type {ElementNode, LexicalNode} from 'lexical';
20
+ import type {Root, ThematicBreak} from 'mdast';
21
+
22
+ import {CodeNode} from '@lexical/code-core';
23
+ import {
24
+ $createHorizontalRuleNode,
25
+ $getExtensionOutput,
26
+ $isHorizontalRuleNode,
27
+ effect,
28
+ getExtensionDependencyFromEditor,
29
+ HorizontalRuleExtension,
30
+ namedSignals,
31
+ } from '@lexical/extension';
32
+ import {LinkNode} from '@lexical/link';
33
+ import {ListItemNode, ListNode} from '@lexical/list';
34
+ import {HeadingNode, QuoteNode} from '@lexical/rich-text';
35
+ import {
36
+ $getState,
37
+ $setState,
38
+ configExtension,
39
+ defineExtension,
40
+ safeCast,
41
+ shallowMergeConfig,
42
+ } from 'lexical';
43
+ import {
44
+ gfmAutolinkLiteralFromMarkdown,
45
+ gfmAutolinkLiteralToMarkdown,
46
+ } from 'mdast-util-gfm-autolink-literal';
47
+ import {
48
+ gfmStrikethroughFromMarkdown,
49
+ gfmStrikethroughToMarkdown,
50
+ } from 'mdast-util-gfm-strikethrough';
51
+ import {
52
+ gfmTaskListItemFromMarkdown,
53
+ gfmTaskListItemToMarkdown,
54
+ } from 'mdast-util-gfm-task-list-item';
55
+ import {gfmAutolinkLiteral} from 'micromark-extension-gfm-autolink-literal';
56
+ import {gfmStrikethrough} from 'micromark-extension-gfm-strikethrough';
57
+ import {gfmTaskListItem} from 'micromark-extension-gfm-task-list-item';
58
+
59
+ import {compileMdast} from './compile';
60
+ import {
61
+ $exportCode,
62
+ $exportHeading,
63
+ $exportLineBreak,
64
+ $exportLink,
65
+ $exportList,
66
+ $importBlockquote,
67
+ $importBreak,
68
+ $importCode,
69
+ $importEmphasis,
70
+ $importHeading,
71
+ $importLink,
72
+ $importLinkReference,
73
+ $importList,
74
+ $importListItem,
75
+ $importParagraph,
76
+ $importShadowRootBlockquote,
77
+ $importStrong,
78
+ exportParagraph,
79
+ exportQuote,
80
+ exportTab,
81
+ exportText,
82
+ importDefinition,
83
+ importDelete,
84
+ importHtml,
85
+ importInlineCode,
86
+ importText,
87
+ } from './handlers';
88
+ import {createMdastImport} from './MdastImport';
89
+ import {registerMarkdownShortcuts} from './MdastShortcuts';
90
+ import {hrMarkerState} from './state';
91
+
92
+ /**
93
+ * Configuration for the core {@link MdastImportExtension} registry. Feature
94
+ * extensions contribute to these arrays via `configExtension(MdastImportExtension,
95
+ * …)`; you rarely need to set them by hand. The shape mirrors
96
+ * `@lexical/html`'s `DOMImportExtension` config: raw contribution arrays that
97
+ * `mergeConfig` concatenates and `build` compiles.
98
+ * @experimental
99
+ */
100
+ export interface MdastConfig {
101
+ /** mdast `type` -> Lexical mapping rules used while importing. */
102
+ readonly importRules: readonly MdastImportRule[];
103
+ /** Lexical `getType()` -> mdast mapping rules used while exporting. */
104
+ readonly exportRules: readonly MdastExportRule[];
105
+ /** micromark syntax extensions (the tokenizer layer). */
106
+ readonly micromarkExtensions: readonly MicromarkExtension[];
107
+ /** `mdast-util-from-markdown` extensions (tokens -> mdast). */
108
+ readonly mdastExtensions: readonly FromMarkdownExtension[];
109
+ /** `mdast-util-to-markdown` extensions (mdast -> Markdown string). */
110
+ readonly toMarkdownExtensions: readonly ToMarkdownExtension[];
111
+ /**
112
+ * mdast inline `type`s that the streaming shortcuts may materialize when
113
+ * their closing delimiter is typed. Extensions that contribute a new inline
114
+ * construct add its type here (with a matching import rule) so shortcuts
115
+ * stay in lock-step with the parser.
116
+ */
117
+ readonly inlineShortcutTypes: readonly string[];
118
+ /**
119
+ * Characters that can close an inline construct; typing one triggers an
120
+ * inline re-scan. Extensions add their construct's closing character here
121
+ * (e.g. `'='` for `==highlight==`).
122
+ */
123
+ readonly inlineShortcutTriggers: readonly string[];
124
+ }
125
+
126
+ /**
127
+ * The runtime API exposed by {@link MdastImportExtension}. Obtain it inside a
128
+ * read/update with `$getExtensionOutput(MdastImportExtension)`, or use the
129
+ * {@link $convertFromMarkdownString} shorthand. Serialization lives in
130
+ * `MdastExportExtension` so import-only editors don't bundle the
131
+ * serializer (`mdast-util-to-markdown`).
132
+ * @experimental
133
+ */
134
+ export interface MdastImportExtensionOutput {
135
+ /**
136
+ * Parses `markdown` with micromark/mdast and replaces the contents of the
137
+ * editor root (or `node`). Must be called inside an `editor.update()`.
138
+ */
139
+ $convertFromMarkdownString(markdown: string, node?: ElementNode): void;
140
+ /**
141
+ * Imports an already-parsed mdast `Root` tree (e.g. produced or
142
+ * transformed by unified/remark tooling) and replaces the contents of the
143
+ * editor root (or `node`). Must be called inside an `editor.update()`.
144
+ * Source-based syntax preservation does not apply (there is no source
145
+ * text to recover literal markers from).
146
+ */
147
+ $convertFromMdast(tree: Root, node?: ElementNode): void;
148
+ /**
149
+ * Parses `markdown` and returns the resulting block-level nodes as a
150
+ * detached array, without modifying the document or the selection — e.g.
151
+ * for insertion at an arbitrary position via `selection.insertNodes()`.
152
+ * Must be called inside an `editor.update()`.
153
+ */
154
+ $generateNodesFromMarkdownString(markdown: string): LexicalNode[];
155
+ /**
156
+ * Walks an already-parsed mdast `Root` tree and returns the resulting
157
+ * block-level nodes as a detached array, without modifying the document
158
+ * or the selection. Must be called inside an `editor.update()`. As with
159
+ * {@link MdastImportExtensionOutput.$convertFromMdast}, source-based
160
+ * syntax preservation does not apply.
161
+ */
162
+ $generateNodesFromMdast(tree: Root): LexicalNode[];
163
+ /**
164
+ * The compiled registry assembled from every contributing extension.
165
+ *
166
+ * @internal consumed by {@link MdastShortcutsExtension}.
167
+ */
168
+ readonly registry: CompiledMdast;
169
+ }
170
+
171
+ // The baseline rules that need no node packages: paragraphs and inline text
172
+ // formatting (CommonMark handles these without any micromark extension).
173
+ const CORE_IMPORT_RULES: readonly MdastImportRule[] = [
174
+ {$import: $importParagraph, type: 'paragraph'},
175
+ {$import: importText, type: 'text'},
176
+ {$import: importHtml, type: 'html'},
177
+ {$import: importInlineCode, type: 'inlineCode'},
178
+ {$import: $importEmphasis, type: 'emphasis'},
179
+ {$import: $importStrong, type: 'strong'},
180
+ {$import: $importBreak, type: 'break'},
181
+ ];
182
+ const CORE_EXPORT_RULES: readonly MdastExportRule[] = [
183
+ {$export: exportParagraph, type: 'paragraph'},
184
+ {$export: exportText, type: 'text'},
185
+ {$export: $exportLineBreak, type: 'linebreak'},
186
+ {$export: exportTab, type: 'tab'},
187
+ ];
188
+
189
+ /**
190
+ * The core Markdown registry for `@lexical/mdast`, modeled on
191
+ * `@lexical/html`'s `DOMImportExtension`. It assembles the import/export rules
192
+ * and micromark/mdast extensions contributed by feature extensions into a
193
+ * compiled registry, and exposes Markdown import through its
194
+ * {@link MdastImportExtensionOutput}. Markdown export is provided separately by
195
+ * `MdastExportExtension`, so editors that never serialize back to Markdown
196
+ * don't bundle the serializer.
197
+ *
198
+ * You normally do not depend on this directly — depend on a feature extension
199
+ * (e.g. {@link MdastCommonMarkExtension}) which contributes its rules here and
200
+ * ships the nodes those rules need.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * import {$convertFromMarkdownString, MdastCommonMarkExtension}
205
+ * from '@lexical/mdast';
206
+ * import {buildEditorFromExtensions} from '@lexical/extension';
207
+ * import {defineExtension} from 'lexical';
208
+ *
209
+ * const editor = buildEditorFromExtensions(
210
+ * defineExtension({dependencies: [MdastCommonMarkExtension], name: '[root]'}),
211
+ * );
212
+ * editor.update(() => $convertFromMarkdownString('# Hi'));
213
+ * ```
214
+ * @experimental
215
+ */
216
+ export const MdastImportExtension = /* @__PURE__ */ defineExtension<
217
+ MdastConfig,
218
+ '@lexical/mdast/Import',
219
+ MdastImportExtensionOutput,
220
+ void
221
+ >({
222
+ build(editor, config): MdastImportExtensionOutput {
223
+ const registry = compileMdast(config);
224
+ const {
225
+ $generateNodesFromMarkdown,
226
+ $generateNodesFromMdast: $generateNodesFromTree,
227
+ $importMarkdown,
228
+ $importMdast,
229
+ } = createMdastImport(registry);
230
+ return {
231
+ $convertFromMarkdownString: $importMarkdown,
232
+ $convertFromMdast: $importMdast,
233
+ $generateNodesFromMarkdownString: $generateNodesFromMarkdown,
234
+ $generateNodesFromMdast: $generateNodesFromTree,
235
+ registry,
236
+ };
237
+ },
238
+ config: /* @__PURE__ */ safeCast<MdastConfig>({
239
+ exportRules: CORE_EXPORT_RULES,
240
+ importRules: CORE_IMPORT_RULES,
241
+ // Core CommonMark inline formatting; feature extensions contribute
242
+ // their own types/triggers (links add 'link'/')', strikethrough adds
243
+ // 'delete'/'~').
244
+ inlineShortcutTriggers: ['*', '_', '`'],
245
+ inlineShortcutTypes: ['emphasis', 'inlineCode', 'strong'],
246
+ mdastExtensions: [],
247
+ micromarkExtensions: [],
248
+ toMarkdownExtensions: [],
249
+ }),
250
+ mergeConfig(config, partial) {
251
+ // Prepend contributed rules so extensions merged later (closer to the
252
+ // editor root) take priority, matching DOMImportExtension's convention.
253
+ // Every key is set explicitly so an explicitly-undefined key in `partial`
254
+ // (allowed by Partial<MdastConfig>) can never clobber the merged arrays.
255
+ function mergeArray<T>(
256
+ contributed: readonly T[] | undefined,
257
+ existing: readonly T[],
258
+ ): readonly T[] {
259
+ return contributed ? [...contributed, ...existing] : existing;
260
+ }
261
+ return shallowMergeConfig(config, {
262
+ exportRules: mergeArray(partial.exportRules, config.exportRules),
263
+ importRules: mergeArray(partial.importRules, config.importRules),
264
+ inlineShortcutTriggers: mergeArray(
265
+ partial.inlineShortcutTriggers,
266
+ config.inlineShortcutTriggers,
267
+ ),
268
+ inlineShortcutTypes: mergeArray(
269
+ partial.inlineShortcutTypes,
270
+ config.inlineShortcutTypes,
271
+ ),
272
+ mdastExtensions: mergeArray(
273
+ partial.mdastExtensions,
274
+ config.mdastExtensions,
275
+ ),
276
+ micromarkExtensions: mergeArray(
277
+ partial.micromarkExtensions,
278
+ config.micromarkExtensions,
279
+ ),
280
+ toMarkdownExtensions: mergeArray(
281
+ partial.toMarkdownExtensions,
282
+ config.toMarkdownExtensions,
283
+ ),
284
+ });
285
+ },
286
+ name: '@lexical/mdast/Import',
287
+ });
288
+
289
+ /**
290
+ * ATX (`# …`) and setext headings, shipping {@link HeadingNode}.
291
+ * @experimental
292
+ */
293
+ export const MdastHeadingExtension = /* @__PURE__ */ defineExtension({
294
+ dependencies: [
295
+ /* @__PURE__ */ configExtension(MdastImportExtension, {
296
+ exportRules: [{$export: $exportHeading, type: 'heading'}],
297
+ importRules: [{$import: $importHeading, type: 'heading'}],
298
+ }),
299
+ ],
300
+ name: '@lexical/mdast/Heading',
301
+ nodes: [HeadingNode],
302
+ });
303
+
304
+ /**
305
+ * Block quotes (`> …`), shipping {@link QuoteNode}. For blockquotes that hold
306
+ * block-level children (nested lists, code, quotes) with full fidelity, add
307
+ * {@link MdastShadowRootQuoteExtension}.
308
+ * @experimental
309
+ */
310
+ export const MdastBlockquoteExtension = /* @__PURE__ */ defineExtension({
311
+ dependencies: [
312
+ /* @__PURE__ */ configExtension(MdastImportExtension, {
313
+ exportRules: [{$export: exportQuote, type: 'quote'}],
314
+ importRules: [{$import: $importBlockquote, type: 'blockquote'}],
315
+ }),
316
+ ],
317
+ name: '@lexical/mdast/Blockquote',
318
+ nodes: [QuoteNode],
319
+ });
320
+
321
+ /**
322
+ * Convenience bundle of {@link MdastHeadingExtension} and
323
+ * {@link MdastBlockquoteExtension} — the constructs backed by
324
+ * `@lexical/rich-text` nodes.
325
+ * @experimental
326
+ */
327
+ export const MdastRichTextExtension = /* @__PURE__ */ defineExtension({
328
+ dependencies: [MdastHeadingExtension, MdastBlockquoteExtension],
329
+ name: '@lexical/mdast/RichText',
330
+ });
331
+
332
+ /**
333
+ * Ordered and unordered lists, shipping {@link ListNode} and
334
+ * {@link ListItemNode}. For GFM task lists (`- [x] …`) add
335
+ * {@link MdastTaskListExtension}.
336
+ * @experimental
337
+ */
338
+ export const MdastListExtension = /* @__PURE__ */ defineExtension({
339
+ dependencies: [
340
+ /* @__PURE__ */ configExtension(MdastImportExtension, {
341
+ exportRules: [{$export: $exportList, type: 'list'}],
342
+ importRules: [
343
+ {$import: $importList, type: 'list'},
344
+ {$import: $importListItem, type: 'listItem'},
345
+ ],
346
+ }),
347
+ ],
348
+ name: '@lexical/mdast/List',
349
+ nodes: [ListNode, ListItemNode],
350
+ });
351
+
352
+ /**
353
+ * Opt-in: GFM task lists (`- [x] done`), layered on
354
+ * {@link MdastListExtension}. Contributes the `gfmTaskListItem` grammar; the
355
+ * list import/export handlers already understand `checked`, and the typing
356
+ * shortcut (`[ ] ` / `[x] ` in a list item) is enabled by the grammar's
357
+ * presence in the registry.
358
+ * @experimental
359
+ */
360
+ export const MdastTaskListExtension = /* @__PURE__ */ defineExtension({
361
+ dependencies: [
362
+ MdastListExtension,
363
+ /* @__PURE__ */ configExtension(MdastImportExtension, {
364
+ mdastExtensions: [/* @__PURE__ */ gfmTaskListItemFromMarkdown()],
365
+ micromarkExtensions: [/* @__PURE__ */ gfmTaskListItem()],
366
+ toMarkdownExtensions: [/* @__PURE__ */ gfmTaskListItemToMarkdown()],
367
+ }),
368
+ ],
369
+ name: '@lexical/mdast/TaskList',
370
+ });
371
+
372
+ /**
373
+ * Fenced and indented code blocks, shipping {@link CodeNode}.
374
+ * @experimental
375
+ */
376
+ export const MdastCodeExtension = /* @__PURE__ */ defineExtension({
377
+ dependencies: [
378
+ /* @__PURE__ */ configExtension(MdastImportExtension, {
379
+ exportRules: [{$export: $exportCode, type: 'code'}],
380
+ importRules: [{$import: $importCode, type: 'code'}],
381
+ }),
382
+ ],
383
+ name: '@lexical/mdast/Code',
384
+ nodes: [CodeNode],
385
+ });
386
+
387
+ /**
388
+ * Inline links, CommonMark autolinks (`<https://…>`), and CommonMark
389
+ * reference links (`[text][id]` resolved against `[id]: url` definitions),
390
+ * shipping {@link LinkNode}. Reference links are resolved to their target on
391
+ * import and serialize back as inline links. For GFM *literal* autolinks
392
+ * (bare `https://…` in prose) add {@link MdastAutolinkLiteralExtension}.
393
+ * @experimental
394
+ */
395
+ export const MdastLinkExtension = /* @__PURE__ */ defineExtension({
396
+ dependencies: [
397
+ /* @__PURE__ */ configExtension(MdastImportExtension, {
398
+ exportRules: [{$export: $exportLink, type: 'link'}],
399
+ importRules: [
400
+ {$import: $importLink, type: 'link'},
401
+ {$import: $importLinkReference, type: 'linkReference'},
402
+ {$import: importDefinition, type: 'definition'},
403
+ ],
404
+ inlineShortcutTriggers: [')'],
405
+ inlineShortcutTypes: ['link'],
406
+ }),
407
+ ],
408
+ name: '@lexical/mdast/Link',
409
+ nodes: [LinkNode],
410
+ });
411
+
412
+ /**
413
+ * Opt-in: GFM literal autolinks — bare `https://…` / `www.…` URLs and email
414
+ * addresses in prose become links, the way GitHub renders them. This is a GFM
415
+ * extension rather than CommonMark, so it is not part of
416
+ * {@link MdastCommonMarkExtension}; add it alongside to opt in:
417
+ * ```ts
418
+ * dependencies: [MdastCommonMarkExtension, MdastAutolinkLiteralExtension]
419
+ * ```
420
+ * @experimental
421
+ */
422
+ export const MdastAutolinkLiteralExtension = /* @__PURE__ */ defineExtension({
423
+ dependencies: [
424
+ MdastLinkExtension,
425
+ /* @__PURE__ */ configExtension(MdastImportExtension, {
426
+ mdastExtensions: [/* @__PURE__ */ gfmAutolinkLiteralFromMarkdown()],
427
+ micromarkExtensions: [/* @__PURE__ */ gfmAutolinkLiteral()],
428
+ toMarkdownExtensions: [/* @__PURE__ */ gfmAutolinkLiteralToMarkdown()],
429
+ }),
430
+ ],
431
+ name: '@lexical/mdast/AutolinkLiteral',
432
+ });
433
+
434
+ /**
435
+ * Opt-in: import Markdown blockquotes as *shadow root* {@link QuoteNode}s
436
+ * (`$createQuoteNode({shadowRoot: true})`), which hold block-level children
437
+ * like a table cell. Structured blockquotes — multiple paragraphs, nested
438
+ * lists, code blocks, nested quotes — then round-trip with full fidelity
439
+ * instead of being reassembled from inline content.
440
+ *
441
+ * Not part of {@link MdastCommonMarkExtension}; add it alongside to opt in:
442
+ * ```ts
443
+ * dependencies: [MdastCommonMarkExtension, MdastShadowRootQuoteExtension]
444
+ * ```
445
+ * The quote *export* handler supports both forms per node, so legacy quotes
446
+ * (e.g. created by the `> ` shortcut) and shadow root quotes can coexist.
447
+ * @experimental
448
+ */
449
+ export const MdastShadowRootQuoteExtension = /* @__PURE__ */ defineExtension({
450
+ dependencies: [
451
+ MdastBlockquoteExtension,
452
+ // Declared after (and depending on) MdastBlockquoteExtension so this
453
+ // blockquote rule merges later and takes priority over the default.
454
+ /* @__PURE__ */ configExtension(MdastImportExtension, {
455
+ importRules: [{$import: $importShadowRootBlockquote, type: 'blockquote'}],
456
+ }),
457
+ ],
458
+ name: '@lexical/mdast/ShadowRootQuote',
459
+ });
460
+
461
+ const $importThematicBreak: MdastImportHandler<ThematicBreak> = (node, ctx) => {
462
+ const hr = $createHorizontalRuleNode();
463
+ // Preserve the marker character (`---` vs `***` vs `___`).
464
+ if (ctx.source && node.position && node.position.start.offset != null) {
465
+ const marker = ctx.source
466
+ .slice(node.position.start.offset, node.position.start.offset + 4)
467
+ .trimStart()[0];
468
+ if (marker === '-' || marker === '*' || marker === '_') {
469
+ $setState(hr, hrMarkerState, marker);
470
+ }
471
+ }
472
+ return hr;
473
+ };
474
+
475
+ const $exportThematicBreak: MdastExportHandler = node => {
476
+ if (!$isHorizontalRuleNode(node)) {
477
+ return null;
478
+ }
479
+ const rule: ThematicBreak = {type: 'thematicBreak'};
480
+ const marker = $getState(node, hrMarkerState);
481
+ if (marker) {
482
+ rule.data = {mdastRule: marker};
483
+ }
484
+ return rule;
485
+ };
486
+
487
+ /**
488
+ * Thematic breaks (`---`, `***`, `___`), mapped to
489
+ * {@link HorizontalRuleExtension}'s `HorizontalRuleNode`. The original marker
490
+ * character is preserved on round-trip.
491
+ * @experimental
492
+ */
493
+ export const MdastHorizontalRuleExtension = /* @__PURE__ */ defineExtension({
494
+ dependencies: [
495
+ HorizontalRuleExtension,
496
+ /* @__PURE__ */ configExtension(MdastImportExtension, {
497
+ exportRules: [{$export: $exportThematicBreak, type: 'horizontalrule'}],
498
+ importRules: [{$import: $importThematicBreak, type: 'thematicBreak'}],
499
+ }),
500
+ ],
501
+ name: '@lexical/mdast/HorizontalRule',
502
+ });
503
+
504
+ /**
505
+ * GFM `~~strikethrough~~`, mapped to the Lexical `strikethrough` text format.
506
+ * Needs no extra nodes (the core text handlers carry the format bit).
507
+ * @experimental
508
+ */
509
+ export const MdastStrikethroughExtension = /* @__PURE__ */ defineExtension({
510
+ dependencies: [
511
+ /* @__PURE__ */ configExtension(MdastImportExtension, {
512
+ importRules: [{$import: importDelete, type: 'delete'}],
513
+ inlineShortcutTriggers: ['~'],
514
+ inlineShortcutTypes: ['delete'],
515
+ mdastExtensions: [/* @__PURE__ */ gfmStrikethroughFromMarkdown()],
516
+ micromarkExtensions: [/* @__PURE__ */ gfmStrikethrough()],
517
+ toMarkdownExtensions: [/* @__PURE__ */ gfmStrikethroughToMarkdown()],
518
+ }),
519
+ ],
520
+ name: '@lexical/mdast/Strikethrough',
521
+ });
522
+
523
+ /**
524
+ * Convenience bundle of every CommonMark construct: headings, block quotes,
525
+ * lists, code blocks, links, and thematic breaks. GFM features
526
+ * (strikethrough, task lists, literal autolinks, tables) are bundled
527
+ * separately as `MdastGfmExtension`, and `MdastExportExtension` (or the
528
+ * `MdastExtension` bundle) adds serialization back to Markdown.
529
+ * @experimental
530
+ */
531
+ export const MdastCommonMarkExtension = /* @__PURE__ */ defineExtension({
532
+ dependencies: [
533
+ MdastRichTextExtension,
534
+ MdastListExtension,
535
+ MdastCodeExtension,
536
+ MdastLinkExtension,
537
+ MdastHorizontalRuleExtension,
538
+ ],
539
+ name: '@lexical/mdast/CommonMark',
540
+ });
541
+
542
+ export interface MdastShortcutsConfig {
543
+ /** Disable the streaming shortcuts without removing the extension. */
544
+ disabled: boolean;
545
+ }
546
+
547
+ /**
548
+ * Streaming Markdown shortcuts (block markers convert on space, fenced code on
549
+ * Enter, inline constructs on their closing delimiter). Each keystroke is fed
550
+ * back through micromark, so shortcut recognition uses the same grammar and
551
+ * the same enabled extensions as import: shortcuts exist for exactly the
552
+ * feature extensions in the editor and no others. Combine with
553
+ * {@link MdastCommonMarkExtension} (and `MdastGfmExtension`) — this extension
554
+ * only wires up the behavior, it does not pull in any grammar of its own.
555
+ * @experimental
556
+ */
557
+ export const MdastShortcutsExtension = /* @__PURE__ */ defineExtension({
558
+ build: (editor, config) => namedSignals(config),
559
+ config: /* @__PURE__ */ safeCast<MdastShortcutsConfig>({disabled: false}),
560
+ dependencies: [MdastImportExtension],
561
+ name: '@lexical/mdast/Shortcuts',
562
+ register: (editor, config, state) => {
563
+ const {disabled} = state.getOutput();
564
+ return effect(() => {
565
+ if (disabled.value) {
566
+ return undefined;
567
+ }
568
+ const {registry} = getExtensionDependencyFromEditor(
569
+ editor,
570
+ MdastImportExtension,
571
+ ).output;
572
+ return registerMarkdownShortcuts(editor, registry);
573
+ });
574
+ },
575
+ });
576
+
577
+ /**
578
+ * Shorthand for `$getExtensionOutput(MdastImportExtension).$convertFromMarkdownString`.
579
+ * Must be called inside an `editor.update()`. Throws if the editor was not
580
+ * built with {@link MdastImportExtension} (or an extension that depends on it).
581
+ * @experimental
582
+ */
583
+ export function $convertFromMarkdownString(
584
+ markdown: string,
585
+ node?: ElementNode,
586
+ ): void {
587
+ $getExtensionOutput(MdastImportExtension).$convertFromMarkdownString(
588
+ markdown,
589
+ node,
590
+ );
591
+ }
592
+
593
+ /**
594
+ * Shorthand for `$getExtensionOutput(MdastImportExtension).$convertFromMdast`.
595
+ * Must be called inside an `editor.update()`. Throws if the editor was not
596
+ * built with {@link MdastImportExtension} (or an extension that depends on
597
+ * it).
598
+ * @experimental
599
+ */
600
+ export function $convertFromMdast(tree: Root, node?: ElementNode): void {
601
+ $getExtensionOutput(MdastImportExtension).$convertFromMdast(tree, node);
602
+ }
603
+
604
+ /**
605
+ * Shorthand for
606
+ * `$getExtensionOutput(MdastImportExtension).$generateNodesFromMarkdownString`.
607
+ * Parses `markdown` and returns the resulting block-level nodes as a
608
+ * detached array, without modifying the document or the selection. Must be
609
+ * called inside an `editor.update()`. Throws if the editor was not built
610
+ * with {@link MdastImportExtension} (or an extension that depends on it).
611
+ * @experimental
612
+ */
613
+ export function $generateNodesFromMarkdownString(
614
+ markdown: string,
615
+ ): LexicalNode[] {
616
+ return $getExtensionOutput(
617
+ MdastImportExtension,
618
+ ).$generateNodesFromMarkdownString(markdown);
619
+ }
620
+
621
+ /**
622
+ * Shorthand for
623
+ * `$getExtensionOutput(MdastImportExtension).$generateNodesFromMdast`.
624
+ * Walks an already-parsed mdast `Root` tree and returns the resulting
625
+ * block-level nodes as a detached array, without modifying the document or
626
+ * the selection. Must be called inside an `editor.update()`. Throws if the
627
+ * editor was not built with {@link MdastImportExtension} (or an extension
628
+ * that depends on it).
629
+ * @experimental
630
+ */
631
+ export function $generateNodesFromMdast(tree: Root): LexicalNode[] {
632
+ return $getExtensionOutput(MdastImportExtension).$generateNodesFromMdast(
633
+ tree,
634
+ );
635
+ }