@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,44 @@
1
+ import { Node } from '@tiptap/pm/model';
2
+ import { EditorView } from '@tiptap/pm/view';
3
+ import { NodeViewRendererProps } from '@tiptap/core';
4
+ export declare abstract class HandleNodeView {
5
+ dom: HTMLElement;
6
+ contentDOM: HTMLElement;
7
+ node: Node;
8
+ view: EditorView;
9
+ getPos: () => number | undefined;
10
+ private eventCleanups;
11
+ constructor(node: Node, view: EditorView, getPos: () => number | undefined);
12
+ private setupDragHandling;
13
+ createBlockHandle(view: EditorView, getPos: () => number | undefined): HTMLElement;
14
+ createTypeAround(view: EditorView, getPos: () => number | undefined, node: Node): HTMLElement;
15
+ /**
16
+ * Ngăn các sự kiện từ handle và type-around buttons lan truyền vào editor
17
+ * CHỈ chặn events từ các controls, KHÔNG chặn events từ content
18
+ *
19
+ * QUAN TRỌNG:
20
+ * - return true = ProseMirror BỎ QUA event này
21
+ * - return false = ProseMirror XỬ LÝ event này (table editing, selection, drag, etc.)
22
+ */
23
+ stopEvent(event: Event): boolean;
24
+ selectNode(): void;
25
+ deselectNode(): void;
26
+ destroy(): void;
27
+ /**
28
+ * Phương thức trừu tượng: Lớp con BẮT BUỘC phải định nghĩa
29
+ * để cho biết nội dung của nó là gì.
30
+ */
31
+ abstract createContentElement(node: Node): HTMLElement;
32
+ }
33
+ export declare class TableNodeView extends HandleNodeView {
34
+ createContentElement(): HTMLElement;
35
+ /**
36
+ * Get table element from DOM
37
+ * Query from this.dom instead of storing reference to avoid stale references
38
+ */
39
+ private getTableElement;
40
+ update(node: Node): boolean;
41
+ private updateTableStyles;
42
+ private updateColgroup;
43
+ }
44
+ export declare const createTableNodeView: (props: NodeViewRendererProps) => TableNodeView;
@@ -0,0 +1,310 @@
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
+ HandleNodeView: function() {
13
+ return HandleNodeView;
14
+ },
15
+ TableNodeView: function() {
16
+ return TableNodeView;
17
+ },
18
+ createTableNodeView: function() {
19
+ return createTableNodeView;
20
+ }
21
+ });
22
+ const _model = require("@tiptap/pm/model");
23
+ const _state = require("@tiptap/pm/state");
24
+ class HandleNodeView {
25
+ setupDragHandling(view, getPos, node) {
26
+ const handleDragStart = (event)=>{
27
+ const pos = getPos();
28
+ if (pos === undefined || !event.dataTransfer) return;
29
+ // Let ProseMirror know which node is being dragged
30
+ // Store node position and allow move effect
31
+ event.dataTransfer.effectAllowed = 'move';
32
+ // ProseMirror expects this format
33
+ const slice = view.state.doc.slice(pos, pos + node.nodeSize);
34
+ const serializer = _model.DOMSerializer.fromSchema(view.state.schema);
35
+ const dom = document.createElement('div');
36
+ dom.appendChild(serializer.serializeFragment(slice.content));
37
+ event.dataTransfer.setDragImage(this.dom, 0, 0);
38
+ event.dataTransfer.setData('text/html', dom.innerHTML);
39
+ // Add dragging state
40
+ this.dom.classList.add('widget-dragging');
41
+ };
42
+ const handleDragEnd = ()=>{
43
+ this.dom.classList.remove('widget-dragging');
44
+ };
45
+ this.dom.addEventListener('dragstart', handleDragStart);
46
+ this.dom.addEventListener('dragend', handleDragEnd);
47
+ this.eventCleanups.push(()=>{
48
+ this.dom.removeEventListener('dragstart', handleDragStart);
49
+ this.dom.removeEventListener('dragend', handleDragEnd);
50
+ });
51
+ }
52
+ createBlockHandle(view, getPos) {
53
+ const handle = document.createElement('div');
54
+ handle.className = 'widget__selection-handle';
55
+ handle.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" fill="currentColor" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M360-160q-33 0-56.5-23.5T280-240q0-33 23.5-56.5T360-320q33 0 56.5 23.5T440-240q0 33-23.5 56.5T360-160Zm240 0q-33 0-56.5-23.5T520-240q0-33 23.5-56.5T600-320q33 0 56.5 23.5T680-240q0 33-23.5 56.5T600-160ZM360-400q-33 0-56.5-23.5T280-480q0-33 23.5-56.5T360-560q33 0 56.5 23.5T440-480q0 33-23.5 56.5T360-400Zm240 0q-33 0-56.5-23.5T520-480q0-33 23.5-56.5T600-560q33 0 56.5 23.5T680-480q0 33-23.5 56.5T600-400ZM360-640q-33 0-56.5-23.5T280-720q0-33 23.5-56.5T360-800q33 0 56.5 23.5T440-720q0 33-23.5 56.5T360-640Zm240 0q-33 0-56.5-23.5T520-720q0-33 23.5-56.5T600-800q33 0 56.5 23.5T680-720q0 33-23.5 56.5T600-640Z"/></svg>';
56
+ handle.contentEditable = 'false';
57
+ // handle.draggable = true;
58
+ handle.setAttribute('data-drag-handle', '');
59
+ handle.setAttribute('data-block-handle', ''); // Mark để dễ check trong stopEvent
60
+ // Logic click-to-select (only on click, not drag)
61
+ let isDragging = false;
62
+ const handleMouseDown = ()=>{
63
+ isDragging = false;
64
+ // Let drag happen naturally, don't prevent
65
+ };
66
+ const handleDragStart = ()=>{
67
+ isDragging = true;
68
+ };
69
+ const handleClick = (event)=>{
70
+ // Only select if it was a click, not a drag
71
+ if (!isDragging && event.button === 0) {
72
+ event.preventDefault();
73
+ event.stopPropagation();
74
+ const pos = getPos() ?? 0;
75
+ const tr = view.state.tr.setSelection(_state.NodeSelection.create(view.state.doc, pos));
76
+ view.dispatch(tr);
77
+ }
78
+ isDragging = false;
79
+ };
80
+ handle.addEventListener('mousedown', handleMouseDown);
81
+ // handle.addEventListener('dragstart', handleDragStart);
82
+ handle.addEventListener('click', handleClick);
83
+ // Store cleanup
84
+ this.eventCleanups.push(()=>{
85
+ handle.removeEventListener('mousedown', handleMouseDown);
86
+ // handle.removeEventListener('dragstart', handleDragStart);
87
+ handle.removeEventListener('click', handleClick);
88
+ });
89
+ return handle;
90
+ }
91
+ createTypeAround(view, getPos, node) {
92
+ const wrapper = document.createElement('div');
93
+ wrapper.className = 'type-around';
94
+ const icon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/></svg>';
95
+ const buttonBefore = document.createElement('div');
96
+ buttonBefore.className = 'widget__type-around__button widget__type-around__button_before';
97
+ buttonBefore.title = 'Insert paragraph before';
98
+ buttonBefore.innerHTML = icon;
99
+ const buttonAfter = document.createElement('div');
100
+ buttonAfter.className = 'widget__type-around__button widget__type-around__button_after';
101
+ buttonAfter.title = 'Insert paragraph after';
102
+ buttonAfter.innerHTML = icon;
103
+ // Logic chèn node 'paragraph' mới
104
+ const handleInsert = (position)=>{
105
+ const currentPos = getPos();
106
+ if (currentPos === undefined) return;
107
+ const newNode = view.state.schema.nodes['paragraph'].create();
108
+ let insertPos;
109
+ if (position === 'before') {
110
+ insertPos = currentPos;
111
+ } else {
112
+ const tableStartPos = view.state.doc.resolve(currentPos);
113
+ const tableIndex = tableStartPos.index();
114
+ insertPos = tableStartPos.posAtIndex(tableIndex + 1);
115
+ }
116
+ const tr = view.state.tr.insert(insertPos, newNode);
117
+ const mappedInsertPos = tr.mapping.map(insertPos, -1);
118
+ const selectionPos = mappedInsertPos + 1;
119
+ // Di chuyển con trỏ vào node mới
120
+ view.dispatch(tr.setSelection(_state.TextSelection.create(tr.doc, selectionPos)));
121
+ view.focus();
122
+ };
123
+ // Button Before event handlers
124
+ const beforeMouseDown = (e)=>{
125
+ e.preventDefault();
126
+ e.stopPropagation();
127
+ };
128
+ const beforeClick = (e)=>{
129
+ e.preventDefault();
130
+ e.stopPropagation();
131
+ handleInsert('before');
132
+ };
133
+ buttonBefore.addEventListener('mousedown', beforeMouseDown);
134
+ buttonBefore.addEventListener('click', beforeClick);
135
+ // Button After event handlers
136
+ const afterMouseDown = (e)=>{
137
+ e.preventDefault();
138
+ e.stopPropagation();
139
+ };
140
+ const afterClick = (e)=>{
141
+ e.preventDefault();
142
+ e.stopPropagation();
143
+ handleInsert('after');
144
+ };
145
+ buttonAfter.addEventListener('mousedown', afterMouseDown);
146
+ buttonAfter.addEventListener('click', afterClick);
147
+ // Store cleanup functions
148
+ this.eventCleanups.push(()=>{
149
+ buttonBefore.removeEventListener('mousedown', beforeMouseDown);
150
+ buttonBefore.removeEventListener('click', beforeClick);
151
+ buttonAfter.removeEventListener('mousedown', afterMouseDown);
152
+ buttonAfter.removeEventListener('click', afterClick);
153
+ });
154
+ wrapper.append(buttonBefore, buttonAfter);
155
+ return wrapper;
156
+ }
157
+ /**
158
+ * Ngăn các sự kiện từ handle và type-around buttons lan truyền vào editor
159
+ * CHỈ chặn events từ các controls, KHÔNG chặn events từ content
160
+ *
161
+ * QUAN TRỌNG:
162
+ * - return true = ProseMirror BỎ QUA event này
163
+ * - return false = ProseMirror XỬ LÝ event này (table editing, selection, drag, etc.)
164
+ */ stopEvent(event) {
165
+ const target = event.target;
166
+ // Chỉ chặn events từ control elements (handle, type-around buttons)
167
+ const isControl = target.closest('[data-block-handle]') || target.closest('.widget__type-around__button') || target.closest('.type-around');
168
+ if (!isControl) {
169
+ // Event từ table content → cho ProseMirror xử lý
170
+ return false;
171
+ }
172
+ // Event từ control → chặn hầu hết, NHƯNG cho phép một số events quan trọng
173
+ const eventType = event.type;
174
+ // Cho phép drag events để ProseMirror handle drag & drop
175
+ if (eventType === 'dragstart' || eventType === 'drag' || eventType === 'dragend') {
176
+ return false; // Let ProseMirror handle drag
177
+ }
178
+ // Cho phép focus events để không phá navigation
179
+ if (eventType === 'focus' || eventType === 'blur') {
180
+ return false;
181
+ }
182
+ // Chặn các events khác từ controls
183
+ return true;
184
+ }
185
+ selectNode() {
186
+ this.dom.classList.add('ProseMirror-selectednode');
187
+ }
188
+ deselectNode() {
189
+ this.dom.classList.remove('ProseMirror-selectednode');
190
+ }
191
+ destroy() {
192
+ // Cleanup all event listeners
193
+ this.eventCleanups.forEach((cleanup)=>cleanup());
194
+ this.eventCleanups = [];
195
+ // Remove DOM
196
+ this.dom.remove();
197
+ }
198
+ constructor(node, view, getPos){
199
+ // Store event listeners for cleanup
200
+ this.eventCleanups = [];
201
+ this.node = node;
202
+ this.view = view;
203
+ this.getPos = getPos;
204
+ // 1. Dùng <figure> làm wrapper, KHÔNG set contentEditable vì sẽ chặn tất cả
205
+ this.dom = document.createElement('figure');
206
+ this.dom.classList.add('widget');
207
+ // Make the widget draggable (via the handle with data-drag-handle attribute)
208
+ // this.dom.draggable = true;
209
+ // 2. Tạo Handle kéo thả
210
+ const handle = this.createBlockHandle(view, getPos);
211
+ // 3. Tạo Nội dung (do lớp con định nghĩa)
212
+ const contentElement = this.createContentElement(node);
213
+ contentElement.classList.add('widget-content');
214
+ // 4. Tạo các nút "Type Around"
215
+ const typeAround = this.createTypeAround(view, getPos, node);
216
+ // 5. Lắp ráp cấu trúc: Handle -> Nội dung -> TypeAround
217
+ // this.dom.append(handle, contentElement, typeAround);
218
+ this.dom.append(contentElement, typeAround);
219
+ // 6. Setup drag handling for ProseMirror
220
+ // this.setupDragHandling(view, getPos, node);
221
+ }
222
+ }
223
+ class TableNodeView extends HandleNodeView {
224
+ createContentElement() {
225
+ // Create table element
226
+ const table = document.createElement('table');
227
+ // Apply table styles (border and backgroundColor)
228
+ this.updateTableStyles(table);
229
+ // Create tbody - this is the contentDOM where ProseMirror renders <tr>s
230
+ const tbody = document.createElement('tbody');
231
+ this.contentDOM = tbody;
232
+ table.appendChild(tbody);
233
+ // Render colgroup
234
+ this.updateColgroup(table);
235
+ return table;
236
+ }
237
+ /**
238
+ * Get table element from DOM
239
+ * Query from this.dom instead of storing reference to avoid stale references
240
+ */ getTableElement() {
241
+ return this.dom.querySelector('table');
242
+ }
243
+ update(node) {
244
+ // Check if node actually changed
245
+ if (node.type !== this.node.type) {
246
+ return false;
247
+ }
248
+ // Update node reference
249
+ this.node = node;
250
+ // Get table element
251
+ const table = this.getTableElement();
252
+ if (!table) {
253
+ return true;
254
+ }
255
+ // Update table styles if border or backgroundColor changed
256
+ this.updateTableStyles(table);
257
+ // Update colgroup if colwidths changed
258
+ this.updateColgroup(table);
259
+ return true;
260
+ }
261
+ updateTableStyles(table) {
262
+ const border = this.node.attrs['border'];
263
+ const backgroundColor = this.node.attrs['backgroundColor'];
264
+ // Reset styles
265
+ // (Chúng ta reset để xử lý trường hợp attribute bị xóa)
266
+ table.style.borderStyle = '';
267
+ table.style.borderColor = '';
268
+ table.style.borderWidth = '';
269
+ table.style.backgroundColor = '';
270
+ // Apply backgroundColor
271
+ if (backgroundColor) {
272
+ table.style.backgroundColor = backgroundColor;
273
+ }
274
+ // Apply border
275
+ if (border) {
276
+ if (border.style) table.style.borderStyle = border.style;
277
+ if (border.color) table.style.borderColor = border.color;
278
+ if (border.width) table.style.borderWidth = border.width;
279
+ }
280
+ }
281
+ updateColgroup(table) {
282
+ // Remove existing colgroup if exists
283
+ const existingColgroup = table.querySelector('colgroup');
284
+ if (existingColgroup) {
285
+ existingColgroup.remove();
286
+ }
287
+ // Render new colgroup if colwidths exist
288
+ const colwidths = this.node.attrs['colwidths'];
289
+ if (colwidths && colwidths.length > 0) {
290
+ const colgroup = document.createElement('colgroup');
291
+ colwidths.forEach((width)=>{
292
+ const col = document.createElement('col');
293
+ if (width) {
294
+ col.style.width = `${width}%`;
295
+ }
296
+ colgroup.appendChild(col);
297
+ });
298
+ // Insert before tbody
299
+ const tbody = table.querySelector('tbody');
300
+ if (tbody) {
301
+ table.insertBefore(colgroup, tbody);
302
+ } else {
303
+ table.appendChild(colgroup);
304
+ }
305
+ }
306
+ }
307
+ }
308
+ const createTableNodeView = (props)=>{
309
+ return new TableNodeView(props.node, props.view, props.getPos);
310
+ };
@@ -0,0 +1 @@
1
+ export * from './block-handler';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ _export_star(require("./block-handler"), exports);
6
+ function _export_star(from, to) {
7
+ Object.keys(from).forEach(function(k) {
8
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
9
+ Object.defineProperty(to, k, {
10
+ enumerable: true,
11
+ get: function() {
12
+ return from[k];
13
+ }
14
+ });
15
+ }
16
+ });
17
+ return from;
18
+ }