@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,228 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { $getNodeByKey } from 'lexical';
|
|
5
|
+
import { $isImageNode } from './ImageNode.js';
|
|
6
|
+
import { useAssetContext } from '../AssetContext.js';
|
|
7
|
+
import { DiagramContextMenu } from './DiagramContextMenu.js';
|
|
8
|
+
import { useEditorHost } from '../../../host/context.js';
|
|
9
|
+
function isAbsoluteUrl(path) {
|
|
10
|
+
return (path.startsWith('http://') ||
|
|
11
|
+
path.startsWith('https://') ||
|
|
12
|
+
path.startsWith('data:') ||
|
|
13
|
+
path.startsWith('blob:') ||
|
|
14
|
+
path.startsWith('vscode-webview-resource:'));
|
|
15
|
+
}
|
|
16
|
+
export function ImageComponent({ src, alt, title, width, height, nodeKey, }) {
|
|
17
|
+
const [editor] = useLexicalComposerContext();
|
|
18
|
+
const { resolveAssetPath, resolveLocalAsset } = useAssetContext();
|
|
19
|
+
const { notifyError } = useEditorHost();
|
|
20
|
+
const imageRef = useRef(null);
|
|
21
|
+
// Resolve the src path for display
|
|
22
|
+
const resolvedSrc = resolveAssetPath(src);
|
|
23
|
+
const [localDataUrl, setLocalDataUrl] = useState(null);
|
|
24
|
+
const [loadError, setLoadError] = useState(false);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (isAbsoluteUrl(resolvedSrc) || !resolveLocalAsset) {
|
|
27
|
+
setLocalDataUrl(null);
|
|
28
|
+
setLoadError(false);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
let cancelled = false;
|
|
32
|
+
setLoadError(false);
|
|
33
|
+
resolveLocalAsset(resolvedSrc).then((dataUrl) => {
|
|
34
|
+
if (!cancelled) {
|
|
35
|
+
if (dataUrl) {
|
|
36
|
+
setLocalDataUrl(dataUrl);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
setLoadError(true);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}).catch(() => {
|
|
43
|
+
if (!cancelled)
|
|
44
|
+
setLoadError(true);
|
|
45
|
+
});
|
|
46
|
+
return () => { cancelled = true; };
|
|
47
|
+
}, [resolvedSrc, resolveLocalAsset]);
|
|
48
|
+
// Use local data URL for relative paths, otherwise use the resolved src directly
|
|
49
|
+
const displaySrc = isAbsoluteUrl(resolvedSrc) ? resolvedSrc : (localDataUrl ?? resolvedSrc);
|
|
50
|
+
// Context menu state
|
|
51
|
+
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 });
|
|
52
|
+
const handleContextMenu = useCallback((e) => {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
e.stopPropagation();
|
|
55
|
+
setContextMenu({ visible: true, x: e.clientX, y: e.clientY });
|
|
56
|
+
}, []);
|
|
57
|
+
const handleCopyImage = useCallback(async () => {
|
|
58
|
+
const img = imageRef.current;
|
|
59
|
+
if (!img) {
|
|
60
|
+
setContextMenu((prev) => ({ ...prev, visible: false }));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
// Use rendered size (not naturalWidth which can be 0 for viewBox-only SVGs)
|
|
65
|
+
const scale = window.devicePixelRatio || 2;
|
|
66
|
+
const rect = img.getBoundingClientRect();
|
|
67
|
+
const w = rect.width;
|
|
68
|
+
const h = rect.height;
|
|
69
|
+
if (!w || !h) {
|
|
70
|
+
setContextMenu((prev) => ({ ...prev, visible: false }));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const canvas = document.createElement('canvas');
|
|
74
|
+
const canvasWidth = Math.round(w * scale);
|
|
75
|
+
const canvasHeight = Math.round(h * scale);
|
|
76
|
+
canvas.width = canvasWidth;
|
|
77
|
+
canvas.height = canvasHeight;
|
|
78
|
+
const ctx = canvas.getContext('2d');
|
|
79
|
+
ctx.scale(canvasWidth / w, canvasHeight / h);
|
|
80
|
+
ctx.drawImage(img, 0, 0, w, h);
|
|
81
|
+
const blob = await new Promise((resolve, reject) => {
|
|
82
|
+
canvas.toBlob((b) => b ? resolve(b) : reject(new Error('toBlob failed')), 'image/png');
|
|
83
|
+
});
|
|
84
|
+
await navigator.clipboard.write([
|
|
85
|
+
new ClipboardItem({ 'image/png': blob })
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
console.error('Failed to copy image to clipboard:', err);
|
|
90
|
+
// The <img> no longer requests CORS mode (see crossOrigin removal below),
|
|
91
|
+
// so drawing a cross-origin image onto the canvas taints it and toBlob()
|
|
92
|
+
// rejects with a SecurityError. That's an accepted tradeoff: display
|
|
93
|
+
// wins over copy for remote images that don't support CORS. Since this
|
|
94
|
+
// is now the primary expected trigger for this catch (rather than an
|
|
95
|
+
// edge case), give it a message that explains the cause instead of the
|
|
96
|
+
// generic DOMException text.
|
|
97
|
+
const isTaintedCanvas = err instanceof DOMException && err.name === 'SecurityError';
|
|
98
|
+
notifyError('Failed to copy image', isTaintedCanvas
|
|
99
|
+
? 'This image is hosted on another site that does not allow copying — try saving it instead.'
|
|
100
|
+
: err instanceof Error ? err.message : String(err));
|
|
101
|
+
}
|
|
102
|
+
setContextMenu((prev) => ({ ...prev, visible: false }));
|
|
103
|
+
}, [notifyError]);
|
|
104
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
105
|
+
const [isSelected, setIsSelected] = useState(false);
|
|
106
|
+
const [currentWidth, setCurrentWidth] = useState(width);
|
|
107
|
+
const [naturalWidth, setNaturalWidth] = useState(0);
|
|
108
|
+
const [naturalHeight, setNaturalHeight] = useState(0);
|
|
109
|
+
// Track resize start position
|
|
110
|
+
const resizeStartRef = useRef(null);
|
|
111
|
+
// Update current width when prop changes
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
setCurrentWidth(width);
|
|
114
|
+
}, [width]);
|
|
115
|
+
// Handle image load to get natural dimensions
|
|
116
|
+
const handleImageLoad = useCallback(() => {
|
|
117
|
+
if (imageRef.current) {
|
|
118
|
+
setNaturalWidth(imageRef.current.naturalWidth);
|
|
119
|
+
setNaturalHeight(imageRef.current.naturalHeight);
|
|
120
|
+
}
|
|
121
|
+
}, []);
|
|
122
|
+
// Handle click to select image
|
|
123
|
+
const handleClick = useCallback((e) => {
|
|
124
|
+
e.stopPropagation();
|
|
125
|
+
setIsSelected(true);
|
|
126
|
+
}, []);
|
|
127
|
+
// Handle click outside to deselect
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const handleClickOutside = (e) => {
|
|
130
|
+
if (imageRef.current && !imageRef.current.parentElement?.contains(e.target)) {
|
|
131
|
+
setIsSelected(false);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
135
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
136
|
+
}, []);
|
|
137
|
+
// Handle resize start
|
|
138
|
+
const handleResizeStart = useCallback((e, corner) => {
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
e.stopPropagation();
|
|
141
|
+
const startWidth = currentWidth || imageRef.current?.offsetWidth || naturalWidth;
|
|
142
|
+
resizeStartRef.current = {
|
|
143
|
+
startX: e.clientX,
|
|
144
|
+
startWidth,
|
|
145
|
+
corner,
|
|
146
|
+
};
|
|
147
|
+
setIsResizing(true);
|
|
148
|
+
}, [currentWidth, naturalWidth]);
|
|
149
|
+
// Use refs for values accessed in event handlers to avoid effect re-registration
|
|
150
|
+
const currentWidthRef = useRef(currentWidth);
|
|
151
|
+
currentWidthRef.current = currentWidth;
|
|
152
|
+
const naturalWidthRef = useRef(naturalWidth);
|
|
153
|
+
naturalWidthRef.current = naturalWidth;
|
|
154
|
+
const naturalHeightRef = useRef(naturalHeight);
|
|
155
|
+
naturalHeightRef.current = naturalHeight;
|
|
156
|
+
// Handle resize move
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (!isResizing)
|
|
159
|
+
return;
|
|
160
|
+
const handleMouseMove = (e) => {
|
|
161
|
+
if (!resizeStartRef.current)
|
|
162
|
+
return;
|
|
163
|
+
const { startX, startWidth, corner } = resizeStartRef.current;
|
|
164
|
+
let deltaX = e.clientX - startX;
|
|
165
|
+
// For left corners, invert the delta
|
|
166
|
+
if (corner === 'nw' || corner === 'sw') {
|
|
167
|
+
deltaX = -deltaX;
|
|
168
|
+
}
|
|
169
|
+
const newWidth = Math.max(50, startWidth + deltaX);
|
|
170
|
+
setCurrentWidth(newWidth);
|
|
171
|
+
};
|
|
172
|
+
const handleMouseUp = () => {
|
|
173
|
+
const width = currentWidthRef.current;
|
|
174
|
+
const natWidth = naturalWidthRef.current;
|
|
175
|
+
const natHeight = naturalHeightRef.current;
|
|
176
|
+
if (resizeStartRef.current && width) {
|
|
177
|
+
// Calculate proportional height
|
|
178
|
+
const aspectRatio = natHeight / natWidth;
|
|
179
|
+
const newHeight = Math.round(width * aspectRatio);
|
|
180
|
+
// Update the node with new dimensions
|
|
181
|
+
editor.update(() => {
|
|
182
|
+
const node = $getNodeByKey(nodeKey);
|
|
183
|
+
if ($isImageNode(node)) {
|
|
184
|
+
node.setWidth(width);
|
|
185
|
+
node.setHeight(newHeight);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
resizeStartRef.current = null;
|
|
190
|
+
setIsResizing(false);
|
|
191
|
+
};
|
|
192
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
193
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
194
|
+
return () => {
|
|
195
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
196
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
197
|
+
};
|
|
198
|
+
}, [isResizing, editor, nodeKey]);
|
|
199
|
+
// Calculate display dimensions
|
|
200
|
+
const displayWidth = currentWidth || width;
|
|
201
|
+
const displayHeight = displayWidth && naturalWidth && naturalHeight
|
|
202
|
+
? Math.round(displayWidth * (naturalHeight / naturalWidth))
|
|
203
|
+
: height;
|
|
204
|
+
// Show placeholder while loading local assets
|
|
205
|
+
if (!isAbsoluteUrl(resolvedSrc) && resolveLocalAsset && !localDataUrl && !loadError) {
|
|
206
|
+
return (_jsx("div", { className: "image-wrapper", children: _jsx("div", { className: "image-placeholder", style: { padding: '1em', opacity: 0.5 }, children: "Loading image\u2026" }) }));
|
|
207
|
+
}
|
|
208
|
+
if (loadError) {
|
|
209
|
+
return (_jsx("div", { className: "image-wrapper", children: _jsxs("div", { className: "image-placeholder", style: { padding: '1em', opacity: 0.5 }, children: ["Image not found: ", src] }) }));
|
|
210
|
+
}
|
|
211
|
+
return (_jsxs("div", { className: `image-wrapper ${isSelected ? 'selected' : ''} ${isResizing ? 'resizing' : ''} ${!displayWidth ? 'no-dimensions' : ''}`, onClick: handleClick, onContextMenu: handleContextMenu, children: [_jsx("img", { ref: imageRef, src: displaySrc, alt: alt, title: title, draggable: false,
|
|
212
|
+
// No crossOrigin attribute: requesting CORS mode makes display of a
|
|
213
|
+
// remote image depend on the host sending Access-Control-Allow-Origin,
|
|
214
|
+
// which most ordinary image hosts (e.g. shields.io badges) don't do —
|
|
215
|
+
// the browser would render nothing instead of the image. The only
|
|
216
|
+
// consumer of an untainted canvas is handleCopyImage's clipboard
|
|
217
|
+
// read-back above, which already handles (and now surfaces) failure
|
|
218
|
+
// when a cross-origin image taints the canvas.
|
|
219
|
+
onLoad: handleImageLoad, style: {
|
|
220
|
+
// If an explicit width is set (from resize), use it.
|
|
221
|
+
// Otherwise, let the image display at natural size but cap at 100%
|
|
222
|
+
// of the container — raster images should never scale up beyond
|
|
223
|
+
// their natural dimensions.
|
|
224
|
+
width: displayWidth ? `${displayWidth}px` : undefined,
|
|
225
|
+
height: displayHeight ? `${displayHeight}px` : undefined,
|
|
226
|
+
maxWidth: '100%',
|
|
227
|
+
} }), isSelected && (_jsxs(_Fragment, { children: [_jsx("div", { className: "image-resize-handle nw", onMouseDown: (e) => handleResizeStart(e, 'nw') }), _jsx("div", { className: "image-resize-handle ne", onMouseDown: (e) => handleResizeStart(e, 'ne') }), _jsx("div", { className: "image-resize-handle sw", onMouseDown: (e) => handleResizeStart(e, 'sw') }), _jsx("div", { className: "image-resize-handle se", onMouseDown: (e) => handleResizeStart(e, 'se') })] })), isSelected && displayWidth && (_jsxs("div", { className: "image-size-badge", children: [displayWidth, " \u00D7 ", displayHeight] })), _jsx(DiagramContextMenu, { visible: contextMenu.visible, x: contextMenu.x, y: contextMenu.y, onCopyImage: handleCopyImage, onClose: () => setContextMenu((prev) => ({ ...prev, visible: false })) })] }));
|
|
228
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DecoratorNode, DOMConversionMap, DOMExportOutput, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from 'lexical';
|
|
2
|
+
export type SerializedImageNode = Spread<{
|
|
3
|
+
src: string;
|
|
4
|
+
alt: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
8
|
+
linkUrl?: string;
|
|
9
|
+
}, SerializedLexicalNode>;
|
|
10
|
+
export declare class ImageNode extends DecoratorNode<JSX.Element> {
|
|
11
|
+
__src: string;
|
|
12
|
+
__alt: string;
|
|
13
|
+
__title: string | undefined;
|
|
14
|
+
__displaySrc: string | undefined;
|
|
15
|
+
__width: number | undefined;
|
|
16
|
+
__height: number | undefined;
|
|
17
|
+
__linkUrl: string | undefined;
|
|
18
|
+
static getType(): string;
|
|
19
|
+
static clone(node: ImageNode): ImageNode;
|
|
20
|
+
constructor(src: string, alt: string, options?: {
|
|
21
|
+
title?: string;
|
|
22
|
+
linkUrl?: string;
|
|
23
|
+
key?: NodeKey;
|
|
24
|
+
});
|
|
25
|
+
getSrc(): string;
|
|
26
|
+
getAlt(): string;
|
|
27
|
+
getTitle(): string | undefined;
|
|
28
|
+
setSrc(src: string): void;
|
|
29
|
+
setAlt(alt: string): void;
|
|
30
|
+
setTitle(title: string | undefined): void;
|
|
31
|
+
getDisplaySrc(): string;
|
|
32
|
+
setDisplaySrc(displaySrc: string | undefined): void;
|
|
33
|
+
getWidth(): number | undefined;
|
|
34
|
+
setWidth(width: number | undefined): void;
|
|
35
|
+
getHeight(): number | undefined;
|
|
36
|
+
setHeight(height: number | undefined): void;
|
|
37
|
+
getLinkUrl(): string | undefined;
|
|
38
|
+
setLinkUrl(linkUrl: string | undefined): void;
|
|
39
|
+
createDOM(): HTMLElement;
|
|
40
|
+
updateDOM(): boolean;
|
|
41
|
+
static importDOM(): DOMConversionMap | null;
|
|
42
|
+
exportDOM(): DOMExportOutput;
|
|
43
|
+
static importJSON(serializedNode: SerializedImageNode): ImageNode;
|
|
44
|
+
exportJSON(): SerializedImageNode;
|
|
45
|
+
decorate(): JSX.Element;
|
|
46
|
+
isInline(): boolean;
|
|
47
|
+
}
|
|
48
|
+
export declare function $createImageNode(src: string, alt: string, options?: {
|
|
49
|
+
title?: string;
|
|
50
|
+
displaySrc?: string;
|
|
51
|
+
linkUrl?: string;
|
|
52
|
+
}): ImageNode;
|
|
53
|
+
export declare function $isImageNode(node: LexicalNode | null | undefined): node is ImageNode;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { DecoratorNode, } from 'lexical';
|
|
2
|
+
import { createElement } from 'react';
|
|
3
|
+
import { ImageComponent } from './ImageComponent.js';
|
|
4
|
+
export class ImageNode extends DecoratorNode {
|
|
5
|
+
__src; // Relative path for markdown (e.g., "assets/image.png")
|
|
6
|
+
__alt;
|
|
7
|
+
__title;
|
|
8
|
+
__displaySrc; // Webview URI for display
|
|
9
|
+
__width;
|
|
10
|
+
__height;
|
|
11
|
+
__linkUrl; // Optional URL to link to when clicked
|
|
12
|
+
static getType() {
|
|
13
|
+
return 'image';
|
|
14
|
+
}
|
|
15
|
+
static clone(node) {
|
|
16
|
+
const cloned = new ImageNode(node.__src, node.__alt, {
|
|
17
|
+
title: node.__title,
|
|
18
|
+
linkUrl: node.__linkUrl,
|
|
19
|
+
key: node.__key,
|
|
20
|
+
});
|
|
21
|
+
cloned.__displaySrc = node.__displaySrc;
|
|
22
|
+
cloned.__width = node.__width;
|
|
23
|
+
cloned.__height = node.__height;
|
|
24
|
+
return cloned;
|
|
25
|
+
}
|
|
26
|
+
constructor(src, alt, options) {
|
|
27
|
+
super(options?.key);
|
|
28
|
+
this.__src = src;
|
|
29
|
+
this.__alt = alt;
|
|
30
|
+
this.__title = options?.title;
|
|
31
|
+
this.__linkUrl = options?.linkUrl;
|
|
32
|
+
this.__displaySrc = undefined;
|
|
33
|
+
this.__width = undefined;
|
|
34
|
+
this.__height = undefined;
|
|
35
|
+
}
|
|
36
|
+
getSrc() {
|
|
37
|
+
return this.__src;
|
|
38
|
+
}
|
|
39
|
+
getAlt() {
|
|
40
|
+
return this.__alt;
|
|
41
|
+
}
|
|
42
|
+
getTitle() {
|
|
43
|
+
return this.__title;
|
|
44
|
+
}
|
|
45
|
+
setSrc(src) {
|
|
46
|
+
const writable = this.getWritable();
|
|
47
|
+
writable.__src = src;
|
|
48
|
+
}
|
|
49
|
+
setAlt(alt) {
|
|
50
|
+
const writable = this.getWritable();
|
|
51
|
+
writable.__alt = alt;
|
|
52
|
+
}
|
|
53
|
+
setTitle(title) {
|
|
54
|
+
const writable = this.getWritable();
|
|
55
|
+
writable.__title = title;
|
|
56
|
+
}
|
|
57
|
+
getDisplaySrc() {
|
|
58
|
+
// Return displaySrc if set, otherwise fall back to src (for URLs)
|
|
59
|
+
return this.__displaySrc || this.__src;
|
|
60
|
+
}
|
|
61
|
+
setDisplaySrc(displaySrc) {
|
|
62
|
+
const writable = this.getWritable();
|
|
63
|
+
writable.__displaySrc = displaySrc;
|
|
64
|
+
}
|
|
65
|
+
getWidth() {
|
|
66
|
+
return this.__width;
|
|
67
|
+
}
|
|
68
|
+
setWidth(width) {
|
|
69
|
+
const writable = this.getWritable();
|
|
70
|
+
writable.__width = width;
|
|
71
|
+
}
|
|
72
|
+
getHeight() {
|
|
73
|
+
return this.__height;
|
|
74
|
+
}
|
|
75
|
+
setHeight(height) {
|
|
76
|
+
const writable = this.getWritable();
|
|
77
|
+
writable.__height = height;
|
|
78
|
+
}
|
|
79
|
+
getLinkUrl() {
|
|
80
|
+
return this.__linkUrl;
|
|
81
|
+
}
|
|
82
|
+
setLinkUrl(linkUrl) {
|
|
83
|
+
const writable = this.getWritable();
|
|
84
|
+
writable.__linkUrl = linkUrl;
|
|
85
|
+
}
|
|
86
|
+
createDOM() {
|
|
87
|
+
const element = document.createElement('figure');
|
|
88
|
+
element.className = 'image-block';
|
|
89
|
+
return element;
|
|
90
|
+
}
|
|
91
|
+
updateDOM() {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
static importDOM() {
|
|
95
|
+
return {
|
|
96
|
+
img: () => ({
|
|
97
|
+
conversion: (domNode) => {
|
|
98
|
+
const img = domNode;
|
|
99
|
+
const src = img.getAttribute('src') || '';
|
|
100
|
+
const alt = img.getAttribute('alt') || '';
|
|
101
|
+
const title = img.getAttribute('title') || undefined;
|
|
102
|
+
const widthAttr = img.getAttribute('width');
|
|
103
|
+
const heightAttr = img.getAttribute('height');
|
|
104
|
+
const node = new ImageNode(src, alt, { title });
|
|
105
|
+
if (widthAttr) {
|
|
106
|
+
node.__width = parseInt(widthAttr, 10);
|
|
107
|
+
}
|
|
108
|
+
if (heightAttr) {
|
|
109
|
+
node.__height = parseInt(heightAttr, 10);
|
|
110
|
+
}
|
|
111
|
+
return { node };
|
|
112
|
+
},
|
|
113
|
+
priority: 0,
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
exportDOM() {
|
|
118
|
+
const figure = document.createElement('figure');
|
|
119
|
+
figure.className = 'image-block';
|
|
120
|
+
const img = document.createElement('img');
|
|
121
|
+
img.src = this.__src;
|
|
122
|
+
img.alt = this.__alt;
|
|
123
|
+
if (this.__title) {
|
|
124
|
+
img.title = this.__title;
|
|
125
|
+
}
|
|
126
|
+
if (this.__width) {
|
|
127
|
+
img.width = this.__width;
|
|
128
|
+
}
|
|
129
|
+
if (this.__height) {
|
|
130
|
+
img.height = this.__height;
|
|
131
|
+
}
|
|
132
|
+
figure.appendChild(img);
|
|
133
|
+
return { element: figure };
|
|
134
|
+
}
|
|
135
|
+
static importJSON(serializedNode) {
|
|
136
|
+
const node = new ImageNode(serializedNode.src, serializedNode.alt, {
|
|
137
|
+
title: serializedNode.title,
|
|
138
|
+
linkUrl: serializedNode.linkUrl,
|
|
139
|
+
});
|
|
140
|
+
if (serializedNode.width) {
|
|
141
|
+
node.__width = serializedNode.width;
|
|
142
|
+
}
|
|
143
|
+
if (serializedNode.height) {
|
|
144
|
+
node.__height = serializedNode.height;
|
|
145
|
+
}
|
|
146
|
+
return node;
|
|
147
|
+
}
|
|
148
|
+
exportJSON() {
|
|
149
|
+
return {
|
|
150
|
+
type: 'image',
|
|
151
|
+
src: this.__src,
|
|
152
|
+
alt: this.__alt,
|
|
153
|
+
title: this.__title,
|
|
154
|
+
width: this.__width,
|
|
155
|
+
height: this.__height,
|
|
156
|
+
linkUrl: this.__linkUrl,
|
|
157
|
+
version: 1,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
decorate() {
|
|
161
|
+
return createElement(ImageComponent, {
|
|
162
|
+
src: this.getDisplaySrc(),
|
|
163
|
+
alt: this.__alt,
|
|
164
|
+
title: this.__title,
|
|
165
|
+
width: this.__width,
|
|
166
|
+
height: this.__height,
|
|
167
|
+
nodeKey: this.__key,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
isInline() {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export function $createImageNode(src, alt, options) {
|
|
175
|
+
const node = new ImageNode(src, alt, { title: options?.title, linkUrl: options?.linkUrl });
|
|
176
|
+
if (options?.displaySrc) {
|
|
177
|
+
node.__displaySrc = options.displaySrc;
|
|
178
|
+
}
|
|
179
|
+
return node;
|
|
180
|
+
}
|
|
181
|
+
export function $isImageNode(node) {
|
|
182
|
+
return node instanceof ImageNode;
|
|
183
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListItemParagraphBreakNode — inline sentinel marking the boundary between two
|
|
3
|
+
* consecutive mdast paragraphs flattened onto a single ListItemNode (see
|
|
4
|
+
* convertListItem in mdastToLexical.ts). @lexical/list's ListItemNode.append()
|
|
5
|
+
* unwraps a directly-appended ParagraphNode, flattening its inline content onto the
|
|
6
|
+
* item, so this marker is what convertListItem inserts instead to record where one
|
|
7
|
+
* paragraph ends and the next begins.
|
|
8
|
+
*
|
|
9
|
+
* Its own node type — distinct from LineBreakNode — makes it structurally
|
|
10
|
+
* impossible to collide with a genuine hard break (however many appear
|
|
11
|
+
* consecutively): $isLineBreakNode and $isListItemParagraphBreakNode are mutually
|
|
12
|
+
* exclusive by construction. This replaces the earlier two-consecutive-
|
|
13
|
+
* LineBreakNode marker, which was ambiguous with a real double hard break inside a
|
|
14
|
+
* single mdast paragraph (see #902).
|
|
15
|
+
*
|
|
16
|
+
* Modeled as an inline ElementNode (the same shape @lexical/link's LinkNode uses to
|
|
17
|
+
* sit inline among TextNode siblings) rather than extending LexicalNode directly —
|
|
18
|
+
* the `lexical` package's entry point re-exports LexicalNode via `export type`
|
|
19
|
+
* only (see node_modules/lexical/index.d.ts), so it's usable here solely as a
|
|
20
|
+
* TypeScript type annotation (as every other custom node in this directory does),
|
|
21
|
+
* never as a runtime value in an `extends` clause. ListItemNode.append() only unwraps
|
|
22
|
+
* ListItemNode/ParagraphNode children (see its canMergeWith override in
|
|
23
|
+
* @lexical/list), so this node — being neither — is never merged or unwrapped when
|
|
24
|
+
* appended directly to a list item. ElementNode's default canBeEmpty() is already
|
|
25
|
+
* `true`, so a childless instance is never garbage-collected by reconciliation
|
|
26
|
+
* either.
|
|
27
|
+
*/
|
|
28
|
+
import { DOMConversionMap, DOMExportOutput, ElementNode, LexicalNode, SerializedElementNode } from 'lexical';
|
|
29
|
+
export type SerializedListItemParagraphBreakNode = SerializedElementNode;
|
|
30
|
+
export declare class ListItemParagraphBreakNode extends ElementNode {
|
|
31
|
+
static getType(): string;
|
|
32
|
+
static clone(node: ListItemParagraphBreakNode): ListItemParagraphBreakNode;
|
|
33
|
+
isInline(): boolean;
|
|
34
|
+
createDOM(): HTMLElement;
|
|
35
|
+
updateDOM(): boolean;
|
|
36
|
+
exportDOM(): DOMExportOutput;
|
|
37
|
+
static importDOM(): DOMConversionMap | null;
|
|
38
|
+
static importJSON(serializedNode: SerializedListItemParagraphBreakNode): ListItemParagraphBreakNode;
|
|
39
|
+
exportJSON(): SerializedListItemParagraphBreakNode;
|
|
40
|
+
}
|
|
41
|
+
export declare function $createListItemParagraphBreakNode(): ListItemParagraphBreakNode;
|
|
42
|
+
export declare function $isListItemParagraphBreakNode(node: LexicalNode | null | undefined): node is ListItemParagraphBreakNode;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListItemParagraphBreakNode — inline sentinel marking the boundary between two
|
|
3
|
+
* consecutive mdast paragraphs flattened onto a single ListItemNode (see
|
|
4
|
+
* convertListItem in mdastToLexical.ts). @lexical/list's ListItemNode.append()
|
|
5
|
+
* unwraps a directly-appended ParagraphNode, flattening its inline content onto the
|
|
6
|
+
* item, so this marker is what convertListItem inserts instead to record where one
|
|
7
|
+
* paragraph ends and the next begins.
|
|
8
|
+
*
|
|
9
|
+
* Its own node type — distinct from LineBreakNode — makes it structurally
|
|
10
|
+
* impossible to collide with a genuine hard break (however many appear
|
|
11
|
+
* consecutively): $isLineBreakNode and $isListItemParagraphBreakNode are mutually
|
|
12
|
+
* exclusive by construction. This replaces the earlier two-consecutive-
|
|
13
|
+
* LineBreakNode marker, which was ambiguous with a real double hard break inside a
|
|
14
|
+
* single mdast paragraph (see #902).
|
|
15
|
+
*
|
|
16
|
+
* Modeled as an inline ElementNode (the same shape @lexical/link's LinkNode uses to
|
|
17
|
+
* sit inline among TextNode siblings) rather than extending LexicalNode directly —
|
|
18
|
+
* the `lexical` package's entry point re-exports LexicalNode via `export type`
|
|
19
|
+
* only (see node_modules/lexical/index.d.ts), so it's usable here solely as a
|
|
20
|
+
* TypeScript type annotation (as every other custom node in this directory does),
|
|
21
|
+
* never as a runtime value in an `extends` clause. ListItemNode.append() only unwraps
|
|
22
|
+
* ListItemNode/ParagraphNode children (see its canMergeWith override in
|
|
23
|
+
* @lexical/list), so this node — being neither — is never merged or unwrapped when
|
|
24
|
+
* appended directly to a list item. ElementNode's default canBeEmpty() is already
|
|
25
|
+
* `true`, so a childless instance is never garbage-collected by reconciliation
|
|
26
|
+
* either.
|
|
27
|
+
*/
|
|
28
|
+
import { ElementNode, } from 'lexical';
|
|
29
|
+
const MARKER_ATTRIBUTE = 'data-list-item-paragraph-break';
|
|
30
|
+
function $convertListItemParagraphBreakElement(_domNode) {
|
|
31
|
+
return { node: $createListItemParagraphBreakNode() };
|
|
32
|
+
}
|
|
33
|
+
export class ListItemParagraphBreakNode extends ElementNode {
|
|
34
|
+
static getType() {
|
|
35
|
+
return 'list-item-paragraph-break';
|
|
36
|
+
}
|
|
37
|
+
static clone(node) {
|
|
38
|
+
return new ListItemParagraphBreakNode(node.__key);
|
|
39
|
+
}
|
|
40
|
+
// Sits inline within a ListItemNode's flattened children, alongside TextNode and
|
|
41
|
+
// CustomLinkNode siblings, never as a block-level sibling of its own.
|
|
42
|
+
isInline() {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// Renders two <br> elements — the same visual gap the previous two-LineBreakNode
|
|
46
|
+
// marker produced — so a list item with consecutive paragraphs still shows a
|
|
47
|
+
// blank-line-like separator while editing, instead of the paragraphs visually
|
|
48
|
+
// running together with no boundary at all.
|
|
49
|
+
createDOM() {
|
|
50
|
+
const el = document.createElement('span');
|
|
51
|
+
el.setAttribute(MARKER_ATTRIBUTE, '');
|
|
52
|
+
el.appendChild(document.createElement('br'));
|
|
53
|
+
el.appendChild(document.createElement('br'));
|
|
54
|
+
return el;
|
|
55
|
+
}
|
|
56
|
+
updateDOM() {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
exportDOM() {
|
|
60
|
+
const el = document.createElement('span');
|
|
61
|
+
el.setAttribute(MARKER_ATTRIBUTE, '');
|
|
62
|
+
el.appendChild(document.createElement('br'));
|
|
63
|
+
el.appendChild(document.createElement('br'));
|
|
64
|
+
return { element: el };
|
|
65
|
+
}
|
|
66
|
+
static importDOM() {
|
|
67
|
+
return {
|
|
68
|
+
span: (domNode) => {
|
|
69
|
+
if (!domNode.hasAttribute(MARKER_ATTRIBUTE))
|
|
70
|
+
return null;
|
|
71
|
+
return { conversion: $convertListItemParagraphBreakElement, priority: 1 };
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
static importJSON(serializedNode) {
|
|
76
|
+
const node = $createListItemParagraphBreakNode();
|
|
77
|
+
node.setFormat(serializedNode.format);
|
|
78
|
+
node.setIndent(serializedNode.indent);
|
|
79
|
+
node.setDirection(serializedNode.direction);
|
|
80
|
+
return node;
|
|
81
|
+
}
|
|
82
|
+
exportJSON() {
|
|
83
|
+
return {
|
|
84
|
+
...super.exportJSON(),
|
|
85
|
+
type: 'list-item-paragraph-break',
|
|
86
|
+
version: 1,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function $createListItemParagraphBreakNode() {
|
|
91
|
+
return new ListItemParagraphBreakNode();
|
|
92
|
+
}
|
|
93
|
+
export function $isListItemParagraphBreakNode(node) {
|
|
94
|
+
return node instanceof ListItemParagraphBreakNode;
|
|
95
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MermaidComponent - React component for rendering and editing Mermaid diagrams
|
|
3
|
+
* Uses Shadow DOM for style isolation
|
|
4
|
+
*/
|
|
5
|
+
import { NodeKey } from 'lexical';
|
|
6
|
+
interface MermaidComponentProps {
|
|
7
|
+
code: string;
|
|
8
|
+
nodeKey: NodeKey;
|
|
9
|
+
}
|
|
10
|
+
export default function MermaidComponent({ code, nodeKey, }: MermaidComponentProps): JSX.Element;
|
|
11
|
+
export {};
|