@editability/editor-lexical 0.1.2 → 0.1.4

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/dist/overlay.js CHANGED
@@ -1,18 +1,31 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { diffWords, fnv1a64, normalizeTextContent, sanitizeHref } from "@editability/shared";
4
- import { createActiveEditor } from "./lexical/editor.js";
4
+ import { createActiveEditor, } from "./lexical/editor.js";
5
5
  const DRAFT_KEY = "editability:draft:v1";
6
+ const EMPTY_FORMAT_STATE = {
7
+ bold: false,
8
+ italic: false,
9
+ link: false,
10
+ blockType: "paragraph",
11
+ listType: null,
12
+ };
6
13
  export function OverlayApp({ apiBase, googleClientId }) {
7
14
  const [user, setUser] = useState(null);
8
15
  const [loading, setLoading] = useState(true);
9
16
  const [draft, setDraft] = useState(() => loadDraft());
10
17
  const [activeId, setActiveId] = useState(null);
11
18
  const [activeEditor, setActiveEditor] = useState(null);
19
+ const [formatState, setFormatState] = useState(EMPTY_FORMAT_STATE);
20
+ const [historyState, setHistoryState] = useState({ canUndo: false, canRedo: false });
12
21
  const [publishOpen, setPublishOpen] = useState(false);
13
22
  const [message, setMessage] = useState("");
14
23
  const [error, setError] = useState(null);
15
24
  const [publishing, setPublishing] = useState(false);
25
+ const [linkOpen, setLinkOpen] = useState(false);
26
+ const [linkUrl, setLinkUrl] = useState("");
27
+ const [linkError, setLinkError] = useState(null);
28
+ const [blockMenuOpen, setBlockMenuOpen] = useState(false);
16
29
  const observerRef = useRef(null);
17
30
  const applyingRef = useRef(false);
18
31
  const applyScheduledRef = useRef(null);
@@ -22,6 +35,9 @@ export function OverlayApp({ apiBase, googleClientId }) {
22
35
  const activeSnapshotRef = useRef(null);
23
36
  const activeChangeRef = useRef(null);
24
37
  const draftRef = useRef(draft);
38
+ const linkInputRef = useRef(null);
39
+ const linkPanelRef = useRef(null);
40
+ const blockMenuRef = useRef(null);
25
41
  const changes = useMemo(() => Object.values(draft.changes), [draft]);
26
42
  useEffect(() => {
27
43
  ensureStyles();
@@ -42,6 +58,46 @@ export function OverlayApp({ apiBase, googleClientId }) {
42
58
  useEffect(() => {
43
59
  activeIdRef.current = activeId;
44
60
  }, [activeId]);
61
+ useEffect(() => {
62
+ if (!activeEditor) {
63
+ setFormatState(EMPTY_FORMAT_STATE);
64
+ setHistoryState({ canUndo: false, canRedo: false });
65
+ }
66
+ }, [activeEditor]);
67
+ useEffect(() => {
68
+ if (!linkOpen) {
69
+ return;
70
+ }
71
+ const id = window.requestAnimationFrame(() => linkInputRef.current?.focus());
72
+ return () => window.cancelAnimationFrame(id);
73
+ }, [linkOpen]);
74
+ useEffect(() => {
75
+ if (!linkOpen && !blockMenuOpen) {
76
+ return;
77
+ }
78
+ const handler = (event) => {
79
+ const target = event.target;
80
+ if (!target) {
81
+ return;
82
+ }
83
+ if (linkOpen && linkPanelRef.current?.contains(target)) {
84
+ return;
85
+ }
86
+ if (blockMenuOpen && blockMenuRef.current?.contains(target)) {
87
+ return;
88
+ }
89
+ setLinkOpen(false);
90
+ setBlockMenuOpen(false);
91
+ };
92
+ document.addEventListener("mousedown", handler, true);
93
+ return () => document.removeEventListener("mousedown", handler, true);
94
+ }, [linkOpen, blockMenuOpen]);
95
+ useEffect(() => {
96
+ setLinkOpen(false);
97
+ setBlockMenuOpen(false);
98
+ setLinkUrl("");
99
+ setLinkError(null);
100
+ }, [activeId]);
45
101
  useEffect(() => {
46
102
  document.body.classList.toggle("editability-authenticated", Boolean(user));
47
103
  return () => {
@@ -90,6 +146,11 @@ export function OverlayApp({ apiBase, googleClientId }) {
90
146
  if (!id) {
91
147
  return;
92
148
  }
149
+ const nestedInteractive = findNestedInteractive(block);
150
+ if (nestedInteractive) {
151
+ console.warn("Editability: blocked editing for element containing interactive children. Wrap only text nodes or add nested <Editability> for interactive labels.");
152
+ return;
153
+ }
93
154
  if (activeId === id) {
94
155
  return;
95
156
  }
@@ -98,10 +159,18 @@ export function OverlayApp({ apiBase, googleClientId }) {
98
159
  }
99
160
  const existing = draftRef.current.changes[id];
100
161
  activeSnapshotRef.current = { id, html: existing ? existing.originalHtml : block.innerHTML };
101
- const { editor, change } = activateBlock(block, id, draftRef.current, setDraft, setActiveId, setActiveEditor);
162
+ const { editor, change } = activateBlock(block, id, draftRef.current, setDraft, setActiveId, setActiveEditor, setFormatState, setHistoryState);
102
163
  activeEditorRef.current = editor;
103
164
  activeElementRef.current = block;
104
165
  activeChangeRef.current = change;
166
+ const clickPoint = { x: event.clientX, y: event.clientY };
167
+ window.requestAnimationFrame(() => {
168
+ if (activeElementRef.current !== block) {
169
+ return;
170
+ }
171
+ placeCaretAtPoint(block, clickPoint);
172
+ editor.editor.focus();
173
+ });
105
174
  };
106
175
  const linkGuard = (event) => {
107
176
  const target = event.target;
@@ -137,6 +206,66 @@ export function OverlayApp({ apiBase, googleClientId }) {
137
206
  destroyActiveEditor("done", true, draftRef, activeEditorRef, activeElementRef, activeIdRef, activeSnapshotRef, activeChangeRef, setDraft, setActiveEditor, setActiveId, observerRef, applyingRef);
138
207
  }
139
208
  };
209
+ const isBlockMode = activeEditor?.mode === "block";
210
+ const blockLabel = formatState.blockType === "paragraph"
211
+ ? "Paragraph"
212
+ : formatState.blockType === "blockquote"
213
+ ? "Quote"
214
+ : `Heading ${formatState.blockType.replace("h", "")}`;
215
+ const handleBlockSelect = (type) => {
216
+ if (!activeEditor || !isBlockMode) {
217
+ return;
218
+ }
219
+ activeEditor.setBlockType(type);
220
+ setBlockMenuOpen(false);
221
+ };
222
+ const handleListToggle = (type) => {
223
+ if (!activeEditor || !isBlockMode) {
224
+ return;
225
+ }
226
+ activeEditor.toggleList(type);
227
+ };
228
+ const handleLinkOpen = () => {
229
+ if (!activeEditor) {
230
+ return;
231
+ }
232
+ activeEditor.captureSelection();
233
+ const existingUrl = activeEditor.getSelectedLinkUrl();
234
+ setLinkUrl(existingUrl ?? "");
235
+ setLinkError(null);
236
+ setLinkOpen(true);
237
+ };
238
+ const handleLinkApply = () => {
239
+ if (!activeEditor) {
240
+ return;
241
+ }
242
+ const trimmed = linkUrl.trim();
243
+ if (!trimmed) {
244
+ activeEditor.setLink(null);
245
+ setLinkOpen(false);
246
+ setLinkUrl("");
247
+ setLinkError(null);
248
+ return;
249
+ }
250
+ const sanitized = sanitizeHref(trimmed);
251
+ if (!sanitized) {
252
+ setLinkError("Please enter a valid URL.");
253
+ return;
254
+ }
255
+ activeEditor.setLink(sanitized.href);
256
+ setLinkOpen(false);
257
+ setLinkUrl("");
258
+ setLinkError(null);
259
+ };
260
+ const handleLinkRemove = () => {
261
+ if (!activeEditor) {
262
+ return;
263
+ }
264
+ activeEditor.setLink(null);
265
+ setLinkOpen(false);
266
+ setLinkUrl("");
267
+ setLinkError(null);
268
+ };
140
269
  const handlePublish = async () => {
141
270
  if (publishing) {
142
271
  return;
@@ -221,25 +350,30 @@ export function OverlayApp({ apiBase, googleClientId }) {
221
350
  if (!user) {
222
351
  return (_jsx("div", { className: "editability-overlay", children: _jsxs("div", { className: "editability-login", children: [_jsx("h2", { children: "Sign in to edit" }), _jsx("p", { children: "Only allowlisted editors can enter edit mode." }), _jsx(LoginPanel, { apiBase: apiBase, googleClientId: googleClientId, onLogin: setUser, loading: false }), _jsx("button", { className: "editability-btn", onClick: handleExit, children: "Exit" })] }) }));
223
352
  }
224
- return (_jsxs("div", { className: "editability-overlay", children: [_jsxs("div", { className: "editability-bar", children: [_jsxs("div", { className: "editability-left", children: [_jsx("span", { className: "editability-pill", children: "Editability" }), activeId ? _jsx("span", { className: "editability-status", children: "Editing" }) : _jsx("span", { className: "editability-status", children: "Select text" })] }), _jsxs("div", { className: "editability-center", children: [_jsx("button", { className: "editability-btn", onClick: () => activeEditor?.undo(), disabled: !activeEditor, children: "Undo" }), _jsx("button", { className: "editability-btn", onClick: () => activeEditor?.redo(), disabled: !activeEditor, children: "Redo" }), _jsx("button", { className: "editability-btn", onClick: () => activeEditor?.setBold(), disabled: !activeEditor, children: "Bold" }), _jsx("button", { className: "editability-btn", onClick: () => activeEditor?.setItalic(), disabled: !activeEditor, children: "Italic" }), _jsx("button", { className: "editability-btn", onClick: () => {
225
- if (!activeEditor)
226
- return;
227
- activeEditor.captureSelection();
228
- const url = prompt("Link URL (leave blank to remove):");
229
- if (url === null)
230
- return;
231
- const normalized = url.trim();
232
- if (!normalized) {
233
- activeEditor.setLink(null);
234
- return;
235
- }
236
- const sanitized = sanitizeHref(normalized);
237
- if (!sanitized) {
238
- alert("Invalid link.");
239
- return;
240
- }
241
- activeEditor.setLink(sanitized.href);
242
- }, disabled: !activeEditor, children: "Link" }), _jsx("button", { className: "editability-btn", onClick: handleDone, disabled: !activeEditor, children: "Done" })] }), _jsxs("div", { className: "editability-right", children: [_jsx("button", { className: "editability-btn", onClick: () => setPublishOpen(true), disabled: changeCount === 0 || publishing, children: publishing ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "editability-spinner", "aria-hidden": true }), "Publishing\u2026"] })) : (`Publish (${changeCount})`) }), _jsx("button", { className: "editability-btn", onClick: handleDiscard, disabled: changeCount === 0, children: "Discard" }), user ? (_jsxs("div", { className: "editability-user", children: [user.picture ? _jsx("img", { src: user.picture, alt: "" }) : null, _jsx("span", { children: user.name ?? user.email }), _jsx("button", { className: "editability-btn", onClick: handleLogout, children: "Log out" })] })) : (_jsx(LoginPanel, { apiBase: apiBase, googleClientId: googleClientId, onLogin: setUser, loading: loading })), _jsx("button", { className: "editability-btn", onClick: handleExit, children: "Exit" })] })] }), publishOpen ? (_jsx(PublishModal, { changes: changes, message: message, setMessage: setMessage, onClose: () => setPublishOpen(false), onPublish: handlePublish, error: error, publishing: publishing })) : null] }));
353
+ return (_jsxs("div", { className: "editability-overlay", children: [_jsxs("div", { className: "editability-bar", children: [_jsxs("div", { className: "editability-left", children: [_jsxs("div", { className: "editability-brand", children: [_jsx("span", { className: "editability-pill", children: "Editability" }), activeId ? _jsx("span", { className: "editability-status", children: "Editing" }) : _jsx("span", { className: "editability-status", children: "Select text" })] }), _jsxs("span", { className: `editability-badge ${changeCount > 0 ? "editability-badge-active" : ""}`, children: [changeCount, " ", changeCount === 1 ? "change" : "changes"] })] }), _jsxs("div", { className: "editability-center", children: [_jsxs("div", { className: "editability-group", children: [_jsx("button", { className: "editability-btn editability-icon-btn", onClick: () => activeEditor?.undo(), disabled: !activeEditor || !historyState.canUndo, "aria-label": "Undo", children: _jsx(IconUndo, {}) }), _jsx("button", { className: "editability-btn editability-icon-btn", onClick: () => activeEditor?.redo(), disabled: !activeEditor || !historyState.canRedo, "aria-label": "Redo", children: _jsx(IconRedo, {}) })] }), _jsx("div", { className: "editability-divider" }), _jsx("div", { className: "editability-group", children: _jsxs("div", { className: "editability-popover-anchor", children: [_jsxs("button", { className: "editability-btn editability-block-btn", onClick: () => setBlockMenuOpen((open) => !open), disabled: !activeEditor || !isBlockMode, "aria-haspopup": "menu", "aria-expanded": blockMenuOpen, children: [_jsx("span", { children: blockLabel }), _jsx(IconChevron, {})] }), blockMenuOpen ? (_jsxs("div", { className: "editability-menu", ref: blockMenuRef, role: "menu", children: [_jsxs("button", { className: `editability-menu-item ${formatState.blockType === "paragraph" ? "active" : ""}`, onClick: () => handleBlockSelect("paragraph"), role: "menuitem", children: [_jsx(IconParagraph, {}), _jsx("span", { children: "Paragraph" })] }), _jsxs("button", { className: `editability-menu-item ${formatState.blockType === "h1" ? "active" : ""}`, onClick: () => handleBlockSelect("h1"), role: "menuitem", children: [_jsx(IconHeading, { label: "H1" }), _jsx("span", { children: "Heading 1" })] }), _jsxs("button", { className: `editability-menu-item ${formatState.blockType === "h2" ? "active" : ""}`, onClick: () => handleBlockSelect("h2"), role: "menuitem", children: [_jsx(IconHeading, { label: "H2" }), _jsx("span", { children: "Heading 2" })] }), _jsxs("button", { className: `editability-menu-item ${formatState.blockType === "h3" ? "active" : ""}`, onClick: () => handleBlockSelect("h3"), role: "menuitem", children: [_jsx(IconHeading, { label: "H3" }), _jsx("span", { children: "Heading 3" })] }), _jsxs("button", { className: `editability-menu-item ${formatState.blockType === "h4" ? "active" : ""}`, onClick: () => handleBlockSelect("h4"), role: "menuitem", children: [_jsx(IconHeading, { label: "H4" }), _jsx("span", { children: "Heading 4" })] }), _jsxs("button", { className: `editability-menu-item ${formatState.blockType === "h5" ? "active" : ""}`, onClick: () => handleBlockSelect("h5"), role: "menuitem", children: [_jsx(IconHeading, { label: "H5" }), _jsx("span", { children: "Heading 5" })] }), _jsxs("button", { className: `editability-menu-item ${formatState.blockType === "h6" ? "active" : ""}`, onClick: () => handleBlockSelect("h6"), role: "menuitem", children: [_jsx(IconHeading, { label: "H6" }), _jsx("span", { children: "Heading 6" })] }), _jsxs("button", { className: `editability-menu-item ${formatState.blockType === "blockquote" ? "active" : ""}`, onClick: () => handleBlockSelect("blockquote"), role: "menuitem", children: [_jsx(IconQuote, {}), _jsx("span", { children: "Quote" })] })] })) : null] }) }), _jsxs("div", { className: "editability-group", children: [_jsx("button", { className: `editability-btn editability-icon-btn ${formatState.listType === "bullet" ? "active" : ""}`, onClick: () => handleListToggle("bullet"), disabled: !activeEditor || !isBlockMode, "aria-pressed": formatState.listType === "bullet", "aria-label": "Bulleted list", children: _jsx(IconListBullet, {}) }), _jsx("button", { className: `editability-btn editability-icon-btn ${formatState.listType === "number" ? "active" : ""}`, onClick: () => handleListToggle("number"), disabled: !activeEditor || !isBlockMode, "aria-pressed": formatState.listType === "number", "aria-label": "Numbered list", children: _jsx(IconListNumber, {}) })] }), _jsxs("div", { className: "editability-group", children: [_jsx("button", { className: `editability-btn editability-icon-btn ${formatState.bold ? "active" : ""}`, onClick: () => activeEditor?.setBold(), disabled: !activeEditor, "aria-pressed": formatState.bold, "aria-label": "Bold", children: _jsx(IconBold, {}) }), _jsx("button", { className: `editability-btn editability-icon-btn ${formatState.italic ? "active" : ""}`, onClick: () => activeEditor?.setItalic(), disabled: !activeEditor, "aria-pressed": formatState.italic, "aria-label": "Italic", children: _jsx(IconItalic, {}) }), _jsxs("div", { className: "editability-popover-anchor", children: [_jsx("button", { className: `editability-btn editability-icon-btn ${formatState.link ? "active" : ""}`, onMouseDown: (event) => {
354
+ if (!activeEditor) {
355
+ return;
356
+ }
357
+ event.preventDefault();
358
+ activeEditor.captureSelection();
359
+ }, onClick: () => {
360
+ if (!activeEditor)
361
+ return;
362
+ if (linkOpen) {
363
+ setLinkOpen(false);
364
+ return;
365
+ }
366
+ handleLinkOpen();
367
+ }, disabled: !activeEditor, "aria-pressed": formatState.link, "aria-label": "Link", children: _jsx(IconLink, {}) }), linkOpen ? (_jsxs("div", { className: "editability-popover", ref: linkPanelRef, children: [_jsx("div", { className: "editability-popover-title", children: "Add link" }), _jsx("input", { ref: linkInputRef, className: "editability-input", placeholder: "https:// or /path", value: linkUrl, onChange: (event) => setLinkUrl(event.target.value), onKeyDown: (event) => {
368
+ if (event.key === "Enter") {
369
+ event.preventDefault();
370
+ handleLinkApply();
371
+ }
372
+ if (event.key === "Escape") {
373
+ setLinkOpen(false);
374
+ setLinkError(null);
375
+ }
376
+ } }), linkError ? _jsx("div", { className: "editability-error editability-error-inline", children: linkError }) : null, _jsxs("div", { className: "editability-popover-actions", children: [_jsx("button", { className: "editability-btn editability-primary", onClick: handleLinkApply, children: "Apply" }), _jsx("button", { className: "editability-btn editability-ghost", onClick: handleLinkRemove, children: "Remove" })] })] })) : null] })] }), _jsx("button", { className: "editability-btn editability-cta", onClick: handleDone, disabled: !activeEditor, children: "Done" })] }), _jsxs("div", { className: "editability-right", children: [_jsx("button", { className: "editability-btn editability-primary", onClick: () => setPublishOpen(true), disabled: changeCount === 0 || publishing, children: publishing ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "editability-spinner", "aria-hidden": true }), "Publishing\u2026"] })) : (`Publish (${changeCount})`) }), _jsx("button", { className: "editability-btn", onClick: handleDiscard, disabled: changeCount === 0, children: "Discard" }), user ? (_jsxs("div", { className: "editability-user", children: [user.picture ? _jsx("img", { src: user.picture, alt: "" }) : null, _jsx("span", { children: user.name ?? user.email }), _jsx("button", { className: "editability-btn", onClick: handleLogout, children: "Log out" })] })) : (_jsx(LoginPanel, { apiBase: apiBase, googleClientId: googleClientId, onLogin: setUser, loading: loading })), _jsx("button", { className: "editability-btn editability-ghost", onClick: handleExit, children: "Exit" })] })] }), publishOpen ? (_jsx(PublishModal, { changes: changes, message: message, setMessage: setMessage, onClose: () => setPublishOpen(false), onPublish: handlePublish, error: error, publishing: publishing })) : null] }));
243
377
  }
