phx-react 1.3.1142 → 1.3.1143

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.
@@ -0,0 +1,746 @@
1
+ "use strict";
2
+ 'use client';
3
+ exports.__esModule = true;
4
+ var tslib_1 = require("tslib");
5
+ var react_1 = tslib_1.__importDefault(require("react"));
6
+ var react_2 = require("react");
7
+ var react_3 = require("@tiptap/react");
8
+ var starter_kit_1 = tslib_1.__importDefault(require("@tiptap/starter-kit"));
9
+ var extension_underline_1 = tslib_1.__importDefault(require("@tiptap/extension-underline"));
10
+ var extension_image_1 = tslib_1.__importDefault(require("@tiptap/extension-image"));
11
+ var extension_heading_1 = tslib_1.__importDefault(require("@tiptap/extension-heading"));
12
+ var extension_link_1 = tslib_1.__importDefault(require("@tiptap/extension-link"));
13
+ var extension_text_align_1 = tslib_1.__importDefault(require("@tiptap/extension-text-align"));
14
+ var extension_text_style_1 = require("@tiptap/extension-text-style");
15
+ var extension_color_1 = tslib_1.__importDefault(require("@tiptap/extension-color"));
16
+ var react_colorful_1 = require("react-colorful");
17
+ var editor_constant_1 = require("./editor.constant");
18
+ require("katex/dist/katex.min.css");
19
+ require("./index.scss");
20
+ var react_hook_form_1 = require("react-hook-form");
21
+ var Modal_1 = require("../Modal/Modal");
22
+ var Input_1 = require("../Input/Input");
23
+ var Image = extension_image_1["default"].extend({
24
+ addAttributes: function () {
25
+ var _a;
26
+ return tslib_1.__assign(tslib_1.__assign({}, (_a = this.parent) === null || _a === void 0 ? void 0 : _a.call(this)), { 'data-loading': {
27
+ "default": null,
28
+ renderHTML: function (attributes) {
29
+ if (attributes['data-loading']) {
30
+ return { 'data-loading': attributes['data-loading'] };
31
+ }
32
+ return {};
33
+ }
34
+ }, 'data-temp-id': {
35
+ "default": null,
36
+ renderHTML: function (attributes) {
37
+ if (attributes['data-temp-id']) {
38
+ return { 'data-temp-id': attributes['data-temp-id'] };
39
+ }
40
+ return {};
41
+ }
42
+ } });
43
+ }
44
+ });
45
+ function TextEditor(_a) {
46
+ var _this = this;
47
+ var _b;
48
+ var initialData = _a.initialData, onChange = _a.onChange, apiCdnUpload = _a.apiCdnUpload;
49
+ var _c = (0, react_2.useState)(null), editor = _c[0], setEditor = _c[1];
50
+ var _d = (0, react_2.useState)(0), forceUpdate = _d[1];
51
+ var _e = (0, react_2.useState)(false), showColorPicker = _e[0], setShowColorPicker = _e[1];
52
+ var _f = (0, react_2.useState)('#333333'), fontColor = _f[0], setFontColor = _f[1];
53
+ var pickerRef = (0, react_2.useRef)(null);
54
+ var toolbarRef = (0, react_2.useRef)(null);
55
+ var _g = (0, react_2.useState)(''), fontSizeInput = _g[0], setFontSizeInput = _g[1];
56
+ var _h = (0, react_2.useState)(false), isEditingFontSize = _h[0], setIsEditingFontSize = _h[1];
57
+ var _j = (0, react_2.useState)({
58
+ left: 0,
59
+ top: 0
60
+ }), pickerPos = _j[0], setPickerPos = _j[1];
61
+ var _k = (0, react_2.useState)(false), showLinkInput = _k[0], setShowLinkInput = _k[1];
62
+ var _l = (0, react_2.useState)(new Set()), disabledItems = _l[0], setDisabledItems = _l[1];
63
+ var _m = (0, react_hook_form_1.useForm)({
64
+ defaultValues: {
65
+ link: ''
66
+ }
67
+ }), errors = _m.formState.errors, register = _m.register, handleSubmit = _m.handleSubmit, reset = _m.reset;
68
+ // eslint-disable-next-line no-useless-escape
69
+ var regexUrl = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
70
+ var normalizeColor = function (val) {
71
+ try {
72
+ if (val.startsWith('#')) {
73
+ return val;
74
+ }
75
+ if (val.startsWith('rgba')) {
76
+ var _a = val
77
+ .replace(/rgba\(/, '')
78
+ .replace(/\)/, '')
79
+ .split(',')
80
+ .map(function (v) { return v.trim(); }), r = _a[0], g = _a[1], b = _a[2], a = _a[3];
81
+ var rn = parseInt(r, 10).toString(16).padStart(2, '0');
82
+ var gn = parseInt(g, 10).toString(16).padStart(2, '0');
83
+ var bn = parseInt(b, 10).toString(16).padStart(2, '0');
84
+ var an = Math.round(parseFloat(a) * 255)
85
+ .toString(16)
86
+ .padStart(2, '0');
87
+ return "#".concat(rn).concat(gn).concat(bn).concat(an);
88
+ }
89
+ if (val.startsWith('rgb')) {
90
+ var _b = val
91
+ .replace(/rgb\(/, '')
92
+ .replace(/\)/, '')
93
+ .split(',')
94
+ .map(function (v) { return v.trim(); }), r = _b[0], g = _b[1], b = _b[2];
95
+ var rn = parseInt(r, 10).toString(16).padStart(2, '0');
96
+ var gn = parseInt(g, 10).toString(16).padStart(2, '0');
97
+ var bn = parseInt(b, 10).toString(16).padStart(2, '0');
98
+ return "#".concat(rn).concat(gn).concat(bn);
99
+ }
100
+ return val;
101
+ }
102
+ catch (_c) {
103
+ return val;
104
+ }
105
+ };
106
+ var getCurrentSelectionFontSize = (0, react_2.useCallback)(function () {
107
+ var _a;
108
+ try {
109
+ var attrSize = (_a = editor === null || editor === void 0 ? void 0 : editor.getAttributes('textStyle')) === null || _a === void 0 ? void 0 : _a.fontSize;
110
+ if (attrSize)
111
+ return attrSize;
112
+ var sel = window.getSelection();
113
+ var node = sel === null || sel === void 0 ? void 0 : sel.anchorNode;
114
+ var el = null;
115
+ if (node) {
116
+ el = node.nodeType === 1 ? node : node.parentElement;
117
+ }
118
+ if (el) {
119
+ var size = window.getComputedStyle(el).fontSize;
120
+ if (size)
121
+ return size;
122
+ }
123
+ return '';
124
+ }
125
+ catch (_b) {
126
+ return '';
127
+ }
128
+ }, [editor]);
129
+ var getCurrentSelectionColor = function () {
130
+ var _a;
131
+ try {
132
+ var attrColor = (_a = editor === null || editor === void 0 ? void 0 : editor.getAttributes('textStyle')) === null || _a === void 0 ? void 0 : _a.color;
133
+ if (attrColor) {
134
+ return normalizeColor(attrColor);
135
+ }
136
+ var sel = window.getSelection();
137
+ var node = sel === null || sel === void 0 ? void 0 : sel.anchorNode;
138
+ var el = null;
139
+ if (node) {
140
+ el = node.nodeType === 1 ? node : node.parentElement;
141
+ }
142
+ if (el) {
143
+ var color = window.getComputedStyle(el).color;
144
+ if (color)
145
+ return normalizeColor(color);
146
+ }
147
+ return null;
148
+ }
149
+ catch (_b) {
150
+ return null;
151
+ }
152
+ };
153
+ var uploadImageFunction = function (formData) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
154
+ var response;
155
+ return tslib_1.__generator(this, function (_a) {
156
+ switch (_a.label) {
157
+ case 0: return [4 /*yield*/, fetch(apiCdnUpload, {
158
+ method: 'POST',
159
+ body: formData
160
+ })];
161
+ case 1:
162
+ response = _a.sent();
163
+ if (response.ok) {
164
+ return [2 /*return*/, response.json()];
165
+ }
166
+ return [2 /*return*/];
167
+ }
168
+ });
169
+ }); };
170
+ (0, react_2.useEffect)(function () {
171
+ var e = new react_3.Editor({
172
+ content: initialData,
173
+ extensions: [
174
+ extension_text_style_1.TextStyle,
175
+ extension_text_style_1.FontSize,
176
+ extension_color_1["default"],
177
+ starter_kit_1["default"].configure({
178
+ heading: false
179
+ }),
180
+ extension_underline_1["default"],
181
+ Image,
182
+ extension_heading_1["default"].configure({ levels: [1, 2, 3, 4] }),
183
+ extension_link_1["default"].configure({ openOnClick: false }),
184
+ extension_text_align_1["default"].configure({
185
+ types: ['heading', 'paragraph']
186
+ }),
187
+ ],
188
+ editorProps: {
189
+ attributes: {
190
+ "class": 'prose h-[calc(100vh-220px)] px-8 py-4 focus:outline-none border border-t-0 border-gray-300 rounded-b-md bg-white overflow-y-auto'
191
+ }
192
+ }
193
+ });
194
+ e.on('transaction', function () {
195
+ forceUpdate(function (n) { return n + 1; });
196
+ });
197
+ e.on('update', function (_a) {
198
+ var _b;
199
+ var editor = _a.editor;
200
+ onChange === null || onChange === void 0 ? void 0 : onChange((_b = editor.getHTML()) !== null && _b !== void 0 ? _b : '');
201
+ });
202
+ setEditor(e);
203
+ return function () {
204
+ e.destroy();
205
+ };
206
+ }, []);
207
+ (0, react_2.useEffect)(function () {
208
+ if (!editor)
209
+ return;
210
+ var updateDisabledItems = function () {
211
+ var disabled = new Set();
212
+ if (editor.isActive('blockquote')) {
213
+ disabled.add('Văn bản');
214
+ }
215
+ if (editor.isActive('heading')) {
216
+ disabled.add('Trích dẫn');
217
+ }
218
+ setDisabledItems(disabled);
219
+ };
220
+ updateDisabledItems();
221
+ editor.on('selectionUpdate', updateDisabledItems);
222
+ editor.on('transaction', updateDisabledItems);
223
+ return function () {
224
+ editor.off('selectionUpdate', updateDisabledItems);
225
+ editor.off('transaction', updateDisabledItems);
226
+ };
227
+ }, [editor]);
228
+ (0, react_2.useEffect)(function () {
229
+ if (!editor)
230
+ return;
231
+ var handlePaste = function (event) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
232
+ var items, _loop_1, i, state_1;
233
+ var _a;
234
+ return tslib_1.__generator(this, function (_b) {
235
+ items = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.items;
236
+ if (!items)
237
+ return [2 /*return*/];
238
+ event.preventDefault();
239
+ _loop_1 = function (i) {
240
+ var item = items[i];
241
+ if (item.type.indexOf('image') !== -1) {
242
+ var file = item.getAsFile();
243
+ if (!file)
244
+ return "continue";
245
+ var tempId_1 = "img-".concat(Date.now());
246
+ var tempUrl_1 = URL.createObjectURL(file);
247
+ try {
248
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().insertContent({
249
+ type: 'image',
250
+ attrs: {
251
+ src: tempUrl_1,
252
+ 'data-loading': 'true',
253
+ 'data-temp-id': tempId_1
254
+ }
255
+ }).run();
256
+ uploadImage(file)
257
+ .then(function (res) {
258
+ if (!res)
259
+ throw new Error('Upload failed');
260
+ editor === null || editor === void 0 ? void 0 : editor.chain().command(function (_a) {
261
+ var tr = _a.tr;
262
+ var targetPos = null;
263
+ tr.doc.descendants(function (node, pos) {
264
+ if (node.type.name === 'image' && node.attrs['data-temp-id'] === tempId_1) {
265
+ targetPos = pos;
266
+ return false;
267
+ }
268
+ return true;
269
+ });
270
+ if (targetPos !== null) {
271
+ var node = tr.doc.nodeAt(targetPos);
272
+ if (node) {
273
+ tr.setNodeMarkup(targetPos, undefined, tslib_1.__assign(tslib_1.__assign({}, node.attrs), { src: res, 'data-loading': null, 'data-temp-id': null }));
274
+ return true;
275
+ }
276
+ }
277
+ return false;
278
+ }).run();
279
+ })["catch"](function (error) {
280
+ console.error('Failed to upload pasted image:', error);
281
+ editor === null || editor === void 0 ? void 0 : editor.chain().command(function (_a) {
282
+ var tr = _a.tr;
283
+ var targetPos = null;
284
+ tr.doc.descendants(function (node, pos) {
285
+ if (node.type.name === 'image' && node.attrs['data-temp-id'] === tempId_1) {
286
+ targetPos = pos;
287
+ return false;
288
+ }
289
+ return true;
290
+ });
291
+ if (targetPos !== null) {
292
+ tr["delete"](targetPos, targetPos + 1);
293
+ }
294
+ return true;
295
+ }).run();
296
+ })["finally"](function () {
297
+ URL.revokeObjectURL(tempUrl_1);
298
+ });
299
+ }
300
+ catch (error) {
301
+ console.error('Error handling pasted image:', error);
302
+ URL.revokeObjectURL(tempUrl_1);
303
+ }
304
+ return "break";
305
+ }
306
+ };
307
+ for (i = 0; i < items.length; i++) {
308
+ state_1 = _loop_1(i);
309
+ if (state_1 === "break")
310
+ break;
311
+ }
312
+ return [2 /*return*/];
313
+ });
314
+ }); };
315
+ var dom = editor.view.dom;
316
+ dom.addEventListener('paste', handlePaste);
317
+ return function () {
318
+ dom.removeEventListener('paste', handlePaste);
319
+ };
320
+ }, [editor, getCurrentSelectionFontSize]);
321
+ (0, react_2.useEffect)(function () {
322
+ if (!showColorPicker)
323
+ return;
324
+ var onDocMouseDown = function (e) {
325
+ var node = pickerRef.current;
326
+ if (node && e.target instanceof Node && !node.contains(e.target)) {
327
+ setShowColorPicker(false);
328
+ }
329
+ };
330
+ document.addEventListener('mousedown', onDocMouseDown);
331
+ return function () { return document.removeEventListener('mousedown', onDocMouseDown); };
332
+ }, [showColorPicker]);
333
+ (0, react_2.useEffect)(function () {
334
+ if (!editor || !showColorPicker)
335
+ return;
336
+ var updateFromSelection = function () {
337
+ var current = getCurrentSelectionColor();
338
+ if (current)
339
+ setFontColor(current);
340
+ };
341
+ updateFromSelection();
342
+ editor.on('selectionUpdate', updateFromSelection);
343
+ return function () {
344
+ editor.off('selectionUpdate', updateFromSelection);
345
+ };
346
+ }, [editor, showColorPicker]);
347
+ (0, react_2.useEffect)(function () {
348
+ if (!editor)
349
+ return;
350
+ var onSelection = function () {
351
+ if (!isEditingFontSize) {
352
+ var cur = getCurrentSelectionFontSize();
353
+ var m = cur.match(/^(\d+(?:\.\d+)?)/);
354
+ setFontSizeInput(m ? m[1] : '');
355
+ }
356
+ forceUpdate(function (n) { return n + 1; });
357
+ };
358
+ editor.on('selectionUpdate', onSelection);
359
+ return function () {
360
+ editor.off('selectionUpdate', onSelection);
361
+ };
362
+ }, [editor, isEditingFontSize, getCurrentSelectionFontSize]);
363
+ (0, react_2.useEffect)(function () {
364
+ if (!editor)
365
+ return;
366
+ var cur = getCurrentSelectionFontSize();
367
+ var m = cur.match(/^(\d+(?:\.\d+)?)/);
368
+ setFontSizeInput(m ? m[1] : '');
369
+ }, [editor]);
370
+ if (!editor)
371
+ return react_1["default"].createElement("p", null, "Loading editor...");
372
+ var addLink = function () {
373
+ setShowLinkInput(true);
374
+ };
375
+ var handleInsertLink = handleSubmit(function (data) {
376
+ var link = data.link;
377
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().extendMarkRange('link').setLink({ href: link }).run();
378
+ setShowLinkInput(false);
379
+ reset();
380
+ });
381
+ var fontSizeOptions = [
382
+ { label: '12', value: '12px' },
383
+ { label: '14', value: '14px' },
384
+ { label: '16', value: '16px' },
385
+ { label: '18', value: '18px' },
386
+ { label: '20', value: '20px' },
387
+ { label: '24', value: '24px' },
388
+ { label: '28', value: '28px' },
389
+ { label: '32', value: '32px' },
390
+ { label: '36', value: '36px' },
391
+ { label: '40', value: '40px' },
392
+ { label: '44', value: '44px' },
393
+ { label: '48', value: '48px' },
394
+ { label: '52', value: '52px' },
395
+ { label: '56', value: '56px' },
396
+ { label: '60', value: '60px' },
397
+ { label: '64', value: '64px' },
398
+ { label: '72', value: '72px' },
399
+ { label: '80', value: '80px' },
400
+ { label: '96', value: '96px' },
401
+ ];
402
+ var paragraphOptions = [
403
+ { label: 'Văn bản', value: 0 },
404
+ { label: 'Tiêu đề 1', value: 1 },
405
+ { label: 'Tiêu đề 2', value: 2 },
406
+ { label: 'Tiêu đề 3', value: 3 },
407
+ { label: 'Tiêu đề 4', value: 4 },
408
+ ];
409
+ var toolbarItems = [
410
+ {
411
+ name: 'Văn bản',
412
+ isDisabled: function () { return disabledItems.has('Văn bản'); },
413
+ component: function () { return (react_1["default"].createElement("select", { className: "w-24 text-xs leading-6 focus:ring-0 bg-transparent border-none rounded-md py-1 px-2 duration-200 ".concat(disabledItems.has('Văn bản') ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer hover:bg-gray-200'), disabled: disabledItems.has('Văn bản'), onChange: function (e) {
414
+ var level = Number(e.target.value);
415
+ if (level === 0) {
416
+ editor.chain().focus().setParagraph().run();
417
+ }
418
+ else {
419
+ //@ts-expect-error: nothing
420
+ editor.chain().focus().setHeading({ level: level }).run();
421
+ }
422
+ }, value: (function () {
423
+ if (editor.isActive('heading', { level: 1 }))
424
+ return 1;
425
+ if (editor.isActive('heading', { level: 2 }))
426
+ return 2;
427
+ if (editor.isActive('heading', { level: 3 }))
428
+ return 3;
429
+ if (editor.isActive('heading', { level: 4 }))
430
+ return 4;
431
+ return 0;
432
+ })() }, paragraphOptions.map(function (option) { return (react_1["default"].createElement("option", { key: option.value, value: option.value }, option.label)); }))); },
433
+ borderRight: true
434
+ },
435
+ {
436
+ name: 'Giảm cỡ chữ',
437
+ action: function () {
438
+ var currentSize = parseInt(fontSizeInput) || 12;
439
+ var newSize = Math.max(1, currentSize - 1);
440
+ setFontSizeInput(String(newSize));
441
+ editor.chain().focus().setFontSize("".concat(newSize, "px")).run();
442
+ forceUpdate(function (x) { return x + 1; });
443
+ },
444
+ icon: editor_constant_1.toolbar_svg.minus
445
+ },
446
+ {
447
+ name: 'Cỡ chữ',
448
+ component: function () { return (react_1["default"].createElement("div", { className: 'relative flex items-center' },
449
+ react_1["default"].createElement("input", { type: 'number', className: 'w-10 p-0 text-xs leading-5 text-center bg-transparent bg-white border-gray-500 rounded-md focus:ring-0 focus:border-gray-500', value: fontSizeInput || isEditingFontSize ? fontSizeInput : 12, onFocus: function () {
450
+ setIsEditingFontSize(true);
451
+ var cur = getCurrentSelectionFontSize();
452
+ var m = cur.match(/^(\d+(?:\.\d+)?)/);
453
+ setFontSizeInput(m ? m[1] : '');
454
+ }, onChange: function (e) {
455
+ setFontSizeInput(e.target.value);
456
+ }, onBlur: function (e) {
457
+ requestAnimationFrame(function () { return setIsEditingFontSize(false); });
458
+ var raw = e.target.value.trim();
459
+ if (!raw) {
460
+ setFontSizeInput('');
461
+ editor.chain().focus().setMark('textStyle', { fontSize: '12px' }).run();
462
+ }
463
+ else {
464
+ var num = Number(raw);
465
+ if (!Number.isNaN(num) && num > 0) {
466
+ num = Math.min(500, num);
467
+ editor.chain().focus().setFontSize("".concat(num, "px")).run();
468
+ setFontSizeInput(num.toString());
469
+ }
470
+ else {
471
+ setFontSizeInput('12');
472
+ editor.chain().focus().setFontSize('12px').run();
473
+ }
474
+ }
475
+ }, onKeyDown: function (e) {
476
+ if (e.key === 'Enter') {
477
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
478
+ ;
479
+ e.target.blur();
480
+ }
481
+ } }),
482
+ isEditingFontSize && (react_1["default"].createElement("div", { className: 'absolute left-0 top-[110%] z-50 w-16 max-h-60 overflow-auto rounded-md border bg-white shadow' }, fontSizeOptions.map(function (option) { return (react_1["default"].createElement("button", { key: option.value, type: 'button', className: 'w-full px-2 py-1 text-left text-[12px] hover:bg-gray-100', onMouseDown: function (e) {
483
+ e.preventDefault();
484
+ var numMatch = option.value.match(/^(\d+(?:\.\d+)?)/);
485
+ setFontSizeInput(numMatch ? numMatch[1] : '');
486
+ editor.chain().focus().setFontSize(option.value).run();
487
+ setIsEditingFontSize(false);
488
+ } }, option.label)); }))))); }
489
+ },
490
+ {
491
+ name: 'Tăng cỡ chữ',
492
+ icon: editor_constant_1.toolbar_svg.plus,
493
+ borderRight: true,
494
+ action: function () {
495
+ var currentSize = parseInt(fontSizeInput) || 12;
496
+ var newSize = Math.min(500, currentSize + 1);
497
+ setFontSizeInput(String(newSize));
498
+ editor.chain().focus().setFontSize("".concat(newSize, "px")).run();
499
+ forceUpdate(function (x) { return x + 1; });
500
+ }
501
+ },
502
+ {
503
+ name: 'In đậm',
504
+ icon: editor_constant_1.toolbar_svg.bold,
505
+ isActive: function () { return editor.isActive('bold'); },
506
+ action: function () { return editor.chain().focus().toggleBold().run(); }
507
+ },
508
+ {
509
+ name: 'In nghiêng',
510
+ icon: editor_constant_1.toolbar_svg.italic,
511
+ isActive: function () { return editor.isActive('italic'); },
512
+ action: function () { return editor.chain().focus().toggleItalic().run(); }
513
+ },
514
+ {
515
+ name: 'Gạch chân',
516
+ icon: editor_constant_1.toolbar_svg.underline,
517
+ isActive: function () { return editor.isActive('underline'); },
518
+ action: function () { return editor.chain().focus().toggleUnderline().run(); }
519
+ },
520
+ {
521
+ name: 'Màu chữ',
522
+ icon: editor_constant_1.toolbar_svg.textColor,
523
+ action: function () { return setShowColorPicker(function (s) { return !s; }); },
524
+ isActive: function () { return showColorPicker; },
525
+ borderRight: true
526
+ },
527
+ {
528
+ name: 'Căn trái',
529
+ icon: editor_constant_1.toolbar_svg.alignLeft,
530
+ action: function () { return editor.chain().focus().setTextAlign('left').run(); },
531
+ isActive: function () {
532
+ var isLeft = editor.isActive({ textAlign: 'left' });
533
+ var isCenter = editor.isActive({ textAlign: 'center' });
534
+ var isRight = editor.isActive({ textAlign: 'right' });
535
+ var isJustify = editor.isActive({ textAlign: 'justify' });
536
+ var noAlign = !isLeft && !isCenter && !isRight && !isJustify;
537
+ return isLeft || noAlign;
538
+ }
539
+ },
540
+ {
541
+ name: 'Căn giữa',
542
+ icon: editor_constant_1.toolbar_svg.alignMiddle,
543
+ action: function () { return editor.chain().focus().setTextAlign('center').run(); },
544
+ isActive: function () { return editor.isActive({ textAlign: 'center' }); }
545
+ },
546
+ {
547
+ name: 'Căn phải',
548
+ icon: editor_constant_1.toolbar_svg.alignRight,
549
+ action: function () { return editor.chain().focus().setTextAlign('right').run(); },
550
+ isActive: function () { return editor.isActive({ textAlign: 'right' }); }
551
+ },
552
+ {
553
+ name: 'Căn đều',
554
+ icon: editor_constant_1.toolbar_svg.alignJustify,
555
+ action: function () { return editor.chain().focus().setTextAlign('justify').run(); },
556
+ isActive: function () { return editor.isActive({ textAlign: 'justify' }); },
557
+ borderRight: true
558
+ },
559
+ {
560
+ name: 'Trích dẫn',
561
+ icon: editor_constant_1.toolbar_svg.quotes,
562
+ isActive: function () { return editor.isActive('blockquote'); },
563
+ isDisabled: function () { return disabledItems.has('Trích dẫn'); },
564
+ action: function () { return editor.chain().focus().toggleBlockquote().run(); }
565
+ },
566
+ {
567
+ name: 'Danh sách không thứ tự',
568
+ icon: editor_constant_1.toolbar_svg.unorderedList,
569
+ isActive: function () { return editor.isActive('bulletList'); },
570
+ action: function () { return editor.chain().focus().toggleBulletList().run(); }
571
+ },
572
+ {
573
+ name: 'Danh sách có thứ tự',
574
+ icon: editor_constant_1.toolbar_svg.orderList,
575
+ isActive: function () { return editor.isActive('orderedList'); },
576
+ action: function () { return editor.chain().focus().toggleOrderedList().run(); },
577
+ borderRight: true
578
+ },
579
+ {
580
+ name: 'Hình ảnh',
581
+ icon: editor_constant_1.toolbar_svg.image,
582
+ action: function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
583
+ var input;
584
+ var _this = this;
585
+ return tslib_1.__generator(this, function (_a) {
586
+ input = document.createElement('input');
587
+ input.type = 'file';
588
+ input.accept = 'image/*';
589
+ input.onchange = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
590
+ var file, tempId_2, res_1;
591
+ var _a;
592
+ return tslib_1.__generator(this, function (_b) {
593
+ switch (_b.label) {
594
+ case 0:
595
+ file = (_a = input.files) === null || _a === void 0 ? void 0 : _a[0];
596
+ if (!file) return [3 /*break*/, 2];
597
+ tempId_2 = "img-".concat(Date.now());
598
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().insertContent({
599
+ type: 'image',
600
+ attrs: {
601
+ src: URL.createObjectURL(file),
602
+ 'data-loading': 'true',
603
+ 'data-temp-id': tempId_2
604
+ }
605
+ }).run();
606
+ return [4 /*yield*/, uploadImage(file)];
607
+ case 1:
608
+ res_1 = _b.sent();
609
+ if (res_1) {
610
+ editor === null || editor === void 0 ? void 0 : editor.chain().command(function (_a) {
611
+ var tr = _a.tr;
612
+ var targetPos = null;
613
+ tr.doc.descendants(function (node, pos) {
614
+ if (node.type.name === 'image' && node.attrs['data-temp-id'] === tempId_2) {
615
+ targetPos = pos;
616
+ return false;
617
+ }
618
+ return true;
619
+ });
620
+ if (targetPos !== null) {
621
+ var node = tr.doc.nodeAt(targetPos);
622
+ if (node) {
623
+ tr.setNodeMarkup(targetPos, undefined, tslib_1.__assign(tslib_1.__assign({}, node.attrs), { src: res_1, 'data-loading': null, 'data-temp-id': null }));
624
+ }
625
+ }
626
+ return true;
627
+ }).run();
628
+ }
629
+ else {
630
+ editor === null || editor === void 0 ? void 0 : editor.chain().command(function () {
631
+ var _a;
632
+ var node = document.querySelector("img[data-temp-id=\"".concat(tempId_2, "\"]"));
633
+ if (node) {
634
+ (_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(node);
635
+ }
636
+ return true;
637
+ }).run();
638
+ }
639
+ _b.label = 2;
640
+ case 2: return [2 /*return*/];
641
+ }
642
+ });
643
+ }); };
644
+ input.click();
645
+ return [2 /*return*/];
646
+ });
647
+ }); }
648
+ },
649
+ {
650
+ name: 'Đường dẫn',
651
+ icon: editor_constant_1.toolbar_svg.link,
652
+ isActive: function () { return editor.isActive('link'); },
653
+ action: addLink
654
+ },
655
+ ];
656
+ var uploadImage = function (file) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
657
+ var formData, res, error_1;
658
+ return tslib_1.__generator(this, function (_a) {
659
+ switch (_a.label) {
660
+ case 0:
661
+ _a.trys.push([0, 2, , 3]);
662
+ formData = new FormData();
663
+ formData.append('file', file);
664
+ formData.append('projectId', 'test');
665
+ formData.append('moduleId', 'test');
666
+ return [4 /*yield*/, uploadImageFunction(formData)];
667
+ case 1:
668
+ res = _a.sent();
669
+ if (res) {
670
+ return [2 /*return*/, res.link];
671
+ }
672
+ return [3 /*break*/, 3];
673
+ case 2:
674
+ error_1 = _a.sent();
675
+ console.error(error_1);
676
+ return [2 /*return*/, null];
677
+ case 3: return [2 /*return*/];
678
+ }
679
+ });
680
+ }); };
681
+ return (react_1["default"].createElement("div", { className: 'phx-editor' },
682
+ react_1["default"].createElement("div", { className: 'sticky top-0 z-50' },
683
+ react_1["default"].createElement("div", { ref: toolbarRef, className: 'flex flex-wrap gap-1 p-2 bg-gray-100 border border-gray-300 rounded-t-md' },
684
+ toolbarItems.map(function (item) {
685
+ var _a, _b, _c, _d;
686
+ (_a = item.isActive) === null || _a === void 0 ? void 0 : _a.call(item);
687
+ return item.component ? (react_1["default"].createElement("div", { className: 'flex items-center gap-x-2' },
688
+ item.component(),
689
+ item.borderRight && react_1["default"].createElement("div", { className: 'w-[1px] h-full bg-gray-300' }))) : (react_1["default"].createElement("div", { className: 'flex items-center gap-x-2' },
690
+ react_1["default"].createElement("button", { key: item.name, type: 'button', onMouseDown: function (e) {
691
+ var _a;
692
+ e.preventDefault();
693
+ if (item.name === 'Màu chữ') {
694
+ var container = toolbarRef.current;
695
+ var btnRect = e.currentTarget.getBoundingClientRect();
696
+ var contRect = container === null || container === void 0 ? void 0 : container.getBoundingClientRect();
697
+ if (contRect) {
698
+ setPickerPos({
699
+ left: btnRect.left - contRect.left - 90,
700
+ top: btnRect.bottom - contRect.top + 8
701
+ });
702
+ }
703
+ var current = getCurrentSelectionColor();
704
+ if (current)
705
+ setFontColor(current);
706
+ setShowColorPicker(function (s) { return !s; });
707
+ forceUpdate(function (x) { return x + 1; });
708
+ }
709
+ else {
710
+ (_a = item.action) === null || _a === void 0 ? void 0 : _a.call(item);
711
+ forceUpdate(function (x) { return x + 1; });
712
+ }
713
+ }, className: "p-1 rounded-md duration-200 ".concat(((_b = item.isActive) === null || _b === void 0 ? void 0 : _b.call(item)) ? 'bg-gray-200' : '', " ").concat(((_c = item.isDisabled) === null || _c === void 0 ? void 0 : _c.call(item)) ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-200 cursor-pointer'), disabled: (_d = item.isDisabled) === null || _d === void 0 ? void 0 : _d.call(item) }, (item === null || item === void 0 ? void 0 : item.icon) && react_1["default"].createElement("span", { dangerouslySetInnerHTML: { __html: item.icon } })),
714
+ item.borderRight && react_1["default"].createElement("div", { className: 'w-[1px] h-5 bg-gray-300' })));
715
+ }),
716
+ showColorPicker && (react_1["default"].createElement("div", { ref: pickerRef, className: 'absolute z-50 p-5 bg-white border shadow rounded-xl', style: { left: pickerPos.left, top: pickerPos.top } },
717
+ react_1["default"].createElement(react_colorful_1.HexAlphaColorPicker, { color: fontColor, onChange: function (color) {
718
+ var norm = normalizeColor(color);
719
+ setFontColor(norm);
720
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().setColor(norm).setMark('textStyle', { color: "".concat(norm, " !important") }).run();
721
+ } }),
722
+ react_1["default"].createElement("div", { className: 'flex items-center gap-2 mt-2 ' },
723
+ react_1["default"].createElement("div", { className: 'flex items-center gap-x-2 border border-gray-300 py-1.5 px-3 rounded-xl' },
724
+ react_1["default"].createElement("div", { className: 'w-6 h-6 border rounded ', style: { backgroundColor: fontColor }, title: fontColor }),
725
+ react_1["default"].createElement("input", { type: 'text', className: 'p-0 border-none !ring-0 w-[144px]', value: fontColor, onChange: function (e) {
726
+ var raw = e.target.value.startsWith('#')
727
+ ? e.target.value
728
+ : "#".concat(e.target.value.replace(/^#/, ''));
729
+ var norm = normalizeColor(raw);
730
+ setFontColor(norm);
731
+ if (/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(norm)) {
732
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().setColor(norm).run();
733
+ }
734
+ } }))))))),
735
+ react_1["default"].createElement(react_3.EditorContent, { editor: editor }),
736
+ react_1["default"].createElement(Modal_1.PHXModal, { onHide: function () {
737
+ setShowLinkInput(false);
738
+ reset();
739
+ }, show: showLinkInput, title: 'Ch\u00E8n \u0111\u01B0\u1EDDng li\u00EAn k\u1EBFt', onPrimaryClick: handleInsertLink, closeButton: true },
740
+ react_1["default"].createElement(Input_1.PHXInput, { error: !!errors.link, errorType: 'custom-message', errorMessageCustom: (_b = errors.link) === null || _b === void 0 ? void 0 : _b.message, register: tslib_1.__assign({}, register('link', {
741
+ pattern: { value: regexUrl, message: 'Đường dẫn không hợp lệ' },
742
+ required: 'Vui lòng nhập đường dẫn'
743
+ })), label: 'Nh\u1EADp \u0111\u01B0\u1EDDng d\u1EABn', placeholder: 'https://example.com' }))));
744
+ }
745
+ exports["default"] = TextEditor;
746
+ //# sourceMappingURL=TextEditor.js.map