tiptap-editor-custom-stg 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1292 @@
1
+ // src/TiptapEditor.tsx
2
+ import React, { useRef, useCallback, useEffect } from "react";
3
+ import { useEditor, EditorContent } from "@tiptap/react";
4
+
5
+ // src/extensions.ts
6
+ import StarterKit from "@tiptap/starter-kit";
7
+ import Image from "@tiptap/extension-image";
8
+ import Link from "@tiptap/extension-link";
9
+ import Underline from "@tiptap/extension-underline";
10
+ import { TextStyle } from "@tiptap/extension-text-style";
11
+ import FontFamily from "@tiptap/extension-font-family";
12
+ import { Color } from "@tiptap/extension-color";
13
+ import Superscript from "@tiptap/extension-superscript";
14
+ import Subscript from "@tiptap/extension-subscript";
15
+ import TextAlign from "@tiptap/extension-text-align";
16
+ import Placeholder from "@tiptap/extension-placeholder";
17
+ import { Table } from "@tiptap/extension-table";
18
+ import { TableRow } from "@tiptap/extension-table-row";
19
+ import TableHeader from "@tiptap/extension-table-header";
20
+ import { TableCell } from "@tiptap/extension-table-cell";
21
+
22
+ // src/fontSize.ts
23
+ import { Extension } from "@tiptap/core";
24
+ import "@tiptap/extension-text-style";
25
+ var FontSize = Extension.create({
26
+ name: "fontSize",
27
+ addOptions() {
28
+ return {
29
+ types: ["textStyle"]
30
+ };
31
+ },
32
+ addGlobalAttributes() {
33
+ return [
34
+ {
35
+ types: this.options.types,
36
+ attributes: {
37
+ fontSize: {
38
+ default: null,
39
+ parseHTML: (element) => element.style.fontSize.replace(/['"]+/g, ""),
40
+ renderHTML: (attributes) => {
41
+ if (!attributes.fontSize) {
42
+ return {};
43
+ }
44
+ return {
45
+ style: `font-size: ${attributes.fontSize}`
46
+ };
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ];
52
+ },
53
+ addCommands() {
54
+ return {
55
+ setFontSize: (fontSize) => ({ chain }) => {
56
+ const mapping = {
57
+ "tiny": "0.7em",
58
+ "small": "0.85em",
59
+ "big": "1.4em",
60
+ "huge": "1.8em"
61
+ };
62
+ const value = mapping[fontSize] || fontSize;
63
+ return chain().setMark("textStyle", { fontSize: value }).run();
64
+ },
65
+ unsetFontSize: () => ({ chain }) => {
66
+ return chain().setMark("textStyle", { fontSize: null }).removeEmptyTextStyle().run();
67
+ }
68
+ };
69
+ }
70
+ });
71
+
72
+ // src/backgroundColor.ts
73
+ import { Extension as Extension2 } from "@tiptap/core";
74
+ import "@tiptap/extension-text-style";
75
+ var BackgroundColor = Extension2.create({
76
+ name: "backgroundColor",
77
+ addOptions() {
78
+ return {
79
+ types: ["textStyle"]
80
+ };
81
+ },
82
+ addGlobalAttributes() {
83
+ return [
84
+ {
85
+ types: this.options.types,
86
+ attributes: {
87
+ backgroundColor: {
88
+ default: null,
89
+ parseHTML: (element) => element.style.backgroundColor.replace(/['"]+/g, ""),
90
+ renderHTML: (attributes) => {
91
+ if (!attributes.backgroundColor) {
92
+ return {};
93
+ }
94
+ return {
95
+ style: `background-color: ${attributes.backgroundColor}`
96
+ };
97
+ }
98
+ }
99
+ }
100
+ }
101
+ ];
102
+ },
103
+ addCommands() {
104
+ return {
105
+ setBackgroundColor: (backgroundColor) => ({ chain }) => {
106
+ return chain().setMark("textStyle", { backgroundColor }).run();
107
+ },
108
+ unsetBackgroundColor: () => ({ chain }) => {
109
+ return chain().setMark("textStyle", { backgroundColor: null }).removeEmptyTextStyle().run();
110
+ }
111
+ };
112
+ }
113
+ });
114
+
115
+ // src/extensions.ts
116
+ var createTiptapExtensions = (placeholder = "Enter content here...") => [
117
+ StarterKit.configure({
118
+ heading: { levels: [1, 2, 3, 4, 5, 6] }
119
+ }),
120
+ Link.configure({
121
+ openOnClick: false,
122
+ autolink: true,
123
+ HTMLAttributes: { rel: "noopener noreferrer" }
124
+ }),
125
+ Underline,
126
+ Image.configure({ inline: false, allowBase64: false }),
127
+ TextStyle,
128
+ FontSize,
129
+ BackgroundColor,
130
+ FontFamily,
131
+ Color,
132
+ Superscript,
133
+ Subscript,
134
+ TextAlign.configure({ types: ["heading", "paragraph"] }),
135
+ Placeholder.configure({ placeholder }),
136
+ Table.configure({
137
+ resizable: true,
138
+ HTMLAttributes: {
139
+ class: "tiptap-table"
140
+ }
141
+ }),
142
+ TableRow,
143
+ TableHeader,
144
+ TableCell
145
+ ];
146
+
147
+ // src/utils.ts
148
+ async function uploadFileToCOS(file, docNum, onUploadFile, receiveStatus, elementId, onLoadingChange, suppressStatusChange = false, maxFileSizeMB = 50) {
149
+ const maxFileSizeBytes = maxFileSizeMB * 1024 * 1024;
150
+ if (file.size > maxFileSizeBytes) {
151
+ throw new Error(`${file.name} attachment too large (Max ${maxFileSizeMB}M)`);
152
+ }
153
+ receiveStatus && elementId && receiveStatus(elementId, true);
154
+ if (!suppressStatusChange) onLoadingChange == null ? void 0 : onLoadingChange(true);
155
+ try {
156
+ const url = await onUploadFile(file, docNum);
157
+ if (!url) throw new Error("Upload failed: no URL returned");
158
+ return url;
159
+ } finally {
160
+ if (!suppressStatusChange) onLoadingChange == null ? void 0 : onLoadingChange(false);
161
+ receiveStatus && elementId && receiveStatus(elementId, false);
162
+ }
163
+ }
164
+
165
+ // src/constants.ts
166
+ var TIPTAP_COLORS = [
167
+ "#000000",
168
+ "#4d4d4d",
169
+ "#999999",
170
+ "#e6e6e6",
171
+ "#ffffff",
172
+ "#e64d3d",
173
+ "#eb9120",
174
+ "#f3da35",
175
+ "#7ed822",
176
+ "#00d924",
177
+ "#1abc9c",
178
+ "#32dada",
179
+ "#3498db",
180
+ "#2980b9",
181
+ "#a290e4"
182
+ ];
183
+ var FONT_FAMILIES = [
184
+ { label: "Default", value: "" },
185
+ { label: "Arial", value: "Arial" },
186
+ { label: "Courier New", value: "Courier New" },
187
+ { label: "Georgia", value: "Georgia" },
188
+ { label: "Lucida Sans Unicode", value: "Lucida Sans Unicode" },
189
+ { label: "Tahoma", value: "Tahoma" },
190
+ { label: "Times New Roman", value: "Times New Roman" },
191
+ { label: "Trebuchet MS", value: "Trebuchet MS" },
192
+ { label: "Verdana", value: "Verdana" }
193
+ ];
194
+ var FONT_SIZES = [
195
+ { label: "Tiny", value: "tiny" },
196
+ { label: "Small", value: "small" },
197
+ { label: "Default", value: "" },
198
+ { label: "Big", value: "big" },
199
+ { label: "Huge", value: "huge" }
200
+ ];
201
+ var IMAGE_MIME_TYPES = [
202
+ "image/png",
203
+ "image/jpeg",
204
+ "image/jpg",
205
+ "image/gif",
206
+ "image/webp",
207
+ "image/bmp"
208
+ ];
209
+
210
+ // src/Icons.tsx
211
+ import { jsx, jsxs } from "react/jsx-runtime";
212
+ var LucideSvg = ({ children }) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", xmlns: "http://www.w3.org/2000/svg", children });
213
+ var UndoIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
214
+ /* @__PURE__ */ jsx("path", { d: "M3 7v6h6" }),
215
+ /* @__PURE__ */ jsx("path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" })
216
+ ] });
217
+ var RedoIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
218
+ /* @__PURE__ */ jsx("path", { d: "M21 7v6h-6" }),
219
+ /* @__PURE__ */ jsx("path", { d: "M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7" })
220
+ ] });
221
+ var BulletListIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
222
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "6", x2: "21", y2: "6" }),
223
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "12", x2: "21", y2: "12" }),
224
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "18", x2: "21", y2: "18" }),
225
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "3.01", y2: "6" }),
226
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "12", x2: "3.01", y2: "12" }),
227
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "18", x2: "3.01", y2: "18" })
228
+ ] });
229
+ var OrderedListIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
230
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "6", x2: "21", y2: "6" }),
231
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "12", x2: "21", y2: "12" }),
232
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "18", x2: "21", y2: "18" }),
233
+ /* @__PURE__ */ jsx("path", { d: "M4 6h1v4" }),
234
+ /* @__PURE__ */ jsx("path", { d: "M4 10h2" }),
235
+ /* @__PURE__ */ jsx("path", { d: "M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" })
236
+ ] });
237
+ var BlockquoteIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
238
+ /* @__PURE__ */ jsx("path", { d: "M17 6H3" }),
239
+ /* @__PURE__ */ jsx("path", { d: "M21 12H8" }),
240
+ /* @__PURE__ */ jsx("path", { d: "M21 18H8" }),
241
+ /* @__PURE__ */ jsx("path", { d: "M3 12v6" })
242
+ ] });
243
+ var BoldIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
244
+ /* @__PURE__ */ jsx("path", { d: "M14 12a4 4 0 0 0 0-8H6v8" }),
245
+ /* @__PURE__ */ jsx("path", { d: "M15 20a4 4 0 0 0 0-8H6v8Z" })
246
+ ] });
247
+ var ItalicIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
248
+ /* @__PURE__ */ jsx("line", { x1: "19", y1: "4", x2: "10", y2: "4" }),
249
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "20", x2: "5", y2: "20" }),
250
+ /* @__PURE__ */ jsx("line", { x1: "15", y1: "4", x2: "9", y2: "20" })
251
+ ] });
252
+ var StrikeIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
253
+ /* @__PURE__ */ jsx("path", { d: "M16 4H9a3 3 0 0 0-2.83 4" }),
254
+ /* @__PURE__ */ jsx("path", { d: "M14 12a4 4 0 0 1 0 8H6" }),
255
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "12", x2: "20", y2: "12" })
256
+ ] });
257
+ var UnderlineIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
258
+ /* @__PURE__ */ jsx("path", { d: "M6 4v6a6 6 0 0 0 12 0V4" }),
259
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "20", x2: "20", y2: "20" })
260
+ ] });
261
+ var CodeIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
262
+ /* @__PURE__ */ jsx("polyline", { points: "16 18 22 12 16 6" }),
263
+ /* @__PURE__ */ jsx("polyline", { points: "8 6 2 12 8 18" })
264
+ ] });
265
+ var HighlightIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
266
+ /* @__PURE__ */ jsx("path", { d: "m9 11-6 6v3h9l3-3" }),
267
+ /* @__PURE__ */ jsx("path", { d: "m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4" })
268
+ ] });
269
+ var LinkIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
270
+ /* @__PURE__ */ 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" }),
271
+ /* @__PURE__ */ 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" })
272
+ ] });
273
+ var ImageIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
274
+ /* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
275
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "2" }),
276
+ /* @__PURE__ */ jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
277
+ ] });
278
+ var AlignLeftIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
279
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "6", x2: "3", y2: "6" }),
280
+ /* @__PURE__ */ jsx("line", { x1: "15", y1: "12", x2: "3", y2: "12" }),
281
+ /* @__PURE__ */ jsx("line", { x1: "17", y1: "18", x2: "3", y2: "18" })
282
+ ] });
283
+ var AlignCenterIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
284
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "6", x2: "3", y2: "6" }),
285
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "12", x2: "3", y2: "12" }),
286
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "18", x2: "3", y2: "18" }),
287
+ /* @__PURE__ */ jsx("line", { x1: "17", y1: "12", x2: "7", y2: "12" })
288
+ ] });
289
+ var AlignRightIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
290
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "6", x2: "3", y2: "6" }),
291
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "12", x2: "9", y2: "12" }),
292
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "18", x2: "7", y2: "18" })
293
+ ] });
294
+ var TableIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
295
+ /* @__PURE__ */ jsx("path", { d: "M3 3h18v18H3z" }),
296
+ /* @__PURE__ */ jsx("path", { d: "M3 9h18" }),
297
+ /* @__PURE__ */ jsx("path", { d: "M3 15h18" }),
298
+ /* @__PURE__ */ jsx("path", { d: "M9 3v18" }),
299
+ /* @__PURE__ */ jsx("path", { d: "M15 3v18" })
300
+ ] });
301
+ var AddRowBeforeIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
302
+ /* @__PURE__ */ jsx("path", { d: "M3 13v5a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5" }),
303
+ /* @__PURE__ */ jsx("path", { d: "M3 9h18" }),
304
+ /* @__PURE__ */ jsx("path", { d: "M12 2v8" }),
305
+ /* @__PURE__ */ jsx("path", { d: "m9 5 3-3 3 3" })
306
+ ] });
307
+ var AddRowAfterIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
308
+ /* @__PURE__ */ jsx("path", { d: "M3 11V6a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5" }),
309
+ /* @__PURE__ */ jsx("path", { d: "M3 15h18" }),
310
+ /* @__PURE__ */ jsx("path", { d: "M12 22v-8" }),
311
+ /* @__PURE__ */ jsx("path", { d: "m9 19 3 3 3-3" })
312
+ ] });
313
+ var DeleteRowIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
314
+ /* @__PURE__ */ jsx("path", { d: "M3 11V6a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5" }),
315
+ /* @__PURE__ */ jsx("path", { d: "M3 15h18" }),
316
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "20", x2: "12", y2: "20" })
317
+ ] });
318
+ var AddColBeforeIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
319
+ /* @__PURE__ */ jsx("path", { d: "M13 3h5a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-5" }),
320
+ /* @__PURE__ */ jsx("path", { d: "M9 3v18" }),
321
+ /* @__PURE__ */ jsx("path", { d: "M2 12h8" }),
322
+ /* @__PURE__ */ jsx("path", { d: "m5 9-3 3 3 3" })
323
+ ] });
324
+ var AddColAfterIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
325
+ /* @__PURE__ */ jsx("path", { d: "M11 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5" }),
326
+ /* @__PURE__ */ jsx("path", { d: "M15 3v18" }),
327
+ /* @__PURE__ */ jsx("path", { d: "M22 12h-8" }),
328
+ /* @__PURE__ */ jsx("path", { d: "m19 9 3 3-3 3" })
329
+ ] });
330
+ var DeleteColIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
331
+ /* @__PURE__ */ jsx("path", { d: "M11 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5" }),
332
+ /* @__PURE__ */ jsx("path", { d: "M15 3v18" }),
333
+ /* @__PURE__ */ jsx("line", { x1: "20", y1: "18", x2: "20", y2: "12" })
334
+ ] });
335
+ var MergeCellsIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
336
+ /* @__PURE__ */ jsx("path", { d: "M15 3v18" }),
337
+ /* @__PURE__ */ jsx("path", { d: "M3 9h18" }),
338
+ /* @__PURE__ */ jsx("path", { d: "M3 15h18" }),
339
+ /* @__PURE__ */ jsx("path", { d: "M9 3v18" })
340
+ ] });
341
+ var SplitCellIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
342
+ /* @__PURE__ */ jsx("path", { d: "M3 3h18v18H3z" }),
343
+ /* @__PURE__ */ jsx("path", { d: "M3 9h18" }),
344
+ /* @__PURE__ */ jsx("path", { d: "M3 15h18" }),
345
+ /* @__PURE__ */ jsx("path", { d: "M9 3v18" }),
346
+ /* @__PURE__ */ jsx("path", { d: "M15 3v18" })
347
+ ] });
348
+ var DeleteTableIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
349
+ /* @__PURE__ */ jsx("path", { d: "M3 3h18v18H3z" }),
350
+ /* @__PURE__ */ jsx("path", { d: "M3 9h18" }),
351
+ /* @__PURE__ */ jsx("path", { d: "M3 15h18" }),
352
+ /* @__PURE__ */ jsx("path", { d: "M9 3v18" }),
353
+ /* @__PURE__ */ jsx("path", { d: "M15 3v18" }),
354
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "18", x2: "12", y2: "12" })
355
+ ] });
356
+ var ClearIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
357
+ /* @__PURE__ */ jsx("path", { d: "M18 6L6 18" }),
358
+ /* @__PURE__ */ jsx("path", { d: "M6 6l12 12" })
359
+ ] });
360
+ var SubmitIcon = () => /* @__PURE__ */ jsx(LucideSvg, { children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
361
+ var AttachIcon = () => /* @__PURE__ */ jsx(LucideSvg, { children: /* @__PURE__ */ jsx("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.82-2.82l8.49-8.48" }) });
362
+ var FontSizeIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", xmlns: "http://www.w3.org/2000/svg", children: [
363
+ /* @__PURE__ */ jsx("path", { d: "m3 7 5-5 5 5" }),
364
+ /* @__PURE__ */ jsx("path", { d: "M8 2v10" }),
365
+ /* @__PURE__ */ jsx("path", { d: "m21 17-5 5-5-5" }),
366
+ /* @__PURE__ */ jsx("path", { d: "M16 12v10" }),
367
+ /* @__PURE__ */ jsx("path", { d: "M3 13h18" })
368
+ ] });
369
+ var FontFamilyIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
370
+ /* @__PURE__ */ jsx("polyline", { points: "4 7 4 4 20 4 20 7" }),
371
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "20", x2: "15", y2: "20" }),
372
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "4", x2: "12", y2: "20" })
373
+ ] });
374
+ var TextColorIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
375
+ /* @__PURE__ */ jsx("path", { d: "M4 20h16" }),
376
+ /* @__PURE__ */ jsx("path", { d: "m6 16 6-12 6 12" }),
377
+ /* @__PURE__ */ jsx("path", { d: "M8 12h8" })
378
+ ] });
379
+ var EraserIcon = () => /* @__PURE__ */ jsxs(LucideSvg, { children: [
380
+ /* @__PURE__ */ jsx("path", { d: "m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.9-9.9c1-1 2.5-1 3.4 0l4.4 4.4c1 1 1 2.5 0 3.4L10.8 21z" }),
381
+ /* @__PURE__ */ jsx("path", { d: "m22 21h-8" }),
382
+ /* @__PURE__ */ jsx("path", { d: "m5 11 9 9" })
383
+ ] });
384
+
385
+ // src/ToolbarGroups.tsx
386
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
387
+ var HistoryGroup = ({ editor, isReadOnly }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
388
+ /* @__PURE__ */ jsx2(
389
+ "button",
390
+ {
391
+ title: "Undo",
392
+ onClick: () => editor.chain().focus().undo().run(),
393
+ onMouseDown: (e) => e.preventDefault(),
394
+ disabled: isReadOnly || !editor.can().undo(),
395
+ "aria-label": "Undo",
396
+ children: /* @__PURE__ */ jsx2(UndoIcon, {})
397
+ }
398
+ ),
399
+ /* @__PURE__ */ jsx2(
400
+ "button",
401
+ {
402
+ title: "Redo",
403
+ onClick: () => editor.chain().focus().redo().run(),
404
+ onMouseDown: (e) => e.preventDefault(),
405
+ disabled: isReadOnly || !editor.can().redo(),
406
+ "aria-label": "Redo",
407
+ children: /* @__PURE__ */ jsx2(RedoIcon, {})
408
+ }
409
+ )
410
+ ] });
411
+ var FontGroup = ({ editor, isReadOnly, showMenu, onToggle, onClose }) => /* @__PURE__ */ jsxs2("div", { style: { position: "relative", display: "inline-flex" }, children: [
412
+ /* @__PURE__ */ jsxs2(
413
+ "button",
414
+ {
415
+ title: "Font family",
416
+ onClick: onToggle,
417
+ onMouseDown: (e) => e.preventDefault(),
418
+ disabled: isReadOnly,
419
+ className: `toolbar-button-dropdown ${showMenu ? "is-active" : ""}`,
420
+ "aria-label": "Font family selection",
421
+ children: [
422
+ /* @__PURE__ */ jsx2(FontFamilyIcon, {}),
423
+ /* @__PURE__ */ jsx2("svg", { className: "dropdown-arrow", viewBox: "0 0 24 24", width: "10", height: "10", children: /* @__PURE__ */ jsx2("path", { d: "M7 10l5 5 5-5z", fill: "currentColor" }) })
424
+ ]
425
+ }
426
+ ),
427
+ showMenu && !isReadOnly && /* @__PURE__ */ jsx2("div", { className: "tiptap-font-menu", children: FONT_FAMILIES.map((font) => {
428
+ const isActive = editor.getAttributes("textStyle").fontFamily === font.value;
429
+ return /* @__PURE__ */ jsx2(
430
+ "button",
431
+ {
432
+ className: `font-option ${isActive ? "is-active" : ""}`,
433
+ style: { fontFamily: font.value || "inherit" },
434
+ onClick: () => {
435
+ if (font.value) {
436
+ editor.chain().focus().setFontFamily(font.value).run();
437
+ } else {
438
+ editor.chain().focus().unsetFontFamily().run();
439
+ }
440
+ onClose();
441
+ },
442
+ children: font.label
443
+ },
444
+ font.label
445
+ );
446
+ }) })
447
+ ] });
448
+ var FontSizeGroup = ({ editor, isReadOnly, showMenu, onToggle, onClose }) => /* @__PURE__ */ jsxs2("div", { style: { position: "relative", display: "inline-flex" }, children: [
449
+ /* @__PURE__ */ jsxs2(
450
+ "button",
451
+ {
452
+ title: "Font size",
453
+ onClick: onToggle,
454
+ onMouseDown: (e) => e.preventDefault(),
455
+ disabled: isReadOnly,
456
+ className: `toolbar-button-dropdown ${showMenu ? "is-active" : ""}`,
457
+ "aria-label": "Font size selection",
458
+ children: [
459
+ /* @__PURE__ */ jsx2(FontSizeIcon, {}),
460
+ /* @__PURE__ */ jsx2("svg", { className: "dropdown-arrow", viewBox: "0 0 24 24", width: "10", height: "10", children: /* @__PURE__ */ jsx2("path", { d: "M7 10l5 5 5-5z", fill: "currentColor" }) })
461
+ ]
462
+ }
463
+ ),
464
+ showMenu && !isReadOnly && /* @__PURE__ */ jsx2("div", { className: "tiptap-font-menu", children: FONT_SIZES.map((size) => {
465
+ const currentSize = editor.getAttributes("textStyle").fontSize;
466
+ const mapping = {
467
+ "tiny": "0.7em",
468
+ "small": "0.85em",
469
+ "big": "1.4em",
470
+ "huge": "1.8em"
471
+ };
472
+ const targetValue = mapping[size.value] || size.value || null;
473
+ const isActive = !currentSize && !size.value || currentSize === targetValue;
474
+ return /* @__PURE__ */ jsx2(
475
+ "button",
476
+ {
477
+ className: `font-option ${isActive ? "is-active" : ""}`,
478
+ onClick: () => {
479
+ if (size.value) {
480
+ editor.chain().focus().setFontSize(size.value).run();
481
+ } else {
482
+ editor.chain().focus().unsetFontSize().run();
483
+ }
484
+ onClose();
485
+ },
486
+ children: size.label
487
+ },
488
+ size.label
489
+ );
490
+ }) })
491
+ ] });
492
+ var TextColorGroup = ({ editor, isReadOnly, showMenu, onToggle, onClose }) => /* @__PURE__ */ jsxs2("div", { style: { position: "relative", display: "inline-flex" }, children: [
493
+ /* @__PURE__ */ jsxs2(
494
+ "button",
495
+ {
496
+ title: "Text color",
497
+ onClick: onToggle,
498
+ onMouseDown: (e) => e.preventDefault(),
499
+ disabled: isReadOnly,
500
+ className: `toolbar-button-dropdown ${showMenu ? "is-active" : ""}`,
501
+ "aria-label": "Text color picker",
502
+ children: [
503
+ /* @__PURE__ */ jsx2(TextColorIcon, {}),
504
+ /* @__PURE__ */ jsx2("svg", { className: "dropdown-arrow", viewBox: "0 0 24 24", width: "10", height: "10", children: /* @__PURE__ */ jsx2("path", { d: "M7 10l5 5 5-5z", fill: "currentColor" }) })
505
+ ]
506
+ }
507
+ ),
508
+ showMenu && !isReadOnly && /* @__PURE__ */ jsxs2("div", { className: "tiptap-color-picker", children: [
509
+ /* @__PURE__ */ jsxs2(
510
+ "button",
511
+ {
512
+ className: "color-remove-btn",
513
+ onClick: () => {
514
+ editor.chain().focus().unsetColor().run();
515
+ onClose();
516
+ },
517
+ children: [
518
+ /* @__PURE__ */ jsx2(EraserIcon, {}),
519
+ " Remove color"
520
+ ]
521
+ }
522
+ ),
523
+ /* @__PURE__ */ jsx2("div", { className: "color-grid", children: TIPTAP_COLORS.map((color) => /* @__PURE__ */ jsx2(
524
+ "button",
525
+ {
526
+ className: `color-swatch ${editor.isActive("textStyle", { color }) ? "is-active" : ""}`,
527
+ style: { backgroundColor: color },
528
+ onClick: () => {
529
+ editor.chain().focus().setColor(color).run();
530
+ onClose();
531
+ }
532
+ },
533
+ color
534
+ )) })
535
+ ] })
536
+ ] });
537
+ var HighlightGroup = ({ editor, isReadOnly, showMenu, onToggle, onClose }) => /* @__PURE__ */ jsxs2("div", { style: { position: "relative", display: "inline-flex" }, children: [
538
+ /* @__PURE__ */ jsxs2(
539
+ "button",
540
+ {
541
+ title: "Highlight",
542
+ onClick: onToggle,
543
+ onMouseDown: (e) => e.preventDefault(),
544
+ disabled: isReadOnly,
545
+ className: `toolbar-button-dropdown ${showMenu ? "is-active" : ""}`,
546
+ "aria-label": "Background color picker",
547
+ children: [
548
+ /* @__PURE__ */ jsx2(HighlightIcon, {}),
549
+ /* @__PURE__ */ jsx2("svg", { className: "dropdown-arrow", viewBox: "0 0 24 24", width: "10", height: "10", children: /* @__PURE__ */ jsx2("path", { d: "M7 10l5 5 5-5z", fill: "currentColor" }) })
550
+ ]
551
+ }
552
+ ),
553
+ showMenu && !isReadOnly && /* @__PURE__ */ jsxs2("div", { className: "tiptap-color-picker", children: [
554
+ /* @__PURE__ */ jsxs2(
555
+ "button",
556
+ {
557
+ className: "color-remove-btn",
558
+ onClick: () => {
559
+ editor.chain().focus().unsetBackgroundColor().run();
560
+ onClose();
561
+ },
562
+ children: [
563
+ /* @__PURE__ */ jsx2(EraserIcon, {}),
564
+ " Remove color"
565
+ ]
566
+ }
567
+ ),
568
+ /* @__PURE__ */ jsx2("div", { className: "color-grid", children: TIPTAP_COLORS.map((color) => /* @__PURE__ */ jsx2(
569
+ "button",
570
+ {
571
+ className: `color-swatch ${editor.isActive("textStyle", { backgroundColor: color }) ? "is-active" : ""}`,
572
+ style: { backgroundColor: color },
573
+ onClick: () => {
574
+ editor.chain().focus().setBackgroundColor(color).run();
575
+ onClose();
576
+ }
577
+ },
578
+ color
579
+ )) })
580
+ ] })
581
+ ] });
582
+ var ListGroup = ({ editor, isReadOnly }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
583
+ /* @__PURE__ */ jsx2(
584
+ "button",
585
+ {
586
+ title: "Bullet list",
587
+ onClick: () => editor.chain().focus().toggleBulletList().run(),
588
+ onMouseDown: (e) => e.preventDefault(),
589
+ disabled: isReadOnly,
590
+ className: editor.isActive("bulletList") ? "is-active" : "",
591
+ "aria-label": "Bullet list",
592
+ children: /* @__PURE__ */ jsx2(BulletListIcon, {})
593
+ }
594
+ ),
595
+ /* @__PURE__ */ jsx2(
596
+ "button",
597
+ {
598
+ title: "Ordered list",
599
+ onClick: () => editor.chain().focus().toggleOrderedList().run(),
600
+ onMouseDown: (e) => e.preventDefault(),
601
+ disabled: isReadOnly,
602
+ className: editor.isActive("orderedList") ? "is-active" : "",
603
+ "aria-label": "Ordered list",
604
+ children: /* @__PURE__ */ jsx2(OrderedListIcon, {})
605
+ }
606
+ ),
607
+ /* @__PURE__ */ jsx2(
608
+ "button",
609
+ {
610
+ title: "Blockquote",
611
+ onClick: () => editor.chain().focus().toggleBlockquote().run(),
612
+ onMouseDown: (e) => e.preventDefault(),
613
+ disabled: isReadOnly,
614
+ className: editor.isActive("blockquote") ? "is-active" : "",
615
+ "aria-label": "Blockquote",
616
+ children: /* @__PURE__ */ jsx2(BlockquoteIcon, {})
617
+ }
618
+ )
619
+ ] });
620
+ var InlineGroup = ({ editor, isReadOnly }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
621
+ /* @__PURE__ */ jsx2(
622
+ "button",
623
+ {
624
+ title: "Bold",
625
+ onClick: () => editor.chain().focus().toggleBold().run(),
626
+ onMouseDown: (e) => e.preventDefault(),
627
+ disabled: isReadOnly,
628
+ className: editor.isActive("bold") ? "is-active" : "",
629
+ "aria-label": "Bold",
630
+ children: /* @__PURE__ */ jsx2(BoldIcon, {})
631
+ }
632
+ ),
633
+ /* @__PURE__ */ jsx2(
634
+ "button",
635
+ {
636
+ title: "Italic",
637
+ onClick: () => editor.chain().focus().toggleItalic().run(),
638
+ onMouseDown: (e) => e.preventDefault(),
639
+ disabled: isReadOnly,
640
+ className: editor.isActive("italic") ? "is-active" : "",
641
+ "aria-label": "Italic",
642
+ children: /* @__PURE__ */ jsx2(ItalicIcon, {})
643
+ }
644
+ ),
645
+ /* @__PURE__ */ jsx2(
646
+ "button",
647
+ {
648
+ title: "Strikethrough",
649
+ onClick: () => editor.chain().focus().toggleStrike().run(),
650
+ onMouseDown: (e) => e.preventDefault(),
651
+ disabled: isReadOnly,
652
+ className: editor.isActive("strike") ? "is-active" : "",
653
+ "aria-label": "Strikethrough",
654
+ children: /* @__PURE__ */ jsx2(StrikeIcon, {})
655
+ }
656
+ ),
657
+ /* @__PURE__ */ jsx2(
658
+ "button",
659
+ {
660
+ title: "Underline",
661
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
662
+ onMouseDown: (e) => e.preventDefault(),
663
+ disabled: isReadOnly,
664
+ className: editor.isActive("underline") ? "is-active" : "",
665
+ "aria-label": "Underline",
666
+ children: /* @__PURE__ */ jsx2(UnderlineIcon, {})
667
+ }
668
+ ),
669
+ /* @__PURE__ */ jsx2(
670
+ "button",
671
+ {
672
+ title: "Code",
673
+ onClick: () => editor.chain().focus().toggleCode().run(),
674
+ onMouseDown: (e) => e.preventDefault(),
675
+ disabled: isReadOnly,
676
+ className: editor.isActive("code") ? "is-active" : "",
677
+ "aria-label": "Inline code",
678
+ children: /* @__PURE__ */ jsx2(CodeIcon, {})
679
+ }
680
+ )
681
+ ] });
682
+ var LinkGroup = ({ editor, isReadOnly, showMenu, onToggle, onClose, linkUrl, onLinkUrlChange, onSubmit }) => /* @__PURE__ */ jsxs2("div", { style: { position: "relative", display: "inline-flex" }, children: [
683
+ /* @__PURE__ */ jsx2(
684
+ "button",
685
+ {
686
+ title: "Insert link",
687
+ onClick: onToggle,
688
+ onMouseDown: (e) => e.preventDefault(),
689
+ disabled: isReadOnly,
690
+ className: editor.isActive("link") ? "is-active" : "",
691
+ "aria-label": "Insert link",
692
+ children: /* @__PURE__ */ jsx2(LinkIcon, {})
693
+ }
694
+ ),
695
+ showMenu && !isReadOnly && /* @__PURE__ */ jsxs2("div", { className: "tiptap-link-menu", children: [
696
+ /* @__PURE__ */ jsx2(
697
+ "input",
698
+ {
699
+ type: "url",
700
+ placeholder: "Paste a link...",
701
+ value: linkUrl,
702
+ onChange: (e) => onLinkUrlChange(e.target.value),
703
+ onKeyDown: (e) => {
704
+ if (e.key === "Enter") onSubmit();
705
+ if (e.key === "Escape") onClose();
706
+ },
707
+ autoFocus: true
708
+ }
709
+ ),
710
+ /* @__PURE__ */ jsx2("button", { onClick: onSubmit, title: "Apply", className: "link-submit-btn", children: /* @__PURE__ */ jsx2(SubmitIcon, {}) }),
711
+ /* @__PURE__ */ jsx2(
712
+ "button",
713
+ {
714
+ onClick: () => {
715
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
716
+ onClose();
717
+ },
718
+ title: "Unlink",
719
+ className: "link-clear-btn",
720
+ children: /* @__PURE__ */ jsx2(ClearIcon, {})
721
+ }
722
+ )
723
+ ] })
724
+ ] });
725
+ var AlignmentGroup = ({ editor, isReadOnly }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
726
+ /* @__PURE__ */ jsx2(
727
+ "button",
728
+ {
729
+ title: "Align left",
730
+ onClick: () => editor.chain().focus().setTextAlign("left").run(),
731
+ onMouseDown: (e) => e.preventDefault(),
732
+ disabled: isReadOnly,
733
+ className: editor.isActive({ textAlign: "left" }) ? "is-active" : "",
734
+ "aria-label": "Align left",
735
+ children: /* @__PURE__ */ jsx2(AlignLeftIcon, {})
736
+ }
737
+ ),
738
+ /* @__PURE__ */ jsx2(
739
+ "button",
740
+ {
741
+ title: "Align center",
742
+ onClick: () => editor.chain().focus().setTextAlign("center").run(),
743
+ onMouseDown: (e) => e.preventDefault(),
744
+ disabled: isReadOnly,
745
+ className: editor.isActive({ textAlign: "center" }) ? "is-active" : "",
746
+ "aria-label": "Align center",
747
+ children: /* @__PURE__ */ jsx2(AlignCenterIcon, {})
748
+ }
749
+ ),
750
+ /* @__PURE__ */ jsx2(
751
+ "button",
752
+ {
753
+ title: "Align right",
754
+ onClick: () => editor.chain().focus().setTextAlign("right").run(),
755
+ onMouseDown: (e) => e.preventDefault(),
756
+ disabled: isReadOnly,
757
+ className: editor.isActive({ textAlign: "right" }) ? "is-active" : "",
758
+ "aria-label": "Align right",
759
+ children: /* @__PURE__ */ jsx2(AlignRightIcon, {})
760
+ }
761
+ )
762
+ ] });
763
+ var TableGroup = ({ editor, isReadOnly, showMenu, onToggle, onClose, tableHoverSize, onGridHover }) => /* @__PURE__ */ jsxs2("div", { style: { position: "relative", display: "inline-flex" }, children: [
764
+ /* @__PURE__ */ jsxs2(
765
+ "button",
766
+ {
767
+ title: "Table",
768
+ onClick: onToggle,
769
+ onMouseDown: (e) => e.preventDefault(),
770
+ disabled: isReadOnly,
771
+ className: `toolbar-button-dropdown ${editor.isActive("table") ? "is-active" : ""}`,
772
+ "aria-label": "Table options",
773
+ children: [
774
+ /* @__PURE__ */ jsx2(TableIcon, {}),
775
+ /* @__PURE__ */ jsx2("svg", { className: "dropdown-arrow", viewBox: "0 0 24 24", width: "10", height: "10", children: /* @__PURE__ */ jsx2("path", { d: "M7 10l5 5 5-5z", fill: "currentColor" }) })
776
+ ]
777
+ }
778
+ ),
779
+ showMenu && !isReadOnly && /* @__PURE__ */ jsx2("div", { className: "tiptap-table-menu", children: !editor.isActive("table") ? /* @__PURE__ */ jsxs2(
780
+ "div",
781
+ {
782
+ className: "table-grid-picker-wrapper",
783
+ onMouseLeave: () => onGridHover(0, 0),
784
+ children: [
785
+ /* @__PURE__ */ jsx2("div", { className: "table-grid-picker", children: [...Array(10)].map((_, r) => /* @__PURE__ */ jsx2("div", { className: "grid-row", children: [...Array(10)].map((_2, c) => /* @__PURE__ */ jsx2(
786
+ "div",
787
+ {
788
+ className: `grid-cell ${r + 1 <= tableHoverSize.rows && c + 1 <= tableHoverSize.cols ? "is-active" : ""}`,
789
+ onMouseEnter: () => onGridHover(r + 1, c + 1),
790
+ onClick: () => {
791
+ editor.chain().focus().insertTable({ rows: r + 1, cols: c + 1, withHeaderRow: true }).run();
792
+ onClose();
793
+ onGridHover(0, 0);
794
+ }
795
+ },
796
+ c
797
+ )) }, r)) }),
798
+ /* @__PURE__ */ jsx2("div", { className: "table-grid-label", children: tableHoverSize.rows > 0 ? `${tableHoverSize.rows} x ${tableHoverSize.cols}` : "Square Grid" })
799
+ ]
800
+ }
801
+ ) : /* @__PURE__ */ jsxs2("div", { className: "table-menu-grid", children: [
802
+ /* @__PURE__ */ jsx2("button", { onClick: () => editor.chain().focus().addColumnBefore().run(), title: "Add column before", children: /* @__PURE__ */ jsx2(AddColBeforeIcon, {}) }),
803
+ /* @__PURE__ */ jsx2("button", { onClick: () => editor.chain().focus().addColumnAfter().run(), title: "Add column after", children: /* @__PURE__ */ jsx2(AddColAfterIcon, {}) }),
804
+ /* @__PURE__ */ jsx2("button", { onClick: () => editor.chain().focus().deleteColumn().run(), title: "Delete column", children: /* @__PURE__ */ jsx2(DeleteColIcon, {}) }),
805
+ /* @__PURE__ */ jsx2("button", { onClick: () => editor.chain().focus().addRowBefore().run(), title: "Add row before", children: /* @__PURE__ */ jsx2(AddRowBeforeIcon, {}) }),
806
+ /* @__PURE__ */ jsx2("button", { onClick: () => editor.chain().focus().addRowAfter().run(), title: "Add row after", children: /* @__PURE__ */ jsx2(AddRowAfterIcon, {}) }),
807
+ /* @__PURE__ */ jsx2("button", { onClick: () => editor.chain().focus().deleteRow().run(), title: "Delete row", children: /* @__PURE__ */ jsx2(DeleteRowIcon, {}) }),
808
+ /* @__PURE__ */ jsx2("button", { onClick: () => editor.chain().focus().mergeCells().run(), title: "Merge cells", children: /* @__PURE__ */ jsx2(MergeCellsIcon, {}) }),
809
+ /* @__PURE__ */ jsx2("button", { onClick: () => editor.chain().focus().splitCell().run(), title: "Split cell", children: /* @__PURE__ */ jsx2(SplitCellIcon, {}) }),
810
+ /* @__PURE__ */ jsx2("button", { onClick: () => {
811
+ editor.chain().focus().deleteTable().run();
812
+ onClose();
813
+ }, className: "danger", title: "Delete table", children: /* @__PURE__ */ jsx2(DeleteTableIcon, {}) })
814
+ ] }) })
815
+ ] });
816
+ var InsertGroup = ({ isReadOnly, onImageClick, onAttachClick }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
817
+ /* @__PURE__ */ jsx2(
818
+ "button",
819
+ {
820
+ title: "Insert image",
821
+ onClick: onImageClick,
822
+ onMouseDown: (e) => e.preventDefault(),
823
+ disabled: isReadOnly,
824
+ "aria-label": "Insert image",
825
+ children: /* @__PURE__ */ jsx2(ImageIcon, {})
826
+ }
827
+ ),
828
+ /* @__PURE__ */ jsx2(
829
+ "button",
830
+ {
831
+ title: "Attach file",
832
+ onClick: onAttachClick,
833
+ onMouseDown: (e) => e.preventDefault(),
834
+ disabled: isReadOnly,
835
+ "aria-label": "Attach file",
836
+ children: /* @__PURE__ */ jsx2(AttachIcon, {})
837
+ }
838
+ )
839
+ ] });
840
+
841
+ // src/TiptapEditor.tsx
842
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
843
+ var TiptapEditor = ({
844
+ elementId,
845
+ content,
846
+ editMode,
847
+ docNum,
848
+ receiveData,
849
+ receiveStatus,
850
+ onUploadFile,
851
+ onAlert,
852
+ onLoadingChange,
853
+ onRegisterReset,
854
+ maxFileSizeMB = 50,
855
+ placeholder = "Enter content here...",
856
+ toolbarConfig
857
+ }) => {
858
+ const alertFn = onAlert || ((msg) => window.alert(msg));
859
+ const loadingChangeFn = onLoadingChange || (() => {
860
+ });
861
+ const isReadOnly = !editMode;
862
+ const containerRef = useRef(null);
863
+ const imageInputRef = useRef(null);
864
+ const attachInputRef = useRef(null);
865
+ const [showHighlightMenu, setShowHighlightMenu] = React.useState(false);
866
+ const [showTextColorMenu, setShowTextColorMenu] = React.useState(false);
867
+ const [showFontMenu, setShowFontMenu] = React.useState(false);
868
+ const [showFontSizeMenu, setShowFontSizeMenu] = React.useState(false);
869
+ const [showLinkMenu, setShowLinkMenu] = React.useState(false);
870
+ const [showTableMenu, setShowTableMenu] = React.useState(false);
871
+ const [tableHoverSize, setTableHoverSize] = React.useState({ rows: 0, cols: 0 });
872
+ const [linkUrl, setLinkUrl] = React.useState("");
873
+ const initializedRef = useRef(false);
874
+ const lastEmittedContentRef = useRef(content || "");
875
+ const lastUpdateTimestampRef = useRef(0);
876
+ const activeUploadsRef = useRef(0);
877
+ const closeAllMenus = React.useCallback(() => {
878
+ setShowHighlightMenu(false);
879
+ setShowTextColorMenu(false);
880
+ setShowFontMenu(false);
881
+ setShowFontSizeMenu(false);
882
+ setShowLinkMenu(false);
883
+ setShowTableMenu(false);
884
+ }, []);
885
+ React.useEffect(() => {
886
+ const handleClickOutside = (event) => {
887
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
888
+ closeAllMenus();
889
+ }
890
+ };
891
+ document.addEventListener("mousedown", handleClickOutside);
892
+ return () => {
893
+ document.removeEventListener("mousedown", handleClickOutside);
894
+ };
895
+ }, [closeAllMenus]);
896
+ const editor = useEditor({
897
+ immediatelyRender: false,
898
+ extensions: createTiptapExtensions(placeholder),
899
+ content: content || "",
900
+ editable: editMode,
901
+ onFocus: () => {
902
+ closeAllMenus();
903
+ },
904
+ onSelectionUpdate: () => {
905
+ closeAllMenus();
906
+ },
907
+ editorProps: {
908
+ // Handle clipboard paste - support pasting images (e.g. screenshots)
909
+ handlePaste: (view, event) => {
910
+ if (!editMode) return false;
911
+ const clipboardData = event.clipboardData;
912
+ if (!clipboardData) return false;
913
+ const items = Array.from(clipboardData.items);
914
+ const imageItems = items.filter((item) => item.kind === "file" && IMAGE_MIME_TYPES.includes(item.type));
915
+ if (imageItems.length > 0) {
916
+ event.preventDefault();
917
+ if (!onUploadFile) {
918
+ alertFn("Upload function not provided");
919
+ return true;
920
+ }
921
+ activeUploadsRef.current += 1;
922
+ loadingChangeFn(true);
923
+ Promise.all(
924
+ imageItems.map((item) => {
925
+ const file = item.getAsFile();
926
+ if (!file) return Promise.resolve();
927
+ return uploadFileToCOS(
928
+ file,
929
+ docNum,
930
+ onUploadFile,
931
+ receiveStatus,
932
+ elementId,
933
+ loadingChangeFn,
934
+ true,
935
+ maxFileSizeMB
936
+ ).then((url) => {
937
+ view.dispatch(
938
+ view.state.tr.replaceSelectionWith(
939
+ view.state.schema.nodes.image.create({ src: url, alt: file.name })
940
+ )
941
+ );
942
+ }).catch((err) => {
943
+ alertFn(err.message || "Image upload failed");
944
+ });
945
+ })
946
+ ).finally(() => {
947
+ activeUploadsRef.current -= 1;
948
+ loadingChangeFn(false);
949
+ if (activeUploadsRef.current === 0) {
950
+ const html = editor == null ? void 0 : editor.getHTML();
951
+ if (html) {
952
+ lastEmittedContentRef.current = html;
953
+ receiveData && receiveData(elementId, html);
954
+ }
955
+ }
956
+ });
957
+ return true;
958
+ }
959
+ return false;
960
+ }
961
+ },
962
+ onUpdate: ({ editor: editor2 }) => {
963
+ const html = editor2.getHTML();
964
+ lastEmittedContentRef.current = html;
965
+ lastUpdateTimestampRef.current = Date.now();
966
+ if (activeUploadsRef.current === 0) {
967
+ receiveData && receiveData(elementId, html);
968
+ }
969
+ }
970
+ });
971
+ useEffect(() => {
972
+ if (!editor) return;
973
+ if (!initializedRef.current) {
974
+ initializedRef.current = true;
975
+ if (content && content !== editor.getHTML()) {
976
+ editor.commands.setContent(content || "", false);
977
+ lastEmittedContentRef.current = content;
978
+ }
979
+ return;
980
+ }
981
+ if (Date.now() - lastUpdateTimestampRef.current < 150) {
982
+ return;
983
+ }
984
+ if (editor.isFocused) {
985
+ return;
986
+ }
987
+ if (content === lastEmittedContentRef.current) {
988
+ return;
989
+ }
990
+ if (content !== editor.getHTML()) {
991
+ editor.commands.setContent(content || "", false);
992
+ lastEmittedContentRef.current = content || "";
993
+ }
994
+ }, [content, editor]);
995
+ useEffect(() => {
996
+ if (!editor) return;
997
+ editor.setEditable(editMode);
998
+ }, [editMode, editor]);
999
+ useEffect(() => {
1000
+ if (!editor || !onRegisterReset) return;
1001
+ const handleReset = (newContent) => {
1002
+ editor.commands.setContent(newContent || "", false);
1003
+ };
1004
+ const unregister = onRegisterReset(handleReset);
1005
+ return () => {
1006
+ unregister && unregister();
1007
+ };
1008
+ }, [editor, onRegisterReset]);
1009
+ const handleImageFileChange = useCallback(
1010
+ async (e) => {
1011
+ const files = Array.from(e.target.files || []);
1012
+ if (files.length === 0 || !editor) return;
1013
+ e.target.value = "";
1014
+ if (!onUploadFile) {
1015
+ alertFn("Upload function not provided");
1016
+ return;
1017
+ }
1018
+ activeUploadsRef.current += 1;
1019
+ loadingChangeFn(true);
1020
+ try {
1021
+ await Promise.all(
1022
+ files.map(async (file) => {
1023
+ const url = await uploadFileToCOS(
1024
+ file,
1025
+ docNum,
1026
+ onUploadFile,
1027
+ receiveStatus,
1028
+ elementId,
1029
+ loadingChangeFn,
1030
+ true,
1031
+ maxFileSizeMB
1032
+ );
1033
+ editor.chain().focus().setImage({ src: url, alt: file.name }).run();
1034
+ })
1035
+ );
1036
+ } catch (err) {
1037
+ alertFn(err.message || "Image upload failed");
1038
+ } finally {
1039
+ activeUploadsRef.current -= 1;
1040
+ loadingChangeFn(false);
1041
+ if (activeUploadsRef.current === 0) {
1042
+ const html = editor.getHTML();
1043
+ lastEmittedContentRef.current = html;
1044
+ receiveData && receiveData(elementId, html);
1045
+ }
1046
+ }
1047
+ },
1048
+ [editor, docNum, receiveStatus, elementId, receiveData, onUploadFile, alertFn, loadingChangeFn, maxFileSizeMB]
1049
+ );
1050
+ const handleAttachFileChange = useCallback(
1051
+ async (e) => {
1052
+ const files = Array.from(e.target.files || []);
1053
+ if (files.length === 0 || !editor) return;
1054
+ e.target.value = "";
1055
+ if (!onUploadFile) {
1056
+ alertFn("Upload function not provided");
1057
+ return;
1058
+ }
1059
+ activeUploadsRef.current += 1;
1060
+ loadingChangeFn(true);
1061
+ try {
1062
+ await Promise.all(
1063
+ files.map(async (file) => {
1064
+ const url = await uploadFileToCOS(
1065
+ file,
1066
+ docNum,
1067
+ onUploadFile,
1068
+ receiveStatus,
1069
+ elementId,
1070
+ loadingChangeFn,
1071
+ true,
1072
+ maxFileSizeMB
1073
+ );
1074
+ editor.chain().focus().insertContent(
1075
+ `<a href="${url}" target="_blank" rel="noopener noreferrer" data-attachment="true">${file.name}</a>&nbsp;`
1076
+ ).run();
1077
+ })
1078
+ );
1079
+ } catch (err) {
1080
+ alertFn(err.message || "Attachment upload failed");
1081
+ } finally {
1082
+ activeUploadsRef.current -= 1;
1083
+ loadingChangeFn(false);
1084
+ if (activeUploadsRef.current === 0) {
1085
+ const html = editor.getHTML();
1086
+ lastEmittedContentRef.current = html;
1087
+ receiveData && receiveData(elementId, html);
1088
+ }
1089
+ }
1090
+ },
1091
+ [editor, docNum, receiveStatus, elementId, receiveData, onUploadFile, alertFn, loadingChangeFn, maxFileSizeMB]
1092
+ );
1093
+ const handleLinkClick = useCallback(() => {
1094
+ if (!editor || isReadOnly) return;
1095
+ const nextState = !showLinkMenu;
1096
+ closeAllMenus();
1097
+ if (nextState) {
1098
+ const previousUrl = editor.getAttributes("link").href;
1099
+ setLinkUrl(previousUrl || "");
1100
+ }
1101
+ setShowLinkMenu(nextState);
1102
+ }, [editor, isReadOnly, showLinkMenu, closeAllMenus]);
1103
+ const submitLink = useCallback(() => {
1104
+ if (!editor) return;
1105
+ if (linkUrl === null || linkUrl.trim() === "") {
1106
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
1107
+ } else {
1108
+ const formattedUrl = /^https?:\/\//.test(linkUrl) ? linkUrl : `https://${linkUrl}`;
1109
+ editor.chain().focus().extendMarkRange("link").setLink({ href: formattedUrl }).run();
1110
+ }
1111
+ setShowLinkMenu(false);
1112
+ }, [editor, linkUrl]);
1113
+ if (!editor) return null;
1114
+ const getHiddenInputs = () => /* @__PURE__ */ jsxs3(Fragment2, { children: [
1115
+ /* @__PURE__ */ jsx3(
1116
+ "input",
1117
+ {
1118
+ ref: imageInputRef,
1119
+ type: "file",
1120
+ accept: "image/*",
1121
+ multiple: true,
1122
+ className: "tiptap-hidden-input",
1123
+ onChange: handleImageFileChange
1124
+ }
1125
+ ),
1126
+ /* @__PURE__ */ jsx3(
1127
+ "input",
1128
+ {
1129
+ ref: attachInputRef,
1130
+ type: "file",
1131
+ accept: "*/*",
1132
+ multiple: true,
1133
+ className: "tiptap-hidden-input",
1134
+ onChange: handleAttachFileChange
1135
+ }
1136
+ )
1137
+ ] });
1138
+ const defaultGroups = [
1139
+ "history",
1140
+ "font",
1141
+ "fontSize",
1142
+ "textColor",
1143
+ "highlight",
1144
+ "list",
1145
+ "alignment",
1146
+ "table",
1147
+ "inline",
1148
+ "link",
1149
+ "insert"
1150
+ ];
1151
+ const toolbarGroups = (toolbarConfig == null ? void 0 : toolbarConfig.groups) || defaultGroups;
1152
+ const showDividers = (toolbarConfig == null ? void 0 : toolbarConfig.showDividers) !== void 0 ? toolbarConfig.showDividers : true;
1153
+ const getToolbar = () => {
1154
+ const toolbarComponents = {
1155
+ history: /* @__PURE__ */ jsx3(HistoryGroup, { editor, isReadOnly }),
1156
+ font: /* @__PURE__ */ jsx3(
1157
+ FontGroup,
1158
+ {
1159
+ editor,
1160
+ isReadOnly,
1161
+ showMenu: showFontMenu,
1162
+ onToggle: () => {
1163
+ const nextState = !showFontMenu;
1164
+ closeAllMenus();
1165
+ setShowFontMenu(nextState);
1166
+ },
1167
+ onClose: () => setShowFontMenu(false)
1168
+ }
1169
+ ),
1170
+ fontSize: /* @__PURE__ */ jsx3(
1171
+ FontSizeGroup,
1172
+ {
1173
+ editor,
1174
+ isReadOnly,
1175
+ showMenu: showFontSizeMenu,
1176
+ onToggle: () => {
1177
+ const nextState = !showFontSizeMenu;
1178
+ closeAllMenus();
1179
+ setShowFontSizeMenu(nextState);
1180
+ },
1181
+ onClose: () => setShowFontSizeMenu(false)
1182
+ }
1183
+ ),
1184
+ textColor: /* @__PURE__ */ jsx3(
1185
+ TextColorGroup,
1186
+ {
1187
+ editor,
1188
+ isReadOnly,
1189
+ showMenu: showTextColorMenu,
1190
+ onToggle: () => {
1191
+ const nextState = !showTextColorMenu;
1192
+ closeAllMenus();
1193
+ setShowTextColorMenu(nextState);
1194
+ },
1195
+ onClose: () => setShowTextColorMenu(false)
1196
+ }
1197
+ ),
1198
+ highlight: /* @__PURE__ */ jsx3(
1199
+ HighlightGroup,
1200
+ {
1201
+ editor,
1202
+ isReadOnly,
1203
+ showMenu: showHighlightMenu,
1204
+ onToggle: () => {
1205
+ const nextState = !showHighlightMenu;
1206
+ closeAllMenus();
1207
+ setShowHighlightMenu(nextState);
1208
+ },
1209
+ onClose: () => setShowHighlightMenu(false)
1210
+ }
1211
+ ),
1212
+ list: /* @__PURE__ */ jsx3(ListGroup, { editor, isReadOnly }),
1213
+ alignment: /* @__PURE__ */ jsx3(AlignmentGroup, { editor, isReadOnly }),
1214
+ table: /* @__PURE__ */ jsx3(
1215
+ TableGroup,
1216
+ {
1217
+ editor,
1218
+ isReadOnly,
1219
+ showMenu: showTableMenu,
1220
+ onToggle: () => !isReadOnly && setShowTableMenu(!showTableMenu),
1221
+ onClose: () => setShowTableMenu(false),
1222
+ tableHoverSize,
1223
+ onGridHover: (rows, cols) => setTableHoverSize({ rows, cols })
1224
+ }
1225
+ ),
1226
+ inline: /* @__PURE__ */ jsx3(InlineGroup, { editor, isReadOnly }),
1227
+ link: /* @__PURE__ */ jsx3(
1228
+ LinkGroup,
1229
+ {
1230
+ editor,
1231
+ isReadOnly,
1232
+ showMenu: showLinkMenu,
1233
+ onToggle: handleLinkClick,
1234
+ onClose: () => setShowLinkMenu(false),
1235
+ linkUrl,
1236
+ onLinkUrlChange: setLinkUrl,
1237
+ onSubmit: submitLink
1238
+ }
1239
+ ),
1240
+ insert: /* @__PURE__ */ jsx3(
1241
+ InsertGroup,
1242
+ {
1243
+ editor,
1244
+ isReadOnly,
1245
+ onImageClick: () => {
1246
+ var _a;
1247
+ return !isReadOnly && ((_a = imageInputRef.current) == null ? void 0 : _a.click());
1248
+ },
1249
+ onAttachClick: () => {
1250
+ var _a;
1251
+ return !isReadOnly && ((_a = attachInputRef.current) == null ? void 0 : _a.click());
1252
+ }
1253
+ }
1254
+ )
1255
+ };
1256
+ const toolbarItems = toolbarGroups.map((group, index) => {
1257
+ const component = toolbarComponents[group];
1258
+ if (!component) return null;
1259
+ const divider = showDividers && index > 0 ? /* @__PURE__ */ jsx3("span", { className: "toolbar-divider" }) : null;
1260
+ return /* @__PURE__ */ jsxs3(React.Fragment, { children: [
1261
+ divider,
1262
+ component
1263
+ ] }, group);
1264
+ }).filter(Boolean);
1265
+ return /* @__PURE__ */ jsx3(
1266
+ "div",
1267
+ {
1268
+ className: `tiptap-toolbar${isReadOnly ? " toolbar-disabled" : ""}`,
1269
+ role: "toolbar",
1270
+ "aria-label": "Editor toolbar",
1271
+ children: toolbarItems
1272
+ }
1273
+ );
1274
+ };
1275
+ return /* @__PURE__ */ jsxs3("div", { id: elementId, className: `tiptap-editor-wrapper${isReadOnly ? " read-only" : ""}`, ref: containerRef, children: [
1276
+ getHiddenInputs(),
1277
+ getToolbar(),
1278
+ /* @__PURE__ */ jsx3("div", { onMouseDown: closeAllMenus, children: /* @__PURE__ */ jsx3(EditorContent, { editor }) })
1279
+ ] });
1280
+ };
1281
+ var TiptapEditor_default = TiptapEditor;
1282
+ export {
1283
+ BackgroundColor,
1284
+ FONT_FAMILIES,
1285
+ FONT_SIZES,
1286
+ FontSize,
1287
+ IMAGE_MIME_TYPES,
1288
+ TIPTAP_COLORS,
1289
+ TiptapEditor_default as TiptapEditor,
1290
+ createTiptapExtensions
1291
+ };
1292
+ //# sourceMappingURL=index.mjs.map