@zuilib/text-editor 0.2.0 → 0.3.0
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/CHANGELOG.md +58 -0
- package/DRAWING_FORMAT.md +149 -0
- package/README.md +208 -130
- package/dist/index.d.ts +254 -9
- package/dist/index.js +956 -708
- package/dist/styles.css +6 -0
- package/package.json +10 -8
package/dist/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
1
|
+
// src/EditorRoot.tsx
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useCallback as useCallback2,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect as useEffect6,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef as useRef3,
|
|
9
|
+
useState as useState2
|
|
10
|
+
} from "react";
|
|
3
11
|
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
|
4
|
-
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
|
|
5
|
-
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
|
|
6
12
|
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
|
|
7
|
-
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
|
|
8
13
|
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
|
9
14
|
import { CheckListPlugin } from "@lexical/react/LexicalCheckListPlugin";
|
|
10
15
|
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
|
|
@@ -12,7 +17,7 @@ import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPl
|
|
|
12
17
|
import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
|
|
13
18
|
import { TablePlugin } from "@lexical/react/LexicalTablePlugin";
|
|
14
19
|
import { CHECK_LIST, CODE, TRANSFORMERS as TRANSFORMERS2 } from "@lexical/markdown";
|
|
15
|
-
import { useLexicalComposerContext as
|
|
20
|
+
import { useLexicalComposerContext as useLexicalComposerContext6 } from "@lexical/react/LexicalComposerContext";
|
|
16
21
|
import { HeadingNode, QuoteNode } from "@lexical/rich-text";
|
|
17
22
|
import { ListItemNode, ListNode as ListNode2 } from "@lexical/list";
|
|
18
23
|
import { CodeHighlightNode, CodeNode } from "@lexical/code";
|
|
@@ -166,146 +171,9 @@ function CodeHighlightPlugin() {
|
|
|
166
171
|
return null;
|
|
167
172
|
}
|
|
168
173
|
|
|
169
|
-
// src/plugins/FoldingPlugin.tsx
|
|
170
|
-
import {
|
|
171
|
-
useCallback,
|
|
172
|
-
useEffect as useEffect4,
|
|
173
|
-
useRef,
|
|
174
|
-
useState
|
|
175
|
-
} from "react";
|
|
176
|
-
import { $getRoot, $getSelection as $getSelection2, $isRangeSelection as $isRangeSelection2 } from "lexical";
|
|
177
|
-
import { useLexicalComposerContext as useLexicalComposerContext4 } from "@lexical/react/LexicalComposerContext";
|
|
178
|
-
import { $isHeadingNode } from "@lexical/rich-text";
|
|
179
|
-
import { jsx } from "react/jsx-runtime";
|
|
180
|
-
var HEADING_LEVELS = {
|
|
181
|
-
h1: 1,
|
|
182
|
-
h2: 2,
|
|
183
|
-
h3: 3,
|
|
184
|
-
h4: 4,
|
|
185
|
-
h5: 5,
|
|
186
|
-
h6: 6
|
|
187
|
-
};
|
|
188
|
-
function FoldingPlugin() {
|
|
189
|
-
const [editor] = useLexicalComposerContext4();
|
|
190
|
-
const [foldedKeys, setFoldedKeys] = useState(/* @__PURE__ */ new Set());
|
|
191
|
-
const [buttons, setButtons] = useState([]);
|
|
192
|
-
const foldedRef = useRef(foldedKeys);
|
|
193
|
-
foldedRef.current = foldedKeys;
|
|
194
|
-
const sync = useCallback(() => {
|
|
195
|
-
editor.getEditorState().read(() => {
|
|
196
|
-
const children = $getRoot().getChildren();
|
|
197
|
-
const folded = foldedRef.current;
|
|
198
|
-
const hidden = /* @__PURE__ */ new Set();
|
|
199
|
-
const headings = [];
|
|
200
|
-
for (let i = 0; i < children.length; i++) {
|
|
201
|
-
const node = children[i];
|
|
202
|
-
if (!$isHeadingNode(node)) continue;
|
|
203
|
-
const level = HEADING_LEVELS[node.getTag()] ?? 6;
|
|
204
|
-
const isFolded = folded.has(node.getKey());
|
|
205
|
-
headings.push({ key: node.getKey(), folded: isFolded });
|
|
206
|
-
if (!isFolded) continue;
|
|
207
|
-
for (let j = i + 1; j < children.length; j++) {
|
|
208
|
-
const sibling = children[j];
|
|
209
|
-
if ($isHeadingNode(sibling) && (HEADING_LEVELS[sibling.getTag()] ?? 6) <= level) {
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
hidden.add(sibling.getKey());
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
const selection = $getSelection2();
|
|
216
|
-
if ($isRangeSelection2(selection)) {
|
|
217
|
-
const topLevel = selection.anchor.getNode().getTopLevelElement();
|
|
218
|
-
if (topLevel && hidden.has(topLevel.getKey())) {
|
|
219
|
-
let node = topLevel.getPreviousSibling();
|
|
220
|
-
while (node) {
|
|
221
|
-
if ($isHeadingNode(node) && folded.has(node.getKey())) {
|
|
222
|
-
const unfoldKey = node.getKey();
|
|
223
|
-
setFoldedKeys((prev) => {
|
|
224
|
-
const next = new Set(prev);
|
|
225
|
-
next.delete(unfoldKey);
|
|
226
|
-
return next;
|
|
227
|
-
});
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
node = node.getPreviousSibling();
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
for (const child of children) {
|
|
235
|
-
const element = editor.getElementByKey(child.getKey());
|
|
236
|
-
if (!element) continue;
|
|
237
|
-
const shouldHide = hidden.has(child.getKey());
|
|
238
|
-
if (shouldHide) {
|
|
239
|
-
element.style.display = "none";
|
|
240
|
-
} else if (element.style.display === "none") {
|
|
241
|
-
element.style.display = "";
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
setButtons(
|
|
245
|
-
headings.map(({ key, folded: isFolded }) => {
|
|
246
|
-
const element = editor.getElementByKey(key);
|
|
247
|
-
element?.classList.toggle("zui-heading-folded", isFolded);
|
|
248
|
-
return {
|
|
249
|
-
key,
|
|
250
|
-
folded: isFolded,
|
|
251
|
-
top: element?.offsetTop ?? 0,
|
|
252
|
-
height: element?.offsetHeight ?? 0
|
|
253
|
-
};
|
|
254
|
-
})
|
|
255
|
-
);
|
|
256
|
-
});
|
|
257
|
-
}, [editor]);
|
|
258
|
-
useEffect4(() => {
|
|
259
|
-
sync();
|
|
260
|
-
const unregister = editor.registerUpdateListener(sync);
|
|
261
|
-
const rootElement = editor.getRootElement();
|
|
262
|
-
const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(sync) : null;
|
|
263
|
-
if (rootElement && observer) observer.observe(rootElement);
|
|
264
|
-
return () => {
|
|
265
|
-
unregister();
|
|
266
|
-
observer?.disconnect();
|
|
267
|
-
};
|
|
268
|
-
}, [editor, sync, foldedKeys]);
|
|
269
|
-
const toggle = useCallback((key) => {
|
|
270
|
-
setFoldedKeys((prev) => {
|
|
271
|
-
const next = new Set(prev);
|
|
272
|
-
if (next.has(key)) next.delete(key);
|
|
273
|
-
else next.add(key);
|
|
274
|
-
return next;
|
|
275
|
-
});
|
|
276
|
-
}, []);
|
|
277
|
-
return /* @__PURE__ */ jsx("div", { className: "zui-text-editor-fold-gutter", children: buttons.map(({ key, folded, top, height }) => /* @__PURE__ */ jsx(
|
|
278
|
-
"button",
|
|
279
|
-
{
|
|
280
|
-
type: "button",
|
|
281
|
-
className: `zui-text-editor-fold ${folded ? "is-folded" : ""}`,
|
|
282
|
-
style: { top: top + Math.max(0, (Math.min(height, 44) - 18) / 2) },
|
|
283
|
-
title: folded ? "Expand section" : "Collapse section",
|
|
284
|
-
"aria-label": folded ? "Expand section" : "Collapse section",
|
|
285
|
-
"aria-expanded": !folded,
|
|
286
|
-
onClick: () => toggle(key),
|
|
287
|
-
children: /* @__PURE__ */ jsx(
|
|
288
|
-
"svg",
|
|
289
|
-
{
|
|
290
|
-
viewBox: "0 0 20 20",
|
|
291
|
-
width: "12",
|
|
292
|
-
height: "12",
|
|
293
|
-
fill: "none",
|
|
294
|
-
stroke: "currentColor",
|
|
295
|
-
strokeWidth: "2",
|
|
296
|
-
strokeLinecap: "round",
|
|
297
|
-
strokeLinejoin: "round",
|
|
298
|
-
children: /* @__PURE__ */ jsx("path", { d: "M6 8l4 4 4-4" })
|
|
299
|
-
}
|
|
300
|
-
)
|
|
301
|
-
},
|
|
302
|
-
key
|
|
303
|
-
)) });
|
|
304
|
-
}
|
|
305
|
-
|
|
306
174
|
// src/plugins/MarkdownSyncPlugin.tsx
|
|
307
|
-
import { useEffect as
|
|
308
|
-
import { useLexicalComposerContext as
|
|
175
|
+
import { useEffect as useEffect4, useRef } from "react";
|
|
176
|
+
import { useLexicalComposerContext as useLexicalComposerContext4 } from "@lexical/react/LexicalComposerContext";
|
|
309
177
|
import {
|
|
310
178
|
$convertFromMarkdownString,
|
|
311
179
|
$convertToMarkdownString
|
|
@@ -315,11 +183,11 @@ function MarkdownSyncPlugin({
|
|
|
315
183
|
onChange,
|
|
316
184
|
transformers
|
|
317
185
|
}) {
|
|
318
|
-
const [editor] =
|
|
319
|
-
const lastMarkdownRef =
|
|
320
|
-
const onChangeRef =
|
|
186
|
+
const [editor] = useLexicalComposerContext4();
|
|
187
|
+
const lastMarkdownRef = useRef("");
|
|
188
|
+
const onChangeRef = useRef(onChange);
|
|
321
189
|
onChangeRef.current = onChange;
|
|
322
|
-
|
|
190
|
+
useEffect4(() => {
|
|
323
191
|
if (initialMarkdown && initialMarkdown !== lastMarkdownRef.current) {
|
|
324
192
|
lastMarkdownRef.current = initialMarkdown;
|
|
325
193
|
editor.update(() => {
|
|
@@ -327,7 +195,7 @@ function MarkdownSyncPlugin({
|
|
|
327
195
|
}, { tag: "initial-load" });
|
|
328
196
|
}
|
|
329
197
|
}, [editor, initialMarkdown]);
|
|
330
|
-
|
|
198
|
+
useEffect4(() => {
|
|
331
199
|
return editor.registerUpdateListener(
|
|
332
200
|
({ editorState, dirtyElements, dirtyLeaves, tags }) => {
|
|
333
201
|
if (dirtyElements.size === 0 && dirtyLeaves.size === 0) return;
|
|
@@ -342,145 +210,101 @@ function MarkdownSyncPlugin({
|
|
|
342
210
|
return null;
|
|
343
211
|
}
|
|
344
212
|
|
|
345
|
-
// src/
|
|
346
|
-
import { useCallback as useCallback2, useEffect as useEffect6, useState as useState2 } from "react";
|
|
347
|
-
import { useLexicalComposerContext as useLexicalComposerContext6 } from "@lexical/react/LexicalComposerContext";
|
|
348
|
-
import {
|
|
349
|
-
TableOfContentsPlugin
|
|
350
|
-
} from "@lexical/react/LexicalTableOfContentsPlugin";
|
|
351
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
352
|
-
var INDENT_PER_LEVEL = {
|
|
353
|
-
h1: 0,
|
|
354
|
-
h2: 1,
|
|
355
|
-
h3: 2,
|
|
356
|
-
h4: 3,
|
|
357
|
-
h5: 4,
|
|
358
|
-
h6: 5
|
|
359
|
-
};
|
|
360
|
-
function OutlinePlugin() {
|
|
361
|
-
const [editor] = useLexicalComposerContext6();
|
|
362
|
-
const [collapsed, setCollapsed] = useState2(false);
|
|
363
|
-
const [activeKey, setActiveKey] = useState2(null);
|
|
364
|
-
useEffect6(() => {
|
|
365
|
-
const rootElement = editor.getRootElement();
|
|
366
|
-
const scroller = rootElement?.closest(".zui-text-editor");
|
|
367
|
-
if (!rootElement || !(scroller instanceof HTMLElement)) return;
|
|
368
|
-
let frame = 0;
|
|
369
|
-
const updateActive = () => {
|
|
370
|
-
cancelAnimationFrame(frame);
|
|
371
|
-
frame = requestAnimationFrame(() => {
|
|
372
|
-
const headings = rootElement.querySelectorAll(
|
|
373
|
-
"h1, h2, h3, h4, h5, h6"
|
|
374
|
-
);
|
|
375
|
-
const threshold = scroller.scrollTop + 80;
|
|
376
|
-
let current = null;
|
|
377
|
-
for (const el of headings) {
|
|
378
|
-
if (el.offsetTop <= threshold) {
|
|
379
|
-
current = el.getAttribute("data-outline-key");
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
setActiveKey(current);
|
|
383
|
-
});
|
|
384
|
-
};
|
|
385
|
-
updateActive();
|
|
386
|
-
scroller.addEventListener("scroll", updateActive, { passive: true });
|
|
387
|
-
const unregister = editor.registerUpdateListener(updateActive);
|
|
388
|
-
return () => {
|
|
389
|
-
cancelAnimationFrame(frame);
|
|
390
|
-
scroller.removeEventListener("scroll", updateActive);
|
|
391
|
-
unregister();
|
|
392
|
-
};
|
|
393
|
-
}, [editor]);
|
|
394
|
-
const scrollTo = useCallback2(
|
|
395
|
-
(key) => {
|
|
396
|
-
editor.getEditorState().read(() => {
|
|
397
|
-
const element = editor.getElementByKey(key);
|
|
398
|
-
element?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
399
|
-
});
|
|
400
|
-
},
|
|
401
|
-
[editor]
|
|
402
|
-
);
|
|
403
|
-
return /* @__PURE__ */ jsx2(TableOfContentsPlugin, { children: (entries) => {
|
|
404
|
-
for (const [key] of entries) {
|
|
405
|
-
editor.getElementByKey(key)?.setAttribute("data-outline-key", key);
|
|
406
|
-
}
|
|
407
|
-
return /* @__PURE__ */ jsxs(
|
|
408
|
-
"div",
|
|
409
|
-
{
|
|
410
|
-
className: `zui-text-editor-outline ${collapsed ? "is-collapsed" : ""}`,
|
|
411
|
-
children: [
|
|
412
|
-
/* @__PURE__ */ jsx2(
|
|
413
|
-
"button",
|
|
414
|
-
{
|
|
415
|
-
type: "button",
|
|
416
|
-
className: "zui-text-editor-outline-toggle",
|
|
417
|
-
title: collapsed ? "Show outline" : "Hide outline",
|
|
418
|
-
"aria-label": collapsed ? "Show outline" : "Hide outline",
|
|
419
|
-
"aria-expanded": !collapsed,
|
|
420
|
-
onClick: () => setCollapsed((c) => !c),
|
|
421
|
-
children: /* @__PURE__ */ jsx2(
|
|
422
|
-
"svg",
|
|
423
|
-
{
|
|
424
|
-
viewBox: "0 0 20 20",
|
|
425
|
-
width: "14",
|
|
426
|
-
height: "14",
|
|
427
|
-
fill: "none",
|
|
428
|
-
stroke: "currentColor",
|
|
429
|
-
strokeWidth: "1.8",
|
|
430
|
-
strokeLinecap: "round",
|
|
431
|
-
children: /* @__PURE__ */ jsx2("path", { d: "M4 5h12M4 10h8M4 15h10" })
|
|
432
|
-
}
|
|
433
|
-
)
|
|
434
|
-
}
|
|
435
|
-
),
|
|
436
|
-
!collapsed && /* @__PURE__ */ jsxs("nav", { className: "zui-text-editor-outline-list", "aria-label": "Table of contents", children: [
|
|
437
|
-
entries.length === 0 && /* @__PURE__ */ jsx2("div", { className: "zui-text-editor-outline-empty", children: "No headings" }),
|
|
438
|
-
entries.map(([key, text, tag]) => /* @__PURE__ */ jsx2(
|
|
439
|
-
"button",
|
|
440
|
-
{
|
|
441
|
-
type: "button",
|
|
442
|
-
className: `zui-text-editor-outline-item ${key === activeKey ? "is-active" : ""}`,
|
|
443
|
-
style: {
|
|
444
|
-
paddingLeft: `${0.5 + INDENT_PER_LEVEL[tag] * 0.75}rem`
|
|
445
|
-
},
|
|
446
|
-
onClick: () => scrollTo(key),
|
|
447
|
-
children: text || "Untitled"
|
|
448
|
-
},
|
|
449
|
-
key
|
|
450
|
-
))
|
|
451
|
-
] })
|
|
452
|
-
]
|
|
453
|
-
}
|
|
454
|
-
);
|
|
455
|
-
} });
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// src/plugins/ToolbarPlugin.tsx
|
|
459
|
-
import { useCallback as useCallback4, useEffect as useEffect8, useState as useState4 } from "react";
|
|
460
|
-
import {
|
|
461
|
-
$getSelection as $getSelection3,
|
|
462
|
-
$isRangeSelection as $isRangeSelection3,
|
|
463
|
-
FORMAT_TEXT_COMMAND
|
|
464
|
-
} from "lexical";
|
|
465
|
-
import { useLexicalComposerContext as useLexicalComposerContext8 } from "@lexical/react/LexicalComposerContext";
|
|
466
|
-
import { INSERT_TABLE_COMMAND } from "@lexical/table";
|
|
467
|
-
import { $insertNodeToNearestRoot } from "@lexical/utils";
|
|
468
|
-
|
|
469
|
-
// src/nodes/DrawingNode.tsx
|
|
213
|
+
// src/nodes/FrontmatterNode.ts
|
|
470
214
|
import {
|
|
471
215
|
$applyNodeReplacement,
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
216
|
+
$createLineBreakNode,
|
|
217
|
+
$createTextNode,
|
|
218
|
+
ElementNode
|
|
219
|
+
} from "lexical";
|
|
220
|
+
var FrontmatterNode = class _FrontmatterNode extends ElementNode {
|
|
221
|
+
static getType() {
|
|
222
|
+
return "frontmatter";
|
|
223
|
+
}
|
|
224
|
+
static clone(node) {
|
|
225
|
+
return new _FrontmatterNode(node.__key);
|
|
226
|
+
}
|
|
227
|
+
constructor(key) {
|
|
228
|
+
super(key);
|
|
229
|
+
}
|
|
230
|
+
createDOM(config) {
|
|
231
|
+
const dom = document.createElement("div");
|
|
232
|
+
const className = config.theme.frontmatter;
|
|
233
|
+
dom.className = typeof className === "string" ? className : "zui-frontmatter";
|
|
234
|
+
return dom;
|
|
235
|
+
}
|
|
236
|
+
updateDOM() {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
static importJSON(_serializedNode) {
|
|
240
|
+
return $createFrontmatterNode();
|
|
241
|
+
}
|
|
242
|
+
exportJSON() {
|
|
243
|
+
return {
|
|
244
|
+
...super.exportJSON(),
|
|
245
|
+
type: "frontmatter",
|
|
246
|
+
version: 1
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
isInline() {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
canBeEmpty() {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
canIndent() {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
function $createFrontmatterNode() {
|
|
260
|
+
return $applyNodeReplacement(new FrontmatterNode());
|
|
261
|
+
}
|
|
262
|
+
function $isFrontmatterNode(node) {
|
|
263
|
+
return node instanceof FrontmatterNode;
|
|
264
|
+
}
|
|
265
|
+
var FRONTMATTER_DELIMITER = /^---\s*$/;
|
|
266
|
+
var FRONTMATTER = {
|
|
267
|
+
dependencies: [FrontmatterNode],
|
|
268
|
+
export: (node) => {
|
|
269
|
+
if (!$isFrontmatterNode(node)) return null;
|
|
270
|
+
const body = node.getTextContent();
|
|
271
|
+
return body ? `---
|
|
272
|
+
${body}
|
|
273
|
+
---` : "---\n---";
|
|
274
|
+
},
|
|
275
|
+
regExpStart: FRONTMATTER_DELIMITER,
|
|
276
|
+
regExpEnd: FRONTMATTER_DELIMITER,
|
|
277
|
+
replace: (rootNode, children, _startMatch, _endMatch, linesInBetween, isImport) => {
|
|
278
|
+
if (!isImport || children != null || linesInBetween == null) return false;
|
|
279
|
+
if (rootNode.getChildrenSize() !== 0) return false;
|
|
280
|
+
const lines = [...linesInBetween];
|
|
281
|
+
while (lines.length > 0 && lines[0].length === 0) lines.shift();
|
|
282
|
+
while (lines.length > 0 && lines[lines.length - 1].length === 0) lines.pop();
|
|
283
|
+
const node = $createFrontmatterNode();
|
|
284
|
+
lines.forEach((line, index) => {
|
|
285
|
+
if (index > 0) node.append($createLineBreakNode());
|
|
286
|
+
node.append($createTextNode(line));
|
|
287
|
+
});
|
|
288
|
+
rootNode.append(node);
|
|
289
|
+
},
|
|
290
|
+
type: "multiline-element"
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/nodes/DrawingNode.tsx
|
|
294
|
+
import {
|
|
295
|
+
$applyNodeReplacement as $applyNodeReplacement2,
|
|
296
|
+
DecoratorNode
|
|
297
|
+
} from "lexical";
|
|
298
|
+
|
|
475
299
|
// src/components/DrawingCanvas.tsx
|
|
476
300
|
import {
|
|
477
|
-
useCallback
|
|
478
|
-
useEffect as
|
|
479
|
-
useRef as
|
|
480
|
-
useState
|
|
301
|
+
useCallback,
|
|
302
|
+
useEffect as useEffect5,
|
|
303
|
+
useRef as useRef2,
|
|
304
|
+
useState
|
|
481
305
|
} from "react";
|
|
482
306
|
import { $getNodeByKey as $getNodeByKey2 } from "lexical";
|
|
483
|
-
import { useLexicalComposerContext as
|
|
307
|
+
import { useLexicalComposerContext as useLexicalComposerContext5 } from "@lexical/react/LexicalComposerContext";
|
|
484
308
|
import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
|
|
485
309
|
|
|
486
310
|
// src/components/drawingTypes.ts
|
|
@@ -791,7 +615,7 @@ function resolveBindings(shapes) {
|
|
|
791
615
|
}
|
|
792
616
|
|
|
793
617
|
// src/components/DrawingCanvas.tsx
|
|
794
|
-
import { jsx
|
|
618
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
795
619
|
import { createElement } from "react";
|
|
796
620
|
var MIN_HEIGHT = 120;
|
|
797
621
|
var MAX_HEIGHT = 900;
|
|
@@ -825,7 +649,7 @@ function BoxTexts({
|
|
|
825
649
|
opacity: 0.35,
|
|
826
650
|
style: { userSelect: "none", pointerEvents: "none" }
|
|
827
651
|
};
|
|
828
|
-
return /* @__PURE__ */
|
|
652
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
829
653
|
hideField !== "label" && (shape.label ? labelLines.map((line, i) => /* @__PURE__ */ createElement(
|
|
830
654
|
"text",
|
|
831
655
|
{
|
|
@@ -838,7 +662,7 @@ function BoxTexts({
|
|
|
838
662
|
opacity: 0.85
|
|
839
663
|
},
|
|
840
664
|
line
|
|
841
|
-
)) : showHints && /* @__PURE__ */
|
|
665
|
+
)) : showHints && /* @__PURE__ */ jsx("text", { ...hint, x: cx, y: b.y + SMALL_FONT_SIZE + 7, fontSize: SMALL_FONT_SIZE, children: "Label" })),
|
|
842
666
|
hideField !== "text" && (shape.text ? mainLines.map((line, i) => /* @__PURE__ */ createElement(
|
|
843
667
|
"text",
|
|
844
668
|
{
|
|
@@ -849,7 +673,7 @@ function BoxTexts({
|
|
|
849
673
|
fontSize: FONT_SIZE
|
|
850
674
|
},
|
|
851
675
|
line
|
|
852
|
-
)) : showHints && /* @__PURE__ */
|
|
676
|
+
)) : showHints && /* @__PURE__ */ jsx("text", { ...hint, x: cx, y: mainStart, fontSize: FONT_SIZE, children: "Text" })),
|
|
853
677
|
hideField !== "footer" && (shape.footer ? footerLines.map((line, i) => /* @__PURE__ */ createElement(
|
|
854
678
|
"text",
|
|
855
679
|
{
|
|
@@ -861,7 +685,7 @@ function BoxTexts({
|
|
|
861
685
|
opacity: 0.65
|
|
862
686
|
},
|
|
863
687
|
line
|
|
864
|
-
)) : showHints && /* @__PURE__ */
|
|
688
|
+
)) : showHints && /* @__PURE__ */ jsx("text", { ...hint, x: cx, y: b.y + b.h - 9, fontSize: SMALL_FONT_SIZE, children: "Footer" }))
|
|
865
689
|
] });
|
|
866
690
|
}
|
|
867
691
|
function ShapeView({
|
|
@@ -878,8 +702,8 @@ function ShapeView({
|
|
|
878
702
|
switch (shape.type) {
|
|
879
703
|
case "rect": {
|
|
880
704
|
const b = bbox(shape);
|
|
881
|
-
return /* @__PURE__ */
|
|
882
|
-
/* @__PURE__ */
|
|
705
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
706
|
+
/* @__PURE__ */ jsx(
|
|
883
707
|
"rect",
|
|
884
708
|
{
|
|
885
709
|
...common,
|
|
@@ -891,13 +715,13 @@ function ShapeView({
|
|
|
891
715
|
fill: shape.fill
|
|
892
716
|
}
|
|
893
717
|
),
|
|
894
|
-
/* @__PURE__ */
|
|
718
|
+
/* @__PURE__ */ jsx(BoxTexts, { shape, hideField, showHints })
|
|
895
719
|
] });
|
|
896
720
|
}
|
|
897
721
|
case "ellipse": {
|
|
898
722
|
const b = bbox(shape);
|
|
899
|
-
return /* @__PURE__ */
|
|
900
|
-
/* @__PURE__ */
|
|
723
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
724
|
+
/* @__PURE__ */ jsx(
|
|
901
725
|
"ellipse",
|
|
902
726
|
{
|
|
903
727
|
...common,
|
|
@@ -908,13 +732,13 @@ function ShapeView({
|
|
|
908
732
|
fill: shape.fill
|
|
909
733
|
}
|
|
910
734
|
),
|
|
911
|
-
/* @__PURE__ */
|
|
735
|
+
/* @__PURE__ */ jsx(BoxTexts, { shape, hideField, showHints })
|
|
912
736
|
] });
|
|
913
737
|
}
|
|
914
738
|
case "triangle":
|
|
915
739
|
case "pentagon":
|
|
916
|
-
return /* @__PURE__ */
|
|
917
|
-
/* @__PURE__ */
|
|
740
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
741
|
+
/* @__PURE__ */ jsx(
|
|
918
742
|
"polygon",
|
|
919
743
|
{
|
|
920
744
|
...common,
|
|
@@ -922,12 +746,12 @@ function ShapeView({
|
|
|
922
746
|
fill: shape.fill
|
|
923
747
|
}
|
|
924
748
|
),
|
|
925
|
-
/* @__PURE__ */
|
|
749
|
+
/* @__PURE__ */ jsx(BoxTexts, { shape, hideField, showHints })
|
|
926
750
|
] });
|
|
927
751
|
case "line":
|
|
928
752
|
case "arrow":
|
|
929
|
-
return /* @__PURE__ */
|
|
930
|
-
/* @__PURE__ */
|
|
753
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
754
|
+
/* @__PURE__ */ jsx(
|
|
931
755
|
"path",
|
|
932
756
|
{
|
|
933
757
|
...common,
|
|
@@ -935,10 +759,10 @@ function ShapeView({
|
|
|
935
759
|
fill: "none"
|
|
936
760
|
}
|
|
937
761
|
),
|
|
938
|
-
shape.text && hideField !== "text" && /* @__PURE__ */
|
|
762
|
+
shape.text && hideField !== "text" && /* @__PURE__ */ jsx(ConnectorLabel, { shape })
|
|
939
763
|
] });
|
|
940
764
|
case "text":
|
|
941
|
-
return /* @__PURE__ */
|
|
765
|
+
return /* @__PURE__ */ jsx(
|
|
942
766
|
"text",
|
|
943
767
|
{
|
|
944
768
|
x: shape.x,
|
|
@@ -947,7 +771,7 @@ function ShapeView({
|
|
|
947
771
|
fontSize: FONT_SIZE,
|
|
948
772
|
fontFamily: "ui-sans-serif, system-ui, sans-serif",
|
|
949
773
|
style: { userSelect: "none", whiteSpace: "pre" },
|
|
950
|
-
children: (shape.text ?? "").split("\n").map((line, i) => /* @__PURE__ */
|
|
774
|
+
children: (shape.text ?? "").split("\n").map((line, i) => /* @__PURE__ */ jsx("tspan", { x: shape.x, dy: i === 0 ? 0 : FONT_SIZE * 1.35, children: line }, i))
|
|
951
775
|
}
|
|
952
776
|
);
|
|
953
777
|
}
|
|
@@ -959,8 +783,8 @@ function ConnectorLabel({ shape }) {
|
|
|
959
783
|
const { x: midX, y: midY } = connectorMidpoint(shape);
|
|
960
784
|
const lineH = CONNECTOR_FONT_SIZE * LINE_HEIGHT;
|
|
961
785
|
const startY = midY - (lines.length - 1) * lineH / 2 + CONNECTOR_FONT_SIZE * 0.35;
|
|
962
|
-
return /* @__PURE__ */
|
|
963
|
-
/* @__PURE__ */
|
|
786
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
787
|
+
/* @__PURE__ */ jsx(
|
|
964
788
|
"rect",
|
|
965
789
|
{
|
|
966
790
|
x: midX - size.w / 2 - 5,
|
|
@@ -972,7 +796,7 @@ function ConnectorLabel({ shape }) {
|
|
|
972
796
|
opacity: 0.92
|
|
973
797
|
}
|
|
974
798
|
),
|
|
975
|
-
lines.map((line, i) => /* @__PURE__ */
|
|
799
|
+
lines.map((line, i) => /* @__PURE__ */ jsx(
|
|
976
800
|
"text",
|
|
977
801
|
{
|
|
978
802
|
x: midX,
|
|
@@ -989,20 +813,20 @@ function ConnectorLabel({ shape }) {
|
|
|
989
813
|
] });
|
|
990
814
|
}
|
|
991
815
|
var TOOL_ICONS = {
|
|
992
|
-
select: /* @__PURE__ */
|
|
993
|
-
rect: /* @__PURE__ */
|
|
994
|
-
ellipse: /* @__PURE__ */
|
|
995
|
-
triangle: /* @__PURE__ */
|
|
996
|
-
pentagon: /* @__PURE__ */
|
|
997
|
-
arrow: /* @__PURE__ */
|
|
998
|
-
/* @__PURE__ */
|
|
999
|
-
/* @__PURE__ */
|
|
816
|
+
select: /* @__PURE__ */ jsx("path", { d: "M4 2l12 6.5-5.2 1.6L8 16z", fill: "currentColor", stroke: "none" }),
|
|
817
|
+
rect: /* @__PURE__ */ jsx("rect", { x: "3", y: "4.5", width: "14", height: "11", rx: "2" }),
|
|
818
|
+
ellipse: /* @__PURE__ */ jsx("ellipse", { cx: "10", cy: "10", rx: "7", ry: "5.5" }),
|
|
819
|
+
triangle: /* @__PURE__ */ jsx("path", { d: "M10 3.5L17 16.5H3z", strokeLinejoin: "round" }),
|
|
820
|
+
pentagon: /* @__PURE__ */ jsx("path", { d: "M10 3L16.8 8.1L14.2 16.2H5.8L3.2 8.1z", strokeLinejoin: "round" }),
|
|
821
|
+
arrow: /* @__PURE__ */ jsxs("g", { children: [
|
|
822
|
+
/* @__PURE__ */ jsx("path", { d: "M4 16L16 4" }),
|
|
823
|
+
/* @__PURE__ */ jsx("path", { d: "M9 4h7v7" })
|
|
1000
824
|
] }),
|
|
1001
|
-
line: /* @__PURE__ */
|
|
1002
|
-
text: /* @__PURE__ */
|
|
1003
|
-
/* @__PURE__ */
|
|
1004
|
-
/* @__PURE__ */
|
|
1005
|
-
/* @__PURE__ */
|
|
825
|
+
line: /* @__PURE__ */ jsx("path", { d: "M4 16L16 4" }),
|
|
826
|
+
text: /* @__PURE__ */ jsxs("g", { children: [
|
|
827
|
+
/* @__PURE__ */ jsx("path", { d: "M4 5V3.5h12V5" }),
|
|
828
|
+
/* @__PURE__ */ jsx("path", { d: "M10 3.5V16.5" }),
|
|
829
|
+
/* @__PURE__ */ jsx("path", { d: "M7 16.5h6" })
|
|
1006
830
|
] })
|
|
1007
831
|
};
|
|
1008
832
|
function bindEndpoints(shape, shapes) {
|
|
@@ -1026,23 +850,23 @@ var TOOLS = [
|
|
|
1026
850
|
{ tool: "text", label: "Text" }
|
|
1027
851
|
];
|
|
1028
852
|
function DrawingCanvas({ nodeKey, data }) {
|
|
1029
|
-
const [editor] =
|
|
853
|
+
const [editor] = useLexicalComposerContext5();
|
|
1030
854
|
const isEditable = useLexicalEditable();
|
|
1031
|
-
const [shapes, setShapes] =
|
|
1032
|
-
const [height, setHeight] =
|
|
1033
|
-
const [tool, setTool] =
|
|
1034
|
-
const [selectedId, setSelectedId] =
|
|
1035
|
-
const [editingText, setEditingText] =
|
|
1036
|
-
const [stroke, setStroke] =
|
|
1037
|
-
const [fill, setFill] =
|
|
1038
|
-
const svgRef =
|
|
1039
|
-
const dragRef =
|
|
1040
|
-
const lastCommittedRef =
|
|
1041
|
-
const shapesRef =
|
|
855
|
+
const [shapes, setShapes] = useState(data.shapes);
|
|
856
|
+
const [height, setHeight] = useState(data.height);
|
|
857
|
+
const [tool, setTool] = useState("select");
|
|
858
|
+
const [selectedId, setSelectedId] = useState(null);
|
|
859
|
+
const [editingText, setEditingText] = useState(null);
|
|
860
|
+
const [stroke, setStroke] = useState(STROKE_COLORS[0]);
|
|
861
|
+
const [fill, setFill] = useState(FILL_COLORS[0]);
|
|
862
|
+
const svgRef = useRef2(null);
|
|
863
|
+
const dragRef = useRef2(null);
|
|
864
|
+
const lastCommittedRef = useRef2(serializeDrawingData(data));
|
|
865
|
+
const shapesRef = useRef2(shapes);
|
|
1042
866
|
shapesRef.current = shapes;
|
|
1043
|
-
const heightRef =
|
|
867
|
+
const heightRef = useRef2(height);
|
|
1044
868
|
heightRef.current = height;
|
|
1045
|
-
|
|
869
|
+
useEffect5(() => {
|
|
1046
870
|
const incoming = serializeDrawingData(data);
|
|
1047
871
|
if (incoming !== lastCommittedRef.current) {
|
|
1048
872
|
lastCommittedRef.current = incoming;
|
|
@@ -1052,7 +876,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1052
876
|
setEditingText(null);
|
|
1053
877
|
}
|
|
1054
878
|
}, [data]);
|
|
1055
|
-
const commit =
|
|
879
|
+
const commit = useCallback(
|
|
1056
880
|
(nextShapes, nextHeight) => {
|
|
1057
881
|
const payload = {
|
|
1058
882
|
version: 1,
|
|
@@ -1071,7 +895,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1071
895
|
},
|
|
1072
896
|
[editor, nodeKey]
|
|
1073
897
|
);
|
|
1074
|
-
const updateShapes =
|
|
898
|
+
const updateShapes = useCallback(
|
|
1075
899
|
(updater, options) => {
|
|
1076
900
|
setShapes((prev) => {
|
|
1077
901
|
const next = resolveBindings(updater(prev));
|
|
@@ -1081,17 +905,17 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1081
905
|
},
|
|
1082
906
|
[commit]
|
|
1083
907
|
);
|
|
1084
|
-
const getPoint =
|
|
908
|
+
const getPoint = useCallback((e) => {
|
|
1085
909
|
const rect = svgRef.current?.getBoundingClientRect();
|
|
1086
910
|
if (!rect) return { x: 0, y: 0 };
|
|
1087
911
|
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
1088
912
|
}, []);
|
|
1089
913
|
const selectedShape = shapes.find((s) => s.id === selectedId) ?? null;
|
|
1090
|
-
const startTextEditing =
|
|
914
|
+
const startTextEditing = useCallback((id, field) => {
|
|
1091
915
|
setEditingText({ id, field });
|
|
1092
916
|
setSelectedId(id);
|
|
1093
917
|
}, []);
|
|
1094
|
-
const handleBackgroundPointerDown =
|
|
918
|
+
const handleBackgroundPointerDown = useCallback(
|
|
1095
919
|
(e) => {
|
|
1096
920
|
if (!isEditable || editingText) return;
|
|
1097
921
|
if (e.button !== 0) return;
|
|
@@ -1151,7 +975,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1151
975
|
startTextEditing
|
|
1152
976
|
]
|
|
1153
977
|
);
|
|
1154
|
-
const handleShapePointerDown =
|
|
978
|
+
const handleShapePointerDown = useCallback(
|
|
1155
979
|
(e, shape) => {
|
|
1156
980
|
if (!isEditable || tool !== "select" || editingText) return;
|
|
1157
981
|
if (e.button !== 0) return;
|
|
@@ -1170,7 +994,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1170
994
|
},
|
|
1171
995
|
[isEditable, tool, editingText, getPoint, selectedId]
|
|
1172
996
|
);
|
|
1173
|
-
const handleHandlePointerDown =
|
|
997
|
+
const handleHandlePointerDown = useCallback(
|
|
1174
998
|
(e, drag) => {
|
|
1175
999
|
if (!isEditable) return;
|
|
1176
1000
|
if (e.button !== 0) return;
|
|
@@ -1186,7 +1010,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1186
1010
|
},
|
|
1187
1011
|
[isEditable, updateShapes]
|
|
1188
1012
|
);
|
|
1189
|
-
const handlePointerMove =
|
|
1013
|
+
const handlePointerMove = useCallback(
|
|
1190
1014
|
(e) => {
|
|
1191
1015
|
const drag = dragRef.current;
|
|
1192
1016
|
if (!drag) return;
|
|
@@ -1277,7 +1101,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1277
1101
|
},
|
|
1278
1102
|
[getPoint, updateShapes]
|
|
1279
1103
|
);
|
|
1280
|
-
const handlePointerUp =
|
|
1104
|
+
const handlePointerUp = useCallback(() => {
|
|
1281
1105
|
const drag = dragRef.current;
|
|
1282
1106
|
dragRef.current = null;
|
|
1283
1107
|
if (!drag) return;
|
|
@@ -1330,7 +1154,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1330
1154
|
}
|
|
1331
1155
|
updateShapes((prev) => prev.map(normalize), { commit: true });
|
|
1332
1156
|
}, [commit, updateShapes, startTextEditing]);
|
|
1333
|
-
const deleteSelected =
|
|
1157
|
+
const deleteSelected = useCallback(() => {
|
|
1334
1158
|
if (!selectedId) return;
|
|
1335
1159
|
setEditingText(null);
|
|
1336
1160
|
setSelectedId(null);
|
|
@@ -1338,7 +1162,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1338
1162
|
commit: true
|
|
1339
1163
|
});
|
|
1340
1164
|
}, [selectedId, updateShapes]);
|
|
1341
|
-
const handleKeyDown =
|
|
1165
|
+
const handleKeyDown = useCallback(
|
|
1342
1166
|
(e) => {
|
|
1343
1167
|
if (!isEditable || editingText) return;
|
|
1344
1168
|
if ((e.key === "Delete" || e.key === "Backspace") && selectedId) {
|
|
@@ -1366,7 +1190,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1366
1190
|
startTextEditing
|
|
1367
1191
|
]
|
|
1368
1192
|
);
|
|
1369
|
-
const applyStroke =
|
|
1193
|
+
const applyStroke = useCallback(
|
|
1370
1194
|
(color) => {
|
|
1371
1195
|
setStroke(color);
|
|
1372
1196
|
if (selectedId) {
|
|
@@ -1378,7 +1202,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1378
1202
|
},
|
|
1379
1203
|
[selectedId, updateShapes]
|
|
1380
1204
|
);
|
|
1381
|
-
const applyFill =
|
|
1205
|
+
const applyFill = useCallback(
|
|
1382
1206
|
(color) => {
|
|
1383
1207
|
setFill(color);
|
|
1384
1208
|
if (selectedId) {
|
|
@@ -1390,7 +1214,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1390
1214
|
},
|
|
1391
1215
|
[selectedId, updateShapes]
|
|
1392
1216
|
);
|
|
1393
|
-
const applyRouting =
|
|
1217
|
+
const applyRouting = useCallback(
|
|
1394
1218
|
(elbow) => {
|
|
1395
1219
|
if (!selectedId) return;
|
|
1396
1220
|
updateShapes(
|
|
@@ -1407,7 +1231,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1407
1231
|
},
|
|
1408
1232
|
[selectedId, updateShapes]
|
|
1409
1233
|
);
|
|
1410
|
-
const addWaypoint =
|
|
1234
|
+
const addWaypoint = useCallback(
|
|
1411
1235
|
(e, shapeId, segmentIndex, point) => {
|
|
1412
1236
|
if (!isEditable) return;
|
|
1413
1237
|
if (e.button !== 0) return;
|
|
@@ -1429,7 +1253,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1429
1253
|
},
|
|
1430
1254
|
[isEditable, updateShapes]
|
|
1431
1255
|
);
|
|
1432
|
-
const removeWaypoint =
|
|
1256
|
+
const removeWaypoint = useCallback(
|
|
1433
1257
|
(shapeId, index) => {
|
|
1434
1258
|
updateShapes(
|
|
1435
1259
|
(prev) => prev.map((s) => {
|
|
@@ -1442,7 +1266,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1442
1266
|
},
|
|
1443
1267
|
[updateShapes]
|
|
1444
1268
|
);
|
|
1445
|
-
const applyDirection =
|
|
1269
|
+
const applyDirection = useCallback(
|
|
1446
1270
|
(bidirectional) => {
|
|
1447
1271
|
if (!selectedId) return;
|
|
1448
1272
|
updateShapes(
|
|
@@ -1454,7 +1278,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1454
1278
|
},
|
|
1455
1279
|
[selectedId, updateShapes]
|
|
1456
1280
|
);
|
|
1457
|
-
const commitText =
|
|
1281
|
+
const commitText = useCallback(
|
|
1458
1282
|
(id, field, value) => {
|
|
1459
1283
|
setEditingText(null);
|
|
1460
1284
|
updateShapes(
|
|
@@ -1475,15 +1299,15 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1475
1299
|
const editingShape = shapes.find((s) => s.id === editingText?.id) ?? null;
|
|
1476
1300
|
const showFill = selectedShape != null ? isBoxType(selectedShape.type) : tool !== "select" && isBoxType(tool);
|
|
1477
1301
|
const canvasCursor = tool === "select" ? "default" : tool === "text" ? "text" : "crosshair";
|
|
1478
|
-
return /* @__PURE__ */
|
|
1302
|
+
return /* @__PURE__ */ jsxs(
|
|
1479
1303
|
"div",
|
|
1480
1304
|
{
|
|
1481
1305
|
className: "zui-drawing-canvas",
|
|
1482
1306
|
tabIndex: isEditable ? 0 : void 0,
|
|
1483
1307
|
onKeyDown: handleKeyDown,
|
|
1484
1308
|
children: [
|
|
1485
|
-
isEditable && /* @__PURE__ */
|
|
1486
|
-
/* @__PURE__ */
|
|
1309
|
+
isEditable && /* @__PURE__ */ jsxs("div", { className: "zui-drawing-toolbar", onPointerDown: (e) => e.stopPropagation(), children: [
|
|
1310
|
+
/* @__PURE__ */ jsx("div", { className: "zui-drawing-toolbar-group", children: TOOLS.map(({ tool: t, label }) => /* @__PURE__ */ jsx(
|
|
1487
1311
|
"button",
|
|
1488
1312
|
{
|
|
1489
1313
|
type: "button",
|
|
@@ -1494,7 +1318,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1494
1318
|
setTool(t);
|
|
1495
1319
|
if (t !== "select") setSelectedId(null);
|
|
1496
1320
|
},
|
|
1497
|
-
children: /* @__PURE__ */
|
|
1321
|
+
children: /* @__PURE__ */ jsx(
|
|
1498
1322
|
"svg",
|
|
1499
1323
|
{
|
|
1500
1324
|
viewBox: "0 0 20 20",
|
|
@@ -1511,9 +1335,9 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1511
1335
|
},
|
|
1512
1336
|
t
|
|
1513
1337
|
)) }),
|
|
1514
|
-
/* @__PURE__ */
|
|
1515
|
-
/* @__PURE__ */
|
|
1516
|
-
STROKE_COLORS.map((color) => /* @__PURE__ */
|
|
1338
|
+
/* @__PURE__ */ jsxs("div", { className: "zui-drawing-toolbar-group", children: [
|
|
1339
|
+
/* @__PURE__ */ jsx("span", { className: "zui-drawing-swatch-label", children: "Stroke" }),
|
|
1340
|
+
STROKE_COLORS.map((color) => /* @__PURE__ */ jsx(
|
|
1517
1341
|
"button",
|
|
1518
1342
|
{
|
|
1519
1343
|
type: "button",
|
|
@@ -1526,9 +1350,9 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1526
1350
|
color
|
|
1527
1351
|
))
|
|
1528
1352
|
] }),
|
|
1529
|
-
showFill && /* @__PURE__ */
|
|
1530
|
-
/* @__PURE__ */
|
|
1531
|
-
FILL_COLORS.map((color) => /* @__PURE__ */
|
|
1353
|
+
showFill && /* @__PURE__ */ jsxs("div", { className: "zui-drawing-toolbar-group", children: [
|
|
1354
|
+
/* @__PURE__ */ jsx("span", { className: "zui-drawing-swatch-label", children: "Fill" }),
|
|
1355
|
+
FILL_COLORS.map((color) => /* @__PURE__ */ jsx(
|
|
1532
1356
|
"button",
|
|
1533
1357
|
{
|
|
1534
1358
|
type: "button",
|
|
@@ -1541,8 +1365,8 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1541
1365
|
color
|
|
1542
1366
|
))
|
|
1543
1367
|
] }),
|
|
1544
|
-
selectedShape && isConnectorType(selectedShape.type) && /* @__PURE__ */
|
|
1545
|
-
/* @__PURE__ */
|
|
1368
|
+
selectedShape && isConnectorType(selectedShape.type) && /* @__PURE__ */ jsxs("div", { className: "zui-drawing-toolbar-group", children: [
|
|
1369
|
+
/* @__PURE__ */ jsx(
|
|
1546
1370
|
"button",
|
|
1547
1371
|
{
|
|
1548
1372
|
type: "button",
|
|
@@ -1550,7 +1374,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1550
1374
|
"aria-label": "Straight connector",
|
|
1551
1375
|
className: `zui-drawing-tool ${selectedShape.routing !== "elbow" ? "is-active" : ""}`,
|
|
1552
1376
|
onClick: () => applyRouting(false),
|
|
1553
|
-
children: /* @__PURE__ */
|
|
1377
|
+
children: /* @__PURE__ */ jsx(
|
|
1554
1378
|
"svg",
|
|
1555
1379
|
{
|
|
1556
1380
|
viewBox: "0 0 20 20",
|
|
@@ -1560,12 +1384,12 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1560
1384
|
stroke: "currentColor",
|
|
1561
1385
|
strokeWidth: "1.6",
|
|
1562
1386
|
strokeLinecap: "round",
|
|
1563
|
-
children: /* @__PURE__ */
|
|
1387
|
+
children: /* @__PURE__ */ jsx("path", { d: "M4 16L16 4" })
|
|
1564
1388
|
}
|
|
1565
1389
|
)
|
|
1566
1390
|
}
|
|
1567
1391
|
),
|
|
1568
|
-
/* @__PURE__ */
|
|
1392
|
+
/* @__PURE__ */ jsx(
|
|
1569
1393
|
"button",
|
|
1570
1394
|
{
|
|
1571
1395
|
type: "button",
|
|
@@ -1573,7 +1397,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1573
1397
|
"aria-label": "Elbow connector",
|
|
1574
1398
|
className: `zui-drawing-tool ${selectedShape.routing === "elbow" ? "is-active" : ""}`,
|
|
1575
1399
|
onClick: () => applyRouting(true),
|
|
1576
|
-
children: /* @__PURE__ */
|
|
1400
|
+
children: /* @__PURE__ */ jsx(
|
|
1577
1401
|
"svg",
|
|
1578
1402
|
{
|
|
1579
1403
|
viewBox: "0 0 20 20",
|
|
@@ -1584,14 +1408,14 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1584
1408
|
strokeWidth: "1.6",
|
|
1585
1409
|
strokeLinecap: "round",
|
|
1586
1410
|
strokeLinejoin: "round",
|
|
1587
|
-
children: /* @__PURE__ */
|
|
1411
|
+
children: /* @__PURE__ */ jsx("path", { d: "M4 16v-6h12V4" })
|
|
1588
1412
|
}
|
|
1589
1413
|
)
|
|
1590
1414
|
}
|
|
1591
1415
|
)
|
|
1592
1416
|
] }),
|
|
1593
|
-
selectedShape?.type === "arrow" && /* @__PURE__ */
|
|
1594
|
-
/* @__PURE__ */
|
|
1417
|
+
selectedShape?.type === "arrow" && /* @__PURE__ */ jsxs("div", { className: "zui-drawing-toolbar-group", children: [
|
|
1418
|
+
/* @__PURE__ */ jsx(
|
|
1595
1419
|
"button",
|
|
1596
1420
|
{
|
|
1597
1421
|
type: "button",
|
|
@@ -1599,7 +1423,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1599
1423
|
"aria-label": "One-way arrow",
|
|
1600
1424
|
className: `zui-drawing-tool ${!selectedShape.bidirectional ? "is-active" : ""}`,
|
|
1601
1425
|
onClick: () => applyDirection(false),
|
|
1602
|
-
children: /* @__PURE__ */
|
|
1426
|
+
children: /* @__PURE__ */ jsx(
|
|
1603
1427
|
"svg",
|
|
1604
1428
|
{
|
|
1605
1429
|
viewBox: "0 0 20 20",
|
|
@@ -1610,12 +1434,12 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1610
1434
|
strokeWidth: "1.6",
|
|
1611
1435
|
strokeLinecap: "round",
|
|
1612
1436
|
strokeLinejoin: "round",
|
|
1613
|
-
children: /* @__PURE__ */
|
|
1437
|
+
children: /* @__PURE__ */ jsx("path", { d: "M3 10h13M12 5.5L16.5 10L12 14.5" })
|
|
1614
1438
|
}
|
|
1615
1439
|
)
|
|
1616
1440
|
}
|
|
1617
1441
|
),
|
|
1618
|
-
/* @__PURE__ */
|
|
1442
|
+
/* @__PURE__ */ jsx(
|
|
1619
1443
|
"button",
|
|
1620
1444
|
{
|
|
1621
1445
|
type: "button",
|
|
@@ -1623,7 +1447,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1623
1447
|
"aria-label": "Two-way arrow",
|
|
1624
1448
|
className: `zui-drawing-tool ${selectedShape.bidirectional ? "is-active" : ""}`,
|
|
1625
1449
|
onClick: () => applyDirection(true),
|
|
1626
|
-
children: /* @__PURE__ */
|
|
1450
|
+
children: /* @__PURE__ */ jsx(
|
|
1627
1451
|
"svg",
|
|
1628
1452
|
{
|
|
1629
1453
|
viewBox: "0 0 20 20",
|
|
@@ -1634,13 +1458,13 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1634
1458
|
strokeWidth: "1.6",
|
|
1635
1459
|
strokeLinecap: "round",
|
|
1636
1460
|
strokeLinejoin: "round",
|
|
1637
|
-
children: /* @__PURE__ */
|
|
1461
|
+
children: /* @__PURE__ */ jsx("path", { d: "M3.5 10h13M8 5.5L3.5 10L8 14.5M12 5.5L16.5 10L12 14.5" })
|
|
1638
1462
|
}
|
|
1639
1463
|
)
|
|
1640
1464
|
}
|
|
1641
1465
|
)
|
|
1642
1466
|
] }),
|
|
1643
|
-
selectedShape && /* @__PURE__ */
|
|
1467
|
+
selectedShape && /* @__PURE__ */ jsx("div", { className: "zui-drawing-toolbar-group", children: /* @__PURE__ */ jsx(
|
|
1644
1468
|
"button",
|
|
1645
1469
|
{
|
|
1646
1470
|
type: "button",
|
|
@@ -1648,7 +1472,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1648
1472
|
"aria-label": "Delete shape",
|
|
1649
1473
|
className: "zui-drawing-tool zui-drawing-tool-danger",
|
|
1650
1474
|
onClick: deleteSelected,
|
|
1651
|
-
children: /* @__PURE__ */
|
|
1475
|
+
children: /* @__PURE__ */ jsx(
|
|
1652
1476
|
"svg",
|
|
1653
1477
|
{
|
|
1654
1478
|
viewBox: "0 0 20 20",
|
|
@@ -1658,13 +1482,13 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1658
1482
|
stroke: "currentColor",
|
|
1659
1483
|
strokeWidth: "1.6",
|
|
1660
1484
|
strokeLinecap: "round",
|
|
1661
|
-
children: /* @__PURE__ */
|
|
1485
|
+
children: /* @__PURE__ */ jsx("path", { d: "M4 6h12M8 6V4h4v2M6 6l1 10h6l1-10M8.5 9v4M11.5 9v4" })
|
|
1662
1486
|
}
|
|
1663
1487
|
)
|
|
1664
1488
|
}
|
|
1665
1489
|
) })
|
|
1666
1490
|
] }),
|
|
1667
|
-
/* @__PURE__ */
|
|
1491
|
+
/* @__PURE__ */ jsxs(
|
|
1668
1492
|
"svg",
|
|
1669
1493
|
{
|
|
1670
1494
|
ref: svgRef,
|
|
@@ -1691,7 +1515,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1691
1515
|
}
|
|
1692
1516
|
},
|
|
1693
1517
|
children: [
|
|
1694
|
-
shapes.map((shape) => /* @__PURE__ */
|
|
1518
|
+
shapes.map((shape) => /* @__PURE__ */ jsxs(
|
|
1695
1519
|
"g",
|
|
1696
1520
|
{
|
|
1697
1521
|
"data-shape-id": shape.id,
|
|
@@ -1700,7 +1524,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1700
1524
|
},
|
|
1701
1525
|
onPointerDown: (e) => handleShapePointerDown(e, shape),
|
|
1702
1526
|
children: [
|
|
1703
|
-
isConnectorType(shape.type) && /* @__PURE__ */
|
|
1527
|
+
isConnectorType(shape.type) && /* @__PURE__ */ jsx(
|
|
1704
1528
|
"path",
|
|
1705
1529
|
{
|
|
1706
1530
|
d: connectorPath(shape),
|
|
@@ -1709,8 +1533,8 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1709
1533
|
strokeWidth: 14
|
|
1710
1534
|
}
|
|
1711
1535
|
),
|
|
1712
|
-
(isBoxType(shape.type) || shape.type === "text") && /* @__PURE__ */
|
|
1713
|
-
shape.id === editingText?.id && shape.type === "text" ? null : /* @__PURE__ */
|
|
1536
|
+
(isBoxType(shape.type) || shape.type === "text") && /* @__PURE__ */ jsx(HitArea, { shape }),
|
|
1537
|
+
shape.id === editingText?.id && shape.type === "text" ? null : /* @__PURE__ */ jsx(
|
|
1714
1538
|
ShapeView,
|
|
1715
1539
|
{
|
|
1716
1540
|
shape,
|
|
@@ -1722,7 +1546,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1722
1546
|
},
|
|
1723
1547
|
shape.id
|
|
1724
1548
|
)),
|
|
1725
|
-
selectedShape && isEditable && !editingText && /* @__PURE__ */
|
|
1549
|
+
selectedShape && isEditable && !editingText && /* @__PURE__ */ jsx(
|
|
1726
1550
|
SelectionOverlay,
|
|
1727
1551
|
{
|
|
1728
1552
|
shape: selectedShape,
|
|
@@ -1734,7 +1558,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1734
1558
|
]
|
|
1735
1559
|
}
|
|
1736
1560
|
),
|
|
1737
|
-
editingShape && editingText && /* @__PURE__ */
|
|
1561
|
+
editingShape && editingText && /* @__PURE__ */ jsx(
|
|
1738
1562
|
TextEditOverlay,
|
|
1739
1563
|
{
|
|
1740
1564
|
shape: editingShape,
|
|
@@ -1743,7 +1567,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1743
1567
|
},
|
|
1744
1568
|
`${editingShape.id}:${editingText.field}`
|
|
1745
1569
|
),
|
|
1746
|
-
isEditable && /* @__PURE__ */
|
|
1570
|
+
isEditable && /* @__PURE__ */ jsx(
|
|
1747
1571
|
"div",
|
|
1748
1572
|
{
|
|
1749
1573
|
className: "zui-drawing-resize",
|
|
@@ -1782,7 +1606,7 @@ function DrawingCanvas({ nodeKey, data }) {
|
|
|
1782
1606
|
}
|
|
1783
1607
|
function HitArea({ shape }) {
|
|
1784
1608
|
const b = bbox(shape);
|
|
1785
|
-
return /* @__PURE__ */
|
|
1609
|
+
return /* @__PURE__ */ jsx(
|
|
1786
1610
|
"rect",
|
|
1787
1611
|
{
|
|
1788
1612
|
x: b.x,
|
|
@@ -1816,8 +1640,8 @@ function SelectionOverlay({
|
|
|
1816
1640
|
if (hasElbowHandle && i === 1) return [];
|
|
1817
1641
|
return [{ index: i, x: (p.x + q.x) / 2, y: (p.y + q.y) / 2 }];
|
|
1818
1642
|
});
|
|
1819
|
-
return /* @__PURE__ */
|
|
1820
|
-
ghosts.map(({ index, x, y }) => /* @__PURE__ */
|
|
1643
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
1644
|
+
ghosts.map(({ index, x, y }) => /* @__PURE__ */ jsx(
|
|
1821
1645
|
"circle",
|
|
1822
1646
|
{
|
|
1823
1647
|
cx: x,
|
|
@@ -1830,11 +1654,11 @@ function SelectionOverlay({
|
|
|
1830
1654
|
opacity: 0.6,
|
|
1831
1655
|
style: { cursor: "copy" },
|
|
1832
1656
|
onPointerDown: (e) => onWaypointAdd(e, shape.id, index, { x, y }),
|
|
1833
|
-
children: /* @__PURE__ */
|
|
1657
|
+
children: /* @__PURE__ */ jsx("title", { children: "Drag to add a point" })
|
|
1834
1658
|
},
|
|
1835
1659
|
`ghost-${index}`
|
|
1836
1660
|
)),
|
|
1837
|
-
waypoints.map((p, i) => /* @__PURE__ */
|
|
1661
|
+
waypoints.map((p, i) => /* @__PURE__ */ jsx(
|
|
1838
1662
|
"rect",
|
|
1839
1663
|
{
|
|
1840
1664
|
x: p.x - 4,
|
|
@@ -1855,11 +1679,11 @@ function SelectionOverlay({
|
|
|
1855
1679
|
e.stopPropagation();
|
|
1856
1680
|
onWaypointRemove(shape.id, i);
|
|
1857
1681
|
},
|
|
1858
|
-
children: /* @__PURE__ */
|
|
1682
|
+
children: /* @__PURE__ */ jsx("title", { children: "Drag to move, double-click to remove" })
|
|
1859
1683
|
},
|
|
1860
1684
|
`wp-${i}`
|
|
1861
1685
|
)),
|
|
1862
|
-
ends.map(({ end, x, y }) => /* @__PURE__ */
|
|
1686
|
+
ends.map(({ end, x, y }) => /* @__PURE__ */ jsx(
|
|
1863
1687
|
"circle",
|
|
1864
1688
|
{
|
|
1865
1689
|
cx: x,
|
|
@@ -1878,7 +1702,7 @@ function SelectionOverlay({
|
|
|
1878
1702
|
},
|
|
1879
1703
|
end
|
|
1880
1704
|
)),
|
|
1881
|
-
elbowMid && /* @__PURE__ */
|
|
1705
|
+
elbowMid && /* @__PURE__ */ jsx(
|
|
1882
1706
|
"rect",
|
|
1883
1707
|
{
|
|
1884
1708
|
x: elbowMid.x - 4,
|
|
@@ -1908,8 +1732,8 @@ function SelectionOverlay({
|
|
|
1908
1732
|
{ corner: "se", x: b.x + b.w + pad, y: b.y + b.h + pad }
|
|
1909
1733
|
];
|
|
1910
1734
|
const resizable = shape.type !== "text";
|
|
1911
|
-
return /* @__PURE__ */
|
|
1912
|
-
/* @__PURE__ */
|
|
1735
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
1736
|
+
/* @__PURE__ */ jsx(
|
|
1913
1737
|
"rect",
|
|
1914
1738
|
{
|
|
1915
1739
|
x: b.x - pad,
|
|
@@ -1923,7 +1747,7 @@ function SelectionOverlay({
|
|
|
1923
1747
|
pointerEvents: "none"
|
|
1924
1748
|
}
|
|
1925
1749
|
),
|
|
1926
|
-
resizable && corners.map(({ corner, x, y }) => /* @__PURE__ */
|
|
1750
|
+
resizable && corners.map(({ corner, x, y }) => /* @__PURE__ */ jsx(
|
|
1927
1751
|
"rect",
|
|
1928
1752
|
{
|
|
1929
1753
|
x: x - 4,
|
|
@@ -1949,9 +1773,9 @@ function TextEditOverlay({
|
|
|
1949
1773
|
onCommit
|
|
1950
1774
|
}) {
|
|
1951
1775
|
const original = shape[field] ?? "";
|
|
1952
|
-
const [value, setValue] =
|
|
1953
|
-
const ref =
|
|
1954
|
-
|
|
1776
|
+
const [value, setValue] = useState(original);
|
|
1777
|
+
const ref = useRef2(null);
|
|
1778
|
+
useEffect5(() => {
|
|
1955
1779
|
ref.current?.focus();
|
|
1956
1780
|
ref.current?.select();
|
|
1957
1781
|
}, []);
|
|
@@ -1999,7 +1823,7 @@ function TextEditOverlay({
|
|
|
1999
1823
|
});
|
|
2000
1824
|
}
|
|
2001
1825
|
const placeholder = field === "label" ? "Label" : field === "footer" ? "Footer" : "Text";
|
|
2002
|
-
return /* @__PURE__ */
|
|
1826
|
+
return /* @__PURE__ */ jsx(
|
|
2003
1827
|
"textarea",
|
|
2004
1828
|
{
|
|
2005
1829
|
ref,
|
|
@@ -2025,7 +1849,7 @@ function TextEditOverlay({
|
|
|
2025
1849
|
}
|
|
2026
1850
|
|
|
2027
1851
|
// src/nodes/DrawingNode.tsx
|
|
2028
|
-
import { jsx as
|
|
1852
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
2029
1853
|
var DrawingNode = class _DrawingNode extends DecoratorNode {
|
|
2030
1854
|
static getType() {
|
|
2031
1855
|
return "drawing";
|
|
@@ -2089,11 +1913,11 @@ var DrawingNode = class _DrawingNode extends DecoratorNode {
|
|
|
2089
1913
|
return false;
|
|
2090
1914
|
}
|
|
2091
1915
|
decorate(_editor, _config) {
|
|
2092
|
-
return /* @__PURE__ */
|
|
1916
|
+
return /* @__PURE__ */ jsx2(DrawingCanvas, { nodeKey: this.getKey(), data: this.getData() });
|
|
2093
1917
|
}
|
|
2094
1918
|
};
|
|
2095
1919
|
function $createDrawingNode(data = serializeDrawingData(EMPTY_DRAWING)) {
|
|
2096
|
-
return $
|
|
1920
|
+
return $applyNodeReplacement2(new DrawingNode(data));
|
|
2097
1921
|
}
|
|
2098
1922
|
function $isDrawingNode(node) {
|
|
2099
1923
|
return node instanceof DrawingNode;
|
|
@@ -2119,243 +1943,6 @@ ${node.getLatest().__data}
|
|
|
2119
1943
|
type: "multiline-element"
|
|
2120
1944
|
};
|
|
2121
1945
|
|
|
2122
|
-
// src/plugins/ToolbarPlugin.tsx
|
|
2123
|
-
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2124
|
-
var FORMAT_BUTTONS = [
|
|
2125
|
-
{
|
|
2126
|
-
format: "bold",
|
|
2127
|
-
label: "Bold",
|
|
2128
|
-
icon: /* @__PURE__ */ jsx5(
|
|
2129
|
-
"path",
|
|
2130
|
-
{
|
|
2131
|
-
d: "M6 3.5h5a3 3 0 0 1 0 6H6zm0 6h6a3 3 0 0 1 0 6H6z",
|
|
2132
|
-
strokeWidth: "1.8"
|
|
2133
|
-
}
|
|
2134
|
-
)
|
|
2135
|
-
},
|
|
2136
|
-
{
|
|
2137
|
-
format: "italic",
|
|
2138
|
-
label: "Italic",
|
|
2139
|
-
icon: /* @__PURE__ */ jsx5("path", { d: "M8.5 3.5h6M5.5 16.5h6M11.5 3.5l-3 13", strokeWidth: "1.6" })
|
|
2140
|
-
},
|
|
2141
|
-
{
|
|
2142
|
-
format: "strikethrough",
|
|
2143
|
-
label: "Strikethrough",
|
|
2144
|
-
icon: /* @__PURE__ */ jsx5(
|
|
2145
|
-
"path",
|
|
2146
|
-
{
|
|
2147
|
-
d: "M4 10h12M13.5 5.5c-.6-1.2-2-2-3.5-2-2 0-3.5 1.2-3.5 2.8 0 .5.1.9.4 1.3m-.4 5c.5 1.6 2 2.9 3.9 2.9 2 0 3.6-1.2 3.6-2.9 0-.4-.1-.8-.2-1.1",
|
|
2148
|
-
strokeWidth: "1.6"
|
|
2149
|
-
}
|
|
2150
|
-
)
|
|
2151
|
-
},
|
|
2152
|
-
{
|
|
2153
|
-
format: "code",
|
|
2154
|
-
label: "Inline code",
|
|
2155
|
-
icon: /* @__PURE__ */ jsx5("path", { d: "M7.5 6L4 10l3.5 4M12.5 6L16 10l-3.5 4", strokeWidth: "1.6" })
|
|
2156
|
-
}
|
|
2157
|
-
];
|
|
2158
|
-
function ToolbarPlugin() {
|
|
2159
|
-
const [editor] = useLexicalComposerContext8();
|
|
2160
|
-
const [activeFormats, setActiveFormats] = useState4(
|
|
2161
|
-
/* @__PURE__ */ new Set()
|
|
2162
|
-
);
|
|
2163
|
-
useEffect8(() => {
|
|
2164
|
-
return editor.registerUpdateListener(({ editorState }) => {
|
|
2165
|
-
editorState.read(() => {
|
|
2166
|
-
const selection = $getSelection3();
|
|
2167
|
-
if (!$isRangeSelection3(selection)) {
|
|
2168
|
-
setActiveFormats(/* @__PURE__ */ new Set());
|
|
2169
|
-
return;
|
|
2170
|
-
}
|
|
2171
|
-
const next = /* @__PURE__ */ new Set();
|
|
2172
|
-
for (const { format } of FORMAT_BUTTONS) {
|
|
2173
|
-
if (selection.hasFormat(format)) next.add(format);
|
|
2174
|
-
}
|
|
2175
|
-
setActiveFormats(next);
|
|
2176
|
-
});
|
|
2177
|
-
});
|
|
2178
|
-
}, [editor]);
|
|
2179
|
-
const insertTable = useCallback4(() => {
|
|
2180
|
-
editor.dispatchCommand(INSERT_TABLE_COMMAND, {
|
|
2181
|
-
columns: "3",
|
|
2182
|
-
rows: "3",
|
|
2183
|
-
includeHeaders: { rows: true, columns: false }
|
|
2184
|
-
});
|
|
2185
|
-
}, [editor]);
|
|
2186
|
-
const insertDrawing = useCallback4(() => {
|
|
2187
|
-
editor.update(() => {
|
|
2188
|
-
const drawing = $createDrawingNode();
|
|
2189
|
-
$insertNodeToNearestRoot(drawing);
|
|
2190
|
-
});
|
|
2191
|
-
}, [editor]);
|
|
2192
|
-
return /* @__PURE__ */ jsxs3("div", { className: "zui-text-editor-toolbar", children: [
|
|
2193
|
-
FORMAT_BUTTONS.map(({ format, label, icon }) => /* @__PURE__ */ jsx5(
|
|
2194
|
-
"button",
|
|
2195
|
-
{
|
|
2196
|
-
type: "button",
|
|
2197
|
-
title: label,
|
|
2198
|
-
"aria-label": label,
|
|
2199
|
-
"aria-pressed": activeFormats.has(format),
|
|
2200
|
-
className: `zui-text-editor-toolbar-button ${activeFormats.has(format) ? "is-active" : ""}`,
|
|
2201
|
-
onMouseDown: (e) => e.preventDefault(),
|
|
2202
|
-
onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, format),
|
|
2203
|
-
children: /* @__PURE__ */ jsx5(
|
|
2204
|
-
"svg",
|
|
2205
|
-
{
|
|
2206
|
-
viewBox: "0 0 20 20",
|
|
2207
|
-
width: "16",
|
|
2208
|
-
height: "16",
|
|
2209
|
-
fill: "none",
|
|
2210
|
-
stroke: "currentColor",
|
|
2211
|
-
strokeLinecap: "round",
|
|
2212
|
-
strokeLinejoin: "round",
|
|
2213
|
-
children: icon
|
|
2214
|
-
}
|
|
2215
|
-
)
|
|
2216
|
-
},
|
|
2217
|
-
format
|
|
2218
|
-
)),
|
|
2219
|
-
/* @__PURE__ */ jsx5("div", { className: "zui-text-editor-toolbar-divider" }),
|
|
2220
|
-
/* @__PURE__ */ jsx5(
|
|
2221
|
-
"button",
|
|
2222
|
-
{
|
|
2223
|
-
type: "button",
|
|
2224
|
-
title: "Insert table",
|
|
2225
|
-
"aria-label": "Insert table",
|
|
2226
|
-
className: "zui-text-editor-toolbar-button",
|
|
2227
|
-
onMouseDown: (e) => e.preventDefault(),
|
|
2228
|
-
onClick: insertTable,
|
|
2229
|
-
children: /* @__PURE__ */ jsxs3(
|
|
2230
|
-
"svg",
|
|
2231
|
-
{
|
|
2232
|
-
viewBox: "0 0 20 20",
|
|
2233
|
-
width: "16",
|
|
2234
|
-
height: "16",
|
|
2235
|
-
fill: "none",
|
|
2236
|
-
stroke: "currentColor",
|
|
2237
|
-
strokeWidth: "1.5",
|
|
2238
|
-
strokeLinecap: "round",
|
|
2239
|
-
children: [
|
|
2240
|
-
/* @__PURE__ */ jsx5("rect", { x: "3", y: "3.5", width: "14", height: "13", rx: "1.5" }),
|
|
2241
|
-
/* @__PURE__ */ jsx5("path", { d: "M3 8h14M8 8v8.5M13 8v8.5" })
|
|
2242
|
-
]
|
|
2243
|
-
}
|
|
2244
|
-
)
|
|
2245
|
-
}
|
|
2246
|
-
),
|
|
2247
|
-
/* @__PURE__ */ jsx5(
|
|
2248
|
-
"button",
|
|
2249
|
-
{
|
|
2250
|
-
type: "button",
|
|
2251
|
-
title: "Insert drawing",
|
|
2252
|
-
"aria-label": "Insert drawing",
|
|
2253
|
-
className: "zui-text-editor-toolbar-button",
|
|
2254
|
-
onMouseDown: (e) => e.preventDefault(),
|
|
2255
|
-
onClick: insertDrawing,
|
|
2256
|
-
children: /* @__PURE__ */ jsxs3(
|
|
2257
|
-
"svg",
|
|
2258
|
-
{
|
|
2259
|
-
viewBox: "0 0 20 20",
|
|
2260
|
-
width: "16",
|
|
2261
|
-
height: "16",
|
|
2262
|
-
fill: "none",
|
|
2263
|
-
stroke: "currentColor",
|
|
2264
|
-
strokeWidth: "1.5",
|
|
2265
|
-
strokeLinecap: "round",
|
|
2266
|
-
strokeLinejoin: "round",
|
|
2267
|
-
children: [
|
|
2268
|
-
/* @__PURE__ */ jsx5("rect", { x: "3", y: "5", width: "8", height: "6", rx: "1.5" }),
|
|
2269
|
-
/* @__PURE__ */ jsx5("path", { d: "M13.5 8h3M14.5 6.5L17.5 8l-3 1.5" }),
|
|
2270
|
-
/* @__PURE__ */ jsx5("ellipse", { cx: "13", cy: "14.5", rx: "4", ry: "2.8" })
|
|
2271
|
-
]
|
|
2272
|
-
}
|
|
2273
|
-
)
|
|
2274
|
-
}
|
|
2275
|
-
)
|
|
2276
|
-
] });
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
// src/nodes/FrontmatterNode.ts
|
|
2280
|
-
import {
|
|
2281
|
-
$applyNodeReplacement as $applyNodeReplacement2,
|
|
2282
|
-
$createLineBreakNode,
|
|
2283
|
-
$createTextNode,
|
|
2284
|
-
ElementNode
|
|
2285
|
-
} from "lexical";
|
|
2286
|
-
var FrontmatterNode = class _FrontmatterNode extends ElementNode {
|
|
2287
|
-
static getType() {
|
|
2288
|
-
return "frontmatter";
|
|
2289
|
-
}
|
|
2290
|
-
static clone(node) {
|
|
2291
|
-
return new _FrontmatterNode(node.__key);
|
|
2292
|
-
}
|
|
2293
|
-
constructor(key) {
|
|
2294
|
-
super(key);
|
|
2295
|
-
}
|
|
2296
|
-
createDOM(config) {
|
|
2297
|
-
const dom = document.createElement("div");
|
|
2298
|
-
const className = config.theme.frontmatter;
|
|
2299
|
-
dom.className = typeof className === "string" ? className : "zui-frontmatter";
|
|
2300
|
-
return dom;
|
|
2301
|
-
}
|
|
2302
|
-
updateDOM() {
|
|
2303
|
-
return false;
|
|
2304
|
-
}
|
|
2305
|
-
static importJSON(_serializedNode) {
|
|
2306
|
-
return $createFrontmatterNode();
|
|
2307
|
-
}
|
|
2308
|
-
exportJSON() {
|
|
2309
|
-
return {
|
|
2310
|
-
...super.exportJSON(),
|
|
2311
|
-
type: "frontmatter",
|
|
2312
|
-
version: 1
|
|
2313
|
-
};
|
|
2314
|
-
}
|
|
2315
|
-
isInline() {
|
|
2316
|
-
return false;
|
|
2317
|
-
}
|
|
2318
|
-
canBeEmpty() {
|
|
2319
|
-
return false;
|
|
2320
|
-
}
|
|
2321
|
-
canIndent() {
|
|
2322
|
-
return false;
|
|
2323
|
-
}
|
|
2324
|
-
};
|
|
2325
|
-
function $createFrontmatterNode() {
|
|
2326
|
-
return $applyNodeReplacement2(new FrontmatterNode());
|
|
2327
|
-
}
|
|
2328
|
-
function $isFrontmatterNode(node) {
|
|
2329
|
-
return node instanceof FrontmatterNode;
|
|
2330
|
-
}
|
|
2331
|
-
var FRONTMATTER_DELIMITER = /^---\s*$/;
|
|
2332
|
-
var FRONTMATTER = {
|
|
2333
|
-
dependencies: [FrontmatterNode],
|
|
2334
|
-
export: (node) => {
|
|
2335
|
-
if (!$isFrontmatterNode(node)) return null;
|
|
2336
|
-
const body = node.getTextContent();
|
|
2337
|
-
return body ? `---
|
|
2338
|
-
${body}
|
|
2339
|
-
---` : "---\n---";
|
|
2340
|
-
},
|
|
2341
|
-
regExpStart: FRONTMATTER_DELIMITER,
|
|
2342
|
-
regExpEnd: FRONTMATTER_DELIMITER,
|
|
2343
|
-
replace: (rootNode, children, _startMatch, _endMatch, linesInBetween, isImport) => {
|
|
2344
|
-
if (!isImport || children != null || linesInBetween == null) return false;
|
|
2345
|
-
if (rootNode.getChildrenSize() !== 0) return false;
|
|
2346
|
-
const lines = [...linesInBetween];
|
|
2347
|
-
while (lines.length > 0 && lines[0].length === 0) lines.shift();
|
|
2348
|
-
while (lines.length > 0 && lines[lines.length - 1].length === 0) lines.pop();
|
|
2349
|
-
const node = $createFrontmatterNode();
|
|
2350
|
-
lines.forEach((line, index) => {
|
|
2351
|
-
if (index > 0) node.append($createLineBreakNode());
|
|
2352
|
-
node.append($createTextNode(line));
|
|
2353
|
-
});
|
|
2354
|
-
rootNode.append(node);
|
|
2355
|
-
},
|
|
2356
|
-
type: "multiline-element"
|
|
2357
|
-
};
|
|
2358
|
-
|
|
2359
1946
|
// src/transformers/tableTransformer.ts
|
|
2360
1947
|
import {
|
|
2361
1948
|
$isParagraphNode as $isParagraphNode2,
|
|
@@ -2551,10 +2138,20 @@ var editorTheme = {
|
|
|
2551
2138
|
drawing: "zui-drawing"
|
|
2552
2139
|
};
|
|
2553
2140
|
|
|
2554
|
-
// src/
|
|
2555
|
-
import { jsx as
|
|
2141
|
+
// src/EditorRoot.tsx
|
|
2142
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2556
2143
|
var SYNC_TRANSFORMERS = [FRONTMATTER, DRAWING, CHECK_LIST, TABLE, ...TRANSFORMERS2];
|
|
2557
2144
|
var SHORTCUT_TRANSFORMERS = [TABLE, ...TRANSFORMERS2.filter((t) => t !== CODE)];
|
|
2145
|
+
var EditorContext = createContext(null);
|
|
2146
|
+
function useEditorContext() {
|
|
2147
|
+
const ctx = useContext(EditorContext);
|
|
2148
|
+
if (!ctx) {
|
|
2149
|
+
throw new Error(
|
|
2150
|
+
"@zuilib/text-editor: this component must be rendered inside <MarkdownEditor> or <MarkdownEditor.Root>"
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
return ctx;
|
|
2154
|
+
}
|
|
2558
2155
|
function onError(error) {
|
|
2559
2156
|
console.error(error);
|
|
2560
2157
|
}
|
|
@@ -2574,49 +2171,38 @@ var editorNodes = [
|
|
|
2574
2171
|
DrawingNode
|
|
2575
2172
|
];
|
|
2576
2173
|
function ReadOnlyPlugin({ readOnly }) {
|
|
2577
|
-
const [editor] =
|
|
2578
|
-
|
|
2174
|
+
const [editor] = useLexicalComposerContext6();
|
|
2175
|
+
useEffect6(() => {
|
|
2579
2176
|
editor.setEditable(!readOnly);
|
|
2580
2177
|
}, [editor, readOnly]);
|
|
2581
2178
|
return null;
|
|
2582
2179
|
}
|
|
2583
|
-
function
|
|
2180
|
+
function EditorRoot({
|
|
2584
2181
|
value,
|
|
2585
2182
|
onChange,
|
|
2586
|
-
placeholder = "Start writing...",
|
|
2587
2183
|
readOnly = false,
|
|
2588
2184
|
className,
|
|
2589
2185
|
mode = "edit-md",
|
|
2590
2186
|
autoFocus = false,
|
|
2591
|
-
|
|
2592
|
-
outline = false,
|
|
2593
|
-
foldable = true
|
|
2187
|
+
children
|
|
2594
2188
|
}) {
|
|
2595
|
-
const latestValueRef =
|
|
2596
|
-
const [mountKey, setMountKey] =
|
|
2597
|
-
const [capturedMarkdown, setCapturedMarkdown] =
|
|
2598
|
-
|
|
2189
|
+
const latestValueRef = useRef3(value ?? "");
|
|
2190
|
+
const [mountKey, setMountKey] = useState2(0);
|
|
2191
|
+
const [capturedMarkdown, setCapturedMarkdown] = useState2(value ?? "");
|
|
2192
|
+
useEffect6(() => {
|
|
2599
2193
|
if (value !== void 0) {
|
|
2600
2194
|
latestValueRef.current = value;
|
|
2601
2195
|
}
|
|
2602
2196
|
}, [value]);
|
|
2603
|
-
const
|
|
2604
|
-
(e) => {
|
|
2605
|
-
const newValue = e.target.value;
|
|
2606
|
-
latestValueRef.current = newValue;
|
|
2607
|
-
onChange?.(newValue);
|
|
2608
|
-
},
|
|
2609
|
-
[onChange]
|
|
2610
|
-
);
|
|
2611
|
-
const handleLexicalChange = useCallback5(
|
|
2197
|
+
const handleChange = useCallback2(
|
|
2612
2198
|
(newValue) => {
|
|
2613
2199
|
latestValueRef.current = newValue;
|
|
2614
2200
|
onChange?.(newValue);
|
|
2615
2201
|
},
|
|
2616
2202
|
[onChange]
|
|
2617
2203
|
);
|
|
2618
|
-
const prevModeRef =
|
|
2619
|
-
|
|
2204
|
+
const prevModeRef = useRef3(mode);
|
|
2205
|
+
useEffect6(() => {
|
|
2620
2206
|
if (prevModeRef.current === "edit-raw" && mode !== "edit-raw") {
|
|
2621
2207
|
setCapturedMarkdown(latestValueRef.current);
|
|
2622
2208
|
setMountKey((k) => k + 1);
|
|
@@ -2632,72 +2218,734 @@ function MarkdownEditor({
|
|
|
2632
2218
|
}),
|
|
2633
2219
|
[]
|
|
2634
2220
|
);
|
|
2221
|
+
const context = useMemo(
|
|
2222
|
+
() => ({
|
|
2223
|
+
mode,
|
|
2224
|
+
readOnly,
|
|
2225
|
+
autoFocus,
|
|
2226
|
+
rawValue: value ?? "",
|
|
2227
|
+
onRawChange: handleChange
|
|
2228
|
+
}),
|
|
2229
|
+
[mode, readOnly, autoFocus, value, handleChange]
|
|
2230
|
+
);
|
|
2231
|
+
const isRaw = mode === "edit-raw";
|
|
2232
|
+
return /* @__PURE__ */ jsx3(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsx3(EditorContext.Provider, { value: context, children: /* @__PURE__ */ jsxs2("div", { className: `zui-text-editor ${className ?? ""}`, children: [
|
|
2233
|
+
children,
|
|
2234
|
+
!isRaw && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2235
|
+
/* @__PURE__ */ jsx3(
|
|
2236
|
+
MarkdownSyncPlugin,
|
|
2237
|
+
{
|
|
2238
|
+
initialMarkdown: mode === "view" ? value ?? "" : capturedMarkdown,
|
|
2239
|
+
onChange: handleChange,
|
|
2240
|
+
transformers: SYNC_TRANSFORMERS
|
|
2241
|
+
}
|
|
2242
|
+
),
|
|
2243
|
+
/* @__PURE__ */ jsx3(HistoryPlugin, {}),
|
|
2244
|
+
/* @__PURE__ */ jsx3(ListPlugin, {}),
|
|
2245
|
+
/* @__PURE__ */ jsx3(CheckListPlugin, {}),
|
|
2246
|
+
/* @__PURE__ */ jsx3(ChecklistShortcutPlugin, {}),
|
|
2247
|
+
/* @__PURE__ */ jsx3(CodeBlockShortcutPlugin, {}),
|
|
2248
|
+
/* @__PURE__ */ jsx3(CodeHighlightPlugin, {}),
|
|
2249
|
+
/* @__PURE__ */ jsx3(TablePlugin, {}),
|
|
2250
|
+
/* @__PURE__ */ jsx3(LinkPlugin, {}),
|
|
2251
|
+
/* @__PURE__ */ jsx3(MarkdownShortcutPlugin, { transformers: SHORTCUT_TRANSFORMERS }),
|
|
2252
|
+
/* @__PURE__ */ jsx3(ReadOnlyPlugin, { readOnly: readOnly || mode === "view" }),
|
|
2253
|
+
autoFocus && /* @__PURE__ */ jsx3(AutoFocusPlugin, {})
|
|
2254
|
+
] })
|
|
2255
|
+
] }) }) }, mountKey);
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
// src/EditorContent.tsx
|
|
2259
|
+
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
|
|
2260
|
+
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
|
|
2261
|
+
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
|
|
2262
|
+
|
|
2263
|
+
// src/plugins/FoldingPlugin.tsx
|
|
2264
|
+
import {
|
|
2265
|
+
useCallback as useCallback3,
|
|
2266
|
+
useEffect as useEffect7,
|
|
2267
|
+
useRef as useRef4,
|
|
2268
|
+
useState as useState3
|
|
2269
|
+
} from "react";
|
|
2270
|
+
import { $getRoot, $getSelection as $getSelection2, $isRangeSelection as $isRangeSelection2 } from "lexical";
|
|
2271
|
+
import { useLexicalComposerContext as useLexicalComposerContext7 } from "@lexical/react/LexicalComposerContext";
|
|
2272
|
+
import { $isHeadingNode } from "@lexical/rich-text";
|
|
2273
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
2274
|
+
var HEADING_LEVELS = {
|
|
2275
|
+
h1: 1,
|
|
2276
|
+
h2: 2,
|
|
2277
|
+
h3: 3,
|
|
2278
|
+
h4: 4,
|
|
2279
|
+
h5: 5,
|
|
2280
|
+
h6: 6
|
|
2281
|
+
};
|
|
2282
|
+
function FoldingPlugin() {
|
|
2283
|
+
const [editor] = useLexicalComposerContext7();
|
|
2284
|
+
const [foldedKeys, setFoldedKeys] = useState3(/* @__PURE__ */ new Set());
|
|
2285
|
+
const [buttons, setButtons] = useState3([]);
|
|
2286
|
+
const foldedRef = useRef4(foldedKeys);
|
|
2287
|
+
foldedRef.current = foldedKeys;
|
|
2288
|
+
const sync = useCallback3(() => {
|
|
2289
|
+
editor.getEditorState().read(() => {
|
|
2290
|
+
const children = $getRoot().getChildren();
|
|
2291
|
+
const folded = foldedRef.current;
|
|
2292
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
2293
|
+
const headings = [];
|
|
2294
|
+
for (let i = 0; i < children.length; i++) {
|
|
2295
|
+
const node = children[i];
|
|
2296
|
+
if (!$isHeadingNode(node)) continue;
|
|
2297
|
+
const level = HEADING_LEVELS[node.getTag()] ?? 6;
|
|
2298
|
+
const isFolded = folded.has(node.getKey());
|
|
2299
|
+
headings.push({ key: node.getKey(), folded: isFolded });
|
|
2300
|
+
if (!isFolded) continue;
|
|
2301
|
+
for (let j = i + 1; j < children.length; j++) {
|
|
2302
|
+
const sibling = children[j];
|
|
2303
|
+
if ($isHeadingNode(sibling) && (HEADING_LEVELS[sibling.getTag()] ?? 6) <= level) {
|
|
2304
|
+
break;
|
|
2305
|
+
}
|
|
2306
|
+
hidden.add(sibling.getKey());
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
const selection = $getSelection2();
|
|
2310
|
+
if ($isRangeSelection2(selection)) {
|
|
2311
|
+
const topLevel = selection.anchor.getNode().getTopLevelElement();
|
|
2312
|
+
if (topLevel && hidden.has(topLevel.getKey())) {
|
|
2313
|
+
let node = topLevel.getPreviousSibling();
|
|
2314
|
+
while (node) {
|
|
2315
|
+
if ($isHeadingNode(node) && folded.has(node.getKey())) {
|
|
2316
|
+
const unfoldKey = node.getKey();
|
|
2317
|
+
setFoldedKeys((prev) => {
|
|
2318
|
+
const next = new Set(prev);
|
|
2319
|
+
next.delete(unfoldKey);
|
|
2320
|
+
return next;
|
|
2321
|
+
});
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
node = node.getPreviousSibling();
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
for (const child of children) {
|
|
2329
|
+
const element = editor.getElementByKey(child.getKey());
|
|
2330
|
+
if (!element) continue;
|
|
2331
|
+
const shouldHide = hidden.has(child.getKey());
|
|
2332
|
+
if (shouldHide) {
|
|
2333
|
+
element.style.display = "none";
|
|
2334
|
+
} else if (element.style.display === "none") {
|
|
2335
|
+
element.style.display = "";
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
setButtons(
|
|
2339
|
+
headings.map(({ key, folded: isFolded }) => {
|
|
2340
|
+
const element = editor.getElementByKey(key);
|
|
2341
|
+
element?.classList.toggle("zui-heading-folded", isFolded);
|
|
2342
|
+
return {
|
|
2343
|
+
key,
|
|
2344
|
+
folded: isFolded,
|
|
2345
|
+
top: element?.offsetTop ?? 0,
|
|
2346
|
+
height: element?.offsetHeight ?? 0
|
|
2347
|
+
};
|
|
2348
|
+
})
|
|
2349
|
+
);
|
|
2350
|
+
});
|
|
2351
|
+
}, [editor]);
|
|
2352
|
+
useEffect7(() => {
|
|
2353
|
+
sync();
|
|
2354
|
+
const unregister = editor.registerUpdateListener(sync);
|
|
2355
|
+
const rootElement = editor.getRootElement();
|
|
2356
|
+
const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(sync) : null;
|
|
2357
|
+
if (rootElement && observer) observer.observe(rootElement);
|
|
2358
|
+
return () => {
|
|
2359
|
+
unregister();
|
|
2360
|
+
observer?.disconnect();
|
|
2361
|
+
};
|
|
2362
|
+
}, [editor, sync, foldedKeys]);
|
|
2363
|
+
const toggle = useCallback3((key) => {
|
|
2364
|
+
setFoldedKeys((prev) => {
|
|
2365
|
+
const next = new Set(prev);
|
|
2366
|
+
if (next.has(key)) next.delete(key);
|
|
2367
|
+
else next.add(key);
|
|
2368
|
+
return next;
|
|
2369
|
+
});
|
|
2370
|
+
}, []);
|
|
2371
|
+
return /* @__PURE__ */ jsx4("div", { className: "zui-text-editor-fold-gutter", children: buttons.map(({ key, folded, top, height }) => /* @__PURE__ */ jsx4(
|
|
2372
|
+
"button",
|
|
2373
|
+
{
|
|
2374
|
+
type: "button",
|
|
2375
|
+
className: `zui-text-editor-fold ${folded ? "is-folded" : ""}`,
|
|
2376
|
+
style: { top: top + Math.max(0, (Math.min(height, 44) - 18) / 2) },
|
|
2377
|
+
title: folded ? "Expand section" : "Collapse section",
|
|
2378
|
+
"aria-label": folded ? "Expand section" : "Collapse section",
|
|
2379
|
+
"aria-expanded": !folded,
|
|
2380
|
+
onClick: () => toggle(key),
|
|
2381
|
+
children: /* @__PURE__ */ jsx4(
|
|
2382
|
+
"svg",
|
|
2383
|
+
{
|
|
2384
|
+
viewBox: "0 0 20 20",
|
|
2385
|
+
width: "12",
|
|
2386
|
+
height: "12",
|
|
2387
|
+
fill: "none",
|
|
2388
|
+
stroke: "currentColor",
|
|
2389
|
+
strokeWidth: "2",
|
|
2390
|
+
strokeLinecap: "round",
|
|
2391
|
+
strokeLinejoin: "round",
|
|
2392
|
+
children: /* @__PURE__ */ jsx4("path", { d: "M6 8l4 4 4-4" })
|
|
2393
|
+
}
|
|
2394
|
+
)
|
|
2395
|
+
},
|
|
2396
|
+
key
|
|
2397
|
+
)) });
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// src/EditorContent.tsx
|
|
2401
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2402
|
+
function EditorContent({
|
|
2403
|
+
placeholder = "Start writing...",
|
|
2404
|
+
foldable = true,
|
|
2405
|
+
children
|
|
2406
|
+
}) {
|
|
2407
|
+
const { mode, readOnly, autoFocus, rawValue, onRawChange } = useEditorContext();
|
|
2635
2408
|
if (mode === "edit-raw") {
|
|
2636
|
-
return /* @__PURE__ */
|
|
2409
|
+
return /* @__PURE__ */ jsx5(
|
|
2637
2410
|
"textarea",
|
|
2638
2411
|
{
|
|
2639
2412
|
className: "zui-text-editor-textarea",
|
|
2640
|
-
value:
|
|
2641
|
-
onChange:
|
|
2413
|
+
value: rawValue,
|
|
2414
|
+
onChange: (e) => onRawChange(e.target.value),
|
|
2642
2415
|
placeholder,
|
|
2643
2416
|
readOnly,
|
|
2644
2417
|
autoFocus,
|
|
2645
2418
|
spellCheck: false
|
|
2646
2419
|
}
|
|
2647
|
-
)
|
|
2420
|
+
);
|
|
2648
2421
|
}
|
|
2649
|
-
return /* @__PURE__ */
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
),
|
|
2667
|
-
foldable && /* @__PURE__ */ jsx6(FoldingPlugin, {})
|
|
2668
|
-
] }),
|
|
2669
|
-
outline && /* @__PURE__ */ jsx6(OutlinePlugin, {})
|
|
2422
|
+
return /* @__PURE__ */ jsxs3("div", { className: "zui-text-editor-body", children: [
|
|
2423
|
+
/* @__PURE__ */ jsxs3("div", { className: "zui-text-editor-main", children: [
|
|
2424
|
+
/* @__PURE__ */ jsx5(
|
|
2425
|
+
RichTextPlugin,
|
|
2426
|
+
{
|
|
2427
|
+
contentEditable: /* @__PURE__ */ jsx5(
|
|
2428
|
+
ContentEditable,
|
|
2429
|
+
{
|
|
2430
|
+
className: "zui-text-editor-content",
|
|
2431
|
+
"aria-placeholder": placeholder,
|
|
2432
|
+
placeholder: /* @__PURE__ */ jsx5("div", { className: "zui-text-editor-placeholder", children: placeholder })
|
|
2433
|
+
}
|
|
2434
|
+
),
|
|
2435
|
+
ErrorBoundary: LexicalErrorBoundary
|
|
2436
|
+
}
|
|
2437
|
+
),
|
|
2438
|
+
foldable && /* @__PURE__ */ jsx5(FoldingPlugin, {})
|
|
2670
2439
|
] }),
|
|
2671
|
-
|
|
2672
|
-
|
|
2440
|
+
children
|
|
2441
|
+
] });
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
// src/plugins/OutlinePlugin.tsx
|
|
2445
|
+
import { useCallback as useCallback4, useEffect as useEffect8, useState as useState4 } from "react";
|
|
2446
|
+
import { useLexicalComposerContext as useLexicalComposerContext8 } from "@lexical/react/LexicalComposerContext";
|
|
2447
|
+
import {
|
|
2448
|
+
TableOfContentsPlugin
|
|
2449
|
+
} from "@lexical/react/LexicalTableOfContentsPlugin";
|
|
2450
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2451
|
+
var INDENT_PER_LEVEL = {
|
|
2452
|
+
h1: 0,
|
|
2453
|
+
h2: 1,
|
|
2454
|
+
h3: 2,
|
|
2455
|
+
h4: 3,
|
|
2456
|
+
h5: 4,
|
|
2457
|
+
h6: 5
|
|
2458
|
+
};
|
|
2459
|
+
function OutlinePlugin() {
|
|
2460
|
+
const [editor] = useLexicalComposerContext8();
|
|
2461
|
+
const [collapsed, setCollapsed] = useState4(false);
|
|
2462
|
+
const [activeKey, setActiveKey] = useState4(null);
|
|
2463
|
+
useEffect8(() => {
|
|
2464
|
+
const rootElement = editor.getRootElement();
|
|
2465
|
+
const scroller = rootElement?.closest(".zui-text-editor");
|
|
2466
|
+
if (!rootElement || !(scroller instanceof HTMLElement)) return;
|
|
2467
|
+
let frame = 0;
|
|
2468
|
+
const updateActive = () => {
|
|
2469
|
+
cancelAnimationFrame(frame);
|
|
2470
|
+
frame = requestAnimationFrame(() => {
|
|
2471
|
+
const headings = rootElement.querySelectorAll(
|
|
2472
|
+
"h1, h2, h3, h4, h5, h6"
|
|
2473
|
+
);
|
|
2474
|
+
const threshold = scroller.scrollTop + 80;
|
|
2475
|
+
let current = null;
|
|
2476
|
+
for (const el of headings) {
|
|
2477
|
+
if (el.offsetTop <= threshold) {
|
|
2478
|
+
current = el.getAttribute("data-outline-key");
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
setActiveKey(current);
|
|
2482
|
+
});
|
|
2483
|
+
};
|
|
2484
|
+
updateActive();
|
|
2485
|
+
scroller.addEventListener("scroll", updateActive, { passive: true });
|
|
2486
|
+
const unregister = editor.registerUpdateListener(updateActive);
|
|
2487
|
+
return () => {
|
|
2488
|
+
cancelAnimationFrame(frame);
|
|
2489
|
+
scroller.removeEventListener("scroll", updateActive);
|
|
2490
|
+
unregister();
|
|
2491
|
+
};
|
|
2492
|
+
}, [editor]);
|
|
2493
|
+
const scrollTo = useCallback4(
|
|
2494
|
+
(key) => {
|
|
2495
|
+
editor.getEditorState().read(() => {
|
|
2496
|
+
const element = editor.getElementByKey(key);
|
|
2497
|
+
element?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
2498
|
+
});
|
|
2499
|
+
},
|
|
2500
|
+
[editor]
|
|
2501
|
+
);
|
|
2502
|
+
return /* @__PURE__ */ jsx6(TableOfContentsPlugin, { children: (entries) => {
|
|
2503
|
+
for (const [key] of entries) {
|
|
2504
|
+
editor.getElementByKey(key)?.setAttribute("data-outline-key", key);
|
|
2505
|
+
}
|
|
2506
|
+
return /* @__PURE__ */ jsxs4(
|
|
2507
|
+
"div",
|
|
2673
2508
|
{
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2509
|
+
className: `zui-text-editor-outline ${collapsed ? "is-collapsed" : ""}`,
|
|
2510
|
+
children: [
|
|
2511
|
+
/* @__PURE__ */ jsx6(
|
|
2512
|
+
"button",
|
|
2513
|
+
{
|
|
2514
|
+
type: "button",
|
|
2515
|
+
className: "zui-text-editor-outline-toggle",
|
|
2516
|
+
title: collapsed ? "Show outline" : "Hide outline",
|
|
2517
|
+
"aria-label": collapsed ? "Show outline" : "Hide outline",
|
|
2518
|
+
"aria-expanded": !collapsed,
|
|
2519
|
+
onClick: () => setCollapsed((c) => !c),
|
|
2520
|
+
children: /* @__PURE__ */ jsx6(
|
|
2521
|
+
"svg",
|
|
2522
|
+
{
|
|
2523
|
+
viewBox: "0 0 20 20",
|
|
2524
|
+
width: "14",
|
|
2525
|
+
height: "14",
|
|
2526
|
+
fill: "none",
|
|
2527
|
+
stroke: "currentColor",
|
|
2528
|
+
strokeWidth: "1.8",
|
|
2529
|
+
strokeLinecap: "round",
|
|
2530
|
+
children: /* @__PURE__ */ jsx6("path", { d: "M4 5h12M4 10h8M4 15h10" })
|
|
2531
|
+
}
|
|
2532
|
+
)
|
|
2533
|
+
}
|
|
2534
|
+
),
|
|
2535
|
+
!collapsed && /* @__PURE__ */ jsxs4("nav", { className: "zui-text-editor-outline-list", "aria-label": "Table of contents", children: [
|
|
2536
|
+
entries.length === 0 && /* @__PURE__ */ jsx6("div", { className: "zui-text-editor-outline-empty", children: "No headings" }),
|
|
2537
|
+
entries.map(([key, text, tag]) => /* @__PURE__ */ jsx6(
|
|
2538
|
+
"button",
|
|
2539
|
+
{
|
|
2540
|
+
type: "button",
|
|
2541
|
+
className: `zui-text-editor-outline-item ${key === activeKey ? "is-active" : ""}`,
|
|
2542
|
+
style: {
|
|
2543
|
+
paddingLeft: `${0.5 + INDENT_PER_LEVEL[tag] * 0.75}rem`
|
|
2544
|
+
},
|
|
2545
|
+
onClick: () => scrollTo(key),
|
|
2546
|
+
children: text || "Untitled"
|
|
2547
|
+
},
|
|
2548
|
+
key
|
|
2549
|
+
))
|
|
2550
|
+
] })
|
|
2551
|
+
]
|
|
2677
2552
|
}
|
|
2553
|
+
);
|
|
2554
|
+
} });
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// src/useMarkdownEditor.ts
|
|
2558
|
+
import { useCallback as useCallback5, useEffect as useEffect9, useState as useState5 } from "react";
|
|
2559
|
+
import {
|
|
2560
|
+
$getSelection as $getSelection3,
|
|
2561
|
+
$isRangeSelection as $isRangeSelection3,
|
|
2562
|
+
CAN_REDO_COMMAND,
|
|
2563
|
+
CAN_UNDO_COMMAND,
|
|
2564
|
+
COMMAND_PRIORITY_LOW as COMMAND_PRIORITY_LOW2,
|
|
2565
|
+
FORMAT_TEXT_COMMAND,
|
|
2566
|
+
REDO_COMMAND,
|
|
2567
|
+
UNDO_COMMAND
|
|
2568
|
+
} from "lexical";
|
|
2569
|
+
import { useLexicalComposerContext as useLexicalComposerContext9 } from "@lexical/react/LexicalComposerContext";
|
|
2570
|
+
import { INSERT_TABLE_COMMAND } from "@lexical/table";
|
|
2571
|
+
import { $insertNodeToNearestRoot, mergeRegister } from "@lexical/utils";
|
|
2572
|
+
var TRACKED_FORMATS = [
|
|
2573
|
+
"bold",
|
|
2574
|
+
"italic",
|
|
2575
|
+
"underline",
|
|
2576
|
+
"strikethrough",
|
|
2577
|
+
"code"
|
|
2578
|
+
];
|
|
2579
|
+
function useMarkdownEditor() {
|
|
2580
|
+
const [editor] = useLexicalComposerContext9();
|
|
2581
|
+
const [activeFormats, setActiveFormats] = useState5(
|
|
2582
|
+
/* @__PURE__ */ new Set()
|
|
2583
|
+
);
|
|
2584
|
+
const [canUndo, setCanUndo] = useState5(false);
|
|
2585
|
+
const [canRedo, setCanRedo] = useState5(false);
|
|
2586
|
+
useEffect9(
|
|
2587
|
+
() => mergeRegister(
|
|
2588
|
+
editor.registerUpdateListener(({ editorState }) => {
|
|
2589
|
+
editorState.read(() => {
|
|
2590
|
+
const selection = $getSelection3();
|
|
2591
|
+
if (!$isRangeSelection3(selection)) {
|
|
2592
|
+
setActiveFormats(/* @__PURE__ */ new Set());
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
const next = /* @__PURE__ */ new Set();
|
|
2596
|
+
for (const format of TRACKED_FORMATS) {
|
|
2597
|
+
if (selection.hasFormat(format)) next.add(format);
|
|
2598
|
+
}
|
|
2599
|
+
setActiveFormats(next);
|
|
2600
|
+
});
|
|
2601
|
+
}),
|
|
2602
|
+
editor.registerCommand(
|
|
2603
|
+
CAN_UNDO_COMMAND,
|
|
2604
|
+
(payload) => {
|
|
2605
|
+
setCanUndo(payload);
|
|
2606
|
+
return false;
|
|
2607
|
+
},
|
|
2608
|
+
COMMAND_PRIORITY_LOW2
|
|
2609
|
+
),
|
|
2610
|
+
editor.registerCommand(
|
|
2611
|
+
CAN_REDO_COMMAND,
|
|
2612
|
+
(payload) => {
|
|
2613
|
+
setCanRedo(payload);
|
|
2614
|
+
return false;
|
|
2615
|
+
},
|
|
2616
|
+
COMMAND_PRIORITY_LOW2
|
|
2617
|
+
)
|
|
2678
2618
|
),
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2619
|
+
[editor]
|
|
2620
|
+
);
|
|
2621
|
+
const toggleFormat = useCallback5(
|
|
2622
|
+
(format) => {
|
|
2623
|
+
editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
|
|
2624
|
+
},
|
|
2625
|
+
[editor]
|
|
2626
|
+
);
|
|
2627
|
+
const insertTable = useCallback5(
|
|
2628
|
+
({ rows = 3, columns = 3 } = {}) => {
|
|
2629
|
+
editor.dispatchCommand(INSERT_TABLE_COMMAND, {
|
|
2630
|
+
columns: String(columns),
|
|
2631
|
+
rows: String(rows),
|
|
2632
|
+
includeHeaders: { rows: true, columns: false }
|
|
2633
|
+
});
|
|
2634
|
+
},
|
|
2635
|
+
[editor]
|
|
2636
|
+
);
|
|
2637
|
+
const insertDrawing = useCallback5(() => {
|
|
2638
|
+
editor.update(() => {
|
|
2639
|
+
$insertNodeToNearestRoot($createDrawingNode());
|
|
2640
|
+
});
|
|
2641
|
+
}, [editor]);
|
|
2642
|
+
const undo = useCallback5(() => {
|
|
2643
|
+
editor.dispatchCommand(UNDO_COMMAND, void 0);
|
|
2644
|
+
}, [editor]);
|
|
2645
|
+
const redo = useCallback5(() => {
|
|
2646
|
+
editor.dispatchCommand(REDO_COMMAND, void 0);
|
|
2647
|
+
}, [editor]);
|
|
2648
|
+
return {
|
|
2649
|
+
editor,
|
|
2650
|
+
activeFormats,
|
|
2651
|
+
toggleFormat,
|
|
2652
|
+
insertTable,
|
|
2653
|
+
insertDrawing,
|
|
2654
|
+
canUndo,
|
|
2655
|
+
canRedo,
|
|
2656
|
+
undo,
|
|
2657
|
+
redo
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
// src/components/Toolbar.tsx
|
|
2662
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2663
|
+
function Toolbar({ children, className }) {
|
|
2664
|
+
return /* @__PURE__ */ jsx7(
|
|
2665
|
+
"div",
|
|
2666
|
+
{
|
|
2667
|
+
className: `zui-text-editor-toolbar ${className ?? ""}`,
|
|
2668
|
+
role: "toolbar",
|
|
2669
|
+
children: children ?? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2670
|
+
/* @__PURE__ */ jsx7(FormatButtons, {}),
|
|
2671
|
+
/* @__PURE__ */ jsx7(ToolbarDivider, {}),
|
|
2672
|
+
/* @__PURE__ */ jsx7(InsertButtons, {})
|
|
2673
|
+
] })
|
|
2674
|
+
}
|
|
2675
|
+
);
|
|
2676
|
+
}
|
|
2677
|
+
function ToolbarDivider() {
|
|
2678
|
+
return /* @__PURE__ */ jsx7("div", { className: "zui-text-editor-toolbar-divider" });
|
|
2679
|
+
}
|
|
2680
|
+
function ToolbarButton({
|
|
2681
|
+
label,
|
|
2682
|
+
onClick,
|
|
2683
|
+
children,
|
|
2684
|
+
active = false,
|
|
2685
|
+
disabled = false,
|
|
2686
|
+
className
|
|
2687
|
+
}) {
|
|
2688
|
+
return /* @__PURE__ */ jsx7(
|
|
2689
|
+
"button",
|
|
2690
|
+
{
|
|
2691
|
+
type: "button",
|
|
2692
|
+
title: label,
|
|
2693
|
+
"aria-label": label,
|
|
2694
|
+
"aria-pressed": active,
|
|
2695
|
+
disabled,
|
|
2696
|
+
className: `zui-text-editor-toolbar-button ${active ? "is-active" : ""} ${className ?? ""}`,
|
|
2697
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
2698
|
+
onClick,
|
|
2699
|
+
children
|
|
2700
|
+
}
|
|
2701
|
+
);
|
|
2702
|
+
}
|
|
2703
|
+
function Icon({ children, strokeWidth = 1.5 }) {
|
|
2704
|
+
return /* @__PURE__ */ jsx7(
|
|
2705
|
+
"svg",
|
|
2706
|
+
{
|
|
2707
|
+
viewBox: "0 0 20 20",
|
|
2708
|
+
width: "16",
|
|
2709
|
+
height: "16",
|
|
2710
|
+
fill: "none",
|
|
2711
|
+
stroke: "currentColor",
|
|
2712
|
+
strokeWidth,
|
|
2713
|
+
strokeLinecap: "round",
|
|
2714
|
+
strokeLinejoin: "round",
|
|
2715
|
+
children
|
|
2716
|
+
}
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
var FORMAT_BUTTONS = [
|
|
2720
|
+
{
|
|
2721
|
+
format: "bold",
|
|
2722
|
+
label: "Bold",
|
|
2723
|
+
icon: /* @__PURE__ */ jsx7(Icon, { strokeWidth: 1.8, children: /* @__PURE__ */ jsx7("path", { d: "M6 3.5h5a3 3 0 0 1 0 6H6zm0 6h6a3 3 0 0 1 0 6H6z" }) })
|
|
2724
|
+
},
|
|
2725
|
+
{
|
|
2726
|
+
format: "italic",
|
|
2727
|
+
label: "Italic",
|
|
2728
|
+
icon: /* @__PURE__ */ jsx7(Icon, { strokeWidth: 1.6, children: /* @__PURE__ */ jsx7("path", { d: "M8.5 3.5h6M5.5 16.5h6M11.5 3.5l-3 13" }) })
|
|
2729
|
+
},
|
|
2730
|
+
{
|
|
2731
|
+
format: "strikethrough",
|
|
2732
|
+
label: "Strikethrough",
|
|
2733
|
+
icon: /* @__PURE__ */ jsx7(Icon, { strokeWidth: 1.6, children: /* @__PURE__ */ jsx7("path", { d: "M4 10h12M13.5 5.5c-.6-1.2-2-2-3.5-2-2 0-3.5 1.2-3.5 2.8 0 .5.1.9.4 1.3m-.4 5c.5 1.6 2 2.9 3.9 2.9 2 0 3.6-1.2 3.6-2.9 0-.4-.1-.8-.2-1.1" }) })
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
format: "code",
|
|
2737
|
+
label: "Inline code",
|
|
2738
|
+
icon: /* @__PURE__ */ jsx7(Icon, { strokeWidth: 1.6, children: /* @__PURE__ */ jsx7("path", { d: "M7.5 6L4 10l3.5 4M12.5 6L16 10l-3.5 4" }) })
|
|
2739
|
+
}
|
|
2740
|
+
];
|
|
2741
|
+
function FormatButtons() {
|
|
2742
|
+
const { activeFormats, toggleFormat } = useMarkdownEditor();
|
|
2743
|
+
return /* @__PURE__ */ jsx7(Fragment2, { children: FORMAT_BUTTONS.map(({ format, label, icon }) => /* @__PURE__ */ jsx7(
|
|
2744
|
+
ToolbarButton,
|
|
2745
|
+
{
|
|
2746
|
+
label,
|
|
2747
|
+
active: activeFormats.has(format),
|
|
2748
|
+
onClick: () => toggleFormat(format),
|
|
2749
|
+
children: icon
|
|
2750
|
+
},
|
|
2751
|
+
format
|
|
2752
|
+
)) });
|
|
2753
|
+
}
|
|
2754
|
+
function InsertButtons() {
|
|
2755
|
+
const { insertTable, insertDrawing } = useMarkdownEditor();
|
|
2756
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2757
|
+
/* @__PURE__ */ jsx7(ToolbarButton, { label: "Insert table", onClick: () => insertTable(), children: /* @__PURE__ */ jsxs5(Icon, { children: [
|
|
2758
|
+
/* @__PURE__ */ jsx7("rect", { x: "3", y: "3.5", width: "14", height: "13", rx: "1.5" }),
|
|
2759
|
+
/* @__PURE__ */ jsx7("path", { d: "M3 8h14M8 8v8.5M13 8v8.5" })
|
|
2760
|
+
] }) }),
|
|
2761
|
+
/* @__PURE__ */ jsx7(ToolbarButton, { label: "Insert drawing", onClick: insertDrawing, children: /* @__PURE__ */ jsxs5(Icon, { children: [
|
|
2762
|
+
/* @__PURE__ */ jsx7("rect", { x: "3", y: "5", width: "8", height: "6", rx: "1.5" }),
|
|
2763
|
+
/* @__PURE__ */ jsx7("path", { d: "M13.5 8h3M14.5 6.5L17.5 8l-3 1.5" }),
|
|
2764
|
+
/* @__PURE__ */ jsx7("ellipse", { cx: "13", cy: "14.5", rx: "4", ry: "2.8" })
|
|
2765
|
+
] }) })
|
|
2766
|
+
] });
|
|
2767
|
+
}
|
|
2768
|
+
function HistoryButtons() {
|
|
2769
|
+
const { canUndo, canRedo, undo, redo } = useMarkdownEditor();
|
|
2770
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2771
|
+
/* @__PURE__ */ jsx7(ToolbarButton, { label: "Undo", onClick: undo, disabled: !canUndo, children: /* @__PURE__ */ jsxs5(Icon, { children: [
|
|
2772
|
+
/* @__PURE__ */ jsx7("path", { d: "M7 6L3.5 9.5 7 13" }),
|
|
2773
|
+
/* @__PURE__ */ jsx7("path", { d: "M3.5 9.5H12a4 4 0 0 1 0 8h-1" })
|
|
2774
|
+
] }) }),
|
|
2775
|
+
/* @__PURE__ */ jsx7(ToolbarButton, { label: "Redo", onClick: redo, disabled: !canRedo, children: /* @__PURE__ */ jsxs5(Icon, { children: [
|
|
2776
|
+
/* @__PURE__ */ jsx7("path", { d: "M13 6l3.5 3.5L13 13" }),
|
|
2777
|
+
/* @__PURE__ */ jsx7("path", { d: "M16.5 9.5H8a4 4 0 0 0 0 8h1" })
|
|
2778
|
+
] }) })
|
|
2779
|
+
] });
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
// src/MarkdownEditor.tsx
|
|
2783
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2784
|
+
var DEFAULT_ITEMS = {
|
|
2785
|
+
format: /* @__PURE__ */ jsx8(FormatButtons, {}),
|
|
2786
|
+
insert: /* @__PURE__ */ jsx8(InsertButtons, {}),
|
|
2787
|
+
history: /* @__PURE__ */ jsx8(HistoryButtons, {})
|
|
2788
|
+
};
|
|
2789
|
+
function EditorToolbar({
|
|
2790
|
+
children,
|
|
2791
|
+
className
|
|
2792
|
+
}) {
|
|
2793
|
+
const { mode, readOnly } = useEditorContext();
|
|
2794
|
+
if (mode !== "edit-md" || readOnly) return null;
|
|
2795
|
+
return /* @__PURE__ */ jsx8(Toolbar, { className, children });
|
|
2796
|
+
}
|
|
2797
|
+
function MarkdownEditor({
|
|
2798
|
+
placeholder,
|
|
2799
|
+
toolbar = true,
|
|
2800
|
+
outline = false,
|
|
2801
|
+
foldable = true,
|
|
2802
|
+
...rootProps
|
|
2803
|
+
}) {
|
|
2804
|
+
return /* @__PURE__ */ jsxs6(EditorRoot, { ...rootProps, children: [
|
|
2805
|
+
toolbar === true && /* @__PURE__ */ jsx8(EditorToolbar, {}),
|
|
2806
|
+
typeof toolbar === "function" && toolbar(DEFAULT_ITEMS),
|
|
2807
|
+
/* @__PURE__ */ jsx8(EditorContent, { placeholder, foldable, children: outline && /* @__PURE__ */ jsx8(OutlinePlugin, {}) })
|
|
2808
|
+
] });
|
|
2691
2809
|
}
|
|
2810
|
+
MarkdownEditor.Root = EditorRoot;
|
|
2811
|
+
MarkdownEditor.Content = EditorContent;
|
|
2812
|
+
MarkdownEditor.Toolbar = EditorToolbar;
|
|
2813
|
+
MarkdownEditor.ToolbarButton = ToolbarButton;
|
|
2814
|
+
MarkdownEditor.ToolbarDivider = ToolbarDivider;
|
|
2815
|
+
MarkdownEditor.FormatButtons = FormatButtons;
|
|
2816
|
+
MarkdownEditor.InsertButtons = InsertButtons;
|
|
2817
|
+
MarkdownEditor.HistoryButtons = HistoryButtons;
|
|
2818
|
+
MarkdownEditor.Outline = OutlinePlugin;
|
|
2819
|
+
MarkdownEditor.useEditor = useMarkdownEditor;
|
|
2820
|
+
var MarkdownEditor_default = MarkdownEditor;
|
|
2821
|
+
|
|
2822
|
+
// src/components/drawingSchema.ts
|
|
2823
|
+
var DRAWING_DATA_JSON_SCHEMA = {
|
|
2824
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
2825
|
+
title: "DrawingData",
|
|
2826
|
+
description: "Vector drawing embedded in markdown as a ```drawing fenced code block",
|
|
2827
|
+
type: "object",
|
|
2828
|
+
required: ["version", "height", "shapes"],
|
|
2829
|
+
additionalProperties: false,
|
|
2830
|
+
properties: {
|
|
2831
|
+
version: { const: 1 },
|
|
2832
|
+
height: {
|
|
2833
|
+
type: "number",
|
|
2834
|
+
minimum: 80,
|
|
2835
|
+
description: "Canvas height in pixels (width is fluid)"
|
|
2836
|
+
},
|
|
2837
|
+
shapes: {
|
|
2838
|
+
type: "array",
|
|
2839
|
+
items: { $ref: "#/definitions/shape" },
|
|
2840
|
+
description: "Render order: later shapes draw on top"
|
|
2841
|
+
}
|
|
2842
|
+
},
|
|
2843
|
+
definitions: {
|
|
2844
|
+
point: {
|
|
2845
|
+
type: "object",
|
|
2846
|
+
required: ["x", "y"],
|
|
2847
|
+
additionalProperties: false,
|
|
2848
|
+
properties: { x: { type: "number" }, y: { type: "number" } }
|
|
2849
|
+
},
|
|
2850
|
+
shape: {
|
|
2851
|
+
type: "object",
|
|
2852
|
+
required: ["id", "type", "x", "y", "w", "h", "stroke", "fill", "strokeWidth"],
|
|
2853
|
+
additionalProperties: false,
|
|
2854
|
+
properties: {
|
|
2855
|
+
id: {
|
|
2856
|
+
type: "string",
|
|
2857
|
+
description: "Unique within the drawing; connector bindings reference it"
|
|
2858
|
+
},
|
|
2859
|
+
type: {
|
|
2860
|
+
enum: ["rect", "ellipse", "triangle", "pentagon", "arrow", "line", "text"]
|
|
2861
|
+
},
|
|
2862
|
+
x: {
|
|
2863
|
+
type: "number",
|
|
2864
|
+
description: "Boxes/text: left edge of the bounding box. Connectors: start point x."
|
|
2865
|
+
},
|
|
2866
|
+
y: {
|
|
2867
|
+
type: "number",
|
|
2868
|
+
description: "Boxes/text: top edge of the bounding box. Connectors: start point y."
|
|
2869
|
+
},
|
|
2870
|
+
w: {
|
|
2871
|
+
type: "number",
|
|
2872
|
+
description: "Boxes/text: width (non-negative). Connectors: delta x to the end point (may be negative)."
|
|
2873
|
+
},
|
|
2874
|
+
h: {
|
|
2875
|
+
type: "number",
|
|
2876
|
+
description: "Boxes/text: height (non-negative). Connectors: delta y to the end point (may be negative)."
|
|
2877
|
+
},
|
|
2878
|
+
stroke: {
|
|
2879
|
+
type: "string",
|
|
2880
|
+
description: "CSS color of the outline (and of any text)"
|
|
2881
|
+
},
|
|
2882
|
+
fill: {
|
|
2883
|
+
type: "string",
|
|
2884
|
+
description: 'CSS color of the interior; "transparent" for none'
|
|
2885
|
+
},
|
|
2886
|
+
strokeWidth: { type: "number", description: "Outline width; use 2" },
|
|
2887
|
+
text: {
|
|
2888
|
+
type: "string",
|
|
2889
|
+
description: "Standalone text content; center content of a box; midpoint label of a connector"
|
|
2890
|
+
},
|
|
2891
|
+
label: {
|
|
2892
|
+
type: "string",
|
|
2893
|
+
description: "Boxes only: small bold heading at the top"
|
|
2894
|
+
},
|
|
2895
|
+
footer: {
|
|
2896
|
+
type: "string",
|
|
2897
|
+
description: "Boxes only: small dim line at the bottom"
|
|
2898
|
+
},
|
|
2899
|
+
startBinding: {
|
|
2900
|
+
type: "string",
|
|
2901
|
+
description: "Connectors only: id of the box the start attaches to. The editor re-anchors the endpoint onto that box\u2019s border."
|
|
2902
|
+
},
|
|
2903
|
+
endBinding: {
|
|
2904
|
+
type: "string",
|
|
2905
|
+
description: "Connectors only: id of the box the end attaches to"
|
|
2906
|
+
},
|
|
2907
|
+
bidirectional: {
|
|
2908
|
+
type: "boolean",
|
|
2909
|
+
description: "Arrows only: arrowheads on both ends"
|
|
2910
|
+
},
|
|
2911
|
+
routing: {
|
|
2912
|
+
const: "elbow",
|
|
2913
|
+
description: "Connectors only: orthogonal (right-angled) auto-routing. Ignored when waypoints are present."
|
|
2914
|
+
},
|
|
2915
|
+
elbow: {
|
|
2916
|
+
type: "number",
|
|
2917
|
+
minimum: 0,
|
|
2918
|
+
maximum: 1,
|
|
2919
|
+
description: "Elbow middle-segment position as a fraction of the span (default 0.5)"
|
|
2920
|
+
},
|
|
2921
|
+
waypoints: {
|
|
2922
|
+
type: "array",
|
|
2923
|
+
items: { $ref: "#/definitions/point" },
|
|
2924
|
+
description: "Connectors only: intermediate path points in canvas coordinates, ordered start\u2192end. Takes precedence over routing."
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
};
|
|
2692
2930
|
export {
|
|
2693
2931
|
$createDrawingNode,
|
|
2694
2932
|
$createFrontmatterNode,
|
|
2695
2933
|
$isDrawingNode,
|
|
2696
2934
|
$isFrontmatterNode,
|
|
2697
2935
|
DRAWING,
|
|
2936
|
+
DRAWING_DATA_JSON_SCHEMA,
|
|
2698
2937
|
DrawingNode,
|
|
2699
2938
|
FRONTMATTER,
|
|
2939
|
+
FormatButtons,
|
|
2700
2940
|
FrontmatterNode,
|
|
2701
|
-
|
|
2702
|
-
|
|
2941
|
+
HistoryButtons,
|
|
2942
|
+
InsertButtons,
|
|
2943
|
+
MarkdownEditor_default as MarkdownEditor,
|
|
2944
|
+
TABLE,
|
|
2945
|
+
Toolbar,
|
|
2946
|
+
ToolbarButton,
|
|
2947
|
+
ToolbarDivider,
|
|
2948
|
+
parseDrawingData,
|
|
2949
|
+
serializeDrawingData,
|
|
2950
|
+
useMarkdownEditor
|
|
2703
2951
|
};
|