@underverse-ui/underverse 1.0.202 → 1.0.204

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 (39) hide show
  1. package/README.md +8 -2
  2. package/api-reference.json +1 -1
  3. package/dist/EmojiPicker-UN6N7YVB.js +14 -0
  4. package/dist/EmojiPicker-UN6N7YVB.js.map +1 -0
  5. package/dist/chunk-4TYW5K4J.js +769 -0
  6. package/dist/chunk-4TYW5K4J.js.map +1 -0
  7. package/dist/chunk-CC3QO2YM.js +14506 -0
  8. package/dist/chunk-CC3QO2YM.js.map +1 -0
  9. package/dist/chunk-CKKLFVOY.js +206 -0
  10. package/dist/chunk-CKKLFVOY.js.map +1 -0
  11. package/dist/chunk-GSBRTFP2.js +2464 -0
  12. package/dist/chunk-GSBRTFP2.js.map +1 -0
  13. package/dist/chunk-KFTYWTJR.js +4481 -0
  14. package/dist/chunk-KFTYWTJR.js.map +1 -0
  15. package/dist/chunk-NSBPE2FW.js +17 -0
  16. package/dist/chunk-NSBPE2FW.js.map +1 -0
  17. package/dist/chunk-QVQTWZ24.js +199 -0
  18. package/dist/chunk-QVQTWZ24.js.map +1 -0
  19. package/dist/chunk-ZUAIBY2Z.js +74 -0
  20. package/dist/chunk-ZUAIBY2Z.js.map +1 -0
  21. package/dist/emojis-I5AMZ3EE.js +8 -0
  22. package/dist/emojis-I5AMZ3EE.js.map +1 -0
  23. package/dist/index.cjs +36680 -35147
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +19 -134
  26. package/dist/index.d.ts +19 -134
  27. package/dist/index.js +2801 -25223
  28. package/dist/index.js.map +1 -1
  29. package/dist/lowlight-runtime-Y43Y7YTW.js +11 -0
  30. package/dist/lowlight-runtime-Y43Y7YTW.js.map +1 -0
  31. package/dist/menu-bar-6U5S4PS7.js +947 -0
  32. package/dist/menu-bar-6U5S4PS7.js.map +1 -0
  33. package/dist/ueditor.cjs +23503 -0
  34. package/dist/ueditor.cjs.map +1 -0
  35. package/dist/ueditor.d.cts +136 -0
  36. package/dist/ueditor.d.ts +136 -0
  37. package/dist/ueditor.js +20 -0
  38. package/dist/ueditor.js.map +1 -0
  39. package/package.json +54 -46
