@liminis/editor 0.1.0-rc.1

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 (219) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +376 -0
  3. package/dist/annotations/anchor-align.d.ts +50 -0
  4. package/dist/annotations/anchor-align.js +74 -0
  5. package/dist/annotations/anchor-model.d.ts +81 -0
  6. package/dist/annotations/anchor-model.js +80 -0
  7. package/dist/annotations/anchor-resolver.d.ts +92 -0
  8. package/dist/annotations/anchor-resolver.js +231 -0
  9. package/dist/annotations/block-structure.d.ts +65 -0
  10. package/dist/annotations/block-structure.js +113 -0
  11. package/dist/annotations/types.d.ts +159 -0
  12. package/dist/annotations/types.js +50 -0
  13. package/dist/annotations.d.ts +25 -0
  14. package/dist/annotations.js +27 -0
  15. package/dist/app/App.d.ts +64 -0
  16. package/dist/app/App.js +180 -0
  17. package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
  18. package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
  19. package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
  20. package/dist/app/editor/AnchorScrollPlugin.js +134 -0
  21. package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
  22. package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
  23. package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
  24. package/dist/app/editor/AnnotationPlugin.js +135 -0
  25. package/dist/app/editor/AnnotationSurface.d.ts +34 -0
  26. package/dist/app/editor/AnnotationSurface.js +115 -0
  27. package/dist/app/editor/AssetContext.d.ts +20 -0
  28. package/dist/app/editor/AssetContext.js +81 -0
  29. package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
  30. package/dist/app/editor/BlockClickPlugin.js +126 -0
  31. package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
  32. package/dist/app/editor/CodeBlockPlugin.js +219 -0
  33. package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
  34. package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
  35. package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
  36. package/dist/app/editor/DragHandlePlugin.js +300 -0
  37. package/dist/app/editor/Editor.d.ts +84 -0
  38. package/dist/app/editor/Editor.js +539 -0
  39. package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
  40. package/dist/app/editor/FrontmatterPlugin.js +301 -0
  41. package/dist/app/editor/ImageModal.d.ts +13 -0
  42. package/dist/app/editor/ImageModal.js +267 -0
  43. package/dist/app/editor/ImagePlugin.d.ts +3 -0
  44. package/dist/app/editor/ImagePlugin.js +187 -0
  45. package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
  46. package/dist/app/editor/LinkClickPlugin.js +90 -0
  47. package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
  48. package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
  49. package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
  50. package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
  51. package/dist/app/editor/SearchPlugin.d.ts +1 -0
  52. package/dist/app/editor/SearchPlugin.js +379 -0
  53. package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
  54. package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
  55. package/dist/app/editor/SlashMenu.d.ts +11 -0
  56. package/dist/app/editor/SlashMenu.js +494 -0
  57. package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
  58. package/dist/app/editor/SlashMenuPlugin.js +168 -0
  59. package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
  60. package/dist/app/editor/TableActionsPlugin.js +398 -0
  61. package/dist/app/editor/TogglePlugin.d.ts +1 -0
  62. package/dist/app/editor/TogglePlugin.js +159 -0
  63. package/dist/app/editor/Toolbar.d.ts +10 -0
  64. package/dist/app/editor/Toolbar.js +263 -0
  65. package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
  66. package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
  67. package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
  68. package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
  69. package/dist/app/editor/annotation-marks.d.ts +177 -0
  70. package/dist/app/editor/annotation-marks.js +750 -0
  71. package/dist/app/editor/annotationCommands.d.ts +13 -0
  72. package/dist/app/editor/annotationCommands.js +11 -0
  73. package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
  74. package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
  75. package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
  76. package/dist/app/editor/c4/edge-clipping.js +139 -0
  77. package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
  78. package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
  79. package/dist/app/editor/c4/layout.d.ts +25 -0
  80. package/dist/app/editor/c4/layout.js +839 -0
  81. package/dist/app/editor/c4/parser.d.ts +19 -0
  82. package/dist/app/editor/c4/parser.js +410 -0
  83. package/dist/app/editor/c4/render-to-string.d.ts +24 -0
  84. package/dist/app/editor/c4/render-to-string.js +34 -0
  85. package/dist/app/editor/c4/renderer.d.ts +64 -0
  86. package/dist/app/editor/c4/renderer.js +569 -0
  87. package/dist/app/editor/c4/types.d.ts +203 -0
  88. package/dist/app/editor/c4/types.js +43 -0
  89. package/dist/app/editor/correction-yaml.d.ts +10 -0
  90. package/dist/app/editor/correction-yaml.js +40 -0
  91. package/dist/app/editor/editorNodes.d.ts +9 -0
  92. package/dist/app/editor/editorNodes.js +36 -0
  93. package/dist/app/editor/index.d.ts +1 -0
  94. package/dist/app/editor/index.js +1 -0
  95. package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
  96. package/dist/app/editor/mark-overlap-resolver.js +45 -0
  97. package/dist/app/editor/nodes/C4Component.d.ts +14 -0
  98. package/dist/app/editor/nodes/C4Component.js +284 -0
  99. package/dist/app/editor/nodes/C4Node.d.ts +32 -0
  100. package/dist/app/editor/nodes/C4Node.js +112 -0
  101. package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
  102. package/dist/app/editor/nodes/CalloutNode.js +94 -0
  103. package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
  104. package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
  105. package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
  106. package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
  107. package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
  108. package/dist/app/editor/nodes/CustomListNode.js +54 -0
  109. package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
  110. package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
  111. package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
  112. package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
  113. package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
  114. package/dist/app/editor/nodes/EquationComponent.js +130 -0
  115. package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
  116. package/dist/app/editor/nodes/EquationNode.js +179 -0
  117. package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
  118. package/dist/app/editor/nodes/FootnoteNode.js +151 -0
  119. package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
  120. package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
  121. package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
  122. package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
  123. package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
  124. package/dist/app/editor/nodes/HtmlNode.js +121 -0
  125. package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
  126. package/dist/app/editor/nodes/ImageComponent.js +228 -0
  127. package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
  128. package/dist/app/editor/nodes/ImageNode.js +183 -0
  129. package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
  130. package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
  131. package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
  132. package/dist/app/editor/nodes/MermaidComponent.js +216 -0
  133. package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
  134. package/dist/app/editor/nodes/MermaidNode.js +96 -0
  135. package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
  136. package/dist/app/editor/nodes/ToggleNode.js +237 -0
  137. package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
  138. package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
  139. package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
  140. package/dist/app/editor/nodes/diagram-utils.js +29 -0
  141. package/dist/app/editor/nodes/index.d.ts +30 -0
  142. package/dist/app/editor/nodes/index.js +17 -0
  143. package/dist/app/editor/utils.d.ts +2 -0
  144. package/dist/app/editor/utils.js +17 -0
  145. package/dist/app/mapper/index.d.ts +3 -0
  146. package/dist/app/mapper/index.js +2 -0
  147. package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
  148. package/dist/app/mapper/lexicalToMdast.js +1937 -0
  149. package/dist/app/mapper/mdastToLexical.d.ts +31 -0
  150. package/dist/app/mapper/mdastToLexical.js +1075 -0
  151. package/dist/contract.d.ts +8 -0
  152. package/dist/contract.js +8 -0
  153. package/dist/headless.d.ts +40 -0
  154. package/dist/headless.js +45 -0
  155. package/dist/host/context.d.ts +20 -0
  156. package/dist/host/context.js +24 -0
  157. package/dist/host/defaults.d.ts +21 -0
  158. package/dist/host/defaults.js +47 -0
  159. package/dist/host/messages.d.ts +26 -0
  160. package/dist/host/messages.js +47 -0
  161. package/dist/host/types.d.ts +75 -0
  162. package/dist/host/types.js +13 -0
  163. package/dist/index.d.ts +35 -0
  164. package/dist/index.js +51 -0
  165. package/dist/markdown/annotate-sentinels.d.ts +42 -0
  166. package/dist/markdown/annotate-sentinels.js +86 -0
  167. package/dist/markdown/parse.d.ts +56 -0
  168. package/dist/markdown/parse.js +516 -0
  169. package/dist/markdown/stringify.d.ts +7 -0
  170. package/dist/markdown/stringify.js +439 -0
  171. package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
  172. package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
  173. package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
  174. package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
  175. package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
  176. package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
  177. package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
  178. package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
  179. package/dist/markdown.d.ts +28 -0
  180. package/dist/markdown.js +44 -0
  181. package/dist/mathjax-config.d.ts +89 -0
  182. package/dist/mathjax-config.js +133 -0
  183. package/dist/nodes.d.ts +34 -0
  184. package/dist/nodes.js +34 -0
  185. package/dist/stores/correctionStore.d.ts +15 -0
  186. package/dist/stores/correctionStore.js +12 -0
  187. package/dist/stores/editorStore.d.ts +16 -0
  188. package/dist/stores/editorStore.js +27 -0
  189. package/dist/styles.css +2477 -0
  190. package/dist/types.d.ts +296 -0
  191. package/dist/types.js +91 -0
  192. package/dist/utils/file-types.d.ts +10 -0
  193. package/dist/utils/file-types.js +46 -0
  194. package/docs/annotations.md +134 -0
  195. package/docs/decisions/README.md +72 -0
  196. package/docs/decisions/adr-002.md +27 -0
  197. package/docs/decisions/adr-007.md +27 -0
  198. package/docs/decisions/adr-010.md +26 -0
  199. package/docs/decisions/adr-024.md +124 -0
  200. package/docs/decisions/adr-024b.md +0 -0
  201. package/docs/decisions/adr-025.md +103 -0
  202. package/docs/decisions/adr-027.md +66 -0
  203. package/docs/decisions/adr-057.md +67 -0
  204. package/docs/decisions/adr-070.md +131 -0
  205. package/docs/decisions/adr-075.md +410 -0
  206. package/docs/decisions/adr-076.md +183 -0
  207. package/docs/decisions/adr-077.md +410 -0
  208. package/docs/decisions/adr-078.md +243 -0
  209. package/docs/decisions/adr-079.md +167 -0
  210. package/docs/decisions/adr-080.md +325 -0
  211. package/docs/decisions/adr-081.md +108 -0
  212. package/docs/decisions/adr-082.md +123 -0
  213. package/docs/decisions/adr-083.md +204 -0
  214. package/docs/decisions/adr-084.md +179 -0
  215. package/docs/editor-api.md +106 -0
  216. package/docs/markdown-pipeline.md +282 -0
  217. package/docs/provenance.md +106 -0
  218. package/docs/zusammen-editor-capability-map.md +232 -0
  219. package/package.json +175 -0
