@blue-patterns/richtext 0.0.0-stage → 0.7.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/LICENSE +22 -0
  3. package/README.md +88 -2
  4. package/astryx.integration.mjs +11 -0
  5. package/blocks/RichTextEditorShowcase.template.mjs +13 -0
  6. package/blocks/RichTextEditorShowcase.tsx +19 -0
  7. package/dist/RichTextEditor.d.ts +282 -0
  8. package/dist/RichTextEditor.d.ts.map +1 -0
  9. package/dist/RichTextEditor.js +657 -0
  10. package/dist/RichTextEditorAutoLinkPlugin.d.ts +83 -0
  11. package/dist/RichTextEditorAutoLinkPlugin.d.ts.map +1 -0
  12. package/dist/RichTextEditorAutoLinkPlugin.js +99 -0
  13. package/dist/RichTextEditorToolbar.d.ts +186 -0
  14. package/dist/RichTextEditorToolbar.d.ts.map +1 -0
  15. package/dist/RichTextEditorToolbar.js +688 -0
  16. package/dist/RichTextView.d.ts +92 -0
  17. package/dist/RichTextView.d.ts.map +1 -0
  18. package/dist/RichTextView.js +267 -0
  19. package/dist/codeLanguages.d.ts +14 -0
  20. package/dist/codeLanguages.d.ts.map +1 -0
  21. package/dist/codeLanguages.js +34 -0
  22. package/dist/codeLineTokens.d.ts +53 -0
  23. package/dist/codeLineTokens.d.ts.map +1 -0
  24. package/dist/codeLineTokens.js +188 -0
  25. package/dist/editorNodes.d.ts +8 -0
  26. package/dist/editorNodes.d.ts.map +1 -0
  27. package/dist/editorNodes.js +30 -0
  28. package/dist/editorTheme.d.ts +7 -0
  29. package/dist/editorTheme.d.ts.map +1 -0
  30. package/dist/editorTheme.js +328 -0
  31. package/dist/index.d.ts +31 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +28 -0
  34. package/dist/internal/CodeBackspacePlugin.d.ts +2 -0
  35. package/dist/internal/CodeBackspacePlugin.d.ts.map +1 -0
  36. package/dist/internal/CodeBackspacePlugin.js +70 -0
  37. package/dist/internal/CodeBlockHeaderPlugin.d.ts +11 -0
  38. package/dist/internal/CodeBlockHeaderPlugin.d.ts.map +1 -0
  39. package/dist/internal/CodeBlockHeaderPlugin.js +204 -0
  40. package/dist/internal/CodePastePlugin.d.ts +5 -0
  41. package/dist/internal/CodePastePlugin.d.ts.map +1 -0
  42. package/dist/internal/CodePastePlugin.js +104 -0
  43. package/dist/internal/CodeSyntaxPlugin.d.ts +8 -0
  44. package/dist/internal/CodeSyntaxPlugin.d.ts.map +1 -0
  45. package/dist/internal/CodeSyntaxPlugin.js +397 -0
  46. package/dist/internal/CodeTabPlugin.d.ts +2 -0
  47. package/dist/internal/CodeTabPlugin.d.ts.map +1 -0
  48. package/dist/internal/CodeTabPlugin.js +256 -0
  49. package/dist/internal/DisabledLinkGuardPlugin.d.ts +4 -0
  50. package/dist/internal/DisabledLinkGuardPlugin.d.ts.map +1 -0
  51. package/dist/internal/DisabledLinkGuardPlugin.js +93 -0
  52. package/dist/internal/ImageSourceCaretPlugin.d.ts +3 -0
  53. package/dist/internal/ImageSourceCaretPlugin.d.ts.map +1 -0
  54. package/dist/internal/ImageSourceCaretPlugin.js +105 -0
  55. package/dist/internal/ListItemShortcutsPlugin.d.ts +6 -0
  56. package/dist/internal/ListItemShortcutsPlugin.d.ts.map +1 -0
  57. package/dist/internal/ListItemShortcutsPlugin.js +314 -0
  58. package/dist/internal/MarkdownExtensionsPlugin.d.ts +17 -0
  59. package/dist/internal/MarkdownExtensionsPlugin.d.ts.map +1 -0
  60. package/dist/internal/MarkdownExtensionsPlugin.js +185 -0
  61. package/dist/internal/SelectAllScopePlugin.d.ts +2 -0
  62. package/dist/internal/SelectAllScopePlugin.d.ts.map +1 -0
  63. package/dist/internal/SelectAllScopePlugin.js +67 -0
  64. package/dist/internal/TableColumnFloorPlugin.d.ts +12 -0
  65. package/dist/internal/TableColumnFloorPlugin.d.ts.map +1 -0
  66. package/dist/internal/TableColumnFloorPlugin.js +84 -0
  67. package/dist/internal/TablePastePlugin.d.ts +9 -0
  68. package/dist/internal/TablePastePlugin.d.ts.map +1 -0
  69. package/dist/internal/TablePastePlugin.js +183 -0
  70. package/dist/internal/TableScrollRegionPlugin.d.ts +9 -0
  71. package/dist/internal/TableScrollRegionPlugin.d.ts.map +1 -0
  72. package/dist/internal/TableScrollRegionPlugin.js +95 -0
  73. package/dist/internal/TaskCheckboxPlugin.d.ts +7 -0
  74. package/dist/internal/TaskCheckboxPlugin.d.ts.map +1 -0
  75. package/dist/internal/TaskCheckboxPlugin.js +193 -0
  76. package/dist/internal/ToolbarLinkPopover.d.ts +74 -0
  77. package/dist/internal/ToolbarLinkPopover.d.ts.map +1 -0
  78. package/dist/internal/ToolbarLinkPopover.js +292 -0
  79. package/dist/linkEditing.d.ts +6 -0
  80. package/dist/linkEditing.d.ts.map +1 -0
  81. package/dist/linkEditing.js +20 -0
  82. package/dist/linkForm.d.ts +28 -0
  83. package/dist/linkForm.d.ts.map +1 -0
  84. package/dist/linkForm.js +137 -0
  85. package/dist/linkUtils.d.ts +27 -0
  86. package/dist/linkUtils.d.ts.map +1 -0
  87. package/dist/linkUtils.js +101 -0
  88. package/dist/markdown.d.ts +17 -0
  89. package/dist/markdown.d.ts.map +1 -0
  90. package/dist/markdown.js +17 -0
  91. package/dist/markdownCharacterReferences.d.ts +135 -0
  92. package/dist/markdownCharacterReferences.d.ts.map +1 -0
  93. package/dist/markdownCharacterReferences.js +1127 -0
  94. package/dist/markdownCodeFence.d.ts +4 -0
  95. package/dist/markdownCodeFence.d.ts.map +1 -0
  96. package/dist/markdownCodeFence.js +167 -0
  97. package/dist/markdownExtensionNode.d.ts +88 -0
  98. package/dist/markdownExtensionNode.d.ts.map +1 -0
  99. package/dist/markdownExtensionNode.js +248 -0
  100. package/dist/markdownExtensions.d.ts +115 -0
  101. package/dist/markdownExtensions.d.ts.map +1 -0
  102. package/dist/markdownExtensions.js +392 -0
  103. package/dist/markdownHardLineBreak.d.ts +25 -0
  104. package/dist/markdownHardLineBreak.d.ts.map +1 -0
  105. package/dist/markdownHardLineBreak.js +59 -0
  106. package/dist/markdownHeading.d.ts +20 -0
  107. package/dist/markdownHeading.d.ts.map +1 -0
  108. package/dist/markdownHeading.js +30 -0
  109. package/dist/markdownLink.d.ts +5 -0
  110. package/dist/markdownLink.d.ts.map +1 -0
  111. package/dist/markdownLink.js +80 -0
  112. package/dist/markdownListExport.d.ts +3 -0
  113. package/dist/markdownListExport.d.ts.map +1 -0
  114. package/dist/markdownListExport.js +68 -0
  115. package/dist/markdownListIndentation.d.ts +8 -0
  116. package/dist/markdownListIndentation.d.ts.map +1 -0
  117. package/dist/markdownListIndentation.js +101 -0
  118. package/dist/markdownOrderedList.d.ts +3 -0
  119. package/dist/markdownOrderedList.d.ts.map +1 -0
  120. package/dist/markdownOrderedList.js +51 -0
  121. package/dist/markdownQuote.d.ts +22 -0
  122. package/dist/markdownQuote.d.ts.map +1 -0
  123. package/dist/markdownQuote.js +32 -0
  124. package/dist/markdownSerializers.d.ts +49 -0
  125. package/dist/markdownSerializers.d.ts.map +1 -0
  126. package/dist/markdownSerializers.js +83 -0
  127. package/dist/markdownSource.d.ts +71 -0
  128. package/dist/markdownSource.d.ts.map +1 -0
  129. package/dist/markdownSource.js +804 -0
  130. package/dist/markdownSpaceRuns.d.ts +24 -0
  131. package/dist/markdownSpaceRuns.d.ts.map +1 -0
  132. package/dist/markdownSpaceRuns.js +147 -0
  133. package/dist/markdownTable.d.ts +35 -0
  134. package/dist/markdownTable.d.ts.map +1 -0
  135. package/dist/markdownTable.js +227 -0
  136. package/dist/markdownTaskList.d.ts +48 -0
  137. package/dist/markdownTaskList.d.ts.map +1 -0
  138. package/dist/markdownTaskList.js +166 -0
  139. package/dist/markdownThematicBreak.d.ts +3 -0
  140. package/dist/markdownThematicBreak.d.ts.map +1 -0
  141. package/dist/markdownThematicBreak.js +55 -0
  142. package/dist/privateAttributes/layerPortalBoundary.stylex.d.ts +12 -0
  143. package/dist/privateAttributes/layerPortalBoundary.stylex.d.ts.map +1 -0
  144. package/dist/privateAttributes/layerPortalBoundary.stylex.js +16 -0
  145. package/dist/richtext.css +213 -0
  146. package/dist/textSemantics.d.ts +3 -0
  147. package/dist/textSemantics.d.ts.map +1 -0
  148. package/dist/textSemantics.js +87 -0
  149. package/package.json +158 -3
  150. package/src/RichTextEditor.doc.mjs +308 -0
  151. package/src/RichTextEditor.tsx +1109 -0
  152. package/src/RichTextEditorAutoLinkPlugin.doc.mjs +43 -0
  153. package/src/RichTextEditorAutoLinkPlugin.tsx +126 -0
  154. package/src/RichTextEditorToolbar.doc.mjs +92 -0
  155. package/src/RichTextEditorToolbar.tsx +871 -0
  156. package/src/RichTextView.doc.mjs +101 -0
  157. package/src/RichTextView.tsx +351 -0
  158. package/src/codeLanguages.ts +42 -0
  159. package/src/codeLineTokens.ts +241 -0
  160. package/src/doc-mjs.d.ts +23 -0
  161. package/src/editorNodes.ts +44 -0
  162. package/src/editorTheme.ts +550 -0
  163. package/src/index.ts +59 -0
  164. package/src/internal/CodeBackspacePlugin.tsx +96 -0
  165. package/src/internal/CodeBlockHeaderPlugin.tsx +282 -0
  166. package/src/internal/CodePastePlugin.tsx +144 -0
  167. package/src/internal/CodeSyntaxPlugin.tsx +493 -0
  168. package/src/internal/CodeTabPlugin.tsx +298 -0
  169. package/src/internal/DisabledLinkGuardPlugin.tsx +93 -0
  170. package/src/internal/ImageSourceCaretPlugin.tsx +159 -0
  171. package/src/internal/ListItemShortcutsPlugin.tsx +404 -0
  172. package/src/internal/MarkdownExtensionsPlugin.tsx +289 -0
  173. package/src/internal/SelectAllScopePlugin.tsx +86 -0
  174. package/src/internal/TableColumnFloorPlugin.tsx +99 -0
  175. package/src/internal/TablePastePlugin.tsx +223 -0
  176. package/src/internal/TableScrollRegionPlugin.tsx +107 -0
  177. package/src/internal/TaskCheckboxPlugin.tsx +246 -0
  178. package/src/internal/ToolbarLinkPopover.tsx +392 -0
  179. package/src/linkEditing.ts +20 -0
  180. package/src/linkForm.tsx +156 -0
  181. package/src/linkUtils.ts +106 -0
  182. package/src/markdown.ts +25 -0
  183. package/src/markdownCharacterReferences.ts +1137 -0
  184. package/src/markdownCodeFence.ts +216 -0
  185. package/src/markdownExtensionNode.ts +339 -0
  186. package/src/markdownExtensions.ts +496 -0
  187. package/src/markdownHardLineBreak.ts +73 -0
  188. package/src/markdownHeading.ts +33 -0
  189. package/src/markdownLink.ts +86 -0
  190. package/src/markdownListExport.ts +85 -0
  191. package/src/markdownListIndentation.ts +105 -0
  192. package/src/markdownOrderedList.ts +60 -0
  193. package/src/markdownQuote.ts +35 -0
  194. package/src/markdownSerializers.ts +108 -0
  195. package/src/markdownSource.ts +1045 -0
  196. package/src/markdownSpaceRuns.ts +164 -0
  197. package/src/markdownTable.ts +309 -0
  198. package/src/markdownTaskList.ts +222 -0
  199. package/src/markdownThematicBreak.ts +66 -0
  200. package/src/privateAttributes/layerPortalBoundary.stylex.ts +18 -0
  201. package/src/textSemantics.ts +104 -0
