@lumir-company/editor 0.4.13 → 0.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/components/LumirEditor.tsx
4
- import { useEffect as useEffect8, useMemo as useMemo3, useCallback as useCallback16, useState as useState8, useRef as useRef9 } from "react";
4
+ import { useEffect as useEffect10, useMemo as useMemo6, useCallback as useCallback18, useState as useState10, useRef as useRef11 } from "react";
5
5
  import {
6
6
  useCreateBlockNote,
7
7
  SideMenu as BlockSideMenu,
@@ -11,14 +11,15 @@ import {
11
11
  getDefaultReactSlashMenuItems,
12
12
  LinkToolbarController,
13
13
  FormattingToolbarController,
14
- useBlockNoteEditor as useBlockNoteEditor3,
15
- useComponentsContext as useComponentsContext3,
14
+ useBlockNoteEditor as useBlockNoteEditor5,
15
+ useComponentsContext as useComponentsContext4,
16
16
  EditLinkButton,
17
17
  OpenLinkButton,
18
18
  DeleteLinkButton
19
19
  } from "@blocknote/react";
20
20
  import { BlockNoteView } from "@blocknote/mantine";
21
21
  import { insertOrUpdateBlock } from "@blocknote/core";
22
+ import { ko, en } from "@blocknote/core/locales";
22
23
 
23
24
  // src/utils/cn.ts
24
25
  function cn(...inputs) {
@@ -1814,6 +1815,66 @@ var TextStyleButton = ({
1814
1815
 
1815
1816
  // src/components/FloatingMenu/components/AlignButton.tsx
1816
1817
  import { useCallback as useCallback6 } from "react";
1818
+
1819
+ // src/utils/prosemirror-table-utils.ts
1820
+ function getSelectedCellPositions(editor) {
1821
+ const tiptap = editor._tiptapEditor;
1822
+ if (!tiptap) return [];
1823
+ const { state } = tiptap;
1824
+ const { selection } = state;
1825
+ if (typeof selection.forEachCell === "function") {
1826
+ const positions = [];
1827
+ selection.forEachCell((_node, pos) => {
1828
+ positions.push(pos);
1829
+ });
1830
+ return positions;
1831
+ }
1832
+ const $pos = selection.$from;
1833
+ for (let depth = $pos.depth; depth > 0; depth--) {
1834
+ const node = $pos.node(depth);
1835
+ if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
1836
+ return [$pos.before(depth)];
1837
+ }
1838
+ }
1839
+ return [];
1840
+ }
1841
+ function setCellAttrAtPositions(editor, positions, attr, value) {
1842
+ const tiptap = editor?._tiptapEditor;
1843
+ if (!tiptap || positions.length === 0) return false;
1844
+ let tr = tiptap.state.tr;
1845
+ let changed = false;
1846
+ for (const pos of positions) {
1847
+ const node = tr.doc.nodeAt(pos);
1848
+ if (node && (node.type.name === "tableCell" || node.type.name === "tableHeader")) {
1849
+ tr = tr.setNodeMarkup(pos, void 0, { ...node.attrs, [attr]: value });
1850
+ changed = true;
1851
+ }
1852
+ }
1853
+ if (changed) {
1854
+ tiptap.view?.dispatch(tr);
1855
+ }
1856
+ return changed;
1857
+ }
1858
+ function setSelectedCellsAttr(editor, attr, value) {
1859
+ return setCellAttrAtPositions(
1860
+ editor,
1861
+ getSelectedCellPositions(editor),
1862
+ attr,
1863
+ value
1864
+ );
1865
+ }
1866
+ function isInTableCell(editor) {
1867
+ return getSelectedCellPositions(editor).length > 0;
1868
+ }
1869
+ function getFirstSelectedCellAttr(editor, attr) {
1870
+ const tiptap = editor?._tiptapEditor;
1871
+ const positions = getSelectedCellPositions(editor);
1872
+ if (!tiptap || positions.length === 0) return void 0;
1873
+ const node = tiptap.state.doc.nodeAt(positions[0]);
1874
+ return node?.attrs?.[attr];
1875
+ }
1876
+
1877
+ // src/components/FloatingMenu/components/AlignButton.tsx
1817
1878
  import { jsx as jsx8 } from "react/jsx-runtime";
1818
1879
  var iconMap2 = {
1819
1880
  left: Icons.alignLeft,
@@ -1831,6 +1892,9 @@ var AlignButton = ({
1831
1892
  }) => {
1832
1893
  const getCurrentAlignment = () => {
1833
1894
  try {
1895
+ if (isInTableCell(editor)) {
1896
+ return getFirstSelectedCellAttr(editor, "textAlignment") || "left";
1897
+ }
1834
1898
  const block = editor?.getTextCursorPosition()?.block;
1835
1899
  return block?.props?.textAlignment || "left";
1836
1900
  } catch {
@@ -1840,6 +1904,9 @@ var AlignButton = ({
1840
1904
  const isActive = getCurrentAlignment() === alignment;
1841
1905
  const handleClick = useCallback6(() => {
1842
1906
  try {
1907
+ if (setSelectedCellsAttr(editor, "textAlignment", alignment)) {
1908
+ return;
1909
+ }
1843
1910
  const block = editor?.getTextCursorPosition()?.block;
1844
1911
  if (block && editor?.updateBlock) {
1845
1912
  editor.updateBlock(block, { props: { textAlignment: alignment } });
@@ -2005,6 +2072,10 @@ var ColorButton = ({ editor, type }) => {
2005
2072
  const colors = type === "text" ? TEXT_COLORS : BACKGROUND_COLORS;
2006
2073
  const getCurrentColor = useCallback9(() => {
2007
2074
  try {
2075
+ if (isInTableCell(editor)) {
2076
+ const attr = type === "text" ? "textColor" : "backgroundColor";
2077
+ return getFirstSelectedCellAttr(editor, attr) || "default";
2078
+ }
2008
2079
  const activeStyles = editor?.getActiveStyles?.() || {};
2009
2080
  if (type === "text" && activeStyles.textColor) {
2010
2081
  return activeStyles.textColor;
@@ -2034,13 +2105,15 @@ var ColorButton = ({ editor, type }) => {
2034
2105
  (color) => {
2035
2106
  try {
2036
2107
  if (!editor) return;
2037
- if (type === "text") {
2038
- editor.toggleStyles({ textColor: color });
2039
- } else {
2040
- editor.toggleStyles({ backgroundColor: color });
2108
+ const attr = type === "text" ? "textColor" : "backgroundColor";
2109
+ if (!setSelectedCellsAttr(editor, attr, color)) {
2110
+ editor.toggleStyles(
2111
+ type === "text" ? { textColor: color } : { backgroundColor: color }
2112
+ );
2041
2113
  }
2042
2114
  setCurrentColor(color);
2043
2115
  setIsOpen(false);
2116
+ setTimeout(() => editor.focus?.());
2044
2117
  } catch (err) {
2045
2118
  console.error(`Color apply failed:`, err);
2046
2119
  }
@@ -2847,7 +2920,6 @@ var VerticalAlignmentExtension = Extension.create({
2847
2920
  import {
2848
2921
  BasicTextStyleButton,
2849
2922
  BlockTypeSelect as BlockTypeSelect2,
2850
- ColorStyleButton,
2851
2923
  CreateLinkButton,
2852
2924
  FileCaptionButton,
2853
2925
  FileDeleteButton,
@@ -2873,31 +2945,6 @@ import {
2873
2945
  useBlockNoteEditor,
2874
2946
  useSelectedBlocks
2875
2947
  } from "@blocknote/react";
2876
-
2877
- // src/utils/prosemirror-table-utils.ts
2878
- function getSelectedCellPositions(editor) {
2879
- const tiptap = editor._tiptapEditor;
2880
- if (!tiptap) return [];
2881
- const { state } = tiptap;
2882
- const { selection } = state;
2883
- if (typeof selection.forEachCell === "function") {
2884
- const positions = [];
2885
- selection.forEachCell((_node, pos) => {
2886
- positions.push(pos);
2887
- });
2888
- return positions;
2889
- }
2890
- const $pos = selection.$from;
2891
- for (let depth = $pos.depth; depth > 0; depth--) {
2892
- const node = $pos.node(depth);
2893
- if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
2894
- return [$pos.before(depth)];
2895
- }
2896
- }
2897
- return [];
2898
- }
2899
-
2900
- // src/components/TextAlignButtonWithVA.tsx
2901
2948
  import { jsx as jsx17 } from "react/jsx-runtime";
2902
2949
  var icons = {
2903
2950
  left: /* @__PURE__ */ jsx17("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx17("path", { d: "M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z" }) }),
@@ -3078,77 +3125,782 @@ var VerticalAlignButton = (props) => {
3078
3125
  );
3079
3126
  };
3080
3127
 
3128
+ // src/components/color/LumirColorControls.tsx
3129
+ import {
3130
+ isTableCell,
3131
+ mapTableCell as mapTableCell2
3132
+ } from "@blocknote/core";
3133
+ import {
3134
+ SplitButton,
3135
+ useBlockNoteEditor as useBlockNoteEditor3,
3136
+ useComponentsContext as useComponentsContext3,
3137
+ useDictionary,
3138
+ useEditorContentOrSelectionChange,
3139
+ useSelectedBlocks as useSelectedBlocks3
3140
+ } from "@blocknote/react";
3141
+ import { useCallback as useCallback16, useMemo as useMemo3, useRef as useRef9, useState as useState8 } from "react";
3142
+ import { Fragment as Fragment5, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
3143
+ var COLORS = [
3144
+ "default",
3145
+ "gray",
3146
+ "brown",
3147
+ "red",
3148
+ "orange",
3149
+ "yellow",
3150
+ "green",
3151
+ "blue",
3152
+ "purple",
3153
+ "pink"
3154
+ ];
3155
+ function ColorIcon(props) {
3156
+ const textColor = props.textColor || "default";
3157
+ const backgroundColor = props.backgroundColor || "default";
3158
+ const size = props.size || 16;
3159
+ return /* @__PURE__ */ jsx19(
3160
+ "div",
3161
+ {
3162
+ className: "bn-color-icon",
3163
+ "data-background-color": backgroundColor,
3164
+ "data-text-color": textColor,
3165
+ style: {
3166
+ pointerEvents: "none",
3167
+ fontSize: `${size * 0.75}px`,
3168
+ height: `${size}px`,
3169
+ lineHeight: `${size}px`,
3170
+ textAlign: "center",
3171
+ width: `${size}px`
3172
+ },
3173
+ children: "A"
3174
+ }
3175
+ );
3176
+ }
3177
+ function CellFillIcon({ size = 18 }) {
3178
+ return /* @__PURE__ */ jsx19(
3179
+ "svg",
3180
+ {
3181
+ width: size,
3182
+ height: size,
3183
+ viewBox: "0 0 24 24",
3184
+ fill: "currentColor",
3185
+ style: { pointerEvents: "none" },
3186
+ "aria-hidden": "true",
3187
+ children: /* @__PURE__ */ jsx19("path", { d: "M16.56 8.94 7.62 0 6.21 1.41l2.38 2.38-5.15 5.15c-.59.59-.59 1.54 0 2.12l5.5 5.5c.29.29.68.44 1.06.44s.77-.15 1.06-.44l5.5-5.5c.59-.58.59-1.53 0-2.12zM5.21 10 10 5.21 14.79 10H5.21zM19 11.5s-2 2.17-2 3.5c0 1.1.9 2 2 2s2-.9 2-2c0-1.33-2-3.5-2-3.5z" })
3188
+ }
3189
+ );
3190
+ }
3191
+ function LumirColorPicker(props) {
3192
+ const Components = useComponentsContext3();
3193
+ const dict = useDictionary();
3194
+ return /* @__PURE__ */ jsxs12(Fragment5, { children: [
3195
+ props.text ? /* @__PURE__ */ jsxs12(Fragment5, { children: [
3196
+ /* @__PURE__ */ jsx19(Components.Generic.Menu.Label, { children: props.textTitle ?? dict.color_picker.text_title }),
3197
+ COLORS.map((color) => /* @__PURE__ */ jsx19(
3198
+ Components.Generic.Menu.Item,
3199
+ {
3200
+ onClick: () => {
3201
+ props.onClick?.();
3202
+ props.text.setColor(color);
3203
+ },
3204
+ "data-test": "text-color-" + color,
3205
+ icon: /* @__PURE__ */ jsx19(ColorIcon, { textColor: color, size: props.iconSize }),
3206
+ checked: props.text.color === color,
3207
+ children: dict.color_picker.colors[color]
3208
+ },
3209
+ "text-color-" + color
3210
+ ))
3211
+ ] }) : null,
3212
+ props.background ? /* @__PURE__ */ jsxs12(Fragment5, { children: [
3213
+ /* @__PURE__ */ jsx19(Components.Generic.Menu.Label, { children: props.backgroundTitle ?? dict.color_picker.background_title }),
3214
+ COLORS.map((color) => /* @__PURE__ */ jsx19(
3215
+ Components.Generic.Menu.Item,
3216
+ {
3217
+ onClick: () => {
3218
+ props.onClick?.();
3219
+ props.background.setColor(color);
3220
+ },
3221
+ "data-test": "background-color-" + color,
3222
+ icon: /* @__PURE__ */ jsx19(ColorIcon, { backgroundColor: color, size: props.iconSize }),
3223
+ checked: props.background.color === color,
3224
+ children: dict.color_picker.colors[color]
3225
+ },
3226
+ "background-color-" + color
3227
+ ))
3228
+ ] }) : null
3229
+ ] });
3230
+ }
3231
+ function LumirColorStyleButton() {
3232
+ const Components = useComponentsContext3();
3233
+ const editor = useBlockNoteEditor3();
3234
+ const ed = editor;
3235
+ const styleSchema = editor.schema.styleSchema;
3236
+ const textColorInSchema = styleSchema.textColor?.type === "textColor" && styleSchema.textColor?.propSchema === "string";
3237
+ const backgroundColorInSchema = styleSchema.backgroundColor?.type === "backgroundColor" && styleSchema.backgroundColor?.propSchema === "string";
3238
+ const selectedBlocks = useSelectedBlocks3(editor);
3239
+ const [currentTextColor, setCurrentTextColor] = useState8(
3240
+ textColorInSchema ? ed.getActiveStyles().textColor || "default" : "default"
3241
+ );
3242
+ const [currentBackgroundColor, setCurrentBackgroundColor] = useState8(
3243
+ backgroundColorInSchema ? ed.getActiveStyles().backgroundColor || "default" : "default"
3244
+ );
3245
+ useEditorContentOrSelectionChange(() => {
3246
+ const active = ed.getActiveStyles();
3247
+ if (textColorInSchema) {
3248
+ setCurrentTextColor(active.textColor || "default");
3249
+ }
3250
+ if (backgroundColorInSchema) {
3251
+ setCurrentBackgroundColor(active.backgroundColor || "default");
3252
+ }
3253
+ }, editor);
3254
+ const setTextColor = useCallback16(
3255
+ (color) => {
3256
+ color === "default" ? ed.removeStyles({ textColor: color }) : ed.addStyles({ textColor: color });
3257
+ setTimeout(() => editor.focus());
3258
+ },
3259
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3260
+ [editor]
3261
+ );
3262
+ const setBackgroundColor = useCallback16(
3263
+ (color) => {
3264
+ color === "default" ? ed.removeStyles({ backgroundColor: color }) : ed.addStyles({ backgroundColor: color });
3265
+ setTimeout(() => editor.focus());
3266
+ },
3267
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3268
+ [editor]
3269
+ );
3270
+ const show = useMemo3(() => {
3271
+ if (!textColorInSchema && !backgroundColorInSchema) {
3272
+ return false;
3273
+ }
3274
+ for (const block of selectedBlocks) {
3275
+ if (block.content !== void 0) {
3276
+ return true;
3277
+ }
3278
+ }
3279
+ return false;
3280
+ }, [backgroundColorInSchema, selectedBlocks, textColorInSchema]);
3281
+ if (!show || !editor.isEditable) {
3282
+ return null;
3283
+ }
3284
+ const tooltip = "\uD14D\uC2A4\uD2B8 \uC0C9\xB7\uBC30\uACBD";
3285
+ return /* @__PURE__ */ jsxs12(Components.Generic.Menu.Root, { children: [
3286
+ /* @__PURE__ */ jsx19(Components.Generic.Menu.Trigger, { children: /* @__PURE__ */ jsx19(
3287
+ Components.FormattingToolbar.Button,
3288
+ {
3289
+ className: "bn-button",
3290
+ "data-test": "colors",
3291
+ label: tooltip,
3292
+ mainTooltip: tooltip,
3293
+ icon: /* @__PURE__ */ jsx19(
3294
+ ColorIcon,
3295
+ {
3296
+ textColor: currentTextColor,
3297
+ backgroundColor: currentBackgroundColor,
3298
+ size: 20
3299
+ }
3300
+ )
3301
+ }
3302
+ ) }),
3303
+ /* @__PURE__ */ jsx19(
3304
+ Components.Generic.Menu.Dropdown,
3305
+ {
3306
+ className: "bn-menu-dropdown bn-color-picker-dropdown",
3307
+ children: /* @__PURE__ */ jsx19(
3308
+ LumirColorPicker,
3309
+ {
3310
+ textTitle: "\uD14D\uC2A4\uD2B8 \uC0C9",
3311
+ backgroundTitle: "\uD14D\uC2A4\uD2B8 \uBC30\uACBD",
3312
+ text: textColorInSchema ? { color: currentTextColor, setColor: setTextColor } : void 0,
3313
+ background: backgroundColorInSchema ? { color: currentBackgroundColor, setColor: setBackgroundColor } : void 0
3314
+ }
3315
+ )
3316
+ }
3317
+ )
3318
+ ] });
3319
+ }
3320
+ function LumirCellColorToolbarButton() {
3321
+ const Components = useComponentsContext3();
3322
+ const editor = useBlockNoteEditor3();
3323
+ const selectedBlocks = useSelectedBlocks3(editor);
3324
+ const isMultiCell = useMemo3(() => {
3325
+ if (selectedBlocks.length !== 1 || selectedBlocks[0].type !== "table") {
3326
+ return false;
3327
+ }
3328
+ const cs = editor.tableHandles?.getCellSelection();
3329
+ return !!cs && cs.cells.length > 1;
3330
+ }, [editor, selectedBlocks]);
3331
+ const stashRef = useRef9([]);
3332
+ const applyBackground = useCallback16(
3333
+ (color) => {
3334
+ const live = getSelectedCellPositions(editor);
3335
+ const positions = live.length > 0 ? live : stashRef.current;
3336
+ setCellAttrAtPositions(editor, positions, "backgroundColor", color);
3337
+ setTimeout(() => editor.focus());
3338
+ },
3339
+ [editor]
3340
+ );
3341
+ if (!editor.isEditable || !isMultiCell) {
3342
+ return null;
3343
+ }
3344
+ const tooltip = "\uC140 \uBC30\uACBD\uC0C9";
3345
+ return /* @__PURE__ */ jsxs12(
3346
+ Components.Generic.Menu.Root,
3347
+ {
3348
+ onOpenChange: (open) => {
3349
+ if (open) {
3350
+ stashRef.current = getSelectedCellPositions(editor);
3351
+ }
3352
+ },
3353
+ children: [
3354
+ /* @__PURE__ */ jsx19(Components.Generic.Menu.Trigger, { children: /* @__PURE__ */ jsx19(
3355
+ Components.FormattingToolbar.Button,
3356
+ {
3357
+ className: "bn-button",
3358
+ "data-test": "cell-colors",
3359
+ label: tooltip,
3360
+ mainTooltip: tooltip,
3361
+ icon: /* @__PURE__ */ jsx19(CellFillIcon, { size: 18 })
3362
+ }
3363
+ ) }),
3364
+ /* @__PURE__ */ jsx19(
3365
+ Components.Generic.Menu.Dropdown,
3366
+ {
3367
+ className: "bn-menu-dropdown bn-color-picker-dropdown",
3368
+ children: /* @__PURE__ */ jsx19(
3369
+ LumirColorPicker,
3370
+ {
3371
+ backgroundTitle: "\uC140 \uBC30\uACBD",
3372
+ background: { color: "default", setColor: applyBackground }
3373
+ }
3374
+ )
3375
+ }
3376
+ )
3377
+ ]
3378
+ }
3379
+ );
3380
+ }
3381
+ function LumirCellColorPickerButton(props) {
3382
+ const Components = useComponentsContext3();
3383
+ const editor = useBlockNoteEditor3();
3384
+ const updateColor = (color, type) => {
3385
+ const newTable = props.block.content.rows.map((row) => ({
3386
+ ...row,
3387
+ cells: row.cells.map((cell) => mapTableCell2(cell))
3388
+ }));
3389
+ if (type === "text") {
3390
+ newTable[props.rowIndex].cells[props.colIndex].props.textColor = color;
3391
+ } else {
3392
+ newTable[props.rowIndex].cells[props.colIndex].props.backgroundColor = color;
3393
+ }
3394
+ editor.updateBlock(props.block, {
3395
+ type: "table",
3396
+ content: { ...props.block.content, rows: newTable }
3397
+ });
3398
+ editor.setTextCursorPosition(props.block);
3399
+ };
3400
+ const currentCell = props.block.content.rows[props.rowIndex]?.cells?.[props.colIndex];
3401
+ if (!currentCell || editor.settings.tables.cellTextColor === false && editor.settings.tables.cellBackgroundColor === false) {
3402
+ return null;
3403
+ }
3404
+ return /* @__PURE__ */ jsxs12(Components.Generic.Menu.Root, { position: "right", sub: true, children: [
3405
+ /* @__PURE__ */ jsx19(Components.Generic.Menu.Trigger, { sub: true, children: /* @__PURE__ */ jsx19(Components.Generic.Menu.Item, { className: "bn-menu-item", subTrigger: true, children: "\uC140 \uC0C9\xB7\uBC30\uACBD" }) }),
3406
+ /* @__PURE__ */ jsx19(
3407
+ Components.Generic.Menu.Dropdown,
3408
+ {
3409
+ sub: true,
3410
+ className: "bn-menu-dropdown bn-color-picker-dropdown",
3411
+ children: /* @__PURE__ */ jsx19(
3412
+ LumirColorPicker,
3413
+ {
3414
+ iconSize: 18,
3415
+ textTitle: "\uC140 \uAE00\uC790\uC0C9",
3416
+ backgroundTitle: "\uC140 \uBC30\uACBD",
3417
+ text: editor.settings.tables.cellTextColor ? {
3418
+ color: isTableCell(currentCell) ? currentCell.props.textColor : "default",
3419
+ setColor: (color) => updateColor(color, "text")
3420
+ } : void 0,
3421
+ background: editor.settings.tables.cellBackgroundColor ? {
3422
+ color: isTableCell(currentCell) ? currentCell.props.backgroundColor : "default",
3423
+ setColor: (color) => updateColor(color, "background")
3424
+ } : void 0
3425
+ }
3426
+ )
3427
+ }
3428
+ )
3429
+ ] });
3430
+ }
3431
+ function LumirTableCellMenu(props) {
3432
+ const Components = useComponentsContext3();
3433
+ return /* @__PURE__ */ jsx19(
3434
+ Components.Generic.Menu.Dropdown,
3435
+ {
3436
+ className: "bn-menu-dropdown bn-drag-handle-menu",
3437
+ children: props.children || /* @__PURE__ */ jsxs12(Fragment5, { children: [
3438
+ /* @__PURE__ */ jsx19(
3439
+ SplitButton,
3440
+ {
3441
+ block: props.block,
3442
+ rowIndex: props.rowIndex,
3443
+ colIndex: props.colIndex
3444
+ }
3445
+ ),
3446
+ /* @__PURE__ */ jsx19(
3447
+ LumirCellColorPickerButton,
3448
+ {
3449
+ block: props.block,
3450
+ rowIndex: props.rowIndex,
3451
+ colIndex: props.colIndex
3452
+ }
3453
+ )
3454
+ ] })
3455
+ }
3456
+ );
3457
+ }
3458
+
3081
3459
  // src/components/CustomFormattingToolbar.tsx
3082
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
3460
+ import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
3083
3461
  var CustomFormattingToolbar = () => {
3084
- return /* @__PURE__ */ jsxs12(FormattingToolbar, { children: [
3085
- /* @__PURE__ */ jsx19(BlockTypeSelect2, {}, "blockTypeSelect"),
3086
- /* @__PURE__ */ jsx19(TableCellMergeButton, {}, "tableCellMergeButton"),
3087
- /* @__PURE__ */ jsx19(FileCaptionButton, {}, "fileCaptionButton"),
3088
- /* @__PURE__ */ jsx19(FileReplaceButton, {}, "replaceFileButton"),
3089
- /* @__PURE__ */ jsx19(FileRenameButton, {}, "fileRenameButton"),
3090
- /* @__PURE__ */ jsx19(FileDeleteButton, {}, "fileDeleteButton"),
3091
- /* @__PURE__ */ jsx19(FileDownloadButton, {}, "fileDownloadButton"),
3092
- /* @__PURE__ */ jsx19(FilePreviewButton, {}, "filePreviewButton"),
3093
- /* @__PURE__ */ jsx19(BasicTextStyleButton, { basicTextStyle: "bold" }, "boldStyleButton"),
3094
- /* @__PURE__ */ jsx19(
3462
+ return /* @__PURE__ */ jsxs13(FormattingToolbar, { children: [
3463
+ /* @__PURE__ */ jsx20(BlockTypeSelect2, {}, "blockTypeSelect"),
3464
+ /* @__PURE__ */ jsx20(TableCellMergeButton, {}, "tableCellMergeButton"),
3465
+ /* @__PURE__ */ jsx20(FileCaptionButton, {}, "fileCaptionButton"),
3466
+ /* @__PURE__ */ jsx20(FileReplaceButton, {}, "replaceFileButton"),
3467
+ /* @__PURE__ */ jsx20(FileRenameButton, {}, "fileRenameButton"),
3468
+ /* @__PURE__ */ jsx20(FileDeleteButton, {}, "fileDeleteButton"),
3469
+ /* @__PURE__ */ jsx20(FileDownloadButton, {}, "fileDownloadButton"),
3470
+ /* @__PURE__ */ jsx20(FilePreviewButton, {}, "filePreviewButton"),
3471
+ /* @__PURE__ */ jsx20(BasicTextStyleButton, { basicTextStyle: "bold" }, "boldStyleButton"),
3472
+ /* @__PURE__ */ jsx20(
3095
3473
  BasicTextStyleButton,
3096
3474
  {
3097
3475
  basicTextStyle: "italic"
3098
3476
  },
3099
3477
  "italicStyleButton"
3100
3478
  ),
3101
- /* @__PURE__ */ jsx19(
3479
+ /* @__PURE__ */ jsx20(
3102
3480
  BasicTextStyleButton,
3103
3481
  {
3104
3482
  basicTextStyle: "underline"
3105
3483
  },
3106
3484
  "underlineStyleButton"
3107
3485
  ),
3108
- /* @__PURE__ */ jsx19(
3486
+ /* @__PURE__ */ jsx20(
3109
3487
  BasicTextStyleButton,
3110
3488
  {
3111
3489
  basicTextStyle: "strike"
3112
3490
  },
3113
3491
  "strikeStyleButton"
3114
3492
  ),
3115
- /* @__PURE__ */ jsx19(TextAlignButtonWithVA, { textAlignment: "left" }, "textAlignLeftButton"),
3116
- /* @__PURE__ */ jsx19(
3493
+ /* @__PURE__ */ jsx20(TextAlignButtonWithVA, { textAlignment: "left" }, "textAlignLeftButton"),
3494
+ /* @__PURE__ */ jsx20(
3117
3495
  TextAlignButtonWithVA,
3118
3496
  {
3119
3497
  textAlignment: "center"
3120
3498
  },
3121
3499
  "textAlignCenterButton"
3122
3500
  ),
3123
- /* @__PURE__ */ jsx19(TextAlignButtonWithVA, { textAlignment: "right" }, "textAlignRightButton"),
3124
- /* @__PURE__ */ jsx19(
3501
+ /* @__PURE__ */ jsx20(TextAlignButtonWithVA, { textAlignment: "right" }, "textAlignRightButton"),
3502
+ /* @__PURE__ */ jsx20(
3125
3503
  VerticalAlignButton,
3126
3504
  {
3127
3505
  verticalAlignment: "top"
3128
3506
  },
3129
3507
  "verticalAlignTop"
3130
3508
  ),
3131
- /* @__PURE__ */ jsx19(
3509
+ /* @__PURE__ */ jsx20(
3132
3510
  VerticalAlignButton,
3133
3511
  {
3134
3512
  verticalAlignment: "middle"
3135
3513
  },
3136
3514
  "verticalAlignMiddle"
3137
3515
  ),
3138
- /* @__PURE__ */ jsx19(
3516
+ /* @__PURE__ */ jsx20(
3139
3517
  VerticalAlignButton,
3140
3518
  {
3141
3519
  verticalAlignment: "bottom"
3142
3520
  },
3143
3521
  "verticalAlignBottom"
3144
3522
  ),
3145
- /* @__PURE__ */ jsx19(ColorStyleButton, {}, "colorStyleButton"),
3146
- /* @__PURE__ */ jsx19(NestBlockButton, {}, "nestBlockButton"),
3147
- /* @__PURE__ */ jsx19(UnnestBlockButton, {}, "unnestBlockButton"),
3148
- /* @__PURE__ */ jsx19(CreateLinkButton, {}, "createLinkButton")
3523
+ /* @__PURE__ */ jsx20(LumirColorStyleButton, {}, "colorStyleButton"),
3524
+ /* @__PURE__ */ jsx20(LumirCellColorToolbarButton, {}, "cellColorButton"),
3525
+ /* @__PURE__ */ jsx20(NestBlockButton, {}, "nestBlockButton"),
3526
+ /* @__PURE__ */ jsx20(UnnestBlockButton, {}, "unnestBlockButton"),
3527
+ /* @__PURE__ */ jsx20(CreateLinkButton, {}, "createLinkButton")
3149
3528
  ] });
3150
3529
  };
3151
3530
 
3531
+ // src/components/LumirTableHandlesController.tsx
3532
+ import {
3533
+ ExtendButton,
3534
+ TableCellButton,
3535
+ TableHandle,
3536
+ useBlockNoteEditor as useBlockNoteEditor4,
3537
+ useEditorContentOrSelectionChange as useEditorContentOrSelectionChange2,
3538
+ useExtendButtonsPositioning,
3539
+ useUIPluginState
3540
+ } from "@blocknote/react";
3541
+ import { autoUpdate as autoUpdate2, FloatingPortal } from "@floating-ui/react";
3542
+ import { useCallback as useCallback17, useEffect as useEffect9, useMemo as useMemo5, useRef as useRef10, useState as useState9 } from "react";
3543
+
3544
+ // src/components/hooks/useFocusedCellHandlePositioning.ts
3545
+ import {
3546
+ autoUpdate,
3547
+ offset,
3548
+ useFloating,
3549
+ useTransitionStyles
3550
+ } from "@floating-ui/react";
3551
+ import { useEffect as useEffect8, useMemo as useMemo4 } from "react";
3552
+ function useFocusedCellHandlePositioning(cellEl, tbodyEl, orientation, show) {
3553
+ const { refs, floatingStyles, context } = useFloating({
3554
+ open: show,
3555
+ placement: orientation === "row" ? "left" : orientation === "col" ? "top" : "right",
3556
+ // col/row: 가장자리 선(zero-size)에, cell: 셀 우측 보더에 14px hit-area 중앙 정렬(-7).
3557
+ middleware: [offset(-7)],
3558
+ whileElementsMounted: autoUpdate
3559
+ });
3560
+ const { isMounted, styles } = useTransitionStyles(context);
3561
+ useEffect8(() => {
3562
+ if (!cellEl) {
3563
+ refs.setReference(null);
3564
+ return;
3565
+ }
3566
+ refs.setReference({
3567
+ contextElement: cellEl,
3568
+ getBoundingClientRect: () => {
3569
+ const c = cellEl.getBoundingClientRect();
3570
+ const t = tbodyEl?.getBoundingClientRect() ?? c;
3571
+ if (orientation === "col") {
3572
+ return new DOMRect(c.left, t.top, c.width, 0);
3573
+ }
3574
+ if (orientation === "row") {
3575
+ return new DOMRect(t.left, c.top, 0, c.height);
3576
+ }
3577
+ return c;
3578
+ }
3579
+ });
3580
+ }, [cellEl, tbodyEl, orientation, refs]);
3581
+ return useMemo4(
3582
+ () => ({
3583
+ isMounted,
3584
+ ref: refs.setFloating,
3585
+ // display는 CSS 클래스에서 제어한다(absolute 자식 stacking).
3586
+ style: {
3587
+ ...styles,
3588
+ ...floatingStyles
3589
+ }
3590
+ }),
3591
+ [floatingStyles, isMounted, refs.setFloating, styles]
3592
+ );
3593
+ }
3594
+
3595
+ // src/components/LumirTableHandlesController.tsx
3596
+ import { Fragment as Fragment6, jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
3597
+ function syncCoreHoverToFocusedCell(cellEl) {
3598
+ const r = cellEl.getBoundingClientRect();
3599
+ cellEl.dispatchEvent(
3600
+ new MouseEvent("mousemove", {
3601
+ bubbles: true,
3602
+ cancelable: true,
3603
+ view: window,
3604
+ clientX: r.left + r.width / 2,
3605
+ clientY: r.top + r.height / 2
3606
+ })
3607
+ );
3608
+ }
3609
+ function LumirTableHandlesController() {
3610
+ const editor = useBlockNoteEditor4();
3611
+ const [focused, setFocused] = useState9(null);
3612
+ const [menuContainerRef, setMenuContainerRef] = useState9(null);
3613
+ const [overlayEl, setOverlayEl] = useState9(null);
3614
+ const [openMenu, setOpenMenu] = useState9(null);
3615
+ const frozenRef = useRef10(false);
3616
+ const menuOpenRef = useRef10(false);
3617
+ const draggingRef = useRef10(false);
3618
+ const recompute = useCallback17(() => {
3619
+ if (frozenRef.current) {
3620
+ return;
3621
+ }
3622
+ const th2 = editor.tableHandles;
3623
+ const view = editor.prosemirrorView;
3624
+ if (!th2 || !view) {
3625
+ setFocused(null);
3626
+ return;
3627
+ }
3628
+ let cellEl2 = null;
3629
+ try {
3630
+ const { node } = view.domAtPos(view.state.selection.from);
3631
+ const el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
3632
+ cellEl2 = el?.closest?.("td, th") ?? null;
3633
+ } catch {
3634
+ cellEl2 = null;
3635
+ }
3636
+ if (!cellEl2 || !cellEl2.isConnected) {
3637
+ setFocused(null);
3638
+ return;
3639
+ }
3640
+ const blockId = cellEl2.closest("[data-id]")?.getAttribute("data-id");
3641
+ const block = blockId ? editor.getBlock(blockId) : void 0;
3642
+ if (!block || block.type !== "table") {
3643
+ setFocused(null);
3644
+ return;
3645
+ }
3646
+ const cellSel = th2.getCellSelection();
3647
+ if (!cellSel) {
3648
+ setFocused(null);
3649
+ return;
3650
+ }
3651
+ const widgetContainer = cellEl2.closest(".tableWrapper")?.querySelector(".table-widgets-container");
3652
+ const tbodyEl2 = cellEl2.closest("tbody");
3653
+ if (!widgetContainer || !tbodyEl2) {
3654
+ setFocused(null);
3655
+ return;
3656
+ }
3657
+ setFocused({
3658
+ block,
3659
+ rowIndex: cellSel.from.row,
3660
+ colIndex: cellSel.from.col,
3661
+ cellEl: cellEl2,
3662
+ tbodyEl: tbodyEl2,
3663
+ widgetContainer
3664
+ });
3665
+ }, [editor]);
3666
+ useEditorContentOrSelectionChange2(recompute, editor);
3667
+ useEffect9(() => {
3668
+ recompute();
3669
+ }, [recompute]);
3670
+ useEffect9(() => {
3671
+ const onUp = () => {
3672
+ requestAnimationFrame(() => {
3673
+ if (!menuOpenRef.current && !draggingRef.current && frozenRef.current) {
3674
+ frozenRef.current = false;
3675
+ recompute();
3676
+ }
3677
+ });
3678
+ };
3679
+ window.addEventListener("pointerup", onUp);
3680
+ return () => window.removeEventListener("pointerup", onUp);
3681
+ }, [recompute]);
3682
+ useEffect9(() => {
3683
+ const f = focused;
3684
+ if (!f || !overlayEl) {
3685
+ return;
3686
+ }
3687
+ const PAD = 1;
3688
+ const update = () => {
3689
+ const cr = f.cellEl.getBoundingClientRect();
3690
+ const tr = f.tbodyEl.getBoundingClientRect();
3691
+ const kr = f.widgetContainer.getBoundingClientRect();
3692
+ const x1 = openMenu === "row" ? tr.left : cr.left;
3693
+ const y1 = openMenu === "col" ? tr.top : cr.top;
3694
+ const x2 = openMenu === "row" ? tr.right : cr.right;
3695
+ const y2 = openMenu === "col" ? tr.bottom : cr.bottom;
3696
+ const dpr = window.devicePixelRatio || 1;
3697
+ const rd = (v) => Math.round(v * dpr) / dpr;
3698
+ const left = rd(x1 - kr.left) - PAD;
3699
+ const top = rd(y1 - kr.top) - PAD;
3700
+ const right = rd(x2 - kr.left) + PAD;
3701
+ const bottom = rd(y2 - kr.top) + PAD;
3702
+ overlayEl.style.transform = `translate(${left}px, ${top}px)`;
3703
+ overlayEl.style.width = `${right - left}px`;
3704
+ overlayEl.style.height = `${bottom - top}px`;
3705
+ };
3706
+ update();
3707
+ return autoUpdate2(f.cellEl, overlayEl, update);
3708
+ }, [focused, overlayEl, openMenu]);
3709
+ const cellEl = focused?.cellEl ?? null;
3710
+ const tbodyEl = focused?.tbodyEl ?? null;
3711
+ const show = focused !== null;
3712
+ const rowHandle = useFocusedCellHandlePositioning(cellEl, tbodyEl, "row", show);
3713
+ const colHandle = useFocusedCellHandlePositioning(cellEl, tbodyEl, "col", show);
3714
+ const cellHandle = useFocusedCellHandlePositioning(
3715
+ cellEl,
3716
+ tbodyEl,
3717
+ "cell",
3718
+ show
3719
+ );
3720
+ const th = editor.tableHandles;
3721
+ const coreState = useUIPluginState(
3722
+ editor.tableHandles.onUpdate.bind(editor.tableHandles)
3723
+ );
3724
+ const { addOrRemoveColumnsButton, addOrRemoveRowsButton } = useExtendButtonsPositioning(
3725
+ coreState?.showAddOrRemoveColumnsButton || false,
3726
+ coreState?.showAddOrRemoveRowsButton || false,
3727
+ coreState?.referencePosTable || null
3728
+ );
3729
+ const onStartExtend = useCallback17(() => {
3730
+ editor.tableHandles?.freezeHandles();
3731
+ }, [editor]);
3732
+ const onEndExtend = useCallback17(() => {
3733
+ editor.tableHandles?.unfreezeHandles();
3734
+ }, [editor]);
3735
+ const menuHandlers = useMemo5(() => {
3736
+ const mk = (kind) => ({
3737
+ freeze: () => {
3738
+ menuOpenRef.current = true;
3739
+ frozenRef.current = true;
3740
+ setOpenMenu(kind);
3741
+ editor.tableHandles?.freezeHandles();
3742
+ },
3743
+ unfreeze: () => {
3744
+ menuOpenRef.current = false;
3745
+ frozenRef.current = false;
3746
+ setOpenMenu(null);
3747
+ editor.tableHandles?.unfreezeHandles();
3748
+ recompute();
3749
+ }
3750
+ });
3751
+ return { col: mk("col"), row: mk("row"), cell: mk("cell") };
3752
+ }, [editor, recompute]);
3753
+ const onGutterPointerDown = useCallback17(() => {
3754
+ frozenRef.current = true;
3755
+ }, []);
3756
+ const onGutterPointerEnter = useCallback17(
3757
+ (e) => {
3758
+ if (e.buttons === 0 && focused) {
3759
+ syncCoreHoverToFocusedCell(focused.cellEl);
3760
+ }
3761
+ },
3762
+ [focused]
3763
+ );
3764
+ const makeDragStart = useCallback17(
3765
+ (dir) => (e) => {
3766
+ draggingRef.current = true;
3767
+ frozenRef.current = true;
3768
+ if (dir === "row") {
3769
+ editor.tableHandles?.rowDragStart(e);
3770
+ } else {
3771
+ editor.tableHandles?.colDragStart(e);
3772
+ }
3773
+ },
3774
+ [editor]
3775
+ );
3776
+ const onDragEnd = useCallback17(() => {
3777
+ editor.tableHandles?.dragEnd();
3778
+ draggingRef.current = false;
3779
+ frozenRef.current = false;
3780
+ recompute();
3781
+ }, [editor, recompute]);
3782
+ const noop = useCallback17(() => {
3783
+ }, []);
3784
+ return /* @__PURE__ */ jsxs14(Fragment6, { children: [
3785
+ /* @__PURE__ */ jsx21("div", { ref: setMenuContainerRef }),
3786
+ th && focused && menuContainerRef && /* @__PURE__ */ jsxs14(FloatingPortal, { root: focused.widgetContainer, children: [
3787
+ /* @__PURE__ */ jsx21("div", { ref: setOverlayEl, className: "lumir-tbl-cell-focus" }),
3788
+ colHandle.isMounted && /* @__PURE__ */ jsxs14(
3789
+ "div",
3790
+ {
3791
+ ref: colHandle.ref,
3792
+ style: colHandle.style,
3793
+ className: "lumir-tbl-gutter-wrap lumir-tbl-gutter-wrap--col" + (openMenu === "col" ? " lumir-tbl-gutter-wrap--active" : ""),
3794
+ onPointerEnter: onGutterPointerEnter,
3795
+ onPointerDown: onGutterPointerDown,
3796
+ children: [
3797
+ /* @__PURE__ */ jsx21("span", { className: "lumir-tbl-gutter" }),
3798
+ /* @__PURE__ */ jsx21("div", { className: "lumir-tbl-grip", children: /* @__PURE__ */ jsx21(
3799
+ TableHandle,
3800
+ {
3801
+ editor,
3802
+ orientation: "column",
3803
+ index: focused.colIndex,
3804
+ block: focused.block,
3805
+ dragStart: makeDragStart("col"),
3806
+ dragEnd: onDragEnd,
3807
+ freezeHandles: menuHandlers.col.freeze,
3808
+ unfreezeHandles: menuHandlers.col.unfreeze,
3809
+ menuContainer: menuContainerRef,
3810
+ showOtherSide: noop,
3811
+ hideOtherSide: noop
3812
+ }
3813
+ ) })
3814
+ ]
3815
+ }
3816
+ ),
3817
+ rowHandle.isMounted && /* @__PURE__ */ jsxs14(
3818
+ "div",
3819
+ {
3820
+ ref: rowHandle.ref,
3821
+ style: rowHandle.style,
3822
+ className: "lumir-tbl-gutter-wrap lumir-tbl-gutter-wrap--row" + (openMenu === "row" ? " lumir-tbl-gutter-wrap--active" : ""),
3823
+ onPointerEnter: onGutterPointerEnter,
3824
+ onPointerDown: onGutterPointerDown,
3825
+ children: [
3826
+ /* @__PURE__ */ jsx21("span", { className: "lumir-tbl-gutter" }),
3827
+ /* @__PURE__ */ jsx21("div", { className: "lumir-tbl-grip", children: /* @__PURE__ */ jsx21(
3828
+ TableHandle,
3829
+ {
3830
+ editor,
3831
+ orientation: "row",
3832
+ index: focused.rowIndex,
3833
+ block: focused.block,
3834
+ dragStart: makeDragStart("row"),
3835
+ dragEnd: onDragEnd,
3836
+ freezeHandles: menuHandlers.row.freeze,
3837
+ unfreezeHandles: menuHandlers.row.unfreeze,
3838
+ menuContainer: menuContainerRef,
3839
+ showOtherSide: noop,
3840
+ hideOtherSide: noop
3841
+ }
3842
+ ) })
3843
+ ]
3844
+ }
3845
+ ),
3846
+ cellHandle.isMounted && openMenu !== "col" && openMenu !== "row" && /* @__PURE__ */ jsxs14(
3847
+ "div",
3848
+ {
3849
+ ref: cellHandle.ref,
3850
+ style: cellHandle.style,
3851
+ className: "lumir-tbl-gutter-wrap lumir-tbl-gutter-wrap--cell" + (openMenu === "cell" ? " lumir-tbl-gutter-wrap--active" : ""),
3852
+ onPointerDown: onGutterPointerDown,
3853
+ children: [
3854
+ /* @__PURE__ */ jsx21("span", { className: "lumir-tbl-gutter" }),
3855
+ /* @__PURE__ */ jsx21("div", { className: "lumir-tbl-grip", children: /* @__PURE__ */ jsx21(
3856
+ TableCellButton,
3857
+ {
3858
+ editor,
3859
+ rowIndex: focused.rowIndex,
3860
+ colIndex: focused.colIndex,
3861
+ block: focused.block,
3862
+ tableCellMenu: LumirTableCellMenu,
3863
+ menuContainer: menuContainerRef,
3864
+ freezeHandles: menuHandlers.cell.freeze,
3865
+ unfreezeHandles: menuHandlers.cell.unfreeze
3866
+ }
3867
+ ) })
3868
+ ]
3869
+ }
3870
+ )
3871
+ ] }),
3872
+ th && coreState?.widgetContainer && /* @__PURE__ */ jsxs14(FloatingPortal, { root: coreState.widgetContainer, children: [
3873
+ /* @__PURE__ */ jsx21("div", { ref: addOrRemoveRowsButton.ref, style: addOrRemoveRowsButton.style, children: /* @__PURE__ */ jsx21(
3874
+ ExtendButton,
3875
+ {
3876
+ editor,
3877
+ orientation: "addOrRemoveRows",
3878
+ block: coreState.block,
3879
+ onMouseDown: onStartExtend,
3880
+ onMouseUp: onEndExtend
3881
+ }
3882
+ ) }),
3883
+ /* @__PURE__ */ jsx21(
3884
+ "div",
3885
+ {
3886
+ ref: addOrRemoveColumnsButton.ref,
3887
+ style: addOrRemoveColumnsButton.style,
3888
+ children: /* @__PURE__ */ jsx21(
3889
+ ExtendButton,
3890
+ {
3891
+ editor,
3892
+ orientation: "addOrRemoveColumns",
3893
+ block: coreState.block,
3894
+ onMouseDown: onStartExtend,
3895
+ onMouseUp: onEndExtend
3896
+ }
3897
+ )
3898
+ }
3899
+ )
3900
+ ] })
3901
+ ] });
3902
+ }
3903
+
3152
3904
  // src/utils/table-vertical-alignment.ts
3153
3905
  function injectVerticalAlignment(blocks, editor) {
3154
3906
  const tiptap = editor?._tiptapEditor;
@@ -3231,6 +3983,235 @@ function patchBlocks(blocks, tableVAMap) {
3231
3983
  });
3232
3984
  }
3233
3985
 
3986
+ // src/utils/excel-paste.ts
3987
+ var NAMED_COLORS = {
3988
+ black: "#000000",
3989
+ white: "#ffffff",
3990
+ red: "#ff0000",
3991
+ green: "#008000",
3992
+ blue: "#0000ff",
3993
+ yellow: "#ffff00",
3994
+ orange: "#ffa500",
3995
+ purple: "#800080",
3996
+ gray: "#808080",
3997
+ grey: "#808080"
3998
+ };
3999
+ function parseCssColorToRgb(input) {
4000
+ if (!input) return null;
4001
+ const s = input.trim().toLowerCase();
4002
+ if (!s || s === "transparent" || s === "none" || s === "inherit") return null;
4003
+ let m = s.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);
4004
+ if (m) {
4005
+ let h = m[1];
4006
+ if (h.length === 3)
4007
+ h = h.split("").map((c) => c + c).join("");
4008
+ return [
4009
+ parseInt(h.slice(0, 2), 16),
4010
+ parseInt(h.slice(2, 4), 16),
4011
+ parseInt(h.slice(4, 6), 16)
4012
+ ];
4013
+ }
4014
+ m = s.match(/^rgba?\(([^)]+)\)$/);
4015
+ if (m) {
4016
+ const parts = m[1].split(",").map((x) => parseFloat(x.trim()));
4017
+ if (parts.length >= 3 && parts.slice(0, 3).every((n) => !isNaN(n))) {
4018
+ return [parts[0], parts[1], parts[2]];
4019
+ }
4020
+ return null;
4021
+ }
4022
+ if (NAMED_COLORS[s]) return parseCssColorToRgb(NAMED_COLORS[s]);
4023
+ return null;
4024
+ }
4025
+ function rgbToHsl([r, g, b]) {
4026
+ r /= 255;
4027
+ g /= 255;
4028
+ b /= 255;
4029
+ const max = Math.max(r, g, b);
4030
+ const min = Math.min(r, g, b);
4031
+ const l = (max + min) / 2;
4032
+ let h = 0;
4033
+ let s = 0;
4034
+ const d = max - min;
4035
+ if (d !== 0) {
4036
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
4037
+ switch (max) {
4038
+ case r:
4039
+ h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
4040
+ break;
4041
+ case g:
4042
+ h = ((b - r) / d + 2) * 60;
4043
+ break;
4044
+ default:
4045
+ h = ((r - g) / d + 4) * 60;
4046
+ break;
4047
+ }
4048
+ }
4049
+ return [h, s, l];
4050
+ }
4051
+ var HUE_REFERENCE = [
4052
+ { value: "red", hue: 0 },
4053
+ { value: "brown", hue: 17 },
4054
+ { value: "orange", hue: 30 },
4055
+ { value: "yellow", hue: 46 },
4056
+ { value: "green", hue: 150 },
4057
+ { value: "blue", hue: 197 },
4058
+ { value: "purple", hue: 262 },
4059
+ { value: "pink", hue: 324 }
4060
+ ];
4061
+ function hueDist(a, b) {
4062
+ const d = Math.abs(a - b) % 360;
4063
+ return d > 180 ? 360 - d : d;
4064
+ }
4065
+ function paletteValueFromRgb(rgb) {
4066
+ const [h, s, l] = rgbToHsl(rgb);
4067
+ if (s < 0.15) {
4068
+ if (l < 0.35) return "default";
4069
+ if (l > 0.85) return "default";
4070
+ return "gray";
4071
+ }
4072
+ let best = "gray";
4073
+ let bestDist = Infinity;
4074
+ for (const ref of HUE_REFERENCE) {
4075
+ const d = hueDist(h, ref.hue);
4076
+ if (d < bestDist) {
4077
+ bestDist = d;
4078
+ best = ref.value;
4079
+ }
4080
+ }
4081
+ return best;
4082
+ }
4083
+ function nearestTextColorValue(rgb) {
4084
+ return paletteValueFromRgb(rgb);
4085
+ }
4086
+ function nearestBackgroundColorValue(rgb) {
4087
+ const [, s, l] = rgbToHsl(rgb);
4088
+ if (s < 0.12 && l > 0.85) return "default";
4089
+ return paletteValueFromRgb(rgb);
4090
+ }
4091
+ function parseStyle(style) {
4092
+ const out = {};
4093
+ style.split(";").forEach((decl) => {
4094
+ const idx = decl.indexOf(":");
4095
+ if (idx === -1) return;
4096
+ const k = decl.slice(0, idx).trim().toLowerCase();
4097
+ const v = decl.slice(idx + 1).trim();
4098
+ if (k) out[k] = v;
4099
+ });
4100
+ return out;
4101
+ }
4102
+ function colorFromBackgroundShorthand(value) {
4103
+ if (!value) return null;
4104
+ const direct = parseCssColorToRgb(value);
4105
+ if (direct) return direct;
4106
+ for (const token of value.split(/\s+/)) {
4107
+ const rgb = parseCssColorToRgb(token);
4108
+ if (rgb) return rgb;
4109
+ }
4110
+ return null;
4111
+ }
4112
+ function isTransparentColor(css) {
4113
+ if (!css) return true;
4114
+ const s = css.trim().toLowerCase();
4115
+ if (s === "transparent" || s === "none") return true;
4116
+ const m = s.match(/^rgba?\(([^)]+)\)$/);
4117
+ if (m) {
4118
+ const p = m[1].split(",").map((x) => parseFloat(x.trim()));
4119
+ if (p.length >= 4 && p[3] === 0) return true;
4120
+ }
4121
+ return false;
4122
+ }
4123
+ function normalizeAlign(ta) {
4124
+ const v = (ta || "").trim().toLowerCase();
4125
+ if (v === "right" || v === "end") return "right";
4126
+ if (v === "center") return "center";
4127
+ if (v === "justify") return "justify";
4128
+ return "";
4129
+ }
4130
+ function applyCellFormatting(el, fmt) {
4131
+ if (fmt.bgRgb && !fmt.bgTransparent) {
4132
+ const v = nearestBackgroundColorValue(fmt.bgRgb);
4133
+ if (v !== "default") el.setAttribute("data-background-color", v);
4134
+ }
4135
+ if (fmt.colorRgb) {
4136
+ const v = nearestTextColorValue(fmt.colorRgb);
4137
+ if (v !== "default") el.setAttribute("data-text-color", v);
4138
+ }
4139
+ if (["right", "center", "justify"].includes(fmt.align)) {
4140
+ el.setAttribute("data-text-alignment", fmt.align);
4141
+ }
4142
+ if ((fmt.bold || fmt.italic || fmt.underline) && el.innerHTML.trim()) {
4143
+ let inner = el.innerHTML;
4144
+ if (fmt.underline) inner = `<u>${inner}</u>`;
4145
+ if (fmt.italic) inner = `<em>${inner}</em>`;
4146
+ if (fmt.bold) inner = `<strong>${inner}</strong>`;
4147
+ el.innerHTML = inner;
4148
+ }
4149
+ }
4150
+ function readComputedFormat(el) {
4151
+ const cs = getComputedStyle(el);
4152
+ const fw = cs.fontWeight;
4153
+ const fwNum = parseInt(fw, 10);
4154
+ const decoration = cs.textDecorationLine || cs.textDecoration || "";
4155
+ return {
4156
+ bgRgb: parseCssColorToRgb(cs.backgroundColor),
4157
+ bgTransparent: isTransparentColor(cs.backgroundColor),
4158
+ colorRgb: parseCssColorToRgb(cs.color),
4159
+ align: normalizeAlign(cs.textAlign),
4160
+ bold: fw === "bold" || fw === "bolder" || !isNaN(fwNum) && fwNum >= 600,
4161
+ italic: (cs.fontStyle || "").toLowerCase().includes("italic"),
4162
+ underline: decoration.toLowerCase().includes("underline")
4163
+ };
4164
+ }
4165
+ function readInlineFormat(el) {
4166
+ const sm = parseStyle(el.getAttribute("style") || "");
4167
+ const bgRaw = sm["background-color"] || sm["background"] || "";
4168
+ const bgRgb = colorFromBackgroundShorthand(bgRaw) || parseCssColorToRgb(el.getAttribute("bgcolor"));
4169
+ const fw = (sm["font-weight"] || "").toLowerCase();
4170
+ const decoration = sm["text-decoration"] || sm["text-decoration-line"] || "";
4171
+ return {
4172
+ bgRgb,
4173
+ bgTransparent: !bgRaw && !el.getAttribute("bgcolor"),
4174
+ colorRgb: parseCssColorToRgb(sm["color"]),
4175
+ align: normalizeAlign(sm["text-align"] || el.getAttribute("align")),
4176
+ bold: fw === "bold" || fw === "bolder" || parseInt(fw, 10) >= 600,
4177
+ italic: (sm["font-style"] || "").toLowerCase().includes("italic"),
4178
+ underline: decoration.toLowerCase().includes("underline")
4179
+ };
4180
+ }
4181
+ function normalizeExcelTableHtml(html) {
4182
+ if (!html || typeof DOMParser === "undefined") return html;
4183
+ try {
4184
+ const doc = new DOMParser().parseFromString(html, "text/html");
4185
+ doc.querySelectorAll("script").forEach((s) => s.remove());
4186
+ if (!doc.querySelector("table")) return html;
4187
+ if (typeof document !== "undefined" && document.body && typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.attachShadow === "function") {
4188
+ let host = null;
4189
+ try {
4190
+ host = document.createElement("div");
4191
+ host.setAttribute("aria-hidden", "true");
4192
+ host.style.cssText = "position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none";
4193
+ const shadow = host.attachShadow({ mode: "open" });
4194
+ const styles = Array.from(doc.querySelectorAll("style")).map((s) => s.outerHTML).join("");
4195
+ shadow.innerHTML = styles + doc.body.innerHTML;
4196
+ document.body.appendChild(host);
4197
+ shadow.querySelectorAll("td, th").forEach((node) => {
4198
+ applyCellFormatting(node, readComputedFormat(node));
4199
+ });
4200
+ const out = Array.from(shadow.querySelectorAll("table")).map((t) => t.outerHTML).join("");
4201
+ return out || html;
4202
+ } finally {
4203
+ if (host && host.parentNode) host.parentNode.removeChild(host);
4204
+ }
4205
+ }
4206
+ doc.querySelectorAll("td, th").forEach((node) => {
4207
+ applyCellFormatting(node, readInlineFormat(node));
4208
+ });
4209
+ return Array.from(doc.querySelectorAll("table")).map((t) => t.outerHTML).join("") || html;
4210
+ } catch {
4211
+ return html;
4212
+ }
4213
+ }
4214
+
3234
4215
  // src/constants/limits.ts
3235
4216
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
3236
4217
  var MAX_VIDEO_FILE_SIZE = 100 * 1024 * 1024;
@@ -3250,7 +4231,7 @@ var ALLOWED_VIDEO_EXTENSIONS = [
3250
4231
  ];
3251
4232
 
3252
4233
  // src/components/LumirEditor.tsx
3253
- import { Fragment as Fragment5, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
4234
+ import { Fragment as Fragment7, jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
3254
4235
  var DEBUG_LOG = (loc, msg, data) => {
3255
4236
  const p = fetch("http://127.0.0.1:7686/ingest/1f8ee1c5-0cf0-4ae7-91ed-5ea7ed17130a", {
3256
4237
  method: "POST",
@@ -3475,9 +4456,9 @@ var findBlockWithLink = (blocks, targetUrl) => {
3475
4456
  return null;
3476
4457
  };
3477
4458
  var ConvertToPreviewButton = ({ url }) => {
3478
- const editor = useBlockNoteEditor3();
3479
- const Components = useComponentsContext3();
3480
- return /* @__PURE__ */ jsx20(
4459
+ const editor = useBlockNoteEditor5();
4460
+ const Components = useComponentsContext4();
4461
+ return /* @__PURE__ */ jsx22(
3481
4462
  Components.LinkToolbar.Button,
3482
4463
  {
3483
4464
  className: "bn-button",
@@ -3496,29 +4477,29 @@ var ConvertToPreviewButton = ({ url }) => {
3496
4477
  console.error("Convert to link preview failed:", err);
3497
4478
  }
3498
4479
  },
3499
- icon: /* @__PURE__ */ jsxs13("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
3500
- /* @__PURE__ */ jsx20("rect", { x: "1", y: "3", width: "14", height: "10", rx: "2", stroke: "currentColor", strokeWidth: "1.5", fill: "none" }),
3501
- /* @__PURE__ */ jsx20("line", { x1: "1", y1: "9", x2: "15", y2: "9", stroke: "currentColor", strokeWidth: "1.5" }),
3502
- /* @__PURE__ */ jsx20("circle", { cx: "5", cy: "6.5", r: "1.5", stroke: "currentColor", strokeWidth: "1", fill: "none" })
4480
+ icon: /* @__PURE__ */ jsxs15("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
4481
+ /* @__PURE__ */ jsx22("rect", { x: "1", y: "3", width: "14", height: "10", rx: "2", stroke: "currentColor", strokeWidth: "1.5", fill: "none" }),
4482
+ /* @__PURE__ */ jsx22("line", { x1: "1", y1: "9", x2: "15", y2: "9", stroke: "currentColor", strokeWidth: "1.5" }),
4483
+ /* @__PURE__ */ jsx22("circle", { cx: "5", cy: "6.5", r: "1.5", stroke: "currentColor", strokeWidth: "1", fill: "none" })
3503
4484
  ] })
3504
4485
  }
3505
4486
  );
3506
4487
  };
3507
4488
  var CustomLinkToolbar = (props) => {
3508
- const editor = useBlockNoteEditor3();
3509
- const Components = useComponentsContext3();
4489
+ const editor = useBlockNoteEditor5();
4490
+ const Components = useComponentsContext4();
3510
4491
  const hasLinkPreview = !!editor?._linkPreviewApiEndpoint;
3511
- return /* @__PURE__ */ jsxs13(
4492
+ return /* @__PURE__ */ jsxs15(
3512
4493
  Components.LinkToolbar.Root,
3513
4494
  {
3514
4495
  className: "bn-toolbar bn-link-toolbar",
3515
4496
  onMouseEnter: props.stopHideTimer,
3516
4497
  onMouseLeave: props.startHideTimer,
3517
4498
  children: [
3518
- /* @__PURE__ */ jsx20(EditLinkButton, { url: props.url, text: props.text, editLink: props.editLink }),
3519
- /* @__PURE__ */ jsx20(OpenLinkButton, { url: props.url }),
3520
- /* @__PURE__ */ jsx20(DeleteLinkButton, { deleteLink: props.deleteLink }),
3521
- hasLinkPreview && /* @__PURE__ */ jsx20(ConvertToPreviewButton, { url: props.url })
4499
+ /* @__PURE__ */ jsx22(EditLinkButton, { url: props.url, text: props.text, editLink: props.editLink }),
4500
+ /* @__PURE__ */ jsx22(OpenLinkButton, { url: props.url }),
4501
+ /* @__PURE__ */ jsx22(DeleteLinkButton, { deleteLink: props.deleteLink }),
4502
+ hasLinkPreview && /* @__PURE__ */ jsx22(ConvertToPreviewButton, { url: props.url })
3522
4503
  ]
3523
4504
  }
3524
4505
  );
@@ -3562,13 +4543,13 @@ function LumirEditor({
3562
4543
  onError,
3563
4544
  onImageDelete
3564
4545
  }) {
3565
- const [isUploading, setIsUploading] = useState8(false);
3566
- const [uploadProgress, setUploadProgress] = useState8(null);
3567
- const [errorMessage, setErrorMessage] = useState8(null);
3568
- const floatingMenuFileInputRef = useRef9(null);
3569
- const floatingMenuBlockRef = useRef9(null);
3570
- const floatingMenuUploadStartTimeRef = useRef9(0);
3571
- const handleError = useCallback16(
4546
+ const [isUploading, setIsUploading] = useState10(false);
4547
+ const [uploadProgress, setUploadProgress] = useState10(null);
4548
+ const [errorMessage, setErrorMessage] = useState10(null);
4549
+ const floatingMenuFileInputRef = useRef11(null);
4550
+ const floatingMenuBlockRef = useRef11(null);
4551
+ const floatingMenuUploadStartTimeRef = useRef11(0);
4552
+ const handleError = useCallback18(
3572
4553
  (error) => {
3573
4554
  onError?.(error);
3574
4555
  setErrorMessage(error.getUserMessage());
@@ -3576,10 +4557,10 @@ function LumirEditor({
3576
4557
  },
3577
4558
  [onError]
3578
4559
  );
3579
- const validatedContent = useMemo3(() => {
4560
+ const validatedContent = useMemo6(() => {
3580
4561
  return ContentUtils.validateContent(initialContent, initialEmptyBlocks);
3581
4562
  }, [initialContent, initialEmptyBlocks]);
3582
- const tableConfig = useMemo3(() => {
4563
+ const tableConfig = useMemo6(() => {
3583
4564
  return EditorConfig.getDefaultTableConfig(tables);
3584
4565
  }, [
3585
4566
  tables?.splitCells,
@@ -3587,10 +4568,10 @@ function LumirEditor({
3587
4568
  tables?.cellTextColor,
3588
4569
  tables?.headers
3589
4570
  ]);
3590
- const headingConfig = useMemo3(() => {
4571
+ const headingConfig = useMemo6(() => {
3591
4572
  return EditorConfig.getDefaultHeadingConfig(heading);
3592
4573
  }, [heading?.levels?.join(",") ?? ""]);
3593
- const disabledExtensions = useMemo3(() => {
4574
+ const disabledExtensions = useMemo6(() => {
3594
4575
  return EditorConfig.getDisabledExtensions(
3595
4576
  disableExtensions,
3596
4577
  allowVideoUpload,
@@ -3598,18 +4579,18 @@ function LumirEditor({
3598
4579
  allowFileUpload
3599
4580
  );
3600
4581
  }, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);
3601
- useEffect8(() => {
4582
+ useEffect10(() => {
3602
4583
  DEBUG_LOG("LumirEditor:init:disabledExtensions", "snapshot", {
3603
4584
  allowVideoUpload,
3604
4585
  hasVideoInDisabled: disabledExtensions.includes("video"),
3605
4586
  disabledList: disabledExtensions.slice(0, 15)
3606
4587
  });
3607
4588
  }, [allowVideoUpload, disabledExtensions]);
3608
- const fileNameTransformRef = useRef9(s3Upload?.fileNameTransform);
3609
- useEffect8(() => {
4589
+ const fileNameTransformRef = useRef11(s3Upload?.fileNameTransform);
4590
+ useEffect10(() => {
3610
4591
  fileNameTransformRef.current = s3Upload?.fileNameTransform;
3611
4592
  }, [s3Upload?.fileNameTransform]);
3612
- const memoizedS3Upload = useMemo3(() => {
4593
+ const memoizedS3Upload = useMemo6(() => {
3613
4594
  if (!s3Upload) return void 0;
3614
4595
  return {
3615
4596
  apiEndpoint: s3Upload.apiEndpoint,
@@ -3642,6 +4623,15 @@ function LumirEditor({
3642
4623
  {
3643
4624
  // HTML 미리보기 블록이 포함된 커스텀 스키마 사용
3644
4625
  schema,
4626
+ // 모든 BlockNote UI 텍스트(테이블 드롭다운 등) 한글 적용 + "색깔"→"색"
4627
+ dictionary: {
4628
+ ...ko,
4629
+ drag_handle: { ...ko.drag_handle, colors_menuitem: "\uC0C9" },
4630
+ formatting_toolbar: {
4631
+ ...ko.formatting_toolbar,
4632
+ colors: { ...ko.formatting_toolbar.colors, tooltip: "\uC0C9" }
4633
+ }
4634
+ },
3645
4635
  initialContent: validatedContent,
3646
4636
  tables: tableConfig,
3647
4637
  heading: headingConfig,
@@ -3749,6 +4739,16 @@ function LumirEditor({
3749
4739
  return true;
3750
4740
  }
3751
4741
  }
4742
+ const pastedHtml = event?.clipboardData?.getData?.("text/html") || "";
4743
+ if (/<table[\s>]/i.test(pastedHtml)) {
4744
+ DEBUG_LOG("paste:step0:table", "table HTML detected, using pasteHTML", {
4745
+ htmlLen: pastedHtml.length,
4746
+ hasFiles: !!event?.clipboardData?.files?.length
4747
+ });
4748
+ event.preventDefault();
4749
+ editor2.pasteHTML(normalizeExcelTableHtml(pastedHtml));
4750
+ return true;
4751
+ }
3752
4752
  const fileList = event?.clipboardData?.files ?? null;
3753
4753
  const files = fileList ? Array.from(fileList) : [];
3754
4754
  const acceptedFiles = files.filter(
@@ -3823,12 +4823,12 @@ function LumirEditor({
3823
4823
  if (editor && linkPreview?.apiEndpoint) {
3824
4824
  editor._linkPreviewApiEndpoint = linkPreview.apiEndpoint;
3825
4825
  }
3826
- useEffect8(() => {
4826
+ useEffect10(() => {
3827
4827
  if (editor) {
3828
4828
  editor.isEditable = editable;
3829
4829
  }
3830
4830
  }, [editor, editable]);
3831
- useEffect8(() => {
4831
+ useEffect10(() => {
3832
4832
  if (!editor || !onContentChange) return;
3833
4833
  const handleContentChange = () => {
3834
4834
  const blocks = editor.topLevelBlocks;
@@ -3837,13 +4837,13 @@ function LumirEditor({
3837
4837
  };
3838
4838
  return editor.onEditorContentChange(handleContentChange);
3839
4839
  }, [editor, onContentChange]);
3840
- const previousMediaUrlsRef = useRef9(/* @__PURE__ */ new Set());
3841
- useEffect8(() => {
4840
+ const previousMediaUrlsRef = useRef11(/* @__PURE__ */ new Set());
4841
+ useEffect10(() => {
3842
4842
  if (!editor) return;
3843
4843
  const initialBlocks = editor.topLevelBlocks;
3844
4844
  previousMediaUrlsRef.current = extractMediaUrls(initialBlocks);
3845
4845
  }, [editor]);
3846
- useEffect8(() => {
4846
+ useEffect10(() => {
3847
4847
  if (!editor || !onImageDelete) return;
3848
4848
  const handleMediaDeleteCheck = () => {
3849
4849
  const currentBlocks = editor.topLevelBlocks;
@@ -3857,7 +4857,7 @@ function LumirEditor({
3857
4857
  };
3858
4858
  return editor.onEditorContentChange(handleMediaDeleteCheck);
3859
4859
  }, [editor, onImageDelete]);
3860
- useEffect8(() => {
4860
+ useEffect10(() => {
3861
4861
  const el = editor?.domElement;
3862
4862
  if (!el) return;
3863
4863
  const handleDragOver = (e) => {
@@ -3988,20 +4988,20 @@ function LumirEditor({
3988
4988
  el.removeEventListener("drop", handleDrop, { capture: true });
3989
4989
  };
3990
4990
  }, [editor, allowVideoUpload]);
3991
- const computedSideMenu = useMemo3(() => {
4991
+ const computedSideMenu = useMemo6(() => {
3992
4992
  return sideMenuAddButton ? sideMenu : false;
3993
4993
  }, [sideMenuAddButton, sideMenu]);
3994
- const DragHandleOnlySideMenu = useMemo3(() => {
3995
- return (props) => /* @__PURE__ */ jsx20(BlockSideMenu, { ...props, children: /* @__PURE__ */ jsx20(DragHandleButton, { ...props }) });
4994
+ const DragHandleOnlySideMenu = useMemo6(() => {
4995
+ return (props) => /* @__PURE__ */ jsx22(BlockSideMenu, { ...props, children: /* @__PURE__ */ jsx22(DragHandleButton, { ...props }) });
3996
4996
  }, []);
3997
- return /* @__PURE__ */ jsxs13(
4997
+ return /* @__PURE__ */ jsxs15(
3998
4998
  "div",
3999
4999
  {
4000
5000
  className: cn("lumirEditor", className),
4001
5001
  style: { position: "relative", display: "flex", flexDirection: "column" },
4002
5002
  children: [
4003
- floatingMenu && editor && /* @__PURE__ */ jsxs13(Fragment5, { children: [
4004
- /* @__PURE__ */ jsx20(
5003
+ floatingMenu && editor && /* @__PURE__ */ jsxs15(Fragment7, { children: [
5004
+ /* @__PURE__ */ jsx22(
4005
5005
  "input",
4006
5006
  {
4007
5007
  ref: floatingMenuFileInputRef,
@@ -4072,7 +5072,7 @@ function LumirEditor({
4072
5072
  }
4073
5073
  }
4074
5074
  ),
4075
- /* @__PURE__ */ jsx20(
5075
+ /* @__PURE__ */ jsx22(
4076
5076
  FloatingMenu,
4077
5077
  {
4078
5078
  editor,
@@ -4104,7 +5104,7 @@ function LumirEditor({
4104
5104
  }
4105
5105
  )
4106
5106
  ] }),
4107
- /* @__PURE__ */ jsxs13(
5107
+ /* @__PURE__ */ jsxs15(
4108
5108
  BlockNoteView,
4109
5109
  {
4110
5110
  editor,
@@ -4116,21 +5116,22 @@ function LumirEditor({
4116
5116
  slashMenu: false,
4117
5117
  emojiPicker,
4118
5118
  filePanel,
4119
- tableHandles,
5119
+ tableHandles: false,
4120
5120
  onSelectionChange,
4121
5121
  children: [
4122
- formattingToolbar && /* @__PURE__ */ jsx20(
5122
+ tableHandles && /* @__PURE__ */ jsx22(LumirTableHandlesController, {}),
5123
+ formattingToolbar && /* @__PURE__ */ jsx22(
4123
5124
  FormattingToolbarController,
4124
5125
  {
4125
5126
  formattingToolbar: CustomFormattingToolbar
4126
5127
  }
4127
5128
  ),
4128
- linkToolbar && (linkPreview?.apiEndpoint ? /* @__PURE__ */ jsx20(LinkToolbarController, { linkToolbar: CustomLinkToolbar }) : /* @__PURE__ */ jsx20(LinkToolbarController, {})),
4129
- /* @__PURE__ */ jsx20(
5129
+ linkToolbar && (linkPreview?.apiEndpoint ? /* @__PURE__ */ jsx22(LinkToolbarController, { linkToolbar: CustomLinkToolbar }) : /* @__PURE__ */ jsx22(LinkToolbarController, {})),
5130
+ /* @__PURE__ */ jsx22(
4130
5131
  SuggestionMenuController,
4131
5132
  {
4132
5133
  triggerCharacter: "/",
4133
- getItems: useCallback16(
5134
+ getItems: useCallback18(
4134
5135
  async (query) => {
4135
5136
  const items = getDefaultReactSlashMenuItems(editor);
4136
5137
  const filtered = items.filter((item) => {
@@ -4174,7 +5175,7 @@ function LumirEditor({
4174
5175
  },
4175
5176
  aliases: ["html", "preview", "\uC6F9", "\uC6F9\uD398\uC774\uC9C0"],
4176
5177
  group: "Embeds",
4177
- icon: /* @__PURE__ */ jsxs13(
5178
+ icon: /* @__PURE__ */ jsxs15(
4178
5179
  "svg",
4179
5180
  {
4180
5181
  width: "18",
@@ -4186,8 +5187,8 @@ function LumirEditor({
4186
5187
  strokeLinecap: "round",
4187
5188
  strokeLinejoin: "round",
4188
5189
  children: [
4189
- /* @__PURE__ */ jsx20("polyline", { points: "16 18 22 12 16 6" }),
4190
- /* @__PURE__ */ jsx20("polyline", { points: "8 6 2 12 8 18" })
5190
+ /* @__PURE__ */ jsx22("polyline", { points: "16 18 22 12 16 6" }),
5191
+ /* @__PURE__ */ jsx22("polyline", { points: "8 6 2 12 8 18" })
4191
5192
  ]
4192
5193
  }
4193
5194
  ),
@@ -4212,7 +5213,7 @@ function LumirEditor({
4212
5213
  "\uD504\uB9AC\uBDF0"
4213
5214
  ],
4214
5215
  group: "Embeds",
4215
- icon: /* @__PURE__ */ jsxs13(
5216
+ icon: /* @__PURE__ */ jsxs15(
4216
5217
  "svg",
4217
5218
  {
4218
5219
  width: "18",
@@ -4224,14 +5225,31 @@ function LumirEditor({
4224
5225
  strokeLinecap: "round",
4225
5226
  strokeLinejoin: "round",
4226
5227
  children: [
4227
- /* @__PURE__ */ jsx20("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
4228
- /* @__PURE__ */ jsx20("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
5228
+ /* @__PURE__ */ jsx22("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
5229
+ /* @__PURE__ */ jsx22("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
4229
5230
  ]
4230
5231
  }
4231
5232
  ),
4232
5233
  subtext: "URL\uC758 \uBBF8\uB9AC\uBCF4\uAE30 \uCE74\uB4DC\uB97C \uC0BD\uC785"
4233
5234
  });
4234
5235
  }
5236
+ const enSlash = en.slash_menu;
5237
+ for (const it of allItems) {
5238
+ const enEntry = it.key ? enSlash[it.key] : void 0;
5239
+ if (!enEntry) continue;
5240
+ const extra = [...enEntry.aliases ?? [], enEntry.title].filter((s) => Boolean(s)).map((s) => s.toLowerCase());
5241
+ it.aliases = Array.from(
5242
+ /* @__PURE__ */ new Set([...it.aliases ?? [], ...extra])
5243
+ );
5244
+ }
5245
+ const groupOrder = [];
5246
+ for (const it of allItems) {
5247
+ const g = it.group ?? "";
5248
+ if (!groupOrder.includes(g)) groupOrder.push(g);
5249
+ }
5250
+ allItems.sort(
5251
+ (a, b) => groupOrder.indexOf(a.group ?? "") - groupOrder.indexOf(b.group ?? "")
5252
+ );
4235
5253
  if (!query) return allItems;
4236
5254
  const q = query.toLowerCase();
4237
5255
  return allItems.filter(
@@ -4244,21 +5262,21 @@ function LumirEditor({
4244
5262
  )
4245
5263
  }
4246
5264
  ),
4247
- !sideMenuAddButton && /* @__PURE__ */ jsx20(SideMenuController, { sideMenu: DragHandleOnlySideMenu })
5265
+ !sideMenuAddButton && /* @__PURE__ */ jsx22(SideMenuController, { sideMenu: DragHandleOnlySideMenu })
4248
5266
  ]
4249
5267
  }
4250
5268
  ),
4251
- isUploading && /* @__PURE__ */ jsxs13("div", { className: "lumirEditor-upload-overlay", children: [
4252
- /* @__PURE__ */ jsx20("div", { className: "lumirEditor-spinner" }),
4253
- uploadProgress !== null && /* @__PURE__ */ jsxs13("span", { className: "lumirEditor-upload-progress", children: [
5269
+ isUploading && /* @__PURE__ */ jsxs15("div", { className: "lumirEditor-upload-overlay", children: [
5270
+ /* @__PURE__ */ jsx22("div", { className: "lumirEditor-spinner" }),
5271
+ uploadProgress !== null && /* @__PURE__ */ jsxs15("span", { className: "lumirEditor-upload-progress", children: [
4254
5272
  uploadProgress,
4255
5273
  "%"
4256
5274
  ] })
4257
5275
  ] }),
4258
- errorMessage && /* @__PURE__ */ jsxs13("div", { className: "lumirEditor-error-toast", children: [
4259
- /* @__PURE__ */ jsx20("span", { className: "lumirEditor-error-icon", children: "\u26A0\uFE0F" }),
4260
- /* @__PURE__ */ jsx20("span", { className: "lumirEditor-error-message", children: errorMessage }),
4261
- /* @__PURE__ */ jsx20(
5276
+ errorMessage && /* @__PURE__ */ jsxs15("div", { className: "lumirEditor-error-toast", children: [
5277
+ /* @__PURE__ */ jsx22("span", { className: "lumirEditor-error-icon", children: "\u26A0\uFE0F" }),
5278
+ /* @__PURE__ */ jsx22("span", { className: "lumirEditor-error-message", children: errorMessage }),
5279
+ /* @__PURE__ */ jsx22(
4262
5280
  "button",
4263
5281
  {
4264
5282
  className: "lumirEditor-error-close",