@crewlethq/ui 0.29.0 → 0.31.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.
Files changed (51) hide show
  1. package/README.md +28 -3
  2. package/dist/ArtifactBar/ArtifactBar.d.ts +13 -1
  3. package/dist/ArtifactBar/index.js +1 -1
  4. package/dist/{ArtifactBar-IBWB5POL.css → ArtifactBar-MLMJHWY6.css} +7 -0
  5. package/dist/ArtifactCover/index.js +2 -2
  6. package/dist/CommentThread/index.js +2 -2
  7. package/dist/DataView/index.js +2 -2
  8. package/dist/Document/Document.d.ts +8 -1
  9. package/dist/Document/index.js +1 -1
  10. package/dist/{Document-PQGXJHB7.css → Document-G3YTZIUL.css} +71 -0
  11. package/dist/EditingStatus/EditingStatus.d.ts +25 -0
  12. package/dist/EditingStatus/index.d.ts +3 -0
  13. package/dist/EditingStatus/index.js +2 -0
  14. package/dist/EditingStatus-NFADEPSX.css +53 -0
  15. package/dist/Editor-YZ7KG2RD.js +2099 -0
  16. package/dist/Markdown/grammar.d.ts +170 -0
  17. package/dist/Markdown/index.d.ts +2 -0
  18. package/dist/Markdown/index.js +1 -0
  19. package/dist/RichEditor/Editor.d.ts +17 -0
  20. package/dist/RichEditor/RichEditor.d.ts +26 -0
  21. package/dist/RichEditor/codec.d.ts +78 -0
  22. package/dist/RichEditor/commands.d.ts +89 -0
  23. package/dist/RichEditor/index.d.ts +4 -0
  24. package/dist/RichEditor/index.js +8 -0
  25. package/dist/RichEditor/keys.d.ts +16 -0
  26. package/dist/RichEditor/nodeviews.d.ts +60 -0
  27. package/dist/RichEditor/paste.d.ts +47 -0
  28. package/dist/RichEditor/plugins.d.ts +49 -0
  29. package/dist/RichEditor/roundtrip.d.ts +14 -0
  30. package/dist/RichEditor/schema.d.ts +23 -0
  31. package/dist/RichEditor/types.d.ts +140 -0
  32. package/dist/RichEditor-NPD2KAPS.css +419 -0
  33. package/dist/RichEditorMarkdown/index.d.ts +1 -0
  34. package/dist/RichEditorMarkdown/index.js +3 -0
  35. package/dist/chunk-3DPIU2Q7.js +28 -0
  36. package/dist/{chunk-XALUCIG5.js → chunk-6AH2XJ3Q.js} +1 -1
  37. package/dist/chunk-A4E5YLXO.js +85 -0
  38. package/dist/{chunk-FKULOLKG.js → chunk-GR55WT4R.js} +1 -1
  39. package/dist/chunk-IDBHX5SR.js +12 -0
  40. package/dist/{chunk-WI6K5M5X.js → chunk-M3LUNN7F.js} +1 -1
  41. package/dist/{chunk-QMBP23BL.js → chunk-P4BOXB6E.js} +17 -1
  42. package/dist/chunk-SKOYC2CF.js +288 -0
  43. package/dist/chunk-VOX3YRFO.js +76 -0
  44. package/dist/chunk-YFW4MTK4.js +749 -0
  45. package/dist/generated/index.d.ts +4 -0
  46. package/dist/generated/index.js +50 -44
  47. package/dist/index.js +50 -44
  48. package/dist/styles.css +558 -2
  49. package/package.json +13 -3
  50. package/dist/{chunk-IFNQKWUO.js → chunk-OC46246T.js} +0 -0
  51. package/dist/{chunk-RODMM35O.js → chunk-UVJ5FF5V.js} +1 -1