@@ -0,0 +1,85 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file markdownListExport.ts
5
+ * @input Uses @lexical/list (ListNode, ListItemNode) and @lexical/markdown
6
+ * (ElementTransformer).
7
+ * @output Exports LIST_EXPORT, which writes lists the way Lexical's list
8
+ * transformers do except for nesting: a nested list is indented to its
9
+ * parent item's content column instead of four spaces per level, and each
10
+ * task item writes `[ ]` or `[x]` (markdownTaskList.ts).
11
+ * @position Part of DEFAULT_TRANSFORMERS (markdownTable.ts), ahead of
12
+ * Lexical's list transformers, which still import. Import reads nesting the
13
+ * CommonMark way (markdownListIndentation.ts), so a child written four
14
+ * spaces under `100. parent` (content column five) would read back at the
15
+ * parent's level; indenting to the content column keeps an edited list's
16
+ * structure when it is read again (spec:AST-062 FR3).
17
+ */
18
+
19
+ import {
20
+ $isListItemNode,
21
+ $isListNode,
22
+ ListItemNode,
23
+ ListNode,
24
+ } from '@lexical/list';
25
+ import type {ElementTransformer} from '@lexical/markdown';
26
+ import type {ElementNode} from 'lexical';
27
+ import {$getTaskState} from './markdownTaskList';
28
+
29
+ /** The bullet Lexical recorded for a list on import (`-`, `*`, or `+`). */
30
+ function bulletOf(list: ListNode): string {
31
+ // The export may hand over a view of the list (an object inheriting from
32
+ // the node, see markdownSource.ts); Lexical validates the fields of the
33
+ // node it serializes, so read the recorded state from the node itself.
34
+ const serialized = list.getLatest().exportJSON() as {
35
+ $?: {mdListMarker?: unknown};
36
+ };
37
+ const marker = serialized.$?.mdListMarker;
38
+ return marker === '*' || marker === '+' ? marker : '-';
39
+ }
40
+
41
+ function exportList(
42
+ list: ListNode,
43
+ exportChildren: (node: ElementNode) => string,
44
+ indent: string,
45
+ ): string {
46
+ const lines: Array<string> = [];
47
+ const type = list.getListType();
48
+ let index = 0;
49
+ // Where a nested list goes: the content column of the item before it.
50
+ let childIndent = `${indent} `;
51
+ for (const item of list.getChildren()) {
52
+ if (!$isListItemNode(item)) {
53
+ continue;
54
+ }
55
+ const first = item.getFirstChild();
56
+ if (item.getChildrenSize() === 1 && $isListNode(first)) {
57
+ lines.push(exportList(first, exportChildren, childIndent));
58
+ continue;
59
+ }
60
+ const marker =
61
+ type === 'number' ? `${list.getStart() + index}. ` : `${bulletOf(list)} `;
62
+ // A task item, in any bulleted list, writes its box.
63
+ const task = type === 'number' ? null : $getTaskState(item);
64
+ const checkbox = task == null ? '' : `[${task === 'checked' ? 'x' : ' '}] `;
65
+ let text = exportChildren(item);
66
+ if (type !== 'number') {
67
+ // As Lexical does: keep `1. ` at the start of a bullet item literal.
68
+ text = text.replace(/^(\s{0,3}\d+)(\.\s)/, '$1\\$2');
69
+ }
70
+ lines.push(indent + marker + checkbox + text);
71
+ childIndent = indent + ' '.repeat(marker.length);
72
+ index++;
73
+ }
74
+ return lines.join('\n');
75
+ }
76
+
77
+ export const LIST_EXPORT: ElementTransformer = {
78
+ dependencies: [ListNode, ListItemNode],
79
+ export: (node, exportChildren) =>
80
+ $isListNode(node) ? exportList(node, exportChildren, '') : null,
81
+ // Export only: Lexical's list transformers import.
82
+ regExp: /(?!)/,
83
+ replace: () => false,
84
+ type: 'element',
85
+ };
@@ -0,0 +1,105 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file markdownListIndentation.ts
5
+ * @input Markdown source text.
6
+ * @output Exports normalizeListIndentation, which rewrites the indentation of
7
+ * every list item line so its nesting reads the same to Lexical as it does
8
+ * to CommonMark.
9
+ * @position Used by markdownSource.ts on the text it imports (never on the
10
+ * bytes it records). Lexical nests a list item one level per four spaces or
11
+ * one tab; CommonMark nests an item under the item whose content it is
12
+ * indented to, so `- a` / ` - b` (two spaces) and `1. a` / ` 1. b`
13
+ * (three) are nested lists that Lexical would flatten (spec:AST-061 FR5).
14
+ */
15
+
16
+ /** A list item line: indentation, marker, and the spaces after it. */
17
+ const LIST_ITEM = /^([ \t]*)([-+*]|\d{1,9}[.)])( {1,4}|\t|$)/;
18
+ const FENCE_OPEN = /^[ \t]*(`{3,}|~{3,})/;
19
+ const BLANK_LINE = /^[ \t]*\r?$/;
20
+ /** A thematic break after its indentation: three or more of one marker. */
21
+ const THEMATIC_BREAK_CONTENT = /^([-*_])(?:[ \t]*\1){2,}[ \t]*\r?$/;
22
+
23
+ /** Columns of leading whitespace, with tabs stopping every four columns. */
24
+ function columnsOf(whitespace: string): number {
25
+ let columns = 0;
26
+ for (const character of whitespace) {
27
+ columns = character === '\t' ? columns + 4 - (columns % 4) : columns + 1;
28
+ }
29
+ return columns;
30
+ }
31
+
32
+ /**
33
+ * Rewrites each list item line's indentation as four spaces per CommonMark
34
+ * nesting level, leaving every other line, and every line inside fenced code,
35
+ * as it is. The line count and every line's content after its indentation
36
+ * are unchanged.
37
+ */
38
+ export function normalizeListIndentation(markdown: string): string {
39
+ const lines = markdown.split('\n');
40
+ // The content column of each open list item, outermost first.
41
+ const open: Array<number> = [];
42
+ let fence: string | null = null;
43
+ let afterBlankLine = false;
44
+ for (let index = 0; index < lines.length; index++) {
45
+ const line = lines[index];
46
+ if (fence != null) {
47
+ if (
48
+ new RegExp(`^[ \\t]*${fence[0]}{${fence.length},}[ \\t]*\\r?$`).test(
49
+ line,
50
+ )
51
+ ) {
52
+ fence = null;
53
+ }
54
+ continue;
55
+ }
56
+ if (BLANK_LINE.test(line)) {
57
+ afterBlankLine = true;
58
+ continue;
59
+ }
60
+ const leading = /^[ \t]*/.exec(line)?.[0] ?? '';
61
+ const indent = columnsOf(leading);
62
+ if (THEMATIC_BREAK_CONTENT.test(line.slice(leading.length))) {
63
+ // A thematic break, never an item, even where an item could start
64
+ // (CommonMark 0.31 §4.1). Lexical shows it only as a block of its own,
65
+ // so a break inside an item loses its indentation and reads as one.
66
+ while (open.length > 0 && indent < open[open.length - 1]) {
67
+ open.pop();
68
+ }
69
+ if (open.length > 0) {
70
+ lines[index] = line.slice(leading.length);
71
+ }
72
+ afterBlankLine = false;
73
+ continue;
74
+ }
75
+ const item = LIST_ITEM.exec(line);
76
+ if (item == null) {
77
+ const opening = FENCE_OPEN.exec(line);
78
+ if (opening != null) {
79
+ fence = opening[1];
80
+ }
81
+ // A line after a blank line belongs to the items it is indented into;
82
+ // without a blank line it continues the last item's paragraph lazily.
83
+ if (afterBlankLine) {
84
+ while (open.length > 0 && indent < open[open.length - 1]) {
85
+ open.pop();
86
+ }
87
+ }
88
+ afterBlankLine = false;
89
+ continue;
90
+ }
91
+ afterBlankLine = false;
92
+ // An item nests under every open item whose content it reaches.
93
+ while (open.length > 0 && indent < open[open.length - 1]) {
94
+ open.pop();
95
+ }
96
+ const depth = open.length;
97
+ // The content column, counted across the whole prefix so a tab after
98
+ // the marker stops at the right column; a marker that ends the line has
99
+ // its content one column after it.
100
+ const markerEnd = columnsOf(item[0]) + (item[3] === '' ? 1 : 0);
101
+ open.push(markerEnd);
102
+ lines[index] = ' '.repeat(depth * 4) + line.slice(item[1].length);
103
+ }
104
+ return lines.join('\n');
105
+ }
@@ -0,0 +1,60 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file markdownOrderedList.ts
5
+ * @input Uses @lexical/list (ListNode, ListItemNode) and @lexical/markdown
6
+ * (ORDERED_LIST).
7
+ * @output Exports ORDERED_LIST_KEEPING_START, Lexical's ordered list
8
+ * transformer with one change: a nested numbered list starts at the number
9
+ * its first item was written with.
10
+ * @position Replaces Lexical's ORDERED_LIST in DEFAULT_TRANSFORMERS
11
+ * (markdownTable.ts). Lexical nests an indented item under a numbered list
12
+ * by indenting it into a new list that starts at 1, so `27.` written as the
13
+ * first item of a nested list read as `1.` (or `a.` under spec:AST-061
14
+ * DEC-6). Markdown keeps every list's start, nested or not (DEC-6), so this
15
+ * sets the new nested list's start from the marker. A later item in the same
16
+ * nested list continues its sequence, as Lexical's does.
17
+ */
18
+
19
+ import {$isListItemNode, $isListNode, type ListNode} from '@lexical/list';
20
+ import {ORDERED_LIST, type ElementTransformer} from '@lexical/markdown';
21
+
22
+ /**
23
+ * The list `item` was indented into: the last item under `list`, followed
24
+ * down through the wrapper items Lexical nests lists in.
25
+ */
26
+ function nestedListOfLastItem(list: ListNode): ListNode {
27
+ let current = list;
28
+ for (;;) {
29
+ const last = current.getLastChild();
30
+ const nested = $isListItemNode(last) ? last.getFirstChild() : null;
31
+ if (
32
+ !$isListItemNode(last) ||
33
+ last.getChildrenSize() !== 1 ||
34
+ !$isListNode(nested)
35
+ ) {
36
+ return current;
37
+ }
38
+ current = nested;
39
+ }
40
+ }
41
+
42
+ export const ORDERED_LIST_KEEPING_START: ElementTransformer = {
43
+ ...ORDERED_LIST,
44
+ replace: (parentNode, children, match, isImport) => {
45
+ const previous = parentNode.getPreviousSibling();
46
+ const result = ORDERED_LIST.replace(parentNode, children, match, isImport);
47
+ if ($isListNode(previous) && previous.getListType() === 'number') {
48
+ const nested = nestedListOfLastItem(previous);
49
+ // The new item opened this nested list: start it at its own number.
50
+ if (
51
+ nested !== previous &&
52
+ nested.getListType() === 'number' &&
53
+ nested.getChildrenSize() === 1
54
+ ) {
55
+ nested.setStart(Number(match[2]));
56
+ }
57
+ }
58
+ return result;
59
+ },
60
+ };
@@ -0,0 +1,35 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file markdownQuote.ts
5
+ * @input Uses @lexical/markdown (Lexical's QUOTE element transformer).
6
+ * @output Exports QUOTE_MARKERS, Lexical's quote transformer reading a block
7
+ * quote marker as CommonMark does.
8
+ * @position Part of DEFAULT_TRANSFORMERS (markdownTable.ts) in place of
9
+ * Lexical's QUOTE, whose pattern needs a space after `>` and no indentation.
10
+ * Matches core Markdown's block quote marker (CommonMark 0.31 §5.1). Typing
11
+ * shortcuts keep Lexical's own trigger: `> ` makes a quote. Text that starts
12
+ * like a marker is escaped on export by markdownSource.ts.
13
+ */
14
+
15
+ import {QUOTE, type ElementTransformer} from '@lexical/markdown';
16
+
17
+ /** Lexical's own quote marker, which the typing shortcut keeps using. */
18
+ const SHORTCUT_MARKER = /^>\s/;
19
+
20
+ /**
21
+ * A block quote marker: up to three spaces of indentation, then `>` and the
22
+ * one space or tab that may follow it, so `>a` and ` > a` import as quotes,
23
+ * as in core Markdown. A typed line becomes a quote only at Lexical's own
24
+ * `> `, so typing `>` and Enter, or ` > `, changes nothing new. RichText
25
+ * quotes do not nest: the marker of a quote inside a quote stays in the
26
+ * outer quote's text.
27
+ */
28
+ export const QUOTE_MARKERS: ElementTransformer = {
29
+ ...QUOTE,
30
+ regExp: /^ {0,3}>[ \t]?/,
31
+ replace: (parentNode, children, match, isImport) =>
32
+ isImport || SHORTCUT_MARKER.test(match[0])
33
+ ? QUOTE.replace(parentNode, children, match, isImport)
34
+ : false,
35
+ };
@@ -0,0 +1,108 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file markdownSerializers.ts
5
+ * @input Uses @lexical/headless (createHeadlessEditor), @lexical/markdown
6
+ * (transformer types), the shared DEFAULT_NODES set, DEFAULT_TRANSFORMERS,
7
+ * and the source-preserving import and export in markdownSource.ts.
8
+ * @output Standalone Markdown <-> serialized EditorState helpers:
9
+ * markdownToEditorStateJSON, editorStateJSONToMarkdown.
10
+ * @position Exported from the server-safe `@blue-patterns/richtext/markdown`
11
+ * entry and re-exported from the @blue-patterns/richtext barrel. Complements the ref's getMarkdown() by working WITHOUT a mounted
12
+ * editor (e.g. to produce a `defaultValue` from Markdown on the server).
13
+ *
14
+ * SYNC: When modified, update:
15
+ * - /packages/richtext/src/index.ts (exports)
16
+ * - /packages/richtext/src/RichTextEditor.doc.mjs (usage notes)
17
+ * - /packages/richtext/src/RichTextEditor.test.tsx (tests)
18
+ *
19
+ * NOTE: `@lexical/headless`, `@lexical/markdown`, and `lexical` are OPTIONAL
20
+ * peer dependencies. Install them to use these helpers. `@lexical/headless`
21
+ * runs Lexical without a DOM, so these work in Node / SSR contexts.
22
+ */
23
+
24
+ import {createHeadlessEditor} from '@lexical/headless';
25
+ import {type Transformer} from '@lexical/markdown';
26
+ import {DEFAULT_NODES} from './editorNodes';
27
+ import {
28
+ $exportMarkdownKeepingSource,
29
+ importMarkdownKeepingSource,
30
+ } from './markdownSource';
31
+ import {DEFAULT_TRANSFORMERS} from './markdownTable';
32
+ import {pluginsOf, type RichTextMarkdownExtension} from './markdownExtensions';
33
+ import type {Klass, LexicalNode} from 'lexical';
34
+
35
+ /** Options shared by the Markdown serializer helpers. */
36
+ export interface MarkdownSerializerOptions {
37
+ /**
38
+ * Markdown transformers to use. Defaults to the editor's default set: the
39
+ * standard `@lexical/markdown` `TRANSFORMERS` plus GFM tables. Pass the same
40
+ * array you give `RichTextEditor`'s `transformers` prop so content
41
+ * round-trips consistently.
42
+ */
43
+ transformers?: ReadonlyArray<Transformer>;
44
+ /**
45
+ * Extra Lexical nodes to register beyond the default OSS set, mirroring the
46
+ * editor's `nodes` prop. Required for custom node types to serialize.
47
+ */
48
+ nodes?: ReadonlyArray<Klass<LexicalNode>>;
49
+ /**
50
+ * Markdown plugins to recognize, each adopted with `createRichTextExtension`
51
+ * (spec:AST-064). Their syntax becomes extension nodes holding its exact
52
+ * source; without them, plugin syntax stays text. Pass the same extensions
53
+ * the surfaces that show the document use.
54
+ */
55
+ extensions?: ReadonlyArray<RichTextMarkdownExtension>;
56
+ }
57
+
58
+ function createSerializerEditor(nodes?: ReadonlyArray<Klass<LexicalNode>>) {
59
+ return createHeadlessEditor({
60
+ namespace: 'astryx-editor-serializer',
61
+ nodes: nodes ? [...DEFAULT_NODES, ...nodes] : [...DEFAULT_NODES],
62
+ onError(error: Error) {
63
+ throw error;
64
+ },
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Convert a Markdown string to a serialized Lexical `EditorState` JSON string
70
+ * — the same shape `RichTextEditor`'s `defaultValue` expects. Runs headless
71
+ * (no DOM), so it is safe in Node / SSR.
72
+ *
73
+ * @example
74
+ * ```
75
+ * const value = markdownToEditorStateJSON('# Title\n\nHello');
76
+ * <RichTextEditor label="Notes" defaultValue={value} />
77
+ * ```
78
+ */
79
+ export function markdownToEditorStateJSON(
80
+ markdown: string,
81
+ options: MarkdownSerializerOptions = {},
82
+ ): string {
83
+ const {transformers = DEFAULT_TRANSFORMERS, nodes, extensions = []} = options;
84
+ const plugins = pluginsOf(extensions, transformers);
85
+ const editor = createSerializerEditor(nodes);
86
+ importMarkdownKeepingSource(editor, markdown, [...transformers], plugins);
87
+ return JSON.stringify(editor.getEditorState().toJSON());
88
+ }
89
+
90
+ /**
91
+ * Convert a serialized Lexical `EditorState` JSON string (e.g. the value
92
+ * produced by `RichTextEditor`'s `onChange` or `defaultValue`) to a Markdown
93
+ * string. Runs headless (no DOM), so it is safe in Node / SSR.
94
+ *
95
+ * @example
96
+ * ```
97
+ * const md = editorStateJSONToMarkdown(savedJson);
98
+ * ```
99
+ */
100
+ export function editorStateJSONToMarkdown(
101
+ editorStateJSON: string,
102
+ options: MarkdownSerializerOptions = {},
103
+ ): string {
104
+ const {transformers = DEFAULT_TRANSFORMERS, nodes} = options;
105
+ const editor = createSerializerEditor(nodes);
106
+ const state = editor.parseEditorState(editorStateJSON);
107
+ return state.read(() => $exportMarkdownKeepingSource([...transformers]));
108
+ }