@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,187 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState, useRef } from 'react';
3
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
+ import { $getNodeByKey, $getNearestNodeFromDOMNode, $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical';
5
+ import { ImageModal } from './ImageModal.js';
6
+ import { $createImageNode } from './nodes/index.js';
7
+ import { useEditorHost } from '../../host/context.js';
8
+ import { useHostMessages } from '../../host/messages.js';
9
+ // eslint-disable-next-line react-refresh/only-export-components
10
+ export const INSERT_IMAGE_COMMAND = createCommand('INSERT_IMAGE_COMMAND');
11
+ const HANDLED_IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
12
+ const MAX_IMAGE_SIZE = 5 * 1024 * 1024; // 5 MB
13
+ export function ImagePlugin() {
14
+ const [editor] = useLexicalComposerContext();
15
+ const { bridge } = useEditorHost();
16
+ const { writeAsset } = useHostMessages();
17
+ const [isModalOpen, setIsModalOpen] = useState(false);
18
+ const pendingQueueRef = useRef([]);
19
+ // Listen for INSERT_IMAGE_COMMAND
20
+ useEffect(() => {
21
+ return editor.registerCommand(INSERT_IMAGE_COMMAND, () => {
22
+ setIsModalOpen(true);
23
+ return true;
24
+ }, COMMAND_PRIORITY_EDITOR);
25
+ }, [editor]);
26
+ // Listen for ASSET_WRITTEN messages
27
+ useEffect(() => {
28
+ const removeHandler = bridge.addMessageHandler((message) => {
29
+ if (message.type === 'ASSET_WRITTEN' && pendingQueueRef.current.length > 0) {
30
+ const item = pendingQueueRef.current.shift();
31
+ const { alt, dropTargetKey } = item;
32
+ const relPath = message.relPath;
33
+ const webviewUri = message.webviewUri;
34
+ editor.update(() => {
35
+ if (dropTargetKey !== null) {
36
+ const node = $getNodeByKey(dropTargetKey);
37
+ if (node)
38
+ node.selectEnd();
39
+ }
40
+ const selection = $getSelection();
41
+ if ($isRangeSelection(selection)) {
42
+ const imageNode = $createImageNode(relPath, alt, { displaySrc: webviewUri });
43
+ selection.insertNodes([imageNode]);
44
+ }
45
+ });
46
+ }
47
+ else if (message.type === 'ASSET_WRITE_FAILED' && pendingQueueRef.current.length > 0) {
48
+ pendingQueueRef.current.shift();
49
+ }
50
+ });
51
+ return removeHandler;
52
+ }, [editor, bridge]);
53
+ // Clipboard paste interceptor: captures image/* paste events before Lexical sees them
54
+ useEffect(() => {
55
+ const rootElement = editor.getRootElement();
56
+ if (!rootElement)
57
+ return;
58
+ function handlePaste(e) {
59
+ const items = e.clipboardData?.items;
60
+ if (!items)
61
+ return;
62
+ let imageFile = null;
63
+ for (const item of items) {
64
+ if (item.kind === 'file' && HANDLED_IMAGE_TYPES.includes(item.type)) {
65
+ const file = item.getAsFile();
66
+ if (file) {
67
+ imageFile = file;
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ if (!imageFile)
73
+ return;
74
+ // Suppress the paste event whenever an image is present — even if oversized —
75
+ // so Lexical's default handler never inserts a broken blob URL.
76
+ e.preventDefault();
77
+ e.stopPropagation();
78
+ if (imageFile.size > MAX_IMAGE_SIZE) {
79
+ console.warn('ImagePlugin: pasted image exceeds 5 MB limit, ignoring');
80
+ return;
81
+ }
82
+ const file = imageFile;
83
+ const reader = new FileReader();
84
+ reader.onload = () => {
85
+ const dataUri = reader.result;
86
+ const rawExt = file.type.split('/')[1];
87
+ const ext = rawExt === 'jpeg' ? 'jpg' : rawExt;
88
+ const suggestedName = `pasted-image-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`;
89
+ pendingQueueRef.current.push({ alt: '', dropTargetKey: null });
90
+ writeAsset(dataUri, suggestedName);
91
+ };
92
+ reader.readAsDataURL(file);
93
+ }
94
+ rootElement.addEventListener('paste', handlePaste, { capture: true });
95
+ return () => rootElement.removeEventListener('paste', handlePaste, { capture: true });
96
+ }, [editor, writeAsset]);
97
+ // Drag-drop interceptor: handles external image file drops
98
+ useEffect(() => {
99
+ const rootElement = editor.getRootElement();
100
+ if (!rootElement)
101
+ return;
102
+ function handleDragOver(e) {
103
+ const items = e.dataTransfer?.items;
104
+ if (!items)
105
+ return;
106
+ let hasImage = false;
107
+ for (const item of Array.from(items)) {
108
+ if (item.kind === 'file' && HANDLED_IMAGE_TYPES.includes(item.type)) {
109
+ hasImage = true;
110
+ break;
111
+ }
112
+ }
113
+ if (hasImage) {
114
+ e.preventDefault();
115
+ if (e.dataTransfer)
116
+ e.dataTransfer.dropEffect = 'copy';
117
+ }
118
+ }
119
+ function handleDrop(e) {
120
+ const files = Array.from(e.dataTransfer?.files ?? []);
121
+ const imageFile = files.find(f => HANDLED_IMAGE_TYPES.includes(f.type));
122
+ if (!imageFile)
123
+ return;
124
+ e.preventDefault();
125
+ e.stopPropagation();
126
+ if (imageFile.size > MAX_IMAGE_SIZE) {
127
+ console.warn('ImagePlugin: dropped image exceeds 5 MB limit, ignoring');
128
+ return;
129
+ }
130
+ // Attempt to find the Lexical node key at the drop position
131
+ let dropTargetKey = null;
132
+ if (typeof document.caretRangeFromPoint === 'function') {
133
+ const range = document.caretRangeFromPoint(e.clientX, e.clientY);
134
+ if (range) {
135
+ const domNode = range.startContainer instanceof Element
136
+ ? range.startContainer
137
+ : range.startContainer.parentElement;
138
+ if (domNode) {
139
+ editor.getEditorState().read(() => {
140
+ const lexNode = $getNearestNodeFromDOMNode(domNode);
141
+ if (lexNode)
142
+ dropTargetKey = lexNode.getKey();
143
+ });
144
+ }
145
+ }
146
+ }
147
+ const file = imageFile;
148
+ const dot = file.name.lastIndexOf('.');
149
+ const nameBase = dot > 0 ? file.name.slice(0, dot) : file.name;
150
+ const nameExt = dot > 0 ? file.name.slice(dot) : '';
151
+ const sanitizedName = nameBase.replace(/[^a-zA-Z0-9._-]/g, '-').slice(0, 95) + nameExt.slice(0, 10);
152
+ const reader = new FileReader();
153
+ reader.onload = () => {
154
+ const dataUri = reader.result;
155
+ pendingQueueRef.current.push({ alt: '', dropTargetKey });
156
+ writeAsset(dataUri, sanitizedName);
157
+ };
158
+ reader.readAsDataURL(file);
159
+ }
160
+ rootElement.addEventListener('dragover', handleDragOver);
161
+ rootElement.addEventListener('drop', handleDrop);
162
+ return () => {
163
+ rootElement.removeEventListener('dragover', handleDragOver);
164
+ rootElement.removeEventListener('drop', handleDrop);
165
+ };
166
+ }, [editor, writeAsset]);
167
+ const handleClose = useCallback(() => {
168
+ setIsModalOpen(false);
169
+ }, []);
170
+ const handleInsert = useCallback((data) => {
171
+ if (data.isLocal && data.dataUri) {
172
+ pendingQueueRef.current.push({ alt: data.alt, dropTargetKey: null });
173
+ writeAsset(data.dataUri);
174
+ }
175
+ else {
176
+ // Direct URL — insert immediately without going through the asset pipeline
177
+ editor.update(() => {
178
+ const selection = $getSelection();
179
+ if ($isRangeSelection(selection)) {
180
+ const imageNode = $createImageNode(data.src, data.alt);
181
+ selection.insertNodes([imageNode]);
182
+ }
183
+ });
184
+ }
185
+ }, [editor, writeAsset]);
186
+ return (_jsx(ImageModal, { isOpen: isModalOpen, onClose: handleClose, onInsert: handleInsert }));
187
+ }
@@ -0,0 +1,23 @@
1
+ interface LinkClickPluginProps {
2
+ /** When false (read-only mode), clicks follow links immediately without requiring modifier key */
3
+ editable?: boolean;
4
+ }
5
+ /**
6
+ * LinkClickPlugin - Controls link click behavior
7
+ *
8
+ * In editable mode:
9
+ * - Regular click: Edit the link (prevents default navigation)
10
+ * - Cmd+click (Mac) / Ctrl+click (Windows/Linux): Open the link
11
+ *
12
+ * In read-only mode:
13
+ * - Regular click: Open the link immediately (no modifier key required)
14
+ *
15
+ * Links:
16
+ * - Wiki-links (.md files): Opens the linked document in the editor
17
+ * - External URLs (http/https): Opens in default browser
18
+ *
19
+ * Also provides visual feedback: cursor changes to pointer when hovering over a link
20
+ * (in read-only mode) or when Cmd/Ctrl is held (in editable mode).
21
+ */
22
+ export declare function LinkClickPlugin({ editable }: LinkClickPluginProps): null;
23
+ export {};
@@ -0,0 +1,90 @@
1
+ import { useEffect } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { useHostMessages } from '../../host/messages.js';
4
+ /**
5
+ * LinkClickPlugin - Controls link click behavior
6
+ *
7
+ * In editable mode:
8
+ * - Regular click: Edit the link (prevents default navigation)
9
+ * - Cmd+click (Mac) / Ctrl+click (Windows/Linux): Open the link
10
+ *
11
+ * In read-only mode:
12
+ * - Regular click: Open the link immediately (no modifier key required)
13
+ *
14
+ * Links:
15
+ * - Wiki-links (.md files): Opens the linked document in the editor
16
+ * - External URLs (http/https): Opens in default browser
17
+ *
18
+ * Also provides visual feedback: cursor changes to pointer when hovering over a link
19
+ * (in read-only mode) or when Cmd/Ctrl is held (in editable mode).
20
+ */
21
+ export function LinkClickPlugin({ editable = true }) {
22
+ const [editor] = useLexicalComposerContext();
23
+ const { openLink } = useHostMessages();
24
+ useEffect(() => {
25
+ const rootElement = editor.getRootElement();
26
+ if (!rootElement)
27
+ return;
28
+ const handleClick = (event) => {
29
+ // Find if we clicked on a link
30
+ const target = event.target;
31
+ const linkElement = target.closest('a');
32
+ if (!linkElement)
33
+ return;
34
+ // Check for modifier key (Cmd on Mac, Ctrl on Windows/Linux)
35
+ const isModifierClick = event.metaKey || event.ctrlKey;
36
+ // In read-only mode: click opens link immediately
37
+ // In editable mode: need modifier key to open link
38
+ const shouldOpenLink = !editable || isModifierClick;
39
+ if (shouldOpenLink) {
40
+ // Open the link
41
+ event.preventDefault();
42
+ event.stopPropagation();
43
+ // URL is stored in data-href (not href) to prevent webview interception
44
+ const url = linkElement.getAttribute('data-href');
45
+ if (url) {
46
+ openLink(url);
47
+ }
48
+ }
49
+ // In editable mode without modifier: No action needed - since there's no href,
50
+ // the browser won't navigate, and the editor handles placing the cursor normally
51
+ };
52
+ // Handle modifier key state for cursor feedback (only needed in editable mode)
53
+ const handleKeyDown = (event) => {
54
+ if (editable && (event.metaKey || event.ctrlKey)) {
55
+ rootElement.classList.add('modifier-held');
56
+ }
57
+ };
58
+ const handleKeyUp = (event) => {
59
+ // Remove class when Cmd/Ctrl is released
60
+ if (event.key === 'Meta' || event.key === 'Control') {
61
+ rootElement.classList.remove('modifier-held');
62
+ }
63
+ };
64
+ // Also handle when window loses focus (user switches apps while holding Cmd)
65
+ const handleBlur = () => {
66
+ rootElement.classList.remove('modifier-held');
67
+ };
68
+ // In read-only mode, always show pointer cursor on links
69
+ if (!editable) {
70
+ rootElement.classList.add('links-clickable');
71
+ }
72
+ else {
73
+ rootElement.classList.remove('links-clickable');
74
+ }
75
+ // Use capture phase to intercept before the link's default behavior
76
+ rootElement.addEventListener('click', handleClick, { capture: true });
77
+ window.addEventListener('keydown', handleKeyDown);
78
+ window.addEventListener('keyup', handleKeyUp);
79
+ window.addEventListener('blur', handleBlur);
80
+ return () => {
81
+ rootElement.removeEventListener('click', handleClick, { capture: true });
82
+ window.removeEventListener('keydown', handleKeyDown);
83
+ window.removeEventListener('keyup', handleKeyUp);
84
+ window.removeEventListener('blur', handleBlur);
85
+ rootElement.classList.remove('modifier-held');
86
+ rootElement.classList.remove('links-clickable');
87
+ };
88
+ }, [editor, editable, openLink]);
89
+ return null;
90
+ }
@@ -0,0 +1,49 @@
1
+ import { type ElementTransformer } from '@lexical/markdown';
2
+ import type { TextFormatType } from 'lexical';
3
+ export declare const UNORDERED_LIST: ElementTransformer;
4
+ export declare const ORDERED_LIST: ElementTransformer;
5
+ export declare const CHECK_LIST: ElementTransformer;
6
+ /**
7
+ * Parses a wiki link alias and extracts format markers.
8
+ * Supports nested formats like ***bold italic***.
9
+ *
10
+ * @param alias - The raw alias text potentially containing format markers
11
+ * @returns Object with the plain text and array of formats to apply
12
+ *
13
+ * @example
14
+ * parseFormattedAlias('**bold**') // { text: 'bold', formats: ['bold'] }
15
+ * parseFormattedAlias('*italic*') // { text: 'italic', formats: ['italic'] }
16
+ * parseFormattedAlias('***both***') // { text: 'both', formats: ['bold', 'italic'] }
17
+ * parseFormattedAlias('~~strike~~') // { text: 'strike', formats: ['strikethrough'] }
18
+ */
19
+ export declare function parseFormattedAlias(alias: string): {
20
+ text: string;
21
+ formats: TextFormatType[];
22
+ };
23
+ /**
24
+ * Plugin that enables markdown shortcuts for block types and inline formatting.
25
+ *
26
+ * Block shortcuts (triggered at start of line):
27
+ * - "- " or "* " → Bulleted list
28
+ * - "1. " → Numbered list
29
+ * - "[] " or "[ ] " → Todo/checkbox list
30
+ * - "# ", "## ", "### " → Headings (h1-h6)
31
+ * - "> " → Block quote
32
+ * - "```" → Code block
33
+ *
34
+ * Inline formatting (Slack-style - transforms when closing delimiter is typed):
35
+ * - *text* → Italic
36
+ * - **text** → Bold
37
+ * - ***text*** → Bold + Italic
38
+ * - _text_ → Italic
39
+ * - __text__ → Bold
40
+ * - ___text___ → Bold + Italic
41
+ * - `text` → Inline code
42
+ * - ~~text~~ → Strikethrough
43
+ * - [text](url) → Link
44
+ * - [[target|alias]] → Wiki-link (Obsidian/Foam style)
45
+ * - ![alt](url) → Image
46
+ * - $equation$ → Inline math (KaTeX)
47
+ * - $$equation$$ → Block math (KaTeX)
48
+ */
49
+ export declare function MarkdownShortcutsPlugin(): null;