@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,216 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* MermaidComponent - React component for rendering and editing Mermaid diagrams
|
|
4
|
+
* Uses Shadow DOM for style isolation
|
|
5
|
+
*/
|
|
6
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
7
|
+
import { mergeRegister } from '@lexical/utils';
|
|
8
|
+
import { $getNodeByKey, $getSelection, $isNodeSelection, COMMAND_PRIORITY_HIGH, KEY_ESCAPE_COMMAND, SELECTION_CHANGE_COMMAND, } from 'lexical';
|
|
9
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
10
|
+
import mermaid from 'mermaid';
|
|
11
|
+
import { useHostMessages } from '../../../host/messages.js';
|
|
12
|
+
import { $isMermaidNode } from './MermaidNode.js';
|
|
13
|
+
import { DiagramContextMenu, useDiagramContextMenu } from './DiagramContextMenu.js';
|
|
14
|
+
// Initialize mermaid with default config
|
|
15
|
+
mermaid.initialize({
|
|
16
|
+
startOnLoad: false,
|
|
17
|
+
theme: 'dark',
|
|
18
|
+
securityLevel: 'strict',
|
|
19
|
+
fontFamily: 'inherit',
|
|
20
|
+
});
|
|
21
|
+
// Mermaid Renderer component using Shadow DOM
|
|
22
|
+
function MermaidRenderer({ code, onDoubleClick, }) {
|
|
23
|
+
const containerRef = useRef(null);
|
|
24
|
+
const shadowRootRef = useRef(null);
|
|
25
|
+
const renderIdRef = useRef(0);
|
|
26
|
+
const linkHandlerRef = useRef(null);
|
|
27
|
+
const contextMenu = useDiagramContextMenu();
|
|
28
|
+
const { openLink } = useHostMessages();
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const container = containerRef.current;
|
|
31
|
+
if (container === null)
|
|
32
|
+
return;
|
|
33
|
+
// Create shadow root if it doesn't exist
|
|
34
|
+
if (!shadowRootRef.current) {
|
|
35
|
+
shadowRootRef.current = container.attachShadow({ mode: 'open' });
|
|
36
|
+
// Add basic styles for the shadow DOM
|
|
37
|
+
const style = document.createElement('style');
|
|
38
|
+
style.textContent = `
|
|
39
|
+
:host {
|
|
40
|
+
display: block;
|
|
41
|
+
width: 100%;
|
|
42
|
+
}
|
|
43
|
+
.mermaid-container {
|
|
44
|
+
display: flex;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
padding: 1rem;
|
|
47
|
+
background: rgba(255, 255, 255, 0.05);
|
|
48
|
+
border-radius: 8px;
|
|
49
|
+
overflow-x: auto;
|
|
50
|
+
}
|
|
51
|
+
.mermaid-container svg {
|
|
52
|
+
max-width: 100%;
|
|
53
|
+
height: auto;
|
|
54
|
+
}
|
|
55
|
+
.mermaid-error {
|
|
56
|
+
color: #ff6b6b;
|
|
57
|
+
padding: 1rem;
|
|
58
|
+
font-family: monospace;
|
|
59
|
+
white-space: pre-wrap;
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
shadowRootRef.current.appendChild(style);
|
|
63
|
+
// Intercept link clicks inside Mermaid SVGs and open externally
|
|
64
|
+
const handleLinkClick = (event) => {
|
|
65
|
+
const target = event.target;
|
|
66
|
+
const anchor = target?.closest?.('a');
|
|
67
|
+
if (!anchor)
|
|
68
|
+
return;
|
|
69
|
+
const href = anchor.getAttribute('href') ||
|
|
70
|
+
anchor.getAttribute('xlink:href') ||
|
|
71
|
+
anchor.href?.baseVal;
|
|
72
|
+
if (!href)
|
|
73
|
+
return;
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
event.stopPropagation();
|
|
76
|
+
openLink(href);
|
|
77
|
+
};
|
|
78
|
+
linkHandlerRef.current = handleLinkClick;
|
|
79
|
+
shadowRootRef.current.addEventListener('click', handleLinkClick);
|
|
80
|
+
const handlePointerMove = (event) => {
|
|
81
|
+
const target = event.target;
|
|
82
|
+
const anchor = target?.closest?.('a');
|
|
83
|
+
container.style.cursor = anchor ? 'pointer' : 'default';
|
|
84
|
+
};
|
|
85
|
+
const handlePointerLeave = () => {
|
|
86
|
+
container.style.cursor = 'default';
|
|
87
|
+
};
|
|
88
|
+
shadowRootRef.current.addEventListener('mousemove', handlePointerMove);
|
|
89
|
+
shadowRootRef.current.addEventListener('mouseleave', handlePointerLeave);
|
|
90
|
+
}
|
|
91
|
+
const shadowRoot = shadowRootRef.current;
|
|
92
|
+
// Clear previous content (except style)
|
|
93
|
+
const existingContainer = shadowRoot.querySelector('.mermaid-container, .mermaid-error');
|
|
94
|
+
if (existingContainer) {
|
|
95
|
+
existingContainer.remove();
|
|
96
|
+
}
|
|
97
|
+
// Render the diagram
|
|
98
|
+
const renderDiagram = async () => {
|
|
99
|
+
const currentRenderId = ++renderIdRef.current;
|
|
100
|
+
try {
|
|
101
|
+
// Validate syntax first
|
|
102
|
+
await mermaid.parse(code);
|
|
103
|
+
// Check if this render is still current
|
|
104
|
+
if (currentRenderId !== renderIdRef.current)
|
|
105
|
+
return;
|
|
106
|
+
// Generate unique ID for this render
|
|
107
|
+
const id = `mermaid-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
108
|
+
// Render the diagram
|
|
109
|
+
const { svg } = await mermaid.render(id, code);
|
|
110
|
+
// Check again if this render is still current
|
|
111
|
+
if (currentRenderId !== renderIdRef.current)
|
|
112
|
+
return;
|
|
113
|
+
// Create container and add SVG
|
|
114
|
+
const renderContainer = document.createElement('div');
|
|
115
|
+
renderContainer.className = 'mermaid-container';
|
|
116
|
+
renderContainer.innerHTML = svg;
|
|
117
|
+
shadowRoot.appendChild(renderContainer);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
if (currentRenderId !== renderIdRef.current)
|
|
121
|
+
return;
|
|
122
|
+
const errorMessage = e instanceof Error ? e.message : 'Failed to render diagram';
|
|
123
|
+
// Show error in shadow DOM
|
|
124
|
+
const errorContainer = document.createElement('div');
|
|
125
|
+
errorContainer.className = 'mermaid-error';
|
|
126
|
+
errorContainer.textContent = `Mermaid Error: ${errorMessage}`;
|
|
127
|
+
shadowRoot.appendChild(errorContainer);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
void renderDiagram();
|
|
131
|
+
}, [code, openLink]);
|
|
132
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { role: "button", tabIndex: -1, onDoubleClick: onDoubleClick, onContextMenu: (e) => contextMenu.show(e, containerRef), ref: containerRef, className: "mermaid-renderer", style: {
|
|
133
|
+
display: 'block',
|
|
134
|
+
cursor: 'pointer',
|
|
135
|
+
minHeight: '100px',
|
|
136
|
+
} }), _jsx(DiagramContextMenu, { ...contextMenu.props })] }));
|
|
137
|
+
}
|
|
138
|
+
// Mermaid Editor component
|
|
139
|
+
function MermaidEditor({ code, setCode, onClose, }) {
|
|
140
|
+
const textareaRef = useRef(null);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
// Focus textarea on mount
|
|
143
|
+
textareaRef.current?.focus();
|
|
144
|
+
}, []);
|
|
145
|
+
const handleKeyDown = (e) => {
|
|
146
|
+
if (e.key === 'Escape') {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
onClose();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
return (_jsxs("div", { className: "mermaid-editor-container", children: [_jsxs("div", { className: "mermaid-editor-header", children: [_jsx("span", { className: "mermaid-editor-label", children: "```mermaid" }), _jsx("button", { className: "mermaid-editor-close", onClick: onClose, title: "Close editor (Esc)", children: "\u2715" })] }), _jsx("textarea", { ref: textareaRef, className: "mermaid-editor-textarea", value: code, onChange: (e) => setCode(e.target.value), onKeyDown: handleKeyDown, placeholder: "Enter Mermaid diagram code...", spellCheck: false }), _jsx("div", { className: "mermaid-editor-footer", children: _jsx("span", { className: "mermaid-editor-label", children: "```" }) })] }));
|
|
152
|
+
}
|
|
153
|
+
export default function MermaidComponent({ code, nodeKey, }) {
|
|
154
|
+
const [editor] = useLexicalComposerContext();
|
|
155
|
+
const [isEditable, setIsEditable] = useState(() => editor.isEditable());
|
|
156
|
+
const [codeValue, setCodeValue] = useState(code);
|
|
157
|
+
const [showEditor, setShowEditor] = useState(false);
|
|
158
|
+
// Listen for editable changes
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
return editor.registerEditableListener((editable) => {
|
|
161
|
+
setIsEditable(editable);
|
|
162
|
+
});
|
|
163
|
+
}, [editor]);
|
|
164
|
+
const onHide = useCallback(() => {
|
|
165
|
+
setShowEditor(false);
|
|
166
|
+
editor.update(() => {
|
|
167
|
+
const node = $getNodeByKey(nodeKey);
|
|
168
|
+
if ($isMermaidNode(node)) {
|
|
169
|
+
node.setCode(codeValue);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}, [editor, codeValue, nodeKey]);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (!showEditor && codeValue !== code) {
|
|
175
|
+
setCodeValue(code);
|
|
176
|
+
}
|
|
177
|
+
}, [showEditor, code, codeValue]);
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (!isEditable) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (showEditor) {
|
|
183
|
+
return mergeRegister(editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
|
184
|
+
// Don't close on selection change while editing
|
|
185
|
+
return false;
|
|
186
|
+
}, COMMAND_PRIORITY_HIGH), editor.registerCommand(KEY_ESCAPE_COMMAND, () => {
|
|
187
|
+
if (showEditor) {
|
|
188
|
+
onHide();
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}, COMMAND_PRIORITY_HIGH));
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
return editor.registerUpdateListener(({ editorState }) => {
|
|
196
|
+
const isSelected = editorState.read(() => {
|
|
197
|
+
const selection = $getSelection();
|
|
198
|
+
return ($isNodeSelection(selection) &&
|
|
199
|
+
selection.has(nodeKey) &&
|
|
200
|
+
selection.getNodes().length === 1);
|
|
201
|
+
});
|
|
202
|
+
if (isSelected) {
|
|
203
|
+
setShowEditor(true);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}, [editor, nodeKey, onHide, showEditor, isEditable]);
|
|
208
|
+
if (showEditor && isEditable) {
|
|
209
|
+
return (_jsx(MermaidEditor, { code: codeValue, setCode: setCodeValue, onClose: onHide }));
|
|
210
|
+
}
|
|
211
|
+
return (_jsx(MermaidRenderer, { code: codeValue, onDoubleClick: () => {
|
|
212
|
+
if (isEditable) {
|
|
213
|
+
setShowEditor(true);
|
|
214
|
+
}
|
|
215
|
+
} }));
|
|
216
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MermaidNode - Renders Mermaid diagrams
|
|
3
|
+
* Uses DecoratorNode pattern similar to EquationNode
|
|
4
|
+
*/
|
|
5
|
+
import { DecoratorNode, DOMConversionMap, DOMExportOutput, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from 'lexical';
|
|
6
|
+
export type SerializedMermaidNode = Spread<{
|
|
7
|
+
code: string;
|
|
8
|
+
}, SerializedLexicalNode>;
|
|
9
|
+
export declare class MermaidNode extends DecoratorNode<JSX.Element> {
|
|
10
|
+
__code: string;
|
|
11
|
+
static getType(): string;
|
|
12
|
+
static clone(node: MermaidNode): MermaidNode;
|
|
13
|
+
constructor(code: string, key?: NodeKey);
|
|
14
|
+
static importJSON(serializedNode: SerializedMermaidNode): MermaidNode;
|
|
15
|
+
exportJSON(): SerializedMermaidNode;
|
|
16
|
+
createDOM(): HTMLElement;
|
|
17
|
+
exportDOM(): DOMExportOutput;
|
|
18
|
+
static importDOM(): DOMConversionMap | null;
|
|
19
|
+
updateDOM(): boolean;
|
|
20
|
+
isInline(): boolean;
|
|
21
|
+
getTextContent(): string;
|
|
22
|
+
getCode(): string;
|
|
23
|
+
setCode(code: string): void;
|
|
24
|
+
decorate(): JSX.Element;
|
|
25
|
+
}
|
|
26
|
+
export declare function $createMermaidNode(code?: string): MermaidNode;
|
|
27
|
+
export declare function $isMermaidNode(node: LexicalNode | null | undefined): node is MermaidNode;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* eslint-disable react-refresh/only-export-components */
|
|
2
|
+
/**
|
|
3
|
+
* MermaidNode - Renders Mermaid diagrams
|
|
4
|
+
* Uses DecoratorNode pattern similar to EquationNode
|
|
5
|
+
*/
|
|
6
|
+
import { DecoratorNode, $applyNodeReplacement, } from 'lexical';
|
|
7
|
+
import { createElement, lazy, Suspense } from 'react';
|
|
8
|
+
const MermaidComponent = lazy(() => import('./MermaidComponent.js'));
|
|
9
|
+
function $convertMermaidElement(domNode) {
|
|
10
|
+
const code = domNode.getAttribute('data-lexical-mermaid');
|
|
11
|
+
if (code) {
|
|
12
|
+
// Decode from base64
|
|
13
|
+
const decodedCode = atob(code);
|
|
14
|
+
const node = $createMermaidNode(decodedCode);
|
|
15
|
+
return { node };
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export class MermaidNode extends DecoratorNode {
|
|
20
|
+
__code;
|
|
21
|
+
static getType() {
|
|
22
|
+
return 'mermaid';
|
|
23
|
+
}
|
|
24
|
+
static clone(node) {
|
|
25
|
+
return new MermaidNode(node.__code, node.__key);
|
|
26
|
+
}
|
|
27
|
+
constructor(code, key) {
|
|
28
|
+
super(key);
|
|
29
|
+
this.__code = code;
|
|
30
|
+
}
|
|
31
|
+
static importJSON(serializedNode) {
|
|
32
|
+
return $createMermaidNode(serializedNode.code);
|
|
33
|
+
}
|
|
34
|
+
exportJSON() {
|
|
35
|
+
return {
|
|
36
|
+
type: 'mermaid',
|
|
37
|
+
version: 1,
|
|
38
|
+
code: this.getCode(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
createDOM() {
|
|
42
|
+
const element = document.createElement('div');
|
|
43
|
+
element.className = 'editor-mermaid';
|
|
44
|
+
return element;
|
|
45
|
+
}
|
|
46
|
+
exportDOM() {
|
|
47
|
+
const element = document.createElement('div');
|
|
48
|
+
// Encode as base64 to avoid issues with special characters
|
|
49
|
+
element.setAttribute('data-lexical-mermaid', btoa(this.__code));
|
|
50
|
+
element.className = 'mermaid-export';
|
|
51
|
+
element.textContent = this.__code;
|
|
52
|
+
return { element };
|
|
53
|
+
}
|
|
54
|
+
static importDOM() {
|
|
55
|
+
return {
|
|
56
|
+
div: (domNode) => {
|
|
57
|
+
if (!domNode.hasAttribute('data-lexical-mermaid')) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
conversion: $convertMermaidElement,
|
|
62
|
+
priority: 2,
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
updateDOM() {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
isInline() {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
getTextContent() {
|
|
74
|
+
return this.__code;
|
|
75
|
+
}
|
|
76
|
+
getCode() {
|
|
77
|
+
return this.__code;
|
|
78
|
+
}
|
|
79
|
+
setCode(code) {
|
|
80
|
+
const writable = this.getWritable();
|
|
81
|
+
writable.__code = code;
|
|
82
|
+
}
|
|
83
|
+
decorate() {
|
|
84
|
+
return createElement(Suspense, { fallback: createElement('div', { className: 'mermaid-loading' }, 'Loading diagram...') }, createElement(MermaidComponent, {
|
|
85
|
+
code: this.__code,
|
|
86
|
+
nodeKey: this.__key,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function $createMermaidNode(code = '') {
|
|
91
|
+
const mermaidNode = new MermaidNode(code);
|
|
92
|
+
return $applyNodeReplacement(mermaidNode);
|
|
93
|
+
}
|
|
94
|
+
export function $isMermaidNode(node) {
|
|
95
|
+
return node instanceof MermaidNode;
|
|
96
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { DOMConversionMap, DOMExportOutput, EditorConfig, ElementNode, LexicalNode, NodeKey, RangeSelection, SerializedElementNode, Spread } from 'lexical';
|
|
2
|
+
export type SerializedToggleContainerNode = Spread<{
|
|
3
|
+
open: boolean;
|
|
4
|
+
}, SerializedElementNode>;
|
|
5
|
+
export declare class ToggleContainerNode extends ElementNode {
|
|
6
|
+
__open: boolean;
|
|
7
|
+
static getType(): string;
|
|
8
|
+
static clone(node: ToggleContainerNode): ToggleContainerNode;
|
|
9
|
+
constructor(open?: boolean, key?: NodeKey);
|
|
10
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
11
|
+
updateDOM(prevNode: ToggleContainerNode, dom: HTMLElement): boolean;
|
|
12
|
+
static importDOM(): DOMConversionMap | null;
|
|
13
|
+
static importJSON(serializedNode: SerializedToggleContainerNode): ToggleContainerNode;
|
|
14
|
+
exportDOM(): DOMExportOutput;
|
|
15
|
+
exportJSON(): SerializedToggleContainerNode;
|
|
16
|
+
setOpen(open: boolean): void;
|
|
17
|
+
getOpen(): boolean;
|
|
18
|
+
toggleOpen(): void;
|
|
19
|
+
isShadowRoot(): boolean;
|
|
20
|
+
canBeEmpty(): boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function $createToggleContainerNode(open?: boolean): ToggleContainerNode;
|
|
23
|
+
export declare function $isToggleContainerNode(node: LexicalNode | null | undefined): node is ToggleContainerNode;
|
|
24
|
+
export type SerializedToggleTitleNode = SerializedElementNode;
|
|
25
|
+
export declare class ToggleTitleNode extends ElementNode {
|
|
26
|
+
static getType(): string;
|
|
27
|
+
static clone(node: ToggleTitleNode): ToggleTitleNode;
|
|
28
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
29
|
+
updateDOM(): boolean;
|
|
30
|
+
static importDOM(): DOMConversionMap | null;
|
|
31
|
+
static importJSON(serializedNode: SerializedToggleTitleNode): ToggleTitleNode;
|
|
32
|
+
exportDOM(): DOMExportOutput;
|
|
33
|
+
exportJSON(): SerializedToggleTitleNode;
|
|
34
|
+
collapseAtStart(_selection: RangeSelection): boolean;
|
|
35
|
+
insertNewAfter(_: RangeSelection, restoreSelection?: boolean): null | ElementNode;
|
|
36
|
+
}
|
|
37
|
+
export declare function $createToggleTitleNode(): ToggleTitleNode;
|
|
38
|
+
export declare function $isToggleTitleNode(node: LexicalNode | null | undefined): node is ToggleTitleNode;
|
|
39
|
+
export type SerializedToggleContentNode = SerializedElementNode;
|
|
40
|
+
export declare class ToggleContentNode extends ElementNode {
|
|
41
|
+
static getType(): string;
|
|
42
|
+
static clone(node: ToggleContentNode): ToggleContentNode;
|
|
43
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
44
|
+
updateDOM(): boolean;
|
|
45
|
+
static importDOM(): DOMConversionMap | null;
|
|
46
|
+
static importJSON(serializedNode: SerializedToggleContentNode): ToggleContentNode;
|
|
47
|
+
exportDOM(): DOMExportOutput;
|
|
48
|
+
exportJSON(): SerializedToggleContentNode;
|
|
49
|
+
isShadowRoot(): boolean;
|
|
50
|
+
}
|
|
51
|
+
export declare function $createToggleContentNode(): ToggleContentNode;
|
|
52
|
+
export declare function $isToggleContentNode(node: LexicalNode | null | undefined): node is ToggleContentNode;
|
|
53
|
+
export declare function $createToggleNode(summaryText?: string, contentText?: string, open?: boolean): ToggleContainerNode;
|
|
54
|
+
export declare function $isToggleNode(node: LexicalNode | null | undefined): node is ToggleContainerNode;
|
|
55
|
+
export type ToggleNode = ToggleContainerNode;
|
|
56
|
+
export type SerializedToggleNode = SerializedToggleContainerNode;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { $createParagraphNode, $createTextNode, ElementNode, } from 'lexical';
|
|
2
|
+
export class ToggleContainerNode extends ElementNode {
|
|
3
|
+
__open;
|
|
4
|
+
static getType() {
|
|
5
|
+
return 'toggle-container';
|
|
6
|
+
}
|
|
7
|
+
static clone(node) {
|
|
8
|
+
return new ToggleContainerNode(node.__open, node.__key);
|
|
9
|
+
}
|
|
10
|
+
constructor(open = false, key) {
|
|
11
|
+
super(key);
|
|
12
|
+
this.__open = open;
|
|
13
|
+
}
|
|
14
|
+
createDOM(config) {
|
|
15
|
+
void config;
|
|
16
|
+
const dom = document.createElement('div');
|
|
17
|
+
dom.classList.add('toggle-container');
|
|
18
|
+
if (this.__open) {
|
|
19
|
+
dom.classList.add('toggle-container--open');
|
|
20
|
+
}
|
|
21
|
+
return dom;
|
|
22
|
+
}
|
|
23
|
+
updateDOM(prevNode, dom) {
|
|
24
|
+
if (prevNode.__open !== this.__open) {
|
|
25
|
+
dom.classList.toggle('toggle-container--open', this.__open);
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
static importDOM() {
|
|
30
|
+
return {
|
|
31
|
+
details: (domNode) => {
|
|
32
|
+
void domNode;
|
|
33
|
+
return {
|
|
34
|
+
conversion: convertDetailsElement,
|
|
35
|
+
priority: 1,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
static importJSON(serializedNode) {
|
|
41
|
+
void serializedNode;
|
|
42
|
+
const node = $createToggleContainerNode(serializedNode.open);
|
|
43
|
+
return node;
|
|
44
|
+
}
|
|
45
|
+
exportDOM() {
|
|
46
|
+
const element = document.createElement('details');
|
|
47
|
+
if (this.__open) {
|
|
48
|
+
element.setAttribute('open', '');
|
|
49
|
+
}
|
|
50
|
+
return { element };
|
|
51
|
+
}
|
|
52
|
+
exportJSON() {
|
|
53
|
+
return {
|
|
54
|
+
...super.exportJSON(),
|
|
55
|
+
type: 'toggle-container',
|
|
56
|
+
open: this.__open,
|
|
57
|
+
version: 1,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
setOpen(open) {
|
|
61
|
+
const writable = this.getWritable();
|
|
62
|
+
writable.__open = open;
|
|
63
|
+
}
|
|
64
|
+
getOpen() {
|
|
65
|
+
return this.__open;
|
|
66
|
+
}
|
|
67
|
+
toggleOpen() {
|
|
68
|
+
this.setOpen(!this.getOpen());
|
|
69
|
+
}
|
|
70
|
+
// This prevents direct selection in the container
|
|
71
|
+
isShadowRoot() {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
canBeEmpty() {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function convertDetailsElement(domNode) {
|
|
79
|
+
const isOpen = domNode.hasAttribute('open');
|
|
80
|
+
const node = $createToggleContainerNode(isOpen);
|
|
81
|
+
return { node };
|
|
82
|
+
}
|
|
83
|
+
export function $createToggleContainerNode(open = false) {
|
|
84
|
+
return new ToggleContainerNode(open);
|
|
85
|
+
}
|
|
86
|
+
export function $isToggleContainerNode(node) {
|
|
87
|
+
return node instanceof ToggleContainerNode;
|
|
88
|
+
}
|
|
89
|
+
export class ToggleTitleNode extends ElementNode {
|
|
90
|
+
static getType() {
|
|
91
|
+
return 'toggle-title';
|
|
92
|
+
}
|
|
93
|
+
static clone(node) {
|
|
94
|
+
return new ToggleTitleNode(node.__key);
|
|
95
|
+
}
|
|
96
|
+
createDOM(config) {
|
|
97
|
+
void config;
|
|
98
|
+
const dom = document.createElement('div');
|
|
99
|
+
dom.classList.add('toggle-title');
|
|
100
|
+
return dom;
|
|
101
|
+
}
|
|
102
|
+
updateDOM() {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
static importDOM() {
|
|
106
|
+
return {
|
|
107
|
+
summary: () => ({
|
|
108
|
+
conversion: convertSummaryElement,
|
|
109
|
+
priority: 1,
|
|
110
|
+
}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
static importJSON(serializedNode) {
|
|
114
|
+
void serializedNode;
|
|
115
|
+
return $createToggleTitleNode();
|
|
116
|
+
}
|
|
117
|
+
exportDOM() {
|
|
118
|
+
const element = document.createElement('summary');
|
|
119
|
+
return { element };
|
|
120
|
+
}
|
|
121
|
+
exportJSON() {
|
|
122
|
+
return {
|
|
123
|
+
...super.exportJSON(),
|
|
124
|
+
type: 'toggle-title',
|
|
125
|
+
version: 1,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Collapse at start collapses the entire toggle
|
|
129
|
+
collapseAtStart(_selection) {
|
|
130
|
+
this.getParentOrThrow().insertBefore(this);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
// Prevent inserting nodes that aren't allowed
|
|
134
|
+
insertNewAfter(_, restoreSelection) {
|
|
135
|
+
void restoreSelection;
|
|
136
|
+
const containerNode = this.getParentOrThrow();
|
|
137
|
+
if (!$isToggleContainerNode(containerNode)) {
|
|
138
|
+
throw new Error('ToggleTitleNode must be a child of ToggleContainerNode');
|
|
139
|
+
}
|
|
140
|
+
// If closed, open it
|
|
141
|
+
if (!containerNode.getOpen()) {
|
|
142
|
+
containerNode.toggleOpen();
|
|
143
|
+
}
|
|
144
|
+
// Find or create content node
|
|
145
|
+
const children = containerNode.getChildren();
|
|
146
|
+
const contentNode = children.find($isToggleContentNode);
|
|
147
|
+
if (contentNode) {
|
|
148
|
+
const firstChild = contentNode.getFirstChild();
|
|
149
|
+
if (firstChild) {
|
|
150
|
+
firstChild.selectStart();
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
const paragraph = $createParagraphNode();
|
|
154
|
+
contentNode.append(paragraph);
|
|
155
|
+
paragraph.selectStart();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function convertSummaryElement() {
|
|
162
|
+
const node = $createToggleTitleNode();
|
|
163
|
+
return { node };
|
|
164
|
+
}
|
|
165
|
+
export function $createToggleTitleNode() {
|
|
166
|
+
return new ToggleTitleNode();
|
|
167
|
+
}
|
|
168
|
+
export function $isToggleTitleNode(node) {
|
|
169
|
+
return node instanceof ToggleTitleNode;
|
|
170
|
+
}
|
|
171
|
+
export class ToggleContentNode extends ElementNode {
|
|
172
|
+
static getType() {
|
|
173
|
+
return 'toggle-content';
|
|
174
|
+
}
|
|
175
|
+
static clone(node) {
|
|
176
|
+
return new ToggleContentNode(node.__key);
|
|
177
|
+
}
|
|
178
|
+
createDOM(config) {
|
|
179
|
+
void config;
|
|
180
|
+
const dom = document.createElement('div');
|
|
181
|
+
dom.classList.add('toggle-content-area');
|
|
182
|
+
return dom;
|
|
183
|
+
}
|
|
184
|
+
updateDOM() {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
static importDOM() {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
static importJSON(serializedNode) {
|
|
191
|
+
void serializedNode;
|
|
192
|
+
return $createToggleContentNode();
|
|
193
|
+
}
|
|
194
|
+
exportDOM() {
|
|
195
|
+
const element = document.createElement('div');
|
|
196
|
+
return { element };
|
|
197
|
+
}
|
|
198
|
+
exportJSON() {
|
|
199
|
+
return {
|
|
200
|
+
...super.exportJSON(),
|
|
201
|
+
type: 'toggle-content',
|
|
202
|
+
version: 1,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
isShadowRoot() {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export function $createToggleContentNode() {
|
|
210
|
+
return new ToggleContentNode();
|
|
211
|
+
}
|
|
212
|
+
export function $isToggleContentNode(node) {
|
|
213
|
+
return node instanceof ToggleContentNode;
|
|
214
|
+
}
|
|
215
|
+
// ==================== Helper Functions ====================
|
|
216
|
+
export function $createToggleNode(summaryText = 'Toggle', contentText = '', open = false) {
|
|
217
|
+
const container = $createToggleContainerNode(open);
|
|
218
|
+
const title = $createToggleTitleNode();
|
|
219
|
+
const titleParagraph = $createParagraphNode();
|
|
220
|
+
if (summaryText) {
|
|
221
|
+
titleParagraph.append($createTextNode(summaryText));
|
|
222
|
+
}
|
|
223
|
+
title.append(titleParagraph);
|
|
224
|
+
const content = $createToggleContentNode();
|
|
225
|
+
const contentParagraph = $createParagraphNode();
|
|
226
|
+
if (contentText) {
|
|
227
|
+
contentParagraph.append($createTextNode(contentText));
|
|
228
|
+
}
|
|
229
|
+
content.append(contentParagraph);
|
|
230
|
+
container.append(title);
|
|
231
|
+
container.append(content);
|
|
232
|
+
return container;
|
|
233
|
+
}
|
|
234
|
+
// Legacy compatibility - now just checks for container
|
|
235
|
+
export function $isToggleNode(node) {
|
|
236
|
+
return $isToggleContainerNode(node);
|
|
237
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared "Copy image to clipboard" for diagram nodes (C4, Mermaid).
|
|
3
|
+
*
|
|
4
|
+
* Converts the diagram SVG to a high-res PNG and writes it to the
|
|
5
|
+
* clipboard. Uses devicePixelRatio for crisp retina output.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Copy the diagram to the clipboard as a high-res PNG.
|
|
9
|
+
* Returns true on success, false on failure.
|
|
10
|
+
*/
|
|
11
|
+
export declare function copyDiagramToClipboard(container: HTMLElement): Promise<boolean>;
|