@x12i/editor 1.0.0 → 1.1.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/dist/react.js ADDED
@@ -0,0 +1,4439 @@
1
+ "use client";
2
+ import {
3
+ BLOCK_ID_NODES,
4
+ CALLOUT_VARIANTS,
5
+ HIGHLIGHT_TOKENS,
6
+ TEXT_COLOR_TOKENS,
7
+ buildOutlineTree,
8
+ createEmptyDocument,
9
+ documentToMarkdown,
10
+ documentToTiptap,
11
+ emojiForShortcode,
12
+ generateBlockId,
13
+ getOutline,
14
+ inlineToPlainText,
15
+ isHighlightToken,
16
+ isTextColorToken,
17
+ normalizeUrlInput,
18
+ sanitizeMediaUrl,
19
+ sanitizeUrl,
20
+ slugifyHeading,
21
+ tiptapToDocument
22
+ } from "./chunk-FYXMOAJT.js";
23
+
24
+ // src/react/editor.tsx
25
+ import { useCallback as useCallback3, useEffect as useEffect10, useImperativeHandle, useMemo as useMemo3, useRef as useRef13, useState as useState12 } from "react";
26
+ import { EditorContent, useEditor } from "@tiptap/react";
27
+ import { exitSuggestion } from "@tiptap/suggestion";
28
+
29
+ // src/react/ai-assist-menu.tsx
30
+ import { useEffect, useRef, useState } from "react";
31
+ import { Button, Spinner } from "@x12i/ui";
32
+
33
+ // src/react/internal/glyphs.tsx
34
+ import { jsx } from "react/jsx-runtime";
35
+ var PATHS = {
36
+ undo: "M6 3 3 6l3 3 M3 6h6a4 4 0 0 1 0 8H6",
37
+ redo: "m10 3 3 3-3 3 M13 6H7a4 4 0 0 0 0 8h3",
38
+ bold: "M4 2.5h4.5a2.5 2.5 0 0 1 0 5H4z M4 7.5h5a2.75 2.75 0 0 1 0 5.5H4z",
39
+ italic: "M7 3h5 M4 13h5 M10 3 6 13",
40
+ underline: "M4 2.5V8a4 4 0 0 0 8 0V2.5 M3 14h10",
41
+ strike: "M3 8h10 M10.8 5.2C10.5 3.9 9.4 3 8 3 6.3 3 5.2 4 5.2 5.2c0 1 .6 1.7 1.8 2 M5.2 10.8C5.5 12.1 6.6 13 8 13c1.7 0 2.8-1 2.8-2.2 0-.4-.1-.7-.3-1",
42
+ code: "m6 4-4 4 4 4 M10 4l4 4-4 4",
43
+ highlight: "m9 2.5 4.5 4.5-5 5H5.5V8.5z M3 14h10",
44
+ color: "M4.5 11 8 3l3.5 8 M5.7 8.5h4.6 M3 14.5h10",
45
+ link: "M6.8 9.2a3 3 0 0 0 4.2 0l2-2a3 3 0 0 0-4.2-4.2l-1 1 M9.2 6.8a3 3 0 0 0-4.2 0l-2 2A3 3 0 0 0 7.200 13l1-1",
46
+ bullets: "M6 4h8 M6 8h8 M6 12h8 M2.5 4h.1 M2.5 8h.1 M2.5 12h.1",
47
+ numbers: "M6.5 4h7.500 M6.5 8h7.500 M6.5 12h7.500 M2 3.200l1-.7V6 M2 9.700c0-1.100 2-1.100 2 0 0 1-2 1.500-2 2.300h2",
48
+ tasks: "m2 3.500 1.200 1.200L5.200 2.800 M2 9l1.200 1.200L5.200 8.300 M7.500 4h6.500 M7.500 9.500h6.500",
49
+ indent: "M2 3h12 M8 7h6 M8 11h6 M2 14h12 M2 6l3 2.500L2 11",
50
+ outdent: "M2 3h12 M8 7h6 M8 11h6 M2 14h12 M5 6 2 8.500 5 11",
51
+ quote: "M3 3.500v9 M7 5h7 M7 8h7 M7 11h4.500",
52
+ divider: "M2 8h12 M5 4.500h.1 M8 4.500h.1 M11 4.500h.1 M5 11.500h.1 M8 11.500h.1 M11 11.500h.1",
53
+ table: "M2 3h12v10H2z M2 6.500h12 M2 9.800h12 M6 3v10 M10 3v10",
54
+ image: "M2 3h12v10H2z m0 8 4-4 3 3 2-2 3 3 M10.500 5.800h.1",
55
+ up: "M8 13V3 M4 7l4-4 4 4",
56
+ down: "M8 3v10 M4 9l4 4 4-4",
57
+ duplicate: "M5.500 5.500h8v8h-8z M10.500 5.500v-3h-8v8h3",
58
+ more: "M3.500 8h.1 M8 8h.1 M12.500 8h.1",
59
+ heading: "M3.500 3v10 M12.500 3v10 M3.500 8h9",
60
+ grip: "M6 3.500h.1 M10 3.500h.1 M6 8h.1 M10 8h.1 M6 12.500h.1 M10 12.500h.1",
61
+ chevron: "m4 6 4 4 4-4",
62
+ check: "m3.500 8.500 3 3 6-7",
63
+ close: "m4 4 8 8 M12 4l-8 8",
64
+ copy: "M5.500 5.500h8v8h-8z M10.500 5.500v-3h-8v8h3",
65
+ trash: "M3 4.500h10 M6 4.500v-2h4v2 M4.500 4.500l.6 9h5.800l.6-9 M7 7v4 M9 7v4",
66
+ edit: "m3 13 .8-3L11 2.800a1.400 1.400 0 0 1 2 2L5.800 12z M9.800 4l2 2",
67
+ plus: "M8 3v10 M3 8h10",
68
+ sparkle: "M8 2l1.200 3.800L13 7l-3.800 1.200L8 12l-1.200-3.800L3 7l3.800-1.200z",
69
+ info: "M8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z M8 7.500V11 M8 5h.1",
70
+ success: "M8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z m-2.500-6 2 2 3-3.500",
71
+ warning: "M8 2.500 14 13H2z M8 7v3 M8 11.500h.1",
72
+ danger: "M8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z m-2-8 4 4 m0-4-4 4",
73
+ neutral: "M3 3h10v10H3z M5.500 6h5 M5.500 8h5 M5.500 10h3",
74
+ file: "M4 2h5l3 3v9H4z M9 2v3h3",
75
+ bookmark: "M3 8h3 M10 8h3 M6 5h4a3 3 0 0 1 0 6H6a3 3 0 0 1 0-6z",
76
+ chevronEnd: "m6 4 4 4-4 4",
77
+ unlink: "M6.800 9.200a3 3 0 0 0 4.200 0l2-2a3 3 0 0 0-4.200-4.200l-1 1 M9.200 6.800a3 3 0 0 0-4.200 0l-2 2A3 3 0 0 0 7.200 13l1-1 M2 2l12 12"
78
+ };
79
+ function Glyph({ name, ...props }) {
80
+ return /* @__PURE__ */ jsx(
81
+ "svg",
82
+ {
83
+ "data-kit": "editor-glyph",
84
+ viewBox: "0 0 16 16",
85
+ fill: "none",
86
+ stroke: "currentColor",
87
+ strokeWidth: 1.5,
88
+ strokeLinecap: "round",
89
+ strokeLinejoin: "round",
90
+ "aria-hidden": "true",
91
+ focusable: "false",
92
+ ...props,
93
+ children: /* @__PURE__ */ jsx("path", { d: PATHS[name] })
94
+ }
95
+ );
96
+ }
97
+
98
+ // src/react/messages.ts
99
+ import { createContext, useContext } from "react";
100
+ var BLOCK_KINDS = [
101
+ "paragraph",
102
+ "heading1",
103
+ "heading2",
104
+ "heading3",
105
+ "heading4",
106
+ "quote",
107
+ "callout",
108
+ "toggle",
109
+ "bulletList",
110
+ "numberedList",
111
+ "taskList",
112
+ "code",
113
+ "equation",
114
+ "divider",
115
+ "table",
116
+ "image",
117
+ "video",
118
+ "audio",
119
+ "file",
120
+ "bookmark",
121
+ "columns",
122
+ "pageBreak",
123
+ "toc",
124
+ "chart",
125
+ "diagram",
126
+ "dataView",
127
+ "ai"
128
+ ];
129
+ var defaultMessages = {
130
+ editorLabel: "Document editor",
131
+ placeholder: "Write something, or press / for blocks",
132
+ viewLabel: "Document",
133
+ toolbar: {
134
+ label: "Formatting",
135
+ undo: "Undo",
136
+ redo: "Redo",
137
+ blockType: "Block type",
138
+ bold: "Bold",
139
+ italic: "Italic",
140
+ underline: "Underline",
141
+ strike: "Strikethrough",
142
+ code: "Inline code",
143
+ highlight: "Highlight",
144
+ textColor: "Text color",
145
+ clearColor: "Default color",
146
+ link: "Link",
147
+ bulletList: "Bulleted list",
148
+ numberedList: "Numbered list",
149
+ taskList: "Task list",
150
+ indent: "Increase indent",
151
+ outdent: "Decrease indent",
152
+ quote: "Quote",
153
+ divider: "Divider",
154
+ table: "Insert table",
155
+ image: "Insert image",
156
+ selectionMenu: "Text formatting"
157
+ },
158
+ tokens: {
159
+ "color.text.secondary": "Gray",
160
+ "color.text.link": "Link blue",
161
+ "color.accent.default": "Accent",
162
+ "color.status.success.foreground": "Green",
163
+ "color.status.warning.foreground": "Orange",
164
+ "color.status.danger.foreground": "Red",
165
+ "color.status.info.foreground": "Blue",
166
+ "color.chart.categorical.4": "Magenta",
167
+ "color.highlight": "Yellow",
168
+ "color.accent.subtle": "Accent",
169
+ "color.status.success.background": "Green",
170
+ "color.status.warning.background": "Orange",
171
+ "color.status.danger.background": "Red",
172
+ "color.status.info.background": "Blue"
173
+ },
174
+ blocks: {
175
+ paragraph: { name: "Text", description: "Plain paragraph text", keywords: "paragraph plain body" },
176
+ heading1: { name: "Heading 1", description: "Large section heading", keywords: "h1 title header" },
177
+ heading2: { name: "Heading 2", description: "Medium section heading", keywords: "h2 subtitle header" },
178
+ heading3: { name: "Heading 3", description: "Small section heading", keywords: "h3 header" },
179
+ heading4: { name: "Heading 4", description: "Smallest section heading", keywords: "h4 header" },
180
+ quote: { name: "Quote", description: "Quote another voice", keywords: "blockquote citation" },
181
+ callout: { name: "Callout", description: "Highlight a note or warning", keywords: "note alert info warning tip admonition" },
182
+ toggle: { name: "Toggle", description: "Content that expands and collapses", keywords: "collapse details accordion expand" },
183
+ bulletList: { name: "Bulleted list", description: "A simple list of points", keywords: "ul unordered bullets" },
184
+ numberedList: { name: "Numbered list", description: "A list with numbers", keywords: "ol ordered steps" },
185
+ taskList: { name: "Task list", description: "Track to-dos with checkboxes", keywords: "todo checklist checkbox" },
186
+ code: { name: "Code", description: "A block of code with a language", keywords: "snippet pre programming" },
187
+ equation: { name: "Equation", description: "A formula written in LaTeX", keywords: "math latex formula" },
188
+ divider: { name: "Divider", description: "A horizontal rule between sections", keywords: "hr rule line separator" },
189
+ table: { name: "Table", description: "Rows and columns of data", keywords: "grid rows columns" },
190
+ image: { name: "Image", description: "An image with alternative text", keywords: "picture photo figure" },
191
+ video: { name: "Video", description: "Embed a video by URL", keywords: "movie clip" },
192
+ audio: { name: "Audio", description: "Embed audio by URL", keywords: "sound podcast" },
193
+ file: { name: "File", description: "Link to a downloadable file", keywords: "attachment download pdf" },
194
+ bookmark: { name: "Bookmark", description: "A link shown as a card", keywords: "link url website card" },
195
+ columns: { name: "Columns", description: "Side by side blocks", keywords: "layout two three" },
196
+ pageBreak: { name: "Page break", description: "Start a new page when printing", keywords: "print pdf" },
197
+ toc: { name: "Table of contents", description: "Links to every heading", keywords: "outline toc navigation" },
198
+ chart: { name: "Chart", description: "A chart built from data", keywords: "graph plot bar line pie" },
199
+ diagram: { name: "Diagram", description: "A flow or system diagram", keywords: "flowchart mermaid graph" },
200
+ dataView: { name: "Data view", description: "A saved view of a dataset", keywords: "table grid records query" },
201
+ ai: { name: "AI block", description: "Ask AI to write or summarize", keywords: "assistant generate gpt llm" }
202
+ },
203
+ groups: {
204
+ basic: "Basic blocks",
205
+ lists: "Lists",
206
+ media: "Media",
207
+ advanced: "Advanced",
208
+ embeds: "Embeds"
209
+ },
210
+ callouts: {
211
+ info: "Info",
212
+ success: "Success",
213
+ warning: "Warning",
214
+ danger: "Danger",
215
+ neutral: "Note"
216
+ },
217
+ slash: {
218
+ label: "Insert a block",
219
+ noResults: "No matching blocks",
220
+ resultCount: "{count, plural, one {# block available} other {# blocks available}}",
221
+ instructions: "Type to filter, use the arrow keys to choose, Enter to insert, Escape to close"
222
+ },
223
+ blockMenu: {
224
+ label: "Block actions",
225
+ trigger: "Block actions (Ctrl+/)",
226
+ duplicate: "Duplicate",
227
+ delete: "Delete",
228
+ turnInto: "Turn into",
229
+ moveUp: "Move up",
230
+ moveDown: "Move down",
231
+ insertBelow: "Add paragraph below"
232
+ },
233
+ table: {
234
+ addRowBefore: "Add row above",
235
+ addRowAfter: "Add row below",
236
+ addColumnBefore: "Add column before",
237
+ addColumnAfter: "Add column after",
238
+ deleteRow: "Delete row",
239
+ deleteColumn: "Delete column",
240
+ toggleHeaderRow: "Header row",
241
+ toggleHeaderColumn: "Header column",
242
+ deleteTable: "Delete table",
243
+ controls: "Table controls"
244
+ },
245
+ linkDialog: {
246
+ title: "Link",
247
+ urlLabel: "Link address",
248
+ urlHint: "Use http, https, mailto, tel, or a relative path",
249
+ invalid: "This address is not allowed. Use http, https, mailto, tel or a relative path.",
250
+ required: "Enter a link address.",
251
+ apply: "Apply",
252
+ remove: "Remove link",
253
+ cancel: "Cancel"
254
+ },
255
+ imageDialog: {
256
+ title: "Insert image",
257
+ editTitle: "Edit image",
258
+ urlLabel: "Image address",
259
+ altLabel: "Alternative text",
260
+ altHint: "Describe the image for people who cannot see it",
261
+ altRequired: "Describe the image, or mark it as decorative.",
262
+ decorative: "This image is decorative and needs no description",
263
+ captionLabel: "Caption (optional)",
264
+ urlRequired: "Enter an image address.",
265
+ urlInvalid: "This address is not allowed. Use http, https, a relative path, or a data:image URL.",
266
+ insert: "Insert",
267
+ update: "Update",
268
+ cancel: "Cancel"
269
+ },
270
+ code: {
271
+ language: "Code language",
272
+ plainText: "Plain text",
273
+ copy: "Copy code",
274
+ copied: "Copied"
275
+ },
276
+ task: {
277
+ done: "Mark as not done: {text}",
278
+ todo: "Mark as done: {text}",
279
+ emptyLabel: "empty task"
280
+ },
281
+ toggle: {
282
+ expand: "Expand",
283
+ collapse: "Collapse",
284
+ summaryPlaceholder: "Toggle title"
285
+ },
286
+ equation: {
287
+ label: "LaTeX equation",
288
+ placeholder: "Type LaTeX, for example E = mc^2"
289
+ },
290
+ embed: {
291
+ groupLabel: "{type}: {title}",
292
+ untitled: "Untitled",
293
+ edit: "Edit",
294
+ remove: "Remove",
295
+ moveUp: "Move up",
296
+ moveDown: "Move down",
297
+ toolbar: "Block tools",
298
+ request: "Choose content for this block",
299
+ previewHint: "Preview of the block data. Connect a renderer to draw it.",
300
+ showData: "Show data",
301
+ dataset: "Dataset",
302
+ prompt: "Prompt",
303
+ result: "Result",
304
+ engine: "Engine",
305
+ unknown: "This block type is not supported here. Its data is kept."
306
+ },
307
+ media: {
308
+ file: "File",
309
+ download: "Download {name}",
310
+ bookmark: "Bookmark",
311
+ open: "Open {title}",
312
+ unsafe: "This address is not allowed",
313
+ image: "Image",
314
+ imageDecorative: "Decorative image",
315
+ imageEdit: "Edit image",
316
+ video: "Video",
317
+ audio: "Audio",
318
+ urlLabel: "Address",
319
+ nameLabel: "Name",
320
+ set: "Set"
321
+ },
322
+ ai: {
323
+ menuLabel: "AI assistant",
324
+ improve: "Improve writing",
325
+ shorten: "Make shorter",
326
+ continue: "Continue writing",
327
+ fixGrammar: "Fix spelling and grammar",
328
+ custom: "Ask AI",
329
+ customLabel: "Instruction",
330
+ run: "Run",
331
+ accept: "Accept",
332
+ reject: "Reject",
333
+ cancel: "Stop",
334
+ generating: "Writing a suggestion",
335
+ preview: "Suggestion",
336
+ error: "The assistant could not finish: {message}",
337
+ noSelection: "Select some text first",
338
+ done: "Suggestion ready",
339
+ prompts: {
340
+ improve: "Improve the clarity and flow of the text while keeping its meaning",
341
+ shorten: "Make the text shorter while keeping its meaning",
342
+ continue: "Continue writing from where the text ends"
343
+ }
344
+ },
345
+ status: {
346
+ saved: "Saved",
347
+ markOn: "{name} on",
348
+ markOff: "{name} off",
349
+ blockMoved: "Block moved to position {position} of {total}",
350
+ blockMovedUp: "Block moved up",
351
+ blockMovedDown: "Block moved down",
352
+ blockDuplicated: "Block duplicated",
353
+ blockDeleted: "Block deleted",
354
+ blockTurnedInto: "Turned into {name}",
355
+ linkApplied: "Link applied",
356
+ linkRemoved: "Link removed",
357
+ imageInserted: "Image inserted",
358
+ copied: "Copied to clipboard",
359
+ undone: "Undone",
360
+ redone: "Redone",
361
+ aiApplied: "Suggestion applied",
362
+ cannotMove: "This block cannot move further"
363
+ },
364
+ directions: { up: "up", down: "down" }
365
+ };
366
+ function isPlainObject(value) {
367
+ return typeof value === "object" && value !== null && !Array.isArray(value);
368
+ }
369
+ function mergeMessages(base, overrides) {
370
+ if (!overrides) return base;
371
+ const merge = (target, source) => {
372
+ const result = { ...target };
373
+ for (const [key, value] of Object.entries(source)) {
374
+ if (value === void 0) continue;
375
+ const current = result[key];
376
+ result[key] = isPlainObject(value) && isPlainObject(current) ? merge(current, value) : value;
377
+ }
378
+ return result;
379
+ };
380
+ return merge(base, overrides);
381
+ }
382
+ function matchBrace(text, start) {
383
+ let depth = 0;
384
+ for (let index = start; index < text.length; index += 1) {
385
+ if (text[index] === "{") depth += 1;
386
+ else if (text[index] === "}") {
387
+ depth -= 1;
388
+ if (depth === 0) return index;
389
+ }
390
+ }
391
+ return -1;
392
+ }
393
+ function formatMessage(template, values = {}, locale) {
394
+ let out = "";
395
+ let index = 0;
396
+ while (index < template.length) {
397
+ const char = template[index];
398
+ if (char !== "{") {
399
+ out += char;
400
+ index += 1;
401
+ continue;
402
+ }
403
+ const end = matchBrace(template, index);
404
+ if (end === -1) {
405
+ out += template.slice(index);
406
+ break;
407
+ }
408
+ const inner = template.slice(index + 1, end);
409
+ const plural = /^\s*([\w-]+)\s*,\s*plural\s*,([\s\S]*)$/.exec(inner);
410
+ if (plural) {
411
+ const name = plural[1];
412
+ const count = Number(values[name] ?? 0);
413
+ const branches = /* @__PURE__ */ new Map();
414
+ const body = plural[2];
415
+ let cursor = 0;
416
+ while (cursor < body.length) {
417
+ const selector = /^\s*(=\d+|[a-z]+)\s*\{/.exec(body.slice(cursor));
418
+ if (!selector) break;
419
+ const open = cursor + selector[0].length - 1;
420
+ const close = matchBrace(body, open);
421
+ if (close === -1) break;
422
+ branches.set(selector[1], body.slice(open + 1, close));
423
+ cursor = close + 1;
424
+ }
425
+ let category;
426
+ try {
427
+ category = new Intl.PluralRules(locale).select(count);
428
+ } catch {
429
+ category = count === 1 ? "one" : "other";
430
+ }
431
+ const chosen = branches.get(`=${count}`) ?? branches.get(category) ?? branches.get("other") ?? "";
432
+ out += formatMessage(chosen.replace(/#/g, String(count)), values, locale);
433
+ } else {
434
+ const name = inner.trim();
435
+ out += name in values ? String(values[name]) : `{${inner}}`;
436
+ }
437
+ index = end + 1;
438
+ }
439
+ return out;
440
+ }
441
+ function createTranslator(messages, locale) {
442
+ return {
443
+ messages,
444
+ locale,
445
+ format: (template, values) => formatMessage(template, values, locale)
446
+ };
447
+ }
448
+ var MessagesContext = createContext(createTranslator(defaultMessages));
449
+ function useTranslator() {
450
+ return useContext(MessagesContext);
451
+ }
452
+ function useMessages() {
453
+ return useContext(MessagesContext).messages;
454
+ }
455
+
456
+ // src/react/ai-assist-menu.tsx
457
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
458
+ function AiAssistMenu({ assist, editor, onClose }) {
459
+ const t = useTranslator();
460
+ const m = t.messages.ai;
461
+ const firstRef = useRef(null);
462
+ const acceptRef = useRef(null);
463
+ const [instruction, setInstruction] = useState("");
464
+ const { status } = assist;
465
+ useEffect(() => {
466
+ firstRef.current?.focus();
467
+ }, []);
468
+ useEffect(() => {
469
+ if (status === "preview") acceptRef.current?.focus();
470
+ }, [status]);
471
+ const close = () => {
472
+ assist.reject();
473
+ onClose();
474
+ editor.commands.focus();
475
+ };
476
+ const custom = (event) => {
477
+ event.preventDefault();
478
+ const text = instruction.trim();
479
+ if (text !== "") void assist.run("rewrite", { tone: text });
480
+ };
481
+ const busy = status === "running";
482
+ return /* @__PURE__ */ jsx2(
483
+ "div",
484
+ {
485
+ role: "group",
486
+ "aria-label": m.menuLabel,
487
+ "data-kit": "editor-ai",
488
+ "data-status": status,
489
+ onKeyDown: (event) => {
490
+ if (event.key === "Escape") {
491
+ event.preventDefault();
492
+ event.stopPropagation();
493
+ close();
494
+ }
495
+ },
496
+ children: status === "idle" || status === "error" ? /* @__PURE__ */ jsxs(Fragment, { children: [
497
+ /* @__PURE__ */ jsxs("div", { "data-kit": "editor-ai-actions", children: [
498
+ /* @__PURE__ */ jsx2(Button, { ref: firstRef, type: "button", size: "sm", variant: "soft", tone: "neutral", onMouseDown: (event) => event.preventDefault(), onClick: () => void assist.run("rewrite", { tone: m.prompts.improve }), children: m.improve }),
499
+ /* @__PURE__ */ jsx2(Button, { type: "button", size: "sm", variant: "soft", tone: "neutral", onMouseDown: (event) => event.preventDefault(), onClick: () => void assist.run("summarize", { tone: m.prompts.shorten }), children: m.shorten }),
500
+ /* @__PURE__ */ jsx2(Button, { type: "button", size: "sm", variant: "soft", tone: "neutral", onMouseDown: (event) => event.preventDefault(), onClick: () => void assist.run("continue", { tone: m.prompts.continue }), children: m.continue }),
501
+ /* @__PURE__ */ jsx2(Button, { type: "button", size: "sm", variant: "soft", tone: "neutral", onMouseDown: (event) => event.preventDefault(), onClick: () => void assist.run("fix-grammar"), children: m.fixGrammar })
502
+ ] }),
503
+ /* @__PURE__ */ jsxs("form", { "data-kit": "editor-ai-custom", onSubmit: custom, children: [
504
+ /* @__PURE__ */ jsxs("label", { "data-kit": "editor-field", children: [
505
+ /* @__PURE__ */ jsx2("span", { "data-kit": "editor-field-label", children: m.customLabel }),
506
+ /* @__PURE__ */ jsx2("input", { "data-kit": "editor-input", type: "text", dir: "auto", value: instruction, onChange: (event) => setInstruction(event.target.value) })
507
+ ] }),
508
+ /* @__PURE__ */ jsx2(Button, { type: "submit", size: "sm", variant: "solid", tone: "accent", disabled: instruction.trim() === "", children: m.run })
509
+ ] }),
510
+ status === "error" && assist.error ? /* @__PURE__ */ jsx2("p", { role: "alert", "data-kit": "editor-error", children: assist.error }) : null
511
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
512
+ /* @__PURE__ */ jsxs("div", { "data-kit": "editor-ai-preview", "aria-busy": busy, children: [
513
+ /* @__PURE__ */ jsxs("p", { "data-kit": "editor-ai-preview-label", children: [
514
+ busy ? /* @__PURE__ */ jsx2(Spinner, { size: "xs", label: m.generating }) : /* @__PURE__ */ jsx2(Glyph, { name: "sparkle" }),
515
+ /* @__PURE__ */ jsx2("span", { children: busy ? m.generating : m.preview })
516
+ ] }),
517
+ /* @__PURE__ */ jsx2("div", { "data-kit": "editor-ai-preview-text", dir: "auto", tabIndex: 0, role: "region", "aria-label": m.preview, children: assist.preview })
518
+ ] }),
519
+ /* @__PURE__ */ jsxs("div", { "data-kit": "editor-ai-actions", children: [
520
+ /* @__PURE__ */ jsx2(Button, { ref: acceptRef, type: "button", size: "sm", variant: "solid", tone: "accent", disabled: busy, onClick: () => {
521
+ assist.accept();
522
+ onClose();
523
+ }, children: m.accept }),
524
+ busy ? /* @__PURE__ */ jsx2(Button, { type: "button", size: "sm", variant: "outline", tone: "neutral", onClick: assist.cancel, children: m.cancel }) : null,
525
+ /* @__PURE__ */ jsx2(Button, { type: "button", size: "sm", variant: "ghost", tone: "neutral", onClick: close, children: m.reject })
526
+ ] })
527
+ ] })
528
+ }
529
+ );
530
+ }
531
+
532
+ // src/react/block-menu.tsx
533
+ import { useState as useState3 } from "react";
534
+
535
+ // src/react/commands.ts
536
+ import { NodeSelection, Selection, TextSelection } from "@tiptap/pm/state";
537
+ var CONTAINERS = /* @__PURE__ */ new Set(["doc", "toggle", "column"]);
538
+ var ITEMS = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
539
+ var LISTS = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
540
+ function isMovable(node, parent) {
541
+ const name = node.type.name;
542
+ if (ITEMS.has(name)) return LISTS.has(parent.type.name);
543
+ return BLOCK_ID_NODES.includes(name) && CONTAINERS.has(parent.type.name);
544
+ }
545
+ function blockAt(state, pos) {
546
+ if (pos < 0 || pos > state.doc.content.size) return null;
547
+ const $pos = state.doc.resolve(pos);
548
+ const node = $pos.nodeAfter;
549
+ if (!node) return null;
550
+ return { pos, node, parent: $pos.parent, index: $pos.index() };
551
+ }
552
+ function findBlock(state, options = {}) {
553
+ const items = options.items ?? true;
554
+ if (options.at !== void 0) return blockAt(state, options.at);
555
+ const { selection } = state;
556
+ if (selection instanceof NodeSelection) {
557
+ const parent = selection.$from.parent;
558
+ if (isMovable(selection.node, parent) && (items || !ITEMS.has(selection.node.type.name))) {
559
+ return { pos: selection.from, node: selection.node, parent, index: selection.$from.index() };
560
+ }
561
+ }
562
+ const { $from } = selection;
563
+ for (let depth = $from.depth; depth >= 1; depth -= 1) {
564
+ const node = $from.node(depth);
565
+ const parent = $from.node(depth - 1);
566
+ if (!isMovable(node, parent)) continue;
567
+ if (!items && ITEMS.has(node.type.name)) continue;
568
+ return { pos: $from.before(depth), node, parent, index: $from.index(depth - 1) };
569
+ }
570
+ return null;
571
+ }
572
+ function moveBlock(editor, direction, at) {
573
+ const { state, view } = editor;
574
+ const location = findBlock(state, at === void 0 ? {} : { at });
575
+ const none = { moved: false, position: location ? location.index + 1 : 0, total: location ? location.parent.childCount : 0 };
576
+ if (!location) return none;
577
+ const { pos, node, parent, index } = location;
578
+ const siblingIndex = index + direction;
579
+ if (siblingIndex < 0 || siblingIndex >= parent.childCount) return none;
580
+ const sibling = parent.child(siblingIndex);
581
+ if (sibling.type.name === "toggleSummary" || node.type.name === "toggleSummary") return none;
582
+ const { selection } = state;
583
+ const wasNode = selection instanceof NodeSelection && selection.from === pos;
584
+ const offset = Math.max(0, selection.from - pos);
585
+ const tr = state.tr;
586
+ let newPos;
587
+ if (direction < 0) {
588
+ newPos = pos - sibling.nodeSize;
589
+ tr.delete(pos, pos + node.nodeSize).insert(newPos, node);
590
+ } else {
591
+ newPos = pos + sibling.nodeSize;
592
+ tr.insert(pos + node.nodeSize + sibling.nodeSize, node).delete(pos, pos + node.nodeSize);
593
+ }
594
+ if (wasNode) tr.setSelection(NodeSelection.create(tr.doc, newPos));
595
+ else tr.setSelection(TextSelection.near(tr.doc.resolve(Math.min(newPos + offset, tr.doc.content.size)), 1));
596
+ view.dispatch(tr.scrollIntoView());
597
+ return { moved: true, position: siblingIndex + 1, total: parent.childCount };
598
+ }
599
+ function duplicateBlock(editor, at) {
600
+ const { state, view } = editor;
601
+ const location = findBlock(state, at === void 0 ? {} : { at });
602
+ if (!location) return false;
603
+ const { pos, node } = location;
604
+ const tr = state.tr;
605
+ const insertAt = pos + node.nodeSize;
606
+ tr.insert(insertAt, node);
607
+ const wasNode = state.selection instanceof NodeSelection && state.selection.from === pos;
608
+ if (wasNode) tr.setSelection(NodeSelection.create(tr.doc, insertAt));
609
+ else tr.setSelection(TextSelection.near(tr.doc.resolve(Math.min(insertAt + 1, tr.doc.content.size)), 1));
610
+ view.dispatch(tr.scrollIntoView());
611
+ return true;
612
+ }
613
+ function deleteBlock(editor, at) {
614
+ const { state, view } = editor;
615
+ let location = findBlock(state, at === void 0 ? {} : { at });
616
+ if (!location) return false;
617
+ if (ITEMS.has(location.node.type.name) && location.parent.childCount === 1) {
618
+ const $pos = state.doc.resolve(location.pos);
619
+ const list = blockAt(state, $pos.before($pos.depth));
620
+ if (list) location = list;
621
+ }
622
+ const { pos, node, parent } = location;
623
+ const tr = state.tr;
624
+ const isLastBlock = parent.type.name === "doc" && parent.childCount === 1;
625
+ const paragraph = state.schema.nodes.paragraph;
626
+ if (isLastBlock && paragraph) {
627
+ tr.replaceWith(pos, pos + node.nodeSize, paragraph.create());
628
+ tr.setSelection(TextSelection.near(tr.doc.resolve(1), 1));
629
+ } else {
630
+ tr.delete(pos, pos + node.nodeSize);
631
+ const target = Math.min(pos, tr.doc.content.size);
632
+ tr.setSelection(Selection.near(tr.doc.resolve(target), -1));
633
+ }
634
+ view.dispatch(tr.scrollIntoView());
635
+ return true;
636
+ }
637
+ function currentBlockKind(editor) {
638
+ if (editor.isActive("heading")) {
639
+ const level = editor.getAttributes("heading").level;
640
+ if (level === 1 || level === 2 || level === 3 || level === 4) return `heading${level}`;
641
+ }
642
+ const order = [
643
+ ["taskList", "taskList"],
644
+ ["bulletList", "bulletList"],
645
+ ["orderedList", "numberedList"],
646
+ ["codeBlock", "code"],
647
+ ["equation", "equation"],
648
+ ["callout", "callout"],
649
+ ["blockquote", "quote"],
650
+ ["toggle", "toggle"],
651
+ ["table", "table"]
652
+ ];
653
+ for (const [name, kind] of order) if (editor.isActive(name)) return kind;
654
+ const { selection } = editor.state;
655
+ if (selection instanceof NodeSelection) {
656
+ const byNode = {
657
+ image: "image",
658
+ media: "video",
659
+ file: "file",
660
+ bookmark: "bookmark",
661
+ columns: "columns",
662
+ pageBreak: "pageBreak",
663
+ tableOfContents: "toc",
664
+ chart: "chart",
665
+ diagram: "diagram",
666
+ dataView: "dataView",
667
+ aiBlock: "ai",
668
+ horizontalRule: "divider"
669
+ };
670
+ const kind = byNode[selection.node.type.name];
671
+ if (kind) return kind;
672
+ }
673
+ return "paragraph";
674
+ }
675
+ function getSelectionInfo(editor) {
676
+ const { state } = editor;
677
+ const { selection } = state;
678
+ const marks = Object.keys(editor.schema.marks).filter((name) => editor.isActive(name));
679
+ const location = findBlock(state);
680
+ const id = location && typeof location.node.attrs.id === "string" ? location.node.attrs.id : null;
681
+ return {
682
+ empty: selection.empty,
683
+ text: selection.empty ? "" : state.doc.textBetween(selection.from, selection.to, "\n"),
684
+ blockId: id,
685
+ marks,
686
+ blockKind: currentBlockKind(editor)
687
+ };
688
+ }
689
+ var TEXT_KINDS = /* @__PURE__ */ new Set([
690
+ "paragraph",
691
+ "heading1",
692
+ "heading2",
693
+ "heading3",
694
+ "heading4",
695
+ "quote",
696
+ "callout",
697
+ "bulletList",
698
+ "numberedList",
699
+ "taskList",
700
+ "code"
701
+ ]);
702
+ var DOCUMENT_TYPE_KINDS = {
703
+ quote: "quote",
704
+ callout: "callout",
705
+ toggle: "toggle",
706
+ divider: "divider",
707
+ "bulleted-list": "bulletList",
708
+ "numbered-list": "numberedList",
709
+ "task-list": "taskList",
710
+ code: "code",
711
+ equation: "equation",
712
+ table: "table",
713
+ image: "image",
714
+ video: "video",
715
+ audio: "audio",
716
+ file: "file",
717
+ bookmark: "bookmark",
718
+ columns: "columns",
719
+ "page-break": "pageBreak",
720
+ "table-of-contents": "toc",
721
+ chart: "chart",
722
+ diagram: "diagram",
723
+ "data-view": "dataView",
724
+ ai: "ai",
725
+ paragraph: "paragraph"
726
+ };
727
+ function normalizeBlockKind(type, attrs = {}) {
728
+ if (type === "heading") {
729
+ const level = typeof attrs.level === "number" ? Math.min(4, Math.max(1, Math.round(attrs.level))) : 1;
730
+ return `heading${level}`;
731
+ }
732
+ if (Object.prototype.hasOwnProperty.call(DOCUMENT_TYPE_KINDS, type)) return DOCUMENT_TYPE_KINDS[type] ?? null;
733
+ return BLOCK_KINDS.includes(type) ? type : null;
734
+ }
735
+ var TURN_INTO_KINDS = [
736
+ "paragraph",
737
+ "heading1",
738
+ "heading2",
739
+ "heading3",
740
+ "heading4",
741
+ "quote",
742
+ "callout",
743
+ "bulletList",
744
+ "numberedList",
745
+ "taskList",
746
+ "code"
747
+ ];
748
+ function isTurnIntoKind(kind) {
749
+ return TEXT_KINDS.has(kind);
750
+ }
751
+ function turnInto(editor, kind, attrs = {}) {
752
+ const chain = () => editor.chain().focus();
753
+ switch (kind) {
754
+ case "paragraph":
755
+ return chain().clearNodes().run();
756
+ case "heading1":
757
+ case "heading2":
758
+ case "heading3":
759
+ case "heading4": {
760
+ const level = Number(kind.slice(-1));
761
+ return chain().clearNodes().setHeading({ level }).run();
762
+ }
763
+ case "quote":
764
+ return editor.isActive("blockquote") ? true : chain().clearNodes().setBlockquote().run();
765
+ case "callout":
766
+ return chain().clearNodes().setNode("callout", { variant: typeof attrs.variant === "string" ? attrs.variant : "info" }).run();
767
+ case "code":
768
+ return chain().clearNodes().setNode("codeBlock").run();
769
+ case "bulletList":
770
+ return editor.isActive("bulletList") ? true : chain().toggleBulletList().run();
771
+ case "numberedList":
772
+ return editor.isActive("orderedList") ? true : chain().toggleOrderedList().run();
773
+ case "taskList":
774
+ return editor.isActive("taskList") ? true : chain().toggleTaskList().run();
775
+ default:
776
+ return false;
777
+ }
778
+ }
779
+ function embedKindToType(kind) {
780
+ switch (kind) {
781
+ case "chart":
782
+ return "chart";
783
+ case "diagram":
784
+ return "diagram";
785
+ case "dataView":
786
+ return "data-view";
787
+ case "ai":
788
+ return "ai";
789
+ default:
790
+ return null;
791
+ }
792
+ }
793
+ function defaultEmbedPayload(type) {
794
+ switch (type) {
795
+ case "chart":
796
+ return {
797
+ title: "",
798
+ mark: "bar",
799
+ data: { values: [] },
800
+ x: { field: "x", type: "category" },
801
+ y: { field: "y", type: "number" },
802
+ a11y: { description: "" }
803
+ };
804
+ case "diagram":
805
+ return { engine: "mermaid", source: "graph TD\n A[Start] --> B[Finish]" };
806
+ case "data-view":
807
+ return { dataset: "", view: { version: 1, name: "", columns: [], sort: [], filter: [] } };
808
+ default:
809
+ return { prompt: "" };
810
+ }
811
+ }
812
+ function blockTemplate(kind, attrs = {}) {
813
+ const text = (value) => typeof value === "string" && value !== "" ? [{ type: "text", text: value }] : [];
814
+ switch (kind) {
815
+ case "paragraph":
816
+ return { type: "paragraph" };
817
+ case "heading1":
818
+ case "heading2":
819
+ case "heading3":
820
+ case "heading4":
821
+ return { type: "heading", attrs: { level: Number(kind.slice(-1)) } };
822
+ case "quote":
823
+ return { type: "blockquote", content: [{ type: "paragraph" }] };
824
+ case "callout":
825
+ return { type: "callout", attrs: { variant: typeof attrs.variant === "string" ? attrs.variant : "info" } };
826
+ case "toggle":
827
+ return { type: "toggle", content: [{ type: "toggleSummary" }, { type: "paragraph" }] };
828
+ case "bulletList":
829
+ return { type: "bulletList", content: [{ type: "listItem", content: [{ type: "paragraph" }] }] };
830
+ case "numberedList":
831
+ return { type: "orderedList", content: [{ type: "listItem", content: [{ type: "paragraph" }] }] };
832
+ case "taskList":
833
+ return { type: "taskList", content: [{ type: "taskItem", attrs: { checked: false }, content: [{ type: "paragraph" }] }] };
834
+ case "code":
835
+ return { type: "codeBlock", attrs: { language: typeof attrs.language === "string" ? attrs.language : null } };
836
+ case "equation":
837
+ return { type: "equation", content: text(attrs.latex) };
838
+ case "divider":
839
+ return { type: "horizontalRule" };
840
+ case "table": {
841
+ const rows = typeof attrs.rows === "number" ? attrs.rows : 3;
842
+ const columns = typeof attrs.columns === "number" ? attrs.columns : 3;
843
+ return {
844
+ type: "table",
845
+ attrs: { headerRow: true },
846
+ content: Array.from({ length: rows }, (_, row) => ({
847
+ type: "tableRow",
848
+ content: Array.from({ length: columns }, () => ({
849
+ type: row === 0 ? "tableHeader" : "tableCell",
850
+ content: [{ type: "paragraph" }]
851
+ }))
852
+ }))
853
+ };
854
+ }
855
+ case "image":
856
+ return {
857
+ type: "image",
858
+ attrs: { src: attrs.src ?? "", alt: attrs.alt ?? "", caption: attrs.caption ?? null, align: attrs.align ?? null, width: attrs.width ?? null }
859
+ };
860
+ case "video":
861
+ case "audio":
862
+ return { type: "media", attrs: { kind, src: attrs.src ?? "", caption: attrs.caption ?? null } };
863
+ case "file":
864
+ return { type: "file", attrs: { src: attrs.src ?? "", name: attrs.name ?? "", size: attrs.size ?? null } };
865
+ case "bookmark":
866
+ return { type: "bookmark", attrs: { url: attrs.url ?? "", title: attrs.title ?? null, description: attrs.description ?? null } };
867
+ case "columns":
868
+ return {
869
+ type: "columns",
870
+ content: Array.from({ length: typeof attrs.count === "number" ? Math.max(2, attrs.count) : 2 }, () => ({
871
+ type: "column",
872
+ content: [{ type: "paragraph" }]
873
+ }))
874
+ };
875
+ case "pageBreak":
876
+ return { type: "pageBreak" };
877
+ case "toc":
878
+ return { type: "tableOfContents" };
879
+ case "chart":
880
+ return { type: "chart", attrs: { spec: attrs.spec ?? defaultEmbedPayload("chart") } };
881
+ case "diagram": {
882
+ const payload = { ...defaultEmbedPayload("diagram"), ...attrs };
883
+ return { type: "diagram", attrs: { engine: payload.engine, diagram: payload.diagram ?? null, source: payload.source ?? null } };
884
+ }
885
+ case "dataView": {
886
+ const payload = { ...defaultEmbedPayload("data-view"), ...attrs };
887
+ return { type: "dataView", attrs: { dataset: payload.dataset, view: payload.view } };
888
+ }
889
+ case "ai":
890
+ return { type: "aiBlock", attrs: { prompt: typeof attrs.prompt === "string" ? attrs.prompt : "", result: attrs.result ?? null } };
891
+ default:
892
+ return null;
893
+ }
894
+ }
895
+ function updateBlockAttrs(editor, id, attrs) {
896
+ let target = null;
897
+ editor.state.doc.descendants((node, pos) => {
898
+ if (target === null && node.attrs.id === id) target = { pos, node };
899
+ return target === null;
900
+ });
901
+ const found = target;
902
+ if (!found) return false;
903
+ const { state, view } = editor;
904
+ view.dispatch(state.tr.setNodeMarkup(found.pos, void 0, { ...found.node.attrs, ...attrs }));
905
+ return true;
906
+ }
907
+ function isEmptyParagraph(node) {
908
+ return node.type.name === "paragraph" && node.content.size === 0;
909
+ }
910
+ function insertNode(editor, json) {
911
+ const { state, view } = editor;
912
+ let node;
913
+ try {
914
+ node = state.schema.nodeFromJSON(json);
915
+ } catch {
916
+ return null;
917
+ }
918
+ const location = findBlock(state, { items: false });
919
+ const tr = state.tr;
920
+ let start;
921
+ if (location && isEmptyParagraph(location.node)) {
922
+ start = location.pos;
923
+ tr.replaceWith(location.pos, location.pos + location.node.nodeSize, node);
924
+ } else if (location) {
925
+ start = location.pos + location.node.nodeSize;
926
+ tr.insert(start, node);
927
+ } else {
928
+ start = tr.doc.content.size;
929
+ tr.insert(start, node);
930
+ }
931
+ const inserted = tr.doc.nodeAt(start);
932
+ const parentIsDoc = location ? location.parent.type.name === "doc" : true;
933
+ const emptyParagraph = state.schema.nodes.paragraph;
934
+ if (inserted && emptyParagraph && parentIsDoc && !inserted.isTextblock && start + inserted.nodeSize === tr.doc.content.size) {
935
+ tr.insert(start + inserted.nodeSize, emptyParagraph.create());
936
+ }
937
+ const selectable = inserted !== null && inserted.isAtom && !inserted.isText;
938
+ if (selectable) tr.setSelection(NodeSelection.create(tr.doc, start));
939
+ else tr.setSelection(TextSelection.near(tr.doc.resolve(Math.min(start + 1, tr.doc.content.size)), 1));
940
+ view.dispatch(tr.scrollIntoView());
941
+ const placed = editor.state.doc.nodeAt(start);
942
+ return placed && typeof placed.attrs.id === "string" ? placed.attrs.id : null;
943
+ }
944
+ function applyBlockKind(editor, kind, attrs = {}) {
945
+ if (isTurnIntoKind(kind)) {
946
+ const ok = turnInto(editor, kind, attrs);
947
+ if (!ok) return null;
948
+ const location = findBlock(editor.state, { items: false });
949
+ return location && typeof location.node.attrs.id === "string" ? location.node.attrs.id : null;
950
+ }
951
+ const template = blockTemplate(kind, attrs);
952
+ return template ? insertNode(editor, template) : null;
953
+ }
954
+ function insertBlockKind(editor, kind, attrs = {}) {
955
+ if (isTurnIntoKind(kind)) {
956
+ const location = findBlock(editor.state, { items: false });
957
+ if (location && !isEmptyParagraph(location.node)) {
958
+ const at = location.pos + location.node.nodeSize;
959
+ editor.chain().insertContentAt(at, { type: "paragraph" }).setTextSelection(at + 1).run();
960
+ }
961
+ }
962
+ return applyBlockKind(editor, kind, attrs);
963
+ }
964
+ function execEditorCommand(editor, command, openBlockMenu) {
965
+ const chain = () => editor.chain().focus();
966
+ switch (command) {
967
+ case "undo":
968
+ return chain().undo().run();
969
+ case "redo":
970
+ return chain().redo().run();
971
+ case "bold":
972
+ return chain().toggleBold().run();
973
+ case "italic":
974
+ return chain().toggleItalic().run();
975
+ case "underline":
976
+ return chain().toggleUnderline().run();
977
+ case "strike":
978
+ return chain().toggleStrike().run();
979
+ case "code":
980
+ return chain().toggleCode().run();
981
+ case "clearMarks":
982
+ return chain().unsetAllMarks().run();
983
+ case "bulletList":
984
+ return chain().toggleBulletList().run();
985
+ case "numberedList":
986
+ return chain().toggleOrderedList().run();
987
+ case "taskList":
988
+ return chain().toggleTaskList().run();
989
+ case "indent":
990
+ return indent(editor, 1);
991
+ case "outdent":
992
+ return indent(editor, -1);
993
+ case "quote":
994
+ return chain().toggleBlockquote().run();
995
+ case "divider":
996
+ return chain().setHorizontalRule().run();
997
+ case "paragraph":
998
+ case "heading1":
999
+ case "heading2":
1000
+ case "heading3":
1001
+ case "heading4":
1002
+ return turnInto(editor, command);
1003
+ case "codeBlock":
1004
+ return turnInto(editor, "code");
1005
+ case "moveBlockUp":
1006
+ return moveBlock(editor, -1).moved;
1007
+ case "moveBlockDown":
1008
+ return moveBlock(editor, 1).moved;
1009
+ case "duplicateBlock":
1010
+ return duplicateBlock(editor);
1011
+ case "deleteBlock":
1012
+ return deleteBlock(editor);
1013
+ case "selectAll":
1014
+ return chain().selectAll().run();
1015
+ case "openBlockMenu":
1016
+ if (openBlockMenu) {
1017
+ openBlockMenu();
1018
+ return true;
1019
+ }
1020
+ return false;
1021
+ default:
1022
+ return false;
1023
+ }
1024
+ }
1025
+ function indent(editor, direction) {
1026
+ for (const name of ["taskItem", "listItem"]) {
1027
+ if (editor.isActive(name)) {
1028
+ const chain = editor.chain().focus();
1029
+ return direction > 0 ? chain.sinkListItem(name).run() : chain.liftListItem(name).run();
1030
+ }
1031
+ }
1032
+ return false;
1033
+ }
1034
+
1035
+ // src/react/internal/menu.tsx
1036
+ import {
1037
+ useEffect as useEffect2,
1038
+ useId,
1039
+ useRef as useRef2,
1040
+ useState as useState2
1041
+ } from "react";
1042
+ import { IconButton } from "@x12i/ui";
1043
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1044
+ function MenuList({ label, items, onClose, autoFocus = true, initialId, ref }) {
1045
+ const listRef = useRef2(null);
1046
+ const typeahead = useRef2({ text: "", timer: void 0 });
1047
+ const enabled = () => Array.from(listRef.current?.querySelectorAll("[role^='menuitem']:not([aria-disabled='true'])") ?? []);
1048
+ useEffect2(() => {
1049
+ if (!autoFocus) return;
1050
+ const list = enabled();
1051
+ const preferred = list.find((element) => element.dataset.id === initialId) ?? list.find((element) => element.getAttribute("aria-checked") === "true") ?? list[0];
1052
+ preferred?.focus();
1053
+ }, []);
1054
+ useEffect2(() => {
1055
+ const state = typeahead.current;
1056
+ return () => clearTimeout(state.timer);
1057
+ }, []);
1058
+ const onKeyDown = (event) => {
1059
+ const list = enabled();
1060
+ const index = list.indexOf(document.activeElement);
1061
+ const focusAt = (next) => {
1062
+ event.preventDefault();
1063
+ list[(next + list.length) % list.length]?.focus();
1064
+ };
1065
+ switch (event.key) {
1066
+ case "ArrowDown":
1067
+ focusAt(index + 1);
1068
+ return;
1069
+ case "ArrowUp":
1070
+ focusAt(index === -1 ? list.length - 1 : index - 1);
1071
+ return;
1072
+ case "Home":
1073
+ focusAt(0);
1074
+ return;
1075
+ case "End":
1076
+ focusAt(list.length - 1);
1077
+ return;
1078
+ case "Escape":
1079
+ event.preventDefault();
1080
+ event.stopPropagation();
1081
+ onClose("escape");
1082
+ return;
1083
+ case "Tab":
1084
+ onClose("tab");
1085
+ return;
1086
+ default:
1087
+ break;
1088
+ }
1089
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey && event.key !== " ") {
1090
+ const state = typeahead.current;
1091
+ clearTimeout(state.timer);
1092
+ state.text += event.key.toLowerCase();
1093
+ state.timer = setTimeout(() => {
1094
+ state.text = "";
1095
+ }, 600);
1096
+ const ordered = [...list.slice(index + 1), ...list.slice(0, index + 1)];
1097
+ const match = ordered.find((element) => (element.textContent ?? "").trim().toLowerCase().startsWith(state.text));
1098
+ match?.focus();
1099
+ }
1100
+ };
1101
+ return /* @__PURE__ */ jsx3(
1102
+ "div",
1103
+ {
1104
+ ref: (node) => {
1105
+ listRef.current = node;
1106
+ if (typeof ref === "function") ref(node);
1107
+ else if (ref) ref.current = node;
1108
+ },
1109
+ role: "menu",
1110
+ "aria-label": label,
1111
+ "data-kit": "editor-menu",
1112
+ tabIndex: -1,
1113
+ onKeyDown,
1114
+ children: items.map((item) => {
1115
+ const role = item.role ?? "menuitem";
1116
+ return /* @__PURE__ */ jsxs2("div", { role: "none", "data-kit": "editor-menu-entry", children: [
1117
+ item.heading ? /* @__PURE__ */ jsx3("div", { role: "presentation", "data-kit": "editor-menu-heading", children: item.heading }) : null,
1118
+ /* @__PURE__ */ jsxs2(
1119
+ "button",
1120
+ {
1121
+ type: "button",
1122
+ role,
1123
+ "data-kit": "editor-menu-item",
1124
+ "data-id": item.id,
1125
+ tabIndex: -1,
1126
+ "aria-checked": role === "menuitem" ? void 0 : item.checked === true,
1127
+ "aria-disabled": item.disabled ? true : void 0,
1128
+ onMouseDown: (event) => event.preventDefault(),
1129
+ onClick: () => {
1130
+ if (item.disabled) return;
1131
+ item.onSelect();
1132
+ onClose("select");
1133
+ },
1134
+ children: [
1135
+ item.icon ? /* @__PURE__ */ jsx3("span", { "data-kit": "editor-menu-icon", children: item.icon }) : null,
1136
+ item.swatch ? /* @__PURE__ */ jsx3("span", { "data-kit": "editor-swatch", "data-token": item.swatch, "aria-hidden": "true" }) : null,
1137
+ /* @__PURE__ */ jsx3("span", { "data-kit": "editor-menu-label", children: item.label }),
1138
+ role !== "menuitem" && item.checked ? /* @__PURE__ */ jsx3(Glyph, { name: "check", "data-kit": "editor-menu-check" }) : null
1139
+ ]
1140
+ }
1141
+ )
1142
+ ] }, item.id);
1143
+ })
1144
+ }
1145
+ );
1146
+ }
1147
+ function MenuButton({ label, title, menuLabel, items, children, text, disabled, pressed, onOpenChange }) {
1148
+ const [open, setOpen] = useState2(false);
1149
+ const wrapRef = useRef2(null);
1150
+ const buttonRef = useRef2(null);
1151
+ const menuId = useId();
1152
+ const change = (next) => {
1153
+ setOpen(next);
1154
+ onOpenChange?.(next);
1155
+ };
1156
+ useEffect2(() => {
1157
+ if (!open) return;
1158
+ const onPointerDown = (event) => {
1159
+ if (wrapRef.current && event.target instanceof Node && !wrapRef.current.contains(event.target)) {
1160
+ setOpen(false);
1161
+ onOpenChange?.(false);
1162
+ }
1163
+ };
1164
+ document.addEventListener("pointerdown", onPointerDown);
1165
+ return () => document.removeEventListener("pointerdown", onPointerDown);
1166
+ }, [open, onOpenChange]);
1167
+ return /* @__PURE__ */ jsxs2("span", { ref: wrapRef, "data-kit": "editor-menu-wrap", children: [
1168
+ /* @__PURE__ */ jsxs2(
1169
+ IconButton,
1170
+ {
1171
+ ref: buttonRef,
1172
+ label,
1173
+ title: title ?? label,
1174
+ variant: pressed ? "soft" : "ghost",
1175
+ tone: "neutral",
1176
+ size: "sm",
1177
+ disabled,
1178
+ "data-toolbar-item": "",
1179
+ "data-has-text": text ? "" : void 0,
1180
+ "aria-haspopup": "menu",
1181
+ "aria-expanded": open,
1182
+ "aria-controls": open ? menuId : void 0,
1183
+ onMouseDown: (event) => event.preventDefault(),
1184
+ onClick: () => change(!open),
1185
+ onKeyDown: (event) => {
1186
+ if (event.key === "ArrowDown" && !open) {
1187
+ event.preventDefault();
1188
+ change(true);
1189
+ }
1190
+ },
1191
+ children: [
1192
+ children,
1193
+ text ? /* @__PURE__ */ jsx3("span", { "data-kit": "editor-toolbar-text", children: text }) : null
1194
+ ]
1195
+ }
1196
+ ),
1197
+ open ? /* @__PURE__ */ jsx3("div", { id: menuId, "data-kit": "editor-popup", "data-placement": "below", children: /* @__PURE__ */ jsx3(
1198
+ MenuList,
1199
+ {
1200
+ label: menuLabel,
1201
+ items,
1202
+ onClose: (reason) => {
1203
+ change(false);
1204
+ if (reason === "escape" || reason === "tab") buttonRef.current?.focus();
1205
+ }
1206
+ }
1207
+ ) }) : null
1208
+ ] });
1209
+ }
1210
+
1211
+ // src/react/internal/position.ts
1212
+ function isRtl(element) {
1213
+ const owner = element.closest("[dir]")?.getAttribute("dir");
1214
+ if (owner === "rtl") return true;
1215
+ if (owner === "ltr") return false;
1216
+ return typeof getComputedStyle === "function" && getComputedStyle(element).direction === "rtl";
1217
+ }
1218
+ function positionNear(surface, rect, edge) {
1219
+ const box = surface.getBoundingClientRect();
1220
+ const x = isRtl(surface) ? box.right - rect.right : rect.left - box.left;
1221
+ const y = (edge === "top" ? rect.top : rect.bottom) - box.top;
1222
+ return { x: Number.isFinite(x) ? Math.max(0, x) : 0, y: Number.isFinite(y) ? Math.max(0, y) : 0 };
1223
+ }
1224
+
1225
+ // src/react/block-menu.tsx
1226
+ import { jsx as jsx4 } from "react/jsx-runtime";
1227
+ function BlockMenu({ editor, surfaceRef, onClose, announce }) {
1228
+ const t = useTranslator();
1229
+ const m = t.messages;
1230
+ const [position2] = useState3(() => {
1231
+ const surface = surfaceRef.current;
1232
+ if (!surface) return { x: 0, y: 0 };
1233
+ try {
1234
+ return positionNear(surface, editor.view.coordsAtPos(editor.state.selection.from), "bottom");
1235
+ } catch {
1236
+ return { x: 0, y: 0 };
1237
+ }
1238
+ });
1239
+ const current = currentBlockKind(editor);
1240
+ const turnable = current !== null && TURN_INTO_KINDS.includes(current);
1241
+ const items = [
1242
+ ...turnable ? TURN_INTO_KINDS.map(
1243
+ (kind, index) => ({
1244
+ id: `turn-${kind}`,
1245
+ label: m.blocks[kind].name,
1246
+ role: "menuitemradio",
1247
+ checked: current === kind,
1248
+ heading: index === 0 ? m.blockMenu.turnInto : void 0,
1249
+ onSelect: () => {
1250
+ if (turnInto(editor, kind)) announce(t.format(m.status.blockTurnedInto, { name: m.blocks[kind].name }));
1251
+ }
1252
+ })
1253
+ ) : [],
1254
+ {
1255
+ id: "move-up",
1256
+ label: m.blockMenu.moveUp,
1257
+ onSelect: () => {
1258
+ const result = moveBlock(editor, -1);
1259
+ announce(result.moved ? t.format(m.status.blockMoved, { position: result.position, total: result.total }) : m.status.cannotMove);
1260
+ }
1261
+ },
1262
+ {
1263
+ id: "move-down",
1264
+ label: m.blockMenu.moveDown,
1265
+ onSelect: () => {
1266
+ const result = moveBlock(editor, 1);
1267
+ announce(result.moved ? t.format(m.status.blockMoved, { position: result.position, total: result.total }) : m.status.cannotMove);
1268
+ }
1269
+ },
1270
+ {
1271
+ id: "duplicate",
1272
+ label: m.blockMenu.duplicate,
1273
+ onSelect: () => {
1274
+ if (duplicateBlock(editor)) announce(m.status.blockDuplicated);
1275
+ }
1276
+ },
1277
+ {
1278
+ id: "insert-below",
1279
+ label: m.blockMenu.insertBelow,
1280
+ onSelect: () => {
1281
+ const location = findBlock(editor.state, { items: false });
1282
+ if (!location) return;
1283
+ const at = location.pos + location.node.nodeSize;
1284
+ editor.chain().insertContentAt(at, { type: "paragraph" }).setTextSelection(at + 1).run();
1285
+ }
1286
+ },
1287
+ {
1288
+ id: "delete",
1289
+ label: m.blockMenu.delete,
1290
+ onSelect: () => {
1291
+ if (deleteBlock(editor)) announce(m.status.blockDeleted);
1292
+ }
1293
+ }
1294
+ ];
1295
+ return /* @__PURE__ */ jsx4(
1296
+ "div",
1297
+ {
1298
+ "data-kit": "editor-popup",
1299
+ "data-placement": "below",
1300
+ style: { "--kit-editor-x": `${position2.x}px`, "--kit-editor-y": `${position2.y}px` },
1301
+ children: /* @__PURE__ */ jsx4(
1302
+ MenuList,
1303
+ {
1304
+ label: m.blockMenu.label,
1305
+ items,
1306
+ onClose: () => {
1307
+ onClose();
1308
+ editor.commands.focus();
1309
+ }
1310
+ }
1311
+ )
1312
+ }
1313
+ );
1314
+ }
1315
+
1316
+ // src/react/bubble-menu.tsx
1317
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
1318
+ import { TextSelection as TextSelection2 } from "@tiptap/pm/state";
1319
+ import { useEditorState } from "@tiptap/react";
1320
+
1321
+ // src/react/internal/platform.ts
1322
+ function isApplePlatform() {
1323
+ if (typeof navigator === "undefined") return false;
1324
+ return /Mac|iPhone|iPad|iPod/i.test(navigator.platform || navigator.userAgent || "");
1325
+ }
1326
+ function modKey() {
1327
+ return isApplePlatform() ? "\u2318" : "Ctrl";
1328
+ }
1329
+ function shortcut(...keys) {
1330
+ return [modKey(), ...keys].join("+");
1331
+ }
1332
+
1333
+ // src/react/internal/roving.ts
1334
+ import { useEffect as useEffect3, useRef as useRef3 } from "react";
1335
+ var ITEM_SELECTOR = "[data-toolbar-item]:not([disabled]):not([aria-disabled='true'])";
1336
+ function itemsOf(container) {
1337
+ return Array.from(container.querySelectorAll(ITEM_SELECTOR));
1338
+ }
1339
+ function isRtl2(container) {
1340
+ const owner = container.closest("[dir]");
1341
+ const value = owner?.getAttribute("dir");
1342
+ if (value === "rtl") return true;
1343
+ if (value === "ltr") return false;
1344
+ return typeof getComputedStyle === "function" && getComputedStyle(container).direction === "rtl";
1345
+ }
1346
+ function useRovingToolbar(containerRef) {
1347
+ const activeRef = useRef3(0);
1348
+ const apply = () => {
1349
+ const container = containerRef.current;
1350
+ if (!container) return;
1351
+ const items = itemsOf(container);
1352
+ if (items.length === 0) return;
1353
+ const index = Math.min(activeRef.current, items.length - 1);
1354
+ activeRef.current = index;
1355
+ items.forEach((item, position2) => {
1356
+ item.tabIndex = position2 === index ? 0 : -1;
1357
+ });
1358
+ };
1359
+ useEffect3(apply);
1360
+ const onFocusCapture = (event) => {
1361
+ const container = containerRef.current;
1362
+ if (!container) return;
1363
+ const items = itemsOf(container);
1364
+ const index = items.indexOf(event.target);
1365
+ if (index !== -1 && index !== activeRef.current) {
1366
+ activeRef.current = index;
1367
+ apply();
1368
+ }
1369
+ };
1370
+ const onKeyDown = (event) => {
1371
+ const container = containerRef.current;
1372
+ if (!container || event.defaultPrevented) return;
1373
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
1374
+ const target = event.target;
1375
+ if (target.closest("[role='menu'], [role='dialog'], input, textarea")) return;
1376
+ const items = itemsOf(container);
1377
+ const current = items.indexOf(target.closest("[data-toolbar-item]") ?? target);
1378
+ if (current === -1) return;
1379
+ const rtl = isRtl2(container);
1380
+ let wanted;
1381
+ switch (event.key) {
1382
+ case "ArrowRight":
1383
+ wanted = rtl ? current - 1 : current + 1;
1384
+ break;
1385
+ case "ArrowLeft":
1386
+ wanted = rtl ? current + 1 : current - 1;
1387
+ break;
1388
+ case "Home":
1389
+ wanted = 0;
1390
+ break;
1391
+ case "End":
1392
+ wanted = items.length - 1;
1393
+ break;
1394
+ default:
1395
+ return;
1396
+ }
1397
+ event.preventDefault();
1398
+ const next = (wanted + items.length) % items.length;
1399
+ activeRef.current = next;
1400
+ apply();
1401
+ items[next]?.focus();
1402
+ };
1403
+ return { onKeyDown, onFocusCapture };
1404
+ }
1405
+
1406
+ // src/react/internal/tool-button.tsx
1407
+ import { IconButton as IconButton2 } from "@x12i/ui";
1408
+ import { jsx as jsx5 } from "react/jsx-runtime";
1409
+ function ToolButton({ label, keys, pressed, disabled, onClick, children }) {
1410
+ return /* @__PURE__ */ jsx5(
1411
+ IconButton2,
1412
+ {
1413
+ label,
1414
+ title: keys ? `${label} (${keys})` : label,
1415
+ variant: pressed ? "soft" : "ghost",
1416
+ tone: pressed ? "accent" : "neutral",
1417
+ size: "sm",
1418
+ disabled,
1419
+ "aria-pressed": pressed,
1420
+ "data-toolbar-item": "",
1421
+ onMouseDown: (event) => event.preventDefault(),
1422
+ onClick,
1423
+ children
1424
+ }
1425
+ );
1426
+ }
1427
+
1428
+ // src/react/bubble-menu.tsx
1429
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1430
+ function sameMarks(a, b) {
1431
+ return a === b || !!a && !!b && Object.keys(a).every((key) => a[key] === b[key]);
1432
+ }
1433
+ function BubbleMenu({ editor, surfaceRef, onOpenLink, onOpenAi, announce }) {
1434
+ const t = useTranslator();
1435
+ const m = t.messages;
1436
+ const barRef = useRef4(null);
1437
+ const roving = useRovingToolbar(barRef);
1438
+ const [position2, setPosition] = useState4(null);
1439
+ const marks = useEditorState({
1440
+ editor,
1441
+ selector: ({ editor: current }) => current ? {
1442
+ bold: current.isActive("bold"),
1443
+ italic: current.isActive("italic"),
1444
+ underline: current.isActive("underline"),
1445
+ strike: current.isActive("strike"),
1446
+ code: current.isActive("code"),
1447
+ link: current.isActive("link")
1448
+ } : null,
1449
+ equalityFn: sameMarks
1450
+ });
1451
+ useEffect4(() => {
1452
+ let timer;
1453
+ const update = () => {
1454
+ const surface = surfaceRef.current;
1455
+ const { selection } = editor.state;
1456
+ const focusInside = editor.view.hasFocus() || (barRef.current?.contains(document.activeElement) ?? false);
1457
+ const show = surface !== null && editor.isEditable && focusInside && selection instanceof TextSelection2 && !selection.empty && !selection.$from.parent.type.spec.code;
1458
+ if (!show) {
1459
+ setPosition(null);
1460
+ return;
1461
+ }
1462
+ try {
1463
+ const rect = editor.view.coordsAtPos(selection.from);
1464
+ setPosition(positionNear(surface, rect, "top"));
1465
+ } catch {
1466
+ setPosition({ x: 0, y: 0 });
1467
+ }
1468
+ };
1469
+ const delayed = () => {
1470
+ clearTimeout(timer);
1471
+ timer = setTimeout(update, 0);
1472
+ };
1473
+ editor.on("selectionUpdate", update);
1474
+ editor.on("focus", update);
1475
+ editor.on("blur", delayed);
1476
+ return () => {
1477
+ clearTimeout(timer);
1478
+ editor.off("selectionUpdate", update);
1479
+ editor.off("focus", update);
1480
+ editor.off("blur", delayed);
1481
+ };
1482
+ }, [editor, surfaceRef]);
1483
+ if (!position2 || !marks) return null;
1484
+ const toggle = (command, name, active) => () => {
1485
+ execEditorCommand(editor, command);
1486
+ announce(t.format(active ? m.status.markOff : m.status.markOn, { name }));
1487
+ };
1488
+ return /* @__PURE__ */ jsxs3(
1489
+ "div",
1490
+ {
1491
+ ref: barRef,
1492
+ role: "toolbar",
1493
+ "aria-label": m.toolbar.selectionMenu,
1494
+ "data-kit": "editor-bubble",
1495
+ style: { "--kit-editor-x": `${position2.x}px`, "--kit-editor-y": `${position2.y}px` },
1496
+ onFocusCapture: roving.onFocusCapture,
1497
+ onKeyDown: (event) => {
1498
+ if (event.key === "Escape") {
1499
+ event.preventDefault();
1500
+ editor.commands.focus();
1501
+ return;
1502
+ }
1503
+ roving.onKeyDown(event);
1504
+ },
1505
+ children: [
1506
+ /* @__PURE__ */ jsx6(ToolButton, { label: m.toolbar.bold, keys: shortcut("B"), pressed: marks.bold, onClick: toggle("bold", m.toolbar.bold, marks.bold), children: /* @__PURE__ */ jsx6(Glyph, { name: "bold" }) }),
1507
+ /* @__PURE__ */ jsx6(ToolButton, { label: m.toolbar.italic, keys: shortcut("I"), pressed: marks.italic, onClick: toggle("italic", m.toolbar.italic, marks.italic), children: /* @__PURE__ */ jsx6(Glyph, { name: "italic" }) }),
1508
+ /* @__PURE__ */ jsx6(ToolButton, { label: m.toolbar.underline, keys: shortcut("U"), pressed: marks.underline, onClick: toggle("underline", m.toolbar.underline, marks.underline), children: /* @__PURE__ */ jsx6(Glyph, { name: "underline" }) }),
1509
+ /* @__PURE__ */ jsx6(ToolButton, { label: m.toolbar.strike, keys: shortcut("Shift", "S"), pressed: marks.strike, onClick: toggle("strike", m.toolbar.strike, marks.strike), children: /* @__PURE__ */ jsx6(Glyph, { name: "strike" }) }),
1510
+ /* @__PURE__ */ jsx6(ToolButton, { label: m.toolbar.code, keys: shortcut("E"), pressed: marks.code, onClick: toggle("code", m.toolbar.code, marks.code), children: /* @__PURE__ */ jsx6(Glyph, { name: "code" }) }),
1511
+ /* @__PURE__ */ jsx6(ToolButton, { label: m.toolbar.link, keys: shortcut("K"), pressed: marks.link, onClick: onOpenLink, children: /* @__PURE__ */ jsx6(Glyph, { name: "link" }) }),
1512
+ onOpenAi ? /* @__PURE__ */ jsx6(ToolButton, { label: m.ai.menuLabel, onClick: onOpenAi, children: /* @__PURE__ */ jsx6(Glyph, { name: "sparkle" }) }) : null
1513
+ ]
1514
+ }
1515
+ );
1516
+ }
1517
+
1518
+ // src/react/context.ts
1519
+ import { createContext as createContext2, useContext as useContext2 } from "react";
1520
+ var noop = () => void 0;
1521
+ var defaultEditorUi = {
1522
+ editable: false,
1523
+ blockRenderers: {},
1524
+ announce: noop,
1525
+ onEditBlock: void 0,
1526
+ onRequestBlock: void 0,
1527
+ openImageDialog: noop,
1528
+ openLinkDialog: noop,
1529
+ openBlockMenu: noop
1530
+ };
1531
+ var EditorUiContext = createContext2(defaultEditorUi);
1532
+ function useEditorUi() {
1533
+ return useContext2(EditorUiContext);
1534
+ }
1535
+ var EngineContext = createContext2(null);
1536
+
1537
+ // src/react/extensions/index.ts
1538
+ import { Extension as Extension4 } from "@tiptap/core";
1539
+ import Placeholder from "@tiptap/extension-placeholder";
1540
+ import { TaskItem } from "@tiptap/extension-task-item";
1541
+ import { TaskList } from "@tiptap/extension-task-list";
1542
+ import StarterKit from "@tiptap/starter-kit";
1543
+
1544
+ // src/react/extensions/block-id.ts
1545
+ import { Extension } from "@tiptap/core";
1546
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
1547
+ var blockIdKey = new PluginKey("kitBlockId");
1548
+ var BLOCK_CONTAINERS = /* @__PURE__ */ new Set(["doc", "toggle", "column"]);
1549
+ var ITEM_NODES = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
1550
+ var LIST_NODES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
1551
+ var BlockId = Extension.create({
1552
+ name: "blockId",
1553
+ addGlobalAttributes() {
1554
+ return [
1555
+ {
1556
+ types: [...BLOCK_ID_NODES],
1557
+ attributes: {
1558
+ id: {
1559
+ default: null,
1560
+ keepOnSplit: false,
1561
+ parseHTML: (element) => element.getAttribute("data-block-id"),
1562
+ renderHTML: (attributes) => typeof attributes.id === "string" && attributes.id !== "" ? { "data-block-id": attributes.id } : {}
1563
+ }
1564
+ }
1565
+ }
1566
+ ];
1567
+ },
1568
+ addProseMirrorPlugins() {
1569
+ return [
1570
+ new Plugin({
1571
+ key: blockIdKey,
1572
+ appendTransaction: (transactions, _oldState, newState) => {
1573
+ if (!transactions.some((transaction) => transaction.docChanged)) return null;
1574
+ const seen = /* @__PURE__ */ new Set();
1575
+ const fixes = [];
1576
+ newState.doc.descendants((node, pos, parent) => {
1577
+ const name = node.type.name;
1578
+ if (!BLOCK_ID_NODES.includes(name) || parent === null) return;
1579
+ const parentName = parent.type.name;
1580
+ const carriesId = ITEM_NODES.has(name) ? LIST_NODES.has(parentName) : BLOCK_CONTAINERS.has(parentName) && !(LIST_NODES.has(name) && ITEM_NODES.has(parentName));
1581
+ if (!carriesId) return;
1582
+ const current = node.attrs.id;
1583
+ if (typeof current === "string" && current !== "" && !seen.has(current)) {
1584
+ seen.add(current);
1585
+ return;
1586
+ }
1587
+ let id = generateBlockId();
1588
+ while (seen.has(id)) id = generateBlockId();
1589
+ seen.add(id);
1590
+ fixes.push({ pos, attrs: { ...node.attrs, id } });
1591
+ });
1592
+ if (fixes.length === 0) return null;
1593
+ const tr = newState.tr;
1594
+ for (const fix of fixes) tr.setNodeMarkup(fix.pos, void 0, fix.attrs);
1595
+ tr.setMeta("addToHistory", false);
1596
+ return tr;
1597
+ }
1598
+ })
1599
+ ];
1600
+ }
1601
+ });
1602
+
1603
+ // src/react/extensions/block-keys.ts
1604
+ import { Extension as Extension2 } from "@tiptap/core";
1605
+ import { NodeSelection as NodeSelection2 } from "@tiptap/pm/state";
1606
+
1607
+ // src/react/extensions/attrs.ts
1608
+ function jsonAttribute(name, fallback = null) {
1609
+ const dataName = `data-${name}`;
1610
+ return {
1611
+ default: fallback,
1612
+ parseHTML: (element) => {
1613
+ const raw = element.getAttribute(dataName);
1614
+ if (raw === null || raw === "") return fallback;
1615
+ try {
1616
+ return JSON.parse(raw);
1617
+ } catch {
1618
+ return fallback;
1619
+ }
1620
+ },
1621
+ renderHTML: (attributes) => {
1622
+ const value = attributes[name];
1623
+ return value === null || value === void 0 ? {} : { [dataName]: JSON.stringify(value) };
1624
+ }
1625
+ };
1626
+ }
1627
+ function dataAttribute(name, fallback = null, parse) {
1628
+ const dataName = `data-${name}`;
1629
+ return {
1630
+ default: fallback,
1631
+ parseHTML: (element) => {
1632
+ const raw = element.getAttribute(dataName);
1633
+ if (raw === null) return fallback;
1634
+ return parse ? parse(raw) : raw;
1635
+ },
1636
+ renderHTML: (attributes) => {
1637
+ const value = attributes[name];
1638
+ return value === null || value === void 0 || value === "" ? {} : { [dataName]: String(value) };
1639
+ }
1640
+ };
1641
+ }
1642
+ var EMBED_NODE_NAMES = {
1643
+ chart: "chart",
1644
+ diagram: "diagram",
1645
+ "data-view": "dataView",
1646
+ ai: "aiBlock"
1647
+ };
1648
+ function embedTypeOfNode(name) {
1649
+ for (const [type, nodeName] of Object.entries(EMBED_NODE_NAMES)) if (nodeName === name) return type;
1650
+ return null;
1651
+ }
1652
+ function attrsToPayload(type, attrs) {
1653
+ switch (type) {
1654
+ case "chart":
1655
+ return attrs.spec ?? {};
1656
+ case "diagram": {
1657
+ const payload = { engine: attrs.engine ?? "mermaid" };
1658
+ if (attrs.diagram !== null && attrs.diagram !== void 0) payload.diagram = attrs.diagram;
1659
+ if (typeof attrs.source === "string") payload.source = attrs.source;
1660
+ return payload;
1661
+ }
1662
+ case "data-view":
1663
+ return { dataset: typeof attrs.dataset === "string" ? attrs.dataset : "", view: attrs.view ?? {} };
1664
+ default: {
1665
+ const payload = { prompt: typeof attrs.prompt === "string" ? attrs.prompt : "" };
1666
+ if (attrs.result !== null && attrs.result !== void 0) payload.result = attrs.result;
1667
+ return payload;
1668
+ }
1669
+ }
1670
+ }
1671
+ function payloadToAttrs(type, payload) {
1672
+ const value = payload;
1673
+ switch (type) {
1674
+ case "chart":
1675
+ return { spec: payload };
1676
+ case "diagram":
1677
+ return { engine: value.engine ?? "mermaid", diagram: value.diagram ?? null, source: value.source ?? null };
1678
+ case "data-view":
1679
+ return { dataset: value.dataset ?? "", view: value.view ?? null };
1680
+ default:
1681
+ return { prompt: value.prompt ?? "", result: value.result ?? null };
1682
+ }
1683
+ }
1684
+ function payloadTitle(type, payload) {
1685
+ const value = payload ?? {};
1686
+ const text = (candidate) => typeof candidate === "string" ? candidate.trim() : "";
1687
+ switch (type) {
1688
+ case "chart":
1689
+ return text(value.title);
1690
+ case "diagram": {
1691
+ const diagram = value.diagram;
1692
+ return text(diagram?.title) || text(diagram?.name);
1693
+ }
1694
+ case "data-view": {
1695
+ const view = value.view;
1696
+ return text(view?.name) || text(value.dataset);
1697
+ }
1698
+ default:
1699
+ return text(value.prompt).slice(0, 80);
1700
+ }
1701
+ }
1702
+
1703
+ // src/react/extensions/block-keys.ts
1704
+ var BlockKeys = Extension2.create({
1705
+ name: "blockKeys",
1706
+ addOptions() {
1707
+ return { runtime: null };
1708
+ },
1709
+ addKeyboardShortcuts() {
1710
+ const runtime = this.options.runtime;
1711
+ if (!runtime) return {};
1712
+ const move = (direction) => () => {
1713
+ const ui = runtime.ui();
1714
+ if (!ui.editable) return false;
1715
+ const result = moveBlock(this.editor, direction);
1716
+ const t = runtime.translator();
1717
+ if (result.total > 0) {
1718
+ ui.announce(
1719
+ result.moved ? t.format(t.messages.status.blockMoved, { position: result.position, total: result.total }) : t.messages.status.cannotMove
1720
+ );
1721
+ }
1722
+ return true;
1723
+ };
1724
+ return {
1725
+ "Alt-Shift-ArrowUp": move(-1),
1726
+ "Alt-Shift-ArrowDown": move(1),
1727
+ "Mod-/": () => {
1728
+ const ui = runtime.ui();
1729
+ if (!ui.editable) return false;
1730
+ ui.openBlockMenu();
1731
+ return true;
1732
+ },
1733
+ "Mod-k": () => {
1734
+ const ui = runtime.ui();
1735
+ if (!ui.editable) return false;
1736
+ ui.openLinkDialog();
1737
+ return true;
1738
+ },
1739
+ Enter: () => {
1740
+ const ui = runtime.ui();
1741
+ const { selection } = this.editor.state;
1742
+ if (!ui.editable || !(selection instanceof NodeSelection2)) return false;
1743
+ const type = embedTypeOfNode(selection.node.type.name);
1744
+ const handler = ui.onEditBlock;
1745
+ const id = selection.node.attrs.id;
1746
+ if (!type || !handler || typeof id !== "string") return false;
1747
+ const payload = attrsToPayload(type, selection.node.attrs);
1748
+ void Promise.resolve(handler(type, payload)).then((next) => {
1749
+ if (next === null || next === void 0) return;
1750
+ updateBlockAttrs(this.editor, id, payloadToAttrs(type, next));
1751
+ this.editor.commands.focus();
1752
+ });
1753
+ return true;
1754
+ }
1755
+ };
1756
+ }
1757
+ });
1758
+
1759
+ // src/react/extensions/marks.ts
1760
+ import { mergeAttributes, Mark } from "@tiptap/core";
1761
+ import Highlight from "@tiptap/extension-highlight";
1762
+ function tokenAttribute(isAllowed, fallback) {
1763
+ return {
1764
+ token: {
1765
+ default: fallback,
1766
+ parseHTML: (element) => {
1767
+ const token = element.getAttribute("data-token");
1768
+ return isAllowed(token) ? token : fallback;
1769
+ },
1770
+ renderHTML: (attributes) => isAllowed(attributes.token) ? { "data-token": attributes.token } : {}
1771
+ }
1772
+ };
1773
+ }
1774
+ var TextColor = Mark.create({
1775
+ name: "color",
1776
+ addAttributes() {
1777
+ return tokenAttribute(isTextColorToken, null);
1778
+ },
1779
+ parseHTML() {
1780
+ return [{ tag: 'span[data-kit="editor-text-color"]' }];
1781
+ },
1782
+ renderHTML({ HTMLAttributes }) {
1783
+ return ["span", mergeAttributes({ "data-kit": "editor-text-color" }, HTMLAttributes), 0];
1784
+ }
1785
+ });
1786
+ var KitHighlight = Highlight.extend({
1787
+ addAttributes() {
1788
+ return tokenAttribute(isHighlightToken, "color.highlight");
1789
+ },
1790
+ renderHTML({ HTMLAttributes }) {
1791
+ return ["mark", mergeAttributes({ "data-kit": "editor-highlight" }, HTMLAttributes), 0];
1792
+ }
1793
+ });
1794
+
1795
+ // src/react/extensions/nodes.ts
1796
+ import { InputRule, mergeAttributes as mergeAttributes2, Node as Node2, textblockTypeInputRule } from "@tiptap/core";
1797
+ import Image from "@tiptap/extension-image";
1798
+ import { Table, TableCell, TableHeader, TableRow } from "@tiptap/extension-table";
1799
+ import { TextSelection as TextSelection3 } from "@tiptap/pm/state";
1800
+ import { ReactNodeViewRenderer } from "@tiptap/react";
1801
+
1802
+ // src/react/blocks/embed.tsx
1803
+ import { createElement as createElement2 } from "react";
1804
+
1805
+ // src/react/blocks/frame.tsx
1806
+ import { useRef as useRef5 } from "react";
1807
+ import { NodeViewWrapper } from "@tiptap/react";
1808
+ import { IconButton as IconButton3 } from "@x12i/ui";
1809
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1810
+ function AtomFrame({ view, label, kind, children, onEdit, editLabel }) {
1811
+ const { editor, getPos, selected } = view;
1812
+ const ui = useEditorUi();
1813
+ const t = useTranslator();
1814
+ const m = t.messages;
1815
+ const toolbarRef = useRef5(null);
1816
+ const roving = useRovingToolbar(toolbarRef);
1817
+ const position2 = () => {
1818
+ const pos = getPos();
1819
+ return typeof pos === "number" ? pos : void 0;
1820
+ };
1821
+ const move = (direction) => {
1822
+ const pos = position2();
1823
+ if (pos === void 0) return;
1824
+ const result = moveBlock(editor, direction, pos);
1825
+ ui.announce(
1826
+ result.moved ? t.format(m.status.blockMoved, { position: result.position, total: result.total }) : m.status.cannotMove
1827
+ );
1828
+ editor.commands.focus();
1829
+ };
1830
+ const remove = () => {
1831
+ const pos = position2();
1832
+ if (pos === void 0) return;
1833
+ if (deleteBlock(editor, pos)) ui.announce(m.status.blockDeleted);
1834
+ editor.commands.focus();
1835
+ };
1836
+ const showTools = ui.editable && selected;
1837
+ return /* @__PURE__ */ jsxs4(
1838
+ NodeViewWrapper,
1839
+ {
1840
+ "data-kit": "editor-frame",
1841
+ "data-block": kind,
1842
+ "data-selected": selected ? "" : void 0,
1843
+ role: "group",
1844
+ "aria-label": label,
1845
+ children: [
1846
+ /* @__PURE__ */ jsx7("div", { "data-kit": "editor-frame-body", children }),
1847
+ showTools ? /* @__PURE__ */ jsxs4(
1848
+ "div",
1849
+ {
1850
+ ref: toolbarRef,
1851
+ role: "toolbar",
1852
+ "aria-label": m.embed.toolbar,
1853
+ "data-kit": "editor-frame-toolbar",
1854
+ contentEditable: false,
1855
+ ...roving,
1856
+ children: [
1857
+ onEdit ? /* @__PURE__ */ jsx7(IconButton3, { label: editLabel ?? m.embed.edit, title: editLabel ?? m.embed.edit, variant: "ghost", tone: "neutral", size: "sm", "data-toolbar-item": "", onClick: onEdit, children: /* @__PURE__ */ jsx7(Glyph, { name: "edit" }) }) : null,
1858
+ /* @__PURE__ */ jsx7(IconButton3, { label: m.embed.moveUp, title: m.embed.moveUp, variant: "ghost", tone: "neutral", size: "sm", "data-toolbar-item": "", onClick: () => move(-1), children: /* @__PURE__ */ jsx7(Glyph, { name: "up" }) }),
1859
+ /* @__PURE__ */ jsx7(IconButton3, { label: m.embed.moveDown, title: m.embed.moveDown, variant: "ghost", tone: "neutral", size: "sm", "data-toolbar-item": "", onClick: () => move(1), children: /* @__PURE__ */ jsx7(Glyph, { name: "down" }) }),
1860
+ /* @__PURE__ */ jsx7(IconButton3, { label: m.embed.remove, title: m.embed.remove, variant: "ghost", tone: "neutral", size: "sm", "data-toolbar-item": "", onClick: remove, children: /* @__PURE__ */ jsx7(Glyph, { name: "trash" }) })
1861
+ ]
1862
+ }
1863
+ ) : null
1864
+ ]
1865
+ }
1866
+ );
1867
+ }
1868
+
1869
+ // src/react/blocks/preview.tsx
1870
+ import { Component, createElement, useMemo } from "react";
1871
+ import { Badge } from "@x12i/ui";
1872
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1873
+ var EMBED_KIND = {
1874
+ chart: "chart",
1875
+ diagram: "diagram",
1876
+ "data-view": "dataView",
1877
+ ai: "ai"
1878
+ };
1879
+ var MAX_JSON_LENGTH = 4e3;
1880
+ function stringify(value) {
1881
+ try {
1882
+ const text = JSON.stringify(value, null, 2) ?? "";
1883
+ return text.length > MAX_JSON_LENGTH ? `${text.slice(0, MAX_JSON_LENGTH)}
1884
+ \u2026` : text;
1885
+ } catch {
1886
+ return "";
1887
+ }
1888
+ }
1889
+ function facts(payload) {
1890
+ if (typeof payload !== "object" || payload === null) return [];
1891
+ const out = [];
1892
+ for (const [key, value] of Object.entries(payload)) {
1893
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1894
+ const text = String(value);
1895
+ if (text !== "" && key !== "source") out.push([key, text.length > 120 ? `${text.slice(0, 120)}\u2026` : text]);
1896
+ } else if (Array.isArray(value)) {
1897
+ out.push([key, String(value.length)]);
1898
+ }
1899
+ if (out.length >= 6) break;
1900
+ }
1901
+ return out;
1902
+ }
1903
+ function describe(type, payload) {
1904
+ const value = payload ?? {};
1905
+ if (type === "chart") {
1906
+ const a11y = value.a11y;
1907
+ return typeof a11y?.description === "string" ? a11y.description : "";
1908
+ }
1909
+ if (type === "diagram") return typeof value.source === "string" ? value.source.split("\n").slice(0, 4).join("\n") : "";
1910
+ if (type === "ai") return typeof value.prompt === "string" ? value.prompt : "";
1911
+ return "";
1912
+ }
1913
+ function EmbedPreview({ type, payload, title }) {
1914
+ const m = useMessages();
1915
+ const json = useMemo(() => stringify(payload), [payload]);
1916
+ const summary = useMemo(() => facts(payload), [payload]);
1917
+ const description = describe(type, payload);
1918
+ return /* @__PURE__ */ jsxs5("div", { "data-kit": "editor-embed-card", "data-embed": type, children: [
1919
+ /* @__PURE__ */ jsxs5("div", { "data-kit": "editor-embed-head", children: [
1920
+ /* @__PURE__ */ jsx8(Badge, { tone: "accent", size: "sm", children: m.blocks[EMBED_KIND[type]].name }),
1921
+ /* @__PURE__ */ jsx8("strong", { "data-kit": "editor-embed-title", children: title || m.embed.untitled })
1922
+ ] }),
1923
+ description ? /* @__PURE__ */ jsx8("p", { "data-kit": "editor-embed-description", children: description }) : null,
1924
+ summary.length > 0 ? /* @__PURE__ */ jsx8("dl", { "data-kit": "editor-embed-facts", children: summary.map(([key, value]) => /* @__PURE__ */ jsxs5("div", { "data-kit": "editor-embed-fact", children: [
1925
+ /* @__PURE__ */ jsx8("dt", { children: key }),
1926
+ /* @__PURE__ */ jsx8("dd", { children: value })
1927
+ ] }, key)) }) : null,
1928
+ /* @__PURE__ */ jsxs5("details", { "data-kit": "editor-embed-data", children: [
1929
+ /* @__PURE__ */ jsx8("summary", { children: m.embed.showData }),
1930
+ /* @__PURE__ */ jsx8("pre", { tabIndex: 0, children: /* @__PURE__ */ jsx8("code", { children: json }) })
1931
+ ] }),
1932
+ /* @__PURE__ */ jsx8("p", { "data-kit": "editor-embed-hint", children: m.embed.previewHint })
1933
+ ] });
1934
+ }
1935
+ var RendererBoundary = class extends Component {
1936
+ state = { failed: false };
1937
+ static getDerivedStateFromError() {
1938
+ return { failed: true };
1939
+ }
1940
+ render() {
1941
+ return this.state.failed ? this.props.fallback : this.props.children;
1942
+ }
1943
+ };
1944
+ function EmbedBody({ type, id, payload, selected = false }) {
1945
+ const ui = useEditorUi();
1946
+ const title = payloadTitle(type, payload);
1947
+ const fallback = /* @__PURE__ */ jsx8(EmbedPreview, { type, payload, title });
1948
+ const renderer = ui.blockRenderers[type];
1949
+ if (!renderer) return fallback;
1950
+ return /* @__PURE__ */ jsx8(RendererBoundary, { fallback, children: createElement(renderer, { type, id, payload, title, editable: ui.editable, selected }) });
1951
+ }
1952
+ function embedFromAttrs(type, attrs) {
1953
+ const payload = attrsToPayload(type, attrs);
1954
+ return { payload, title: payloadTitle(type, payload) };
1955
+ }
1956
+
1957
+ // src/react/blocks/unknown-preview.tsx
1958
+ import { Badge as Badge2 } from "@x12i/ui";
1959
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1960
+ function UnknownPreview({ blockType, payload }) {
1961
+ const m = useMessages();
1962
+ let json;
1963
+ try {
1964
+ json = JSON.stringify(payload, null, 2) ?? "";
1965
+ } catch {
1966
+ json = "";
1967
+ }
1968
+ return /* @__PURE__ */ jsxs6("div", { "data-kit": "editor-embed-card", "data-embed": "unknown", children: [
1969
+ /* @__PURE__ */ jsx9("div", { "data-kit": "editor-embed-head", children: /* @__PURE__ */ jsx9(Badge2, { tone: "warning", size: "sm", children: blockType }) }),
1970
+ /* @__PURE__ */ jsx9("p", { "data-kit": "editor-embed-hint", children: m.embed.unknown }),
1971
+ /* @__PURE__ */ jsxs6("details", { "data-kit": "editor-embed-data", children: [
1972
+ /* @__PURE__ */ jsx9("summary", { children: m.embed.showData }),
1973
+ /* @__PURE__ */ jsx9("pre", { tabIndex: 0, children: /* @__PURE__ */ jsx9("code", { children: json.length > 4e3 ? `${json.slice(0, 4e3)}
1974
+ \u2026` : json }) })
1975
+ ] })
1976
+ ] });
1977
+ }
1978
+
1979
+ // src/react/blocks/embed.tsx
1980
+ import { jsx as jsx10 } from "react/jsx-runtime";
1981
+ function EmbedView({ view, type }) {
1982
+ const ui = useEditorUi();
1983
+ const t = useTranslator();
1984
+ const attrs = view.node.attrs;
1985
+ const { payload, title } = embedFromAttrs(type, attrs);
1986
+ const id = typeof attrs.id === "string" ? attrs.id : "";
1987
+ const kindName = t.messages.blocks[EMBED_KIND[type]].name;
1988
+ const label = t.format(t.messages.embed.groupLabel, { type: kindName, title: title || t.messages.embed.untitled });
1989
+ const onEdit = ui.editable && ui.onEditBlock ? () => {
1990
+ const handler = ui.onEditBlock;
1991
+ if (!handler) return;
1992
+ void Promise.resolve(handler(type, payload)).then((next) => {
1993
+ if (next === null || next === void 0) return;
1994
+ view.updateAttributes(payloadToAttrs(type, next));
1995
+ view.editor.commands.focus();
1996
+ });
1997
+ } : void 0;
1998
+ return /* @__PURE__ */ jsx10(AtomFrame, { view, kind: type, label, onEdit, children: /* @__PURE__ */ jsx10(EmbedBody, { type, id, payload, selected: view.selected }) });
1999
+ }
2000
+ function make(type) {
2001
+ return function EmbedNodeView(view) {
2002
+ return createElement2(EmbedView, { view, type });
2003
+ };
2004
+ }
2005
+ var ChartView = make("chart");
2006
+ var DiagramView = make("diagram");
2007
+ var DataViewView = make("data-view");
2008
+ var AiBlockView = make("ai");
2009
+ function UnknownBlockView(view) {
2010
+ const ui = useEditorUi();
2011
+ const t = useTranslator();
2012
+ const attrs = view.node.attrs;
2013
+ const blockType = typeof attrs.blockType === "string" ? attrs.blockType : "unknown";
2014
+ const payload = typeof attrs.payload === "object" && attrs.payload !== null ? attrs.payload : {};
2015
+ const id = typeof attrs.id === "string" ? attrs.id : "";
2016
+ const custom = ui.blockRenderers.unknown;
2017
+ return /* @__PURE__ */ jsx10(AtomFrame, { view, kind: "unknown", label: `${t.messages.embed.unknown} ${blockType}`, children: custom ? createElement2(custom, {
2018
+ id,
2019
+ blockType,
2020
+ payload,
2021
+ editable: ui.editable
2022
+ }) : /* @__PURE__ */ jsx10(UnknownPreview, { blockType, payload }) });
2023
+ }
2024
+
2025
+ // src/react/blocks/media.tsx
2026
+ import { useId as useId2, useState as useState5 } from "react";
2027
+ import { Button as Button2 } from "@x12i/ui";
2028
+
2029
+ // src/react/internal/inline.tsx
2030
+ import { Fragment as Fragment2 } from "react";
2031
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2032
+ var APPLY_ORDER = ["code", "strike", "underline", "italic", "bold", "highlight", "color", "link"];
2033
+ function wrap(children, mark, key) {
2034
+ switch (mark.type) {
2035
+ case "bold":
2036
+ return /* @__PURE__ */ jsx11("strong", { children }, key);
2037
+ case "italic":
2038
+ return /* @__PURE__ */ jsx11("em", { children }, key);
2039
+ case "underline":
2040
+ return /* @__PURE__ */ jsx11("u", { children }, key);
2041
+ case "strike":
2042
+ return /* @__PURE__ */ jsx11("s", { children }, key);
2043
+ case "code":
2044
+ return /* @__PURE__ */ jsx11("code", { children }, key);
2045
+ case "link": {
2046
+ const href = sanitizeUrl(mark.href);
2047
+ return href === null ? /* @__PURE__ */ jsx11(Fragment2, { children }, key) : /* @__PURE__ */ jsx11("a", { href, rel: "noopener noreferrer", children }, key);
2048
+ }
2049
+ case "color":
2050
+ return isTextColorToken(mark.token) ? /* @__PURE__ */ jsx11("span", { "data-kit": "editor-text-color", "data-token": mark.token, children }, key) : /* @__PURE__ */ jsx11(Fragment2, { children }, key);
2051
+ case "highlight":
2052
+ return isHighlightToken(mark.token) ? /* @__PURE__ */ jsx11("mark", { "data-kit": "editor-highlight", "data-token": mark.token, children }, key) : /* @__PURE__ */ jsx11(Fragment2, { children }, key);
2053
+ default:
2054
+ return children;
2055
+ }
2056
+ }
2057
+ function renderNode(node, key) {
2058
+ switch (node.type) {
2059
+ case "text": {
2060
+ const lines = node.text.split("\n");
2061
+ let content = lines.map((line, index) => /* @__PURE__ */ jsxs7(Fragment2, { children: [
2062
+ index > 0 ? /* @__PURE__ */ jsx11("br", {}) : null,
2063
+ line
2064
+ ] }, index));
2065
+ const marks = node.marks ?? [];
2066
+ for (const type of APPLY_ORDER) {
2067
+ for (const mark of marks) if (mark.type === type) content = wrap(content, mark, `${key}-${type}`);
2068
+ }
2069
+ return /* @__PURE__ */ jsx11(Fragment2, { children: content }, key);
2070
+ }
2071
+ case "mention":
2072
+ return /* @__PURE__ */ jsxs7("span", { "data-kit": "editor-mention", "data-kind": node.kind, children: [
2073
+ node.kind === "person" ? "@" : "",
2074
+ node.label
2075
+ ] }, key);
2076
+ case "emoji": {
2077
+ const char = emojiForShortcode(node.shortcode);
2078
+ return char === null ? /* @__PURE__ */ jsx11(Fragment2, { children: `:${node.shortcode}:` }, key) : /* @__PURE__ */ jsx11("span", { "data-kit": "editor-emoji", role: "img", "aria-label": node.shortcode.replace(/[_-]+/g, " "), children: char }, key);
2079
+ }
2080
+ default:
2081
+ return null;
2082
+ }
2083
+ }
2084
+ function InlineContent({ inline }) {
2085
+ return /* @__PURE__ */ jsx11(Fragment3, { children: (inline ?? []).map((node, index) => renderNode(node, String(index))) });
2086
+ }
2087
+
2088
+ // src/react/blocks/media.tsx
2089
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2090
+ function asInline(value) {
2091
+ return Array.isArray(value) && value.length > 0 ? value : null;
2092
+ }
2093
+ function position(view) {
2094
+ const pos = view.getPos();
2095
+ return typeof pos === "number" ? pos : void 0;
2096
+ }
2097
+ function UrlForm({
2098
+ fields,
2099
+ onSubmit
2100
+ }) {
2101
+ const t = useTranslator();
2102
+ const idBase = useId2();
2103
+ const [values, setValues] = useState5({});
2104
+ const [error, setError] = useState5(null);
2105
+ const submit = (event) => {
2106
+ event.preventDefault();
2107
+ event.stopPropagation();
2108
+ setError(onSubmit(values));
2109
+ };
2110
+ return /* @__PURE__ */ jsxs8("form", { "data-kit": "editor-url-form", onSubmit: submit, noValidate: true, children: [
2111
+ fields.map((field, index) => /* @__PURE__ */ jsxs8("label", { "data-kit": "editor-field", children: [
2112
+ /* @__PURE__ */ jsx12("span", { "data-kit": "editor-field-label", children: field.label }),
2113
+ /* @__PURE__ */ jsx12(
2114
+ "input",
2115
+ {
2116
+ id: `${idBase}-${field.name}`,
2117
+ "data-kit": "editor-input",
2118
+ type: "text",
2119
+ inputMode: index === 0 ? "url" : "text",
2120
+ value: values[field.name] ?? "",
2121
+ required: field.required,
2122
+ "aria-invalid": error !== null && index === 0 ? true : void 0,
2123
+ "aria-describedby": error !== null && index === 0 ? `${idBase}-error` : void 0,
2124
+ onChange: (event) => setValues((previous) => ({ ...previous, [field.name]: event.target.value }))
2125
+ }
2126
+ )
2127
+ ] }, field.name)),
2128
+ /* @__PURE__ */ jsx12(Button2, { type: "submit", size: "sm", variant: "soft", tone: "accent", children: t.messages.media.set }),
2129
+ error ? /* @__PURE__ */ jsx12("p", { id: `${idBase}-error`, role: "alert", "data-kit": "editor-error", children: error }) : null
2130
+ ] });
2131
+ }
2132
+ function ImageView(view) {
2133
+ const ui = useEditorUi();
2134
+ const m = useTranslator().messages;
2135
+ const attrs = view.node.attrs;
2136
+ const src = sanitizeMediaUrl(attrs.src);
2137
+ const alt = typeof attrs.alt === "string" ? attrs.alt : "";
2138
+ const caption = asInline(attrs.caption);
2139
+ const align = attrs.align === "start" || attrs.align === "center" || attrs.align === "end" ? attrs.align : void 0;
2140
+ const width = typeof attrs.width === "number" && attrs.width > 0 ? attrs.width : void 0;
2141
+ const pos = position(view);
2142
+ return /* @__PURE__ */ jsx12(
2143
+ AtomFrame,
2144
+ {
2145
+ view,
2146
+ kind: "image",
2147
+ label: alt || m.media.imageDecorative,
2148
+ onEdit: ui.editable ? () => ui.openImageDialog(pos) : void 0,
2149
+ editLabel: m.media.imageEdit,
2150
+ children: /* @__PURE__ */ jsxs8("figure", { "data-kit": "editor-image", "data-align": align, children: [
2151
+ src ? /* @__PURE__ */ jsx12("img", { src, alt, style: width ? { inlineSize: width } : void 0 }) : /* @__PURE__ */ jsx12("p", { "data-kit": "editor-error", children: m.media.unsafe }),
2152
+ caption ? /* @__PURE__ */ jsx12("figcaption", { dir: "auto", children: /* @__PURE__ */ jsx12(InlineContent, { inline: caption }) }) : null
2153
+ ] })
2154
+ }
2155
+ );
2156
+ }
2157
+ function MediaView(view) {
2158
+ const ui = useEditorUi();
2159
+ const m = useTranslator().messages;
2160
+ const attrs = view.node.attrs;
2161
+ const kind = attrs.kind === "audio" ? "audio" : "video";
2162
+ const rawSrc = typeof attrs.src === "string" ? attrs.src : "";
2163
+ const src = rawSrc === "" ? null : sanitizeMediaUrl(rawSrc);
2164
+ const caption = asInline(attrs.caption);
2165
+ const name = kind === "audio" ? m.media.audio : m.media.video;
2166
+ return /* @__PURE__ */ jsx12(AtomFrame, { view, kind, label: name, children: /* @__PURE__ */ jsxs8("figure", { "data-kit": "editor-media", "data-kind": kind, children: [
2167
+ src ? kind === "audio" ? /* @__PURE__ */ jsx12("audio", { controls: true, src, "aria-label": name }) : /* @__PURE__ */ jsx12("video", { controls: true, src, "aria-label": name }) : ui.editable && rawSrc === "" ? /* @__PURE__ */ jsx12(
2168
+ UrlForm,
2169
+ {
2170
+ fields: [{ name: "src", label: m.media.urlLabel, required: true }],
2171
+ onSubmit: (values) => {
2172
+ const next = sanitizeMediaUrl(normalizeUrlInput(values.src ?? ""));
2173
+ if (next === null) return m.media.unsafe;
2174
+ view.updateAttributes({ src: next });
2175
+ return null;
2176
+ }
2177
+ }
2178
+ ) : /* @__PURE__ */ jsx12("p", { "data-kit": "editor-error", children: m.media.unsafe }),
2179
+ caption ? /* @__PURE__ */ jsx12("figcaption", { dir: "auto", children: /* @__PURE__ */ jsx12(InlineContent, { inline: caption }) }) : null
2180
+ ] }) });
2181
+ }
2182
+ function formatSize(bytes, locale) {
2183
+ const units = [
2184
+ [1024 ** 3, "gigabyte"],
2185
+ [1024 ** 2, "megabyte"],
2186
+ [1024, "kilobyte"]
2187
+ ];
2188
+ const [factor, unit] = units.find(([size]) => bytes >= size) ?? [1, "byte"];
2189
+ try {
2190
+ return new Intl.NumberFormat(locale, { style: "unit", unit, unitDisplay: "narrow", maximumFractionDigits: 1 }).format(bytes / factor);
2191
+ } catch {
2192
+ return String(bytes);
2193
+ }
2194
+ }
2195
+ function FileView(view) {
2196
+ const ui = useEditorUi();
2197
+ const t = useTranslator();
2198
+ const m = t.messages;
2199
+ const attrs = view.node.attrs;
2200
+ const rawSrc = typeof attrs.src === "string" ? attrs.src : "";
2201
+ const href = rawSrc === "" ? null : sanitizeUrl(rawSrc);
2202
+ const name = typeof attrs.name === "string" && attrs.name !== "" ? attrs.name : m.media.file;
2203
+ const size = typeof attrs.size === "number" && attrs.size >= 0 ? attrs.size : null;
2204
+ return /* @__PURE__ */ jsx12(AtomFrame, { view, kind: "file", label: name, children: href ? /* @__PURE__ */ jsxs8("a", { "data-kit": "editor-file", href, download: name, rel: "noopener noreferrer", "aria-label": t.format(m.media.download, { name }), children: [
2205
+ /* @__PURE__ */ jsx12(Glyph, { name: "file" }),
2206
+ /* @__PURE__ */ jsx12("span", { "data-kit": "editor-file-name", dir: "auto", children: name }),
2207
+ size !== null ? /* @__PURE__ */ jsx12("span", { "data-kit": "editor-file-size", children: formatSize(size, t.locale) }) : null
2208
+ ] }) : ui.editable && rawSrc === "" ? /* @__PURE__ */ jsx12(
2209
+ UrlForm,
2210
+ {
2211
+ fields: [
2212
+ { name: "src", label: m.media.urlLabel, required: true },
2213
+ { name: "name", label: m.media.nameLabel }
2214
+ ],
2215
+ onSubmit: (values) => {
2216
+ const next = sanitizeUrl(normalizeUrlInput(values.src ?? ""));
2217
+ if (next === null) return m.media.unsafe;
2218
+ view.updateAttributes({ src: next, name: (values.name ?? "").trim() || next });
2219
+ return null;
2220
+ }
2221
+ }
2222
+ ) : /* @__PURE__ */ jsx12("p", { "data-kit": "editor-error", children: m.media.unsafe }) });
2223
+ }
2224
+ function BookmarkView(view) {
2225
+ const ui = useEditorUi();
2226
+ const t = useTranslator();
2227
+ const m = t.messages;
2228
+ const attrs = view.node.attrs;
2229
+ const rawUrl = typeof attrs.url === "string" ? attrs.url : "";
2230
+ const href = rawUrl === "" ? null : sanitizeUrl(rawUrl);
2231
+ const title = typeof attrs.title === "string" && attrs.title !== "" ? attrs.title : rawUrl;
2232
+ const description = typeof attrs.description === "string" ? attrs.description : "";
2233
+ let host = "";
2234
+ if (href) {
2235
+ try {
2236
+ host = new URL(href).host;
2237
+ } catch {
2238
+ host = "";
2239
+ }
2240
+ }
2241
+ return /* @__PURE__ */ jsx12(AtomFrame, { view, kind: "bookmark", label: title || m.media.bookmark, children: href ? /* @__PURE__ */ jsxs8("a", { "data-kit": "editor-bookmark", href, rel: "noopener noreferrer", "aria-label": t.format(m.media.open, { title }), children: [
2242
+ /* @__PURE__ */ jsx12(Glyph, { name: "bookmark" }),
2243
+ /* @__PURE__ */ jsxs8("span", { "data-kit": "editor-bookmark-text", children: [
2244
+ /* @__PURE__ */ jsx12("strong", { dir: "auto", children: title }),
2245
+ description ? /* @__PURE__ */ jsx12("span", { dir: "auto", children: description }) : null,
2246
+ host ? /* @__PURE__ */ jsx12("small", { children: host }) : null
2247
+ ] })
2248
+ ] }) : ui.editable && rawUrl === "" ? /* @__PURE__ */ jsx12(
2249
+ UrlForm,
2250
+ {
2251
+ fields: [
2252
+ { name: "url", label: m.media.urlLabel, required: true },
2253
+ { name: "title", label: m.media.nameLabel }
2254
+ ],
2255
+ onSubmit: (values) => {
2256
+ const next = sanitizeUrl(normalizeUrlInput(values.url ?? ""));
2257
+ if (next === null) return m.media.unsafe;
2258
+ const label = (values.title ?? "").trim();
2259
+ view.updateAttributes({ url: next, title: label === "" ? null : label });
2260
+ return null;
2261
+ }
2262
+ }
2263
+ ) : /* @__PURE__ */ jsx12("p", { "data-kit": "editor-error", children: m.media.unsafe }) });
2264
+ }
2265
+
2266
+ // src/react/blocks/text.tsx
2267
+ import { useEffect as useEffect5, useRef as useRef6, useState as useState6 } from "react";
2268
+ import { NodeViewContent, NodeViewWrapper as NodeViewWrapper2 } from "@tiptap/react";
2269
+
2270
+ // src/react/extensions/toggle-registry.ts
2271
+ var registry = /* @__PURE__ */ new WeakMap();
2272
+ function registerToggle(editor, id, control) {
2273
+ let controls = registry.get(editor);
2274
+ if (!controls) {
2275
+ controls = /* @__PURE__ */ new Map();
2276
+ registry.set(editor, controls);
2277
+ }
2278
+ controls.set(id, control);
2279
+ return () => {
2280
+ const current = registry.get(editor);
2281
+ if (current?.get(id) === control) current.delete(id);
2282
+ };
2283
+ }
2284
+ function toggleControl(editor, id) {
2285
+ return registry.get(editor)?.get(id);
2286
+ }
2287
+
2288
+ // src/react/internal/languages.ts
2289
+ var CODE_LANGUAGES = [
2290
+ "bash",
2291
+ "c",
2292
+ "cpp",
2293
+ "csharp",
2294
+ "css",
2295
+ "diff",
2296
+ "go",
2297
+ "html",
2298
+ "java",
2299
+ "javascript",
2300
+ "json",
2301
+ "kotlin",
2302
+ "markdown",
2303
+ "php",
2304
+ "python",
2305
+ "ruby",
2306
+ "rust",
2307
+ "sql",
2308
+ "swift",
2309
+ "typescript",
2310
+ "yaml"
2311
+ ];
2312
+ var LANGUAGE_NAMES = {
2313
+ cpp: "C++",
2314
+ csharp: "C#",
2315
+ c: "C",
2316
+ css: "CSS",
2317
+ html: "HTML",
2318
+ javascript: "JavaScript",
2319
+ json: "JSON",
2320
+ php: "PHP",
2321
+ sql: "SQL",
2322
+ typescript: "TypeScript",
2323
+ yaml: "YAML",
2324
+ bash: "Bash",
2325
+ go: "Go",
2326
+ java: "Java",
2327
+ kotlin: "Kotlin",
2328
+ markdown: "Markdown",
2329
+ python: "Python",
2330
+ ruby: "Ruby",
2331
+ rust: "Rust",
2332
+ swift: "Swift",
2333
+ diff: "Diff"
2334
+ };
2335
+ function languageName(language) {
2336
+ return LANGUAGE_NAMES[language] ?? language;
2337
+ }
2338
+
2339
+ // src/react/blocks/text.tsx
2340
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
2341
+ function variantOf(value) {
2342
+ return CALLOUT_VARIANTS.find((variant) => variant === value) ?? "info";
2343
+ }
2344
+ function CalloutView({ node, updateAttributes }) {
2345
+ const ui = useEditorUi();
2346
+ const m = useTranslator().messages;
2347
+ const variant = variantOf(node.attrs.variant);
2348
+ return /* @__PURE__ */ jsxs9(NodeViewWrapper2, { "data-kit": "editor-callout", "data-variant": variant, role: "note", "aria-label": m.callouts[variant], children: [
2349
+ /* @__PURE__ */ jsx13("span", { "data-kit": "editor-callout-icon", contentEditable: false, children: /* @__PURE__ */ jsx13(Glyph, { name: variant }) }),
2350
+ /* @__PURE__ */ jsx13(NodeViewContent, { "data-kit": "editor-callout-content", dir: "auto" }),
2351
+ ui.editable ? /* @__PURE__ */ jsx13(
2352
+ "select",
2353
+ {
2354
+ "data-kit": "editor-callout-kind",
2355
+ contentEditable: false,
2356
+ "aria-label": m.blocks.callout.name,
2357
+ value: variant,
2358
+ onChange: (event) => updateAttributes({ variant: variantOf(event.target.value) }),
2359
+ children: CALLOUT_VARIANTS.map((option) => /* @__PURE__ */ jsx13("option", { value: option, children: m.callouts[option] }, option))
2360
+ }
2361
+ ) : null
2362
+ ] });
2363
+ }
2364
+ function ToggleView({ editor, node }) {
2365
+ const m = useTranslator().messages;
2366
+ const [open, setOpen] = useState6(true);
2367
+ const id = typeof node.attrs.id === "string" ? node.attrs.id : "";
2368
+ useEffect5(() => {
2369
+ if (id === "") return;
2370
+ return registerToggle(editor, id, { toggle: () => setOpen((value) => !value), open: () => setOpen(true) });
2371
+ }, [editor, id]);
2372
+ return /* @__PURE__ */ jsxs9(NodeViewWrapper2, { "data-kit": "editor-toggle", "data-open": open ? "true" : "false", children: [
2373
+ /* @__PURE__ */ jsx13(
2374
+ "button",
2375
+ {
2376
+ type: "button",
2377
+ "data-kit": "editor-toggle-button",
2378
+ contentEditable: false,
2379
+ "aria-expanded": open,
2380
+ "aria-label": open ? m.toggle.collapse : m.toggle.expand,
2381
+ title: open ? m.toggle.collapse : m.toggle.expand,
2382
+ onMouseDown: (event) => event.preventDefault(),
2383
+ onClick: () => setOpen((value) => !value),
2384
+ children: /* @__PURE__ */ jsx13(Glyph, { name: "chevron", "data-open": open ? "true" : "false" })
2385
+ }
2386
+ ),
2387
+ /* @__PURE__ */ jsx13(NodeViewContent, { "data-kit": "editor-toggle-content" })
2388
+ ] });
2389
+ }
2390
+ function CodeBlockView({ editor, node, updateAttributes }) {
2391
+ const ui = useEditorUi();
2392
+ const m = useTranslator().messages;
2393
+ const language = typeof node.attrs.language === "string" ? node.attrs.language : "";
2394
+ const known = language === "" || CODE_LANGUAGES.includes(language);
2395
+ const [copied, setCopied] = useState6(false);
2396
+ const timer = useRef6(void 0);
2397
+ useEffect5(() => () => clearTimeout(timer.current), []);
2398
+ const copy = () => {
2399
+ const text = node.textContent;
2400
+ const done = () => {
2401
+ setCopied(true);
2402
+ ui.announce(m.status.copied);
2403
+ clearTimeout(timer.current);
2404
+ timer.current = setTimeout(() => setCopied(false), 2e3);
2405
+ };
2406
+ if (typeof navigator !== "undefined" && navigator.clipboard) {
2407
+ void navigator.clipboard.writeText(text).then(done, () => void 0);
2408
+ }
2409
+ editor.commands.focus();
2410
+ };
2411
+ return /* @__PURE__ */ jsxs9(NodeViewWrapper2, { "data-kit": "editor-code", "data-language": language || void 0, children: [
2412
+ /* @__PURE__ */ jsxs9("div", { "data-kit": "editor-code-bar", contentEditable: false, children: [
2413
+ ui.editable ? /* @__PURE__ */ jsxs9(
2414
+ "select",
2415
+ {
2416
+ "data-kit": "editor-code-language",
2417
+ "aria-label": m.code.language,
2418
+ value: language,
2419
+ onChange: (event) => updateAttributes({ language: event.target.value === "" ? null : event.target.value }),
2420
+ children: [
2421
+ /* @__PURE__ */ jsx13("option", { value: "", children: m.code.plainText }),
2422
+ known ? null : /* @__PURE__ */ jsx13("option", { value: language, children: language }),
2423
+ CODE_LANGUAGES.map((option) => /* @__PURE__ */ jsx13("option", { value: option, children: languageName(option) }, option))
2424
+ ]
2425
+ }
2426
+ ) : /* @__PURE__ */ jsx13("span", { "data-kit": "editor-code-language-label", children: language ? languageName(language) : m.code.plainText }),
2427
+ /* @__PURE__ */ jsxs9("button", { type: "button", "data-kit": "editor-code-copy", onMouseDown: (event) => event.preventDefault(), onClick: copy, children: [
2428
+ /* @__PURE__ */ jsx13(Glyph, { name: copied ? "check" : "copy" }),
2429
+ /* @__PURE__ */ jsx13("span", { children: copied ? m.code.copied : m.code.copy })
2430
+ ] })
2431
+ ] }),
2432
+ /* @__PURE__ */ jsx13("pre", { dir: "ltr", children: /* @__PURE__ */ jsx13(NodeViewContent, { as: "code" }) })
2433
+ ] });
2434
+ }
2435
+ function collectHeadings(editor) {
2436
+ const out = [];
2437
+ editor.state.doc.descendants((node) => {
2438
+ if (node.type.name !== "heading") return;
2439
+ const text = node.textContent.trim();
2440
+ const level = Number(node.attrs.level);
2441
+ if (text !== "" && typeof node.attrs.id === "string") out.push({ id: node.attrs.id, level, text });
2442
+ });
2443
+ return out;
2444
+ }
2445
+ function sameHeadings(a, b) {
2446
+ return a.length === b.length && a.every((item, index) => item.id === b[index]?.id && item.text === b[index]?.text && item.level === b[index]?.level);
2447
+ }
2448
+ function Entries({ nodes, onJump }) {
2449
+ return /* @__PURE__ */ jsx13("ol", { children: nodes.map((item) => /* @__PURE__ */ jsxs9("li", { children: [
2450
+ /* @__PURE__ */ jsx13(
2451
+ "a",
2452
+ {
2453
+ href: `#${slugifyHeading(item.text)}`,
2454
+ dir: "auto",
2455
+ onClick: (event) => {
2456
+ event.preventDefault();
2457
+ onJump(item.id);
2458
+ },
2459
+ children: item.text
2460
+ }
2461
+ ),
2462
+ item.children.length > 0 ? /* @__PURE__ */ jsx13(Entries, { nodes: item.children, onJump }) : null
2463
+ ] }, item.id)) });
2464
+ }
2465
+ function TableOfContentsView({ editor }) {
2466
+ const m = useTranslator().messages;
2467
+ const [headings, setHeadings] = useState6(() => collectHeadings(editor));
2468
+ useEffect5(() => {
2469
+ const update = () => setHeadings((previous) => {
2470
+ const next = collectHeadings(editor);
2471
+ return sameHeadings(previous, next) ? previous : next;
2472
+ });
2473
+ update();
2474
+ editor.on("update", update);
2475
+ return () => {
2476
+ editor.off("update", update);
2477
+ };
2478
+ }, [editor]);
2479
+ const jump = (id) => {
2480
+ let target = -1;
2481
+ editor.state.doc.descendants((node, pos) => {
2482
+ if (target === -1 && node.type.name === "heading" && node.attrs.id === id) target = pos;
2483
+ return target === -1;
2484
+ });
2485
+ if (target === -1) return;
2486
+ editor.chain().focus().setTextSelection(target + 1).scrollIntoView().run();
2487
+ };
2488
+ const tree = buildOutlineTree(headings);
2489
+ return /* @__PURE__ */ jsx13(NodeViewWrapper2, { "data-kit": "editor-toc", role: "navigation", "aria-label": m.blocks.toc.name, contentEditable: false, children: tree.length > 0 ? /* @__PURE__ */ jsx13(Entries, { nodes: tree, onJump: jump }) : /* @__PURE__ */ jsx13("p", { "data-kit": "editor-toc-empty", children: m.blocks.toc.description }) });
2490
+ }
2491
+
2492
+ // src/react/extensions/nodes.ts
2493
+ var KitDocument = Node2.create({
2494
+ name: "doc",
2495
+ topNode: true,
2496
+ content: "block+"
2497
+ });
2498
+ var Callout = Node2.create({
2499
+ name: "callout",
2500
+ group: "block",
2501
+ content: "inline*",
2502
+ defining: true,
2503
+ addAttributes() {
2504
+ return {
2505
+ variant: {
2506
+ default: "info",
2507
+ parseHTML: (element) => CALLOUT_VARIANTS.find((variant) => variant === element.getAttribute("data-variant")) ?? "info",
2508
+ renderHTML: (attributes) => ({ "data-variant": String(attributes.variant ?? "info") })
2509
+ }
2510
+ };
2511
+ },
2512
+ parseHTML() {
2513
+ return [{ tag: 'div[data-kit="editor-callout"]' }];
2514
+ },
2515
+ renderHTML({ HTMLAttributes }) {
2516
+ return ["div", mergeAttributes2({ "data-kit": "editor-callout", role: "note" }, HTMLAttributes), 0];
2517
+ },
2518
+ addNodeView() {
2519
+ return ReactNodeViewRenderer(CalloutView);
2520
+ },
2521
+ addKeyboardShortcuts() {
2522
+ const leave = () => {
2523
+ const { selection } = this.editor.state;
2524
+ const { $anchor, empty } = selection;
2525
+ if (!empty || $anchor.parent.type.name !== this.name || $anchor.parent.content.size !== 0) return false;
2526
+ return this.editor.commands.setNode("paragraph");
2527
+ };
2528
+ return { Enter: leave, Backspace: leave };
2529
+ }
2530
+ });
2531
+ var ToggleSummary = Node2.create({
2532
+ name: "toggleSummary",
2533
+ content: "inline*",
2534
+ defining: true,
2535
+ parseHTML() {
2536
+ return [{ tag: 'div[data-kit="editor-toggle-summary"]' }];
2537
+ },
2538
+ renderHTML({ HTMLAttributes }) {
2539
+ return ["div", mergeAttributes2({ "data-kit": "editor-toggle-summary" }, HTMLAttributes), 0];
2540
+ },
2541
+ addKeyboardShortcuts() {
2542
+ return {
2543
+ // Enter in the summary opens the toggle and continues in its first block.
2544
+ Enter: () => {
2545
+ const { $from, empty } = this.editor.state.selection;
2546
+ if (!empty || $from.parent.type.name !== this.name) return false;
2547
+ const toggle = $from.node($from.depth - 1);
2548
+ const id = toggle.attrs.id;
2549
+ if (typeof id === "string") toggleControl(this.editor, id)?.open();
2550
+ const after = $from.after();
2551
+ return this.editor.commands.command(({ tr }) => {
2552
+ tr.setSelection(TextSelection3.near(tr.doc.resolve(after), 1));
2553
+ return true;
2554
+ });
2555
+ }
2556
+ };
2557
+ }
2558
+ });
2559
+ var Toggle = Node2.create({
2560
+ name: "toggle",
2561
+ group: "block",
2562
+ content: "toggleSummary block+",
2563
+ defining: true,
2564
+ parseHTML() {
2565
+ return [{ tag: 'div[data-kit="editor-toggle"]' }];
2566
+ },
2567
+ renderHTML({ HTMLAttributes }) {
2568
+ return ["div", mergeAttributes2({ "data-kit": "editor-toggle", "data-open": "true" }, HTMLAttributes), 0];
2569
+ },
2570
+ addNodeView() {
2571
+ return ReactNodeViewRenderer(ToggleView);
2572
+ },
2573
+ addKeyboardShortcuts() {
2574
+ return {
2575
+ "Alt-Enter": () => {
2576
+ const { $from } = this.editor.state.selection;
2577
+ for (let depth = $from.depth; depth >= 0; depth -= 1) {
2578
+ const node = $from.node(depth);
2579
+ if (node.type.name !== this.name) continue;
2580
+ const id = node.attrs.id;
2581
+ if (typeof id !== "string") return false;
2582
+ const control = toggleControl(this.editor, id);
2583
+ control?.toggle();
2584
+ return control !== void 0;
2585
+ }
2586
+ return false;
2587
+ }
2588
+ };
2589
+ }
2590
+ });
2591
+ var Columns = Node2.create({
2592
+ name: "columns",
2593
+ group: "block",
2594
+ content: "column+",
2595
+ defining: true,
2596
+ isolating: true,
2597
+ parseHTML() {
2598
+ return [{ tag: 'div[data-kit="editor-columns"]' }];
2599
+ },
2600
+ renderHTML({ HTMLAttributes }) {
2601
+ return ["div", mergeAttributes2({ "data-kit": "editor-columns" }, HTMLAttributes), 0];
2602
+ }
2603
+ });
2604
+ var Column = Node2.create({
2605
+ name: "column",
2606
+ content: "block+",
2607
+ defining: true,
2608
+ isolating: true,
2609
+ parseHTML() {
2610
+ return [{ tag: 'div[data-kit="editor-column"]' }];
2611
+ },
2612
+ renderHTML({ HTMLAttributes }) {
2613
+ return ["div", mergeAttributes2({ "data-kit": "editor-column" }, HTMLAttributes), 0];
2614
+ }
2615
+ });
2616
+ var Equation = Node2.create({
2617
+ name: "equation",
2618
+ group: "block",
2619
+ content: "text*",
2620
+ marks: "",
2621
+ code: true,
2622
+ defining: true,
2623
+ parseHTML() {
2624
+ return [{ tag: 'div[data-kit="editor-equation"]', preserveWhitespace: "full" }];
2625
+ },
2626
+ renderHTML({ HTMLAttributes }) {
2627
+ return ["div", mergeAttributes2({ "data-kit": "editor-equation", dir: "ltr" }, HTMLAttributes), ["code", 0]];
2628
+ }
2629
+ });
2630
+ var FENCE_LANGUAGE = "([a-zA-Z0-9+#_-]*)";
2631
+ var CodeBlock = Node2.create({
2632
+ name: "codeBlock",
2633
+ group: "block",
2634
+ content: "text*",
2635
+ marks: "",
2636
+ code: true,
2637
+ defining: true,
2638
+ addAttributes() {
2639
+ return {
2640
+ language: {
2641
+ default: null,
2642
+ parseHTML: (element) => {
2643
+ const direct = element.getAttribute("data-language");
2644
+ if (direct) return direct;
2645
+ const match = /language-([\w+#-]+)/.exec(element.querySelector("code")?.getAttribute("class") ?? "");
2646
+ return match?.[1] ?? null;
2647
+ },
2648
+ renderHTML: (attributes) => typeof attributes.language === "string" && attributes.language !== "" ? { "data-language": attributes.language } : {}
2649
+ }
2650
+ };
2651
+ },
2652
+ parseHTML() {
2653
+ return [{ tag: "pre", preserveWhitespace: "full" }];
2654
+ },
2655
+ renderHTML({ HTMLAttributes }) {
2656
+ return ["pre", mergeAttributes2({ dir: "ltr" }, HTMLAttributes), ["code", 0]];
2657
+ },
2658
+ addNodeView() {
2659
+ return ReactNodeViewRenderer(CodeBlockView);
2660
+ },
2661
+ addKeyboardShortcuts() {
2662
+ return {
2663
+ "Mod-Alt-c": () => this.editor.commands.toggleNode(this.name, "paragraph"),
2664
+ // Backspace at the very start turns the block back into a paragraph.
2665
+ Backspace: () => {
2666
+ const { empty, $anchor } = this.editor.state.selection;
2667
+ if (!empty || $anchor.parent.type.name !== this.name || $anchor.parentOffset !== 0) return false;
2668
+ return this.editor.commands.clearNodes();
2669
+ },
2670
+ // Mod+Enter leaves the block (Tab stays free to move focus out of the editor).
2671
+ "Mod-Enter": () => {
2672
+ const { $from } = this.editor.state.selection;
2673
+ if ($from.parent.type.name !== this.name) return false;
2674
+ return this.editor.commands.exitCode();
2675
+ }
2676
+ };
2677
+ },
2678
+ addInputRules() {
2679
+ return [
2680
+ textblockTypeInputRule({
2681
+ find: new RegExp(`^\`\`\`${FENCE_LANGUAGE}[\\s\\n]$`),
2682
+ type: this.type,
2683
+ getAttributes: (match) => ({ language: match[1] || null })
2684
+ }),
2685
+ textblockTypeInputRule({
2686
+ find: new RegExp(`^~~~${FENCE_LANGUAGE}[\\s\\n]$`),
2687
+ type: this.type,
2688
+ getAttributes: (match) => ({ language: match[1] || null })
2689
+ })
2690
+ ];
2691
+ }
2692
+ });
2693
+ var PageBreak = Node2.create({
2694
+ name: "pageBreak",
2695
+ group: "block",
2696
+ atom: true,
2697
+ selectable: true,
2698
+ parseHTML() {
2699
+ return [{ tag: 'hr[data-kit="editor-page-break"]' }];
2700
+ },
2701
+ renderHTML({ HTMLAttributes }) {
2702
+ return ["hr", mergeAttributes2({ "data-kit": "editor-page-break" }, HTMLAttributes)];
2703
+ }
2704
+ });
2705
+ var TableOfContents = Node2.create({
2706
+ name: "tableOfContents",
2707
+ group: "block",
2708
+ atom: true,
2709
+ selectable: true,
2710
+ parseHTML() {
2711
+ return [{ tag: 'div[data-kit="editor-toc"]' }];
2712
+ },
2713
+ renderHTML({ HTMLAttributes }) {
2714
+ return ["div", mergeAttributes2({ "data-kit": "editor-toc" }, HTMLAttributes)];
2715
+ },
2716
+ addNodeView() {
2717
+ return ReactNodeViewRenderer(TableOfContentsView);
2718
+ }
2719
+ });
2720
+ var KitImage = Image.extend({
2721
+ name: "image",
2722
+ draggable: false,
2723
+ addAttributes() {
2724
+ return {
2725
+ src: { default: null },
2726
+ alt: { default: "" },
2727
+ caption: jsonAttribute("caption"),
2728
+ align: dataAttribute("align"),
2729
+ width: dataAttribute("width", null, (raw) => {
2730
+ const value = Number(raw);
2731
+ return Number.isFinite(value) && value > 0 ? value : null;
2732
+ })
2733
+ };
2734
+ },
2735
+ addNodeView() {
2736
+ return ReactNodeViewRenderer(ImageView);
2737
+ }
2738
+ });
2739
+ var Media = Node2.create({
2740
+ name: "media",
2741
+ group: "block",
2742
+ atom: true,
2743
+ selectable: true,
2744
+ addAttributes() {
2745
+ return {
2746
+ kind: dataAttribute("kind", "video"),
2747
+ src: dataAttribute("src", ""),
2748
+ caption: jsonAttribute("caption")
2749
+ };
2750
+ },
2751
+ parseHTML() {
2752
+ return [{ tag: 'div[data-kit="editor-media-block"]' }];
2753
+ },
2754
+ renderHTML({ HTMLAttributes }) {
2755
+ return ["div", mergeAttributes2({ "data-kit": "editor-media-block" }, HTMLAttributes)];
2756
+ },
2757
+ addNodeView() {
2758
+ return ReactNodeViewRenderer(MediaView);
2759
+ }
2760
+ });
2761
+ var FileBlock = Node2.create({
2762
+ name: "file",
2763
+ group: "block",
2764
+ atom: true,
2765
+ selectable: true,
2766
+ addAttributes() {
2767
+ return {
2768
+ src: dataAttribute("src", ""),
2769
+ name: dataAttribute("name", ""),
2770
+ size: dataAttribute("size", null, (raw) => {
2771
+ const value = Number(raw);
2772
+ return Number.isFinite(value) && value >= 0 ? value : null;
2773
+ })
2774
+ };
2775
+ },
2776
+ parseHTML() {
2777
+ return [{ tag: 'div[data-kit="editor-file-block"]' }];
2778
+ },
2779
+ renderHTML({ HTMLAttributes }) {
2780
+ return ["div", mergeAttributes2({ "data-kit": "editor-file-block" }, HTMLAttributes)];
2781
+ },
2782
+ addNodeView() {
2783
+ return ReactNodeViewRenderer(FileView);
2784
+ }
2785
+ });
2786
+ var Bookmark = Node2.create({
2787
+ name: "bookmark",
2788
+ group: "block",
2789
+ atom: true,
2790
+ selectable: true,
2791
+ addAttributes() {
2792
+ return {
2793
+ url: dataAttribute("url", ""),
2794
+ title: dataAttribute("title"),
2795
+ description: dataAttribute("description")
2796
+ };
2797
+ },
2798
+ parseHTML() {
2799
+ return [{ tag: 'div[data-kit="editor-bookmark-block"]' }];
2800
+ },
2801
+ renderHTML({ HTMLAttributes }) {
2802
+ return ["div", mergeAttributes2({ "data-kit": "editor-bookmark-block" }, HTMLAttributes)];
2803
+ },
2804
+ addNodeView() {
2805
+ return ReactNodeViewRenderer(BookmarkView);
2806
+ }
2807
+ });
2808
+ function createEmbed(name, type, attributes, view) {
2809
+ return Node2.create({
2810
+ name,
2811
+ group: "block",
2812
+ atom: true,
2813
+ selectable: true,
2814
+ isolating: true,
2815
+ addAttributes() {
2816
+ return attributes;
2817
+ },
2818
+ parseHTML() {
2819
+ return [{ tag: `div[data-kit="editor-embed-block"][data-embed="${type}"]` }];
2820
+ },
2821
+ renderHTML({ HTMLAttributes }) {
2822
+ return ["div", mergeAttributes2({ "data-kit": "editor-embed-block", "data-embed": type }, HTMLAttributes)];
2823
+ },
2824
+ addNodeView() {
2825
+ return ReactNodeViewRenderer(view);
2826
+ }
2827
+ });
2828
+ }
2829
+ var Chart = createEmbed("chart", "chart", { spec: jsonAttribute("spec", {}) }, ChartView);
2830
+ var Diagram = createEmbed(
2831
+ "diagram",
2832
+ "diagram",
2833
+ {
2834
+ engine: dataAttribute("engine", "mermaid"),
2835
+ diagram: jsonAttribute("diagram"),
2836
+ source: dataAttribute("source")
2837
+ },
2838
+ DiagramView
2839
+ );
2840
+ var DataView = createEmbed(
2841
+ "dataView",
2842
+ "data-view",
2843
+ { dataset: dataAttribute("dataset", ""), view: jsonAttribute("view", {}) },
2844
+ DataViewView
2845
+ );
2846
+ var AiBlock = createEmbed(
2847
+ "aiBlock",
2848
+ "ai",
2849
+ { prompt: dataAttribute("prompt", ""), result: jsonAttribute("result") },
2850
+ AiBlockView
2851
+ );
2852
+ var UnknownBlock = Node2.create({
2853
+ name: "unknownBlock",
2854
+ group: "block",
2855
+ atom: true,
2856
+ selectable: true,
2857
+ addAttributes() {
2858
+ return {
2859
+ blockType: dataAttribute("block-type", "unknown"),
2860
+ payload: jsonAttribute("payload", {})
2861
+ };
2862
+ },
2863
+ parseHTML() {
2864
+ return [{ tag: 'div[data-kit="editor-unknown-block"]' }];
2865
+ },
2866
+ renderHTML({ HTMLAttributes }) {
2867
+ return ["div", mergeAttributes2({ "data-kit": "editor-unknown-block" }, HTMLAttributes)];
2868
+ },
2869
+ addNodeView() {
2870
+ return ReactNodeViewRenderer(UnknownBlockView);
2871
+ }
2872
+ });
2873
+ var Mention = Node2.create({
2874
+ name: "mention",
2875
+ group: "inline",
2876
+ inline: true,
2877
+ atom: true,
2878
+ selectable: true,
2879
+ addAttributes() {
2880
+ return {
2881
+ kind: dataAttribute("kind", "person"),
2882
+ id: dataAttribute("id", ""),
2883
+ label: dataAttribute("label", "")
2884
+ };
2885
+ },
2886
+ parseHTML() {
2887
+ return [{ tag: 'span[data-kit="editor-mention"]' }];
2888
+ },
2889
+ renderHTML({ node, HTMLAttributes }) {
2890
+ const prefix = node.attrs.kind === "person" ? "@" : "";
2891
+ return ["span", mergeAttributes2({ "data-kit": "editor-mention" }, HTMLAttributes), `${prefix}${String(node.attrs.label ?? "")}`];
2892
+ },
2893
+ renderText({ node }) {
2894
+ return `${node.attrs.kind === "person" ? "@" : ""}${String(node.attrs.label ?? "")}`;
2895
+ }
2896
+ });
2897
+ var Emoji = Node2.create({
2898
+ name: "emoji",
2899
+ group: "inline",
2900
+ inline: true,
2901
+ atom: true,
2902
+ selectable: true,
2903
+ addAttributes() {
2904
+ return { shortcode: dataAttribute("shortcode", "") };
2905
+ },
2906
+ parseHTML() {
2907
+ return [{ tag: 'span[data-kit="editor-emoji"]' }];
2908
+ },
2909
+ renderHTML({ node, HTMLAttributes }) {
2910
+ const shortcode = String(node.attrs.shortcode ?? "");
2911
+ return [
2912
+ "span",
2913
+ mergeAttributes2({ "data-kit": "editor-emoji", role: "img", "aria-label": shortcode.replace(/[_-]+/g, " ") }, HTMLAttributes),
2914
+ emojiForShortcode(shortcode) ?? `:${shortcode}:`
2915
+ ];
2916
+ },
2917
+ renderText({ node }) {
2918
+ const shortcode = String(node.attrs.shortcode ?? "");
2919
+ return emojiForShortcode(shortcode) ?? `:${shortcode}:`;
2920
+ },
2921
+ addInputRules() {
2922
+ return [
2923
+ new InputRule({
2924
+ find: /:([a-z0-9_+-]+):$/,
2925
+ handler: ({ state, range, match }) => {
2926
+ const shortcode = match[1];
2927
+ if (!shortcode || emojiForShortcode(shortcode) === null) return null;
2928
+ state.tr.replaceWith(range.from, range.to, this.type.create({ shortcode }));
2929
+ return void 0;
2930
+ }
2931
+ })
2932
+ ];
2933
+ }
2934
+ });
2935
+ var KitTable = Table.extend({
2936
+ addAttributes() {
2937
+ return {
2938
+ ...this.parent?.(),
2939
+ headerRow: { default: null, rendered: false },
2940
+ headerColumn: { default: null, rendered: false }
2941
+ };
2942
+ }
2943
+ }).configure({ resizable: false, HTMLAttributes: { "data-kit": "editor-table" } });
2944
+
2945
+ // src/react/extensions/slash.ts
2946
+ import { Extension as Extension3 } from "@tiptap/core";
2947
+ import { PluginKey as PluginKey2 } from "@tiptap/pm/state";
2948
+ import Suggestion from "@tiptap/suggestion";
2949
+
2950
+ // src/react/slash-items.ts
2951
+ var SLASH_GROUP_ORDER = ["basic", "lists", "media", "advanced", "embeds"];
2952
+ var GROUP_OF = {
2953
+ paragraph: "basic",
2954
+ heading1: "basic",
2955
+ heading2: "basic",
2956
+ heading3: "basic",
2957
+ heading4: "basic",
2958
+ quote: "basic",
2959
+ callout: "basic",
2960
+ toggle: "basic",
2961
+ code: "basic",
2962
+ equation: "basic",
2963
+ divider: "basic",
2964
+ bulletList: "lists",
2965
+ numberedList: "lists",
2966
+ taskList: "lists",
2967
+ image: "media",
2968
+ video: "media",
2969
+ audio: "media",
2970
+ file: "media",
2971
+ bookmark: "media",
2972
+ table: "advanced",
2973
+ columns: "advanced",
2974
+ pageBreak: "advanced",
2975
+ toc: "advanced",
2976
+ chart: "embeds",
2977
+ diagram: "embeds",
2978
+ dataView: "embeds",
2979
+ ai: "embeds"
2980
+ };
2981
+ function allSlashItems(messages) {
2982
+ const items = BLOCK_KINDS.map((kind) => ({
2983
+ kind,
2984
+ group: GROUP_OF[kind],
2985
+ name: messages.blocks[kind].name,
2986
+ description: messages.blocks[kind].description
2987
+ }));
2988
+ return SLASH_GROUP_ORDER.flatMap((group) => items.filter((item) => item.group === group));
2989
+ }
2990
+ function normalize(text) {
2991
+ return text.normalize("NFKD").replace(/\p{M}+/gu, "").toLowerCase();
2992
+ }
2993
+ function filterSlashItems(query, messages, items = allSlashItems(messages)) {
2994
+ const needle = normalize(query.trim());
2995
+ if (needle === "") return [...items];
2996
+ const scored = [];
2997
+ items.forEach((item, index) => {
2998
+ const name = normalize(item.name);
2999
+ const keywords = normalize(messages.blocks[item.kind].keywords);
3000
+ const description = normalize(item.description);
3001
+ const words = `${name} ${keywords}`.split(/\s+/);
3002
+ let score = -1;
3003
+ if (name.startsWith(needle)) score = 0;
3004
+ else if (words.some((word) => word.startsWith(needle))) score = 1;
3005
+ else if (name.includes(needle) || keywords.includes(needle)) score = 2;
3006
+ else if (description.includes(needle)) score = 3;
3007
+ if (score !== -1) scored.push({ item, score, index });
3008
+ });
3009
+ return scored.sort((a, b) => a.score - b.score || a.index - b.index).map((entry) => entry.item);
3010
+ }
3011
+
3012
+ // src/react/extensions/slash.ts
3013
+ var slashPluginKey = new PluginKey2("kitSlashMenu");
3014
+ async function runSlashItem(editor, range, item, runtime) {
3015
+ const ui = runtime.ui();
3016
+ const t = runtime.translator();
3017
+ editor.chain().focus().deleteRange(range).run();
3018
+ if (item.kind === "image") {
3019
+ ui.openImageDialog();
3020
+ return;
3021
+ }
3022
+ let attrs = {};
3023
+ const type = embedKindToType(item.kind);
3024
+ if (type && ui.onRequestBlock) {
3025
+ const payload = await ui.onRequestBlock(type);
3026
+ if (payload === null || payload === void 0) return;
3027
+ attrs = type === "chart" ? { spec: payload } : payload;
3028
+ editor.commands.focus();
3029
+ }
3030
+ if (applyBlockKind(editor, item.kind, attrs) !== null) {
3031
+ ui.announce(t.format(t.messages.status.blockTurnedInto, { name: item.name }));
3032
+ }
3033
+ }
3034
+ var SlashCommand = Extension3.create({
3035
+ name: "slashCommand",
3036
+ addOptions() {
3037
+ return { runtime: null };
3038
+ },
3039
+ addProseMirrorPlugins() {
3040
+ const runtime = this.options.runtime;
3041
+ if (!runtime) return [];
3042
+ return [
3043
+ Suggestion({
3044
+ editor: this.editor,
3045
+ pluginKey: slashPluginKey,
3046
+ char: "/",
3047
+ allowSpaces: false,
3048
+ allowedPrefixes: [" "],
3049
+ allow: ({ state, range }) => {
3050
+ if (!runtime.slashEnabled() || !runtime.ui().editable) return false;
3051
+ return !state.doc.resolve(range.from).parent.type.spec.code;
3052
+ },
3053
+ items: ({ query }) => {
3054
+ const messages = runtime.translator().messages;
3055
+ return filterSlashItems(query, messages, allSlashItems(messages));
3056
+ },
3057
+ command: ({ editor, range, props }) => {
3058
+ void runSlashItem(editor, range, props, runtime);
3059
+ },
3060
+ render: () => ({
3061
+ onStart: (props) => runtime.slash?.onStart(props),
3062
+ onUpdate: (props) => runtime.slash?.onUpdate(props),
3063
+ onKeyDown: (props) => runtime.slash?.onKeyDown(props) ?? false,
3064
+ onExit: () => runtime.slash?.onExit()
3065
+ })
3066
+ })
3067
+ ];
3068
+ }
3069
+ });
3070
+
3071
+ // src/react/extensions/index.ts
3072
+ var AUTO_DIRECTION_NODES = ["paragraph", "heading", "blockquote", "bulletList", "orderedList", "taskList", "tableCell", "tableHeader", "toggleSummary"];
3073
+ var AutoDirection = Extension4.create({
3074
+ name: "autoDirection",
3075
+ addGlobalAttributes() {
3076
+ return [
3077
+ {
3078
+ types: AUTO_DIRECTION_NODES,
3079
+ attributes: {
3080
+ dir: {
3081
+ default: null,
3082
+ keepOnSplit: false,
3083
+ parseHTML: () => null,
3084
+ renderHTML: () => ({ dir: "auto" })
3085
+ }
3086
+ }
3087
+ }
3088
+ ];
3089
+ }
3090
+ });
3091
+ function createExtensions({ runtime, extra = [] }) {
3092
+ return [
3093
+ KitDocument,
3094
+ StarterKit.configure({
3095
+ document: false,
3096
+ codeBlock: false,
3097
+ trailingNode: false,
3098
+ heading: { levels: [1, 2, 3, 4] },
3099
+ link: {
3100
+ openOnClick: false,
3101
+ autolink: true,
3102
+ linkOnPaste: true,
3103
+ isAllowedUri: (url) => sanitizeUrl(url) !== null,
3104
+ HTMLAttributes: { rel: "noopener noreferrer", target: null }
3105
+ }
3106
+ }),
3107
+ TextColor,
3108
+ KitHighlight,
3109
+ TaskList.configure({ HTMLAttributes: { "data-kit": "editor-task-list" } }),
3110
+ TaskItem.configure({
3111
+ nested: true,
3112
+ HTMLAttributes: { "data-kit": "editor-task-item" },
3113
+ a11y: {
3114
+ checkboxLabel: (node, checked) => {
3115
+ const t = runtime.translator();
3116
+ const text = node.textContent.trim() || t.messages.task.emptyLabel;
3117
+ return formatMessage(checked ? t.messages.task.done : t.messages.task.todo, { text }, t.locale);
3118
+ }
3119
+ }
3120
+ }),
3121
+ Callout,
3122
+ Toggle,
3123
+ ToggleSummary,
3124
+ Columns,
3125
+ Column,
3126
+ Equation,
3127
+ CodeBlock,
3128
+ KitTable,
3129
+ TableRow,
3130
+ TableHeader,
3131
+ TableCell,
3132
+ KitImage.configure({ allowBase64: true }),
3133
+ Media,
3134
+ FileBlock,
3135
+ Bookmark,
3136
+ PageBreak,
3137
+ TableOfContents,
3138
+ Chart,
3139
+ Diagram,
3140
+ DataView,
3141
+ AiBlock,
3142
+ UnknownBlock,
3143
+ Mention,
3144
+ Emoji,
3145
+ BlockId,
3146
+ AutoDirection,
3147
+ Placeholder.configure({
3148
+ includeChildren: true,
3149
+ // The engine calls this while it applies a transaction, when `editor.state` can still be the previous
3150
+ // state, so it must not look positions up in it. Paragraphs always get the text; the stylesheet only
3151
+ // shows it for paragraphs that are not inside lists, tables, quotes or callouts.
3152
+ placeholder: ({ node }) => {
3153
+ const messages = runtime.translator().messages;
3154
+ switch (node.type.name) {
3155
+ case "heading": {
3156
+ const level = Number(node.attrs.level);
3157
+ return level >= 1 && level <= 4 ? messages.blocks[`heading${level}`].name : "";
3158
+ }
3159
+ case "toggleSummary":
3160
+ return messages.toggle.summaryPlaceholder;
3161
+ case "equation":
3162
+ return messages.equation.placeholder;
3163
+ case "paragraph":
3164
+ return runtime.placeholder();
3165
+ default:
3166
+ return "";
3167
+ }
3168
+ }
3169
+ }),
3170
+ BlockKeys.configure({ runtime }),
3171
+ SlashCommand.configure({ runtime }),
3172
+ ...extra
3173
+ ];
3174
+ }
3175
+
3176
+ // src/react/extensions/runtime.ts
3177
+ function createRuntime() {
3178
+ return {
3179
+ ui: () => {
3180
+ throw new Error("The editor runtime is not connected.");
3181
+ },
3182
+ translator: () => {
3183
+ throw new Error("The editor runtime is not connected.");
3184
+ },
3185
+ placeholder: () => "",
3186
+ slashEnabled: () => false,
3187
+ slash: null
3188
+ };
3189
+ }
3190
+
3191
+ // src/react/image-dialog.tsx
3192
+ import { useRef as useRef8, useState as useState7 } from "react";
3193
+ import { Button as Button3 } from "@x12i/ui";
3194
+
3195
+ // src/react/internal/dialog.tsx
3196
+ import { useEffect as useEffect6, useId as useId3, useRef as useRef7 } from "react";
3197
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
3198
+ var FOCUSABLE = [
3199
+ "a[href]",
3200
+ "button:not([disabled])",
3201
+ "input:not([disabled]):not([type='hidden'])",
3202
+ "select:not([disabled])",
3203
+ "textarea:not([disabled])",
3204
+ "[tabindex]:not([tabindex='-1'])"
3205
+ ].join(",");
3206
+ function Dialog({ open, title, onClose, children, initialFocus, restoreFocus, describedBy }) {
3207
+ const titleId = useId3();
3208
+ const panelRef = useRef7(null);
3209
+ const restoreRef = useRef7(restoreFocus);
3210
+ restoreRef.current = restoreFocus;
3211
+ useEffect6(() => {
3212
+ if (!open) return;
3213
+ const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
3214
+ const panel = panelRef.current;
3215
+ const target = initialFocus?.current ?? panel?.querySelector(FOCUSABLE) ?? panel;
3216
+ target?.focus();
3217
+ const onFocusIn = (event) => {
3218
+ const current = panelRef.current;
3219
+ if (current && event.target instanceof Node && !current.contains(event.target)) {
3220
+ (current.querySelector(FOCUSABLE) ?? current).focus();
3221
+ }
3222
+ };
3223
+ document.addEventListener("focusin", onFocusIn);
3224
+ return () => {
3225
+ document.removeEventListener("focusin", onFocusIn);
3226
+ if (restoreRef.current) restoreRef.current();
3227
+ else if (previous?.isConnected) previous.focus();
3228
+ };
3229
+ }, [open]);
3230
+ if (!open) return null;
3231
+ const onKeyDown = (event) => {
3232
+ if (event.key === "Escape") {
3233
+ event.stopPropagation();
3234
+ event.preventDefault();
3235
+ onClose();
3236
+ return;
3237
+ }
3238
+ if (event.key !== "Tab") return;
3239
+ const panel = panelRef.current;
3240
+ if (!panel) return;
3241
+ const focusable = Array.from(panel.querySelectorAll(FOCUSABLE));
3242
+ if (focusable.length === 0) {
3243
+ event.preventDefault();
3244
+ return;
3245
+ }
3246
+ const first = focusable[0];
3247
+ const last = focusable[focusable.length - 1];
3248
+ const active = document.activeElement;
3249
+ if (event.shiftKey && (active === first || !panel.contains(active))) {
3250
+ event.preventDefault();
3251
+ last.focus();
3252
+ } else if (!event.shiftKey && (active === last || !panel.contains(active))) {
3253
+ event.preventDefault();
3254
+ first.focus();
3255
+ }
3256
+ };
3257
+ return /* @__PURE__ */ jsx14(
3258
+ "div",
3259
+ {
3260
+ "data-kit": "editor-dialog-backdrop",
3261
+ onMouseDown: (event) => {
3262
+ if (event.target === event.currentTarget) onClose();
3263
+ },
3264
+ children: /* @__PURE__ */ jsxs10(
3265
+ "div",
3266
+ {
3267
+ ref: panelRef,
3268
+ role: "dialog",
3269
+ "aria-modal": "true",
3270
+ "aria-labelledby": titleId,
3271
+ "aria-describedby": describedBy,
3272
+ "data-kit": "editor-dialog",
3273
+ tabIndex: -1,
3274
+ onKeyDown,
3275
+ children: [
3276
+ /* @__PURE__ */ jsx14("h2", { id: titleId, "data-kit": "editor-dialog-title", children: title }),
3277
+ children
3278
+ ]
3279
+ }
3280
+ )
3281
+ }
3282
+ );
3283
+ }
3284
+
3285
+ // src/react/internal/field.tsx
3286
+ import { useId as useId4 } from "react";
3287
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
3288
+ function Field({ label, hint, error, required, children }) {
3289
+ const base = useId4();
3290
+ const hintId = `${base}-hint`;
3291
+ const errorId = `${base}-error`;
3292
+ const describedBy = [error ? errorId : null, hint ? hintId : null].filter(Boolean).join(" ") || void 0;
3293
+ return /* @__PURE__ */ jsxs11("div", { "data-kit": "editor-field", children: [
3294
+ /* @__PURE__ */ jsx15("label", { htmlFor: base, "data-kit": "editor-field-label", children: label }),
3295
+ children({
3296
+ id: base,
3297
+ "aria-describedby": describedBy,
3298
+ "aria-invalid": error ? true : void 0,
3299
+ "aria-required": required ? true : void 0
3300
+ }),
3301
+ hint ? /* @__PURE__ */ jsx15("p", { id: hintId, "data-kit": "editor-field-hint", children: hint }) : null,
3302
+ error ? /* @__PURE__ */ jsx15("p", { id: errorId, role: "alert", "data-kit": "editor-error", children: error }) : null
3303
+ ] });
3304
+ }
3305
+
3306
+ // src/react/image-dialog.tsx
3307
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
3308
+ function ImageDialog({ editor, pos, onClose, announce }) {
3309
+ const t = useTranslator();
3310
+ const m = t.messages.imageDialog;
3311
+ const existing = pos === void 0 ? null : editor.state.doc.nodeAt(pos);
3312
+ const current = existing && existing.type.name === "image" ? existing : null;
3313
+ const currentCaption = Array.isArray(current?.attrs.caption) ? current?.attrs.caption : null;
3314
+ const [src, setSrc] = useState7(typeof current?.attrs.src === "string" ? current.attrs.src : "");
3315
+ const [alt, setAlt] = useState7(typeof current?.attrs.alt === "string" ? current.attrs.alt : "");
3316
+ const [decorative, setDecorative] = useState7(current !== null && current.attrs.alt === "" && src !== "");
3317
+ const [caption, setCaption] = useState7(currentCaption ? inlineToPlainText(currentCaption) : "");
3318
+ const [errors, setErrors] = useState7({});
3319
+ const srcRef = useRef8(null);
3320
+ const altRef = useRef8(null);
3321
+ const submit = (event) => {
3322
+ event.preventDefault();
3323
+ const next = {};
3324
+ const value = src.trim();
3325
+ const safe = value === "" ? null : sanitizeMediaUrl(normalizeUrlInput(value));
3326
+ if (value === "") next.src = m.urlRequired;
3327
+ else if (safe === null) next.src = m.urlInvalid;
3328
+ if (!decorative && alt.trim() === "") next.alt = m.altRequired;
3329
+ if (next.src || next.alt) {
3330
+ setErrors(next);
3331
+ (next.src ? srcRef : altRef).current?.focus();
3332
+ return;
3333
+ }
3334
+ const text = caption.trim();
3335
+ const keepCaption = currentCaption !== null && text === inlineToPlainText(currentCaption).trim();
3336
+ const captionValue = keepCaption ? currentCaption : text === "" ? null : [{ type: "text", text }];
3337
+ const altValue = decorative ? "" : alt.trim();
3338
+ if (current && pos !== void 0) {
3339
+ editor.view.dispatch(editor.state.tr.setNodeMarkup(pos, void 0, { ...current.attrs, src: safe, alt: altValue, caption: captionValue }));
3340
+ } else {
3341
+ const template = blockTemplate("image", { src: safe, alt: altValue, caption: captionValue });
3342
+ if (template) insertNode(editor, template);
3343
+ }
3344
+ announce(t.messages.status.imageInserted);
3345
+ onClose();
3346
+ };
3347
+ return /* @__PURE__ */ jsx16(Dialog, { open: true, title: current ? m.editTitle : m.title, onClose, initialFocus: srcRef, restoreFocus: () => editor.commands.focus(), children: /* @__PURE__ */ jsxs12("form", { onSubmit: submit, noValidate: true, "data-kit": "editor-dialog-form", children: [
3348
+ /* @__PURE__ */ jsx16(Field, { label: m.urlLabel, error: errors.src ?? null, required: true, children: (control) => /* @__PURE__ */ jsx16(
3349
+ "input",
3350
+ {
3351
+ ...control,
3352
+ ref: srcRef,
3353
+ "data-kit": "editor-input",
3354
+ type: "text",
3355
+ inputMode: "url",
3356
+ autoComplete: "off",
3357
+ spellCheck: false,
3358
+ dir: "ltr",
3359
+ value: src,
3360
+ onChange: (event) => {
3361
+ setSrc(event.target.value);
3362
+ setErrors((previous) => ({ ...previous, src: void 0 }));
3363
+ }
3364
+ }
3365
+ ) }),
3366
+ /* @__PURE__ */ jsx16(Field, { label: m.altLabel, hint: m.altHint, error: errors.alt ?? null, required: !decorative, children: (control) => /* @__PURE__ */ jsx16(
3367
+ "input",
3368
+ {
3369
+ ...control,
3370
+ ref: altRef,
3371
+ "data-kit": "editor-input",
3372
+ type: "text",
3373
+ dir: "auto",
3374
+ disabled: decorative,
3375
+ value: decorative ? "" : alt,
3376
+ onChange: (event) => {
3377
+ setAlt(event.target.value);
3378
+ setErrors((previous) => ({ ...previous, alt: void 0 }));
3379
+ }
3380
+ }
3381
+ ) }),
3382
+ /* @__PURE__ */ jsxs12("label", { "data-kit": "editor-checkbox", children: [
3383
+ /* @__PURE__ */ jsx16(
3384
+ "input",
3385
+ {
3386
+ type: "checkbox",
3387
+ checked: decorative,
3388
+ onChange: (event) => {
3389
+ setDecorative(event.target.checked);
3390
+ setErrors((previous) => ({ ...previous, alt: void 0 }));
3391
+ }
3392
+ }
3393
+ ),
3394
+ /* @__PURE__ */ jsx16("span", { children: m.decorative })
3395
+ ] }),
3396
+ /* @__PURE__ */ jsx16(Field, { label: m.captionLabel, children: (control) => /* @__PURE__ */ jsx16("input", { ...control, "data-kit": "editor-input", type: "text", dir: "auto", value: caption, onChange: (event) => setCaption(event.target.value) }) }),
3397
+ /* @__PURE__ */ jsxs12("div", { "data-kit": "editor-dialog-actions", children: [
3398
+ /* @__PURE__ */ jsx16(Button3, { type: "submit", variant: "solid", tone: "accent", size: "sm", children: current ? m.update : m.insert }),
3399
+ /* @__PURE__ */ jsx16(Button3, { type: "button", variant: "ghost", tone: "neutral", size: "sm", onClick: onClose, children: m.cancel })
3400
+ ] })
3401
+ ] }) });
3402
+ }
3403
+
3404
+ // src/react/internal/equal.ts
3405
+ function deepEqual(a, b) {
3406
+ if (Object.is(a, b)) return true;
3407
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
3408
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
3409
+ if (Array.isArray(a) && Array.isArray(b)) {
3410
+ return a.length === b.length && a.every((item, index) => deepEqual(item, b[index]));
3411
+ }
3412
+ const left = a;
3413
+ const right = b;
3414
+ const keys = Object.keys(left).filter((key) => left[key] !== void 0);
3415
+ const otherKeys = Object.keys(right).filter((key) => right[key] !== void 0);
3416
+ return keys.length === otherKeys.length && keys.every((key) => deepEqual(left[key], right[key]));
3417
+ }
3418
+
3419
+ // src/react/internal/live-region.tsx
3420
+ import { useCallback, useEffect as useEffect7, useRef as useRef9, useState as useState8 } from "react";
3421
+ import { jsx as jsx17 } from "react/jsx-runtime";
3422
+ function useLiveRegion(clearAfterMs = 4e3) {
3423
+ const [message, setMessage] = useState8("");
3424
+ const timer = useRef9(void 0);
3425
+ useEffect7(() => () => clearTimeout(timer.current), []);
3426
+ const announce = useCallback(
3427
+ (next) => {
3428
+ setMessage((previous) => previous === next ? `${next}\xA0` : next);
3429
+ clearTimeout(timer.current);
3430
+ timer.current = setTimeout(() => setMessage(""), clearAfterMs);
3431
+ },
3432
+ [clearAfterMs]
3433
+ );
3434
+ return { message, announce };
3435
+ }
3436
+ function LiveRegion({ message }) {
3437
+ return /* @__PURE__ */ jsx17("div", { "data-kit": "editor-status", role: "status", "aria-live": "polite", "aria-atomic": "true", children: message });
3438
+ }
3439
+
3440
+ // src/react/link-dialog.tsx
3441
+ import { useRef as useRef10, useState as useState9 } from "react";
3442
+ import { Button as Button4 } from "@x12i/ui";
3443
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
3444
+ function LinkDialog({ editor, initialHref, onClose, announce }) {
3445
+ const t = useTranslator();
3446
+ const m = t.messages.linkDialog;
3447
+ const [href, setHref] = useState9(initialHref);
3448
+ const [error, setError] = useState9(null);
3449
+ const inputRef = useRef10(null);
3450
+ const hasLink = initialHref !== "";
3451
+ const apply = (event) => {
3452
+ event.preventDefault();
3453
+ const value = href.trim();
3454
+ if (value === "") {
3455
+ setError(m.required);
3456
+ inputRef.current?.focus();
3457
+ return;
3458
+ }
3459
+ const next = sanitizeUrl(normalizeUrlInput(value));
3460
+ if (next === null) {
3461
+ setError(m.invalid);
3462
+ inputRef.current?.focus();
3463
+ return;
3464
+ }
3465
+ const chain = editor.chain().focus();
3466
+ if (editor.isActive("link")) chain.extendMarkRange("link").setLink({ href: next }).run();
3467
+ else if (editor.state.selection.empty) {
3468
+ chain.insertContent({ type: "text", text: next, marks: [{ type: "link", attrs: { href: next } }] }).run();
3469
+ } else chain.setLink({ href: next }).run();
3470
+ announce(t.messages.status.linkApplied);
3471
+ onClose();
3472
+ };
3473
+ const remove = () => {
3474
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
3475
+ announce(t.messages.status.linkRemoved);
3476
+ onClose();
3477
+ };
3478
+ return /* @__PURE__ */ jsx18(Dialog, { open: true, title: m.title, onClose, initialFocus: inputRef, restoreFocus: () => editor.commands.focus(), children: /* @__PURE__ */ jsxs13("form", { onSubmit: apply, noValidate: true, "data-kit": "editor-dialog-form", children: [
3479
+ /* @__PURE__ */ jsx18(Field, { label: m.urlLabel, hint: m.urlHint, error, required: true, children: (control) => /* @__PURE__ */ jsx18(
3480
+ "input",
3481
+ {
3482
+ ...control,
3483
+ ref: inputRef,
3484
+ "data-kit": "editor-input",
3485
+ type: "text",
3486
+ inputMode: "url",
3487
+ autoComplete: "off",
3488
+ spellCheck: false,
3489
+ dir: "ltr",
3490
+ value: href,
3491
+ onChange: (event) => {
3492
+ setHref(event.target.value);
3493
+ setError(null);
3494
+ }
3495
+ }
3496
+ ) }),
3497
+ /* @__PURE__ */ jsxs13("div", { "data-kit": "editor-dialog-actions", children: [
3498
+ /* @__PURE__ */ jsx18(Button4, { type: "submit", variant: "solid", tone: "accent", size: "sm", children: m.apply }),
3499
+ hasLink ? /* @__PURE__ */ jsx18(Button4, { type: "button", variant: "outline", tone: "neutral", size: "sm", onClick: remove, children: m.remove }) : null,
3500
+ /* @__PURE__ */ jsx18(Button4, { type: "button", variant: "ghost", tone: "neutral", size: "sm", onClick: onClose, children: m.cancel })
3501
+ ] })
3502
+ ] }) });
3503
+ }
3504
+
3505
+ // src/react/slash-menu.tsx
3506
+ import { useEffect as useEffect8, useId as useId5, useLayoutEffect, useState as useState10 } from "react";
3507
+ import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
3508
+ function SlashMenu({ view, activeIndex, surfaceRef, onHover, onSelect, onIds }) {
3509
+ const t = useTranslator();
3510
+ const m = t.messages;
3511
+ const ui = useEditorUi();
3512
+ const base = useId5();
3513
+ const [position2, setPosition] = useState10({ x: 0, y: 0 });
3514
+ const count = view.items.length;
3515
+ const listboxId = `${base}-listbox`;
3516
+ const optionId = (index) => `${base}-option-${index}`;
3517
+ const active = count > 0 ? Math.min(activeIndex, count - 1) : -1;
3518
+ useLayoutEffect(() => {
3519
+ const surface = surfaceRef.current;
3520
+ const rect = view.clientRect?.();
3521
+ if (!surface || !rect) return;
3522
+ setPosition(positionNear(surface, rect, "bottom"));
3523
+ }, [view, surfaceRef]);
3524
+ useEffect8(() => {
3525
+ onIds({ listbox: count > 0 ? listboxId : null, active: active >= 0 ? optionId(active) : null });
3526
+ }, [count, active, listboxId, onIds]);
3527
+ useEffect8(() => () => onIds({ listbox: null, active: null }), [onIds]);
3528
+ useEffect8(() => {
3529
+ ui.announce(count > 0 ? t.format(m.slash.resultCount, { count }) : m.slash.noResults);
3530
+ }, [count]);
3531
+ useEffect8(() => {
3532
+ if (active < 0) return;
3533
+ document.getElementById(optionId(active))?.scrollIntoView?.({ block: "nearest" });
3534
+ }, [active]);
3535
+ const groups = SLASH_GROUP_ORDER.map((group) => ({
3536
+ group,
3537
+ items: view.items.map((item, index) => ({ item, index })).filter((entry) => entry.item.group === group)
3538
+ })).filter((entry) => entry.items.length > 0);
3539
+ return /* @__PURE__ */ jsx19("div", { "data-kit": "editor-popup", "data-placement": "below", style: { "--kit-editor-x": `${position2.x}px`, "--kit-editor-y": `${position2.y}px` }, children: count === 0 ? /* @__PURE__ */ jsx19("div", { "data-kit": "editor-slash-empty", children: m.slash.noResults }) : /* @__PURE__ */ jsx19("div", { id: listboxId, role: "listbox", "aria-label": m.slash.label, "data-kit": "editor-slash", children: groups.map(({ group, items }) => /* @__PURE__ */ jsxs14("div", { role: "group", "aria-label": m.groups[group], "data-kit": "editor-slash-group", children: [
3540
+ /* @__PURE__ */ jsx19("div", { role: "presentation", "data-kit": "editor-slash-heading", "aria-hidden": "true", children: m.groups[group] }),
3541
+ items.map(({ item, index }) => /* @__PURE__ */ jsxs14(
3542
+ "div",
3543
+ {
3544
+ id: optionId(index),
3545
+ role: "option",
3546
+ "aria-selected": index === active,
3547
+ "data-kit": "editor-slash-option",
3548
+ "data-active": index === active ? "" : void 0,
3549
+ onMouseDown: (event) => event.preventDefault(),
3550
+ onMouseMove: () => onHover(index),
3551
+ onClick: () => onSelect(item),
3552
+ children: [
3553
+ /* @__PURE__ */ jsx19("span", { "data-kit": "editor-slash-name", children: item.name }),
3554
+ /* @__PURE__ */ jsx19("span", { "data-kit": "editor-slash-description", children: item.description })
3555
+ ]
3556
+ },
3557
+ item.kind
3558
+ ))
3559
+ ] }, group)) }) });
3560
+ }
3561
+
3562
+ // src/react/toolbar.tsx
3563
+ import { useRef as useRef11 } from "react";
3564
+ import { useEditorState as useEditorState2 } from "@tiptap/react";
3565
+ import { Divider } from "@x12i/ui";
3566
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
3567
+ function tableHeaders(editor) {
3568
+ const { $from } = editor.state.selection;
3569
+ for (let depth = $from.depth; depth > 0; depth -= 1) {
3570
+ const table = $from.node(depth);
3571
+ if (table.type.name !== "table") continue;
3572
+ let headerRow = table.childCount > 0;
3573
+ let headerColumn = table.childCount > 0;
3574
+ table.forEach((row, _offset, rowIndex) => {
3575
+ row.forEach((cell, _cellOffset, columnIndex) => {
3576
+ const header = cell.type.name === "tableHeader";
3577
+ if (rowIndex === 0 && !header) headerRow = false;
3578
+ if (columnIndex === 0 && !header) headerColumn = false;
3579
+ });
3580
+ });
3581
+ return { headerRow, headerColumn };
3582
+ }
3583
+ return { headerRow: false, headerColumn: false };
3584
+ }
3585
+ function readState(editor) {
3586
+ if (!editor) return null;
3587
+ const table = tableHeaders(editor);
3588
+ const highlight = editor.getAttributes("highlight").token;
3589
+ const color = editor.getAttributes("color").token;
3590
+ const can = editor.can();
3591
+ return {
3592
+ bold: editor.isActive("bold"),
3593
+ italic: editor.isActive("italic"),
3594
+ underline: editor.isActive("underline"),
3595
+ strike: editor.isActive("strike"),
3596
+ code: editor.isActive("code"),
3597
+ link: editor.isActive("link"),
3598
+ bulletList: editor.isActive("bulletList"),
3599
+ numberedList: editor.isActive("orderedList"),
3600
+ taskList: editor.isActive("taskList"),
3601
+ quote: editor.isActive("blockquote"),
3602
+ canUndo: can.undo(),
3603
+ canRedo: can.redo(),
3604
+ canIndent: can.sinkListItem("listItem") || can.sinkListItem("taskItem"),
3605
+ canOutdent: can.liftListItem("listItem") || can.liftListItem("taskItem"),
3606
+ inTable: editor.isActive("table"),
3607
+ headerRow: table.headerRow,
3608
+ headerColumn: table.headerColumn,
3609
+ kind: currentBlockKind(editor),
3610
+ highlight: typeof highlight === "string" ? highlight : null,
3611
+ color: typeof color === "string" ? color : null
3612
+ };
3613
+ }
3614
+ function sameState(a, b) {
3615
+ if (a === b) return true;
3616
+ if (!a || !b) return false;
3617
+ return Object.keys(a).every((key) => a[key] === b[key]);
3618
+ }
3619
+ function EditorToolbar({ editor, onOpenLink, onOpenImage, onOpenBlockMenu, onOpenAi, announce, children, className }) {
3620
+ const t = useTranslator();
3621
+ const m = t.messages;
3622
+ const ref = useRef11(null);
3623
+ const roving = useRovingToolbar(ref);
3624
+ const state = useEditorState2({ editor, selector: () => readState(editor), equalityFn: sameState });
3625
+ const run = (command) => {
3626
+ if (editor) execEditorCommand(editor, command, onOpenBlockMenu);
3627
+ };
3628
+ const toggleMark = (command, name, active) => () => {
3629
+ run(command);
3630
+ announce?.(t.format(active ? m.status.markOff : m.status.markOn, { name }));
3631
+ };
3632
+ const disabled = editor === null || state === null || !editor.isEditable;
3633
+ const s = state ?? {
3634
+ bold: false,
3635
+ italic: false,
3636
+ underline: false,
3637
+ strike: false,
3638
+ code: false,
3639
+ link: false,
3640
+ bulletList: false,
3641
+ numberedList: false,
3642
+ taskList: false,
3643
+ quote: false,
3644
+ canUndo: false,
3645
+ canRedo: false,
3646
+ canIndent: false,
3647
+ canOutdent: false,
3648
+ inTable: false,
3649
+ headerRow: false,
3650
+ headerColumn: false,
3651
+ kind: null,
3652
+ highlight: null,
3653
+ color: null
3654
+ };
3655
+ const blockItems = TURN_INTO_KINDS.map((kind) => ({
3656
+ id: kind,
3657
+ label: m.blocks[kind].name,
3658
+ role: "menuitemradio",
3659
+ checked: s.kind === kind,
3660
+ onSelect: () => {
3661
+ if (editor && turnInto(editor, kind)) announce?.(t.format(m.status.blockTurnedInto, { name: m.blocks[kind].name }));
3662
+ }
3663
+ }));
3664
+ const paletteItems = (tokens, current, mark) => [
3665
+ {
3666
+ id: "none",
3667
+ label: m.toolbar.clearColor,
3668
+ role: "menuitemradio",
3669
+ checked: current === null,
3670
+ onSelect: () => {
3671
+ if (editor) editor.chain().focus().unsetMark(mark).run();
3672
+ }
3673
+ },
3674
+ ...tokens.map((token) => ({
3675
+ id: token,
3676
+ label: m.tokens[token] ?? token,
3677
+ role: "menuitemradio",
3678
+ checked: current === token,
3679
+ swatch: token,
3680
+ onSelect: () => {
3681
+ if (editor) editor.chain().focus().setMark(mark, { token }).run();
3682
+ }
3683
+ }))
3684
+ ];
3685
+ const tableItems = [
3686
+ { id: "row-before", label: m.table.addRowBefore, onSelect: () => editor?.chain().focus().addRowBefore().run() },
3687
+ { id: "row-after", label: m.table.addRowAfter, onSelect: () => editor?.chain().focus().addRowAfter().run() },
3688
+ { id: "col-before", label: m.table.addColumnBefore, onSelect: () => editor?.chain().focus().addColumnBefore().run() },
3689
+ { id: "col-after", label: m.table.addColumnAfter, onSelect: () => editor?.chain().focus().addColumnAfter().run() },
3690
+ { id: "row-delete", label: m.table.deleteRow, onSelect: () => editor?.chain().focus().deleteRow().run() },
3691
+ { id: "col-delete", label: m.table.deleteColumn, onSelect: () => editor?.chain().focus().deleteColumn().run() },
3692
+ {
3693
+ id: "header-row",
3694
+ label: m.table.toggleHeaderRow,
3695
+ role: "menuitemcheckbox",
3696
+ checked: s.headerRow,
3697
+ // The converter reads the header state from the cells, so the stored hint needs no update.
3698
+ onSelect: () => editor?.chain().focus().toggleHeaderRow().run()
3699
+ },
3700
+ {
3701
+ id: "header-column",
3702
+ label: m.table.toggleHeaderColumn,
3703
+ role: "menuitemcheckbox",
3704
+ checked: s.headerColumn,
3705
+ onSelect: () => editor?.chain().focus().toggleHeaderColumn().run()
3706
+ },
3707
+ { id: "table-delete", label: m.table.deleteTable, onSelect: () => editor?.chain().focus().deleteTable().run() }
3708
+ ];
3709
+ const insertTable = () => {
3710
+ const template = blockTemplate("table");
3711
+ if (editor && template) insertNode(editor, template);
3712
+ };
3713
+ const turnIntoAvailable = s.kind !== null && TURN_INTO_KINDS.includes(s.kind);
3714
+ return /* @__PURE__ */ jsxs15(
3715
+ "div",
3716
+ {
3717
+ ref,
3718
+ role: "toolbar",
3719
+ "aria-label": m.toolbar.label,
3720
+ "data-kit": "editor-toolbar",
3721
+ className,
3722
+ ...roving,
3723
+ children: [
3724
+ /* @__PURE__ */ jsxs15("div", { "data-kit": "editor-toolbar-group", children: [
3725
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.undo, keys: shortcut("Z"), disabled: disabled || !s.canUndo, onClick: () => run("undo"), children: /* @__PURE__ */ jsx20(Glyph, { name: "undo" }) }),
3726
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.redo, keys: shortcut("Shift", "Z"), disabled: disabled || !s.canRedo, onClick: () => run("redo"), children: /* @__PURE__ */ jsx20(Glyph, { name: "redo" }) })
3727
+ ] }),
3728
+ /* @__PURE__ */ jsx20(Divider, { orientation: "vertical", decorative: true }),
3729
+ /* @__PURE__ */ jsx20("div", { "data-kit": "editor-toolbar-group", children: /* @__PURE__ */ jsx20(
3730
+ MenuButton,
3731
+ {
3732
+ label: `${m.toolbar.blockType}: ${s.kind ? m.blocks[s.kind].name : m.blocks.paragraph.name}`,
3733
+ title: m.toolbar.blockType,
3734
+ menuLabel: m.toolbar.blockType,
3735
+ items: blockItems,
3736
+ text: s.kind ? m.blocks[s.kind].name : m.blocks.paragraph.name,
3737
+ disabled: disabled || !turnIntoAvailable,
3738
+ children: /* @__PURE__ */ jsx20(Glyph, { name: "heading" })
3739
+ }
3740
+ ) }),
3741
+ /* @__PURE__ */ jsx20(Divider, { orientation: "vertical", decorative: true }),
3742
+ /* @__PURE__ */ jsxs15("div", { "data-kit": "editor-toolbar-group", children: [
3743
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.bold, keys: shortcut("B"), pressed: s.bold, disabled, onClick: toggleMark("bold", m.toolbar.bold, s.bold), children: /* @__PURE__ */ jsx20(Glyph, { name: "bold" }) }),
3744
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.italic, keys: shortcut("I"), pressed: s.italic, disabled, onClick: toggleMark("italic", m.toolbar.italic, s.italic), children: /* @__PURE__ */ jsx20(Glyph, { name: "italic" }) }),
3745
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.underline, keys: shortcut("U"), pressed: s.underline, disabled, onClick: toggleMark("underline", m.toolbar.underline, s.underline), children: /* @__PURE__ */ jsx20(Glyph, { name: "underline" }) }),
3746
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.strike, keys: shortcut("Shift", "S"), pressed: s.strike, disabled, onClick: toggleMark("strike", m.toolbar.strike, s.strike), children: /* @__PURE__ */ jsx20(Glyph, { name: "strike" }) }),
3747
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.code, keys: shortcut("E"), pressed: s.code, disabled, onClick: toggleMark("code", m.toolbar.code, s.code), children: /* @__PURE__ */ jsx20(Glyph, { name: "code" }) }),
3748
+ /* @__PURE__ */ jsx20(MenuButton, { label: m.toolbar.highlight, menuLabel: m.toolbar.highlight, items: paletteItems(HIGHLIGHT_TOKENS, s.highlight, "highlight"), pressed: s.highlight !== null, disabled, children: /* @__PURE__ */ jsx20(Glyph, { name: "highlight" }) }),
3749
+ /* @__PURE__ */ jsx20(MenuButton, { label: m.toolbar.textColor, menuLabel: m.toolbar.textColor, items: paletteItems(TEXT_COLOR_TOKENS, s.color, "color"), pressed: s.color !== null, disabled, children: /* @__PURE__ */ jsx20(Glyph, { name: "color" }) }),
3750
+ onOpenLink ? /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.link, keys: shortcut("K"), pressed: s.link, disabled, onClick: onOpenLink, children: /* @__PURE__ */ jsx20(Glyph, { name: "link" }) }) : null
3751
+ ] }),
3752
+ /* @__PURE__ */ jsx20(Divider, { orientation: "vertical", decorative: true }),
3753
+ /* @__PURE__ */ jsxs15("div", { "data-kit": "editor-toolbar-group", children: [
3754
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.bulletList, keys: shortcut("Shift", "8"), pressed: s.bulletList, disabled, onClick: () => run("bulletList"), children: /* @__PURE__ */ jsx20(Glyph, { name: "bullets" }) }),
3755
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.numberedList, keys: shortcut("Shift", "7"), pressed: s.numberedList, disabled, onClick: () => run("numberedList"), children: /* @__PURE__ */ jsx20(Glyph, { name: "numbers" }) }),
3756
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.taskList, keys: shortcut("Shift", "9"), pressed: s.taskList, disabled, onClick: () => run("taskList"), children: /* @__PURE__ */ jsx20(Glyph, { name: "tasks" }) }),
3757
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.outdent, keys: "Shift+Tab", disabled: disabled || !s.canOutdent, onClick: () => run("outdent"), children: /* @__PURE__ */ jsx20(Glyph, { name: "outdent" }) }),
3758
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.indent, keys: "Tab", disabled: disabled || !s.canIndent, onClick: () => run("indent"), children: /* @__PURE__ */ jsx20(Glyph, { name: "indent" }) })
3759
+ ] }),
3760
+ /* @__PURE__ */ jsx20(Divider, { orientation: "vertical", decorative: true }),
3761
+ /* @__PURE__ */ jsxs15("div", { "data-kit": "editor-toolbar-group", children: [
3762
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.quote, keys: shortcut("Shift", "B"), pressed: s.quote, disabled, onClick: () => run("quote"), children: /* @__PURE__ */ jsx20(Glyph, { name: "quote" }) }),
3763
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.divider, disabled, onClick: () => run("divider"), children: /* @__PURE__ */ jsx20(Glyph, { name: "divider" }) }),
3764
+ /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.table, disabled, onClick: insertTable, children: /* @__PURE__ */ jsx20(Glyph, { name: "table" }) }),
3765
+ s.inTable ? /* @__PURE__ */ jsx20(MenuButton, { label: m.table.controls, menuLabel: m.table.controls, items: tableItems, disabled, children: /* @__PURE__ */ jsx20(Glyph, { name: "more" }) }) : null,
3766
+ onOpenImage ? /* @__PURE__ */ jsx20(ToolButton, { label: m.toolbar.image, disabled, onClick: onOpenImage, children: /* @__PURE__ */ jsx20(Glyph, { name: "image" }) }) : null
3767
+ ] }),
3768
+ onOpenAi || onOpenBlockMenu || children ? /* @__PURE__ */ jsx20(Divider, { orientation: "vertical", decorative: true }) : null,
3769
+ /* @__PURE__ */ jsxs15("div", { "data-kit": "editor-toolbar-group", children: [
3770
+ onOpenAi ? /* @__PURE__ */ jsx20(ToolButton, { label: m.ai.menuLabel, disabled, onClick: onOpenAi, children: /* @__PURE__ */ jsx20(Glyph, { name: "sparkle" }) }) : null,
3771
+ onOpenBlockMenu ? /* @__PURE__ */ jsx20(ToolButton, { label: m.blockMenu.label, keys: shortcut("/"), disabled, onClick: onOpenBlockMenu, children: /* @__PURE__ */ jsx20(Glyph, { name: "more" }) }) : null,
3772
+ children
3773
+ ] })
3774
+ ]
3775
+ }
3776
+ );
3777
+ }
3778
+
3779
+ // src/react/use-ai-assist.ts
3780
+ import { useCallback as useCallback2, useEffect as useEffect9, useRef as useRef12, useState as useState11 } from "react";
3781
+ var CONTEXT_CHARACTERS = 2e3;
3782
+ function textToContent(text) {
3783
+ const inline = (paragraph) => paragraph.split("\n").flatMap((line, index) => {
3784
+ const nodes = [];
3785
+ if (index > 0) nodes.push({ type: "hardBreak" });
3786
+ if (line !== "") nodes.push({ type: "text", text: line });
3787
+ return nodes;
3788
+ });
3789
+ const paragraphs = text.trim().split(/\n{2,}/);
3790
+ if (paragraphs.length === 1) return inline(paragraphs[0] ?? "");
3791
+ return paragraphs.map((paragraph) => ({ type: "paragraph", content: inline(paragraph) }));
3792
+ }
3793
+ function useAiAssist({ provider, editor, announce }) {
3794
+ const t = useTranslator();
3795
+ const [status, setStatus] = useState11("idle");
3796
+ const [command, setCommand] = useState11(null);
3797
+ const [preview, setPreview] = useState11("");
3798
+ const [error, setError] = useState11(null);
3799
+ const abortRef = useRef12(null);
3800
+ const rangeRef = useRef12(null);
3801
+ const textRef = useRef12("");
3802
+ useEffect9(() => () => abortRef.current?.abort(), []);
3803
+ const say = useCallback2((message) => announce?.(message), [announce]);
3804
+ const run = useCallback2(
3805
+ async (next, options = {}) => {
3806
+ if (!provider || !editor) return;
3807
+ const { selection, doc } = editor.state;
3808
+ let input = selection.empty ? "" : doc.textBetween(selection.from, selection.to, "\n");
3809
+ if (input === "" && next === "continue") {
3810
+ const before = doc.textBetween(0, selection.from, "\n");
3811
+ input = before.slice(-CONTEXT_CHARACTERS);
3812
+ }
3813
+ if (input.trim() === "") {
3814
+ setCommand(next);
3815
+ setStatus("error");
3816
+ setError(t.messages.ai.noSelection);
3817
+ return;
3818
+ }
3819
+ abortRef.current?.abort();
3820
+ const controller = new AbortController();
3821
+ abortRef.current = controller;
3822
+ rangeRef.current = { from: selection.from, to: selection.to };
3823
+ textRef.current = "";
3824
+ setCommand(next);
3825
+ setPreview("");
3826
+ setError(null);
3827
+ setStatus("running");
3828
+ say(t.messages.ai.generating);
3829
+ try {
3830
+ const stream = provider.run({
3831
+ command: next,
3832
+ selection: input,
3833
+ ...options.tone === void 0 ? {} : { tone: options.tone },
3834
+ signal: controller.signal
3835
+ });
3836
+ for await (const chunk of stream) {
3837
+ if (controller.signal.aborted) break;
3838
+ textRef.current += chunk;
3839
+ setPreview(textRef.current);
3840
+ }
3841
+ if (abortRef.current !== controller) return;
3842
+ setStatus(textRef.current === "" ? "idle" : "preview");
3843
+ if (textRef.current !== "") say(t.messages.ai.done);
3844
+ } catch (cause) {
3845
+ if (controller.signal.aborted) {
3846
+ if (abortRef.current === controller) setStatus(textRef.current === "" ? "idle" : "preview");
3847
+ return;
3848
+ }
3849
+ const message = cause instanceof Error ? cause.message : String(cause);
3850
+ setError(formatMessage(t.messages.ai.error, { message }, t.locale));
3851
+ setStatus("error");
3852
+ }
3853
+ },
3854
+ [provider, editor, say, t]
3855
+ );
3856
+ const cancel = useCallback2(() => {
3857
+ abortRef.current?.abort();
3858
+ }, []);
3859
+ const reject = useCallback2(() => {
3860
+ abortRef.current?.abort();
3861
+ abortRef.current = null;
3862
+ textRef.current = "";
3863
+ rangeRef.current = null;
3864
+ setPreview("");
3865
+ setError(null);
3866
+ setCommand(null);
3867
+ setStatus("idle");
3868
+ }, []);
3869
+ const accept = useCallback2(() => {
3870
+ const range = rangeRef.current;
3871
+ const text = textRef.current;
3872
+ if (!editor || !range || text === "") return;
3873
+ const content = textToContent(text);
3874
+ const chain = editor.chain().focus();
3875
+ if (command === "continue") {
3876
+ const before = editor.state.doc.textBetween(Math.max(0, range.to - 1), range.to);
3877
+ const separator = before !== "" && !/\s/.test(before) ? [{ type: "text", text: " " }] : [];
3878
+ const inline = content[0]?.type === "text" || content[0]?.type === "hardBreak";
3879
+ chain.insertContentAt(range.to, inline ? [...separator, ...content] : content).run();
3880
+ } else {
3881
+ chain.insertContentAt({ from: range.from, to: range.to }, content).run();
3882
+ }
3883
+ say(t.messages.status.aiApplied);
3884
+ reject();
3885
+ }, [editor, command, say, reject, t]);
3886
+ return { available: provider !== void 0, status, command, preview, error, run, accept, reject, cancel };
3887
+ }
3888
+
3889
+ // src/react/view.tsx
3890
+ import { createElement as createElement3, Fragment as Fragment4, useMemo as useMemo2 } from "react";
3891
+ import { Fragment as Fragment5, jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
3892
+ function formatBytes(size, locale) {
3893
+ const units = [
3894
+ [1024 ** 3, "gigabyte"],
3895
+ [1024 ** 2, "megabyte"],
3896
+ [1024, "kilobyte"]
3897
+ ];
3898
+ const [factor, unit] = units.find(([bytes]) => size >= bytes) ?? [1, "byte"];
3899
+ try {
3900
+ return new Intl.NumberFormat(locale, { style: "unit", unit, unitDisplay: "narrow", maximumFractionDigits: 1 }).format(size / factor);
3901
+ } catch {
3902
+ return String(size);
3903
+ }
3904
+ }
3905
+ function OutlineList({ nodes, ctx }) {
3906
+ if (nodes.length === 0) return null;
3907
+ return /* @__PURE__ */ jsx21("ol", { children: nodes.map((node) => /* @__PURE__ */ jsxs16("li", { children: [
3908
+ /* @__PURE__ */ jsx21("a", { href: `#${ctx.idPrefix}${node.id}`, dir: "auto", children: node.text }),
3909
+ /* @__PURE__ */ jsx21(OutlineList, { nodes: node.children, ctx })
3910
+ ] }, node.id)) });
3911
+ }
3912
+ function Items({ items, kind, ctx }) {
3913
+ const Tag = kind === "ol" ? "ol" : "ul";
3914
+ return /* @__PURE__ */ jsx21(Tag, { "data-kit": kind === "task" ? "editor-task-list" : void 0, dir: "auto", children: items.map((item) => {
3915
+ const nested = item.children && item.children.length > 0 ? /* @__PURE__ */ jsx21(Items, { items: item.children, kind, ctx }) : null;
3916
+ if (kind === "task") {
3917
+ const checked = item.checked === true;
3918
+ const labelId = `${ctx.idPrefix}${item.id}-label`;
3919
+ return /* @__PURE__ */ jsxs16("li", { "data-kit": "editor-task-item", "data-checked": checked ? "true" : "false", children: [
3920
+ /* @__PURE__ */ jsx21("input", { type: "checkbox", checked, disabled: true, readOnly: true, "aria-labelledby": labelId }),
3921
+ /* @__PURE__ */ jsx21("span", { id: labelId, dir: "auto", children: /* @__PURE__ */ jsx21(InlineContent, { inline: item.content }) }),
3922
+ nested
3923
+ ] }, item.id);
3924
+ }
3925
+ return /* @__PURE__ */ jsxs16("li", { dir: "auto", children: [
3926
+ /* @__PURE__ */ jsx21(InlineContent, { inline: item.content }),
3927
+ nested
3928
+ ] }, item.id);
3929
+ }) });
3930
+ }
3931
+ function Caption({ caption }) {
3932
+ return caption && caption.length > 0 ? /* @__PURE__ */ jsx21("figcaption", { dir: "auto", children: /* @__PURE__ */ jsx21(InlineContent, { inline: caption }) }) : null;
3933
+ }
3934
+ function TableBlock({ block }) {
3935
+ const rows = block.rows ?? [];
3936
+ const renderRow = (row, rowIndex) => /* @__PURE__ */ jsx21("tr", { children: row.map((cell, columnIndex) => {
3937
+ const header = cell.header === true || block.headerRow === true && rowIndex === 0 || block.headerColumn === true && columnIndex === 0;
3938
+ let scope;
3939
+ if (header && block.headerRow === true && rowIndex === 0) scope = "col";
3940
+ else if (header && block.headerColumn === true && columnIndex === 0) scope = "row";
3941
+ const props = {
3942
+ key: columnIndex,
3943
+ scope,
3944
+ colSpan: cell.colSpan && cell.colSpan > 1 ? cell.colSpan : void 0,
3945
+ rowSpan: cell.rowSpan && cell.rowSpan > 1 ? cell.rowSpan : void 0,
3946
+ dir: "auto"
3947
+ };
3948
+ return createElement3(header ? "th" : "td", props, /* @__PURE__ */ jsx21(InlineContent, { inline: cell.content }));
3949
+ }) }, rowIndex);
3950
+ const headRow = block.headerRow === true ? rows[0] : void 0;
3951
+ const bodyRows = headRow ? rows.slice(1) : rows;
3952
+ return /* @__PURE__ */ jsx21("div", { "data-kit": "editor-table-wrapper", "data-block-id": block.id, children: /* @__PURE__ */ jsxs16("table", { "data-kit": "editor-table", children: [
3953
+ headRow ? /* @__PURE__ */ jsx21("thead", { children: renderRow(headRow, 0) }) : null,
3954
+ /* @__PURE__ */ jsx21("tbody", { children: bodyRows.map((row, index) => renderRow(row, headRow ? index + 1 : index)) })
3955
+ ] }) });
3956
+ }
3957
+ function Embed({ block, type, payload }) {
3958
+ const t = useTranslator();
3959
+ const title = payloadTitle(type, payload);
3960
+ const label = t.format(t.messages.embed.groupLabel, {
3961
+ type: t.messages.blocks[EMBED_KIND[type]].name,
3962
+ title: title || t.messages.embed.untitled
3963
+ });
3964
+ return /* @__PURE__ */ jsx21("figure", { "data-block-id": block.id, "data-kit": "editor-embed", "data-embed": type, role: "group", "aria-label": label, children: /* @__PURE__ */ jsx21(EmbedBody, { type, id: block.id, payload }) });
3965
+ }
3966
+ function BlockView({ block, ctx }) {
3967
+ const { t } = ctx;
3968
+ const m = t.messages;
3969
+ const id = block.id;
3970
+ switch (block.type) {
3971
+ case "paragraph":
3972
+ return /* @__PURE__ */ jsx21("p", { "data-block-id": id, dir: "auto", children: /* @__PURE__ */ jsx21(InlineContent, { inline: block.content }) });
3973
+ case "heading": {
3974
+ const level = Math.min(4, Math.max(1, Math.round(block.level)));
3975
+ return createElement3(
3976
+ `h${level}`,
3977
+ { id: `${ctx.idPrefix}${id}`, "data-block-id": id, dir: "auto" },
3978
+ /* @__PURE__ */ jsx21(InlineContent, { inline: block.content })
3979
+ );
3980
+ }
3981
+ case "quote":
3982
+ return /* @__PURE__ */ jsx21("blockquote", { "data-block-id": id, dir: "auto", children: /* @__PURE__ */ jsx21("p", { dir: "auto", children: /* @__PURE__ */ jsx21(InlineContent, { inline: block.content }) }) });
3983
+ case "callout": {
3984
+ const variant = CALLOUT_VARIANTS.find((candidate) => candidate === block.variant) ?? "info";
3985
+ return /* @__PURE__ */ jsxs16("div", { "data-block-id": id, "data-kit": "editor-callout", "data-variant": variant, role: "note", "aria-label": m.callouts[variant], children: [
3986
+ /* @__PURE__ */ jsx21("span", { "data-kit": "editor-callout-icon", children: /* @__PURE__ */ jsx21(Glyph, { name: variant }) }),
3987
+ /* @__PURE__ */ jsx21("div", { "data-kit": "editor-callout-content", dir: "auto", children: /* @__PURE__ */ jsx21(InlineContent, { inline: block.content }) })
3988
+ ] });
3989
+ }
3990
+ case "toggle":
3991
+ return /* @__PURE__ */ jsxs16("details", { "data-block-id": id, "data-kit": "editor-toggle", open: true, children: [
3992
+ /* @__PURE__ */ jsx21("summary", { dir: "auto", children: /* @__PURE__ */ jsx21(InlineContent, { inline: block.summary }) }),
3993
+ /* @__PURE__ */ jsx21(Blocks, { blocks: block.children ?? [], ctx })
3994
+ ] });
3995
+ case "divider":
3996
+ return /* @__PURE__ */ jsx21("hr", { "data-block-id": id });
3997
+ case "bulleted-list":
3998
+ return /* @__PURE__ */ jsx21(Items, { items: block.items ?? [], kind: "ul", ctx });
3999
+ case "numbered-list":
4000
+ return /* @__PURE__ */ jsx21(Items, { items: block.items ?? [], kind: "ol", ctx });
4001
+ case "task-list":
4002
+ return /* @__PURE__ */ jsx21(Items, { items: block.items ?? [], kind: "task", ctx });
4003
+ case "code": {
4004
+ const language = block.language && /^[\w+#-]+$/.test(block.language) ? block.language : void 0;
4005
+ return /* @__PURE__ */ jsx21("pre", { "data-block-id": id, "data-kit": "editor-code-block", "data-language": language, dir: "ltr", children: /* @__PURE__ */ jsx21("code", { className: language ? `language-${language}` : void 0, children: block.code }) });
4006
+ }
4007
+ case "equation":
4008
+ return /* @__PURE__ */ jsx21("pre", { "data-block-id": id, "data-kit": "editor-equation", dir: "ltr", children: /* @__PURE__ */ jsx21("code", { children: block.latex }) });
4009
+ case "table":
4010
+ return /* @__PURE__ */ jsx21(TableBlock, { block });
4011
+ case "image": {
4012
+ const src = sanitizeMediaUrl(block.src);
4013
+ return /* @__PURE__ */ jsxs16("figure", { "data-block-id": id, "data-kit": "editor-image", "data-align": block.align, children: [
4014
+ src ? /* @__PURE__ */ jsx21("img", { src, alt: block.alt ?? "", loading: "lazy", decoding: "async", width: block.width }) : null,
4015
+ /* @__PURE__ */ jsx21(Caption, { caption: block.caption })
4016
+ ] });
4017
+ }
4018
+ case "video":
4019
+ case "audio": {
4020
+ const src = sanitizeMediaUrl(block.src);
4021
+ const label = block.type === "audio" ? m.media.audio : m.media.video;
4022
+ return /* @__PURE__ */ jsxs16("figure", { "data-block-id": id, "data-kit": "editor-media", "data-kind": block.type, children: [
4023
+ src ? block.type === "audio" ? /* @__PURE__ */ jsx21("audio", { src, controls: true, preload: "none", "aria-label": label }) : /* @__PURE__ */ jsx21("video", { src, controls: true, preload: "none", "aria-label": label }) : null,
4024
+ /* @__PURE__ */ jsx21(Caption, { caption: block.caption })
4025
+ ] });
4026
+ }
4027
+ case "file": {
4028
+ const href = sanitizeUrl(block.src);
4029
+ return /* @__PURE__ */ jsxs16("p", { "data-block-id": id, "data-kit": "editor-file", dir: "auto", children: [
4030
+ href ? /* @__PURE__ */ jsx21("a", { href, download: true, rel: "noopener noreferrer", children: block.name }) : block.name,
4031
+ typeof block.size === "number" ? /* @__PURE__ */ jsxs16("span", { "data-kit": "editor-file-size", children: [
4032
+ " (",
4033
+ formatBytes(block.size, t.locale),
4034
+ ")"
4035
+ ] }) : null
4036
+ ] });
4037
+ }
4038
+ case "bookmark": {
4039
+ const href = sanitizeUrl(block.url);
4040
+ const inner = /* @__PURE__ */ jsxs16(Fragment5, { children: [
4041
+ block.title ? /* @__PURE__ */ jsx21("strong", { dir: "auto", children: block.title }) : block.url,
4042
+ block.description ? /* @__PURE__ */ jsx21("span", { dir: "auto", children: block.description }) : null
4043
+ ] });
4044
+ return href ? /* @__PURE__ */ jsx21("a", { "data-block-id": id, "data-kit": "editor-bookmark", href, rel: "noopener noreferrer", children: inner }) : /* @__PURE__ */ jsx21("div", { "data-block-id": id, "data-kit": "editor-bookmark", children: inner });
4045
+ }
4046
+ case "columns":
4047
+ return /* @__PURE__ */ jsx21("div", { "data-block-id": id, "data-kit": "editor-columns", "data-count": (block.columns ?? []).length, children: (block.columns ?? []).map((column, index) => /* @__PURE__ */ jsx21("div", { "data-kit": "editor-column", children: /* @__PURE__ */ jsx21(Blocks, { blocks: column, ctx }) }, index)) });
4048
+ case "page-break":
4049
+ return /* @__PURE__ */ jsx21("hr", { "data-block-id": id, "data-kit": "editor-page-break" });
4050
+ case "table-of-contents":
4051
+ return /* @__PURE__ */ jsx21("nav", { "data-block-id": id, "data-kit": "editor-toc", "aria-label": m.blocks.toc.name, children: /* @__PURE__ */ jsx21(OutlineList, { nodes: ctx.outline, ctx }) });
4052
+ case "chart":
4053
+ return /* @__PURE__ */ jsx21(Embed, { block, type: "chart", payload: block.spec });
4054
+ case "diagram":
4055
+ return /* @__PURE__ */ jsx21(Embed, { block, type: "diagram", payload: { engine: block.engine, diagram: block.diagram, source: block.source } });
4056
+ case "data-view":
4057
+ return /* @__PURE__ */ jsx21(Embed, { block, type: "data-view", payload: { dataset: block.dataset, view: block.view } });
4058
+ case "ai":
4059
+ return /* @__PURE__ */ jsx21(Embed, { block, type: "ai", payload: { prompt: block.prompt, result: block.result } });
4060
+ default: {
4061
+ const unknown = block;
4062
+ if (!ctx.unknown) return null;
4063
+ return createElement3(
4064
+ ctx.unknown,
4065
+ { id: typeof unknown.id === "string" ? unknown.id : "", blockType: String(unknown.type), payload: unknown, editable: false }
4066
+ );
4067
+ }
4068
+ }
4069
+ }
4070
+ function Blocks({ blocks, ctx }) {
4071
+ return /* @__PURE__ */ jsx21(Fragment5, { children: blocks.map((block, index) => /* @__PURE__ */ jsx21(Fragment4, { children: /* @__PURE__ */ jsx21(BlockView, { block, ctx }) }, `${block.id}-${index}`)) });
4072
+ }
4073
+ function EditorView({
4074
+ value,
4075
+ blockRenderers,
4076
+ messages,
4077
+ locale,
4078
+ className,
4079
+ idPrefix = "kit-",
4080
+ "aria-label": ariaLabel,
4081
+ "aria-labelledby": ariaLabelledby
4082
+ }) {
4083
+ const translator = useMemo2(() => createTranslator(mergeMessages(defaultMessages, messages), locale), [messages, locale]);
4084
+ const ui = useMemo2(() => ({ ...defaultEditorUi, editable: false, blockRenderers: blockRenderers ?? {} }), [blockRenderers]);
4085
+ const outline = useMemo2(() => buildOutlineTree(getOutline(value)), [value]);
4086
+ const ctx = useMemo2(
4087
+ () => ({ idPrefix, outline, t: translator, unknown: ui.blockRenderers.unknown }),
4088
+ [idPrefix, outline, translator, ui]
4089
+ );
4090
+ const label = ariaLabelledby ? void 0 : ariaLabel ?? translator.messages.viewLabel;
4091
+ return /* @__PURE__ */ jsx21(MessagesContext.Provider, { value: translator, children: /* @__PURE__ */ jsx21(EditorUiContext.Provider, { value: ui, children: /* @__PURE__ */ jsx21(
4092
+ "div",
4093
+ {
4094
+ "data-kit": "editor-view",
4095
+ role: "document",
4096
+ "aria-label": label,
4097
+ "aria-labelledby": ariaLabelledby,
4098
+ lang: locale,
4099
+ className,
4100
+ children: /* @__PURE__ */ jsx21(Blocks, { blocks: value.blocks ?? [], ctx })
4101
+ }
4102
+ ) }) });
4103
+ }
4104
+
4105
+ // src/react/editor.tsx
4106
+ import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
4107
+ function snapshot(props) {
4108
+ return { items: props.items, query: props.query, clientRect: props.clientRect ?? null };
4109
+ }
4110
+ function Editor(props) {
4111
+ const {
4112
+ value,
4113
+ defaultValue,
4114
+ editable = true,
4115
+ autoFocus,
4116
+ blockRenderers,
4117
+ extensions,
4118
+ messages,
4119
+ locale,
4120
+ toolbar = true,
4121
+ selectionMenu = true,
4122
+ aiProvider,
4123
+ className,
4124
+ toolbarEnd,
4125
+ onEditBlock,
4126
+ onRequestBlock,
4127
+ "aria-label": ariaLabel,
4128
+ "aria-labelledby": ariaLabelledby
4129
+ } = props;
4130
+ const translator = useMemo3(() => createTranslator(mergeMessages(defaultMessages, messages), locale), [messages, locale]);
4131
+ const { message: statusMessage, announce } = useLiveRegion();
4132
+ const initialRef = useRef13(null);
4133
+ if (initialRef.current === null) {
4134
+ const doc = value ?? defaultValue ?? createEmptyDocument();
4135
+ const warnings = [];
4136
+ initialRef.current = { doc, json: documentToTiptap(doc, { warnings }), warnings };
4137
+ }
4138
+ const initial = initialRef.current;
4139
+ const latest = useRef13(props);
4140
+ latest.current = props;
4141
+ const editorRef = useRef13(null);
4142
+ const lastDocRef = useRef13(initial.doc);
4143
+ const titleRef = useRef13(initial.doc.title);
4144
+ const surfaceRef = useRef13(null);
4145
+ const timerRef = useRef13(void 0);
4146
+ const pendingRef = useRef13(false);
4147
+ const [dialog, setDialog] = useState12(null);
4148
+ const [slash, setSlash] = useState12(null);
4149
+ const [slashIndex, setSlashIndex] = useState12(0);
4150
+ const [slashIds, setSlashIds] = useState12({ listbox: null, active: null });
4151
+ const slashState = useRef13({ props: null, index: 0 });
4152
+ const readDocument = useCallback3(() => {
4153
+ const editor2 = editorRef.current;
4154
+ if (!editor2) return lastDocRef.current;
4155
+ const doc = tiptapToDocument(editor2.getJSON());
4156
+ return titleRef.current === void 0 ? doc : { ...doc, title: titleRef.current };
4157
+ }, []);
4158
+ const writeDocument = useCallback3((doc) => {
4159
+ const editor2 = editorRef.current;
4160
+ titleRef.current = doc.title;
4161
+ lastDocRef.current = doc;
4162
+ if (!editor2) return;
4163
+ const warnings = [];
4164
+ editor2.commands.setContent(documentToTiptap(doc, { warnings }), { emitUpdate: false });
4165
+ if (warnings.length > 0) latest.current.onWarnings?.(warnings);
4166
+ }, []);
4167
+ const emit = useCallback3(() => {
4168
+ pendingRef.current = false;
4169
+ clearTimeout(timerRef.current);
4170
+ let doc;
4171
+ try {
4172
+ doc = readDocument();
4173
+ } catch {
4174
+ return;
4175
+ }
4176
+ lastDocRef.current = doc;
4177
+ latest.current.onChange?.(doc);
4178
+ }, [readDocument]);
4179
+ const schedule = useCallback3(() => {
4180
+ const wait = latest.current.debounce ?? 0;
4181
+ if (wait <= 0) {
4182
+ emit();
4183
+ return;
4184
+ }
4185
+ pendingRef.current = true;
4186
+ clearTimeout(timerRef.current);
4187
+ timerRef.current = setTimeout(emit, wait);
4188
+ }, [emit]);
4189
+ useEffect10(
4190
+ () => () => {
4191
+ clearTimeout(timerRef.current);
4192
+ if (pendingRef.current) emit();
4193
+ },
4194
+ [emit]
4195
+ );
4196
+ const runtimeRef = useRef13(null);
4197
+ if (runtimeRef.current === null) runtimeRef.current = createRuntime();
4198
+ const runtime = runtimeRef.current;
4199
+ const openLinkDialog = useCallback3(() => {
4200
+ const editor2 = editorRef.current;
4201
+ if (!editor2) return;
4202
+ const href = editor2.getAttributes("link").href;
4203
+ setDialog({ kind: "link", href: typeof href === "string" ? href : "" });
4204
+ }, []);
4205
+ const openImageDialog = useCallback3((pos) => setDialog({ kind: "image", pos }), []);
4206
+ const openBlockMenu = useCallback3(() => setDialog({ kind: "block-menu" }), []);
4207
+ const closeDialog = useCallback3(() => setDialog(null), []);
4208
+ const ui = useMemo3(
4209
+ () => ({
4210
+ ...defaultEditorUi,
4211
+ editable,
4212
+ blockRenderers: blockRenderers ?? {},
4213
+ announce,
4214
+ onEditBlock,
4215
+ onRequestBlock,
4216
+ openImageDialog,
4217
+ openLinkDialog,
4218
+ openBlockMenu
4219
+ }),
4220
+ [editable, blockRenderers, announce, onEditBlock, onRequestBlock, openImageDialog, openLinkDialog, openBlockMenu]
4221
+ );
4222
+ runtime.ui = () => ui;
4223
+ runtime.translator = () => translator;
4224
+ runtime.placeholder = () => latest.current.placeholder ?? translator.messages.placeholder;
4225
+ runtime.slashEnabled = () => latest.current.slashMenu !== false;
4226
+ const bridge = useMemo3(
4227
+ () => ({
4228
+ onStart: (suggestion) => {
4229
+ slashState.current = { props: suggestion, index: 0 };
4230
+ setSlash(snapshot(suggestion));
4231
+ setSlashIndex(0);
4232
+ },
4233
+ onUpdate: (suggestion) => {
4234
+ const previous = slashState.current;
4235
+ const count = suggestion.items.length;
4236
+ const index = previous.props && previous.props.query === suggestion.query ? Math.min(previous.index, Math.max(0, count - 1)) : 0;
4237
+ slashState.current = { props: suggestion, index };
4238
+ setSlash(snapshot(suggestion));
4239
+ setSlashIndex(index);
4240
+ },
4241
+ onKeyDown: ({ event }) => {
4242
+ const current = slashState.current;
4243
+ if (!current.props) return false;
4244
+ const count = current.props.items.length;
4245
+ const go = (index) => {
4246
+ slashState.current = { ...current, index };
4247
+ setSlashIndex(index);
4248
+ };
4249
+ switch (event.key) {
4250
+ case "ArrowDown":
4251
+ if (count === 0) return true;
4252
+ go((current.index + 1) % count);
4253
+ return true;
4254
+ case "ArrowUp":
4255
+ if (count === 0) return true;
4256
+ go((current.index - 1 + count) % count);
4257
+ return true;
4258
+ case "Enter":
4259
+ case "Tab": {
4260
+ const item = current.props.items[current.index];
4261
+ if (!item) return false;
4262
+ current.props.command(item);
4263
+ return true;
4264
+ }
4265
+ case "Escape": {
4266
+ const editor2 = editorRef.current;
4267
+ if (editor2) exitSuggestion(editor2.view, slashPluginKey);
4268
+ return true;
4269
+ }
4270
+ default:
4271
+ return false;
4272
+ }
4273
+ },
4274
+ onExit: () => {
4275
+ slashState.current = { props: null, index: 0 };
4276
+ setSlash(null);
4277
+ }
4278
+ }),
4279
+ []
4280
+ );
4281
+ runtime.slash = bridge;
4282
+ const builtExtensions = useMemo3(
4283
+ () => createExtensions({ runtime, ...extensions ? { extra: extensions } : {} }),
4284
+ // Extensions are created once; props they depend on are read through the runtime.
4285
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4286
+ []
4287
+ );
4288
+ const attributes = {
4289
+ "data-kit": "editor-content",
4290
+ role: editable ? "textbox" : "document",
4291
+ ...editable ? { "aria-multiline": "true" } : {},
4292
+ ...ariaLabelledby ? { "aria-labelledby": ariaLabelledby } : { "aria-label": ariaLabel ?? translator.messages.editorLabel },
4293
+ ...locale ? { lang: locale } : {},
4294
+ ...slashIds.listbox ? { "aria-controls": slashIds.listbox } : {},
4295
+ ...slashIds.active ? { "aria-activedescendant": slashIds.active } : {}
4296
+ };
4297
+ const api = useMemo3(
4298
+ () => ({
4299
+ getDocument: () => readDocument(),
4300
+ setDocument: (doc) => writeDocument(doc),
4301
+ focus: (position2) => {
4302
+ editorRef.current?.commands.focus(position2 ?? null);
4303
+ },
4304
+ insertBlock: async (type, attrs = {}) => {
4305
+ const editor2 = editorRef.current;
4306
+ if (!editor2) return null;
4307
+ const kind = normalizeBlockKind(type, attrs);
4308
+ if (kind === null) return null;
4309
+ let payload = attrs;
4310
+ const embed = embedKindToType(kind);
4311
+ const request = latest.current.onRequestBlock;
4312
+ if (embed && request && Object.keys(attrs).length === 0) {
4313
+ const result = await request(embed);
4314
+ if (result === null || result === void 0) return null;
4315
+ payload = embed === "chart" ? { spec: result } : result;
4316
+ }
4317
+ editor2.commands.focus();
4318
+ return insertBlockKind(editor2, kind, payload);
4319
+ },
4320
+ exec: (command) => {
4321
+ const editor2 = editorRef.current;
4322
+ return editor2 ? execEditorCommand(editor2, command, () => setDialog({ kind: "block-menu" })) : false;
4323
+ },
4324
+ getMarkdown: () => documentToMarkdown(readDocument()),
4325
+ getOutline: () => getOutline(readDocument()),
4326
+ getEngine: () => editorRef.current,
4327
+ announce: (text) => announce(text)
4328
+ }),
4329
+ [readDocument, writeDocument, announce]
4330
+ );
4331
+ useImperativeHandle(props.ref, () => api, [api]);
4332
+ const editor = useEditor({
4333
+ extensions: builtExtensions,
4334
+ content: initial.json,
4335
+ editable,
4336
+ autofocus: autoFocus === true ? "end" : autoFocus === "start" || autoFocus === "end" ? autoFocus : false,
4337
+ immediatelyRender: false,
4338
+ shouldRerenderOnTransaction: false,
4339
+ injectCSS: false,
4340
+ editorProps: { attributes },
4341
+ onCreate: ({ editor: created }) => {
4342
+ editorRef.current = created;
4343
+ if (initial.warnings.length > 0) latest.current.onWarnings?.(initial.warnings);
4344
+ latest.current.onReady?.(api);
4345
+ },
4346
+ onUpdate: () => schedule(),
4347
+ onSelectionUpdate: ({ editor: current }) => latest.current.onSelectionChange?.(getSelectionInfo(current))
4348
+ });
4349
+ editorRef.current = editor;
4350
+ useEffect10(() => {
4351
+ if (!editor || value === void 0) return;
4352
+ if (value === lastDocRef.current) return;
4353
+ if (deepEqual(readDocument(), value)) {
4354
+ lastDocRef.current = value;
4355
+ return;
4356
+ }
4357
+ writeDocument(value);
4358
+ }, [editor, value, readDocument, writeDocument]);
4359
+ useEffect10(() => {
4360
+ if (editor && editor.isEditable !== editable) editor.setEditable(editable, false);
4361
+ }, [editor, editable]);
4362
+ const assist = useAiAssist({ provider: aiProvider, editor, announce });
4363
+ const openAi = assist.available ? () => setDialog({ kind: "ai" }) : void 0;
4364
+ const onSlashIds = useCallback3((ids) => {
4365
+ setSlashIds((previous) => previous.listbox === ids.listbox && previous.active === ids.active ? previous : ids);
4366
+ }, []);
4367
+ const onSlashSelect = useCallback3((item) => {
4368
+ slashState.current.props?.command(item);
4369
+ }, []);
4370
+ const onSlashHover = useCallback3((index) => {
4371
+ slashState.current = { ...slashState.current, index };
4372
+ setSlashIndex(index);
4373
+ }, []);
4374
+ return /* @__PURE__ */ jsx22(MessagesContext.Provider, { value: translator, children: /* @__PURE__ */ jsx22(EditorUiContext.Provider, { value: ui, children: /* @__PURE__ */ jsx22(EngineContext.Provider, { value: editor, children: /* @__PURE__ */ jsxs17("div", { "data-kit": "editor", "data-editable": editable ? "" : void 0, className, children: [
4375
+ toolbar && editable ? /* @__PURE__ */ jsx22(
4376
+ EditorToolbar,
4377
+ {
4378
+ editor,
4379
+ onOpenLink: openLinkDialog,
4380
+ onOpenImage: () => openImageDialog(),
4381
+ onOpenBlockMenu: openBlockMenu,
4382
+ onOpenAi: openAi,
4383
+ announce,
4384
+ children: toolbarEnd
4385
+ }
4386
+ ) : null,
4387
+ /* @__PURE__ */ jsxs17("div", { "data-kit": "editor-surface", ref: surfaceRef, children: [
4388
+ editor ? /* @__PURE__ */ jsx22(EditorContent, { editor, "data-kit": "editor-body" }) : /* @__PURE__ */ jsx22(
4389
+ EditorView,
4390
+ {
4391
+ value: initial.doc,
4392
+ ...blockRenderers ? { blockRenderers } : {},
4393
+ ...messages ? { messages } : {},
4394
+ ...locale ? { locale } : {},
4395
+ ...ariaLabel ? { "aria-label": ariaLabel } : {},
4396
+ ...ariaLabelledby ? { "aria-labelledby": ariaLabelledby } : {}
4397
+ }
4398
+ ),
4399
+ editor && editable && selectionMenu ? /* @__PURE__ */ jsx22(BubbleMenu, { editor, surfaceRef, onOpenLink: openLinkDialog, ...openAi ? { onOpenAi: openAi } : {}, announce }) : null,
4400
+ editor && editable && slash ? /* @__PURE__ */ jsx22(
4401
+ SlashMenu,
4402
+ {
4403
+ view: slash,
4404
+ activeIndex: slashIndex,
4405
+ surfaceRef,
4406
+ onHover: onSlashHover,
4407
+ onSelect: onSlashSelect,
4408
+ onIds: onSlashIds
4409
+ }
4410
+ ) : null,
4411
+ editor && dialog?.kind === "block-menu" ? /* @__PURE__ */ jsx22(BlockMenu, { editor, surfaceRef, onClose: closeDialog, announce }) : null,
4412
+ editor && dialog?.kind === "ai" ? /* @__PURE__ */ jsx22(AiAssistMenu, { assist, editor, onClose: closeDialog }) : null
4413
+ ] }),
4414
+ /* @__PURE__ */ jsx22(LiveRegion, { message: statusMessage }),
4415
+ editor && dialog?.kind === "link" ? /* @__PURE__ */ jsx22(LinkDialog, { editor, initialHref: dialog.href, onClose: closeDialog, announce }) : null,
4416
+ editor && dialog?.kind === "image" ? /* @__PURE__ */ jsx22(ImageDialog, { editor, ...dialog.pos === void 0 ? {} : { pos: dialog.pos }, onClose: closeDialog, announce }) : null
4417
+ ] }) }) }) });
4418
+ }
4419
+ export {
4420
+ AiAssistMenu,
4421
+ BLOCK_KINDS,
4422
+ Editor,
4423
+ EditorToolbar,
4424
+ EditorView,
4425
+ EmbedPreview,
4426
+ allSlashItems,
4427
+ createExtensions,
4428
+ createRuntime,
4429
+ createTranslator,
4430
+ defaultMessages,
4431
+ filterSlashItems,
4432
+ formatMessage,
4433
+ mergeMessages,
4434
+ textToContent,
4435
+ useAiAssist,
4436
+ useMessages,
4437
+ useTranslator
4438
+ };
4439
+ //# sourceMappingURL=react.js.map