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