@lumir-company/editor 0.4.11 → 0.4.13

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.js CHANGED
@@ -40,10 +40,10 @@ __export(index_exports, {
40
40
  module.exports = __toCommonJS(index_exports);
41
41
 
42
42
  // src/components/LumirEditor.tsx
43
- var import_react18 = require("react");
44
- var import_react19 = require("@blocknote/react");
43
+ var import_react23 = require("react");
44
+ var import_react24 = require("@blocknote/react");
45
45
  var import_mantine = require("@blocknote/mantine");
46
- var import_core2 = require("@blocknote/core");
46
+ var import_core4 = require("@blocknote/core");
47
47
 
48
48
  // src/utils/cn.ts
49
49
  function cn(...inputs) {
@@ -2831,6 +2831,399 @@ var LumirEditorError = class _LumirEditorError extends Error {
2831
2831
  }
2832
2832
  };
2833
2833
 
2834
+ // src/extensions/VerticalAlignmentExtension.ts
2835
+ var import_core2 = require("@tiptap/core");
2836
+ var VerticalAlignmentExtension = import_core2.Extension.create({
2837
+ name: "verticalAlignment",
2838
+ addGlobalAttributes() {
2839
+ return [
2840
+ {
2841
+ types: ["tableCell", "tableHeader"],
2842
+ attributes: {
2843
+ verticalAlignment: {
2844
+ default: "top",
2845
+ parseHTML: (element) => {
2846
+ return element.getAttribute("data-vertical-alignment") || "top";
2847
+ },
2848
+ renderHTML: (attributes) => {
2849
+ if (!attributes.verticalAlignment || attributes.verticalAlignment === "top") {
2850
+ return {};
2851
+ }
2852
+ return {
2853
+ "data-vertical-alignment": attributes.verticalAlignment
2854
+ };
2855
+ }
2856
+ }
2857
+ }
2858
+ }
2859
+ ];
2860
+ },
2861
+ addProseMirrorPlugins() {
2862
+ return [];
2863
+ }
2864
+ });
2865
+
2866
+ // src/components/CustomFormattingToolbar.tsx
2867
+ var import_react22 = require("@blocknote/react");
2868
+
2869
+ // src/components/TextAlignButtonWithVA.tsx
2870
+ var import_core3 = require("@blocknote/core");
2871
+ var import_react18 = require("react");
2872
+ var import_react19 = require("@blocknote/react");
2873
+
2874
+ // src/utils/prosemirror-table-utils.ts
2875
+ function getSelectedCellPositions(editor) {
2876
+ const tiptap = editor._tiptapEditor;
2877
+ if (!tiptap) return [];
2878
+ const { state } = tiptap;
2879
+ const { selection } = state;
2880
+ if (typeof selection.forEachCell === "function") {
2881
+ const positions = [];
2882
+ selection.forEachCell((_node, pos) => {
2883
+ positions.push(pos);
2884
+ });
2885
+ return positions;
2886
+ }
2887
+ const $pos = selection.$from;
2888
+ for (let depth = $pos.depth; depth > 0; depth--) {
2889
+ const node = $pos.node(depth);
2890
+ if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
2891
+ return [$pos.before(depth)];
2892
+ }
2893
+ }
2894
+ return [];
2895
+ }
2896
+
2897
+ // src/components/TextAlignButtonWithVA.tsx
2898
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2899
+ var icons = {
2900
+ left: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z" }) }),
2901
+ center: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z" }) }),
2902
+ right: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z" }) }),
2903
+ justify: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M3 21h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18V7H3v2zM3 3v2h18V3H3z" }) })
2904
+ };
2905
+ var tooltipMap = {
2906
+ left: "\uC67C\uCABD \uC815\uB82C",
2907
+ center: "\uAC00\uC6B4\uB370 \uC815\uB82C",
2908
+ right: "\uC624\uB978\uCABD \uC815\uB82C",
2909
+ justify: "\uC591\uCABD \uC815\uB82C"
2910
+ };
2911
+ var TextAlignButtonWithVA = (props) => {
2912
+ const Components = (0, import_react19.useComponentsContext)();
2913
+ const editor = (0, import_react19.useBlockNoteEditor)();
2914
+ const selectedBlocks = (0, import_react19.useSelectedBlocks)(editor);
2915
+ const textAlignment = (0, import_react18.useMemo)(() => {
2916
+ const block = selectedBlocks[0];
2917
+ if ((0, import_core3.checkBlockHasDefaultProp)("textAlignment", block, editor)) {
2918
+ return block.props.textAlignment;
2919
+ }
2920
+ if (block.type === "table") {
2921
+ const cellSelection = editor.tableHandles?.getCellSelection();
2922
+ if (!cellSelection) {
2923
+ return;
2924
+ }
2925
+ const allCellsInTable = cellSelection.cells.map(
2926
+ ({ row, col }) => (0, import_core3.mapTableCell)(
2927
+ block.content.rows[row].cells[col]
2928
+ ).props.textAlignment
2929
+ );
2930
+ const firstAlignment = allCellsInTable[0];
2931
+ if (allCellsInTable.every((alignment) => alignment === firstAlignment)) {
2932
+ return firstAlignment;
2933
+ }
2934
+ }
2935
+ return;
2936
+ }, [editor, selectedBlocks]);
2937
+ const setTextAlignment = (0, import_react18.useCallback)(
2938
+ (newAlignment) => {
2939
+ editor.focus();
2940
+ for (const block of selectedBlocks) {
2941
+ if (block.type === "table") {
2942
+ const tiptap = editor._tiptapEditor;
2943
+ if (!tiptap) continue;
2944
+ const positions = getSelectedCellPositions(editor);
2945
+ if (positions.length === 0) continue;
2946
+ const { state } = tiptap;
2947
+ let tr = state.tr;
2948
+ for (const pos of positions) {
2949
+ const node = tr.doc.nodeAt(pos);
2950
+ if (node) {
2951
+ tr = tr.setNodeMarkup(pos, void 0, {
2952
+ ...node.attrs,
2953
+ textAlignment: newAlignment
2954
+ });
2955
+ }
2956
+ }
2957
+ tiptap.view?.dispatch(tr);
2958
+ } else if ((0, import_core3.checkBlockTypeHasDefaultProp)("textAlignment", block.type, editor)) {
2959
+ editor.updateBlock(block, {
2960
+ props: { textAlignment: newAlignment }
2961
+ });
2962
+ }
2963
+ }
2964
+ },
2965
+ [editor, selectedBlocks]
2966
+ );
2967
+ const show = (0, import_react18.useMemo)(() => {
2968
+ return !!selectedBlocks.find(
2969
+ (block) => "textAlignment" in block.props || block.type === "table" && block.children
2970
+ );
2971
+ }, [selectedBlocks]);
2972
+ if (!show || !editor.isEditable) {
2973
+ return null;
2974
+ }
2975
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2976
+ Components.FormattingToolbar.Button,
2977
+ {
2978
+ className: "bn-button",
2979
+ "data-test": `alignText${props.textAlignment.slice(0, 1).toUpperCase() + props.textAlignment.slice(1)}`,
2980
+ onClick: () => setTextAlignment(props.textAlignment),
2981
+ isSelected: textAlignment === props.textAlignment,
2982
+ label: tooltipMap[props.textAlignment],
2983
+ mainTooltip: tooltipMap[props.textAlignment],
2984
+ icon: icons[props.textAlignment]
2985
+ }
2986
+ );
2987
+ };
2988
+
2989
+ // src/components/VerticalAlignButton.tsx
2990
+ var import_react20 = require("react");
2991
+ var import_react21 = require("@blocknote/react");
2992
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2993
+ var icons2 = {
2994
+ top: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
2995
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("rect", { x: "2", y: "2", width: "12", height: "12", rx: "1" }),
2996
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("line", { x1: "5", y1: "5", x2: "11", y2: "5" })
2997
+ ] }),
2998
+ middle: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
2999
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("rect", { x: "2", y: "2", width: "12", height: "12", rx: "1" }),
3000
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("line", { x1: "5", y1: "8", x2: "11", y2: "8" })
3001
+ ] }),
3002
+ bottom: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
3003
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("rect", { x: "2", y: "2", width: "12", height: "12", rx: "1" }),
3004
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("line", { x1: "5", y1: "11", x2: "11", y2: "11" })
3005
+ ] })
3006
+ };
3007
+ var tooltips = {
3008
+ top: "\uC704\uCABD \uC815\uB82C",
3009
+ middle: "\uC138\uB85C \uAC00\uC6B4\uB370 \uC815\uB82C",
3010
+ bottom: "\uC544\uB798\uCABD \uC815\uB82C"
3011
+ };
3012
+ function getCurrentVerticalAlignment(editor) {
3013
+ const tiptap = editor._tiptapEditor;
3014
+ if (!tiptap) return void 0;
3015
+ const positions = getSelectedCellPositions(editor);
3016
+ if (positions.length === 0) return void 0;
3017
+ const { state } = tiptap;
3018
+ const alignments = positions.map((pos) => {
3019
+ const node = state.doc.nodeAt(pos);
3020
+ return node?.attrs?.verticalAlignment || "top";
3021
+ });
3022
+ const first = alignments[0];
3023
+ return alignments.every((a) => a === first) ? first : void 0;
3024
+ }
3025
+ var VerticalAlignButton = (props) => {
3026
+ const Components = (0, import_react21.useComponentsContext)();
3027
+ const editor = (0, import_react21.useBlockNoteEditor)();
3028
+ const selectedBlocks = (0, import_react21.useSelectedBlocks)(editor);
3029
+ const currentAlignment = (0, import_react20.useMemo)(() => {
3030
+ return getCurrentVerticalAlignment(editor);
3031
+ }, [editor, selectedBlocks]);
3032
+ const setVerticalAlignment = (0, import_react20.useCallback)(
3033
+ (alignment) => {
3034
+ const tiptap = editor._tiptapEditor;
3035
+ if (!tiptap) return;
3036
+ const positions = getSelectedCellPositions(editor);
3037
+ if (positions.length === 0) return;
3038
+ editor.focus();
3039
+ const { state } = tiptap;
3040
+ let tr = state.tr;
3041
+ for (const pos of positions) {
3042
+ const node = tr.doc.nodeAt(pos);
3043
+ if (node) {
3044
+ tr = tr.setNodeMarkup(pos, void 0, {
3045
+ ...node.attrs,
3046
+ verticalAlignment: alignment
3047
+ });
3048
+ }
3049
+ }
3050
+ tiptap.view?.dispatch(tr);
3051
+ },
3052
+ [editor]
3053
+ );
3054
+ const isInTable = (0, import_react20.useMemo)(() => {
3055
+ return selectedBlocks.some((block) => block.type === "table");
3056
+ }, [selectedBlocks]);
3057
+ if (!isInTable || !editor.isEditable) {
3058
+ return null;
3059
+ }
3060
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3061
+ Components.FormattingToolbar.Button,
3062
+ {
3063
+ className: "bn-button",
3064
+ "data-test": `verticalAlign${props.verticalAlignment.charAt(0).toUpperCase() + props.verticalAlignment.slice(1)}`,
3065
+ onClick: () => setVerticalAlignment(props.verticalAlignment),
3066
+ isSelected: currentAlignment === props.verticalAlignment,
3067
+ label: tooltips[props.verticalAlignment],
3068
+ mainTooltip: tooltips[props.verticalAlignment],
3069
+ icon: icons2[props.verticalAlignment]
3070
+ }
3071
+ );
3072
+ };
3073
+
3074
+ // src/components/CustomFormattingToolbar.tsx
3075
+ var import_jsx_runtime19 = require("react/jsx-runtime");
3076
+ var CustomFormattingToolbar = () => {
3077
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react22.FormattingToolbar, { children: [
3078
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.BlockTypeSelect, {}, "blockTypeSelect"),
3079
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.TableCellMergeButton, {}, "tableCellMergeButton"),
3080
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.FileCaptionButton, {}, "fileCaptionButton"),
3081
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.FileReplaceButton, {}, "replaceFileButton"),
3082
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.FileRenameButton, {}, "fileRenameButton"),
3083
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.FileDeleteButton, {}, "fileDeleteButton"),
3084
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.FileDownloadButton, {}, "fileDownloadButton"),
3085
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.FilePreviewButton, {}, "filePreviewButton"),
3086
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.BasicTextStyleButton, { basicTextStyle: "bold" }, "boldStyleButton"),
3087
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3088
+ import_react22.BasicTextStyleButton,
3089
+ {
3090
+ basicTextStyle: "italic"
3091
+ },
3092
+ "italicStyleButton"
3093
+ ),
3094
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3095
+ import_react22.BasicTextStyleButton,
3096
+ {
3097
+ basicTextStyle: "underline"
3098
+ },
3099
+ "underlineStyleButton"
3100
+ ),
3101
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3102
+ import_react22.BasicTextStyleButton,
3103
+ {
3104
+ basicTextStyle: "strike"
3105
+ },
3106
+ "strikeStyleButton"
3107
+ ),
3108
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TextAlignButtonWithVA, { textAlignment: "left" }, "textAlignLeftButton"),
3109
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3110
+ TextAlignButtonWithVA,
3111
+ {
3112
+ textAlignment: "center"
3113
+ },
3114
+ "textAlignCenterButton"
3115
+ ),
3116
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TextAlignButtonWithVA, { textAlignment: "right" }, "textAlignRightButton"),
3117
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3118
+ VerticalAlignButton,
3119
+ {
3120
+ verticalAlignment: "top"
3121
+ },
3122
+ "verticalAlignTop"
3123
+ ),
3124
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3125
+ VerticalAlignButton,
3126
+ {
3127
+ verticalAlignment: "middle"
3128
+ },
3129
+ "verticalAlignMiddle"
3130
+ ),
3131
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3132
+ VerticalAlignButton,
3133
+ {
3134
+ verticalAlignment: "bottom"
3135
+ },
3136
+ "verticalAlignBottom"
3137
+ ),
3138
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.ColorStyleButton, {}, "colorStyleButton"),
3139
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.NestBlockButton, {}, "nestBlockButton"),
3140
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.UnnestBlockButton, {}, "unnestBlockButton"),
3141
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react22.CreateLinkButton, {}, "createLinkButton")
3142
+ ] });
3143
+ };
3144
+
3145
+ // src/utils/table-vertical-alignment.ts
3146
+ function injectVerticalAlignment(blocks, editor) {
3147
+ const tiptap = editor?._tiptapEditor;
3148
+ if (!tiptap) return blocks;
3149
+ const { doc } = tiptap.state;
3150
+ const tableVAMap = buildTableVerticalAlignmentMap(doc);
3151
+ if (tableVAMap.size === 0) return blocks;
3152
+ return patchBlocks(blocks, tableVAMap);
3153
+ }
3154
+ function buildTableVerticalAlignmentMap(doc) {
3155
+ const result = /* @__PURE__ */ new Map();
3156
+ doc.descendants((node) => {
3157
+ if (node.type.name === "blockContainer") {
3158
+ const blockId = node.attrs?.id;
3159
+ const contentNode = node.firstChild;
3160
+ if (blockId && contentNode?.type.name === "table") {
3161
+ const cells = [];
3162
+ let rowIndex = 0;
3163
+ contentNode.forEach((rowNode) => {
3164
+ if (rowNode.type.name === "tableRow") {
3165
+ let colIndex = 0;
3166
+ rowNode.forEach((cellNode) => {
3167
+ const va = cellNode.attrs?.verticalAlignment;
3168
+ if (va && va !== "top") {
3169
+ cells.push({ row: rowIndex, col: colIndex, va });
3170
+ }
3171
+ colIndex++;
3172
+ });
3173
+ rowIndex++;
3174
+ }
3175
+ });
3176
+ if (cells.length > 0) {
3177
+ result.set(blockId, cells);
3178
+ }
3179
+ }
3180
+ return false;
3181
+ }
3182
+ });
3183
+ return result;
3184
+ }
3185
+ function patchBlocks(blocks, tableVAMap) {
3186
+ return blocks.map((block) => {
3187
+ if (block.type !== "table" || !block.id || !block.content) {
3188
+ if (block.children && block.children.length > 0) {
3189
+ return {
3190
+ ...block,
3191
+ children: patchBlocks(block.children, tableVAMap)
3192
+ };
3193
+ }
3194
+ return block;
3195
+ }
3196
+ const cells = tableVAMap.get(block.id);
3197
+ if (!cells || cells.length === 0) {
3198
+ return block;
3199
+ }
3200
+ const content = block.content;
3201
+ if (content.type !== "tableContent" || !content.rows) {
3202
+ return block;
3203
+ }
3204
+ const newRows = content.rows.map((row, rowIndex) => {
3205
+ const newCells = row.cells.map((cell, colIndex) => {
3206
+ const match = cells.find(
3207
+ (c) => c.row === rowIndex && c.col === colIndex
3208
+ );
3209
+ if (!match) return cell;
3210
+ if (cell && typeof cell === "object" && cell.type === "tableCell") {
3211
+ return {
3212
+ ...cell,
3213
+ props: { ...cell.props, verticalAlignment: match.va }
3214
+ };
3215
+ }
3216
+ return cell;
3217
+ });
3218
+ return { ...row, cells: newCells };
3219
+ });
3220
+ return {
3221
+ ...block,
3222
+ content: { ...content, rows: newRows }
3223
+ };
3224
+ });
3225
+ }
3226
+
2834
3227
  // src/constants/limits.ts
