@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.
- package/LICENSE +34 -0
- package/README.md +376 -0
- package/dist/annotations/anchor-align.d.ts +50 -0
- package/dist/annotations/anchor-align.js +74 -0
- package/dist/annotations/anchor-model.d.ts +81 -0
- package/dist/annotations/anchor-model.js +80 -0
- package/dist/annotations/anchor-resolver.d.ts +92 -0
- package/dist/annotations/anchor-resolver.js +231 -0
- package/dist/annotations/block-structure.d.ts +65 -0
- package/dist/annotations/block-structure.js +113 -0
- package/dist/annotations/types.d.ts +159 -0
- package/dist/annotations/types.js +50 -0
- package/dist/annotations.d.ts +25 -0
- package/dist/annotations.js +27 -0
- package/dist/app/App.d.ts +64 -0
- package/dist/app/App.js +180 -0
- package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
- package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
- package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
- package/dist/app/editor/AnchorScrollPlugin.js +134 -0
- package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
- package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
- package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
- package/dist/app/editor/AnnotationPlugin.js +135 -0
- package/dist/app/editor/AnnotationSurface.d.ts +34 -0
- package/dist/app/editor/AnnotationSurface.js +115 -0
- package/dist/app/editor/AssetContext.d.ts +20 -0
- package/dist/app/editor/AssetContext.js +81 -0
- package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
- package/dist/app/editor/BlockClickPlugin.js +126 -0
- package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
- package/dist/app/editor/CodeBlockPlugin.js +219 -0
- package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
- package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
- package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
- package/dist/app/editor/DragHandlePlugin.js +300 -0
- package/dist/app/editor/Editor.d.ts +84 -0
- package/dist/app/editor/Editor.js +539 -0
- package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
- package/dist/app/editor/FrontmatterPlugin.js +301 -0
- package/dist/app/editor/ImageModal.d.ts +13 -0
- package/dist/app/editor/ImageModal.js +267 -0
- package/dist/app/editor/ImagePlugin.d.ts +3 -0
- package/dist/app/editor/ImagePlugin.js +187 -0
- package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
- package/dist/app/editor/LinkClickPlugin.js +90 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
- package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
- package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
- package/dist/app/editor/SearchPlugin.d.ts +1 -0
- package/dist/app/editor/SearchPlugin.js +379 -0
- package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
- package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
- package/dist/app/editor/SlashMenu.d.ts +11 -0
- package/dist/app/editor/SlashMenu.js +494 -0
- package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
- package/dist/app/editor/SlashMenuPlugin.js +168 -0
- package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
- package/dist/app/editor/TableActionsPlugin.js +398 -0
- package/dist/app/editor/TogglePlugin.d.ts +1 -0
- package/dist/app/editor/TogglePlugin.js +159 -0
- package/dist/app/editor/Toolbar.d.ts +10 -0
- package/dist/app/editor/Toolbar.js +263 -0
- package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
- package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
- package/dist/app/editor/annotation-marks.d.ts +177 -0
- package/dist/app/editor/annotation-marks.js +750 -0
- package/dist/app/editor/annotationCommands.d.ts +13 -0
- package/dist/app/editor/annotationCommands.js +11 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
- package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
- package/dist/app/editor/c4/edge-clipping.js +139 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
- package/dist/app/editor/c4/layout.d.ts +25 -0
- package/dist/app/editor/c4/layout.js +839 -0
- package/dist/app/editor/c4/parser.d.ts +19 -0
- package/dist/app/editor/c4/parser.js +410 -0
- package/dist/app/editor/c4/render-to-string.d.ts +24 -0
- package/dist/app/editor/c4/render-to-string.js +34 -0
- package/dist/app/editor/c4/renderer.d.ts +64 -0
- package/dist/app/editor/c4/renderer.js +569 -0
- package/dist/app/editor/c4/types.d.ts +203 -0
- package/dist/app/editor/c4/types.js +43 -0
- package/dist/app/editor/correction-yaml.d.ts +10 -0
- package/dist/app/editor/correction-yaml.js +40 -0
- package/dist/app/editor/editorNodes.d.ts +9 -0
- package/dist/app/editor/editorNodes.js +36 -0
- package/dist/app/editor/index.d.ts +1 -0
- package/dist/app/editor/index.js +1 -0
- package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
- package/dist/app/editor/mark-overlap-resolver.js +45 -0
- package/dist/app/editor/nodes/C4Component.d.ts +14 -0
- package/dist/app/editor/nodes/C4Component.js +284 -0
- package/dist/app/editor/nodes/C4Node.d.ts +32 -0
- package/dist/app/editor/nodes/C4Node.js +112 -0
- package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
- package/dist/app/editor/nodes/CalloutNode.js +94 -0
- package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
- package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
- package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
- package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
- package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
- package/dist/app/editor/nodes/CustomListNode.js +54 -0
- package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
- package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
- package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
- package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
- package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
- package/dist/app/editor/nodes/EquationComponent.js +130 -0
- package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
- package/dist/app/editor/nodes/EquationNode.js +179 -0
- package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
- package/dist/app/editor/nodes/FootnoteNode.js +151 -0
- package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
- package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
- package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
- package/dist/app/editor/nodes/HtmlNode.js +121 -0
- package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
- package/dist/app/editor/nodes/ImageComponent.js +228 -0
- package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
- package/dist/app/editor/nodes/ImageNode.js +183 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
- package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
- package/dist/app/editor/nodes/MermaidComponent.js +216 -0
- package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
- package/dist/app/editor/nodes/MermaidNode.js +96 -0
- package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
- package/dist/app/editor/nodes/ToggleNode.js +237 -0
- package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
- package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
- package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
- package/dist/app/editor/nodes/diagram-utils.js +29 -0
- package/dist/app/editor/nodes/index.d.ts +30 -0
- package/dist/app/editor/nodes/index.js +17 -0
- package/dist/app/editor/utils.d.ts +2 -0
- package/dist/app/editor/utils.js +17 -0
- package/dist/app/mapper/index.d.ts +3 -0
- package/dist/app/mapper/index.js +2 -0
- package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
- package/dist/app/mapper/lexicalToMdast.js +1937 -0
- package/dist/app/mapper/mdastToLexical.d.ts +31 -0
- package/dist/app/mapper/mdastToLexical.js +1075 -0
- package/dist/contract.d.ts +8 -0
- package/dist/contract.js +8 -0
- package/dist/headless.d.ts +40 -0
- package/dist/headless.js +45 -0
- package/dist/host/context.d.ts +20 -0
- package/dist/host/context.js +24 -0
- package/dist/host/defaults.d.ts +21 -0
- package/dist/host/defaults.js +47 -0
- package/dist/host/messages.d.ts +26 -0
- package/dist/host/messages.js +47 -0
- package/dist/host/types.d.ts +75 -0
- package/dist/host/types.js +13 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +51 -0
- package/dist/markdown/annotate-sentinels.d.ts +42 -0
- package/dist/markdown/annotate-sentinels.js +86 -0
- package/dist/markdown/parse.d.ts +56 -0
- package/dist/markdown/parse.js +516 -0
- package/dist/markdown/stringify.d.ts +7 -0
- package/dist/markdown/stringify.js +439 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
- package/dist/markdown.d.ts +28 -0
- package/dist/markdown.js +44 -0
- package/dist/mathjax-config.d.ts +89 -0
- package/dist/mathjax-config.js +133 -0
- package/dist/nodes.d.ts +34 -0
- package/dist/nodes.js +34 -0
- package/dist/stores/correctionStore.d.ts +15 -0
- package/dist/stores/correctionStore.js +12 -0
- package/dist/stores/editorStore.d.ts +16 -0
- package/dist/stores/editorStore.js +27 -0
- package/dist/styles.css +2477 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.js +91 -0
- package/dist/utils/file-types.d.ts +10 -0
- package/dist/utils/file-types.js +46 -0
- package/docs/annotations.md +134 -0
- package/docs/decisions/README.md +72 -0
- package/docs/decisions/adr-002.md +27 -0
- package/docs/decisions/adr-007.md +27 -0
- package/docs/decisions/adr-010.md +26 -0
- package/docs/decisions/adr-024.md +124 -0
- package/docs/decisions/adr-024b.md +0 -0
- package/docs/decisions/adr-025.md +103 -0
- package/docs/decisions/adr-027.md +66 -0
- package/docs/decisions/adr-057.md +67 -0
- package/docs/decisions/adr-070.md +131 -0
- package/docs/decisions/adr-075.md +410 -0
- package/docs/decisions/adr-076.md +183 -0
- package/docs/decisions/adr-077.md +410 -0
- package/docs/decisions/adr-078.md +243 -0
- package/docs/decisions/adr-079.md +167 -0
- package/docs/decisions/adr-080.md +325 -0
- package/docs/decisions/adr-081.md +108 -0
- package/docs/decisions/adr-082.md +123 -0
- package/docs/decisions/adr-083.md +204 -0
- package/docs/decisions/adr-084.md +179 -0
- package/docs/editor-api.md +106 -0
- package/docs/markdown-pipeline.md +282 -0
- package/docs/provenance.md +106 -0
- package/docs/zusammen-editor-capability-map.md +232 -0
- 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
|
+
* -  → Image
|
|
46
|
+
* - $equation$ → Inline math (KaTeX)
|
|
47
|
+
* - $$equation$$ → Block math (KaTeX)
|
|
48
|
+
*/
|
|
49
|
+
export declare function MarkdownShortcutsPlugin(): null;
|