@crewlethq/ui 0.28.0 → 0.30.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 (95) hide show
  1. package/README.md +45 -4
  2. package/dist/AccessDeniedCard/index.js +3 -3
  3. package/dist/Accordion/index.js +3 -3
  4. package/dist/AppShell/index.js +1 -1
  5. package/dist/{AppShell-APHZKJYH.css → AppShell-GCM4TDVY.css} +4 -1
  6. package/dist/ArtifactBar/ArtifactBar.d.ts +143 -0
  7. package/dist/ArtifactBar/index.d.ts +3 -0
  8. package/dist/ArtifactBar/index.js +25 -0
  9. package/dist/ArtifactBar-IBWB5POL.css +358 -0
  10. package/dist/ArtifactCard/index.js +3 -3
  11. package/dist/ArtifactCover/index.js +5 -5
  12. package/dist/ArtifactIcon/index.js +4 -4
  13. package/dist/ArtifactPanel/ArtifactPanel.d.ts +81 -0
  14. package/dist/ArtifactPanel/index.d.ts +3 -0
  15. package/dist/ArtifactPanel/index.js +5 -0
  16. package/dist/ArtifactPanel-YFXT5O77.css +132 -0
  17. package/dist/AuthLayout/index.js +3 -3
  18. package/dist/AvatarPicker/index.js +4 -4
  19. package/dist/Card/index.js +2 -2
  20. package/dist/ChangeMark/index.js +1 -1
  21. package/dist/CommandPalette/index.js +3 -3
  22. package/dist/CommentThread/index.js +5 -5
  23. package/dist/Composer/index.js +2 -2
  24. package/dist/DataView/index.js +4 -4
  25. package/dist/Disclosure/index.js +3 -3
  26. package/dist/Document/Document.d.ts +8 -1
  27. package/dist/Document/index.js +1 -1
  28. package/dist/{Document-PQGXJHB7.css → Document-G3YTZIUL.css} +71 -0
  29. package/dist/EditingStatus/EditingStatus.d.ts +25 -0
  30. package/dist/EditingStatus/index.d.ts +3 -0
  31. package/dist/EditingStatus/index.js +2 -0
  32. package/dist/EditingStatus-NFADEPSX.css +53 -0
  33. package/dist/Editor-YZ7KG2RD.js +2099 -0
  34. package/dist/HuePicker/index.js +2 -2
  35. package/dist/ListInput/index.js +2 -2
  36. package/dist/Markdown/grammar.d.ts +170 -0
  37. package/dist/Markdown/index.d.ts +2 -0
  38. package/dist/Markdown/index.js +1 -0
  39. package/dist/MarkdownEditor/index.js +1 -1
  40. package/dist/PeopleRail/PeopleRail.d.ts +8 -1
  41. package/dist/PeopleRail/index.js +1 -1
  42. package/dist/{PeopleRail-R64WVOSH.css → PeopleRail-SZGHZXVT.css} +8 -0
  43. package/dist/RichEditor/Editor.d.ts +17 -0
  44. package/dist/RichEditor/RichEditor.d.ts +26 -0
  45. package/dist/RichEditor/codec.d.ts +78 -0
  46. package/dist/RichEditor/commands.d.ts +89 -0
  47. package/dist/RichEditor/index.d.ts +4 -0
  48. package/dist/RichEditor/index.js +8 -0
  49. package/dist/RichEditor/keys.d.ts +16 -0
  50. package/dist/RichEditor/nodeviews.d.ts +60 -0
  51. package/dist/RichEditor/paste.d.ts +47 -0
  52. package/dist/RichEditor/plugins.d.ts +49 -0
  53. package/dist/RichEditor/roundtrip.d.ts +14 -0
  54. package/dist/RichEditor/schema.d.ts +23 -0
  55. package/dist/RichEditor/types.d.ts +140 -0
  56. package/dist/RichEditor-NPD2KAPS.css +419 -0
  57. package/dist/RichEditorMarkdown/index.d.ts +1 -0
  58. package/dist/RichEditorMarkdown/index.js +3 -0
  59. package/dist/SheetGrid/index.js +1 -1
  60. package/dist/ThemeSwitcher/index.js +1 -1
  61. package/dist/VersionCompare/index.js +2 -2
  62. package/dist/chunk-3DPIU2Q7.js +28 -0
  63. package/dist/{chunk-V2Z24W2Z.js → chunk-4JUKWWUD.js} +1 -1
  64. package/dist/{chunk-MQEEQVG6.js → chunk-6AH2XJ3Q.js} +2 -2
  65. package/dist/chunk-A4E5YLXO.js +85 -0
  66. package/dist/{chunk-WENCSQ47.js → chunk-EBNI36UQ.js} +1 -1
  67. package/dist/{chunk-6J35F334.js → chunk-GOSQXEZU.js} +1 -1
  68. package/dist/{chunk-M2VJGNCD.js → chunk-GR55WT4R.js} +3 -3
  69. package/dist/chunk-I3BSQUKN.js +121 -0
  70. package/dist/chunk-IDBHX5SR.js +12 -0
  71. package/dist/{chunk-WI6K5M5X.js → chunk-M3LUNN7F.js} +1 -1
  72. package/dist/{chunk-S4RCG44T.js → chunk-MICH6QSJ.js} +1 -1
  73. package/dist/chunk-QMBP23BL.js +347 -0
  74. package/dist/{chunk-G2AVKPSL.js → chunk-RIAZM6L3.js} +1 -1
  75. package/dist/chunk-SKOYC2CF.js +288 -0
  76. package/dist/{chunk-PRNX5AFE.js → chunk-TGYDROIB.js} +1 -1
  77. package/dist/{chunk-G3EEUNZQ.js → chunk-UB7TJEKZ.js} +1 -1
  78. package/dist/chunk-UQHKFF2N.js +106 -0
  79. package/dist/{chunk-WHZFDS62.js → chunk-VDPJGP4Q.js} +1 -1
  80. package/dist/chunk-VOX3YRFO.js +76 -0
  81. package/dist/chunk-YFW4MTK4.js +749 -0
  82. package/dist/generated/index.d.ts +6 -0
  83. package/dist/generated/index.js +83 -75
  84. package/dist/index.js +83 -75
  85. package/dist/styles.css +8849 -7793
  86. package/package.json +13 -3
  87. package/dist/chunk-FVLCTCMV.js +0 -96
  88. package/dist/{chunk-RIEDQSA4.js → chunk-7DZDSSJS.js} +1 -1
  89. package/dist/{chunk-YZCJ63JF.js → chunk-ACA27MZH.js} +1 -1
  90. package/dist/{chunk-ENJUPIND.js → chunk-FZVJDJFN.js} +1 -1
  91. package/dist/{chunk-SGIQCI6Y.js → chunk-LIXSV5YI.js} +1 -1
  92. package/dist/{chunk-NWW4IQJU.js → chunk-OC46246T.js} +0 -0
  93. package/dist/{chunk-VOMYRJV5.js → chunk-RODMM35O.js} +2 -2
  94. package/dist/{chunk-HCKJGBQH.js → chunk-VVRL27HM.js} +1 -1
  95. package/dist/{chunk-D5JZN3ML.js → chunk-Z6VW6CXX.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 };