2835
3228
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
2836
3229
  var MAX_VIDEO_FILE_SIZE = 100 * 1024 * 1024;
@@ -2850,7 +3243,7 @@ var ALLOWED_VIDEO_EXTENSIONS = [
2850
3243
  ];
2851
3244
 
2852
3245
  // src/components/LumirEditor.tsx
2853
- var import_jsx_runtime17 = require("react/jsx-runtime");
3246
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2854
3247
  var DEBUG_LOG = (loc, msg, data) => {
2855
3248
  const p = fetch("http://127.0.0.1:7686/ingest/1f8ee1c5-0cf0-4ae7-91ed-5ea7ed17130a", {
2856
3249
  method: "POST",
@@ -3075,9 +3468,9 @@ var findBlockWithLink = (blocks, targetUrl) => {
3075
3468
  return null;
3076
3469
  };
3077
3470
  var ConvertToPreviewButton = ({ url }) => {
3078
- const editor = (0, import_react19.useBlockNoteEditor)();
3079
- const Components = (0, import_react19.useComponentsContext)();
3080
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3471
+ const editor = (0, import_react24.useBlockNoteEditor)();
3472
+ const Components = (0, import_react24.useComponentsContext)();
3473
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3081
3474
  Components.LinkToolbar.Button,
3082
3475
  {
3083
3476
  className: "bn-button",
@@ -3096,29 +3489,29 @@ var ConvertToPreviewButton = ({ url }) => {
3096
3489
  console.error("Convert to link preview failed:", err);
3097
3490
  }
3098
3491
  },
3099
- icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
3100
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("rect", { x: "1", y: "3", width: "14", height: "10", rx: "2", stroke: "currentColor", strokeWidth: "1.5", fill: "none" }),
3101
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("line", { x1: "1", y1: "9", x2: "15", y2: "9", stroke: "currentColor", strokeWidth: "1.5" }),
3102
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("circle", { cx: "5", cy: "6.5", r: "1.5", stroke: "currentColor", strokeWidth: "1", fill: "none" })
3492
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
3493
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("rect", { x: "1", y: "3", width: "14", height: "10", rx: "2", stroke: "currentColor", strokeWidth: "1.5", fill: "none" }),
3494
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("line", { x1: "1", y1: "9", x2: "15", y2: "9", stroke: "currentColor", strokeWidth: "1.5" }),
3495
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("circle", { cx: "5", cy: "6.5", r: "1.5", stroke: "currentColor", strokeWidth: "1", fill: "none" })
3103
3496
  ] })
3104
3497
  }