@@ -0,0 +1,2099 @@
1
+ import { richEditorLabels } from './chunk-A4E5YLXO.js';
2
+ import { schema, readMarkdown, writeMarkdown } from './chunk-YFW4MTK4.js';
3
+ import { safeHref, MARKDOWN_CALLOUT_TONES } from './chunk-SKOYC2CF.js';
4
+ import { Toolbar } from './chunk-PGRPNDWM.js';
5
+ import { FormField, Input } from './chunk-6QFAK2WV.js';
6
+ import './chunk-PQADV6P6.js';
7
+ import { Menu } from './chunk-2HIOJ4YZ.js';
8
+ import { placePopup, viewportBounds } from './chunk-HMPFGIWM.js';
9
+ import { usePopupLayer } from './chunk-R25RGPUB.js';
10
+ import { Button } from './chunk-OG6CL3EB.js';
11
+ import { IconButton } from './chunk-4FQT6ZAY.js';
12
+ import { VisuallyHidden } from './chunk-BZFWDIV6.js';
13
+ import { cx } from './chunk-ODEQTPFM.js';
14
+ import { forwardRef, useMemo, useRef, useState, useId, useSyncExternalStore, useCallback, useLayoutEffect, useEffect, useImperativeHandle } from 'react';
15
+ import { createPortal } from 'react-dom';
16
+ import { gapCursor } from 'prosemirror-gapcursor';
17
+ import { history, redo, undo } from 'prosemirror-history';
18
+ import { TextSelection, PluginKey, Plugin, EditorState, NodeSelection, Selection } from 'prosemirror-state';
19
+ import { EditorView, DecorationSet, Decoration } from 'prosemirror-view';
20
+ import { TypeGlyph, Heading1Glyph, Heading2Glyph, Heading3Glyph, ListGlyph, ListOrderedGlyph, ListChecksGlyph, TextQuoteGlyph, InfoGlyph, SquareCodeGlyph, TableGlyph, MinusGlyph, ImageGlyph, LinkGlyph, BoldGlyph, ItalicGlyph, StrikethroughGlyph, CodeGlyph, PencilGlyph, TrashGlyph, ExternalLinkGlyph, UnlinkGlyph, ChevronDownGlyph, TextAlignStartGlyph, TextAlignCenterGlyph, TextAlignEndGlyph } from '@crewlethq/icons/glyphs';
21
+ import { toggleMark, setBlockType, wrapIn, chainCommands, baseKeymap, exitCode } from 'prosemirror-commands';
22
+ import { Fragment as Fragment$1, Slice, DOMParser } from 'prosemirror-model';
23
+ import { liftListItem, wrapInList, sinkListItem, splitListItem } from 'prosemirror-schema-list';
24
+ import { InputRule, inputRules, textblockTypeInputRule, wrappingInputRule, undoInputRule } from 'prosemirror-inputrules';
25
+ import { keymap } from 'prosemirror-keymap';
26
+ import { findWrapping } from 'prosemirror-transform';
27
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
28
+
29
+ var N = schema.nodes;
30
+ var M = schema.marks;
31
+ function markActive(state, type) {
32
+ const { from, $from, to, empty } = state.selection;
33
+ if (empty) return Boolean(type.isInSet(state.storedMarks ?? $from.marks()));
34
+ return state.doc.rangeHasMark(from, to, type);
35
+ }
36
+ function headingLevel(state) {
37
+ const parent = state.selection.$from.parent;
38
+ return parent.type === N.heading ? parent.attrs.level : 0;
39
+ }
40
+ function inCode(state) {
41
+ return Boolean(state.selection.$from.parent.type.spec.code);
42
+ }
43
+ var toggleStrong = toggleMark(M.strong);
44
+ var toggleEm = toggleMark(M.em);
45
+ var toggleStrike = toggleMark(M.strike);
46
+ var toggleCode = toggleMark(M.code);
47
+ function linkAt(state) {
48
+ const { $from, from, to, empty } = state.selection;
49
+ const parent = $from.parent;
50
+ if (!parent.inlineContent) return null;
51
+ const runs = [];
52
+ let pos = $from.start();
53
+ let run = null;
54
+ parent.forEach((child) => {
55
+ const mark = M.link.isInSet(child.marks);
56
+ const end = pos + child.nodeSize;
57
+ if (mark && run && run.mark.eq(mark)) {
58
+ run.to = end;
59
+ } else {
60
+ run = mark ? { from: pos, to: end, mark } : null;
61
+ if (run) runs.push(run);
62
+ }
63
+ pos = end;
64
+ });
65
+ return runs.find((r) => empty ? from > r.from && from <= r.to : from >= r.from && to <= r.to) ?? null;
66
+ }
67
+ function setLink(href, text) {
68
+ return (state, dispatch) => {
69
+ if (inCode(state)) return false;
70
+ if (!dispatch) return true;
71
+ const mark = M.link.create({ href });
72
+ const at = linkAt(state);
73
+ const { from, to, empty } = state.selection;
74
+ const tr = state.tr;
75
+ if (!empty) {
76
+ tr.removeMark(from, to, M.link).addMark(from, to, mark);
77
+ } else if (at) {
78
+ tr.removeMark(at.from, at.to, M.link).addMark(at.from, at.to, mark);
79
+ } else {
80
+ const words = text?.trim() || href;
81
+ tr.insert(from, schema.text(words, [mark, ...state.selection.$from.marks().filter((m) => m.type !== M.link)]));
82
+ tr.setSelection(TextSelection.create(tr.doc, from + words.length));
83
+ tr.removeStoredMark(M.link);
84
+ }
85
+ dispatch(tr.scrollIntoView());
86
+ return true;
87
+ };
88
+ }
89
+ var removeLink = (state, dispatch) => {
90
+ const at = linkAt(state);
91
+ const { from, to, empty } = state.selection;
92
+ if (!at && (empty || !state.doc.rangeHasMark(from, to, M.link))) return false;
93
+ if (dispatch) {
94
+ const tr = state.tr;
95
+ if (at) tr.removeMark(at.from, at.to, M.link);
96
+ if (!empty) tr.removeMark(from, to, M.link);
97
+ dispatch(tr);
98
+ }
99
+ return true;
100
+ };
101
+ function toggleHeading(level) {
102
+ return (state, dispatch) => headingLevel(state) === level ? setBlockType(N.paragraph)(state, dispatch) : setBlockType(N.heading, { level })(state, dispatch);
103
+ }
104
+ var setParagraph = setBlockType(N.paragraph);
105
+ function setCodeBlock(lang = "") {
106
+ return setBlockType(N.code_block, { lang, fence: "```" });
107
+ }
108
+ var wrapQuote = wrapIn(N.blockquote);
109
+ function wrapCallout(tone = "note") {
110
+ return wrapIn(N.callout, { tone });
111
+ }
112
+ function listAround(state) {
113
+ const { $from } = state.selection;
114
+ for (let d = $from.depth - 1; d > 0; d--) {
115
+ const node = $from.node(d);
116
+ if (node.type === N.list_item) {
117
+ const list = $from.node(d - 1);
118
+ return { node: list, pos: $from.before(d - 1), depth: d - 1 };
119
+ }
120
+ }
121
+ return null;
122
+ }
123
+ function kindOf(list) {
124
+ if (list.type === N.ordered_list) return "ordered";
125
+ let task = false;
126
+ list.forEach((item) => {
127
+ if (item.attrs.checked !== null) task = true;
128
+ });
129
+ return task ? "task" : "bullet";
130
+ }
131
+ function retask(tr, pos, kind) {
132
+ const list = tr.doc.nodeAt(pos);
133
+ if (!list) return;
134
+ let offset = pos + 1;
135
+ list.forEach((item) => {
136
+ const checked = kind === "task" ? item.attrs.checked ?? false : null;
137
+ if (checked !== item.attrs.checked) tr.setNodeMarkup(offset, void 0, { ...item.attrs, checked });
138
+ offset += item.nodeSize;
139
+ });
140
+ }
141
+ function setList(kind) {
142
+ return (state, dispatch) => {
143
+ const around = listAround(state);
144
+ if (around) {
145
+ if (kindOf(around.node) === kind) return liftListItem(N.list_item)(state, dispatch);
146
+ if (dispatch) {
147
+ const type2 = kind === "ordered" ? N.ordered_list : N.bullet_list;
148
+ const tr = state.tr.setNodeMarkup(around.pos, type2, type2 === around.node.type ? around.node.attrs : null);
149
+ retask(tr, around.pos, kind);
150
+ dispatch(tr);
151
+ }
152
+ return true;
153
+ }
154
+ const type = kind === "ordered" ? N.ordered_list : N.bullet_list;
155
+ let wrapped = null;
156
+ if (!wrapInList(type)(state, (tr) => wrapped = tr)) return false;
157
+ if (dispatch && wrapped) {
158
+ const tr = wrapped;
159
+ if (kind === "task") {
160
+ const $pos = tr.selection.$from;
161
+ for (let d = $pos.depth; d > 0; d--) {
162
+ if ($pos.node(d).type === type) {
163
+ retask(tr, $pos.before(d), "task");
164
+ break;
165
+ }
166
+ }
167
+ }
168
+ dispatch(tr);
169
+ }
170
+ return true;
171
+ };
172
+ }
173
+ var splitItem = (state, dispatch) => {
174
+ const { $from } = state.selection;
175
+ if ($from.depth < 2 || $from.node(-1).type !== N.list_item) return false;
176
+ const checked = $from.node(-1).attrs.checked;
177
+ return splitListItem(N.list_item, checked === null ? void 0 : { checked: false })(state, dispatch);
178
+ };
179
+ function toggleTask(pos) {
180
+ return (state, dispatch) => {
181
+ const item = state.doc.nodeAt(pos);
182
+ if (!item || item.type !== N.list_item || item.attrs.checked === null) return false;
183
+ if (dispatch) dispatch(state.tr.setNodeMarkup(pos, void 0, { ...item.attrs, checked: !item.attrs.checked }));
184
+ return true;
185
+ };
186
+ }
187
+ function insertBlock(node, caret) {
188
+ return (state, dispatch) => {
189
+ const { $from } = state.selection;
190
+ if ($from.parent.type.spec.code) return false;
191
+ const depth = $from.depth;
192
+ const empty = $from.parent.type === N.paragraph && $from.parent.childCount === 0;
193
+ const from = empty ? $from.before(depth) : $from.after(depth);
194
+ const to = empty ? $from.after(depth) : from;
195
+ const container = $from.node(depth - 1);
196
+ const index = empty ? $from.index(depth - 1) : $from.indexAfter(depth - 1);
197
+ const end = empty ? index + 1 : index;
198
+ const nodes = end >= container.childCount ? [node, N.paragraph.create()] : [node];
199
+ if (!container.canReplace(index, end, Fragment$1.from(nodes))) return false;
200
+ if (dispatch) {
201
+ const tr = state.tr.replaceWith(from, to, nodes);
202
+ const target = caret === "inside" ? from + 1 : from + node.nodeSize;
203
+ tr.setSelection(Selection.near(tr.doc.resolve(Math.min(target, tr.doc.content.size)), 1));
204
+ dispatch(tr.scrollIntoView());
205
+ }
206
+ return true;
207
+ };
208
+ }
209
+ var insertRule = insertBlock(N.horizontal_rule.create(), "after");
210
+ function insertImage(src, alt) {
211
+ return (state, dispatch) => {
212
+ if (inCode(state)) return false;
213
+ const image = N.image.create({ src, alt });
214
+ if (dispatch) dispatch(state.tr.replaceSelectionWith(image, false).scrollIntoView());
215
+ return true;
216
+ };
217
+ }
218
+ function tableAt(state) {
219
+ const { $from } = state.selection;
220
+ for (let d = $from.depth; d > 0; d--) {
221
+ if ($from.node(d).type === N.table) {
222
+ if ($from.depth < d + 2) return null;
223
+ return { table: $from.node(d), pos: $from.before(d), row: $from.index(d), col: $from.index(d + 1) };
224
+ }
225
+ }
226
+ return null;
227
+ }
228
+ function gridOf(table) {
229
+ const cells = [];
230
+ const align = [];
231
+ let width = 0;
232
+ table.forEach((row, _o, r) => {
233
+ const line = [];
234
+ row.forEach((cell, _co, c) => {
235
+ line.push(cell.content);
236
+ if (r === 0) align[c] = cell.attrs.align;
237
+ });
238
+ width = Math.max(width, line.length);
239
+ cells.push(line);
240
+ });
241
+ for (const line of cells) while (line.length < width) line.push(Fragment$1.empty);
242
+ for (let c = 0; c < width; c++) align[c] ??= "left";
243
+ return { cells, align };
244
+ }
245
+ function tableOf(grid) {
246
+ return N.table.create(
247
+ null,
248
+ grid.cells.map(
249
+ (line, r) => N.table_row.create(
250
+ null,
251
+ line.map((content, c) => (r === 0 ? N.table_header : N.table_cell).create({ align: grid.align[c] ?? "left" }, content))
252
+ )
253
+ )
254
+ );
255
+ }
256
+ function rebuild(state, at, grid, row, col) {
257
+ const table = tableOf(grid);
258
+ const tr = state.tr.replaceWith(at.pos, at.pos + at.table.nodeSize, table);
259
+ let pos = at.pos + 1;
260
+ table.forEach((r, _o, ri) => {
261
+ if (ri < row) pos += r.nodeSize;
262
+ });
263
+ pos += 1;
264
+ const target = table.child(Math.min(row, table.childCount - 1));
265
+ target.forEach((cell, _o, ci) => {
266
+ if (ci < col) pos += cell.nodeSize;
267
+ });
268
+ tr.setSelection(TextSelection.create(tr.doc, pos + 1));
269
+ return tr.scrollIntoView();
270
+ }
271
+ function insertTable(rows = 3, cols = 3) {
272
+ const grid = {
273
+ cells: Array.from({ length: rows }, () => Array.from({ length: cols }, () => Fragment$1.empty)),
274
+ align: Array.from({ length: cols }, () => "left")
275
+ };
276
+ return insertBlock(tableOf(grid), "inside");
277
+ }
278
+ function tableCommand(edit) {
279
+ return (state, dispatch) => {
280
+ const at = tableAt(state);
281
+ if (!at) return false;
282
+ if (!dispatch) return true;
283
+ const grid = gridOf(at.table);
284
+ const result = edit(grid, at);
285
+ if (result === "delete") {
286
+ const tr = state.tr.delete(at.pos, at.pos + at.table.nodeSize);
287
+ if (tr.doc.childCount === 0) tr.insert(0, N.paragraph.create());
288
+ tr.setSelection(Selection.near(tr.doc.resolve(Math.min(at.pos, tr.doc.content.size)), -1));
289
+ dispatch(tr.scrollIntoView());
290
+ return true;
291
+ }
292
+ dispatch(rebuild(state, at, grid, result.row, result.col));
293
+ return true;
294
+ };
295
+ }
296
+ var blankRow = (grid) => grid.align.map(() => Fragment$1.empty);
297
+ var addRowBefore = tableCommand((grid, at) => {
298
+ grid.cells.splice(at.row, 0, blankRow(grid));
299
+ return { row: at.row, col: at.col };
300
+ });
301
+ var addRowAfter = tableCommand((grid, at) => {
302
+ grid.cells.splice(at.row + 1, 0, blankRow(grid));
303
+ return { row: at.row + 1, col: at.col };
304
+ });
305
+ var addColumnBefore = tableCommand((grid, at) => {
306
+ for (const line of grid.cells) line.splice(at.col, 0, Fragment$1.empty);
307
+ grid.align.splice(at.col, 0, "left");
308
+ return { row: at.row, col: at.col };
309
+ });
310
+ var addColumnAfter = tableCommand((grid, at) => {
311
+ for (const line of grid.cells) line.splice(at.col + 1, 0, Fragment$1.empty);
312
+ grid.align.splice(at.col + 1, 0, "left");
313
+ return { row: at.row, col: at.col + 1 };
314
+ });
315
+ var deleteRow = tableCommand((grid, at) => {
316
+ if (grid.cells.length <= 1) return "delete";
317
+ grid.cells.splice(at.row, 1);
318
+ return { row: Math.min(at.row, grid.cells.length - 1), col: at.col };
319
+ });
320
+ var deleteColumn = tableCommand((grid, at) => {
321
+ if (grid.align.length <= 1) return "delete";
322
+ for (const line of grid.cells) line.splice(at.col, 1);
323
+ grid.align.splice(at.col, 1);
324
+ return { row: at.row, col: Math.min(at.col, grid.align.length - 1) };
325
+ });
326
+ var deleteTable = tableCommand(() => "delete");
327
+ function alignColumn(align) {
328
+ return tableCommand((grid, at) => {
329
+ grid.align[at.col] = align;
330
+ return { row: at.row, col: at.col };
331
+ });
332
+ }
333
+ function columnAlign(state) {
334
+ const at = tableAt(state);
335
+ if (!at) return null;
336
+ return gridOf(at.table).align[at.col] ?? "left";
337
+ }
338
+ function moveCell(direction) {
339
+ return (state, dispatch) => {
340
+ const at = tableAt(state);
341
+ if (!at) return false;
342
+ const rows = at.table.childCount;
343
+ const cols = at.table.child(at.row).childCount;
344
+ let row = at.row;
345
+ let col = at.col;
346
+ if (direction === "next") {
347
+ col++;
348
+ if (col >= cols) {
349
+ col = 0;
350
+ row++;
351
+ }
352
+ } else if (direction === "previous") {
353
+ col--;
354
+ if (col < 0) {
355
+ row--;
356
+ col = row >= 0 ? at.table.child(row).childCount - 1 : 0;
357
+ }
358
+ if (row < 0) return true;
359
+ } else {
360
+ row++;
361
+ }
362
+ if (!dispatch) return true;
363
+ if (row >= rows) {
364
+ const grid = gridOf(at.table);
365
+ grid.cells.push(blankRow(grid));
366
+ dispatch(rebuild(state, at, grid, row, direction === "down" ? at.col : 0));
367
+ return true;
368
+ }
369
+ let pos = at.pos + 1;
370
+ at.table.forEach((r, _o, ri) => {
371
+ if (ri < row) pos += r.nodeSize;
372
+ });
373
+ pos += 1;
374
+ at.table.child(row).forEach((cell2, _o, ci) => {
375
+ if (ci < col) pos += cell2.nodeSize;
376
+ });
377
+ const cell = at.table.child(row).child(Math.min(col, at.table.child(row).childCount - 1));
378
+ const tr = state.tr.setSelection(
379
+ direction === "down" ? TextSelection.create(state.doc, pos + 1) : TextSelection.create(state.doc, pos + 1, pos + 1 + cell.content.size)
380
+ );
381
+ dispatch(tr.scrollIntoView());
382
+ return true;
383
+ };
384
+ }
385
+ var exitCodeOnBlankLines = (state, dispatch) => {
386
+ const { $from, empty } = state.selection;
387
+ if (!empty || !$from.parent.type.spec.code || $from.parentOffset !== $from.parent.content.size) return false;
388
+ const text = $from.parent.textContent;
389
+ if (!text.endsWith("\n\n")) return false;
390
+ if (dispatch) {
391
+ const end = $from.pos;
392
+ const tr = state.tr.delete(end - 2, end);
393
+ const after = tr.mapping.map($from.after());
394
+ tr.insert(after, N.paragraph.create());
395
+ tr.setSelection(TextSelection.create(tr.doc, after + 1));
396
+ dispatch(tr.scrollIntoView());
397
+ }
398
+ return true;
399
+ };
400
+ var fenceToCode = (state, dispatch) => {
401
+ const { $from, empty } = state.selection;
402
+ if (!empty || $from.parent.type !== N.paragraph || $from.parentOffset !== $from.parent.content.size) return false;
403
+ const m = /^```([A-Za-z0-9_+-]*)$/.exec($from.parent.textContent);
404
+ if (!m) return false;
405
+ if (dispatch) {
406
+ const start = $from.start();
407
+ const tr = state.tr.delete(start, $from.pos).setBlockType(start, start, N.code_block, { lang: m[1] ?? "", fence: "```" });
408
+ dispatch(tr.scrollIntoView());
409
+ }
410
+ return true;
411
+ };
412
+ function setCalloutTone(pos, tone) {
413
+ return (state, dispatch) => {
414
+ const node = state.doc.nodeAt(pos);
415
+ if (!node || node.type !== N.callout) return false;
416
+ if (dispatch) dispatch(state.tr.setNodeMarkup(pos, void 0, { ...node.attrs, tone }));
417
+ return true;
418
+ };
419
+ }
420
+ function setCodeLanguage(pos, lang) {
421
+ return (state, dispatch) => {
422
+ const node = state.doc.nodeAt(pos);
423
+ if (!node || node.type !== N.code_block) return false;
424
+ if (dispatch) dispatch(state.tr.setNodeMarkup(pos, void 0, { ...node.attrs, lang: lang.replace(/[^A-Za-z0-9_+-]/g, "") }));
425
+ return true;
426
+ };
427
+ }
428
+ function unwrapAt(pos) {
429
+ return (state, dispatch) => {
430
+ const node = state.doc.nodeAt(pos);
431
+ if (!node || node.type !== N.callout && node.type !== N.blockquote) return false;
432
+ if (dispatch) {
433
+ const tr = state.tr.replaceWith(pos, pos + node.nodeSize, node.content);
434
+ tr.setSelection(Selection.near(tr.doc.resolve(Math.min(pos + 1, tr.doc.content.size))));
435
+ dispatch(tr.scrollIntoView());
436
+ }
437
+ return true;
438
+ };
439
+ }
440
+ var N2 = schema.nodes;
441
+ var M2 = schema.marks;
442
+ function markRule(pattern, type, delimiter, attrs) {
443
+ return new InputRule(pattern, (state, match, start, end) => {
444
+ const whole = match[1] ?? "";
445
+ const inner = match[2] ?? "";
446
+ const from = start + (match[0].length - whole.length);
447
+ const innerFrom = from + delimiter;
448
+ const innerTo = innerFrom + inner.length;
449
+ const tr = state.tr;
450
+ tr.delete(innerTo, end);
451
+ tr.delete(from, innerFrom);
452
+ tr.addMark(from, from + inner.length, type.create(attrs));
453
+ tr.removeStoredMark(type);
454
+ return tr;
455
+ });
456
+ }
457
+ var taskRule = new InputRule(/^\[( |x|X)?\]\s$/, (state, match, start, end) => {
458
+ const checked = (match[1] ?? " ").toLowerCase() === "x";
459
+ const tr = state.tr.delete(start, end);
460
+ const $start = tr.doc.resolve(start);
461
+ const item = $start.depth >= 2 ? $start.node(-1) : null;
462
+ if (item && item.type === N2.list_item && $start.index(-1) === 0) {
463
+ return tr.setNodeMarkup($start.before(-1), void 0, { ...item.attrs, checked });
464
+ }
465
+ const range = $start.blockRange();
466
+ const wrapping = range && findWrapping(range, N2.bullet_list);
467
+ if (!range || !wrapping) return null;
468
+ tr.wrap(range, wrapping);
469
+ const $inner = tr.doc.resolve(tr.mapping.map(start));
470
+ for (let d = $inner.depth; d > 0; d--) {
471
+ if ($inner.node(d).type === N2.list_item) {
472
+ tr.setNodeMarkup($inner.before(d), void 0, { checked });
473
+ break;
474
+ }
475
+ }
476
+ return tr;
477
+ });
478
+ var ruleRule = new InputRule(/^(?:---|\*\*\*|___)$/, (state, _match, start, end) => {
479
+ const $start = state.doc.resolve(start);
480
+ if ($start.parent.type !== N2.paragraph || $start.parent.content.size !== end - start) return null;
481
+ const from = $start.before();
482
+ const to = $start.after();
483
+ const tr = state.tr.replaceWith(from, to, [N2.horizontal_rule.create(), N2.paragraph.create()]);
484
+ return tr.setSelection(TextSelection.create(tr.doc, from + 2));
485
+ });
486
+ var linkRule = new InputRule(/(?:^|[^!])(\[([^\]]+)\]\(([^)\s]+)\))$/, (state, match, start, end) => {
487
+ const whole = match[1] ?? "";
488
+ const text = match[2] ?? "";
489
+ const href = safeHref(match[3] ?? "");
490
+ if (!href) return null;
491
+ const from = start + (match[0].length - whole.length);
492
+ return state.tr.replaceWith(from, end, schema.text(text, [M2.link.create({ href })]));
493
+ });
494
+ function buildInputRules() {
495
+ return inputRules({
496
+ rules: [
497
+ textblockTypeInputRule(/^(#{1,6})\s$/, N2.heading, (m) => ({ level: (m[1] ?? "#").length })),
498
+ taskRule,
499
+ wrappingInputRule(/^\s*([-+*])\s$/, N2.bullet_list, (m) => ({ marker: m[1] })),
500
+ wrappingInputRule(
501
+ /^(\d{1,9})([.)])\s$/,
502
+ N2.ordered_list,
503
+ (m) => ({ start: Number(m[1]), marker: m[2] }),
504
+ (m, node) => node.childCount + node.attrs.start === Number(m[1])
505
+ ),
506
+ wrappingInputRule(/^\s*>\s$/, N2.blockquote),
507
+ textblockTypeInputRule(/^```([A-Za-z0-9_+-]*)\s$/, N2.code_block, (m) => ({ lang: m[1] ?? "", fence: "```" })),
508
+ ruleRule,
509
+ linkRule,
510
+ markRule(/(?:^|[^*\w])(\*\*([^*\s](?:[^*]*[^*\s])?)\*\*)$/, M2.strong, 2, { marker: "**" }),
511
+ markRule(/(?:^|[^_\p{L}\p{N}])(__([^_\s](?:[^_]*[^_\s])?)__)$/u, M2.strong, 2, { marker: "__" }),
512
+ markRule(/(?:^|[^*\w])(\*([^*\s](?:[^*]*[^*\s])?)\*)$/, M2.em, 1, { marker: "*" }),
513
+ markRule(/(?:^|[^_\p{L}\p{N}])(_([^_\s](?:[^_]*[^_\s])?)_)$/u, M2.em, 1, { marker: "_" }),
514
+ markRule(/(?:^|[^~])(~~([^~\s](?:[^~]*[^~\s])?)~~)$/, M2.strike, 2),
515
+ markRule(/(?:^|[^`])(`([^`]+)`)$/, M2.code, 1)
516
+ ]
517
+ });
518
+ }
519
+ var softBreak = (state, dispatch) => {
520
+ if (tableAt(state)) return true;
521
+ if (state.selection.$from.parent.type === N2.heading) return false;
522
+ if (exitCode(state, dispatch)) return true;
523
+ if (dispatch) dispatch(state.tr.replaceSelectionWith(N2.hard_break.create()).scrollIntoView());
524
+ return true;
525
+ };
526
+ var indentCode = (state, dispatch) => {
527
+ if (!state.selection.$from.parent.type.spec.code) return false;
528
+ if (dispatch) dispatch(state.tr.insertText(" ").scrollIntoView());
529
+ return true;
530
+ };
531
+ var outdentCode = (state, dispatch) => {
532
+ const { $from, empty } = state.selection;
533
+ if (!$from.parent.type.spec.code || !empty) return false;
534
+ const text = $from.parent.textContent;
535
+ const lineStart = text.lastIndexOf("\n", $from.parentOffset - 1) + 1;
536
+ const spaces = /^ {1,2}/.exec(text.slice(lineStart))?.[0].length ?? 0;
537
+ if (spaces > 0 && dispatch) dispatch(state.tr.delete($from.start() + lineStart, $from.start() + lineStart + spaces));
538
+ return true;
539
+ };
540
+ function buildKeymap(hooks) {
541
+ const keys = {
542
+ "Mod-z": undo,
543
+ "Shift-Mod-z": redo,
544
+ "Mod-y": redo,
545
+ Backspace: undoInputRule,
546
+ "Mod-b": toggleStrong,
547
+ "Mod-i": toggleEm,
548
+ "Mod-e": toggleCode,
549
+ "Mod-Shift-s": toggleStrike,
550
+ "Mod-Shift-x": toggleStrike,
551
+ "Mod-k": () => hooks.link(),
552
+ "Mod-Alt-0": setParagraph,
553
+ "Mod-Shift-7": setList("ordered"),
554
+ "Mod-Shift-8": setList("bullet"),
555
+ "Mod-Shift-9": setList("task"),
556
+ "Mod-Enter": () => hooks.submit(),
557
+ "Shift-Enter": softBreak,
558
+ Enter: chainCommands(moveCell("down"), fenceToCode, exitCodeOnBlankLines, splitItem),
559
+ Tab: chainCommands(moveCell("next"), sinkListItem(N2.list_item), indentCode),
560
+ "Shift-Tab": chainCommands(moveCell("previous"), liftListItem(N2.list_item), outdentCode),
561
+ "Alt-F10": () => hooks.toolbar()
562
+ };
563
+ for (let level = 1; level <= 6; level++) {
564
+ keys[`Mod-Alt-${level}`] = level <= 3 ? toggleHeading(level) : setBlockType(N2.heading, { level });
565
+ }
566
+ return [keymap(keys), keymap(baseKeymap)];
567
+ }
568
+
569
+ // src/RichEditor/nodeviews.ts
570
+ var SlotStore = class {
571
+ slots = /* @__PURE__ */ new Set();
572
+ listeners = /* @__PURE__ */ new Set();
573
+ snapshot = [];
574
+ add(slot) {
575
+ this.slots.add(slot);
576
+ this.emit();
577
+ }
578
+ remove(slot) {
579
+ this.slots.delete(slot);
580
+ this.emit();
581
+ }
582
+ changed() {
583
+ this.emit();
584
+ }
585
+ subscribe = (listener) => {
586
+ this.listeners.add(listener);
587
+ return () => this.listeners.delete(listener);
588
+ };
589
+ get = () => this.snapshot;
590
+ emit() {
591
+ this.snapshot = [...this.slots];
592
+ for (const listener of this.listeners) listener();
593
+ }
594
+ };
595
+ function slotElement(className) {
596
+ const el = document.createElement("div");
597
+ el.className = className;
598
+ el.contentEditable = "false";
599
+ return el;
600
+ }
601
+ var SlotView = class {
602
+ constructor(node, store, getPos, build) {
603
+ this.node = node;
604
+ this.store = store;
605
+ const built = build();
606
+ this.dom = built.dom;
607
+ this.contentDOM = built.contentDOM;
608
+ this.slot = { kind: built.kind, element: built.slot, node, getPos };
609
+ store.add(this.slot);
610
+ }
611
+ node;
612
+ store;
613
+ dom;
614
+ contentDOM;
615
+ slot;
616
+ update(node) {
617
+ if (node.type !== this.node.type) return false;
618
+ this.node = node;
619
+ this.slot.node = node;
620
+ this.paint(node);
621
+ this.store.changed();
622
+ return true;
623
+ }
624
+ paint(node) {
625
+ if (node.type.name === "callout") this.dom.setAttribute("data-callout", node.attrs.tone);
626
+ if (node.type.name === "code_block") {
627
+ const pre = this.contentDOM.parentElement;
628
+ if (node.attrs.lang) pre?.setAttribute("data-lang", node.attrs.lang);
629
+ else pre?.removeAttribute("data-lang");
630
+ }
631
+ }
632
+ stopEvent(event) {
633
+ return this.slot.element.contains(event.target);
634
+ }
635
+ ignoreMutation(mutation) {
636
+ return this.slot.element.contains(mutation.target);
637
+ }
638
+ destroy() {
639
+ this.store.remove(this.slot);
640
+ }
641
+ };
642
+ function codeBlockView(store) {
643
+ return (node, _view, getPos) => {
644
+ const view = new SlotView(node, store, getPos, () => {
645
+ const dom = document.createElement("div");
646
+ dom.className = "crewlet-rich-editor__code";
647
+ const slot = slotElement("crewlet-rich-editor__code-bar");
648
+ const pre = document.createElement("pre");
649
+ pre.className = "md-code";
650
+ const code = document.createElement("code");
651
+ pre.appendChild(code);
652
+ dom.append(slot, pre);
653
+ return { dom, contentDOM: code, slot, kind: "code" };
654
+ });
655
+ view.paint(node);
656
+ return view;
657
+ };
658
+ }
659
+ function calloutView(store) {
660
+ return (node, _view, getPos) => {
661
+ const view = new SlotView(node, store, getPos, () => {
662
+ const dom = document.createElement("div");
663
+ dom.setAttribute("role", "note");
664
+ const slot = slotElement("crewlet-rich-editor__callout-bar");
665
+ slot.setAttribute("data-callout-label", "");
666
+ const body = document.createElement("div");
667
+ body.setAttribute("data-callout-body", "");
668
+ dom.append(slot, body);
669
+ return { dom, contentDOM: body, slot, kind: "callout" };
670
+ });
671
+ view.paint(node);
672
+ return view;
673
+ };
674
+ }
675
+ function listItemView(labels) {
676
+ return (node, view, getPos) => {
677
+ const li = document.createElement("li");
678
+ if (node.attrs.checked === null) return { dom: li, contentDOM: li };
679
+ const box = document.createElement("input");
680
+ box.type = "checkbox";
681
+ box.contentEditable = "false";
682
+ const body = document.createElement("div");
683
+ body.className = "md-task-body";
684
+ li.append(box, body);
685
+ const paint = (checked) => {
686
+ box.checked = checked;
687
+ box.setAttribute("aria-label", checked ? labels().notDone : labels().done);
688
+ };
689
+ paint(Boolean(node.attrs.checked));
690
+ box.addEventListener("mousedown", (event) => event.preventDefault());
691
+ box.addEventListener("click", (event) => {
692
+ event.preventDefault();
693
+ const pos = getPos();
694
+ if (pos === void 0 || !view.editable) return;
695
+ toggleTask(pos)(view.state, view.dispatch);
696
+ });
697
+ let current = node;
698
+ return {
699
+ dom: li,
700
+ contentDOM: body,
701
+ update(next) {
702
+ if (next.type !== current.type || next.attrs.checked === null !== (current.attrs.checked === null)) return false;
703
+ current = next;
704
+ paint(Boolean(next.attrs.checked));
705
+ return true;
706
+ },
707
+ stopEvent: (event) => event.target === box,
708
+ ignoreMutation: (mutation) => mutation.target === box
709
+ };
710
+ };
711
+ }
712
+ function tableView(node) {
713
+ const dom = document.createElement("div");
714
+ dom.className = "md-table";
715
+ const table = document.createElement("table");
716
+ const body = document.createElement("tbody");
717
+ table.appendChild(body);
718
+ dom.appendChild(table);
719
+ return { dom, contentDOM: body };
720
+ }
721
+ var ImageViews = class {
722
+ constructor(resolve) {
723
+ this.resolve = resolve;
724
+ }
725
+ resolve;
726
+ views = /* @__PURE__ */ new Set();
727
+ /** Paint every picture again, when what the resolver can answer has changed. */
728
+ refresh() {
729
+ for (const view of this.views) view.paint();
730
+ }
731
+ view = (node) => {
732
+ const dom = document.createElement("span");
733
+ dom.className = "crewlet-rich-editor__image";
734
+ const entry = {
735
+ node,
736
+ paint: () => {
737
+ const src = entry.node.attrs.src;
738
+ const alt = entry.node.attrs.alt;
739
+ const resolver = this.resolve();
740
+ const shown = resolver ? resolver(src) : safeHref(src);
741
+ dom.replaceChildren();
742
+ if (shown) {
743
+ const img = document.createElement("img");
744
+ img.alt = alt;
745
+ img.src = shown;
746
+ img.draggable = false;
747
+ dom.appendChild(img);
748
+ } else {
749
+ const text = document.createElement("span");
750
+ text.className = "crewlet-rich-editor__image-alt";
751
+ text.textContent = alt || src;
752
+ dom.appendChild(text);
753
+ }
754
+ }
755
+ };
756
+ entry.paint();
757
+ this.views.add(entry);
758
+ return {
759
+ dom,
760
+ update: (next) => {
761
+ if (next.type !== entry.node.type) return false;
762
+ const same = next.attrs.src === entry.node.attrs.src && next.attrs.alt === entry.node.attrs.alt;
763
+ entry.node = next;
764
+ if (!same) entry.paint();
765
+ return true;
766
+ },
767
+ destroy: () => {
768
+ this.views.delete(entry);
769
+ }
770
+ };
771
+ };
772
+ };
773
+ var SKIP = /* @__PURE__ */ new Set([
774
+ "script",
775
+ "style",
776
+ "template",
777
+ "iframe",
778
+ "object",
779
+ "embed",
780
+ "noscript",
781
+ "head",
782
+ "title",
783
+ "svg",
784
+ "math",
785
+ "canvas",
786
+ "video",
787
+ "audio",
788
+ "select",
789
+ "textarea",
790
+ "button",
791
+ "xml"
792
+ ]);
793
+ var VOID = /* @__PURE__ */ new Set(["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr", "param"]);
794
+ var KEEP = /* @__PURE__ */ new Set([
795
+ "p",
796
+ "div",
797
+ "br",
798
+ "h1",
799
+ "h2",
800
+ "h3",
801
+ "h4",
802
+ "h5",
803
+ "h6",
804
+ "ul",
805
+ "ol",
806
+ "li",
807
+ "blockquote",
808
+ "pre",
809
+ "code",
810
+ "table",
811
+ "thead",
812
+ "tbody",
813
+ "tfoot",
814
+ "tr",
815
+ "th",
816
+ "td",
817
+ "a",
818
+ "img",
819
+ "strong",
820
+ "b",
821
+ "em",
822
+ "i",
823
+ "s",
824
+ "del",
825
+ "strike",
826
+ "hr",
827
+ "input"
828
+ ]);
829
+ var BLOCK = /* @__PURE__ */ new Set([
830
+ "p",
831
+ "div",
832
+ "h1",
833
+ "h2",
834
+ "h3",
835
+ "h4",
836
+ "h5",
837
+ "h6",
838
+ "ul",
839
+ "ol",
840
+ "li",
841
+ "blockquote",
842
+ "pre",
843
+ "table",
844
+ "hr",
845
+ "section",
846
+ "article",
847
+ "header",
848
+ "footer",
849
+ "main",
850
+ "aside",
851
+ "nav",
852
+ "figure"
853
+ ]);
854
+ var ENTITIES = {
855
+ amp: "&",
856
+ lt: "<",
857
+ gt: ">",
858
+ quot: '"',
859
+ apos: "'",
860
+ nbsp: "\xA0",
861
+ ndash: "\u2013",
862
+ mdash: "\u2014",
863
+ hellip: "\u2026",
864
+ lsquo: "\u2018",
865
+ rsquo: "\u2019",
866
+ ldquo: "\u201C",
867
+ rdquo: "\u201D",
868
+ bull: "\u2022",
869
+ middot: "\xB7",
870
+ copy: "\xA9",
871
+ reg: "\xAE",
872
+ trade: "\u2122",
873
+ times: "\xD7",
874
+ laquo: "\xAB",
875
+ raquo: "\xBB"
876
+ };
877
+ function decode(text) {
878
+ return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (whole, name) => {
879
+ if (name[0] === "#") {
880
+ const code = name[1] === "x" || name[1] === "X" ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
881
+ return Number.isFinite(code) && code > 0 && code <= 1114111 ? String.fromCodePoint(code) : whole;
882
+ }
883
+ return ENTITIES[name.toLowerCase()] ?? whole;
884
+ });
885
+ }
886
+ var TAG = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)((?:\s+[^\s"'>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?)*)\s*\/?>/y;
887
+ var ATTR = /([^\s"'>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
888
+ function tokenize(html) {
889
+ const out = [];
890
+ let i = 0;
891
+ let text = "";
892
+ const flush = () => {
893
+ if (text) out.push({ kind: "text", text: decode(text) });
894
+ text = "";
895
+ };
896
+ while (i < html.length) {
897
+ const c = html[i];
898
+ if (c !== "<") {
899
+ text += c;
900
+ i++;
901
+ continue;
902
+ }
903
+ if (html.startsWith("<!--", i)) {
904
+ const end = html.indexOf("-->", i + 4);
905
+ i = end < 0 ? html.length : end + 3;
906
+ continue;
907
+ }
908
+ if (html[i + 1] === "!" || html[i + 1] === "?") {
909
+ const end = html.indexOf(">", i);
910
+ i = end < 0 ? html.length : end + 1;
911
+ continue;
912
+ }
913
+ TAG.lastIndex = i;
914
+ const m = TAG.exec(html);
915
+ if (!m) {
916
+ text += c;
917
+ i++;
918
+ continue;
919
+ }
920
+ flush();
921
+ const name = (m[2] ?? "").toLowerCase();
922
+ if (m[1]) {
923
+ out.push({ kind: "close", name });
924
+ } else {
925
+ const attrs = {};
926
+ for (const a of (m[3] ?? "").matchAll(ATTR)) {
927
+ attrs[(a[1] ?? "").toLowerCase()] = decode(a[2] ?? a[3] ?? a[4] ?? "");
928
+ }
929
+ out.push({ kind: "open", name, attrs });
930
+ if (SKIP.has(name)) {
931
+ const end = html.toLowerCase().indexOf(`</${name}`, TAG.lastIndex);
932
+ i = end < 0 ? html.length : end;
933
+ continue;
934
+ }
935
+ }
936
+ i = TAG.lastIndex;
937
+ }
938
+ flush();
939
+ return out;
940
+ }
941
+ function styleOf(style) {
942
+ const read2 = (prop) => new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([^;]+)`, "i").exec(style)?.[1]?.trim().toLowerCase();
943
+ const weight = read2("font-weight");
944
+ const out = {};
945
+ if (weight) {
946
+ if (weight === "bold" || weight === "bolder" || Number(weight) >= 600) out.bold = true;
947
+ if (weight === "normal" || Number(weight) <= 400) out.normal = true;
948
+ }
949
+ if (read2("font-style") === "italic") out.italic = true;
950
+ if (/line-through/.test(read2("text-decoration") ?? read2("text-decoration-line") ?? "")) out.strike = true;
951
+ const align = read2("text-align");
952
+ if (align === "center" || align === "right") out.align = align;
953
+ return out;
954
+ }
955
+ function htmlTree(html, doc = document.implementation.createHTMLDocument("")) {
956
+ const root = doc.createElement("div");
957
+ const stack = [{ name: "#root", el: root }];
958
+ const top = () => stack[stack.length - 1];
959
+ const closeTo = (names, stop = []) => {
960
+ for (let k = stack.length - 1; k > 0; k--) {
961
+ const n = stack[k].name;
962
+ if (stop.includes(n)) return;
963
+ if (names.includes(n)) {
964
+ stack.length = k;
965
+ return;
966
+ }
967
+ }
968
+ };
969
+ for (const token of tokenize(html)) {
970
+ if (token.kind === "text") {
971
+ top().el.appendChild(doc.createTextNode(token.text ?? ""));
972
+ continue;
973
+ }
974
+ const name = token.name ?? "";
975
+ if (token.kind === "close") {
976
+ if (SKIP.has(name)) continue;
977
+ for (let k = stack.length - 1; k > 0; k--) {
978
+ if (stack[k].name === name) {
979
+ stack.length = k;
980
+ break;
981
+ }
982
+ }
983
+ continue;
984
+ }
985
+ if (SKIP.has(name)) continue;
986
+ if (BLOCK.has(name)) closeTo(["p"], ["li", "td", "th", "blockquote"]);
987
+ if (name === "li") closeTo(["li"], ["ul", "ol"]);
988
+ if (name === "tr") closeTo(["tr"], ["table"]);
989
+ if (name === "td" || name === "th") closeTo(["td", "th"], ["tr", "table"]);
990
+ const attrs = token.attrs ?? {};
991
+ const style = styleOf(attrs.style ?? "");
992
+ let el = null;
993
+ if (KEEP.has(name)) {
994
+ el = element(doc, name, attrs, style);
995
+ } else if (style.bold || style.italic || style.strike) {
996
+ el = doc.createElement(style.bold ? "strong" : style.italic ? "em" : "s");
997
+ } else if (BLOCK.has(name)) {
998
+ el = doc.createElement("div");
999
+ }
1000
+ if (el === null) {
1001
+ if (VOID.has(name)) continue;
1002
+ el = doc.createElement("span");
1003
+ }
1004
+ top().el.appendChild(el);
1005
+ if (!VOID.has(name)) stack.push({ name, el });
1006
+ }
1007
+ return root;
1008
+ }
1009
+ function element(doc, name, attrs, style) {
1010
+ if (name === "a") {
1011
+ const href = safeHref(attrs.href ?? "");
1012
+ if (!href) return doc.createElement("span");
1013
+ const a = doc.createElement("a");
1014
+ a.setAttribute("href", href);
1015
+ if (attrs.title) a.setAttribute("title", attrs.title);
1016
+ return a;
1017
+ }
1018
+ if (name === "img") {
1019
+ const own = attrs["data-src"];
1020
+ const src = own ?? attrs.src ?? "";
1021
+ if (!src) return null;
1022
+ if (own === void 0 ? !safeHref(src) : /^[a-z][a-z0-9+.-]*:/i.test(src) && !safeHref(src)) return null;
1023
+ const img = doc.createElement("img");
1024
+ img.setAttribute("data-src", src);
1025
+ img.setAttribute("alt", attrs.alt ?? "");
1026
+ if (attrs.title) img.setAttribute("title", attrs.title);
1027
+ return img;
1028
+ }
1029
+ if (name === "input") {
1030
+ if ((attrs.type ?? "").toLowerCase() !== "checkbox") return null;
1031
+ const box = doc.createElement("input");
1032
+ box.setAttribute("type", "checkbox");
1033
+ if ("checked" in attrs) box.setAttribute("checked", "");
1034
+ return box;
1035
+ }
1036
+ if (name === "b" && style.normal) return doc.createElement("span");
1037
+ const el = doc.createElement(name);
1038
+ if (name === "ol" && attrs.start) el.setAttribute("start", attrs.start.replace(/\D/g, "") || "1");
1039
+ if ((name === "pre" || name === "code") && attrs.class) {
1040
+ const lang = /(?:^|\s)(?:language|lang)-([A-Za-z0-9_+-]+)/.exec(attrs.class)?.[1];
1041
+ if (lang) el.setAttribute("class", `language-${lang}`);
1042
+ }
1043
+ if (name === "pre" && attrs["data-lang"] && /^[A-Za-z0-9_+-]+$/.test(attrs["data-lang"])) {
1044
+ el.setAttribute("data-lang", attrs["data-lang"]);
1045
+ }
1046
+ if (/^h[1-6]$/.test(name) && /^[1-6]$/.test(attrs["data-level"] ?? "")) el.setAttribute("data-level", attrs["data-level"]);
1047
+ if (name === "div" && attrs["data-callout"] !== void 0) el.setAttribute("data-callout", attrs["data-callout"]);
1048
+ if (name === "div" && attrs["data-callout-body"] !== void 0) el.setAttribute("data-callout-body", "");
1049
+ if ((name === "td" || name === "th") && (style.align || attrs.align || attrs["data-align"])) {
1050
+ const align = style.align ?? (attrs["data-align"] ?? attrs.align)?.toLowerCase();
1051
+ if (align === "center" || align === "right") el.setAttribute("data-align", align);
1052
+ }
1053
+ return el;
1054
+ }
1055
+ function htmlSlice(html, $context) {
1056
+ return DOMParser.fromSchema(schema).parseSlice(htmlTree(html), { context: $context });
1057
+ }
1058
+ function looksLikeMarkdown(text) {
1059
+ return /^(#{1,6}\s|[-*+]\s|\d{1,9}[.)]\s|>|```|~~~|\|.*\|)/m.test(text) || /\*\*[^*\n]+\*\*|__[^_\n]+__|\[[^\]\n]+\]\([^)\s]+\)|`[^`\n]+`|~~[^~\n]+~~/.test(text);
1060
+ }
1061
+ function markdownSlice(text) {
1062
+ const doc = readMarkdown(text).doc;
1063
+ return Slice.maxOpen(doc.content);
1064
+ }
1065
+ function textSlice(text) {
1066
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
1067
+ const paragraphs = lines.map((line) => schema.node("paragraph", null, line ? schema.text(line) : void 0));
1068
+ return Slice.maxOpen(Fragment$1.from(paragraphs));
1069
+ }
1070
+ function richHtml(html, types) {
1071
+ if (!html) return false;
1072
+ if (types.includes("vscode-editor-data")) return false;
1073
+ return /<(p|h[1-6]|ul|ol|li|table|blockquote|pre|strong|b|em|i|a|img|code|br)[\s>]/i.test(html);
1074
+ }
1075
+
1076
+ // src/RichEditor/plugins.ts
1077
+ var slashKey = new PluginKey("crewlet-slash");
1078
+ var CLOSED = { open: false, from: 0, query: "" };
1079
+ function slashPlugin(keys) {
1080
+ return new Plugin({
1081
+ key: slashKey,
1082
+ state: {
1083
+ init: () => CLOSED,
1084
+ apply(tr, prev, _old, state) {
1085
+ const meta = tr.getMeta(slashKey);
1086
+ if (meta) return meta.open ? read(state, meta.from ?? 0) : CLOSED;
1087
+ if (!prev.open) return prev;
1088
+ return read(state, tr.mapping.map(prev.from));
1089
+ }
1090
+ },
1091
+ props: {
1092
+ handleTextInput(view, from, to, text) {
1093
+ if (text !== "/" || from !== to) return false;
1094
+ const $from = view.state.doc.resolve(from);
1095
+ if (!$from.parent.isTextblock || $from.parent.type.spec.code) return false;
1096
+ const before = $from.parent.textBetween(0, $from.parentOffset, void 0, "\uFFFC");
1097
+ if (before !== "" && !/\s$/.test(before)) return false;
1098
+ view.dispatch(view.state.tr.insertText("/", from, to).setMeta(slashKey, { open: true, from }));
1099
+ return true;
1100
+ },
1101
+ handleKeyDown(_view, event) {
1102
+ return keys.current?.(event) ?? false;
1103
+ },
1104
+ decorations(state) {
1105
+ const slash = slashKey.getState(state);
1106
+ if (!slash?.open) return null;
1107
+ const to = slash.from + 1 + slash.query.length;
1108
+ return DecorationSet.create(state.doc, [Decoration.inline(slash.from, to, { class: "crewlet-rich-editor__slash" })]);
1109
+ }
1110
+ }
1111
+ });
1112
+ }
1113
+ function read(state, from) {
1114
+ const { selection } = state;
1115
+ if (!(selection instanceof TextSelection) || !selection.empty) return CLOSED;
1116
+ const cursor = selection.from;
1117
+ if (cursor <= from || from < 0 || from >= state.doc.content.size) return CLOSED;
1118
+ const $from = state.doc.resolve(from);
1119
+ const $cursor = selection.$from;
1120
+ if ($from.parent !== $cursor.parent) return CLOSED;
1121
+ const typed = state.doc.textBetween(from, cursor, "\n", "\uFFFC");
1122
+ if (!typed.startsWith("/") || /\s/.test(typed) || typed.length > 32) return CLOSED;
1123
+ return { open: true, from, query: typed.slice(1) };
1124
+ }
1125
+ var focusKey = new PluginKey("crewlet-focus");
1126
+ var focusPlugin = new Plugin({
1127
+ key: focusKey,
1128
+ state: {
1129
+ init: () => false,
1130
+ apply: (tr, prev) => tr.getMeta(focusKey) ?? prev
1131
+ },
1132
+ props: {
1133
+ handleDOMEvents: {
1134
+ focus(view) {
1135
+ if (!focusKey.getState(view.state)) view.dispatch(view.state.tr.setMeta(focusKey, true));
1136
+ return false;
1137
+ },
1138
+ blur(view) {
1139
+ if (focusKey.getState(view.state)) view.dispatch(view.state.tr.setMeta(focusKey, false));
1140
+ return false;
1141
+ }
1142
+ }
1143
+ }
1144
+ });
1145
+ function placeholderPlugin(text) {
1146
+ return new Plugin({
1147
+ props: {
1148
+ decorations(state) {
1149
+ const doc = state.doc;
1150
+ const words = text();
1151
+ if (doc.childCount === 1 && doc.firstChild.isTextblock && doc.firstChild.content.size === 0) {
1152
+ return DecorationSet.create(doc, [
1153
+ Decoration.node(0, doc.firstChild.nodeSize, { class: "is-empty", "data-placeholder": words.empty })
1154
+ ]);
1155
+ }
1156
+ const { $from, empty } = state.selection;
1157
+ if (!empty || !focusKey.getState(state) || slashKey.getState(state)?.open) return null;
1158
+ const parent = $from.parent;
1159
+ if (parent.type !== schema.nodes.paragraph || parent.content.size > 0) return null;
1160
+ const pos = $from.before();
1161
+ return DecorationSet.create(doc, [Decoration.node(pos, pos + parent.nodeSize, { class: "is-empty", "data-placeholder": words.line })]);
1162
+ }
1163
+ }
1164
+ });
1165
+ }
1166
+ function sliceMarkdown(slice) {
1167
+ const content = slice.content;
1168
+ const first = content.firstChild;
1169
+ const blocks = first && first.isInline ? Fragment$1.from(schema.node("paragraph", null, content)) : content;
1170
+ const doc = schema.topNodeType.createAndFill(null, blocks);
1171
+ return doc ? writeMarkdown(doc) : "";
1172
+ }
1173
+ var lastCopy = null;
1174
+ function remember(view) {
1175
+ const slice = view.state.selection.content();
1176
+ lastCopy = { text: sliceMarkdown(slice), slice };
1177
+ }
1178
+ function insert(view, slice, text) {
1179
+ const { state } = view;
1180
+ const href = text.trim();
1181
+ if (!state.selection.empty && !/\s/.test(href) && safeHref(href) && slice.content.childCount <= 1) {
1182
+ const mark = schema.marks.link.create({ href: safeHref(href) });
1183
+ view.dispatch(state.tr.addMark(state.selection.from, state.selection.to, mark).scrollIntoView());
1184
+ return;
1185
+ }
1186
+ view.dispatch(state.tr.replaceSelection(slice).scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
1187
+ }
1188
+ function sliceOf(view, data, at, plain) {
1189
+ const text = data.getData("text/plain");
1190
+ const html = data.getData("text/html");
1191
+ const $at = view.state.doc.resolve(at);
1192
+ if ($at.parent.type.spec.code) {
1193
+ return text ? { slice: new Slice(Fragment$1.from(schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0), text } : null;
1194
+ }
1195
+ if (lastCopy && text && text === lastCopy.text && !plain) return { slice: lastCopy.slice, text };
1196
+ if (!plain && richHtml(html, [...data.types])) return { slice: htmlSlice(html, $at), text };
1197
+ if (!text) return null;
1198
+ if (!plain && looksLikeMarkdown(text)) return { slice: markdownSlice(text), text };
1199
+ return { slice: textSlice(text), text };
1200
+ }
1201
+ function clipboardPlugin(shift) {
1202
+ return new Plugin({
1203
+ props: {
1204
+ clipboardTextSerializer: (slice) => sliceMarkdown(slice),
1205
+ handleDOMEvents: {
1206
+ keydown(_view, event) {
1207
+ shift.current = event.shiftKey;
1208
+ return false;
1209
+ },
1210
+ keyup(_view, event) {
1211
+ shift.current = event.shiftKey;
1212
+ return false;
1213
+ },
1214
+ copy(view) {
1215
+ remember(view);
1216
+ return false;
1217
+ },
1218
+ cut(view) {
1219
+ remember(view);
1220
+ return false;
1221
+ },
1222
+ paste(view, event) {
1223
+ const data = event.clipboardData;
1224
+ event.preventDefault();
1225
+ if (!data || !view.editable) return true;
1226
+ const found = sliceOf(view, data, view.state.selection.from, shift.current);
1227
+ if (found) insert(view, found.slice, found.text);
1228
+ return true;
1229
+ },
1230
+ drop(view, event) {
1231
+ if (view.dragging) return false;
1232
+ event.preventDefault();
1233
+ const data = event.dataTransfer;
1234
+ if (!data || !view.editable) return true;
1235
+ const at = view.posAtCoords({ left: event.clientX, top: event.clientY });
1236
+ if (!at) return true;
1237
+ const found = sliceOf(view, data, at.pos, false);
1238
+ if (!found) return true;
1239
+ const tr = view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(at.pos)));
1240
+ view.dispatch(tr);
1241
+ insert(view, found.slice, found.text);
1242
+ return true;
1243
+ }
1244
+ }
1245
+ }
1246
+ });
1247
+ }
1248
+ var N3 = schema.nodes;
1249
+ var M3 = schema.marks;
1250
+ var BLOCKS = [
1251
+ { key: "paragraph", icon: /* @__PURE__ */ jsx(TypeGlyph, {}), keywords: ["text", "paragraph", "plain"], run: setParagraph },
1252
+ { key: "heading1", icon: /* @__PURE__ */ jsx(Heading1Glyph, {}), hint: "#", keywords: ["h1", "title", "heading"], run: toggleHeading(1) },
1253
+ { key: "heading2", icon: /* @__PURE__ */ jsx(Heading2Glyph, {}), hint: "##", keywords: ["h2", "subtitle", "heading"], run: toggleHeading(2) },
1254
+ { key: "heading3", icon: /* @__PURE__ */ jsx(Heading3Glyph, {}), hint: "###", keywords: ["h3", "heading"], run: toggleHeading(3) },
1255
+ { key: "bullet", icon: /* @__PURE__ */ jsx(ListGlyph, {}), hint: "-", keywords: ["bullet", "unordered", "list"], run: setList("bullet") },
1256
+ { key: "ordered", icon: /* @__PURE__ */ jsx(ListOrderedGlyph, {}), hint: "1.", keywords: ["numbered", "ordered", "list"], run: setList("ordered") },
1257
+ { key: "task", icon: /* @__PURE__ */ jsx(ListChecksGlyph, {}), hint: "[ ]", keywords: ["todo", "task", "checkbox", "checklist"], run: setList("task") },
1258
+ { key: "quote", icon: /* @__PURE__ */ jsx(TextQuoteGlyph, {}), hint: ">", keywords: ["quote", "blockquote", "citation"], run: wrapQuote },
1259
+ { key: "callout", icon: /* @__PURE__ */ jsx(InfoGlyph, {}), keywords: ["callout", "note", "tip", "warning", "alert", "admonition"], run: wrapCallout("note") },
1260
+ { key: "code", icon: /* @__PURE__ */ jsx(SquareCodeGlyph, {}), hint: "```", keywords: ["code", "snippet", "pre"], run: setCodeBlock() },
1261
+ { key: "table", icon: /* @__PURE__ */ jsx(TableGlyph, {}), keywords: ["table", "grid", "columns", "rows"], run: insertTable() },
1262
+ { key: "divider", icon: /* @__PURE__ */ jsx(MinusGlyph, {}), hint: "-".repeat(3), keywords: ["divider", "rule", "separator", "line", "hr"], run: insertRule },
1263
+ { key: "image", icon: /* @__PURE__ */ jsx(ImageGlyph, {}), keywords: ["image", "picture", "photo", "figure"] }
1264
+ ];
1265
+ function matches(item, query) {
1266
+ const q = query.toLowerCase();
1267
+ if (!q) return 1;
1268
+ const label = item.label.toLowerCase();
1269
+ if (label.startsWith(q)) return 3;
1270
+ if (label.split(/\s+/).some((word) => word.startsWith(q))) return 2;
1271
+ if (item.keywords.some((word) => word.startsWith(q))) return 1;
1272
+ return 0;
1273
+ }
1274
+ function useViewportTick(active) {
1275
+ const [, setTick] = useState(0);
1276
+ useEffect(() => {
1277
+ if (!active) return;
1278
+ let frame = 0;
1279
+ const tick = () => {
1280
+ cancelAnimationFrame(frame);
1281
+ frame = requestAnimationFrame(() => setTick((n) => n + 1));
1282
+ };
1283
+ window.addEventListener("scroll", tick, true);
1284
+ window.addEventListener("resize", tick);
1285
+ return () => {
1286
+ cancelAnimationFrame(frame);
1287
+ window.removeEventListener("scroll", tick, true);
1288
+ window.removeEventListener("resize", tick);
1289
+ };
1290
+ }, [active]);
1291
+ }
1292
+ function rectOf(view, from, to = from) {
1293
+ try {
1294
+ const a = view.coordsAtPos(from);
1295
+ const b = view.coordsAtPos(to, -1);
1296
+ const left = Math.min(a.left, b.left);
1297
+ const top = Math.min(a.top, b.top);
1298
+ return { x: left, y: top, width: Math.max(1, Math.max(a.right, b.right) - left), height: Math.max(a.bottom, b.bottom) - top };
1299
+ } catch {
1300
+ return null;
1301
+ }
1302
+ }
1303
+ function coarse() {
1304
+ return typeof window.matchMedia === "function" && window.matchMedia("(pointer: coarse)").matches;
1305
+ }
1306
+ function Floating({
1307
+ anchor,
1308
+ prefer = "above",
1309
+ popup,
1310
+ center = false,
1311
+ className,
1312
+ children,
1313
+ ...rest
1314
+ }) {
1315
+ const layer = usePopupLayer({ open: popup !== void 0, onDismiss: () => popup?.onDismiss() });
1316
+ const node = useRef(null);
1317
+ const [place, setPlace] = useState(null);
1318
+ const { x, y, width, height } = anchor;
1319
+ useLayoutEffect(() => {
1320
+ const el = node.current;
1321
+ if (!el) return;
1322
+ const size = { width: el.offsetWidth, height: el.offsetHeight };
1323
+ const target = center ? { x: x + width / 2 - size.width / 2, y, width: size.width, height } : { x, y, width, height };
1324
+ const at = placePopup(target, size, viewportBounds(), 8, prefer);
1325
+ setPlace((prev) => prev && prev.left === at.left && prev.top === at.top ? prev : { left: at.left, top: at.top });
1326
+ }, [x, y, width, height, center, prefer]);
1327
+ return /* @__PURE__ */ jsx(
1328
+ "div",
1329
+ {
1330
+ ...rest,
1331
+ ref: (el) => {
1332
+ node.current = el;
1333
+ layer.panelRef(el);
1334
+ },
1335
+ className,
1336
+ style: {
1337
+ left: place?.left ?? 0,
1338
+ top: place?.top ?? 0,
1339
+ visibility: place ? "visible" : "hidden",
1340
+ ...popup ? { zIndex: layer.zIndex } : {}
1341
+ },
1342
+ children
1343
+ }
1344
+ );
1345
+ }
1346
+ var Editor = forwardRef(function Editor2(props, ref) {
1347
+ const {
1348
+ value,
1349
+ label,
1350
+ disabled = false,
1351
+ imageVersion,
1352
+ commands,
1353
+ size = "md",
1354
+ className
1355
+ } = props;
1356
+ const text = useMemo(() => richEditorLabels(props.labels), [props.labels]);
1357
+ const latest = useRef(props);
1358
+ latest.current = props;
1359
+ const labelsRef = useRef(text);
1360
+ labelsRef.current = text;
1361
+ const mount = useRef(null);
1362
+ const viewRef = useRef(null);
1363
+ const source = useRef(null);
1364
+ const emitted = useRef(value);
1365
+ const slashKeys = useRef(null);
1366
+ const shift = useRef(false);
1367
+ const toolbarRef = useRef(null);
1368
+ const [state, setState] = useState(null);
1369
+ const [panel, setPanel] = useState(null);
1370
+ const [active, setActive] = useState(0);
1371
+ const [pointerDown, setPointerDown] = useState(false);
1372
+ const [barFocus, setBarFocus] = useState(false);
1373
+ const descriptionId = useId();
1374
+ const listboxId = useId();
1375
+ const slots = useMemo(() => new SlotStore(), []);
1376
+ const images = useMemo(() => new ImageViews(() => latest.current.resolveImage), []);
1377
+ const openSlots = useSyncExternalStore(slots.subscribe, slots.get, slots.get);
1378
+ const hooks = useRef({
1379
+ submit: () => {
1380
+ latest.current.onSubmit?.();
1381
+ return Boolean(latest.current.onSubmit);
1382
+ },
1383
+ link: () => false,
1384
+ toolbar: () => false
1385
+ });
1386
+ const plugins = useMemo(
1387
+ () => [
1388
+ slashPlugin(slashKeys),
1389
+ buildInputRules(),
1390
+ ...buildKeymap({
1391
+ submit: () => hooks.current.submit(),
1392
+ link: () => hooks.current.link(),
1393
+ toolbar: () => hooks.current.toolbar()
1394
+ }),
1395
+ history(),
1396
+ gapCursor(),
1397
+ focusPlugin,
1398
+ placeholderPlugin(() => ({
1399
+ empty: latest.current.placeholder ?? labelsRef.current.placeholder,
1400
+ line: labelsRef.current.emptyLine
1401
+ })),
1402
+ clipboardPlugin(shift)
1403
+ ],
1404
+ []
1405
+ );
1406
+ const stateFor = useCallback(
1407
+ (markdown) => {
1408
+ const loaded = readMarkdown(markdown);
1409
+ source.current = loaded.source;
1410
+ return EditorState.create({ doc: loaded.doc, plugins });
1411
+ },
1412
+ [plugins]
1413
+ );
1414
+ useLayoutEffect(() => {
1415
+ const host = mount.current;
1416
+ if (!host) return;
1417
+ const initial = stateFor(latest.current.value);
1418
+ emitted.current = latest.current.value;
1419
+ const view2 = new EditorView(
1420
+ { mount: host },
1421
+ {
1422
+ state: initial,
1423
+ nodeViews: {
1424
+ code_block: codeBlockView(slots),
1425
+ callout: calloutView(slots),
1426
+ list_item: listItemView(() => ({ done: labelsRef.current.taskDone, notDone: labelsRef.current.taskNotDone })),
1427
+ table: tableView,
1428
+ image: images.view
1429
+ },
1430
+ handleDOMEvents: {
1431
+ // The pointer, so the toolbar waits for a drag-selection to finish.
1432
+ mousedown: () => {
1433
+ setPointerDown(true);
1434
+ return false;
1435
+ }
1436
+ },
1437
+ dispatchTransaction(tr) {
1438
+ const next = view2.state.apply(tr);
1439
+ view2.updateState(next);
1440
+ setState(next);
1441
+ if (tr.docChanged) {
1442
+ const markdown = writeMarkdown(next.doc, source.current ?? void 0);
1443
+ if (markdown !== emitted.current) {
1444
+ emitted.current = markdown;
1445
+ latest.current.onChange(markdown);
1446
+ }
1447
+ }
1448
+ }
1449
+ }
1450
+ );
1451
+ viewRef.current = view2;
1452
+ setState(view2.state);
1453
+ if (latest.current.autoFocus) view2.focus();
1454
+ return () => {
1455
+ view2.destroy();
1456
+ viewRef.current = null;
1457
+ };
1458
+ }, [images, slots, stateFor]);
1459
+ useEffect(() => {
1460
+ const view2 = viewRef.current;
1461
+ if (!view2 || value === emitted.current) return;
1462
+ emitted.current = value;
1463
+ const next = stateFor(value);
1464
+ view2.updateState(next);
1465
+ setState(next);
1466
+ setPanel(null);
1467
+ }, [value, stateFor]);
1468
+ useEffect(() => {
1469
+ images.refresh();
1470
+ }, [images, imageVersion, props.resolveImage]);
1471
+ const slash = state ? slashKey.getState(state) : void 0;
1472
+ const slashOpen = Boolean(slash?.open) && !disabled && panel === null;
1473
+ useEffect(() => {
1474
+ const view2 = viewRef.current;
1475
+ if (!view2) return;
1476
+ view2.setProps({
1477
+ editable: () => !disabled,
1478
+ attributes: {
1479
+ role: "textbox",
1480
+ "aria-multiline": "true",
1481
+ "aria-label": label,
1482
+ "aria-describedby": descriptionId,
1483
+ ...disabled ? { "aria-readonly": "true" } : {},
1484
+ ...slashOpen ? { "aria-controls": listboxId, "aria-activedescendant": `${listboxId}-${active}` } : {},
1485
+ class: cx(
1486
+ "crewlet-rich-editor__content",
1487
+ "crewlet-document",
1488
+ `crewlet-document--${size}`,
1489
+ "crewlet-document--measure"
1490
+ ),
1491
+ spellcheck: "true"
1492
+ }
1493
+ });
1494
+ });
1495
+ useImperativeHandle(ref, () => ({ focus: () => viewRef.current?.focus(), view: () => viewRef.current }), []);
1496
+ useEffect(() => {
1497
+ if (!pointerDown) return;
1498
+ const up = () => setPointerDown(false);
1499
+ window.addEventListener("pointerup", up);
1500
+ return () => window.removeEventListener("pointerup", up);
1501
+ }, [pointerDown]);
1502
+ const focused = state ? Boolean(focusKey.getState(state)) : false;
1503
+ const view = viewRef.current;
1504
+ const run = (command) => {
1505
+ const v = viewRef.current;
1506
+ if (!v) return;
1507
+ command(v.state, v.dispatch, v);
1508
+ v.focus();
1509
+ };
1510
+ const items = useMemo(() => {
1511
+ const blocks = BLOCKS.map((b) => ({
1512
+ key: b.key,
1513
+ label: text.blocks[b.key].label,
1514
+ description: text.blocks[b.key].description,
1515
+ icon: b.icon,
1516
+ hint: b.hint,
1517
+ group: b.key === "image" || b.key === "table" || b.key === "divider" ? "insert" : "blocks",
1518
+ keywords: b.keywords,
1519
+ ...b.run ? { run: b.run } : { panel: "image" }
1520
+ }));
1521
+ const own = (commands ?? []).map((command) => ({
1522
+ key: `command:${command.key}`,
1523
+ label: command.label,
1524
+ description: command.description,
1525
+ icon: command.icon ?? /* @__PURE__ */ jsx(LinkGlyph, {}),
1526
+ group: "insert",
1527
+ keywords: (command.keywords ?? []).map((k) => k.toLowerCase()),
1528
+ panel: { command }
1529
+ }));
1530
+ return [...blocks, ...own];
1531
+ }, [commands, text]);
1532
+ const shown = useMemo(() => {
1533
+ if (!slashOpen || !state || !slash) return [];
1534
+ const without = state.apply(state.tr.delete(slash.from, state.selection.from));
1535
+ return items.map((item) => ({ item, score: matches(item, slash.query) })).filter(({ item, score }) => score > 0 && (!item.run || item.key === "paragraph" || item.run(without))).sort((a, b) => b.score - a.score || (a.item.group === b.item.group ? 0 : a.item.group === "blocks" ? -1 : 1)).map(({ item }) => item);
1536
+ }, [items, slash, slashOpen, state]);
1537
+ useEffect(() => setActive(0), [slash?.query, slashOpen]);
1538
+ const choose = (item) => {
1539
+ const v = viewRef.current;
1540
+ const s = v ? slashKey.getState(v.state) : void 0;
1541
+ if (!v || !s?.open) return;
1542
+ v.dispatch(v.state.tr.delete(s.from, v.state.selection.from).setMeta(slashKey, { open: false }));
1543
+ const at = v.state.selection.from;
1544
+ if (item.panel === "image") setPanel({ kind: "image", at, editing: null });
1545
+ else if (item.panel) setPanel({ kind: "command", command: item.panel.command, at });
1546
+ else if (item.run) item.run(v.state, v.dispatch, v);
1547
+ v.focus();
1548
+ };
1549
+ const closeSlash = () => {
1550
+ const v = viewRef.current;
1551
+ if (v && slashKey.getState(v.state)?.open) v.dispatch(v.state.tr.setMeta(slashKey, { open: false }));
1552
+ };
1553
+ slashKeys.current = (event) => {
1554
+ if (event.key === "Escape") {
1555
+ if (slashOpen) {
1556
+ closeSlash();
1557
+ return true;
1558
+ }
1559
+ if (panel) {
1560
+ setPanel(null);
1561
+ return true;
1562
+ }
1563
+ if (latest.current.onEscape) {
1564
+ event.preventDefault();
1565
+ latest.current.onEscape();
1566
+ return true;
1567
+ }
1568
+ return false;
1569
+ }
1570
+ if (!slashOpen) return false;
1571
+ const count = shown.length;
1572
+ if (event.key === "ArrowDown" || event.key === "n" && event.ctrlKey) {
1573
+ if (count) setActive((i) => (i + 1) % count);
1574
+ return true;
1575
+ }
1576
+ if (event.key === "ArrowUp" || event.key === "p" && event.ctrlKey) {
1577
+ if (count) setActive((i) => (i - 1 + count) % count);
1578
+ return true;
1579
+ }
1580
+ if (event.key === "Home" && count) {
1581
+ setActive(0);
1582
+ return true;
1583
+ }
1584
+ if (event.key === "End" && count) {
1585
+ setActive(count - 1);
1586
+ return true;
1587
+ }
1588
+ if (event.key === "Enter" || event.key === "Tab") {
1589
+ const item = shown[active];
1590
+ if (!item) {
1591
+ closeSlash();
1592
+ return event.key === "Tab";
1593
+ }
1594
+ choose(item);
1595
+ return true;
1596
+ }
1597
+ return false;
1598
+ };
1599
+ const openLink = () => {
1600
+ const v = viewRef.current;
1601
+ if (!v || disabled || inCode(v.state)) return false;
1602
+ const at = linkAt(v.state);
1603
+ const { from, to } = v.state.selection;
1604
+ if (at) setPanel({ kind: "link", from: at.from, to: at.to, href: at.mark.attrs.href, creating: false });
1605
+ else setPanel({ kind: "link", from, to, href: "", creating: from === to });
1606
+ return true;
1607
+ };
1608
+ hooks.current.link = openLink;
1609
+ hooks.current.toolbar = () => {
1610
+ const first = toolbarRef.current?.querySelector("button:not([disabled])");
1611
+ if (!first) return false;
1612
+ first.focus();
1613
+ return true;
1614
+ };
1615
+ const closePanel = () => {
1616
+ setPanel(null);
1617
+ viewRef.current?.focus();
1618
+ };
1619
+ const selection = state?.selection;
1620
+ const textSelected = Boolean(selection && selection instanceof TextSelection && !selection.empty);
1621
+ const showFormat = Boolean(state && view) && !disabled && !panel && !slashOpen && (focused || barFocus) && textSelected && !pointerDown && !inCode(state);
1622
+ const link = state && selection?.empty && focused && !panel && !slashOpen ? linkAt(state) : null;
1623
+ const table = state && (focused || barFocus) && !panel && !slashOpen && !disabled ? tableAt(state) : null;
1624
+ const imageSelected = state && selection instanceof NodeSelection && selection.node.type === N3.image && (focused || barFocus) && !panel && !disabled ? selection : null;
1625
+ useViewportTick(Boolean(slashOpen || showFormat || link || table || panel || imageSelected));
1626
+ const barScope = {
1627
+ onFocus: () => setBarFocus(true),
1628
+ onBlur: (event) => {
1629
+ if (!event.currentTarget.contains(event.relatedTarget)) setBarFocus(false);
1630
+ },
1631
+ onKeyDown: (event) => {
1632
+ if (event.key !== "Escape" || event.defaultPrevented) return;
1633
+ event.preventDefault();
1634
+ setBarFocus(false);
1635
+ viewRef.current?.focus();
1636
+ }
1637
+ };
1638
+ const formatBar = showFormat && view && state && selection ? /* @__PURE__ */ jsx(
1639
+ Floating,
1640
+ {
1641
+ anchor: rectOf(view, selection.from, selection.to) ?? { x: 0, y: 0, width: 0, height: 0 },
1642
+ prefer: coarse() ? "below" : "above",
1643
+ center: true,
1644
+ className: "crewlet-rich-editor__bar",
1645
+ children: /* @__PURE__ */ jsx("div", { ref: toolbarRef, ...barScope, children: /* @__PURE__ */ jsxs(Toolbar, { label: text.toolbar, children: [
1646
+ /* @__PURE__ */ jsx("span", { className: "crewlet-rich-editor__group", children: [1, 2, 3].map((level) => /* @__PURE__ */ jsx(
1647
+ BarButton,
1648
+ {
1649
+ label: text.heading(level),
1650
+ icon: level === 1 ? /* @__PURE__ */ jsx(Heading1Glyph, {}) : level === 2 ? /* @__PURE__ */ jsx(Heading2Glyph, {}) : /* @__PURE__ */ jsx(Heading3Glyph, {}),
1651
+ pressed: headingLevel(state) === level,
1652
+ onPress: () => run(toggleHeading(level))
1653
+ },
1654
+ level
1655
+ )) }),
1656
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-rich-editor__group", children: [
1657
+ /* @__PURE__ */ jsx(BarButton, { label: text.bold, icon: /* @__PURE__ */ jsx(BoldGlyph, {}), pressed: markActive(state, M3.strong), onPress: () => run(toggleStrong) }),
1658
+ /* @__PURE__ */ jsx(BarButton, { label: text.italic, icon: /* @__PURE__ */ jsx(ItalicGlyph, {}), pressed: markActive(state, M3.em), onPress: () => run(toggleEm) }),
1659
+ /* @__PURE__ */ jsx(
1660
+ BarButton,
1661
+ {
1662
+ label: text.strike,
1663
+ icon: /* @__PURE__ */ jsx(StrikethroughGlyph, {}),
1664
+ pressed: markActive(state, M3.strike),
1665
+ onPress: () => run(toggleStrike)
1666
+ }
1667
+ ),
1668
+ /* @__PURE__ */ jsx(BarButton, { label: text.code, icon: /* @__PURE__ */ jsx(CodeGlyph, {}), pressed: markActive(state, M3.code), onPress: () => run(toggleCode) })
1669
+ ] }),
1670
+ /* @__PURE__ */ jsx("span", { className: "crewlet-rich-editor__group", children: /* @__PURE__ */ jsx(BarButton, { label: text.link, icon: /* @__PURE__ */ jsx(LinkGlyph, {}), pressed: markActive(state, M3.link), onPress: () => openLink() }) })
1671
+ ] }) })
1672
+ }
1673
+ ) : null;
1674
+ const linkBubble = link && view && !showFormat ? /* @__PURE__ */ jsx(Floating, { anchor: rectOf(view, link.from, link.to) ?? { x: 0, y: 0, width: 0, height: 0 }, prefer: "below", className: "crewlet-rich-editor__bubble", children: /* @__PURE__ */ jsx(
1675
+ LinkBubble,
1676
+ {
1677
+ href: link.mark.attrs.href,
1678
+ text,
1679
+ onEdit: () => openLink(),
1680
+ onRemove: () => run(removeLink)
1681
+ }
1682
+ ) }) : null;
1683
+ const tableBar = table && view && !showFormat ? /* @__PURE__ */ jsx(
1684
+ Floating,
1685
+ {
1686
+ anchor: view.nodeDOM(table.pos)?.getBoundingClientRect() ?? { x: 0, y: 0, width: 0, height: 0 },
1687
+ prefer: "below",
1688
+ className: "crewlet-rich-editor__bar",
1689
+ children: /* @__PURE__ */ jsx("div", { ...barScope, children: /* @__PURE__ */ jsx(TableBar, { text, align: state ? columnAlign(state) : null, run }) })
1690
+ }
1691
+ ) : null;
1692
+ const imageBar = imageSelected && view ? /* @__PURE__ */ jsx(Floating, { anchor: rectOf(view, imageSelected.from, imageSelected.to) ?? { x: 0, y: 0, width: 0, height: 0 }, prefer: "below", className: "crewlet-rich-editor__bar", children: /* @__PURE__ */ jsx("div", { ...barScope, children: /* @__PURE__ */ jsxs(Toolbar, { label: text.imagePanel, children: [
1693
+ /* @__PURE__ */ jsx(
1694
+ BarButton,
1695
+ {
1696
+ label: text.imageEdit,
1697
+ icon: /* @__PURE__ */ jsx(PencilGlyph, {}),
1698
+ onPress: () => setPanel({
1699
+ kind: "image",
1700
+ at: imageSelected.from,
1701
+ editing: {
1702
+ pos: imageSelected.from,
1703
+ src: imageSelected.node.attrs.src,
1704
+ alt: imageSelected.node.attrs.alt
1705
+ }
1706
+ })
1707
+ }
1708
+ ),
1709
+ /* @__PURE__ */ jsx(
1710
+ BarButton,
1711
+ {
1712
+ label: text.imageRemove,
1713
+ icon: /* @__PURE__ */ jsx(TrashGlyph, {}),
1714
+ onPress: () => run((s, d) => d ? (d(s.tr.deleteSelection()), true) : true)
1715
+ }
1716
+ )
1717
+ ] }) }) }) : null;
1718
+ const slashMenu = slashOpen && view && slash ? /* @__PURE__ */ jsx(
1719
+ Floating,
1720
+ {
1721
+ anchor: rectOf(view, slash.from) ?? { x: 0, y: 0, width: 0, height: 0 },
1722
+ prefer: "below",
1723
+ popup: { onDismiss: closeSlash },
1724
+ className: "crewlet-rich-editor__menu",
1725
+ children: /* @__PURE__ */ jsx("div", { role: "listbox", id: listboxId, "aria-label": text.slashMenu, className: "crewlet-rich-editor__options", children: shown.length === 0 ? /* @__PURE__ */ jsxs("p", { className: "crewlet-rich-editor__empty", role: "presentation", children: [
1726
+ text.slashEmpty,
1727
+ " \u201C",
1728
+ slash.query,
1729
+ "\u201D"
1730
+ ] }) : shown.map((item, i) => {
1731
+ const group = i === 0 || shown[i - 1].group !== item.group ? item.group : null;
1732
+ return /* @__PURE__ */ jsx(
1733
+ SlashOption,
1734
+ {
1735
+ id: `${listboxId}-${i}`,
1736
+ item,
1737
+ heading: group === null ? null : group === "blocks" ? text.slashGroupBlocks : text.slashGroupInsert,
1738
+ selected: i === active,
1739
+ onHover: () => setActive(i),
1740
+ onChoose: () => choose(item)
1741
+ },
1742
+ item.key
1743
+ );
1744
+ }) })
1745
+ }
1746
+ ) : null;
1747
+ const panelAnchor = (at, to = at) => view ? rectOf(view, at, to) ?? { x: 0, y: 0, width: 0, height: 0 } : { x: 0, y: 0, width: 0, height: 0 };
1748
+ const panelView = panel && view ? /* @__PURE__ */ jsx(
1749
+ Floating,
1750
+ {
1751
+ anchor: panel.kind === "link" ? panelAnchor(panel.from, panel.to) : panelAnchor(panel.at),
1752
+ prefer: "below",
1753
+ popup: { onDismiss: closePanel },
1754
+ className: "crewlet-rich-editor__panel",
1755
+ role: "dialog",
1756
+ "aria-label": panel.kind === "link" ? text.linkPanel : panel.kind === "image" ? text.imagePanel : panel.command.label,
1757
+ children: panel.kind === "link" ? /* @__PURE__ */ jsx(
1758
+ LinkPanel,
1759
+ {
1760
+ text,
1761
+ href: panel.href,
1762
+ creating: panel.creating,
1763
+ existing: !panel.creating && panel.href !== "",
1764
+ onApply: (href, words) => {
1765
+ const v = viewRef.current;
1766
+ if (!v) return;
1767
+ v.dispatch(v.state.tr.setSelection(TextSelection.create(v.state.doc, panel.from, panel.to)));
1768
+ setLink(href, words)(v.state, v.dispatch);
1769
+ closePanel();
1770
+ },
1771
+ onRemove: () => {
1772
+ const v = viewRef.current;
1773
+ if (!v) return;
1774
+ v.dispatch(v.state.tr.removeMark(panel.from, panel.to, M3.link));
1775
+ closePanel();
1776
+ }
1777
+ }
1778
+ ) : panel.kind === "image" ? /* @__PURE__ */ jsx(
1779
+ ImagePanel,
1780
+ {
1781
+ text,
1782
+ initial: panel.editing,
1783
+ onApply: (src, alt) => {
1784
+ const v = viewRef.current;
1785
+ if (!v) return;
1786
+ if (panel.editing) {
1787
+ const node = v.state.doc.nodeAt(panel.editing.pos);
1788
+ if (node?.type === N3.image) v.dispatch(v.state.tr.setNodeMarkup(panel.editing.pos, void 0, { ...node.attrs, src, alt }));
1789
+ } else {
1790
+ v.dispatch(v.state.tr.setSelection(TextSelection.create(v.state.doc, Math.min(panel.at, v.state.doc.content.size))));
1791
+ insertImage(src, alt)(v.state, v.dispatch);
1792
+ }
1793
+ closePanel();
1794
+ }
1795
+ }
1796
+ ) : panel.command.panel({
1797
+ insertLink: (words, href) => {
1798
+ const v = viewRef.current;
1799
+ if (!v) return;
1800
+ v.dispatch(v.state.tr.setSelection(TextSelection.create(v.state.doc, Math.min(panel.at, v.state.doc.content.size))));
1801
+ setLink(href, words)(v.state, v.dispatch);
1802
+ closePanel();
1803
+ },
1804
+ close: closePanel
1805
+ })
1806
+ }
1807
+ ) : null;
1808
+ return /* @__PURE__ */ jsxs("div", { className: cx("crewlet-rich-editor", disabled && "crewlet-rich-editor--disabled", className), children: [
1809
+ /* @__PURE__ */ jsx("div", { ref: mount, className: "crewlet-rich-editor__mount" }),
1810
+ /* @__PURE__ */ jsx(VisuallyHidden, { id: descriptionId, children: text.description }),
1811
+ openSlots.map((slot) => createPortal(/* @__PURE__ */ jsx(SlotControls, { slot, text, run, disabled }), slot.element)),
1812
+ slashMenu,
1813
+ formatBar,
1814
+ linkBubble,
1815
+ tableBar,
1816
+ imageBar,
1817
+ panelView
1818
+ ] });
1819
+ });
1820
+ function BarButton({ label, icon, pressed, onPress }) {
1821
+ return /* @__PURE__ */ jsx(
1822
+ IconButton,
1823
+ {
1824
+ size: "sm",
1825
+ variant: "ghost",
1826
+ label,
1827
+ icon,
1828
+ pressed,
1829
+ onMouseDown: (event) => event.preventDefault(),
1830
+ onClick: onPress
1831
+ }
1832
+ );
1833
+ }
1834
+ function SlashOption({
1835
+ id,
1836
+ item,
1837
+ heading,
1838
+ selected,
1839
+ onHover,
1840
+ onChoose
1841
+ }) {
1842
+ const node = useRef(null);
1843
+ useEffect(() => {
1844
+ const option = node.current;
1845
+ const menu = option?.closest(".crewlet-rich-editor__menu");
1846
+ if (!selected || !option || !menu) return;
1847
+ const top = option.offsetTop;
1848
+ const bottom = top + option.offsetHeight;
1849
+ if (top < menu.scrollTop) menu.scrollTop = top;
1850
+ else if (bottom > menu.scrollTop + menu.clientHeight) menu.scrollTop = bottom - menu.clientHeight;
1851
+ }, [selected]);
1852
+ const pointer = {
1853
+ onMouseDown: (event) => event.preventDefault(),
1854
+ onMouseMove: onHover,
1855
+ onClick: onChoose
1856
+ };
1857
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1858
+ heading && /* @__PURE__ */ jsx("div", { className: "crewlet-rich-editor__group-heading", role: "presentation", children: heading }),
1859
+ /* @__PURE__ */ jsxs(
1860
+ "div",
1861
+ {
1862
+ ref: node,
1863
+ id,
1864
+ role: "option",
1865
+ "aria-selected": selected,
1866
+ className: cx("crewlet-rich-editor__option", selected && "is-active"),
1867
+ ...pointer,
1868
+ children: [
1869
+ /* @__PURE__ */ jsx("span", { className: "crewlet-rich-editor__option-icon", "aria-hidden": "true", children: item.icon }),
1870
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-rich-editor__option-text", children: [
1871
+ /* @__PURE__ */ jsx("span", { className: "crewlet-rich-editor__option-label", children: item.label }),
1872
+ item.description && /* @__PURE__ */ jsx("span", { className: "crewlet-rich-editor__option-description", children: item.description })
1873
+ ] }),
1874
+ item.hint && /* @__PURE__ */ jsx("span", { className: "crewlet-rich-editor__option-hint", "aria-hidden": "true", children: item.hint })
1875
+ ]
1876
+ }
1877
+ )
1878
+ ] });
1879
+ }
1880
+ function LinkBubble({ href, text, onEdit, onRemove }) {
1881
+ const safe = safeHref(href);
1882
+ const internal = safe?.startsWith("#/") ?? false;
1883
+ return /* @__PURE__ */ jsxs(Toolbar, { label: text.linkPanel, children: [
1884
+ safe ? /* @__PURE__ */ jsxs(
1885
+ "a",
1886
+ {
1887
+ className: "crewlet-rich-editor__bubble-link",
1888
+ href: safe,
1889
+ ...internal ? {} : { target: "_blank", rel: "noopener noreferrer" },
1890
+ title: text.linkOpen,
1891
+ onMouseDown: (event) => event.preventDefault(),
1892
+ children: [
1893
+ /* @__PURE__ */ jsx("span", { className: "crewlet-rich-editor__bubble-href", children: href }),
1894
+ !internal && /* @__PURE__ */ jsx(ExternalLinkGlyph, { size: "sm" })
1895
+ ]
1896
+ }
1897
+ ) : /* @__PURE__ */ jsx("span", { className: "crewlet-rich-editor__bubble-refused", children: text.linkRefused }),
1898
+ /* @__PURE__ */ jsx(BarButton, { label: text.linkEdit, icon: /* @__PURE__ */ jsx(PencilGlyph, {}), onPress: onEdit }),
1899
+ /* @__PURE__ */ jsx(BarButton, { label: text.linkRemove, icon: /* @__PURE__ */ jsx(UnlinkGlyph, {}), onPress: onRemove })
1900
+ ] });
1901
+ }
1902
+ function LinkPanel({
1903
+ text,
1904
+ href,
1905
+ creating,
1906
+ existing,
1907
+ onApply,
1908
+ onRemove
1909
+ }) {
1910
+ const [address, setAddress] = useState(href);
1911
+ const [words, setWords] = useState("");
1912
+ const [tried, setTried] = useState(false);
1913
+ const addressId = useId();
1914
+ const wordsId = useId();
1915
+ const field = useRef(null);
1916
+ useEffect(() => field.current?.focus(), []);
1917
+ const safe = safeHref(address);
1918
+ const submit = () => {
1919
+ setTried(true);
1920
+ if (safe) onApply(safe, words);
1921
+ };
1922
+ return /* @__PURE__ */ jsxs(
1923
+ "form",
1924
+ {
1925
+ className: "crewlet-rich-editor__form",
1926
+ onSubmit: (event) => {
1927
+ event.preventDefault();
1928
+ submit();
1929
+ },
1930
+ children: [
1931
+ /* @__PURE__ */ jsx(FormField, { label: text.linkAddress, htmlFor: addressId, error: tried && !safe ? text.linkInvalid : void 0, children: /* @__PURE__ */ jsx(
1932
+ Input,
1933
+ {
1934
+ id: addressId,
1935
+ ref: field,
1936
+ inputSize: "sm",
1937
+ value: address,
1938
+ error: tried && !safe,
1939
+ placeholder: "https://",
1940
+ onChange: (event) => setAddress(event.target.value)
1941
+ }
1942
+ ) }),
1943
+ creating && /* @__PURE__ */ jsx(FormField, { label: text.linkText, htmlFor: wordsId, optional: true, children: /* @__PURE__ */ jsx(Input, { id: wordsId, inputSize: "sm", value: words, onChange: (event) => setWords(event.target.value) }) }),
1944
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-rich-editor__form-actions", children: [
1945
+ existing && /* @__PURE__ */ jsx(Button, { type: "button", size: "small", variant: "ghost", onClick: onRemove, children: text.linkRemove }),
1946
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "small", variant: "primary", children: text.linkApply })
1947
+ ] })
1948
+ ]
1949
+ }
1950
+ );
1951
+ }
1952
+ function ImagePanel({
1953
+ text,
1954
+ initial,
1955
+ onApply
1956
+ }) {
1957
+ const [src, setSrc] = useState(initial?.src ?? "");
1958
+ const [alt, setAlt] = useState(initial?.alt ?? "");
1959
+ const srcId = useId();
1960
+ const altId = useId();
1961
+ const field = useRef(null);
1962
+ useEffect(() => field.current?.focus(), []);
1963
+ const address = src.trim();
1964
+ return /* @__PURE__ */ jsxs(
1965
+ "form",
1966
+ {
1967
+ className: "crewlet-rich-editor__form",
1968
+ onSubmit: (event) => {
1969
+ event.preventDefault();
1970
+ if (address) onApply(address.replace(/\s/g, "%20"), alt.trim());
1971
+ },
1972
+ children: [
1973
+ /* @__PURE__ */ jsx(FormField, { label: text.imageAddress, htmlFor: srcId, children: /* @__PURE__ */ jsx(Input, { id: srcId, ref: field, inputSize: "sm", value: src, onChange: (event) => setSrc(event.target.value) }) }),
1974
+ /* @__PURE__ */ jsx(FormField, { label: text.imageAlt, htmlFor: altId, helper: text.imageAltHelp, children: /* @__PURE__ */ jsx(Input, { id: altId, inputSize: "sm", value: alt, onChange: (event) => setAlt(event.target.value) }) }),
1975
+ /* @__PURE__ */ jsx("div", { className: "crewlet-rich-editor__form-actions", children: /* @__PURE__ */ jsx(Button, { type: "submit", size: "small", variant: "primary", disabledReason: address ? void 0 : text.imageAddress, children: initial ? text.linkApply : text.imageInsert }) })
1976
+ ]
1977
+ }
1978
+ );
1979
+ }
1980
+ function TableBar({ text, align, run }) {
1981
+ return /* @__PURE__ */ jsxs(Toolbar, { label: text.tableBar, children: [
1982
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-rich-editor__group", children: [
1983
+ /* @__PURE__ */ jsx(
1984
+ Menu,
1985
+ {
1986
+ label: text.tableRow,
1987
+ trigger: text.tableRow,
1988
+ trailingIcon: /* @__PURE__ */ jsx(ChevronDownGlyph, { size: "sm" }),
1989
+ items: [
1990
+ { key: "above", label: text.rowAbove, onSelect: () => run(addRowBefore) },
1991
+ { key: "below", label: text.rowBelow, onSelect: () => run(addRowAfter) },
1992
+ { kind: "separator", key: "sep" },
1993
+ { key: "delete", label: text.rowDelete, danger: true, onSelect: () => run(deleteRow) }
1994
+ ]
1995
+ }
1996
+ ),
1997
+ /* @__PURE__ */ jsx(
1998
+ Menu,
1999
+ {
2000
+ label: text.tableColumn,
2001
+ trigger: text.tableColumn,
2002
+ trailingIcon: /* @__PURE__ */ jsx(ChevronDownGlyph, { size: "sm" }),
2003
+ items: [
2004
+ { key: "left", label: text.columnLeft, onSelect: () => run(addColumnBefore) },
2005
+ { key: "right", label: text.columnRight, onSelect: () => run(addColumnAfter) },
2006
+ { kind: "separator", key: "sep" },
2007
+ { key: "delete", label: text.columnDelete, danger: true, onSelect: () => run(deleteColumn) }
2008
+ ]
2009
+ }
2010
+ )
2011
+ ] }),
2012
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-rich-editor__group", children: [
2013
+ /* @__PURE__ */ jsx(BarButton, { label: text.alignStart, icon: /* @__PURE__ */ jsx(TextAlignStartGlyph, {}), pressed: align === "left", onPress: () => run(alignColumn("left")) }),
2014
+ /* @__PURE__ */ jsx(BarButton, { label: text.alignCenter, icon: /* @__PURE__ */ jsx(TextAlignCenterGlyph, {}), pressed: align === "center", onPress: () => run(alignColumn("center")) }),
2015
+ /* @__PURE__ */ jsx(BarButton, { label: text.alignEnd, icon: /* @__PURE__ */ jsx(TextAlignEndGlyph, {}), pressed: align === "right", onPress: () => run(alignColumn("right")) })
2016
+ ] }),
2017
+ /* @__PURE__ */ jsx("span", { className: "crewlet-rich-editor__group", children: /* @__PURE__ */ jsx(
2018
+ IconButton,
2019
+ {
2020
+ size: "sm",
2021
+ variant: "ghost-danger",
2022
+ label: text.tableDelete,
2023
+ icon: /* @__PURE__ */ jsx(TrashGlyph, {}),
2024
+ onMouseDown: (event) => event.preventDefault(),
2025
+ onClick: () => run(deleteTable)
2026
+ }
2027
+ ) })
2028
+ ] });
2029
+ }
2030
+ function SlotControls({ slot, text, run, disabled }) {
2031
+ if (slot.kind === "code") {
2032
+ return /* @__PURE__ */ jsx(
2033
+ "input",
2034
+ {
2035
+ className: "crewlet-rich-editor__lang",
2036
+ "aria-label": text.codeLanguage,
2037
+ placeholder: text.codeLanguagePlaceholder,
2038
+ value: slot.node.attrs.lang,
2039
+ disabled,
2040
+ spellCheck: false,
2041
+ onChange: (event) => {
2042
+ const pos = slot.getPos();
2043
+ if (pos !== void 0) run(setCodeLanguage(pos, event.target.value));
2044
+ },
2045
+ onKeyDown: (event) => {
2046
+ if (event.key === "Enter" || event.key === "Escape") {
2047
+ event.preventDefault();
2048
+ const pos = slot.getPos();
2049
+ if (pos !== void 0) run((s, d) => d ? (d(s.tr.setSelection(TextSelection.create(s.doc, pos + 1))), true) : true);
2050
+ }
2051
+ }
2052
+ }
2053
+ );
2054
+ }
2055
+ const tone = slot.node.attrs.tone;
2056
+ return /* @__PURE__ */ jsx(
2057
+ Menu,
2058
+ {
2059
+ label: text.calloutTone,
2060
+ items: [
2061
+ ...MARKDOWN_CALLOUT_TONES.map((t) => ({
2062
+ key: t,
2063
+ label: text.tones[t],
2064
+ checked: t === tone,
2065
+ onSelect: () => {
2066
+ const pos = slot.getPos();
2067
+ if (pos !== void 0) run(setCalloutTone(pos, t));
2068
+ }
2069
+ })),
2070
+ { kind: "separator", key: "sep" },
2071
+ {
2072
+ key: "remove",
2073
+ label: text.calloutRemove,
2074
+ onSelect: () => {
2075
+ const pos = slot.getPos();
2076
+ if (pos !== void 0) run(unwrapAt(pos));
2077
+ }
2078
+ }
2079
+ ],
2080
+ renderTrigger: (trigger) => /* @__PURE__ */ jsxs(
2081
+ "button",
2082
+ {
2083
+ ...trigger,
2084
+ ref: trigger.ref,
2085
+ type: "button",
2086
+ className: "crewlet-rich-editor__tone",
2087
+ disabled,
2088
+ "aria-label": `${text.calloutTone}: ${text.tones[tone]}`,
2089
+ children: [
2090
+ text.tones[tone],
2091
+ /* @__PURE__ */ jsx(ChevronDownGlyph, { size: "sm" })
2092
+ ]
2093
+ }
2094
+ )
2095
+ }
2096
+ );
2097
+ }
2098
+
2099
+ export { Editor };