@@ -0,0 +1,373 @@
1
+ import { useEffect } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { registerMarkdownShortcuts, HEADING, QUOTE, CODE,
4
+ // Text format transformers for inline markdown syntax
5
+ BOLD_STAR, BOLD_UNDERSCORE, BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, ITALIC_STAR, ITALIC_UNDERSCORE, STRIKETHROUGH, INLINE_CODE,
6
+ // Text match transformers for links
7
+ LINK, } from '@lexical/markdown';
8
+ import { $createTextNode } from 'lexical';
9
+ import { LinkNode } from '@lexical/link';
10
+ import { $createCustomLinkNode, $createCustomListNode, $isCustomListNode, CustomListNode, $createCustomListItemNode, CustomListItemNode } from './nodes/index.js';
11
+ import { $createImageNode, ImageNode } from './nodes/ImageNode.js';
12
+ import { $createEquationNode, EquationNode } from './nodes/index.js';
13
+ import { getFileType } from '../../utils/file-types.js';
14
+ // Amount of spaces that define one indentation level, matching @lexical/markdown's own default.
15
+ const LIST_INDENT_SIZE = 4;
16
+ function getListIndent(whitespaces) {
17
+ const tabs = whitespaces.match(/\t/g);
18
+ const spaces = whitespaces.match(/ /g);
19
+ let indent = 0;
20
+ if (tabs) {
21
+ indent += tabs.length;
22
+ }
23
+ if (spaces) {
24
+ indent += Math.floor(spaces.length / LIST_INDENT_SIZE);
25
+ }
26
+ return indent;
27
+ }
28
+ /**
29
+ * Builds an `ElementTransformer.replace` for list markdown shortcuts ("- ", "1. ", "[] ")
30
+ * that creates `CustomListNode`s instead of going through `@lexical/markdown`'s built-in
31
+ * UNORDERED_LIST/ORDERED_LIST/CHECK_LIST transformers.
32
+ *
33
+ * The stock transformers call `@lexical/list`'s raw `$createListNode`, which constructs a
34
+ * plain `ListNode`. Since this editor replaces the registered 'list' node type with
35
+ * `CustomListNode` (to carry markdown loose/tight "spread" state through the round trip —
36
+ * see #898), constructing a plain `ListNode` throws
37
+ * ("Create node: Type list in node ListNode does not match registered node CustomListNode
38
+ * with the same type") the instant a user types a list shortcut. Mirrors the stock
39
+ * `listReplace` logic (list creation + merging with an adjacent list of the same type)
40
+ * with `$createCustomListNode` substituted for `$createListNode`.
41
+ */
42
+ function createCustomListReplace(listType) {
43
+ return (parentNode, children, match, isImport) => {
44
+ const previousNode = parentNode.getPreviousSibling();
45
+ const nextNode = parentNode.getNextSibling();
46
+ const listItem = $createCustomListItemNode(listType === 'check' ? match[3]?.toLowerCase() === 'x' : undefined);
47
+ if ($isCustomListNode(nextNode) && nextNode.getListType() === listType) {
48
+ const firstChild = nextNode.getFirstChild();
49
+ if (firstChild !== null) {
50
+ firstChild.insertBefore(listItem);
51
+ }
52
+ else {
53
+ nextNode.append(listItem);
54
+ }
55
+ parentNode.remove();
56
+ }
57
+ else if ($isCustomListNode(previousNode) && previousNode.getListType() === listType) {
58
+ previousNode.append(listItem);
59
+ parentNode.remove();
60
+ }
61
+ else {
62
+ const listNode = $createCustomListNode(listType, listType === 'number' ? Number(match[2]) : undefined);
63
+ listNode.append(listItem);
64
+ parentNode.replace(listNode);
65
+ }
66
+ listItem.append(...children);
67
+ if (!isImport) {
68
+ listItem.select(0, 0);
69
+ }
70
+ const indent = getListIndent(match[1]);
71
+ if (indent) {
72
+ listItem.setIndent(indent);
73
+ }
74
+ };
75
+ }
76
+ // eslint-disable-next-line react-refresh/only-export-components
77
+ export const UNORDERED_LIST = {
78
+ dependencies: [CustomListNode, CustomListItemNode],
79
+ export: () => null,
80
+ regExp: /^(\s*)[-*+]\s/,
81
+ replace: createCustomListReplace('bullet'),
82
+ type: 'element',
83
+ };
84
+ // eslint-disable-next-line react-refresh/only-export-components
85
+ export const ORDERED_LIST = {
86
+ dependencies: [CustomListNode, CustomListItemNode],
87
+ export: () => null,
88
+ regExp: /^(\s*)(\d{1,})\.\s/,
89
+ replace: createCustomListReplace('number'),
90
+ type: 'element',
91
+ };
92
+ // eslint-disable-next-line react-refresh/only-export-components
93
+ export const CHECK_LIST = {
94
+ dependencies: [CustomListNode, CustomListItemNode],
95
+ export: () => null,
96
+ regExp: /^(\s*)(?:[-*+]\s)?\s?(\[(\s|x)?\])\s/i,
97
+ replace: createCustomListReplace('check'),
98
+ type: 'element',
99
+ };
100
+ /**
101
+ * Parses a wiki link alias and extracts format markers.
102
+ * Supports nested formats like ***bold italic***.
103
+ *
104
+ * @param alias - The raw alias text potentially containing format markers
105
+ * @returns Object with the plain text and array of formats to apply
106
+ *
107
+ * @example
108
+ * parseFormattedAlias('**bold**') // { text: 'bold', formats: ['bold'] }
109
+ * parseFormattedAlias('*italic*') // { text: 'italic', formats: ['italic'] }
110
+ * parseFormattedAlias('***both***') // { text: 'both', formats: ['bold', 'italic'] }
111
+ * parseFormattedAlias('~~strike~~') // { text: 'strike', formats: ['strikethrough'] }
112
+ */
113
+ // eslint-disable-next-line react-refresh/only-export-components
114
+ export function parseFormattedAlias(alias) {
115
+ const formats = [];
116
+ let text = alias;
117
+ // Process from outermost to innermost format markers
118
+ // We loop to handle nested formats like ***bold italic*** or ~~**bold strike**~~
119
+ let changed = true;
120
+ while (changed && text.length >= 2) {
121
+ changed = false;
122
+ // Check for strikethrough: ~~text~~
123
+ if (text.startsWith('~~') && text.endsWith('~~') && text.length >= 4) {
124
+ const inner = text.slice(2, -2);
125
+ if (inner.length > 0) {
126
+ formats.push('strikethrough');
127
+ text = inner;
128
+ changed = true;
129
+ continue;
130
+ }
131
+ }
132
+ // Check for bold: **text** (must check before single *)
133
+ if (text.startsWith('**') && text.endsWith('**') && text.length >= 4) {
134
+ const inner = text.slice(2, -2);
135
+ if (inner.length > 0) {
136
+ formats.push('bold');
137
+ text = inner;
138
+ changed = true;
139
+ continue;
140
+ }
141
+ }
142
+ // Check for bold: __text__ (must check before single _)
143
+ if (text.startsWith('__') && text.endsWith('__') && text.length >= 4) {
144
+ const inner = text.slice(2, -2);
145
+ if (inner.length > 0) {
146
+ formats.push('bold');
147
+ text = inner;
148
+ changed = true;
149
+ continue;
150
+ }
151
+ }
152
+ // Check for italic: *text* (but not if inner starts/ends with * which would be **)
153
+ if (text.startsWith('*') && text.endsWith('*') && text.length >= 2 && !text.startsWith('**') && !text.endsWith('**')) {
154
+ const inner = text.slice(1, -1);
155
+ if (inner.length > 0) {
156
+ formats.push('italic');
157
+ text = inner;
158
+ changed = true;
159
+ continue;
160
+ }
161
+ }
162
+ // Check for italic: _text_ (but not if inner starts/ends with _ which would be __)
163
+ if (text.startsWith('_') && text.endsWith('_') && text.length >= 2 && !text.startsWith('__') && !text.endsWith('__')) {
164
+ const inner = text.slice(1, -1);
165
+ if (inner.length > 0) {
166
+ formats.push('italic');
167
+ text = inner;
168
+ changed = true;
169
+ continue;
170
+ }
171
+ }
172
+ }
173
+ return { text, formats };
174
+ }
175
+ /**
176
+ * Custom IMAGE transformer for ![alt](url) markdown syntax.
177
+ * Creates an ImageNode block when the pattern is completed.
178
+ */
179
+ const IMAGE = {
180
+ dependencies: [ImageNode],
181
+ // Export: convert ImageNode back to markdown
182
+ export: (node) => {
183
+ if (node instanceof ImageNode) {
184
+ const alt = node.getAlt();
185
+ const src = node.getSrc();
186
+ const title = node.getTitle();
187
+ if (title) {
188
+ return `![${alt}](${src} "${title}")`;
189
+ }
190
+ return `![${alt}](${src})`;
191
+ }
192
+ return null;
193
+ },
194
+ // Import regex for parsing markdown files (more permissive)
195
+ importRegExp: /!\[([^\]]*)\]\(([^()\s]+)(?:\s"([^"]*)")?\)/,
196
+ // Trigger regex for live typing (must end at cursor position)
197
+ regExp: /!\[([^\]]*)\]\(([^()\s]+)(?:\s"([^"]*)")?\)$/,
198
+ // Replace the matched text with an ImageNode
199
+ replace: (textNode, match) => {
200
+ const [, alt, src, title] = match;
201
+ const imageNode = $createImageNode(src, alt || '', { title });
202
+ textNode.replace(imageNode);
203
+ },
204
+ // Trigger character - transform happens when ) is typed
205
+ trigger: ')',
206
+ type: 'text-match',
207
+ };
208
+ /**
209
+ * Custom BLOCK_EQUATION transformer for $$...$$ markdown syntax.
210
+ * Creates a block (display) EquationNode when the pattern is completed.
211
+ */
212
+ const BLOCK_EQUATION = {
213
+ dependencies: [EquationNode],
214
+ export: (node) => {
215
+ if (node instanceof EquationNode && !node.isInline()) {
216
+ return `$$${node.getEquation()}$$`;
217
+ }
218
+ return null;
219
+ },
220
+ // Import regex - matches $$...$$ anywhere
221
+ importRegExp: /\$\$([^$]+)\$\$/,
222
+ // Trigger regex - must end at cursor
223
+ regExp: /\$\$([^$]+)\$\$$/,
224
+ replace: (textNode, match) => {
225
+ const [, equation] = match;
226
+ const equationNode = $createEquationNode(equation.trim(), false);
227
+ textNode.replace(equationNode);
228
+ },
229
+ trigger: '$',
230
+ type: 'text-match',
231
+ };
232
+ /**
233
+ * Custom INLINE_EQUATION transformer for $...$ markdown syntax.
234
+ * Creates an inline EquationNode when the pattern is completed.
235
+ * Note: Must come after BLOCK_EQUATION to avoid premature matching.
236
+ */
237
+ const INLINE_EQUATION = {
238
+ dependencies: [EquationNode],
239
+ export: (node) => {
240
+ if (node instanceof EquationNode && node.isInline()) {
241
+ return `$${node.getEquation()}$`;
242
+ }
243
+ return null;
244
+ },
245
+ // Import regex - matches $...$ but not $$...$$ (negative lookahead/lookbehind)
246
+ importRegExp: /(?<!\$)\$([^$]+)\$(?!\$)/,
247
+ // Trigger regex - matches single $ but not $$
248
+ regExp: /(?<!\$)\$([^$]+)\$$/,
249
+ replace: (textNode, match) => {
250
+ const [, equation] = match;
251
+ const equationNode = $createEquationNode(equation.trim(), true);
252
+ textNode.replace(equationNode);
253
+ },
254
+ trigger: '$',
255
+ type: 'text-match',
256
+ };
257
+ /**
258
+ * Custom WIKI_LINK transformer for [[target|alias]] and [[target]] markdown syntax.
259
+ * Creates a LinkNode that will be exported back as a wiki-link.
260
+ * The URL is set to target.md so the exporter knows it's a wiki-link.
261
+ *
262
+ * Supports:
263
+ * - [[page]] → page.md
264
+ * - [[page|alias]] → page.md with display text "alias"
265
+ * - [[#anchor]] → #anchor (anchor-only, no .md)
266
+ * - [[page#anchor]] → page.md#anchor
267
+ * - [[page#anchor|alias]] → page.md#anchor with display text "alias"
268
+ */
269
+ const WIKI_LINK = {
270
+ dependencies: [LinkNode],
271
+ export: (_node) => {
272
+ void _node;
273
+ // Export is handled by lexicalToMdast.ts convertLinkNode
274
+ // which detects .md URLs and exports as wiki-links
275
+ return null;
276
+ },
277
+ // Import regex - matches [[target|alias]], [[target|]] (empty alias), or [[target]]
278
+ importRegExp: /\[\[([^\]|]+)(?:\|([^\]]*))?\]\]/,
279
+ // Trigger regex - must end at cursor position
280
+ regExp: /\[\[([^\]|]+)(?:\|([^\]]*))?\]\]$/,
281
+ replace: (textNode, match) => {
282
+ const [, target, alias] = match;
283
+ // Handle anchors properly
284
+ let url;
285
+ if (target.startsWith('#')) {
286
+ // Anchor-only link: [[#anchor]] → #anchor
287
+ url = target;
288
+ }
289
+ else if (target.includes('#')) {
290
+ // Path with anchor: [[page#anchor]] → page.md#anchor
291
+ const [path, anchor] = target.split('#', 2);
292
+ const pathWithExt = getFileType(path) !== 'unknown' ? path : `${path}.md`;
293
+ url = `${pathWithExt}#${anchor}`;
294
+ }
295
+ else {
296
+ // Simple path: [[page]] → page.md (unless it already has a recognized extension)
297
+ url = getFileType(target) !== 'unknown' ? target : `${target}.md`;
298
+ }
299
+ // Parse alias for format markers (e.g., **bold**, *italic*, ~~strikethrough~~)
300
+ const rawDisplayText = alias || target;
301
+ const { text: displayText, formats } = parseFormattedAlias(rawDisplayText);
302
+ const linkNode = $createCustomLinkNode(url);
303
+ const textNodeChild = $createTextNode(displayText);
304
+ // Apply detected formats to the text node
305
+ for (const format of formats) {
306
+ textNodeChild.toggleFormat(format);
307
+ }
308
+ linkNode.append(textNodeChild);
309
+ textNode.replace(linkNode);
310
+ },
311
+ trigger: ']',
312
+ type: 'text-match',
313
+ };
314
+ /**
315
+ * Plugin that enables markdown shortcuts for block types and inline formatting.
316
+ *
317
+ * Block shortcuts (triggered at start of line):
318
+ * - "- " or "* " → Bulleted list
319
+ * - "1. " → Numbered list
320
+ * - "[] " or "[ ] " → Todo/checkbox list
321
+ * - "# ", "## ", "### " → Headings (h1-h6)
322
+ * - "> " → Block quote
323
+ * - "```" → Code block
324
+ *
325
+ * Inline formatting (Slack-style - transforms when closing delimiter is typed):
326
+ * - *text* → Italic
327
+ * - **text** → Bold
328
+ * - ***text*** → Bold + Italic
329
+ * - _text_ → Italic
330
+ * - __text__ → Bold
331
+ * - ___text___ → Bold + Italic
332
+ * - `text` → Inline code
333
+ * - ~~text~~ → Strikethrough
334
+ * - [text](url) → Link
335
+ * - [[target|alias]] → Wiki-link (Obsidian/Foam style)
336
+ * - ![alt](url) → Image
337
+ * - $equation$ → Inline math (KaTeX)
338
+ * - $$equation$$ → Block math (KaTeX)
339
+ */
340
+ export function MarkdownShortcutsPlugin() {
341
+ const [editor] = useLexicalComposerContext();
342
+ useEffect(() => {
343
+ // Register both element (block) and text format (inline) transformers
344
+ // This enables "live preview" markdown editing where typing *text* becomes italic, **text** becomes bold
345
+ return registerMarkdownShortcuts(editor, [
346
+ // Block-level transformers
347
+ UNORDERED_LIST,
348
+ ORDERED_LIST,
349
+ CHECK_LIST,
350
+ HEADING,
351
+ QUOTE,
352
+ CODE,
353
+ // Inline text format transformers (Slack-style resolution)
354
+ // Order matters: more specific patterns (longer) should come first
355
+ BOLD_ITALIC_STAR, // ***text*** → bold + italic
356
+ BOLD_ITALIC_UNDERSCORE, // ___text___ → bold + italic
357
+ BOLD_STAR, // **text** → bold
358
+ BOLD_UNDERSCORE, // __text__ → bold
359
+ ITALIC_STAR, // *text* → italic
360
+ ITALIC_UNDERSCORE, // _text_ → italic
361
+ STRIKETHROUGH, // ~~text~~ → strikethrough
362
+ INLINE_CODE, // `text` → code
363
+ // Link and image transformers
364
+ IMAGE, // ![alt](url) → image (must come before LINK)
365
+ WIKI_LINK, // [[target|alias]] → wiki-link (must come before LINK)
366
+ LINK, // [text](url) → link
367
+ // Equation transformers (block must come before inline)
368
+ BLOCK_EQUATION, // $$equation$$ → block math
369
+ INLINE_EQUATION, // $equation$ → inline math
370
+ ]);
371
+ }, [editor]);
372
+ return null;
373
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * OrderedTaskListPlugin - Click-to-toggle for ordered task-item checkboxes.
3
+ *
4
+ * @lexical/react's CheckListPlugin only wires up click/keyboard handling for
5
+ * a list whose DOM `__lexicalListType` is 'check' (see @lexical/list's
6
+ * registerCheckList/handleCheckItemEvent) — it never fires for our
7
+ * 'number'-typed ordered lists, so this plugin reimplements the click half
8
+ * of that behavior for CustomListItemNode's own ordered-task classes
9
+ * (CustomListItemNode.tsx's createDOM/updateDOM override). The hit-box
10
+ * geometry (clientX vs. the rendered ::before pseudo-element's width) is
11
+ * copied from @lexical/list's handleCheckItemEvent so clicking anywhere
12
+ * across the visible checkbox — not just its exact CSS box — toggles it,
13
+ * matching the feel of the unordered checklist.
14
+ *
15
+ * Toggling calls setTaskChecked() exclusively, never setChecked()/
16
+ * toggleChecked() — see CustomListItemNode's docstring and
17
+ * ListNode's updateChildrenListItemValue transform, which resets any
18
+ * non-null raw __checked back to undefined for a non-'check' list and would
19
+ * cascade into wiping __taskChecked too.
20
+ */
21
+ export declare function OrderedTaskListPlugin(): null;
@@ -0,0 +1,78 @@
1
+ import { useEffect } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { calculateZoomLevel } from '@lexical/utils';
4
+ import { $getNearestNodeFromDOMNode } from 'lexical';
5
+ import { $isCustomListItemNode, ORDERED_TASK_CHECKED_CLASS, ORDERED_TASK_UNCHECKED_CLASS } from './nodes/CustomListItemNode.js';
6
+ /**
7
+ * OrderedTaskListPlugin - Click-to-toggle for ordered task-item checkboxes.
8
+ *
9
+ * @lexical/react's CheckListPlugin only wires up click/keyboard handling for
10
+ * a list whose DOM `__lexicalListType` is 'check' (see @lexical/list's
11
+ * registerCheckList/handleCheckItemEvent) — it never fires for our
12
+ * 'number'-typed ordered lists, so this plugin reimplements the click half
13
+ * of that behavior for CustomListItemNode's own ordered-task classes
14
+ * (CustomListItemNode.tsx's createDOM/updateDOM override). The hit-box
15
+ * geometry (clientX vs. the rendered ::before pseudo-element's width) is
16
+ * copied from @lexical/list's handleCheckItemEvent so clicking anywhere
17
+ * across the visible checkbox — not just its exact CSS box — toggles it,
18
+ * matching the feel of the unordered checklist.
19
+ *
20
+ * Toggling calls setTaskChecked() exclusively, never setChecked()/
21
+ * toggleChecked() — see CustomListItemNode's docstring and
22
+ * ListNode's updateChildrenListItemValue transform, which resets any
23
+ * non-null raw __checked back to undefined for a non-'check' list and would
24
+ * cascade into wiping __taskChecked too.
25
+ */
26
+ export function OrderedTaskListPlugin() {
27
+ const [editor] = useLexicalComposerContext();
28
+ useEffect(() => {
29
+ const rootElement = editor.getRootElement();
30
+ if (!rootElement)
31
+ return;
32
+ const isOrderedTaskTarget = (target) => target instanceof HTMLElement &&
33
+ (target.classList.contains(ORDERED_TASK_CHECKED_CLASS) || target.classList.contains(ORDERED_TASK_UNCHECKED_CLASS));
34
+ const isCheckboxHit = (target, clientX) => {
35
+ const rect = target.getBoundingClientRect();
36
+ const zoom = calculateZoomLevel(target);
37
+ const clientXInPixels = clientX / zoom;
38
+ const beforeStyles = window.getComputedStyle(target, '::before');
39
+ const beforeWidthInPixels = parseFloat(beforeStyles.width);
40
+ return target.dir === 'rtl'
41
+ ? clientXInPixels < rect.right && clientXInPixels > rect.right - beforeWidthInPixels
42
+ : clientXInPixels > rect.left && clientXInPixels < rect.left + beforeWidthInPixels;
43
+ };
44
+ const handleMouseDown = (event) => {
45
+ const target = event.target;
46
+ if (!isOrderedTaskTarget(target))
47
+ return;
48
+ if (!isCheckboxHit(target, event.clientX))
49
+ return;
50
+ // Prevents the caret moving when clicking on the checkbox, matching
51
+ // CheckListPlugin's handleSelectDefaults for the unordered case.
52
+ event.preventDefault();
53
+ };
54
+ const handleClick = (event) => {
55
+ const target = event.target;
56
+ if (!isOrderedTaskTarget(target))
57
+ return;
58
+ if (!isCheckboxHit(target, event.clientX))
59
+ return;
60
+ if (!editor.isEditable())
61
+ return;
62
+ const domNode = target;
63
+ editor.update(() => {
64
+ const node = $getNearestNodeFromDOMNode(domNode);
65
+ if ($isCustomListItemNode(node)) {
66
+ node.setTaskChecked(!node.getTaskChecked());
67
+ }
68
+ });
69
+ };
70
+ rootElement.addEventListener('mousedown', handleMouseDown, { capture: true });
71
+ rootElement.addEventListener('click', handleClick);
72
+ return () => {
73
+ rootElement.removeEventListener('mousedown', handleMouseDown, { capture: true });
74
+ rootElement.removeEventListener('click', handleClick);
75
+ };
76
+ }, [editor]);
77
+ return null;
78
+ }
@@ -0,0 +1 @@
1
+ export declare function SearchPlugin(): import("react").JSX.Element | null;