3105
3498
  );
3106
3499
  };
3107
3500
  var CustomLinkToolbar = (props) => {
3108
- const editor = (0, import_react19.useBlockNoteEditor)();
3109
- const Components = (0, import_react19.useComponentsContext)();
3501
+ const editor = (0, import_react24.useBlockNoteEditor)();
3502
+ const Components = (0, import_react24.useComponentsContext)();
3110
3503
  const hasLinkPreview = !!editor?._linkPreviewApiEndpoint;
3111
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3504
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
3112
3505
  Components.LinkToolbar.Root,
3113
3506
  {
3114
3507
  className: "bn-toolbar bn-link-toolbar",
3115
3508
  onMouseEnter: props.stopHideTimer,
3116
3509
  onMouseLeave: props.startHideTimer,
3117
3510
  children: [
3118
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react19.EditLinkButton, { url: props.url, text: props.text, editLink: props.editLink }),
3119
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react19.OpenLinkButton, { url: props.url }),
3120
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react19.DeleteLinkButton, { deleteLink: props.deleteLink }),
3121
- hasLinkPreview && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ConvertToPreviewButton, { url: props.url })
3511
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react24.EditLinkButton, { url: props.url, text: props.text, editLink: props.editLink }),
3512
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react24.OpenLinkButton, { url: props.url }),
3513
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react24.DeleteLinkButton, { deleteLink: props.deleteLink }),
3514
+ hasLinkPreview && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ConvertToPreviewButton, { url: props.url })
3122
3515
  ]
