@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,24 @@
1
+ /**
2
+ * @file markdownSpaceRuns.ts
3
+ * @input Lexical nodes to export as Markdown, and the transformers to use
4
+ * @output $convertToMarkdownKeepingTimeLinear: Lexical's Markdown export, in
5
+ * time linear in the text even when a text holds long runs of spaces
6
+ * @position Every Markdown export in this package goes through it
7
+ * (markdownSource.ts, markdownTable.ts). Lexical's text-format export splits
8
+ * each text into leading spaces, content, and trailing spaces with
9
+ * `/^(\s*)(.*?)(\s*)$/s`, which retries from every character of a long run
10
+ * of spaces inside the text, so its time grows with the square of the run.
11
+ * Each such run is exported as private-use stand-ins, which that pattern
12
+ * passes in one step each, and put back afterwards. Leading and trailing
13
+ * spaces, which decide where marks attach, are left as they are.
14
+ */
15
+ import type { ElementNode } from 'lexical';
16
+ import type { Transformer } from '@lexical/markdown';
17
+ /**
18
+ * Lexical's `$convertToMarkdownString(transformers, root)`, with every long
19
+ * run of spaces inside a text exported as stand-ins and put back, so the
20
+ * export takes time linear in the text. The tree is read through views, never
21
+ * changed; a tree with no long run exports directly.
22
+ */
23
+ export declare function $convertToMarkdownKeepingTimeLinear(transformers: Array<Transformer>, root: ElementNode): string;
24
+ //# sourceMappingURL=markdownSpaceRuns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownSpaceRuns.d.ts","sourceRoot":"","sources":["../src/markdownSpaceRuns.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAc,MAAM,SAAS,CAAC;AACtD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAqBnD;;;;;GAKG;AACH,wBAAgB,mCAAmC,CACjD,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAChC,IAAI,EAAE,WAAW,GAChB,MAAM,CA2DR"}
@@ -0,0 +1,147 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file markdownSpaceRuns.ts
5
+ * @input Lexical nodes to export as Markdown, and the transformers to use
6
+ * @output $convertToMarkdownKeepingTimeLinear: Lexical's Markdown export, in
7
+ * time linear in the text even when a text holds long runs of spaces
8
+ * @position Every Markdown export in this package goes through it
9
+ * (markdownSource.ts, markdownTable.ts). Lexical's text-format export splits
10
+ * each text into leading spaces, content, and trailing spaces with
11
+ * `/^(\s*)(.*?)(\s*)$/s`, which retries from every character of a long run
12
+ * of spaces inside the text, so its time grows with the square of the run.
13
+ * Each such run is exported as private-use stand-ins, which that pattern
14
+ * passes in one step each, and put back afterwards. Leading and trailing
15
+ * spaces, which decide where marks attach, are left as they are.
16
+ */
17
+
18
+ import { $isCodeNode } from '@lexical/code';
19
+ import { $convertToMarkdownString } from '@lexical/markdown';
20
+ import { $isElementNode, $isTextNode } from 'lexical';
21
+ import { absentCharacters } from "./markdownCharacterReferences.js";
22
+
23
+ /**
24
+ * A run of spaces with other characters on both sides, long enough for
25
+ * Lexical's pattern to cost noticeably more than one step per space; shorter
26
+ * runs, as ordinary text has, export as they are.
27
+ */
28
+ const INNER_SPACE_RUN = /(?<=\S)\s{64,}(?=\S)/g;
29
+
30
+ /** Whether Lexical exports the text node's text as written, untouched. */
31
+ function isLiteralText(node) {
32
+ return $isTextNode(node) && (node.hasFormat('code') || $isCodeNode(node.getParent()));
33
+ }
34
+
35
+ /**
36
+ * Lexical's `$convertToMarkdownString(transformers, root)`, with every long
37
+ * run of spaces inside a text exported as stand-ins and put back, so the
38
+ * export takes time linear in the text. The tree is read through views, never
39
+ * changed; a tree with no long run exports directly.
40
+ */
41
+ export function $convertToMarkdownKeepingTimeLinear(transformers, root) {
42
+ const spaces = new Set();
43
+ const findRuns = node => {
44
+ if ($isTextNode(node)) {
45
+ if (!isLiteralText(node)) {
46
+ for (const run of node.getTextContent().match(INNER_SPACE_RUN) ?? []) {
47
+ for (const space of run) {
48
+ spaces.add(space);
49
+ }
50
+ }
51
+ }
52
+ } else if ($isElementNode(node) && !$isCodeNode(node)) {
53
+ node.getChildren().forEach(findRuns);
54
+ }
55
+ };
56
+ root.getChildren().forEach(findRuns);
57
+ if (spaces.size === 0) {
58
+ return $convertToMarkdownString(transformers, root);
59
+ }
60
+ // Every string the export can write — texts, and the properties nodes
61
+ // serialize, such as a link's address and title — so a stand-in occurs
62
+ // nowhere else in the output.
63
+ const written = [];
64
+ const collect = node => {
65
+ written.push(JSON.stringify(node.exportJSON()));
66
+ if ($isTextNode(node)) {
67
+ written.push(node.getTextContent());
68
+ } else if ($isElementNode(node)) {
69
+ node.getChildren().forEach(collect);
70
+ }
71
+ };
72
+ root.getChildren().forEach(collect);
73
+ const free = absentCharacters(written.join(''));
74
+ const standIns = new Map();
75
+ for (const space of spaces) {
76
+ const standIn = free.next().value;
77
+ if (standIn == null) {
78
+ return $convertToMarkdownString(transformers, root);
79
+ }
80
+ standIns.set(space, standIn);
81
+ }
82
+ const placed = {
83
+ count: 0
84
+ };
85
+ const children = viewsOf(root.getChildren(), standIns, placed);
86
+ const view = Object.create(root);
87
+ view.getChildren = () => children;
88
+ let markdown = $convertToMarkdownString(transformers, view);
89
+ // Every stand-in in the output must be one this export placed; otherwise
90
+ // the plain export, slower but exact, is the answer.
91
+ let found = 0;
92
+ for (const standIn of standIns.values()) {
93
+ found += markdown.split(standIn).length - 1;
94
+ }
95
+ if (found !== placed.count) {
96
+ return $convertToMarkdownString(transformers, root);
97
+ }
98
+ for (const [space, standIn] of standIns) {
99
+ markdown = markdown.split(standIn).join(space);
100
+ }
101
+ return markdown;
102
+ }
103
+
104
+ /**
105
+ * Views of `nodes` whose texts hold stand-ins for their inner runs of
106
+ * spaces; a node with no such run is itself. Each view's siblings are the
107
+ * views beside it, as Lexical's exporter reads neighbors to place marks.
108
+ */
109
+ function viewsOf(nodes, standIns, placed) {
110
+ const views = nodes.map(node => viewOf(node, standIns, placed));
111
+ views.forEach((view, index) => {
112
+ if (view !== nodes[index]) {
113
+ view.getPreviousSibling = () => views[index - 1] ?? null;
114
+ view.getNextSibling = () => views[index + 1] ?? null;
115
+ }
116
+ });
117
+ return views;
118
+ }
119
+ function viewOf(node, standIns, placed) {
120
+ if ($isTextNode(node)) {
121
+ if (isLiteralText(node)) {
122
+ return node;
123
+ }
124
+ const text = node.getTextContent();
125
+ const stood = text.replace(INNER_SPACE_RUN, run => {
126
+ placed.count += run.length;
127
+ return Array.from(run, space => standIns.get(space) ?? space).join('');
128
+ });
129
+ if (stood === text) {
130
+ return node;
131
+ }
132
+ const view = Object.create(node);
133
+ view.getTextContent = () => stood;
134
+ return view;
135
+ }
136
+ if ($isElementNode(node) && !$isCodeNode(node)) {
137
+ const original = node.getChildren();
138
+ const children = viewsOf(original, standIns, placed);
139
+ if (children.every((child, index) => child === original[index])) {
140
+ return node;
141
+ }
142
+ const view = Object.create(node);
143
+ view.getChildren = () => children;
144
+ return view;
145
+ }
146
+ return node;
147
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @file markdownTable.ts
3
+ * @input Uses @lexical/markdown (multiline element transformer, inline text
4
+ * transformers) and the @lexical/table nodes.
5
+ * @output Exports TABLE, the GFM pipe-table transformer, and DEFAULT_TRANSFORMERS,
6
+ * the editor's default Markdown transformers (Lexical's standard set plus TABLE).
7
+ * @position Internal to @blue-patterns/richtext. RichTextEditor.tsx (shortcuts,
8
+ * getMarkdown) and markdownSerializers.ts (headless import/export) default to
9
+ * DEFAULT_TRANSFORMERS so tables import, edit, and export the same way.
10
+ *
11
+ * SYNC: Table recognition mirrors core Markdown's parser (a line with a pipe
12
+ * followed by a delimiter row; body rows continue while they contain a pipe),
13
+ * so the same source is a table on both surfaces. Keep the two in step.
14
+ */
15
+ import { type MultilineElementTransformer, type Transformer } from '@lexical/markdown';
16
+ /**
17
+ * GFM pipe tables. The header row is the first row and renders as header
18
+ * cells; delimiter-row colons set each column's alignment; rows shorter than
19
+ * the widest row gain empty cells, so no cell is dropped. Export writes the
20
+ * canonical form: outer pipes, one space of padding, and three-dash delimiters.
21
+ */
22
+ export declare const TABLE: MultilineElementTransformer;
23
+ /**
24
+ * The editor's default Markdown transformers: thematic breaks, list export
25
+ * that nests to each parent's content column, GFM task list items, Lexical's
26
+ * standard set (with `~~~` fences beside its backtick ones, both reading the
27
+ * opening line's info string as core does, its ordered list keeping each
28
+ * nested list's start, and its
29
+ * link writing an unbalanced destination's parentheses escaped), hard line
30
+ * breaks for breaks typed in the editor, and GFM tables. Thematic breaks come first so a line such as `* * *` is a rule
31
+ * rather than a list item, and task items before bullets so `- [ ] text` is a
32
+ * task rather than a bullet that starts with `[ ]`.
33
+ */
34
+ export declare const DEFAULT_TRANSFORMERS: ReadonlyArray<Transformer>;
35
+ //# sourceMappingURL=markdownTable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownTable.d.ts","sourceRoot":"","sources":["../src/markdownTable.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAUL,KAAK,2BAA2B,EAChC,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAkL3B;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,2BA8DnB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,CAAC,WAAW,CAoB3D,CAAC"}
@@ -0,0 +1,227 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file markdownTable.ts
5
+ * @input Uses @lexical/markdown (multiline element transformer, inline text
6
+ * transformers) and the @lexical/table nodes.
7
+ * @output Exports TABLE, the GFM pipe-table transformer, and DEFAULT_TRANSFORMERS,
8
+ * the editor's default Markdown transformers (Lexical's standard set plus TABLE).
9
+ * @position Internal to @blue-patterns/richtext. RichTextEditor.tsx (shortcuts,
10
+ * getMarkdown) and markdownSerializers.ts (headless import/export) default to
11
+ * DEFAULT_TRANSFORMERS so tables import, edit, and export the same way.
12
+ *
13
+ * SYNC: Table recognition mirrors core Markdown's parser (a line with a pipe
14
+ * followed by a delimiter row; body rows continue while they contain a pipe),
15
+ * so the same source is a table on both surfaces. Keep the two in step.
16
+ */
17
+
18
+ import { $convertFromMarkdownString, CODE, HEADING, TEXT_FORMAT_TRANSFORMERS, TEXT_MATCH_TRANSFORMERS, LINK, ORDERED_LIST, QUOTE, TRANSFORMERS } from '@lexical/markdown';
19
+ import { $createTableCellNode, $createTableNode, $createTableRowNode, $isTableCellNode, $isTableNode, $isTableRowNode, TableCellHeaderStates, TableCellNode, TableNode, TableRowNode } from '@lexical/table';
20
+ import { HARD_LINE_BREAK } from "./markdownHardLineBreak.js";
21
+ import { THEMATIC_BREAK } from "./markdownThematicBreak.js";
22
+ import { HEADING_MARKERS } from "./markdownHeading.js";
23
+ import { QUOTE_MARKERS } from "./markdownQuote.js";
24
+ import { LIST_EXPORT } from "./markdownListExport.js";
25
+ import { TASK_LIST } from "./markdownTaskList.js";
26
+ import { ORDERED_LIST_KEEPING_START } from "./markdownOrderedList.js";
27
+ import { LINK_KEEPING_DESTINATIONS } from "./markdownLink.js";
28
+ import { BACKTICK_CODE, TILDE_CODE } from "./markdownCodeFence.js";
29
+ import { $convertToMarkdownKeepingTimeLinear } from "./markdownSpaceRuns.js";
30
+
31
+ /**
32
+ * Cells hold inline Markdown only, so they are imported and exported with the
33
+ * inline transformers.
34
+ */
35
+ const CELL_TRANSFORMERS = [...TEXT_FORMAT_TRANSFORMERS, ...TEXT_MATCH_TRANSFORMERS.map(transformer => transformer === LINK ? LINK_KEEPING_DESTINATIONS : transformer)];
36
+ /**
37
+ * Splits a row into trimmed cell sources: the row's indentation, one leading
38
+ * pipe, trailing spaces, and one trailing pipe are dropped, and the rest
39
+ * splits on unescaped pipes. An escaped pipe stays escaped for the cell's
40
+ * inline import.
41
+ */
42
+ function splitTableRow(line) {
43
+ let start = 0;
44
+ let end = line.length;
45
+ while (start < end && (line[start] === ' ' || line[start] === '\t')) {
46
+ start++;
47
+ }
48
+ if (line[start] === '|') {
49
+ start++;
50
+ while (start < end && line[start] === ' ') {
51
+ start++;
52
+ }
53
+ }
54
+ while (end > start && line[end - 1] === ' ') {
55
+ end--;
56
+ }
57
+ if (end > start && line[end - 1] === '|') {
58
+ end--;
59
+ }
60
+ const content = line.slice(start, end);
61
+ const cells = [];
62
+ let current = '';
63
+ for (let index = 0; index < content.length; index++) {
64
+ const character = content[index];
65
+ if (character === '\\' && content[index + 1] === '|') {
66
+ current += '\\|';
67
+ index++;
68
+ } else if (character === '|') {
69
+ cells.push(current.trim());
70
+ current = '';
71
+ } else {
72
+ current += character;
73
+ }
74
+ }
75
+ cells.push(current.trim());
76
+ return cells;
77
+ }
78
+
79
+ /** A delimiter row: every non-empty cell is dashes with optional colons. */
80
+ function isDelimiterRow(line) {
81
+ if (line == null || !line.includes('|')) {
82
+ return false;
83
+ }
84
+ const cells = line.split('|').map(cell => cell.trim()).filter(cell => cell.length > 0);
85
+ return cells.length > 0 && cells.every(cell => /^:?-+:?$/.test(cell));
86
+ }
87
+ function columnAlignment(delimiterCell) {
88
+ const left = delimiterCell.startsWith(':');
89
+ const right = delimiterCell.endsWith(':');
90
+ if (left && right) {
91
+ return 'center';
92
+ }
93
+ return right ? 'right' : left ? 'left' : null;
94
+ }
95
+
96
+ /**
97
+ * Alignment is stored as the cell's element format. Left and right map to
98
+ * logical start and end, so an aligned column follows the reading direction
99
+ * the way core Markdown's table does.
100
+ */
101
+ const ALIGNMENT_FORMAT = {
102
+ left: 'start',
103
+ center: 'center',
104
+ right: 'end'
105
+ };
106
+ function delimiterFor(format) {
107
+ switch (format) {
108
+ case 'left':
109
+ case 'start':
110
+ return ':---';
111
+ case 'center':
112
+ return ':---:';
113
+ case 'right':
114
+ case 'end':
115
+ return '---:';
116
+ default:
117
+ return '---';
118
+ }
119
+ }
120
+ function $createCell(source, isHeader, alignment) {
121
+ const cell = $createTableCellNode(isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS);
122
+ // The pipe was only escaped to keep it out of the row split.
123
+ $convertFromMarkdownString(source.replace(/\\\|/g, '|'), CELL_TRANSFORMERS, cell);
124
+ if (alignment != null) {
125
+ cell.setFormat(ALIGNMENT_FORMAT[alignment]);
126
+ }
127
+ return cell;
128
+ }
129
+ function cellMarkdown(cell) {
130
+ // A cell is one line of Markdown, so paragraph breaks inside it become
131
+ // spaces, and literal pipes are escaped to stay inside the cell.
132
+ return oneLine($convertToMarkdownKeepingTimeLinear(CELL_TRANSFORMERS, cell)).replace(/\|/g, '\\|').trim();
133
+ }
134
+
135
+ /**
136
+ * `markdown` with each run of spaces that holds a line break made one space.
137
+ * Read line by line: a pattern matching optional spaces around line breaks
138
+ * retries from every space in a long run without a line break, so its time
139
+ * grows with the square of the run.
140
+ */
141
+ function oneLine(markdown) {
142
+ if (!markdown.includes('\n')) {
143
+ return markdown;
144
+ }
145
+ const lines = markdown.split('\n');
146
+ let joined = lines[0].trimEnd();
147
+ for (let index = 1; index < lines.length; index++) {
148
+ const isLast = index === lines.length - 1;
149
+ const line = isLast ? lines[index].trimStart() : lines[index].trim();
150
+ // A blank line is part of the run of spaces around it.
151
+ if (line !== '' || isLast) {
152
+ joined += ` ${line}`;
153
+ }
154
+ }
155
+ return joined;
156
+ }
157
+
158
+ /**
159
+ * GFM pipe tables. The header row is the first row and renders as header
160
+ * cells; delimiter-row colons set each column's alignment; rows shorter than
161
+ * the widest row gain empty cells, so no cell is dropped. Export writes the
162
+ * canonical form: outer pipes, one space of padding, and three-dash delimiters.
163
+ */
164
+ export const TABLE = {
165
+ dependencies: [TableNode, TableRowNode, TableCellNode],
166
+ export(node) {
167
+ if (!$isTableNode(node)) {
168
+ return null;
169
+ }
170
+ const rows = node.getChildren().filter($isTableRowNode);
171
+ const lines = [];
172
+ rows.forEach((row, rowIndex) => {
173
+ const cells = row.getChildren().filter($isTableCellNode);
174
+ lines.push(`| ${cells.map(cellMarkdown).join(' | ')} |`);
175
+ if (rowIndex === 0) {
176
+ lines.push(`| ${cells.map(cell => delimiterFor(cell.getFormatType())).join(' | ')} |`);
177
+ }
178
+ });
179
+ return lines.join('\n');
180
+ },
181
+ regExpStart: /\|/,
182
+ handleImportAfterStartMatch({
183
+ lines,
184
+ rootNode,
185
+ startLineIndex
186
+ }) {
187
+ if (!isDelimiterRow(lines[startLineIndex + 1])) {
188
+ return null;
189
+ }
190
+ const alignments = splitTableRow(lines[startLineIndex + 1]).map(columnAlignment);
191
+ const rowSources = [splitTableRow(lines[startLineIndex])];
192
+ let lastLineIndex = startLineIndex + 1;
193
+ while (lastLineIndex + 1 < lines.length && lines[lastLineIndex + 1].includes('|') && lines[lastLineIndex + 1].trim() !== '') {
194
+ lastLineIndex++;
195
+ rowSources.push(splitTableRow(lines[lastLineIndex]));
196
+ }
197
+ const columnCount = Math.max(...rowSources.map(cells => cells.length));
198
+ const table = $createTableNode();
199
+ rowSources.forEach((cells, rowIndex) => {
200
+ const row = $createTableRowNode();
201
+ for (let column = 0; column < columnCount; column++) {
202
+ row.append($createCell(cells[column] ?? '', rowIndex === 0, alignments[column] ?? null));
203
+ }
204
+ table.append(row);
205
+ });
206
+ rootNode.append(table);
207
+ return [true, lastLineIndex];
208
+ },
209
+ replace() {
210
+ // Every table imports through handleImportAfterStartMatch.
211
+ return false;
212
+ },
213
+ type: 'multiline-element'
214
+ };
215
+
216
+ /**
217
+ * The editor's default Markdown transformers: thematic breaks, list export
218
+ * that nests to each parent's content column, GFM task list items, Lexical's
219
+ * standard set (with `~~~` fences beside its backtick ones, both reading the
220
+ * opening line's info string as core does, its ordered list keeping each
221
+ * nested list's start, and its
222
+ * link writing an unbalanced destination's parentheses escaped), hard line
223
+ * breaks for breaks typed in the editor, and GFM tables. Thematic breaks come first so a line such as `* * *` is a rule
224
+ * rather than a list item, and task items before bullets so `- [ ] text` is a
225
+ * task rather than a bullet that starts with `[ ]`.
226
+ */
227
+ export const DEFAULT_TRANSFORMERS = [THEMATIC_BREAK, LIST_EXPORT, TASK_LIST, TILDE_CODE, ...TRANSFORMERS.map(transformer => transformer === ORDERED_LIST ? ORDERED_LIST_KEEPING_START : transformer === LINK ? LINK_KEEPING_DESTINATIONS : transformer === CODE ? BACKTICK_CODE : transformer === QUOTE ? QUOTE_MARKERS : transformer === HEADING ? HEADING_MARKERS : transformer), HARD_LINE_BREAK, TABLE];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @file markdownTaskList.ts
3
+ * @input Uses lexical (node state), @lexical/list, and @lexical/markdown
4
+ * (ElementTransformer).
5
+ * @output Exports TASK_LIST, the transformer that imports GFM task list
6
+ * items; $getTaskState / $setTaskState, which read and set whether a list
7
+ * item is a task and whether it is checked; and registerTaskListEditing,
8
+ * the editor's Enter and Mod+Enter behavior for task items.
9
+ * @position Part of DEFAULT_TRANSFORMERS (markdownTable.ts), ahead of
10
+ * Lexical's bullet transformer. It reads exactly the task items core
11
+ * Markdown reads (spec:AST-061 FR5): a `-`, `*`, or `+` bullet, one space,
12
+ * `[ ]`, `[x]`, or `[X]`, and a space. Lexical's own CHECK_LIST also takes a
13
+ * line with no bullet, `[]`, and missing spaces, and reads `[X]` as
14
+ * unchecked; those stay a paragraph or an ordinary bullet here.
15
+ *
16
+ * A task is a property of an item, not of its list: GFM lets task and
17
+ * plain items share one list, and Lexical keeps a checked state only in its
18
+ * all-checkbox lists. So task items live in ordinary bullet lists, beside
19
+ * plain items, and carry their state as node state; LIST_EXPORT writes
20
+ * `[ ]` or `[x]` for each task item. TaskCheckboxPlugin draws them, and
21
+ * registers registerTaskListEditing in the editor.
22
+ */
23
+ import { ListItemNode } from '@lexical/list';
24
+ import type { ElementTransformer } from '@lexical/markdown';
25
+ import { type LexicalEditor } from 'lexical';
26
+ export type TaskState = 'checked' | 'unchecked';
27
+ /**
28
+ * Whether `item` is a task and whether it is checked; null for a plain item.
29
+ * An item in one of Lexical's own checkbox lists is a task too.
30
+ */
31
+ export declare function $getTaskState(item: ListItemNode): TaskState | null;
32
+ /** Makes `item` a task in the given state, or a plain item for null. */
33
+ export declare function $setTaskState(item: ListItemNode, state: TaskState | null): void;
34
+ export declare const TASK_LIST: ElementTransformer;
35
+ /**
36
+ * Task editing in an editor:
37
+ *
38
+ * - Enter in a task makes the new item a task, unchecked, as Lexical's own
39
+ * checkbox items do. Lexical copies the item to make the new one, state and
40
+ * all, so the new item's state is set here, where Enter makes it — never
41
+ * inferred later from content, which would uncheck a written `- [x]` with
42
+ * no text. Enter at the start of a task leaves its text, and its state, on
43
+ * the item that holds it, and puts the new, unchecked item before it. Enter
44
+ * in an empty task is left to the list, which ends the list there.
45
+ * - Mod+Enter checks or unchecks the task holding the caret.
46
+ */
47
+ export declare function registerTaskListEditing(editor: LexicalEditor): () => void;
48
+ //# sourceMappingURL=markdownTaskList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownTaskList.d.ts","sourceRoot":"","sources":["../src/markdownTaskList.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAKL,YAAY,EAEb,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAE1D,OAAO,EASL,KAAK,aAAa,EACnB,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAahD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI,CASlE;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAC3B,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,SAAS,GAAG,IAAI,GACtB,IAAI,CAQN;AAWD,eAAO,MAAM,SAAS,EAAE,kBAsCvB,CAAC;AAeF;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,IAAI,CA0DzE"}
@@ -0,0 +1,166 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file markdownTaskList.ts
5
+ * @input Uses lexical (node state), @lexical/list, and @lexical/markdown
6
+ * (ElementTransformer).
7
+ * @output Exports TASK_LIST, the transformer that imports GFM task list
8
+ * items; $getTaskState / $setTaskState, which read and set whether a list
9
+ * item is a task and whether it is checked; and registerTaskListEditing,
10
+ * the editor's Enter and Mod+Enter behavior for task items.
11
+ * @position Part of DEFAULT_TRANSFORMERS (markdownTable.ts), ahead of
12
+ * Lexical's bullet transformer. It reads exactly the task items core
13
+ * Markdown reads (spec:AST-061 FR5): a `-`, `*`, or `+` bullet, one space,
14
+ * `[ ]`, `[x]`, or `[X]`, and a space. Lexical's own CHECK_LIST also takes a
15
+ * line with no bullet, `[]`, and missing spaces, and reads `[X]` as
16
+ * unchecked; those stay a paragraph or an ordinary bullet here.
17
+ *
18
+ * A task is a property of an item, not of its list: GFM lets task and
19
+ * plain items share one list, and Lexical keeps a checked state only in its
20
+ * all-checkbox lists. So task items live in ordinary bullet lists, beside
21
+ * plain items, and carry their state as node state; LIST_EXPORT writes
22
+ * `[ ]` or `[x]` for each task item. TaskCheckboxPlugin draws them, and
23
+ * registers registerTaskListEditing in the editor.
24
+ */
25
+
26
+ import { $createListItemNode, $createListNode, $isListItemNode, $isListNode, ListItemNode, ListNode } from '@lexical/list';
27
+ import { $findMatchingParent, mergeRegister } from '@lexical/utils';
28
+ import { $getSelection, $getState, $isRangeSelection, $setState, COMMAND_PRIORITY_LOW, createState, INSERT_PARAGRAPH_COMMAND, KEY_ENTER_COMMAND } from 'lexical';
29
+ // Whether an item is a task. A copy keeps it, so the item Enter makes after
30
+ // a task is a task too.
31
+ const taskKind = createState('astryxTask', {
32
+ parse: value => value === true
33
+ });
34
+
35
+ // Whether a task is checked.
36
+ const taskChecked = createState('astryxTaskChecked', {
37
+ parse: value => value === true
38
+ });
39
+
40
+ /**
41
+ * Whether `item` is a task and whether it is checked; null for a plain item.
42
+ * An item in one of Lexical's own checkbox lists is a task too.
43
+ */
44
+ export function $getTaskState(item) {
45
+ if ($getState(item, taskKind)) {
46
+ return $getState(item, taskChecked) ? 'checked' : 'unchecked';
47
+ }
48
+ const list = item.getParent();
49
+ if ($isListNode(list) && list.getListType() === 'check') {
50
+ return item.getChecked() === true ? 'checked' : 'unchecked';
51
+ }
52
+ return null;
53
+ }
54
+
55
+ /** Makes `item` a task in the given state, or a plain item for null. */
56
+ export function $setTaskState(item, state) {
57
+ const list = item.getParent();
58
+ if ($isListNode(list) && list.getListType() === 'check') {
59
+ item.setChecked(state === 'checked');
60
+ return;
61
+ }
62
+ $setState(item, taskKind, state != null);
63
+ $setState(item, taskChecked, state === 'checked');
64
+ }
65
+ const TASK_ITEM = /^(\s*)([-*+])\s\[([ xX])\]\s/;
66
+
67
+ /** Nesting depth from leading whitespace, counted as Lexical's lists do. */
68
+ function indentOf(whitespace) {
69
+ const tabs = whitespace.match(/\t/g)?.length ?? 0;
70
+ const spaces = whitespace.match(/ /g)?.length ?? 0;
71
+ return tabs + Math.floor(spaces / 4);
72
+ }
73
+ export const TASK_LIST = {
74
+ dependencies: [ListNode, ListItemNode],
75
+ // LIST_EXPORT writes every list, task items included.
76
+ export: () => null,
77
+ regExp: TASK_ITEM,
78
+ replace: (parentNode, children, match, isImport) => {
79
+ const item = $createListItemNode();
80
+ $setState(item, taskKind, true);
81
+ $setState(item, taskChecked, match[3] !== ' ');
82
+ // A task item joins the bullet list beside it, as a plain item does.
83
+ const previous = parentNode.getPreviousSibling();
84
+ const next = parentNode.getNextSibling();
85
+ if ($isListNode(next) && next.getListType() === 'bullet') {
86
+ const first = next.getFirstChild();
87
+ if (first == null) {
88
+ next.append(item);
89
+ } else {
90
+ first.insertBefore(item);
91
+ }
92
+ parentNode.remove();
93
+ } else if ($isListNode(previous) && previous.getListType() === 'bullet') {
94
+ previous.append(item);
95
+ parentNode.remove();
96
+ } else {
97
+ const list = $createListNode('bullet');
98
+ list.append(item);
99
+ parentNode.replace(list);
100
+ }
101
+ item.append(...children);
102
+ if (!isImport) {
103
+ item.select(0, 0);
104
+ }
105
+ const indent = indentOf(match[1]);
106
+ if (indent > 0) {
107
+ item.setIndent(indent);
108
+ }
109
+ },
110
+ type: 'element'
111
+ };
112
+
113
+ /** The task item holding the selection's anchor, if any. */
114
+ function $selectedTaskItem() {
115
+ const selection = $getSelection();
116
+ if (!$isRangeSelection(selection)) {
117
+ return null;
118
+ }
119
+ return $findMatchingParent(selection.anchor.getNode(), node => $isListItemNode(node) && $getTaskState(node) != null);
120
+ }
121
+
122
+ /**
123
+ * Task editing in an editor:
124
+ *
125
+ * - Enter in a task makes the new item a task, unchecked, as Lexical's own
126
+ * checkbox items do. Lexical copies the item to make the new one, state and
127
+ * all, so the new item's state is set here, where Enter makes it — never
128
+ * inferred later from content, which would uncheck a written `- [x]` with
129
+ * no text. Enter at the start of a task leaves its text, and its state, on
130
+ * the item that holds it, and puts the new, unchecked item before it. Enter
131
+ * in an empty task is left to the list, which ends the list there.
132
+ * - Mod+Enter checks or unchecks the task holding the caret.
133
+ */
134
+ export function registerTaskListEditing(editor) {
135
+ return mergeRegister(editor.registerCommand(INSERT_PARAGRAPH_COMMAND, () => {
136
+ const selection = $getSelection();
137
+ const item = $selectedTaskItem();
138
+ if (item == null || !$isRangeSelection(selection) || item.getTextContent().trim() === '') {
139
+ return false;
140
+ }
141
+ const state = $getTaskState(item);
142
+ const isAtStart = selection.isCollapsed() && selection.anchor.offset === 0 && (selection.anchor.getNode().is(item) || selection.anchor.getNode().is(item.getFirstDescendant()));
143
+ const created = selection.insertParagraph();
144
+ if (!$isListItemNode(created) || $getTaskState(created) == null) {
145
+ return true;
146
+ }
147
+ if (isAtStart) {
148
+ // The text moved to the new item: it keeps its state, and the item
149
+ // left empty before it is the new, unchecked one.
150
+ $setTaskState(created, state);
151
+ $setTaskState(item, 'unchecked');
152
+ } else {
153
+ $setTaskState(created, 'unchecked');
154
+ }
155
+ return true;
156
+ }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ENTER_COMMAND, event => {
157
+ const isModified = event != null && (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey;
158
+ const item = isModified ? $selectedTaskItem() : null;
159
+ if (item == null) {
160
+ return false;
161
+ }
162
+ event?.preventDefault();
163
+ $setTaskState(item, $getTaskState(item) === 'checked' ? 'unchecked' : 'checked');
164
+ return true;
165
+ }, COMMAND_PRIORITY_LOW));
166
+ }
@@ -0,0 +1,3 @@
1
+ import type { ElementTransformer } from '@lexical/markdown';
2
+ export declare const THEMATIC_BREAK: ElementTransformer;
3
+ //# sourceMappingURL=markdownThematicBreak.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownThematicBreak.d.ts","sourceRoot":"","sources":["../src/markdownThematicBreak.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAe1D,eAAO,MAAM,cAAc,EAAE,kBAgC5B,CAAC"}