@@ -0,0 +1,947 @@
1
+ "use client";
2
+ import {
3
+ Modal_default
4
+ } from "./chunk-QVQTWZ24.js";
5
+ import {
6
+ DEFAULT_TABLE_ROW_HEIGHT,
7
+ DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE,
8
+ DEFAULT_UEDITOR_IMAGE_MIME_TYPES,
9
+ DropdownMenu,
10
+ DropdownMenuItem,
11
+ DropdownMenuSeparator,
12
+ DropdownMenuSub,
13
+ ImageInput,
14
+ LinkInput,
15
+ MIN_TABLE_ROW_HEIGHT,
16
+ TableInsertGrid,
17
+ UEDITOR_PROSEMIRROR_CLASS_NAME,
18
+ applyEditorLink,
19
+ fileToDataUrl,
20
+ getTableAnchorPos,
21
+ sanitizeUEditorUrl,
22
+ useDropdownMenuClose,
23
+ useSharedEditorUiRenderState
24
+ } from "./chunk-KFTYWTJR.js";
25
+ import {
26
+ cn,
27
+ useSmartTranslations
28
+ } from "./chunk-GSBRTFP2.js";
29
+ import "./chunk-NSBPE2FW.js";
30
+
31
+ // src/components/UEditor/menu-bar.tsx
32
+ import React, { useMemo, useRef, useState } from "react";
33
+ import {
34
+ AlignCenter,
35
+ AlignJustify,
36
+ AlignLeft,
37
+ AlignRight,
38
+ Bold as BoldIcon,
39
+ Code as CodeIcon,
40
+ Eye,
41
+ FileCode,
42
+ Heading1 as Heading1Icon,
43
+ Heading2 as Heading2Icon,
44
+ Heading3 as Heading3Icon,
45
+ Image as ImageIcon,
46
+ Italic as ItalicIcon,
47
+ Link as LinkIcon,
48
+ ListTodo,
49
+ Maximize2,
50
+ Quote as QuoteIcon,
51
+ Redo as RedoIcon,
52
+ Scissors,
53
+ Strikethrough as StrikethroughIcon,
54
+ Subscript as SubscriptIcon,
55
+ Superscript as SuperscriptIcon,
56
+ Table as TableIcon,
57
+ Trash2,
58
+ Underline as UnderlineIcon,
59
+ Undo as UndoIcon,
60
+ Square,
61
+ CircleDot,
62
+ CircleCheckBig,
63
+ Upload
64
+ } from "lucide-react";
65
+
66
+ // src/components/UEditor/preview-html.ts
67
+ var DEFAULT_TABLE_COLUMN_WIDTH = 100;
68
+ var TIPTAP_TABLE_MIN_COLUMN_WIDTH = 25;
69
+ function parsePixelWidth(value) {
70
+ if (!value) return null;
71
+ const match = String(value).match(/(?:^|[\s:])(\d+(?:\.\d+)?)px\b/i) ?? String(value).match(/^(\d+(?:\.\d+)?)$/);
72
+ if (!match) return null;
73
+ const parsed = Number.parseFloat(match[1] ?? match[0]);
74
+ return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed) : null;
75
+ }
76
+ function parseStyleWidth(style) {
77
+ return parsePixelWidth(style.width) ?? parsePixelWidth(style.minWidth);
78
+ }
79
+ function parseStyleHeight(style) {
80
+ return parsePixelWidth(style.height) ?? parsePixelWidth(style.minHeight);
81
+ }
82
+ function getCellColspan(cell) {
83
+ return Math.max(1, cell.colSpan || Number.parseInt(cell.getAttribute("colspan") || "1", 10) || 1);
84
+ }
85
+ function getCellWidths(cell) {
86
+ const dataColwidth = cell.getAttribute("data-colwidth") || cell.getAttribute("colwidth");
87
+ if (dataColwidth) {
88
+ const widths = dataColwidth.split(/[,\s]+/).map((part) => parsePixelWidth(part)).filter((width2) => typeof width2 === "number");
89
+ if (widths.length > 0) return widths;
90
+ }
91
+ const width = parsePixelWidth(cell.getAttribute("width")) ?? parseStyleWidth(cell.style);
92
+ if (!width) return null;
93
+ const colspan = getCellColspan(cell);
94
+ return Array.from({ length: colspan }, () => Math.max(DEFAULT_TABLE_COLUMN_WIDTH, Math.round(width / colspan)));
95
+ }
96
+ function getColumnCount(table) {
97
+ const colCount = table.querySelectorAll("colgroup > col").length;
98
+ if (colCount > 0) return colCount;
99
+ return Math.max(
100
+ 0,
101
+ ...Array.from(table.rows).map(
102
+ (row) => Array.from(row.cells).reduce((count, cell) => count + getCellColspan(cell), 0)
103
+ )
104
+ );
105
+ }
106
+ function resolveColumnWidths(table) {
107
+ const columnCount = getColumnCount(table);
108
+ if (columnCount <= 0) return [];
109
+ const widths = Array.from({ length: columnCount }, () => DEFAULT_TABLE_COLUMN_WIDTH);
110
+ const cols = Array.from(table.querySelectorAll("colgroup > col"));
111
+ cols.slice(0, columnCount).forEach((col, index) => {
112
+ const width = parsePixelWidth(col.getAttribute("width")) ?? parseStyleWidth(col.style);
113
+ if (width && width > TIPTAP_TABLE_MIN_COLUMN_WIDTH) {
114
+ widths[index] = width;
115
+ }
116
+ });
117
+ Array.from(table.rows).forEach((row) => {
118
+ let columnIndex = 0;
119
+ Array.from(row.cells).forEach((cell) => {
120
+ const cellWidths = getCellWidths(cell);
121
+ if (cellWidths) {
122
+ cellWidths.slice(0, getCellColspan(cell)).forEach((width, offset) => {
123
+ if (width > TIPTAP_TABLE_MIN_COLUMN_WIDTH && columnIndex + offset < widths.length) {
124
+ widths[columnIndex + offset] = width;
125
+ }
126
+ });
127
+ }
128
+ columnIndex += getCellColspan(cell);
129
+ });
130
+ });
131
+ return widths;
132
+ }
133
+ function setStyleProperty(element, property, value) {
134
+ element.style.setProperty(property, value);
135
+ }
136
+ function resolveExplicitRowHeight(row) {
137
+ const explicitRowHeight = parsePixelWidth(row.getAttribute("data-row-height")) ?? parseStyleHeight(row.style);
138
+ if (explicitRowHeight) return Math.max(MIN_TABLE_ROW_HEIGHT, explicitRowHeight);
139
+ const cellHeight = Array.from(row.cells).reduce((maxHeight, cell) => {
140
+ const height = parsePixelWidth(cell.getAttribute("height")) ?? parseStyleHeight(cell.style);
141
+ return height ? Math.max(maxHeight, height) : maxHeight;
142
+ }, 0);
143
+ return cellHeight ? Math.max(MIN_TABLE_ROW_HEIGHT, cellHeight) : DEFAULT_TABLE_ROW_HEIGHT;
144
+ }
145
+ function normalizePreviewRowHeight(row) {
146
+ const rowHeight = resolveExplicitRowHeight(row);
147
+ row.style.height = `${rowHeight}px`;
148
+ row.style.minHeight = `${rowHeight}px`;
149
+ Array.from(row.cells).forEach((cell) => {
150
+ cell.style.height = `${rowHeight}px`;
151
+ cell.style.minHeight = `${rowHeight}px`;
152
+ });
153
+ }
154
+ function normalizePreviewTable(table) {
155
+ const widths = resolveColumnWidths(table);
156
+ if (widths.length === 0) return;
157
+ let colgroup = table.querySelector("colgroup");
158
+ if (!colgroup) {
159
+ colgroup = document.createElement("colgroup");
160
+ table.insertBefore(colgroup, table.firstChild);
161
+ }
162
+ while (colgroup.children.length < widths.length) {
163
+ colgroup.appendChild(document.createElement("col"));
164
+ }
165
+ Array.from(colgroup.children).forEach((child, index) => {
166
+ if (child.tagName.toLowerCase() !== "col") return;
167
+ const col = child;
168
+ if (index >= widths.length) {
169
+ child.remove();
170
+ return;
171
+ }
172
+ col.style.width = `${widths[index]}px`;
173
+ col.style.minWidth = `${widths[index]}px`;
174
+ col.setAttribute("width", String(widths[index]));
175
+ });
176
+ const tableWidth = widths.reduce((sum, width) => sum + width, 0);
177
+ setStyleProperty(table, "width", `${tableWidth}px`);
178
+ setStyleProperty(table, "min-width", `${tableWidth}px`);
179
+ setStyleProperty(table, "table-layout", "fixed");
180
+ Array.from(table.rows).forEach((row) => {
181
+ let columnIndex = 0;
182
+ normalizePreviewRowHeight(row);
183
+ Array.from(row.cells).forEach((cell) => {
184
+ const colspan = getCellColspan(cell);
185
+ const cellWidth = widths.slice(columnIndex, columnIndex + colspan).reduce((sum, width) => sum + width, 0);
186
+ if (cellWidth > 0) {
187
+ cell.style.width = `${cellWidth}px`;
188
+ cell.style.minWidth = `${cellWidth}px`;
189
+ }
190
+ columnIndex += colspan;
191
+ });
192
+ });
193
+ }
194
+ function prepareUEditorPreviewHtml(html) {
195
+ if (typeof document === "undefined" || !html) return html;
196
+ const container = document.createElement("div");
197
+ container.innerHTML = html;
198
+ container.querySelectorAll("table").forEach((table) => normalizePreviewTable(table));
199
+ return container.innerHTML;
200
+ }
201
+
202
+ // src/components/UEditor/menu-bar.tsx
203
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
204
+ function MenuTableInsertGrid({
205
+ insertLabel,
206
+ previewTemplate,
207
+ onInsert
208
+ }) {
209
+ const closeMenu = useDropdownMenuClose();
210
+ return /* @__PURE__ */ jsx(
211
+ TableInsertGrid,
212
+ {
213
+ insertLabel,
214
+ previewTemplate,
215
+ onInsert: (rows, cols) => {
216
+ onInsert(rows, cols);
217
+ closeMenu();
218
+ }
219
+ }
220
+ );
221
+ }
222
+ function renderMenuItems(items) {
223
+ return items.map((item, i) => {
224
+ switch (item.type) {
225
+ case "separator":
226
+ return /* @__PURE__ */ jsx(DropdownMenuSeparator, {}, i);
227
+ case "action":
228
+ return /* @__PURE__ */ jsx(
229
+ DropdownMenuItem,
230
+ {
231
+ label: item.label,
232
+ icon: item.icon,
233
+ shortcut: item.shortcut,
234
+ onClick: item.onClick,
235
+ disabled: item.disabled,
236
+ active: item.active,
237
+ destructive: item.destructive
238
+ },
239
+ i
240
+ );
241
+ case "sub":
242
+ return /* @__PURE__ */ jsx(DropdownMenuSub, { label: item.label, icon: item.icon, disabled: item.disabled, children: renderMenuItems(item.items) }, i);
243
+ case "custom":
244
+ return /* @__PURE__ */ jsx(React.Fragment, { children: item.render() }, item.key);
245
+ }
246
+ });
247
+ }
248
+ function buildFileMenuItems(t, editor, { onSave, onExport }) {
249
+ return [
250
+ {
251
+ type: "action",
252
+ label: t("menubar.save"),
253
+ shortcut: "Ctrl+S",
254
+ onClick: () => onSave?.(),
255
+ disabled: !onSave
256
+ },
257
+ {
258
+ type: "action",
259
+ label: t("menubar.exportHtml"),
260
+ onClick: () => {
261
+ if (onExport) {
262
+ onExport();
263
+ } else {
264
+ const html = editor.getHTML();
265
+ const blob = new Blob([`<!DOCTYPE html><html><body>${html}</body></html>`], { type: "text/html" });
266
+ const url = URL.createObjectURL(blob);
267
+ const a = document.createElement("a");
268
+ a.href = url;
269
+ a.download = "document.html";
270
+ a.click();
271
+ URL.revokeObjectURL(url);
272
+ }
273
+ }
274
+ }
275
+ ];
276
+ }
277
+ function buildEditMenuItems(t, editor) {
278
+ return [
279
+ {
280
+ type: "action",
281
+ label: t("menubar.undo"),
282
+ icon: UndoIcon,
283
+ shortcut: t("menubar.undoShortcut"),
284
+ onClick: () => editor.chain().focus().undo().run(),
285
+ disabled: !editor.can().undo()
286
+ },
287
+ {
288
+ type: "action",
289
+ label: t("menubar.redo"),
290
+ icon: RedoIcon,
291
+ shortcut: t("menubar.redoShortcut"),
292
+ onClick: () => editor.chain().focus().redo().run(),
293
+ disabled: !editor.can().redo()
294
+ },
295
+ { type: "separator" },
296
+ {
297
+ type: "action",
298
+ label: t("menubar.cut"),
299
+ icon: Scissors,
300
+ shortcut: t("menubar.cutShortcut"),
301
+ onClick: () => document.execCommand("cut")
302
+ },
303
+ {
304
+ type: "action",
305
+ label: t("menubar.copy"),
306
+ shortcut: t("menubar.copyShortcut"),
307
+ onClick: () => document.execCommand("copy")
308
+ },
309
+ {
310
+ type: "action",
311
+ label: t("menubar.paste"),
312
+ shortcut: t("menubar.pasteShortcut"),
313
+ onClick: () => document.execCommand("paste")
314
+ },
315
+ {
316
+ type: "action",
317
+ label: t("menubar.pasteAsText"),
318
+ onClick: async () => {
319
+ try {
320
+ const text = await navigator.clipboard.readText();
321
+ editor.chain().focus().insertContent(text).run();
322
+ } catch {
323
+ document.execCommand("paste");
324
+ }
325
+ }
326
+ },
327
+ { type: "separator" },
328
+ {
329
+ type: "action",
330
+ label: t("menubar.selectAll"),
331
+ shortcut: t("menubar.selectAllShortcut"),
332
+ onClick: () => editor.chain().focus().selectAll().run()
333
+ }
334
+ ];
335
+ }
336
+ function buildViewMenuItems(t, {
337
+ containerRef,
338
+ onSourceCode,
339
+ openSourceDialog,
340
+ onPreview,
341
+ openPreviewDialog
342
+ }) {
343
+ return [
344
+ {
345
+ type: "action",
346
+ label: t("menubar.sourceCode"),
347
+ icon: FileCode,
348
+ onClick: () => onSourceCode ? onSourceCode() : openSourceDialog()
349
+ },
350
+ {
351
+ type: "action",
352
+ label: t("menubar.preview"),
353
+ onClick: () => onPreview ? onPreview() : openPreviewDialog()
354
+ },
355
+ { type: "separator" },
356
+ {
357
+ type: "action",
358
+ label: t("menubar.fullscreen"),
359
+ icon: Maximize2,
360
+ shortcut: t("menubar.fullscreenShortcut"),
361
+ onClick: () => {
362
+ const el = containerRef.current;
363
+ if (!el) return;
364
+ if (!document.fullscreenElement) {
365
+ el.requestFullscreen?.();
366
+ } else {
367
+ document.exitFullscreen?.();
368
+ }
369
+ }
370
+ }
371
+ ];
372
+ }
373
+ function buildFormatMenuItems(t, editor) {
374
+ return [
375
+ {
376
+ type: "action",
377
+ label: t("menubar.bold"),
378
+ icon: BoldIcon,
379
+ shortcut: t("menubar.boldShortcut"),
380
+ active: editor.isActive("bold"),
381
+ onClick: () => editor.chain().focus().toggleBold().run()
382
+ },
383
+ {
384
+ type: "action",
385
+ label: t("menubar.italic"),
386
+ icon: ItalicIcon,
387
+ shortcut: t("menubar.italicShortcut"),
388
+ active: editor.isActive("italic"),
389
+ onClick: () => editor.chain().focus().toggleItalic().run()
390
+ },
391
+ {
392
+ type: "action",
393
+ label: t("menubar.underline"),
394
+ icon: UnderlineIcon,
395
+ shortcut: t("menubar.underlineShortcut"),
396
+ active: editor.isActive("underline"),
397
+ onClick: () => editor.chain().focus().toggleUnderline().run()
398
+ },
399
+ {
400
+ type: "action",
401
+ label: t("menubar.strike"),
402
+ icon: StrikethroughIcon,
403
+ active: editor.isActive("strike"),
404
+ onClick: () => editor.chain().focus().toggleStrike().run()
405
+ },
406
+ {
407
+ type: "action",
408
+ label: t("menubar.superscript"),
409
+ icon: SuperscriptIcon,
410
+ active: editor.isActive("superscript"),
411
+ onClick: () => editor.chain().focus().toggleSuperscript().run()
412
+ },
413
+ {
414
+ type: "action",
415
+ label: t("menubar.subscript"),
416
+ icon: SubscriptIcon,
417
+ active: editor.isActive("subscript"),
418
+ onClick: () => editor.chain().focus().toggleSubscript().run()
419
+ },
420
+ {
421
+ type: "action",
422
+ label: t("menubar.code"),
423
+ icon: CodeIcon,
424
+ active: editor.isActive("code"),
425
+ onClick: () => editor.chain().focus().toggleCode().run()
426
+ },
427
+ { type: "separator" },
428
+ {
429
+ type: "sub",
430
+ label: t("menubar.wrap"),
431
+ items: [
432
+ {
433
+ type: "action",
434
+ label: t("menubar.paragraph"),
435
+ active: editor.isActive("paragraph"),
436
+ onClick: () => editor.chain().focus().setParagraph().run()
437
+ },
438
+ {
439
+ type: "action",
440
+ label: t("menubar.heading1"),
441
+ icon: Heading1Icon,
442
+ active: editor.isActive("heading", { level: 1 }),
443
+ onClick: () => editor.chain().focus().toggleHeading({ level: 1 }).run()
444
+ },
445
+ {
446
+ type: "action",
447
+ label: t("menubar.heading2"),
448
+ icon: Heading2Icon,
449
+ active: editor.isActive("heading", { level: 2 }),
450
+ onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run()
451
+ },
452
+ {
453
+ type: "action",
454
+ label: t("menubar.heading3"),
455
+ icon: Heading3Icon,
456
+ active: editor.isActive("heading", { level: 3 }),
457
+ onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run()
458
+ },
459
+ { type: "separator" },
460
+ {
461
+ type: "action",
462
+ label: t("menubar.blockquote"),
463
+ icon: QuoteIcon,
464
+ active: editor.isActive("blockquote"),
465
+ onClick: () => editor.chain().focus().toggleBlockquote().run()
466
+ },
467
+ {
468
+ type: "action",
469
+ label: t("menubar.codeBlock"),
470
+ icon: FileCode,
471
+ active: editor.isActive("codeBlock"),
472
+ onClick: () => editor.chain().focus().toggleCodeBlock().run()
473
+ },
474
+ {
475
+ type: "action",
476
+ label: t("menubar.taskList"),
477
+ icon: ListTodo,
478
+ active: editor.isActive("taskList"),
479
+ onClick: () => editor.chain().focus().toggleTaskList().run()
480
+ }
481
+ ]
482
+ },
483
+ {
484
+ type: "sub",
485
+ label: t("menubar.align"),
486
+ items: [
487
+ {
488
+ type: "action",
489
+ label: t("menubar.alignLeft"),
490
+ icon: AlignLeft,
491
+ active: editor.isActive({ textAlign: "left" }),
492
+ onClick: () => editor.chain().focus().setTextAlign("left").run()
493
+ },
494
+ {
495
+ type: "action",
496
+ label: t("menubar.alignCenter"),
497
+ icon: AlignCenter,
498
+ active: editor.isActive({ textAlign: "center" }),
499
+ onClick: () => editor.chain().focus().setTextAlign("center").run()
500
+ },
501
+ {
502
+ type: "action",
503
+ label: t("menubar.alignRight"),
504
+ icon: AlignRight,
505
+ active: editor.isActive({ textAlign: "right" }),
506
+ onClick: () => editor.chain().focus().setTextAlign("right").run()
507
+ },
508
+ {
509
+ type: "action",
510
+ label: t("menubar.alignJustify"),
511
+ icon: AlignJustify,
512
+ active: editor.isActive({ textAlign: "justify" }),
513
+ onClick: () => editor.chain().focus().setTextAlign("justify").run()
514
+ }
515
+ ]
516
+ },
517
+ { type: "separator" },
518
+ {
519
+ type: "action",
520
+ label: t("menubar.clearFormatting"),
521
+ onClick: () => editor.chain().focus().unsetAllMarks().clearNodes().run()
522
+ }
523
+ ];
524
+ }
525
+ function buildToolsMenuItems(t, { onSourceCode, openSourceDialog }) {
526
+ return [
527
+ {
528
+ type: "action",
529
+ label: t("menubar.sourceCode"),
530
+ icon: FileCode,
531
+ onClick: () => onSourceCode ? onSourceCode() : openSourceDialog()
532
+ }
533
+ ];
534
+ }
535
+ function buildTableMenuItems(t, editor, onInsertTable) {
536
+ const inTable = getTableAnchorPos(editor) !== null;
537
+ return [
538
+ {
539
+ type: "sub",
540
+ label: t("menubar.insertTable"),
541
+ icon: TableIcon,
542
+ items: [
543
+ {
544
+ type: "custom",
545
+ key: "table-insert-grid",
546
+ render: () => /* @__PURE__ */ jsx(
547
+ MenuTableInsertGrid,
548
+ {
549
+ insertLabel: t("tableMenu.insertTable"),
550
+ previewTemplate: t("tableMenu.gridPreview"),
551
+ onInsert: onInsertTable
552
+ }
553
+ )
554
+ }
555
+ ]
556
+ },
557
+ {
558
+ type: "action",
559
+ label: t("menubar.tableProperties"),
560
+ disabled: true,
561
+ onClick: () => {
562
+ }
563
+ },
564
+ {
565
+ type: "action",
566
+ label: t("menubar.deleteTable"),
567
+ icon: Trash2,
568
+ destructive: true,
569
+ disabled: !inTable,
570
+ onClick: () => editor.chain().focus().deleteTable().run()
571
+ },
572
+ { type: "separator" },
573
+ {
574
+ type: "sub",
575
+ label: t("menubar.row"),
576
+ disabled: !inTable,
577
+ items: [
578
+ {
579
+ type: "action",
580
+ label: t("menubar.deleteRow"),
581
+ destructive: true,
582
+ onClick: () => editor.chain().focus().deleteRow().run()
583
+ }
584
+ ]
585
+ },
586
+ {
587
+ type: "sub",
588
+ label: t("menubar.column"),
589
+ disabled: !inTable,
590
+ items: [
591
+ {
592
+ type: "action",
593
+ label: t("menubar.deleteColumn"),
594
+ destructive: true,
595
+ onClick: () => editor.chain().focus().deleteColumn().run()
596
+ }
597
+ ]
598
+ },
599
+ {
600
+ type: "sub",
601
+ label: t("menubar.cell"),
602
+ disabled: !inTable,
603
+ items: [
604
+ {
605
+ type: "action",
606
+ label: t("menubar.mergeCells"),
607
+ onClick: () => editor.chain().focus().mergeCells().run(),
608
+ disabled: !editor.can().mergeCells()
609
+ },
610
+ {
611
+ type: "action",
612
+ label: t("menubar.splitCell"),
613
+ onClick: () => editor.chain().focus().splitCell().run(),
614
+ disabled: !editor.can().splitCell()
615
+ }
616
+ ]
617
+ }
618
+ ];
619
+ }
620
+ var MenuBarTrigger = React.forwardRef(
621
+ ({ children, className, ...props }, ref) => /* @__PURE__ */ jsx(
622
+ "button",
623
+ {
624
+ ref,
625
+ type: "button",
626
+ onMouseDown: (e) => e.preventDefault(),
627
+ className: cn(
628
+ "px-2 py-0.5 rounded text-sm transition-colors cursor-pointer",
629
+ "hover:bg-accent hover:text-accent-foreground",
630
+ "focus:outline-none focus:bg-accent focus:text-accent-foreground",
631
+ className
632
+ ),
633
+ ...props,
634
+ children
635
+ }
636
+ )
637
+ );
638
+ MenuBarTrigger.displayName = "MenuBarTrigger";
639
+ var MenuBar = ({
640
+ editor,
641
+ containerRef,
642
+ uploadImage,
643
+ imageInsertMode = "base64",
644
+ maxImageFileSize = DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE,
645
+ allowedImageMimeTypes = DEFAULT_UEDITOR_IMAGE_MIME_TYPES,
646
+ onSave,
647
+ onExport,
648
+ onSourceCode,
649
+ onPreview
650
+ }) => {
651
+ const t = useSmartTranslations("UEditor");
652
+ useSharedEditorUiRenderState(editor);
653
+ const fileInputRef = useRef(null);
654
+ const [showImageInput, setShowImageInput] = useState(false);
655
+ const [showLinkInput, setShowLinkInput] = useState(false);
656
+ const [openMenuKey, setOpenMenuKey] = useState(null);
657
+ const [showSourceDialog, setShowSourceDialog] = useState(false);
658
+ const [sourceHtml, setSourceHtml] = useState("");
659
+ const [showPreviewDialog, setShowPreviewDialog] = useState(false);
660
+ const previewHtml = useMemo(
661
+ () => showPreviewDialog ? prepareUEditorPreviewHtml(editor.getHTML()) : "",
662
+ [editor, showPreviewDialog]
663
+ );
664
+ const openSourceDialog = () => {
665
+ setSourceHtml(editor.getHTML());
666
+ setShowSourceDialog(true);
667
+ };
668
+ const openPreviewDialog = () => {
669
+ setShowPreviewDialog(true);
670
+ };
671
+ const handlePreview = () => {
672
+ if (onPreview?.(editor.getHTML()) === false) return;
673
+ openPreviewDialog();
674
+ };
675
+ const applySourceHtml = () => {
676
+ editor.chain().focus().setContent(sourceHtml).run();
677
+ setShowSourceDialog(false);
678
+ };
679
+ const closeInsertMenu = () => {
680
+ setOpenMenuKey(null);
681
+ setShowImageInput(false);
682
+ setShowLinkInput(false);
683
+ };
684
+ const handleImageUrl = (url, alt) => {
685
+ const safe = sanitizeUEditorUrl(url, "image");
686
+ if (safe) {
687
+ editor.chain().focus().setImage({ src: safe, alt: alt ?? "" }).run();
688
+ editor.commands.createParagraphNear();
689
+ }
690
+ closeInsertMenu();
691
+ };
692
+ const handleImageFiles = async (files) => {
693
+ if (!files?.length) return;
694
+ for (const file of Array.from(files)) {
695
+ if (!file.type.startsWith("image/")) continue;
696
+ if (file.size > maxImageFileSize) continue;
697
+ if (allowedImageMimeTypes.length > 0 && !allowedImageMimeTypes.includes(file.type)) continue;
698
+ try {
699
+ const src = imageInsertMode === "upload" && uploadImage ? await uploadImage(file) : await fileToDataUrl(file);
700
+ const safe = sanitizeUEditorUrl(src, "image");
701
+ if (!safe) continue;
702
+ editor.chain().focus().setImage({ src: safe, alt: file.name }).run();
703
+ editor.commands.createParagraphNear();
704
+ } catch {
705
+ }
706
+ }
707
+ closeInsertMenu();
708
+ };
709
+ const handleInsertTable = (rows, cols) => {
710
+ editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();
711
+ closeInsertMenu();
712
+ };
713
+ const handleLinkSubmit = (url) => {
714
+ applyEditorLink(editor, url);
715
+ closeInsertMenu();
716
+ };
717
+ const insertMenuItems = [
718
+ {
719
+ type: "custom",
720
+ key: "image",
721
+ render: () => showImageInput ? /* @__PURE__ */ jsx(ImageInput, { onSubmit: handleImageUrl, onCancel: () => setShowImageInput(false) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
722
+ /* @__PURE__ */ jsx(DropdownMenuItem, { label: t("menubar.image"), icon: ImageIcon, onClick: () => setShowImageInput(true), closeOnSelect: false }),
723
+ /* @__PURE__ */ jsx(
724
+ DropdownMenuItem,
725
+ {
726
+ label: t("menubar.imageUpload"),
727
+ icon: Upload,
728
+ onClick: () => fileInputRef.current?.click(),
729
+ closeOnSelect: false
730
+ }
731
+ )
732
+ ] })
733
+ },
734
+ { type: "separator" },
735
+ {
736
+ type: "custom",
737
+ key: "link",
738
+ render: () => showLinkInput ? /* @__PURE__ */ jsx(LinkInput, { onSubmit: handleLinkSubmit, onCancel: () => setShowLinkInput(false), initialUrl: editor.getAttributes("link").href ?? "" }) : /* @__PURE__ */ jsx(
739
+ DropdownMenuItem,
740
+ {
741
+ label: t("menubar.link"),
742
+ icon: LinkIcon,
743
+ shortcut: t("menubar.linkShortcut"),
744
+ onClick: () => setShowLinkInput(true),
745
+ closeOnSelect: false
746
+ }
747
+ )
748
+ },
749
+ { type: "separator" },
750
+ {
751
+ type: "sub",
752
+ label: t("menubar.insertTable"),
753
+ icon: TableIcon,
754
+ items: [
755
+ {
756
+ type: "custom",
757
+ key: "table-grid",
758
+ render: () => /* @__PURE__ */ jsx(
759
+ MenuTableInsertGrid,
760
+ {
761
+ insertLabel: t("tableMenu.insertTable"),
762
+ previewTemplate: t("tableMenu.gridPreview"),
763
+ onInsert: handleInsertTable
764
+ }
765
+ )
766
+ }
767
+ ]
768
+ },
769
+ { type: "separator" },
770
+ {
771
+ type: "sub",
772
+ label: t("menubar.formFields") || "Form Fields",
773
+ icon: ListTodo,
774
+ items: [
775
+ {
776
+ type: "action",
777
+ label: t("menubar.formCheckbox") || "Form Checkbox",
778
+ icon: Square,
779
+ onClick: () => editor.chain().focus().setFormCheckbox().run()
780
+ },
781
+ {
782
+ type: "action",
783
+ label: t("slashCommand.roundCheckbox") || "Round Checkbox",
784
+ icon: CircleCheckBig,
785
+ onClick: () => editor.chain().focus().setFormCheckbox({ variant: "circle" }).run()
786
+ },
787
+ {
788
+ type: "action",
789
+ label: t("menubar.formRadio") || "Form Radio Button",
790
+ icon: CircleDot,
791
+ onClick: () => editor.chain().focus().setFormRadio().run()
792
+ }
793
+ ]
794
+ },
795
+ { type: "separator" },
796
+ {
797
+ type: "action",
798
+ label: t("menubar.horizontalRule"),
799
+ onClick: () => editor.chain().focus().setHorizontalRule().run()
800
+ }
801
+ ];
802
+ const menus = [
803
+ {
804
+ key: "file",
805
+ label: t("menubar.file"),
806
+ items: buildFileMenuItems(t, editor, { onSave, onExport })
807
+ },
808
+ { key: "edit", label: t("menubar.edit"), items: buildEditMenuItems(t, editor) },
809
+ {
810
+ key: "view",
811
+ label: t("menubar.view"),
812
+ items: buildViewMenuItems(t, { containerRef, onSourceCode, openSourceDialog, onPreview: handlePreview, openPreviewDialog })
813
+ },
814
+ {
815
+ key: "insert",
816
+ label: t("menubar.insert"),
817
+ items: insertMenuItems
818
+ },
819
+ { key: "format", label: t("menubar.format"), items: buildFormatMenuItems(t, editor) },
820
+ {
821
+ key: "tools",
822
+ label: t("menubar.tools"),
823
+ items: buildToolsMenuItems(t, { onSourceCode, openSourceDialog })
824
+ },
825
+ { key: "table", label: t("menubar.table"), items: buildTableMenuItems(t, editor, handleInsertTable) }
826
+ ];
827
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
828
+ /* @__PURE__ */ jsx(
829
+ "input",
830
+ {
831
+ ref: fileInputRef,
832
+ type: "file",
833
+ accept: allowedImageMimeTypes.join(","),
834
+ multiple: true,
835
+ className: "hidden",
836
+ onChange: (e) => handleImageFiles(e.target.files)
837
+ }
838
+ ),
839
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 border-b border-border/35 bg-muted/20 px-1.5 py-0.5", children: [
840
+ menus.map(({ key, label, items }) => /* @__PURE__ */ jsx(
841
+ DropdownMenu,
842
+ {
843
+ trigger: /* @__PURE__ */ jsx(MenuBarTrigger, { children: label }),
844
+ placement: "bottom-start",
845
+ isOpen: openMenuKey === key,
846
+ onOpenChange: (open) => {
847
+ setOpenMenuKey((current) => open ? key : current === key ? null : current);
848
+ if (key === "insert" && !open) {
849
+ setShowImageInput(false);
850
+ setShowLinkInput(false);
851
+ }
852
+ },
853
+ openOnHover: true,
854
+ children: renderMenuItems(items)
855
+ },
856
+ key
857
+ )),
858
+ /* @__PURE__ */ jsx(
859
+ "button",
860
+ {
861
+ type: "button",
862
+ onClick: handlePreview,
863
+ "aria-label": t("menubar.preview"),
864
+ title: t("menubar.preview"),
865
+ className: cn(
866
+ "ml-auto inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors",
867
+ "hover:bg-accent hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1"
868
+ ),
869
+ children: /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4", "aria-hidden": "true" })
870
+ }
871
+ )
872
+ ] }),
873
+ /* @__PURE__ */ jsx(
874
+ Modal_default,
875
+ {
876
+ isOpen: showSourceDialog,
877
+ onClose: () => setShowSourceDialog(false),
878
+ title: t("menubar.sourceCode"),
879
+ size: "lg",
880
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
881
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("menubar.sourceCodeHint") }),
882
+ /* @__PURE__ */ jsx(
883
+ "textarea",
884
+ {
885
+ "data-testid": "source-code-textarea",
886
+ className: "w-full h-64 font-mono text-xs p-3 bg-muted rounded-lg border border-border resize-y focus:outline-none focus:ring-2 focus:ring-primary/20",
887
+ value: sourceHtml,
888
+ onChange: (e) => setSourceHtml(e.target.value),
889
+ spellCheck: false
890
+ }
891
+ ),
892
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-end", children: [
893
+ /* @__PURE__ */ jsx(
894
+ "button",
895
+ {
896
+ type: "button",
897
+ onClick: applySourceHtml,
898
+ className: "px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm hover:bg-primary/90 transition-colors",
899
+ children: t("menubar.applySource")
900
+ }
901
+ ),
902
+ /* @__PURE__ */ jsx(
903
+ "button",
904
+ {
905
+ type: "button",
906
+ onClick: () => setShowSourceDialog(false),
907
+ className: "px-4 py-2 bg-muted rounded-lg text-sm hover:bg-accent transition-colors",
908
+ children: t("menubar.closeDialog")
909
+ }
910
+ )
911
+ ] })
912
+ ] })
913
+ }
914
+ ),
915
+ /* @__PURE__ */ jsx(
916
+ Modal_default,
917
+ {
918
+ isOpen: showPreviewDialog,
919
+ onClose: () => setShowPreviewDialog(false),
920
+ title: t("menubar.preview"),
921
+ size: "full",
922
+ width: "min(1180px, calc(100vw - 2rem))",
923
+ height: "min(860px, calc(100vh - 2rem))",
924
+ className: "flex flex-col overflow-hidden rounded-xl md:rounded-2xl",
925
+ contentClassName: "flex min-h-0 flex-1 overflow-hidden p-0",
926
+ children: /* @__PURE__ */ jsx(
927
+ "div",
928
+ {
929
+ "data-testid": "preview-content",
930
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain",
931
+ children: editor.isEmpty ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: t("menubar.previewEmpty") }) : /* @__PURE__ */ jsx(
932
+ "div",
933
+ {
934
+ className: UEDITOR_PROSEMIRROR_CLASS_NAME,
935
+ dangerouslySetInnerHTML: { __html: previewHtml }
936
+ }
937
+ )
938
+ }
939
+ )
940
+ }
941
+ )
942
+ ] });
943
+ };
944
+ export {
945
+ MenuBar
946
+ };
947
+ //# sourceMappingURL=menu-bar-6U5S4PS7.js.map