@phuong-tran-redoc/document-engine-core 0.0.12

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.
Files changed (63) hide show
  1. package/LICENSE.md +19 -0
  2. package/README.md +224 -0
  3. package/package.json +30 -0
  4. package/src/constants/indent.constant.d.ts +1 -0
  5. package/src/constants/indent.constant.js +11 -0
  6. package/src/constants/index.d.ts +1 -0
  7. package/src/constants/index.js +18 -0
  8. package/src/constants/table.constant.d.ts +1 -0
  9. package/src/constants/table.constant.js +11 -0
  10. package/src/extensions/clear-content.extension.d.ts +9 -0
  11. package/src/extensions/clear-content.extension.js +19 -0
  12. package/src/extensions/indent.extension.d.ts +16 -0
  13. package/src/extensions/indent.extension.js +69 -0
  14. package/src/extensions/index.d.ts +8 -0
  15. package/src/extensions/index.js +25 -0
  16. package/src/extensions/ordered-list.extension.d.ts +13 -0
  17. package/src/extensions/ordered-list.extension.js +48 -0
  18. package/src/extensions/reset-format.extension.d.ts +9 -0
  19. package/src/extensions/reset-format.extension.js +19 -0
  20. package/src/extensions/reset-on-enter.extension.d.ts +2 -0
  21. package/src/extensions/reset-on-enter.extension.js +42 -0
  22. package/src/extensions/restricted-editing.extension.d.ts +17 -0
  23. package/src/extensions/restricted-editing.extension.js +384 -0
  24. package/src/extensions/table-resizing.extension.d.ts +6 -0
  25. package/src/extensions/table-resizing.extension.js +171 -0
  26. package/src/extensions/table-style.extension.d.ts +41 -0
  27. package/src/extensions/table-style.extension.js +499 -0
  28. package/src/extensions/text-case.extension.d.ts +13 -0
  29. package/src/extensions/text-case.extension.js +49 -0
  30. package/src/index.d.ts +7 -0
  31. package/src/index.js +24 -0
  32. package/src/models/color.model.d.ts +14 -0
  33. package/src/models/color.model.js +40 -0
  34. package/src/models/index.d.ts +1 -0
  35. package/src/models/index.js +18 -0
  36. package/src/nodes/dynamic-field.node.d.ts +49 -0
  37. package/src/nodes/dynamic-field.node.js +99 -0
  38. package/src/nodes/heading.node.d.ts +8 -0
  39. package/src/nodes/heading.node.js +23 -0
  40. package/src/nodes/index.d.ts +3 -0
  41. package/src/nodes/index.js +20 -0
  42. package/src/nodes/page-break.node.d.ts +9 -0
  43. package/src/nodes/page-break.node.js +50 -0
  44. package/src/types/index.d.ts +2 -0
  45. package/src/types/index.js +19 -0
  46. package/src/types/ordered-list.type.d.ts +1 -0
  47. package/src/types/ordered-list.type.js +4 -0
  48. package/src/types/text-case.type.d.ts +1 -0
  49. package/src/types/text-case.type.js +4 -0
  50. package/src/utils/color.util.d.ts +14 -0
  51. package/src/utils/color.util.js +41 -0
  52. package/src/utils/dom.util.d.ts +2 -0
  53. package/src/utils/dom.util.js +43 -0
  54. package/src/utils/index.d.ts +4 -0
  55. package/src/utils/index.js +21 -0
  56. package/src/utils/table.util.d.ts +36 -0
  57. package/src/utils/table.util.js +103 -0
  58. package/src/utils/text.util.d.ts +9 -0
  59. package/src/utils/text.util.js +59 -0
  60. package/src/views/block-handler.d.ts +44 -0
  61. package/src/views/block-handler.js +310 -0
  62. package/src/views/index.d.ts +1 -0
  63. package/src/views/index.js +18 -0
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: all[name]
9
+ });
10
+ }
11
+ _export(exports, {
12
+ EditableRegion: function() {
13
+ return EditableRegion;
14
+ },
15
+ RestrictedEditing: function() {
16
+ return RestrictedEditing;
17
+ }
18
+ });
19
+ const _core = require("@tiptap/core");
20
+ const _state = require("@tiptap/pm/state");
21
+ const EditableRegion = _core.Node.create({
22
+ name: 'editableRegion',
23
+ group: 'inline',
24
+ inline: true,
25
+ content: 'text*',
26
+ selectable: false,
27
+ parseHTML () {
28
+ return [
29
+ {
30
+ tag: 'span[data-editable-region]'
31
+ }
32
+ ];
33
+ },
34
+ renderHTML ({ HTMLAttributes }) {
35
+ return [
36
+ 'span',
37
+ (0, _core.mergeAttributes)(HTMLAttributes, {
38
+ 'data-editable-region': 'true',
39
+ class: 'editable-region'
40
+ }),
41
+ 0
42
+ ];
43
+ }
44
+ });
45
+ const RestrictedEditing = _core.Extension.create({
46
+ name: 'restrictedEditing',
47
+ addOptions () {
48
+ return {
49
+ editableRegionNodeName: 'editableRegion',
50
+ disabledNodeTypes: [
51
+ 'listItem',
52
+ 'blockquote'
53
+ ],
54
+ initialMode: 'restricted'
55
+ };
56
+ },
57
+ addStorage () {
58
+ return {
59
+ mode: this.options.initialMode
60
+ };
61
+ },
62
+ addCommands () {
63
+ return {
64
+ wrapSelectionInEditableRegion: ()=>({ state, dispatch })=>{
65
+ // 0. Always return false if the editable region node is not registered
66
+ const type = state.schema.nodes[this.options.editableRegionNodeName];
67
+ if (!type) return false;
68
+ const { from, to, empty } = state.selection;
69
+ // 1. Insert an empty editable region with a zero-width space
70
+ if (empty) {
71
+ const node = type.create(null, state.schema.text('\u200b'));
72
+ const tr = state.tr.replaceSelectionWith(node, false);
73
+ tr.setMeta('restrictedEditing', {
74
+ allow: true
75
+ });
76
+ dispatch?.(tr.scrollIntoView());
77
+ return true;
78
+ }
79
+ // Disallow wrapping when selection contains non-text inline nodes
80
+ let containsNonTextInline = false;
81
+ state.doc.nodesBetween(from, to, (node)=>{
82
+ if (node.isInline && !node.isText) {
83
+ containsNonTextInline = true;
84
+ return false;
85
+ }
86
+ return undefined;
87
+ });
88
+ if (containsNonTextInline) {
89
+ return false;
90
+ }
91
+ const multiLine = state.doc.textBetween(from, to, '\n', '\n').includes('\n');
92
+ let tr = state.tr;
93
+ if (multiLine) {
94
+ // Wrap each textblock segment independently
95
+ const ranges = [];
96
+ state.doc.nodesBetween(from, to, (node, pos)=>{
97
+ if (node.isTextblock) {
98
+ const s = Math.max(from, pos + 1);
99
+ const e = Math.min(to, pos + node.nodeSize - 1);
100
+ if (e > s) ranges.push({
101
+ s,
102
+ e
103
+ });
104
+ }
105
+ return undefined;
106
+ });
107
+ // Apply replacements from end to start to keep positions stable
108
+ for(let i = ranges.length - 1; i >= 0; i--){
109
+ const { s, e } = ranges[i];
110
+ const text = state.doc.textBetween(s, e, '\n', '\n');
111
+ if (text.length === 0) continue;
112
+ const node = type.create(null, state.schema.text(text));
113
+ tr = tr.replaceRangeWith(s, e, node);
114
+ }
115
+ } else {
116
+ const text = state.doc.textBetween(from, to, '\n', '\n');
117
+ if (text.length === 0) return false;
118
+ const node = type.create(null, state.schema.text(text));
119
+ tr = tr.replaceRangeWith(from, to, node);
120
+ }
121
+ tr.setMeta('restrictedEditing', {
122
+ allow: true
123
+ });
124
+ dispatch?.(tr.scrollIntoView());
125
+ return true;
126
+ },
127
+ removeEditableRegion: ()=>({ state, dispatch })=>{
128
+ const type = state.schema.nodes[this.options.editableRegionNodeName];
129
+ if (!type) return false;
130
+ const $pos = state.selection.$head;
131
+ for(let depth = $pos.depth; depth >= 0; depth--){
132
+ const node = $pos.node(depth);
133
+ if (node.type === type) {
134
+ const from = $pos.before(depth);
135
+ const to = $pos.after(depth);
136
+ const tr = state.tr.replaceWith(from, to, node.content);
137
+ tr.setMeta('restrictedEditing', {
138
+ allow: true
139
+ });
140
+ dispatch?.(tr.scrollIntoView());
141
+ return true;
142
+ }
143
+ }
144
+ return false;
145
+ },
146
+ toggleEditableRegion: ()=>({ state, dispatch, commands })=>{
147
+ const type = state.schema.nodes[this.options.editableRegionNodeName];
148
+ if (!type) return false;
149
+ const { from, to, empty } = state.selection;
150
+ const findRegionAtPos = (pos)=>{
151
+ const $pos = state.doc.resolve(pos);
152
+ for(let depth = $pos.depth; depth >= 0; depth--){
153
+ const node = $pos.node(depth);
154
+ if (node.type === type) {
155
+ const nodeFrom = $pos.before(depth);
156
+ const nodeTo = $pos.after(depth);
157
+ return {
158
+ from: nodeFrom,
159
+ to: nodeTo,
160
+ node
161
+ };
162
+ }
163
+ }
164
+ return null;
165
+ };
166
+ // Cursor case: inside a region -> unwrap that region; else insert empty region
167
+ if (empty) {
168
+ const region = findRegionAtPos(from);
169
+ if (region) {
170
+ const tr = state.tr.replaceWith(region.from, region.to, region.node.content);
171
+ tr.setMeta('restrictedEditing', {
172
+ allow: true
173
+ });
174
+ dispatch?.(tr.scrollIntoView());
175
+ return true;
176
+ }
177
+ const node = type.create(null, state.schema.text('\u200b'));
178
+ const tr = state.tr.replaceSelectionWith(node, false);
179
+ tr.setMeta('restrictedEditing', {
180
+ allow: true
181
+ });
182
+ dispatch?.(tr.scrollIntoView());
183
+ return true;
184
+ }
185
+ // Range case: unwrap only if the entire selection is inside the SAME region; otherwise
186
+ // if selection is completely outside all regions, wrap it; else ignore
187
+ const regionStart = findRegionAtPos(from);
188
+ const regionEnd = findRegionAtPos(Math.max(from, to - 1));
189
+ if (regionStart && regionEnd && regionStart.from === regionEnd.from && regionStart.to === regionEnd.to) {
190
+ const tr = state.tr.replaceWith(regionStart.from, regionStart.to, regionStart.node.content);
191
+ tr.setMeta('restrictedEditing', {
192
+ allow: true
193
+ });
194
+ dispatch?.(tr.scrollIntoView());
195
+ return true;
196
+ }
197
+ // If neither endpoint is in a region, ensure there is no region intersecting the range
198
+ if (!regionStart && !regionEnd) {
199
+ let hasRegionInside = false;
200
+ state.doc.nodesBetween(from, to, (node)=>{
201
+ if (node.type === type) {
202
+ hasRegionInside = true;
203
+ return false;
204
+ }
205
+ return undefined;
206
+ });
207
+ if (!hasRegionInside) {
208
+ return commands.wrapSelectionInEditableRegion();
209
+ }
210
+ }
211
+ return false;
212
+ }
213
+ };
214
+ },
215
+ addKeyboardShortcuts () {
216
+ return {
217
+ Enter: ()=>{
218
+ // Get the current selection
219
+ const selection = this.editor.state.selection;
220
+ const $from = selection.$from;
221
+ if (!selection.empty) return false;
222
+ let editableRegionInfo = null;
223
+ for(let d = $from.depth; d > 0; d--){
224
+ const node = $from.node(d);
225
+ if (node.type.name === 'editableRegion') {
226
+ editableRegionInfo = {
227
+ node: node,
228
+ depth: d,
229
+ // Position of the entire node (before the opening tag)
230
+ posBefore: $from.before(d),
231
+ // Position of the entire node (after the closing tag)
232
+ posAfter: $from.after(d),
233
+ // Position of the start of the content inside the node
234
+ contentStartPos: $from.start(d),
235
+ // Position of the end of the content inside the node
236
+ contentEndPos: $from.end(d)
237
+ };
238
+ break;
239
+ }
240
+ }
241
+ if (!editableRegionInfo) return false;
242
+ const atStart = $from.pos === editableRegionInfo.contentStartPos;
243
+ const atEnd = $from.pos === editableRegionInfo.contentEndPos;
244
+ // If cursor is at the start of the editable region, add a new paragraph before the editable region
245
+ if (atStart) {
246
+ return this.editor.chain().insertContentAt(editableRegionInfo.posBefore, '<p></p>').focus(editableRegionInfo.posBefore - 2).run();
247
+ }
248
+ // If cursor is at the end of the editable region, add a new paragraph after the editable region
249
+ if (atEnd) {
250
+ return this.editor.chain().insertContentAt(editableRegionInfo.posAfter, '<p></p>').focus(editableRegionInfo.posAfter + 1).run();
251
+ }
252
+ // If cursor is in the middle of the editable region, return false
253
+ return false;
254
+ }
255
+ };
256
+ },
257
+ addProseMirrorPlugins () {
258
+ const key = new _state.PluginKey('restricted-editing');
259
+ const editor = this.editor;
260
+ const editableRegionNodeName = this.options.editableRegionNodeName;
261
+ const disabledNodeTypes = this.options.disabledNodeTypes ?? [];
262
+ const getEditableRegionBounds = (doc, schema, pos)=>{
263
+ const $pos = doc.resolve(pos);
264
+ const type = schema.nodes[editableRegionNodeName];
265
+ for(let depth = $pos.depth; depth >= 0; depth--){
266
+ if ($pos.node(depth).type === type) {
267
+ return {
268
+ depth,
269
+ from: $pos.start(depth),
270
+ to: $pos.end(depth)
271
+ };
272
+ }
273
+ }
274
+ return null;
275
+ };
276
+ const rangesFromTransaction = (tr)=>{
277
+ const ranges = [];
278
+ for(let i = 0; i < tr.steps.length; i++){
279
+ const map = tr.mapping.maps[i];
280
+ const forward = tr.mapping.slice(i + 1);
281
+ map.forEach((oldStart, oldEnd, newStart, newEnd)=>{
282
+ const finalNewFrom = forward.map(newStart, 1);
283
+ const finalNewTo = forward.map(newEnd, -1);
284
+ ranges.push({
285
+ oldFrom: oldStart,
286
+ oldTo: oldEnd,
287
+ newFrom: finalNewFrom,
288
+ newTo: finalNewTo
289
+ });
290
+ });
291
+ }
292
+ return ranges;
293
+ };
294
+ return [
295
+ new _state.Plugin({
296
+ key,
297
+ filterTransaction: (tr, state)=>{
298
+ // In standard mode, do not restrict edits
299
+ if (this.storage.mode === 'standard') return true;
300
+ if (!tr.docChanged) return true;
301
+ if (tr.getMeta('restrictedEditing')?.allow) return true;
302
+ // Allow while the document is still the default empty state
303
+ if (editor.isEmpty) return true;
304
+ // In restricted mode: disable mark/style/attr changes from other extensions entirely
305
+ const disallowedStepTypes = new Set([
306
+ 'addMark',
307
+ 'removeMark',
308
+ 'setNodeMarkup'
309
+ ]);
310
+ for (const step of tr.steps){
311
+ const t = step?.toJSON?.().stepType;
312
+ if (t && disallowedStepTypes.has(t)) {
313
+ return false;
314
+ }
315
+ }
316
+ if (disabledNodeTypes.some((type)=>editor.isActive(type))) {
317
+ return true;
318
+ }
319
+ const ranges = rangesFromTransaction(tr);
320
+ if (ranges.length === 0) return true;
321
+ for (const range of ranges){
322
+ // Pre-change validation (against state.doc)
323
+ const preStart = getEditableRegionBounds(state.doc, state.schema, range.oldFrom);
324
+ const preEnd = getEditableRegionBounds(state.doc, state.schema, range.oldTo);
325
+ if (!preStart || !preEnd) return false;
326
+ if (preStart.from !== preEnd.from || preStart.to !== preEnd.to) return false;
327
+ // Post-change validation (against final tr.doc)
328
+ const postStart = getEditableRegionBounds(tr.doc, state.schema, range.newFrom);
329
+ const postEnd = getEditableRegionBounds(tr.doc, state.schema, range.newTo);
330
+ if (!postStart || !postEnd) return false;
331
+ if (postStart.from !== postEnd.from || postStart.to !== postEnd.to) return false;
332
+ }
333
+ return true;
334
+ },
335
+ appendTransaction: (_transactions, _oldState, newState)=>{
336
+ const type = newState.schema.nodes[editableRegionNodeName];
337
+ if (!type) return null;
338
+ const isRestricted = this.storage.mode === 'restricted';
339
+ const edits = [];
340
+ newState.doc.descendants((node, pos)=>{
341
+ if (node.type === type) {
342
+ const contentText = node.textContent ?? '';
343
+ if (contentText.length === 0) {
344
+ if (isRestricted) {
345
+ // Ensure a single space exists inside empty region
346
+ const innerFrom = pos + 1;
347
+ const innerTo = pos + node.nodeSize - 1;
348
+ edits.push({
349
+ kind: 'fill',
350
+ from: innerFrom,
351
+ to: innerTo
352
+ });
353
+ } else {
354
+ // Remove empty region
355
+ edits.push({
356
+ kind: 'delete',
357
+ from: pos,
358
+ to: pos + node.nodeSize
359
+ });
360
+ }
361
+ }
362
+ }
363
+ });
364
+ if (edits.length === 0) return null;
365
+ // Apply in descending order of from
366
+ edits.sort((a, b)=>b.from - a.from);
367
+ const tr = newState.tr;
368
+ for (const e of edits){
369
+ if (e.kind === 'delete') {
370
+ tr.delete(e.from, e.to);
371
+ } else {
372
+ const textNode = newState.schema.text(' ');
373
+ tr.replaceWith(e.from, e.to, textNode);
374
+ }
375
+ }
376
+ tr.setMeta('restrictedEditing', {
377
+ allow: true
378
+ });
379
+ return tr;
380
+ }
381
+ })
382
+ ];
383
+ }
384
+ });
@@ -0,0 +1,6 @@
1
+ import { Extension } from '@tiptap/core';
2
+ /**
3
+ * Extension PercentageColumnResizing
4
+ * * Thay thế logic resize cột mặc định bằng logic dựa trên %.
5
+ */
6
+ export declare const PercentageColumnResizing: Extension<any, any>;
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "PercentageColumnResizing", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return PercentageColumnResizing;
9
+ }
10
+ });
11
+ const _core = require("@tiptap/core");
12
+ const _state = require("@tiptap/pm/state");
13
+ const _tables = require("@tiptap/pm/tables");
14
+ const _view = require("@tiptap/pm/view");
15
+ const _utils = require("../utils");
16
+ /**
17
+ * Hàm xử lý khi di chuột (kéo)
18
+ */ function handleMouseMoveEvent(event, dragInfo) {
19
+ if (!dragInfo.isDragging) return;
20
+ event.preventDefault();
21
+ const { startX, tableWidthPx, initialLeftWidth, initialRightWidth, leftColIndex, tablePos, tableNode, view } = dragInfo;
22
+ // 1. Tính toán delta (pixel)
23
+ const deltaX = event.clientX - startX;
24
+ // 2. Chuyển delta pixel sang delta percentage
25
+ // Đây là logic quan trọng nhất
26
+ const deltaPercent = deltaX / tableWidthPx * 100;
27
+ // 3. Tính toán width mới
28
+ let newLeftWidth = initialLeftWidth + deltaPercent;
29
+ let newRightWidth = initialRightWidth - deltaPercent;
30
+ // 4. Áp dụng ràng buộc (ví dụ: min-width 5%)
31
+ const minWidth = 5; // Cấu hình min-width
32
+ if (newLeftWidth < minWidth) {
33
+ const diff = minWidth - newLeftWidth;
34
+ newLeftWidth = minWidth;
35
+ newRightWidth = newRightWidth - diff; // Cột phải bù phần bị thiếu
36
+ } else if (newRightWidth < minWidth) {
37
+ const diff = minWidth - newRightWidth;
38
+ newRightWidth = minWidth;
39
+ newLeftWidth = newLeftWidth - diff; // Cột trái bù phần bị thiếu
40
+ }
41
+ // Đảm bảo không có cột nào bị âm (nếu kéo quá nhanh)
42
+ if (newLeftWidth < 0) newLeftWidth = 0;
43
+ if (newRightWidth < 0) newRightWidth = 0;
44
+ // 5. Tạo mảng colwidths mới
45
+ const newColWidths = [
46
+ ...tableNode?.attrs?.['colwidths']
47
+ ];
48
+ newColWidths[leftColIndex] = newLeftWidth;
49
+ newColWidths[leftColIndex + 1] = newRightWidth;
50
+ // 6. Dispatch transaction để cập nhật state
51
+ if (tableNode && view) {
52
+ const tr = view.state.tr.setNodeMarkup(tablePos, null, {
53
+ ...tableNode.attrs,
54
+ colwidths: newColWidths
55
+ });
56
+ // Dùng dispatchTransaction để Tiptap/ProseMirror tự cập nhật UI
57
+ view.dispatch(tr);
58
+ }
59
+ }
60
+ /**
61
+ * Hàm xử lý khi nhả chuột (kết thúc kéo)
62
+ */ function handleMouseUpEvent(event, dragInfo) {
63
+ if (!dragInfo.isDragging) return;
64
+ event.preventDefault();
65
+ // Reset trạng thái
66
+ dragInfo = {
67
+ isDragging: false,
68
+ startX: 0,
69
+ tablePos: -1,
70
+ tableNode: null,
71
+ tableWidthPx: 0,
72
+ leftColIndex: -1,
73
+ initialLeftWidth: 0,
74
+ initialRightWidth: 0,
75
+ view: null
76
+ };
77
+ }
78
+ const PercentageColumnResizing = _core.Extension.create({
79
+ name: 'percentageColumnResizing',
80
+ addProseMirrorPlugins () {
81
+ // Một đối tượng tạm thời để lưu trữ trạng thái kéo-thả
82
+ // Chúng ta không dùng Plugin State vì nó không cần thiết phải "undo"
83
+ let dragInfo = {
84
+ isDragging: false,
85
+ startX: 0,
86
+ tablePos: -1,
87
+ tableNode: null,
88
+ tableWidthPx: 0,
89
+ leftColIndex: -1,
90
+ initialLeftWidth: 0,
91
+ initialRightWidth: 0,
92
+ view: null
93
+ };
94
+ const editorInstance = this.editor;
95
+ return [
96
+ new _state.Plugin({
97
+ key: new _state.PluginKey('percentageColumnResizing'),
98
+ props: {
99
+ /**
100
+ * Vẽ các resize handles vào DOM
101
+ */ decorations (state) {
102
+ if (!editorInstance?.isEditable) return;
103
+ const decorations = [];
104
+ const { doc } = state;
105
+ const tableTypes = (0, _tables.tableNodeTypes)(state.schema);
106
+ // Duyệt qua tất cả node trong document
107
+ doc.descendants((node, pos)=>{
108
+ if (node.type.name !== tableTypes.table.name) return;
109
+ const colwidths = node.attrs['colwidths'];
110
+ if (!colwidths || colwidths.length === 0) return;
111
+ let accumulatedWidth = 0;
112
+ // Tạo handle cho N-1 cột (không cần cho cột cuối)
113
+ for(let i = 0; i < colwidths.length - 1; i++){
114
+ accumulatedWidth += colwidths[i] ?? 0;
115
+ // Tạo một widget 'div' để làm handle
116
+ const handle = document.createElement('div');
117
+ handle.className = 'pm-col-resizer';
118
+ handle.style.left = `calc(${accumulatedWidth}% - 2px)`;
119
+ // Lưu vị trí cột để biết đang resize cột nào
120
+ handle.setAttribute('data-col-index', i.toString());
121
+ handle.setAttribute('data-table-pos', pos.toString());
122
+ // Đặt widget ngay sau thẻ <table> (pos + 1)
123
+ decorations.push(_view.Decoration.widget(pos + 1, handle));
124
+ }
125
+ });
126
+ return _view.DecorationSet.create(doc, decorations);
127
+ },
128
+ /**
129
+ * Xử lý sự kiện mousedown trên handle
130
+ */ handleDOMEvents: {
131
+ mousedown (view, event) {
132
+ const target = event.target;
133
+ // Chỉ kích hoạt khi click vào handle
134
+ if (!target.classList.contains('pm-col-resizer')) return false;
135
+ event.preventDefault();
136
+ const colIndex = parseInt(target.getAttribute('data-col-index') ?? '0', 10);
137
+ const tablePos = parseInt(target.getAttribute('data-table-pos') ?? '0', 10);
138
+ const tableNode = view.state.doc.nodeAt(tablePos);
139
+ const colwidths = tableNode?.attrs['colwidths'];
140
+ if (!tableNode || !colwidths) return false;
141
+ // Get table DOM element (handles NodeView wrappers and nested tables)
142
+ const tableDOM = (0, _utils.getTableDOMFromView)(view, tablePos);
143
+ if (!tableDOM) return false;
144
+ // Lưu thông tin cần thiết cho việc kéo
145
+ dragInfo = {
146
+ isDragging: true,
147
+ startX: event.clientX,
148
+ tablePos: tablePos,
149
+ tableNode: tableNode,
150
+ tableWidthPx: tableDOM.offsetWidth,
151
+ leftColIndex: colIndex,
152
+ initialLeftWidth: colwidths[colIndex],
153
+ initialRightWidth: colwidths[colIndex + 1],
154
+ view: view
155
+ };
156
+ const handleMouseMove = (event)=>handleMouseMoveEvent(event, dragInfo);
157
+ const handleMouseUp = (event)=>{
158
+ handleMouseUpEvent(event, dragInfo);
159
+ window.removeEventListener('mousemove', handleMouseMove, true);
160
+ window.removeEventListener('mouseup', handleMouseUp, true);
161
+ };
162
+ window.addEventListener('mousemove', handleMouseMove, true);
163
+ window.addEventListener('mouseup', handleMouseUp, true);
164
+ return true; // Đã xử lý event
165
+ }
166
+ }
167
+ }
168
+ })
169
+ ];
170
+ }
171
+ });
@@ -0,0 +1,41 @@
1
+ import { Extension, Node } from '@tiptap/core';
2
+ import { TableKitOptions, TableOptions } from '@tiptap/extension-table';
3
+ export declare const MIN_NEW_COL_WIDTH = 5;
4
+ export declare const TableDefaultAttributes: {
5
+ border: {
6
+ style: string;
7
+ color: string;
8
+ width: string;
9
+ };
10
+ backgroundColor: null;
11
+ colwidths: null;
12
+ cellBorder: {
13
+ style: string;
14
+ color: string;
15
+ width: string;
16
+ };
17
+ cellBackgroundColor: null;
18
+ cellVerticalAlign: string;
19
+ cellTextAlign: null;
20
+ };
21
+ export type TableBorder = {
22
+ style?: string | null;
23
+ color?: string | null;
24
+ width?: string | null;
25
+ } | null | undefined;
26
+ export declare const StyledTableCell: Node<import("@tiptap/extension-table").TableCellOptions, any>;
27
+ export declare const StyledTableHeader: Node<import("@tiptap/extension-table").TableHeaderOptions, any>;
28
+ declare module '@tiptap/core' {
29
+ interface Commands<ReturnType> {
30
+ tableStyles: {
31
+ setCellBorder: (border: TableBorder) => ReturnType;
32
+ setCellBackgroundColor: (color: string | null) => ReturnType;
33
+ setCellVerticalAlign: (align: 'top' | 'middle' | 'bottom' | null) => ReturnType;
34
+ setCellTextAlign: (align: 'left' | 'center' | 'right' | 'justify' | null) => ReturnType;
35
+ setTableBorder: (border: TableBorder) => ReturnType;
36
+ setTableBackgroundColor: (color: string | null) => ReturnType;
37
+ };
38
+ }
39
+ }
40
+ export declare const StyledTable: Node<TableOptions, any>;
41
+ export declare const StyledTableKit: Extension<TableKitOptions, any>;