phx-react 1.3.1142 → 1.3.1144
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 +16 -3
|
@@ -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-5 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-5 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
|