3123
3516
  }
3124
3517
  );
@@ -3162,13 +3555,13 @@ function LumirEditor({
3162
3555
  onError,
3163
3556
  onImageDelete
3164
3557
  }) {
3165
- const [isUploading, setIsUploading] = (0, import_react18.useState)(false);
3166
- const [uploadProgress, setUploadProgress] = (0, import_react18.useState)(null);
3167
- const [errorMessage, setErrorMessage] = (0, import_react18.useState)(null);
3168
- const floatingMenuFileInputRef = (0, import_react18.useRef)(null);
3169
- const floatingMenuBlockRef = (0, import_react18.useRef)(null);
3170
- const floatingMenuUploadStartTimeRef = (0, import_react18.useRef)(0);
3171
- const handleError = (0, import_react18.useCallback)(
3558
+ const [isUploading, setIsUploading] = (0, import_react23.useState)(false);
3559
+ const [uploadProgress, setUploadProgress] = (0, import_react23.useState)(null);
3560
+ const [errorMessage, setErrorMessage] = (0, import_react23.useState)(null);
3561
+ const floatingMenuFileInputRef = (0, import_react23.useRef)(null);
3562
+ const floatingMenuBlockRef = (0, import_react23.useRef)(null);
3563
+ const floatingMenuUploadStartTimeRef = (0, import_react23.useRef)(0);
3564
+ const handleError = (0, import_react23.useCallback)(
3172
3565
  (error) => {
3173
3566
  onError?.(error);
3174
3567
  setErrorMessage(error.getUserMessage());
@@ -3176,10 +3569,10 @@ function LumirEditor({
3176
3569
  },
3177
3570
  [onError]
3178
3571
  );
3179
- const validatedContent = (0, import_react18.useMemo)(() => {
3572
+ const validatedContent = (0, import_react23.useMemo)(() => {
3180
3573
  return ContentUtils.validateContent(initialContent, initialEmptyBlocks);
3181
3574
  }, [initialContent, initialEmptyBlocks]);
3182
- const tableConfig = (0, import_react18.useMemo)(() => {
3575
+ const tableConfig = (0, import_react23.useMemo)(() => {
3183
3576
  return EditorConfig.getDefaultTableConfig(tables);
3184
3577
  }, [
3185
3578
  tables?.splitCells,
@@ -3187,10 +3580,10 @@ function LumirEditor({
3187
3580
  tables?.cellTextColor,
3188
3581
  tables?.headers
3189
3582
  ]);
3190
- const headingConfig = (0, import_react18.useMemo)(() => {
3583
+ const headingConfig = (0, import_react23.useMemo)(() => {
3191
3584
  return EditorConfig.getDefaultHeadingConfig(heading);
3192
3585
  }, [heading?.levels?.join(",") ?? ""]);
3193
- const disabledExtensions = (0, import_react18.useMemo)(() => {
3586
+ const disabledExtensions = (0, import_react23.useMemo)(() => {
3194
3587
  return EditorConfig.getDisabledExtensions(
3195
3588
  disableExtensions,
3196
3589
  allowVideoUpload,
@@ -3198,18 +3591,18 @@ function LumirEditor({
3198
3591
  allowFileUpload
3199
3592
  );
3200
3593
  }, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);
3201
- (0, import_react18.useEffect)(() => {
3594
+ (0, import_react23.useEffect)(() => {
3202
3595
  DEBUG_LOG("LumirEditor:init:disabledExtensions", "snapshot", {
3203
3596
  allowVideoUpload,
3204
3597
  hasVideoInDisabled: disabledExtensions.includes("video"),
3205
3598
  disabledList: disabledExtensions.slice(0, 15)
3206
3599
  });
3207
3600
  }, [allowVideoUpload, disabledExtensions]);
3208
- const fileNameTransformRef = (0, import_react18.useRef)(s3Upload?.fileNameTransform);
3209
- (0, import_react18.useEffect)(() => {
3601
+ const fileNameTransformRef = (0, import_react23.useRef)(s3Upload?.fileNameTransform);
3602
+ (0, import_react23.useEffect)(() => {
3210
3603
  fileNameTransformRef.current = s3Upload?.fileNameTransform;
3211
3604
  }, [s3Upload?.fileNameTransform]);
3212
- const memoizedS3Upload = (0, import_react18.useMemo)(() => {
3605
+ const memoizedS3Upload = (0, import_react23.useMemo)(() => {
3213
3606
  if (!s3Upload) return void 0;
3214
3607
  return {
3215
3608
  apiEndpoint: s3Upload.apiEndpoint,
@@ -3238,7 +3631,7 @@ function LumirEditor({
3238
3631
  s3Upload?.maxRetries,
3239
3632
  s3Upload?.onProgress
3240
3633
  ]);
3241
- const editor = (0, import_react19.useCreateBlockNote)(
3634
+ const editor = (0, import_react24.useCreateBlockNote)(
3242
3635
  {
3243
3636
  // HTML 미리보기 블록이 포함된 커스텀 스키마 사용
3244
3637
  schema,
@@ -3250,6 +3643,9 @@ function LumirEditor({
3250
3643
  defaultStyles,
3251
3644
  // 확장 비활성: 비디오/오디오/파일 제어
3252
3645
  disableExtensions: disabledExtensions,
3646
+ _tiptapOptions: {
3647
+ extensions: [VerticalAlignmentExtension]
3648
+ },
3253
3649
  placeholders: placeholder ? { default: placeholder, emptyDocument: placeholder } : void 0,
3254
3650
  tabBehavior,
3255
3651
  trailingBlock,
@@ -3420,26 +3816,27 @@ function LumirEditor({
3420
3816
  if (editor && linkPreview?.apiEndpoint) {
3421
3817
  editor._linkPreviewApiEndpoint = linkPreview.apiEndpoint;
3422
3818
  }
3423
- (0, import_react18.useEffect)(() => {
3819
+ (0, import_react23.useEffect)(() => {
3424
3820
  if (editor) {
3425
3821
  editor.isEditable = editable;
3426
3822
  }
3427
3823
  }, [editor, editable]);
3428
- (0, import_react18.useEffect)(() => {
3824
+ (0, import_react23.useEffect)(() => {
3429
3825
  if (!editor || !onContentChange) return;
3430
3826
  const handleContentChange = () => {
3431
3827
  const blocks = editor.topLevelBlocks;
3432
- onContentChange(blocks);
3828
+ const patched = injectVerticalAlignment(blocks, editor);
3829
+ onContentChange(patched);
3433
3830
  };
3434
3831
  return editor.onEditorContentChange(handleContentChange);
3435
3832
  }, [editor, onContentChange]);
3436
- const previousMediaUrlsRef = (0, import_react18.useRef)(/* @__PURE__ */ new Set());
3437
- (0, import_react18.useEffect)(() => {
3833
+ const previousMediaUrlsRef = (0, import_react23.useRef)(/* @__PURE__ */ new Set());
3834
+ (0, import_react23.useEffect)(() => {
3438
3835
  if (!editor) return;
3439
3836
  const initialBlocks = editor.topLevelBlocks;
3440
3837
  previousMediaUrlsRef.current = extractMediaUrls(initialBlocks);
3441
3838
  }, [editor]);
3442
- (0, import_react18.useEffect)(() => {
3839
+ (0, import_react23.useEffect)(() => {
3443
3840
  if (!editor || !onImageDelete) return;
3444
3841
  const handleMediaDeleteCheck = () => {
3445
3842
  const currentBlocks = editor.topLevelBlocks;
@@ -3453,7 +3850,7 @@ function LumirEditor({
3453
3850
  };
3454
3851
  return editor.onEditorContentChange(handleMediaDeleteCheck);
3455
3852
  }, [editor, onImageDelete]);
3456
- (0, import_react18.useEffect)(() => {
3853
+ (0, import_react23.useEffect)(() => {
3457
3854
  const el = editor?.domElement;
3458
3855
  if (!el) return;
3459
3856
  const handleDragOver = (e) => {
@@ -3584,20 +3981,20 @@ function LumirEditor({
3584
3981
  el.removeEventListener("drop", handleDrop, { capture: true });
3585
3982
  };
3586
3983
  }, [editor, allowVideoUpload]);
3587
- const computedSideMenu = (0, import_react18.useMemo)(() => {
3984
+ const computedSideMenu = (0, import_react23.useMemo)(() => {
3588
3985
  return sideMenuAddButton ? sideMenu : false;
3589
3986
  }, [sideMenuAddButton, sideMenu]);
3590
- const DragHandleOnlySideMenu = (0, import_react18.useMemo)(() => {
3591
- return (props) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react19.SideMenu, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react19.DragHandleButton, { ...props }) });
3987
+ const DragHandleOnlySideMenu = (0, import_react23.useMemo)(() => {
3988
+ return (props) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react24.SideMenu, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react24.DragHandleButton, { ...props }) });
3592
3989
  }, []);
3593
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3990
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
3594
3991
  "div",
3595
3992
  {
3596
3993
  className: cn("lumirEditor", className),
3597
3994
  style: { position: "relative", display: "flex", flexDirection: "column" },
3598
3995
  children: [
3599
- floatingMenu && editor && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
3600
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3996
+ floatingMenu && editor && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
3997
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3601
3998
  "input",
3602
3999
  {
3603
4000
  ref: floatingMenuFileInputRef,
@@ -3668,7 +4065,7 @@ function LumirEditor({
3668
4065
  }
3669
4066
  }
3670
4067
  ),
3671
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4068
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3672
4069
  FloatingMenu,
3673
4070
  {
3674
4071
  editor,
@@ -3700,13 +4097,13 @@ function LumirEditor({
3700
4097
  }
3701
4098
  )
3702
4099
  ] }),
3703
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4100
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
3704
4101
  import_mantine.BlockNoteView,
3705
4102
  {
3706
4103
  editor,
3707
4104
  editable,
3708
4105
  theme,
3709
- formattingToolbar,
4106
+ formattingToolbar: false,
3710
4107
  linkToolbar: false,
3711
4108
  sideMenu: computedSideMenu,
3712
4109
  slashMenu: false,
@@ -3715,14 +4112,20 @@ function LumirEditor({
3715
4112
  tableHandles,
3716
4113
  onSelectionChange,
3717
4114
  children: [
3718
- linkToolbar && (linkPreview?.apiEndpoint ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react19.LinkToolbarController, { linkToolbar: CustomLinkToolbar }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react19.LinkToolbarController, {})),
3719
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3720
- import_react19.SuggestionMenuController,
4115
+ formattingToolbar && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4116
+ import_react24.FormattingToolbarController,
4117
+ {
4118
+ formattingToolbar: CustomFormattingToolbar
4119
+ }
4120
+ ),
4121
+ linkToolbar && (linkPreview?.apiEndpoint ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react24.LinkToolbarController, { linkToolbar: CustomLinkToolbar }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react24.LinkToolbarController, {})),
4122
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4123
+ import_react24.SuggestionMenuController,
3721
4124
  {
3722
4125
  triggerCharacter: "/",
3723
- getItems: (0, import_react18.useCallback)(
4126
+ getItems: (0, import_react23.useCallback)(
3724
4127
  async (query) => {
3725
- const items = (0, import_react19.getDefaultReactSlashMenuItems)(editor);
4128
+ const items = (0, import_react24.getDefaultReactSlashMenuItems)(editor);
3726
4129
  const filtered = items.filter((item) => {
3727
4130
  const key = (item?.key || "").toString().toLowerCase();
3728
4131
  const title = (item?.title || "").toString().toLowerCase();
@@ -3764,7 +4167,7 @@ function LumirEditor({
3764
4167
  },
3765
4168
  aliases: ["html", "preview", "\uC6F9", "\uC6F9\uD398\uC774\uC9C0"],
3766
4169
  group: "Embeds",
3767
- icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4170
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
3768
4171
  "svg",
3769
4172
  {
3770
4173
  width: "18",
@@ -3776,8 +4179,8 @@ function LumirEditor({
3776
4179
  strokeLinecap: "round",
3777
4180
  strokeLinejoin: "round",
3778
4181
  children: [
3779
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("polyline", { points: "16 18 22 12 16 6" }),
3780
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("polyline", { points: "8 6 2 12 8 18" })
4182
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("polyline", { points: "16 18 22 12 16 6" }),
4183
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("polyline", { points: "8 6 2 12 8 18" })
3781
4184
  ]
3782
4185
  }
3783
4186
  ),
@@ -3788,7 +4191,7 @@ function LumirEditor({
3788
4191
  allItems.push({
3789
4192
  title: "Link Preview",
3790
4193
  onItemClick: () => {
3791
- (0, import_core2.insertOrUpdateBlock)(editor, {
4194
+ (0, import_core4.insertOrUpdateBlock)(editor, {
3792
4195
  type: "linkPreview",
3793
4196
  props: { url: "" }
3794
4197
  });
@@ -3802,7 +4205,7 @@ function LumirEditor({
3802
4205
  "\uD504\uB9AC\uBDF0"
3803
4206
  ],
3804
4207
  group: "Embeds",
3805
- icon: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4208
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
3806
4209
  "svg",
3807
4210
  {
3808
4211
  width: "18",
@@ -3814,8 +4217,8 @@ function LumirEditor({
3814
4217
  strokeLinecap: "round",
3815
4218
  strokeLinejoin: "round",
3816
4219
  children: [
3817
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
3818
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
4220
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
4221
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
3819
4222
  ]
3820
4223
  }
3821
4224
  ),
@@ -3834,21 +4237,21 @@ function LumirEditor({
3834
4237
  )
3835
4238
  }
3836
4239
  ),
3837
- !sideMenuAddButton && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react19.SideMenuController, { sideMenu: DragHandleOnlySideMenu })
4240
+ !sideMenuAddButton && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react24.SideMenuController, { sideMenu: DragHandleOnlySideMenu })
3838
4241
  ]
3839
4242
  }
3840
4243
  ),
3841
- isUploading && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "lumirEditor-upload-overlay", children: [
3842
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "lumirEditor-spinner" }),
3843
- uploadProgress !== null && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "lumirEditor-upload-progress", children: [
4244
+ isUploading && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "lumirEditor-upload-overlay", children: [
4245
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "lumirEditor-spinner" }),
4246
+ uploadProgress !== null && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "lumirEditor-upload-progress", children: [
3844
4247
  uploadProgress,
3845
4248
  "%"
3846
4249
  ] })
3847
4250
  ] }),
3848
- errorMessage && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "lumirEditor-error-toast", children: [
3849
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "lumirEditor-error-icon", children: "\u26A0\uFE0F" }),
3850
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "lumirEditor-error-message", children: errorMessage }),
3851
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4251
+ errorMessage && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "lumirEditor-error-toast", children: [
4252
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "lumirEditor-error-icon", children: "\u26A0\uFE0F" }),
4253
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "lumirEditor-error-message", children: errorMessage }),
4254
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3852
4255
  "button",
3853
4256
  {
3854
4257
  className: "lumirEditor-error-close",