244
378
  function LoginPanel({ apiBase, googleClientId, onLogin, loading }) {
245
379
  const containerRef = useRef(null);
@@ -303,19 +437,20 @@ function buildLabel(change) {
303
437
  const tag = change.tag.toLowerCase();
304
438
  return `${tag.toUpperCase()}: ${snippet}${change.originalText.length > 40 ? "…" : ""}`;
305
439
  }
306
- function activateBlock(element, id, draft, setDraft, setActiveId, setActiveEditor) {
440
+ function activateBlock(element, id, draft, setDraft, setActiveId, setActiveEditor, setFormatState, setHistoryState) {
307
441
  const mode = determineMode(element);
308
442
  const existing = draft.changes[id];
309
443
  const originalHtml = existing?.originalHtml ?? element.innerHTML;
310
444
  const originalText = existing?.originalText ?? normalizeTextContent(element.textContent ?? "");
311
445
  const datasetFingerprint = element.dataset.editabilityFp;
312
446
  const fingerprint = datasetFingerprint ?? existing?.fingerprint ?? fnv1a64(originalText);
447
+ const baselineAst = htmlToAst(originalHtml, mode);
313
448
  const current = existing
314
449
  ? { ...existing, fingerprint }
315
450
  : {
316
451
  id,
317
452
  mode,
318
- ast: htmlToAst(originalHtml, mode),
453
+ ast: baselineAst,
319
454
  fingerprint,
320
455
  originalHtml,
321
456
  originalText,
@@ -328,11 +463,8 @@ function activateBlock(element, id, draft, setDraft, setActiveId, setActiveEdito
328
463
  initialHtml: existing ? renderAstToHtml(existing.ast) : originalHtml,
329
464
  onUpdate: (ast) => {
330
465
  const next = { ...current, ast, lastPath: window.location.pathname };
331
- const afterHtml = renderAstToHtml(ast);
332
- const normalizedOriginal = normalizeHtml(current.originalHtml);
333
- const normalizedAfter = normalizeHtml(afterHtml);
334
466
  const updated = { ...draft, updatedAt: Date.now(), changes: { ...draft.changes } };
335
- if (normalizedOriginal === normalizedAfter) {
467
+ if (astEquals(ast, baselineAst)) {
336
468
  delete updated.changes[id];
337
469
  }
338
470
  else {
@@ -341,6 +473,8 @@ function activateBlock(element, id, draft, setDraft, setActiveId, setActiveEdito
341
473
  saveDraft(updated);
342
474
  setDraft(updated);
343
475
  },
476
+ onFormat: setFormatState,
477
+ onHistory: setHistoryState,
344
478
  });
345
479
  setActiveId(id);
346
480
  setActiveEditor(active);
@@ -348,7 +482,16 @@ function activateBlock(element, id, draft, setDraft, setActiveId, setActiveEdito
348
482
  }
349
483
  function determineMode(element) {
350
484
  const tag = element.tagName.toLowerCase();
351
- if (["div", "section", "article", "main", "aside", "nav", "header", "footer"].includes(tag)) {
485
+ if ([
486
+ "div",
487
+ "section",
488
+ "article",
489
+ "main",
490
+ "aside",
491
+ "nav",
492
+ "header",
493
+ "footer",
494
+ ].includes(tag)) {
352
495
  return "block";
353
496
  }
354
497
  return "inline";
@@ -435,7 +578,121 @@ function renderAstToHtml(ast) {
435
578
  if (ast.mode === "inline") {
436
579
  return ast.nodes.map(renderInlineNode).join("");
437
580
  }
438
- return ast.nodes.map((block) => `<p>${block.children.map(renderInlineNode).join("")}</p>`).join("");
581
+ return ast.nodes.map(renderBlockNode).join("");
582
+ }
583
+ function renderBlockNode(block) {
584
+ switch (block.type) {
585
+ case "paragraph":
586
+ return `<p>${block.children.map(renderInlineNode).join("")}</p>`;
587
+ case "heading": {
588
+ const level = Math.min(6, Math.max(1, block.level));
589
+ return `<h${level}>${block.children.map(renderInlineNode).join("")}</h${level}>`;
590
+ }
591
+ case "blockquote":
592
+ return `<blockquote>${block.children.map(renderInlineNode).join("")}</blockquote>`;
593
+ case "list": {
594
+ const tag = block.ordered ? "ol" : "ul";
595
+ const items = block.items
596
+ .map((item) => `<li>${item.map(renderInlineNode).join("")}</li>`)
597
+ .join("");
598
+ return `<${tag}>${items}</${tag}>`;
599
+ }
600
+ default:
601
+ return "";
602
+ }
603
+ }
604
+ function isInlineAst(ast) {
605
+ return ast.mode === "inline";
606
+ }
607
+ function isBlockAst(ast) {
608
+ return ast.mode === "block";
609
+ }
610
+ function astEquals(a, b) {
611
+ if (isInlineAst(a) && isInlineAst(b)) {
612
+ return inlineNodesEqual(a.nodes, b.nodes);
613
+ }
614
+ if (isBlockAst(a) && isBlockAst(b)) {
615
+ return blockNodesEqual(a.nodes, b.nodes);
616
+ }
617
+ return false;
618
+ }
619
+ function blockNodesEqual(a, b) {
620
+ if (a.length !== b.length) {
621
+ return false;
622
+ }
623
+ return a.every((node, idx) => {
624
+ const right = b[idx];
625
+ if (node.type !== right.type) {
626
+ return false;
627
+ }
628
+ switch (node.type) {
629
+ case "paragraph":
630
+ case "blockquote":
631
+ return inlineNodesEqual(node.children, right.children);
632
+ case "heading":
633
+ return (node.level === right.level &&
634
+ inlineNodesEqual(node.children, right.children));
635
+ case "list":
636
+ return (node.ordered === right.ordered &&
637
+ listItemsEqual(node.items, right.items));
638
+ default:
639
+ return false;
640
+ }
641
+ });
642
+ }
643
+ function listItemsEqual(a, b) {
644
+ if (a.length !== b.length) {
645
+ return false;
646
+ }
647
+ for (let i = 0; i < a.length; i += 1) {
648
+ if (!inlineNodesEqual(a[i], b[i])) {
649
+ return false;
650
+ }
651
+ }
652
+ return true;
653
+ }
654
+ function inlineNodesEqual(a, b) {
655
+ if (a.length !== b.length) {
656
+ return false;
657
+ }
658
+ for (let i = 0; i < a.length; i += 1) {
659
+ const left = a[i];
660
+ const right = b[i];
661
+ if (left.type !== right.type) {
662
+ return false;
663
+ }
664
+ switch (left.type) {
665
+ case "text": {
666
+ if (right.type !== "text") {
667
+ return false;
668
+ }
669
+ if (left.text !== right.text ||
670
+ Boolean(left.bold) !== Boolean(right.bold) ||
671
+ Boolean(left.italic) !== Boolean(right.italic)) {
672
+ return false;
673
+ }
674
+ break;
675
+ }
676
+ case "linebreak": {
677
+ if (right.type !== "linebreak") {
678
+ return false;
679
+ }
680
+ break;
681
+ }
682
+ case "link": {
683
+ if (right.type !== "link") {
684
+ return false;
685
+ }
686
+ if (left.href !== right.href || !inlineNodesEqual(left.children, right.children)) {
687
+ return false;
688
+ }
689
+ break;
690
+ }
691
+ default:
692
+ return false;
693
+ }
694
+ }
695
+ return true;
439
696
  }
440
697
  function renderInlineNode(node) {
441
698
  if (node.type === "text") {
@@ -465,6 +722,24 @@ function renderInlineNode(node) {
465
722
  }
466
723
  return "";
467
724
  }
725
+ const BLOCKED_INTERACTIVE_SELECTOR = "button, input, textarea, select, option, label, details, summary";
726
+ function findNestedInteractive(root) {
727
+ if (root.matches(BLOCKED_INTERACTIVE_SELECTOR)) {
728
+ return root;
729
+ }
730
+ const matches = root.querySelectorAll(BLOCKED_INTERACTIVE_SELECTOR);
731
+ for (const el of Array.from(matches)) {
732
+ if (el === root) {
733
+ continue;
734
+ }
735
+ const editableAncestor = el.closest("[data-editability-id]");
736
+ if (editableAncestor && editableAncestor !== root) {
737
+ continue;
738
+ }
739
+ return el;
740
+ }
741
+ return null;
742
+ }
468
743
  function escapeHtml(value) {
469
744
  return value
470
745
  .replace(/&/g, "&amp;")
@@ -472,14 +747,23 @@ function escapeHtml(value) {
472
747
  .replace(/>/g, "&gt;")
473
748
  .replace(/\"/g, "&quot;");
474
749
  }
475
- function normalizeHtml(html) {
476
- return html.replace(/\s+/g, " ").trim();
477
- }
478
750
  function astToText(ast) {
479
751
  if (ast.mode === "inline") {
480
752
  return ast.nodes.map(nodeToText).join("");
481
753
  }
482
- return ast.nodes.map((block) => block.children.map(nodeToText).join(" ")).join(" ");
754
+ return ast.nodes.map(blockToText).join(" ");
755
+ }
756
+ function blockToText(block) {
757
+ switch (block.type) {
758
+ case "paragraph":
759
+ case "heading":
760
+ case "blockquote":
761
+ return block.children.map(nodeToText).join(" ");
762
+ case "list":
763
+ return block.items.map((item) => item.map(nodeToText).join(" ")).join(" ");
764
+ default:
765
+ return "";
766
+ }
483
767
  }
484
768
  function nodeToText(node) {
485
769
  if (node.type === "text") {
@@ -499,26 +783,96 @@ function htmlToAst(html, mode) {
499
783
  if (mode === "inline") {
500
784
  return { mode: "inline", nodes: collectInline(wrapper) };
501
785
  }
502
- const blocks = [];
503
- const paragraphs = wrapper.querySelectorAll("p");
504
- if (paragraphs.length === 0) {
505
- blocks.push({ type: "paragraph", children: collectInline(wrapper) });
506
- }
507
- else {
508
- paragraphs.forEach((p) => {
509
- blocks.push({ type: "paragraph", children: collectInline(p) });
510
- });
786
+ const blocks = collectBlocks(wrapper);
787
+ if (blocks.length === 0) {
788
+ const inline = collectInline(wrapper);
789
+ return { mode: "block", nodes: [{ type: "paragraph", children: inline }] };
511
790
  }
512
791
  return { mode: "block", nodes: blocks };
513
792
  }
793
+ function collectBlocks(wrapper) {
794
+ const blocks = [];
795
+ let pendingInline = [];
796
+ const flushInline = () => {
797
+ if (!hasInlineContent(pendingInline)) {
798
+ pendingInline = [];
799
+ return;
800
+ }
801
+ blocks.push({ type: "paragraph", children: pendingInline });
802
+ pendingInline = [];
803
+ };
804
+ wrapper.childNodes.forEach((node) => {
805
+ if (node.nodeType === Node.TEXT_NODE) {
806
+ pendingInline.push(...collectInlineFromText(node.textContent ?? ""));
807
+ return;
808
+ }
809
+ if (node.nodeType !== Node.ELEMENT_NODE) {
810
+ return;
811
+ }
812
+ const element = node;
813
+ const tag = element.tagName.toLowerCase();
814
+ if (tag === "p") {
815
+ flushInline();
816
+ blocks.push({ type: "paragraph", children: collectInline(element) });
817
+ return;
818
+ }
819
+ if (tag === "blockquote") {
820
+ flushInline();
821
+ blocks.push({ type: "blockquote", children: collectInline(element) });
822
+ return;
823
+ }
824
+ if (tag === "ul" || tag === "ol") {
825
+ flushInline();
826
+ blocks.push({ type: "list", ordered: tag === "ol", items: collectListItems(element) });
827
+ return;
828
+ }
829
+ if (isHeadingTag(tag)) {
830
+ flushInline();
831
+ blocks.push({
832
+ type: "heading",
833
+ level: Math.min(6, Math.max(1, Number(tag.slice(1)))),
834
+ children: collectInline(element),
835
+ });
836
+ return;
837
+ }
838
+ pendingInline.push(...collectInline(element));
839
+ });
840
+ flushInline();
841
+ return blocks;
842
+ }
843
+ function collectListItems(list) {
844
+ const items = [];
845
+ list.childNodes.forEach((child) => {
846
+ if (child.nodeType !== Node.ELEMENT_NODE) {
847
+ return;
848
+ }
849
+ const element = child;
850
+ if (element.tagName.toLowerCase() !== "li") {
851
+ return;
852
+ }
853
+ const onlyParagraph = element.children.length === 1 && element.children[0].tagName.toLowerCase() === "p";
854
+ const target = onlyParagraph ? element.children[0] : element;
855
+ items.push(collectInline(target));
856
+ });
857
+ return items;
858
+ }
859
+ function isHeadingTag(tag) {
860
+ return /^h[1-6]$/.test(tag);
861
+ }
862
+ function hasInlineContent(nodes) {
863
+ return normalizeTextContent(nodes.map(nodeToText).join("")).length > 0;
864
+ }
865
+ function collectInlineFromText(text) {
866
+ if (!text) {
867
+ return [];
868
+ }
869
+ return [{ type: "text", text }];
870
+ }
514
871
  function collectInline(root) {
515
872
  const nodes = [];
516
873
  root.childNodes.forEach((node) => {
517
874
  if (node.nodeType === Node.TEXT_NODE) {
518
- const text = node.textContent ?? "";
519
- if (text) {
520
- nodes.push({ type: "text", text });
521
- }
875
+ nodes.push(...collectInlineFromText(node.textContent ?? ""));
522
876
  return;
523
877
  }
524
878
  if (node.nodeType !== Node.ELEMENT_NODE) {
@@ -589,15 +943,13 @@ function persistActiveDraft(draftRef, activeChangeRef, editor, setDraft) {
589
943
  return;
590
944
  }
591
945
  const ast = editor.getAst();
592
- const afterHtml = renderAstToHtml(ast);
593
- const normalizedOriginal = normalizeHtml(change.originalHtml);
594
- const normalizedAfter = normalizeHtml(afterHtml);
946
+ const baselineAst = htmlToAst(change.originalHtml, change.mode);
595
947
  const updated = {
596
948
  ...draftRef.current,
597
949
  updatedAt: Date.now(),
598
950
  changes: { ...draftRef.current.changes },
599
951
  };
600
- if (normalizedOriginal === normalizedAfter) {
952
+ if (astEquals(ast, baselineAst)) {
601
953
  delete updated.changes[change.id];
602
954
  }
603
955
  else {
@@ -615,6 +967,47 @@ async function fetchMe(apiBase) {
615
967
  const data = (await response.json());
616
968
  return data.user;
617
969
  }
970
+ function getRangeFromPoint(point) {
971
+ const doc = document;
972
+ if (doc.caretRangeFromPoint) {
973
+ return doc.caretRangeFromPoint(point.x, point.y);
974
+ }
975
+ if (doc.caretPositionFromPoint) {
976
+ const position = doc.caretPositionFromPoint(point.x, point.y);
977
+ if (!position) {
978
+ return null;
979
+ }
980
+ const range = document.createRange();
981
+ range.setStart(position.offsetNode, position.offset);
982
+ range.collapse(true);
983
+ return range;
984
+ }
985
+ return null;
986
+ }
987
+ function placeCaretAtPoint(container, point) {
988
+ const range = getRangeFromPoint(point);
989
+ if (!range || !container.contains(range.startContainer)) {
990
+ placeCaretAtEnd(container);
991
+ return;
992
+ }
993
+ const selection = window.getSelection();
994
+ if (!selection) {
995
+ return;
996
+ }
997
+ selection.removeAllRanges();
998
+ selection.addRange(range);
999
+ }
1000
+ function placeCaretAtEnd(container) {
1001
+ const selection = window.getSelection();
1002
+ if (!selection) {
1003
+ return;
1004
+ }
1005
+ const range = document.createRange();
1006
+ range.selectNodeContents(container);
1007
+ range.collapse(false);
1008
+ selection.removeAllRanges();
1009
+ selection.addRange(range);
1010
+ }
618
1011
  function ensureStyles() {
619
1012
  if (document.getElementById("editability-style")) {
620
1013
  return;
@@ -622,37 +1015,85 @@ function ensureStyles() {
622
1015
  const style = document.createElement("style");
623
1016
  style.id = "editability-style";
624
1017
  style.textContent = `
625
- .editability-authenticated [data-editability-id]:hover { outline: 2px dashed #2f855a; outline-offset: 2px; cursor: text; }
626
- .editability-authenticated [data-editability-id].editability-dirty { outline: 2px solid #3182ce; outline-offset: 2px; }
627
- .editability-overlay { position: fixed; inset: auto 0 0 0; z-index: 9999; font-family: ui-sans-serif, system-ui; }
628
- .editability-bar { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 16px; background: #0f172a; color: #f8fafc; box-shadow: 0 -6px 20px rgba(15, 23, 42, 0.25); }
629
- .editability-left, .editability-center, .editability-right { display: flex; align-items: center; gap: 8px; }
630
- .editability-pill { padding: 4px 10px; background: #38bdf8; color: #0f172a; border-radius: 999px; font-weight: 600; }
631
- .editability-status { opacity: 0.8; }
632
- .editability-btn { padding: 6px 10px; border-radius: 6px; border: 1px solid rgba(148, 163, 184, 0.3); background: #1e293b; color: #f8fafc; font-size: 13px; }
633
- .editability-btn:disabled { opacity: 0.5; cursor: not-allowed; }
634
- .editability-btn:hover:not(:disabled) { background: #334155; }
635
- .editability-primary { background: #38bdf8; color: #0f172a; border-color: transparent; }
1018
+ @import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap");
1019
+ :root {
1020
+ --editability-bg: #0b1020;
1021
+ --editability-surface: rgba(11, 18, 38, 0.92);
1022
+ --editability-surface-soft: rgba(14, 24, 45, 0.9);
1023
+ --editability-border: rgba(148, 163, 184, 0.28);
1024
+ --editability-accent: #5eead4;
1025
+ --editability-accent-strong: #38bdf8;
1026
+ --editability-text: #f8fafc;
1027
+ --editability-muted: #94a3b8;
1028
+ }
1029
+ .editability-authenticated [data-editability-id]:hover { outline: 2px dashed rgba(94, 234, 212, 0.9); outline-offset: 2px; cursor: text; }
1030
+ .editability-authenticated [data-editability-id].editability-dirty { outline: 2px solid rgba(56, 189, 248, 0.9); outline-offset: 2px; }
1031
+ .editability-active.editability-inline p { margin: 0; padding: 0; display: inline; font: inherit; line-height: inherit; }
1032
+ .editability-active.editability-inline p > span,
1033
+ .editability-active.editability-inline span[data-lexical-text] { font: inherit; line-height: inherit; }
1034
+ .editability-active.editability-block.editability-no-paragraphs p { margin: 0; padding: 0; font: inherit; line-height: inherit; }
1035
+ .editability-active.editability-block.editability-no-paragraphs p > span,
1036
+ .editability-active.editability-block.editability-no-paragraphs span[data-lexical-text] { font: inherit; line-height: inherit; }
1037
+ .editability-overlay { position: fixed; inset: auto 0 0 0; z-index: 9999; font-family: "Space Grotesk", ui-sans-serif, system-ui; color: var(--editability-text); }
1038
+ .editability-bar { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 18px; background: linear-gradient(135deg, rgba(11, 16, 32, 0.98), rgba(15, 23, 42, 0.92)); border-top: 1px solid rgba(148, 163, 184, 0.2); box-shadow: 0 -14px 30px rgba(2, 6, 23, 0.5); backdrop-filter: blur(16px); }
1039
+ .editability-left, .editability-center, .editability-right { display: flex; align-items: center; gap: 12px; }
1040
+ .editability-left { min-width: 220px; }
1041
+ .editability-center { flex: 1; flex-wrap: wrap; justify-content: center; }
1042
+ .editability-right { justify-content: flex-end; min-width: 260px; }
1043
+ .editability-brand { display: flex; align-items: center; gap: 8px; }
1044
+ .editability-pill { padding: 4px 12px; background: var(--editability-accent); color: #05121d; border-radius: 999px; font-weight: 700; letter-spacing: 0.2px; }
1045
+ .editability-status { font-size: 12px; opacity: 0.8; }
1046
+ .editability-badge { padding: 4px 10px; border-radius: 999px; border: 1px solid var(--editability-border); font-size: 12px; color: var(--editability-muted); }
1047
+ .editability-badge-active { color: #bae6fd; border-color: rgba(56, 189, 248, 0.6); background: rgba(56, 189, 248, 0.15); }
1048
+ .editability-group { display: flex; align-items: center; gap: 6px; }
1049
+ .editability-divider { width: 1px; height: 26px; background: rgba(148, 163, 184, 0.2); }
1050
+ .editability-btn { display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px; border-radius: 10px; border: 1px solid var(--editability-border); background: var(--editability-surface-soft); color: var(--editability-text); font-size: 12px; font-weight: 600; cursor: pointer; transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease; }
1051
+ .editability-btn:disabled { opacity: 0.45; cursor: not-allowed; }
1052
+ .editability-btn:hover:not(:disabled) { background: rgba(30, 41, 59, 0.85); border-color: rgba(148, 163, 184, 0.45); transform: translateY(-1px); }
1053
+ .editability-btn.active { background: rgba(94, 234, 212, 0.18); border-color: rgba(94, 234, 212, 0.6); color: var(--editability-accent); }
1054
+ .editability-icon-btn { width: 36px; height: 34px; padding: 0; justify-content: center; }
1055
+ .editability-icon { width: 18px; height: 18px; }
1056
+ .editability-block-btn { min-width: 140px; justify-content: space-between; }
1057
+ .editability-primary { background: var(--editability-accent); color: #05121d; border-color: transparent; }
1058
+ .editability-cta { background: rgba(56, 189, 248, 0.2); border-color: rgba(56, 189, 248, 0.5); color: #bae6fd; }
1059
+ .editability-ghost { background: transparent; border-color: rgba(148, 163, 184, 0.2); color: var(--editability-muted); }
636
1060
  .editability-spinner { display: inline-block; width: 12px; height: 12px; border-radius: 999px; border: 2px solid rgba(248, 250, 252, 0.4); border-top-color: #f8fafc; animation: editability-spin 0.8s linear infinite; margin-right: 6px; vertical-align: -2px; }
637
1061
  @keyframes editability-spin { to { transform: rotate(360deg); } }
638
- .editability-user { display: flex; align-items: center; gap: 8px; }
639
- .editability-user img { width: 24px; height: 24px; border-radius: 999px; }
640
- .editability-modal { position: fixed; inset: 0; background: rgba(15, 23, 42, 0.6); display: flex; align-items: center; justify-content: center; z-index: 10000; }
641
- .editability-modal-content { background: #ffffff; color: #0f172a; padding: 24px; border-radius: 16px; width: min(900px, 90vw); max-height: 80vh; overflow: auto; }
1062
+ .editability-popover-anchor { position: relative; }
1063
+ .editability-menu { position: absolute; bottom: calc(100% + 10px); left: 0; top: auto; background: var(--editability-surface); border: 1px solid var(--editability-border); border-radius: 14px; padding: 6px; min-width: 180px; display: flex; flex-direction: column; gap: 4px; box-shadow: 0 18px 30px rgba(2, 6, 23, 0.45); z-index: 10001; }
1064
+ .editability-menu-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 10px; border-radius: 10px; border: 1px solid transparent; background: transparent; color: var(--editability-text); font-size: 12px; font-weight: 600; text-align: left; cursor: pointer; }
1065
+ .editability-menu-item:hover { background: rgba(148, 163, 184, 0.12); }
1066
+ .editability-menu-item.active { background: rgba(94, 234, 212, 0.2); border-color: rgba(94, 234, 212, 0.5); color: var(--editability-accent); }
1067
+ .editability-popover { position: absolute; bottom: calc(100% + 10px); right: 0; top: auto; min-width: 240px; background: var(--editability-surface); border: 1px solid var(--editability-border); border-radius: 14px; padding: 12px; box-shadow: 0 18px 30px rgba(2, 6, 23, 0.45); display: flex; flex-direction: column; gap: 8px; z-index: 10001; }
1068
+ .editability-popover-title { font-size: 12px; font-weight: 600; color: var(--editability-muted); }
1069
+ .editability-input { padding: 8px 10px; border-radius: 10px; border: 1px solid rgba(148, 163, 184, 0.3); background: rgba(15, 23, 42, 0.6); color: var(--editability-text); font-size: 13px; }
1070
+ .editability-input:focus { outline: none; border-color: rgba(94, 234, 212, 0.7); box-shadow: 0 0 0 3px rgba(94, 234, 212, 0.15); }
1071
+ .editability-popover-actions { display: flex; gap: 8px; justify-content: flex-end; }
1072
+ .editability-user { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: 999px; background: rgba(148, 163, 184, 0.12); }
1073
+ .editability-user img { width: 26px; height: 26px; border-radius: 999px; }
1074
+ .editability-modal { position: fixed; inset: 0; background: rgba(11, 16, 32, 0.7); display: flex; align-items: center; justify-content: center; z-index: 10000; }
1075
+ .editability-modal-content { background: #ffffff; color: #0f172a; padding: 24px; border-radius: 18px; width: min(900px, 92vw); max-height: 80vh; overflow: auto; }
642
1076
  .editability-change { border-bottom: 1px solid #e2e8f0; padding: 12px 0; }
643
1077
  .editability-change-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
644
- .editability-preview { background: #f8fafc; border-radius: 8px; padding: 12px; min-height: 40px; }
1078
+ .editability-preview { background: #f8fafc; border-radius: 10px; padding: 12px; min-height: 40px; }
645
1079
  .editability-diff { margin-top: 8px; font-size: 12px; }
646
1080
  .editability-diff span.added { background: #bbf7d0; }
647
1081
  .editability-diff span.removed { background: #fecaca; text-decoration: line-through; }
648
1082
  .editability-muted { opacity: 0.7; }
649
1083
  .editability-label { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
650
- .editability-label input { padding: 8px; border-radius: 6px; border: 1px solid #cbd5f5; }
1084
+ .editability-label input { padding: 8px; border-radius: 8px; border: 1px solid #cbd5f5; }
651
1085
  .editability-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
652
1086
  .editability-error { color: #dc2626; }
653
- .editability-login { position: fixed; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background: rgba(15, 23, 42, 0.8); color: #f8fafc; padding: 24px; text-align: center; }
654
- .editability-login h2 { margin: 0; font-size: 20px; }
1087
+ .editability-error-inline { font-size: 12px; color: #fecaca; }
1088
+ .editability-login { position: fixed; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background: radial-gradient(circle at top, rgba(56, 189, 248, 0.2), rgba(11, 16, 32, 0.95)); color: #f8fafc; padding: 24px; text-align: center; }
1089
+ .editability-login h2 { margin: 0; font-size: 22px; }
655
1090
  .editability-login p { margin: 0; max-width: 360px; opacity: 0.9; }
1091
+ @media (max-width: 900px) {
1092
+ .editability-bar { flex-direction: column; align-items: stretch; }
1093
+ .editability-left, .editability-right { justify-content: space-between; }
1094
+ .editability-center { justify-content: flex-start; }
1095
+ .editability-right { flex-wrap: wrap; }
1096
+ }
656
1097
  `;
657
1098
  document.head.appendChild(style);
658
1099
  }
@@ -671,4 +1112,40 @@ function loadGoogleScript() {
671
1112
  document.head.appendChild(script);
672
1113
  });
673
1114
  }
1115
+ function Icon({ children, viewBox = "0 0 24 24", }) {
1116
+ return (_jsx("svg", { className: "editability-icon", viewBox: viewBox, "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: children }));
1117
+ }
1118
+ function IconUndo() {
1119
+ return (_jsxs(Icon, { children: [_jsx("path", { d: "M7 7l-4 5 4 5" }), _jsx("path", { d: "M3 12h9a6 6 0 0 1 0 12h-2" })] }));
1120
+ }
1121
+ function IconRedo() {
1122
+ return (_jsxs(Icon, { children: [_jsx("path", { d: "M17 7l4 5-4 5" }), _jsx("path", { d: "M21 12h-9a6 6 0 0 0 0 12h2" })] }));
1123
+ }
1124
+ function IconChevron() {
1125
+ return (_jsx(Icon, { children: _jsx("path", { d: "M6 9l6 6 6-6" }) }));
1126
+ }
1127
+ function IconBold() {
1128
+ return (_jsxs(Icon, { children: [_jsx("path", { d: "M8 5h6a3 3 0 0 1 0 6H8z" }), _jsx("path", { d: "M8 11h7a3 3 0 0 1 0 6H8z" })] }));
1129
+ }
1130
+ function IconItalic() {
1131
+ return (_jsxs(Icon, { children: [_jsx("path", { d: "M10 5h8" }), _jsx("path", { d: "M6 19h8" }), _jsx("path", { d: "M14 5l-4 14" })] }));
1132
+ }
1133
+ function IconLink() {
1134
+ return (_jsxs(Icon, { children: [_jsx("path", { d: "M10 13a5 5 0 0 1 0-7l1.5-1.5a5 5 0 1 1 7 7L17 12" }), _jsx("path", { d: "M14 11a5 5 0 0 1 0 7L12.5 19.5a5 5 0 0 1-7-7L7 11" })] }));
1135
+ }
1136
+ function IconListBullet() {
1137
+ return (_jsxs(Icon, { children: [_jsx("circle", { cx: "5", cy: "7", r: "1.5", fill: "currentColor", stroke: "none" }), _jsx("circle", { cx: "5", cy: "12", r: "1.5", fill: "currentColor", stroke: "none" }), _jsx("circle", { cx: "5", cy: "17", r: "1.5", fill: "currentColor", stroke: "none" }), _jsx("path", { d: "M9 7h10" }), _jsx("path", { d: "M9 12h10" }), _jsx("path", { d: "M9 17h10" })] }));
1138
+ }
1139
+ function IconListNumber() {
1140
+ return (_jsxs(Icon, { children: [_jsx("text", { x: "3", y: "9", fontSize: "7", fontWeight: "600", fontFamily: "inherit", fill: "currentColor", stroke: "none", children: "1" }), _jsx("text", { x: "3", y: "17", fontSize: "7", fontWeight: "600", fontFamily: "inherit", fill: "currentColor", stroke: "none", children: "2" }), _jsx("path", { d: "M9 7h10" }), _jsx("path", { d: "M9 12h10" }), _jsx("path", { d: "M9 17h10" })] }));
1141
+ }
1142
+ function IconParagraph() {
1143
+ return (_jsxs(Icon, { children: [_jsx("text", { x: "4", y: "16", fontSize: "9", fontWeight: "600", fontFamily: "inherit", fill: "currentColor", stroke: "none", children: "P" }), _jsx("path", { d: "M12 8h8" }), _jsx("path", { d: "M12 12h8" }), _jsx("path", { d: "M12 16h8" })] }));
1144
+ }
1145
+ function IconHeading({ label }) {
1146
+ return (_jsxs(Icon, { children: [_jsx("text", { x: "3", y: "15", fontSize: "8", fontWeight: "700", fontFamily: "inherit", fill: "currentColor", stroke: "none", children: label }), _jsx("path", { d: "M12 8h8" }), _jsx("path", { d: "M12 12h8" }), _jsx("path", { d: "M12 16h8" })] }));
1147
+ }
1148
+ function IconQuote() {
1149
+ return (_jsxs(Icon, { children: [_jsx("path", { d: "M6 8h5v5H6z" }), _jsx("path", { d: "M13 8h5v5h-5z" }), _jsx("path", { d: "M6 13a4 4 0 0 0 4 4" }), _jsx("path", { d: "M13 13a4 4 0 0 0 4 4" })] }));
1150
+ }
674
1151
  //# sourceMappingURL=overlay.js.map