@phuong-tran-redoc/document-engine-core 0.1.0 → 0.1.2

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 (46) hide show
  1. package/README.md +105 -115
  2. package/THIRD-PARTY-NOTICES.txt +67 -0
  3. package/index.d.ts +1 -0
  4. package/index.esm.js +2238 -0
  5. package/package.json +21 -5
  6. package/src/__tests__/helpers/editor-factory.d.ts +0 -13
  7. package/src/__tests__/helpers/editor-factory.js +0 -25
  8. package/src/constants/indent.constant.js +0 -1
  9. package/src/constants/index.js +0 -1
  10. package/src/constants/table.constant.js +0 -1
  11. package/src/extensions/clear-content.extension.js +0 -9
  12. package/src/extensions/indent.extension.js +0 -59
  13. package/src/extensions/index.js +0 -8
  14. package/src/extensions/ordered-list.extension.js +0 -47
  15. package/src/extensions/reset-format.extension.js +0 -9
  16. package/src/extensions/reset-on-enter.extension.js +0 -37
  17. package/src/extensions/restricted-editing.extension.js +0 -376
  18. package/src/extensions/table-resizing.extension.js +0 -164
  19. package/src/extensions/table-style.extension.js +0 -481
  20. package/src/extensions/text-case.extension.js +0 -39
  21. package/src/index.js +0 -9
  22. package/src/kit/default-extensions.js +0 -86
  23. package/src/kit/generate-html.js +0 -40
  24. package/src/kit/index.js +0 -2
  25. package/src/migrations/doc-migrations.js +0 -24
  26. package/src/migrations/index.js +0 -3
  27. package/src/migrations/migrate-doc.js +0 -32
  28. package/src/migrations/types.js +0 -4
  29. package/src/models/color.model.js +0 -30
  30. package/src/models/index.js +0 -1
  31. package/src/nodes/dynamic-field.node.js +0 -141
  32. package/src/nodes/heading.node.js +0 -13
  33. package/src/nodes/image-ref.node.js +0 -138
  34. package/src/nodes/index.js +0 -4
  35. package/src/nodes/page-break.node.js +0 -52
  36. package/src/types/index.js +0 -2
  37. package/src/types/ordered-list.type.js +0 -2
  38. package/src/types/text-case.type.js +0 -1
  39. package/src/types/tiptap-html-server.d.js +0 -10
  40. package/src/utils/color.util.js +0 -31
  41. package/src/utils/dom.util.js +0 -33
  42. package/src/utils/index.js +0 -4
  43. package/src/utils/table.util.js +0 -95
  44. package/src/utils/text.util.js +0 -46
  45. package/src/views/block-handler.js +0 -311
  46. package/src/views/index.js +0 -1
@@ -1,164 +0,0 @@
1
- import { Extension } from '@tiptap/core';
2
- import { Plugin, PluginKey } from '@tiptap/pm/state';
3
- import { tableNodeTypes } from '@tiptap/pm/tables';
4
- import { Decoration, DecorationSet } from '@tiptap/pm/view';
5
- import { getTableDOMFromView } from '../utils';
6
- /**
7
- * Handler for mouse move (dragging)
8
- */ function handleMouseMoveEvent(event, dragInfo) {
9
- if (!dragInfo.isDragging) return;
10
- event.preventDefault();
11
- const { startX, tableWidthPx, initialLeftWidth, initialRightWidth, leftColIndex, tablePos, tableNode, view } = dragInfo;
12
- // 1. Compute the delta (pixels)
13
- const deltaX = event.clientX - startX;
14
- // 2. Convert the pixel delta into a percentage delta
15
- // This is the most important piece of logic
16
- const deltaPercent = deltaX / tableWidthPx * 100;
17
- // 3. Compute the new widths
18
- let newLeftWidth = initialLeftWidth + deltaPercent;
19
- let newRightWidth = initialRightWidth - deltaPercent;
20
- // 4. Apply constraints (e.g. min-width 5%)
21
- const minWidth = 5; // min-width configuration
22
- if (newLeftWidth < minWidth) {
23
- const diff = minWidth - newLeftWidth;
24
- newLeftWidth = minWidth;
25
- newRightWidth = newRightWidth - diff; // Right column absorbs the shortfall
26
- } else if (newRightWidth < minWidth) {
27
- const diff = minWidth - newRightWidth;
28
- newRightWidth = minWidth;
29
- newLeftWidth = newLeftWidth - diff; // Left column absorbs the shortfall
30
- }
31
- // Make sure no column goes negative (in case of a very fast drag)
32
- if (newLeftWidth < 0) newLeftWidth = 0;
33
- if (newRightWidth < 0) newRightWidth = 0;
34
- // 5. Build the new colwidths array
35
- const newColWidths = [
36
- ...tableNode?.attrs?.['colwidths']
37
- ];
38
- newColWidths[leftColIndex] = newLeftWidth;
39
- newColWidths[leftColIndex + 1] = newRightWidth;
40
- // 6. Dispatch a transaction to update the state
41
- if (tableNode && view) {
42
- const tr = view.state.tr.setNodeMarkup(tablePos, null, {
43
- ...tableNode.attrs,
44
- colwidths: newColWidths
45
- });
46
- // Use dispatchTransaction so Tiptap/ProseMirror updates the UI itself
47
- view.dispatch(tr);
48
- }
49
- }
50
- /**
51
- * Handler for mouse up (end of drag)
52
- */ function handleMouseUpEvent(event, dragInfo) {
53
- if (!dragInfo.isDragging) return;
54
- event.preventDefault();
55
- // Reset the state
56
- dragInfo = {
57
- isDragging: false,
58
- startX: 0,
59
- tablePos: -1,
60
- tableNode: null,
61
- tableWidthPx: 0,
62
- leftColIndex: -1,
63
- initialLeftWidth: 0,
64
- initialRightWidth: 0,
65
- view: null
66
- };
67
- }
68
- /**
69
- * Extension PercentageColumnResizing
70
- * * Replaces the default column-resize logic with percentage-based logic.
71
- */ export const PercentageColumnResizing = Extension.create({
72
- name: 'percentageColumnResizing',
73
- addProseMirrorPlugins () {
74
- // A temporary object to hold the drag-and-drop state
75
- // We don't use Plugin State because this state never needs to be "undone"
76
- let dragInfo = {
77
- isDragging: false,
78
- startX: 0,
79
- tablePos: -1,
80
- tableNode: null,
81
- tableWidthPx: 0,
82
- leftColIndex: -1,
83
- initialLeftWidth: 0,
84
- initialRightWidth: 0,
85
- view: null
86
- };
87
- const editorInstance = this.editor;
88
- return [
89
- new Plugin({
90
- key: new PluginKey('percentageColumnResizing'),
91
- props: {
92
- /**
93
- * Draw the resize handles into the DOM
94
- */ decorations (state) {
95
- if (!editorInstance?.isEditable) return;
96
- const decorations = [];
97
- const { doc } = state;
98
- const tableTypes = tableNodeTypes(state.schema);
99
- // Walk every node in the document
100
- doc.descendants((node, pos)=>{
101
- if (node.type.name !== tableTypes.table.name) return;
102
- const colwidths = node.attrs['colwidths'];
103
- if (!colwidths || colwidths.length === 0) return;
104
- let accumulatedWidth = 0;
105
- // Create a handle for N-1 columns (the last column doesn't need one)
106
- for(let i = 0; i < colwidths.length - 1; i++){
107
- accumulatedWidth += colwidths[i] ?? 0;
108
- // Create a 'div' widget to act as the handle
109
- const handle = document.createElement('div');
110
- handle.className = 'pm-col-resizer';
111
- handle.style.left = `calc(${accumulatedWidth}% - 2px)`;
112
- // Store the column position so we know which column is being resized
113
- handle.setAttribute('data-col-index', i.toString());
114
- handle.setAttribute('data-table-pos', pos.toString());
115
- // Place the widget right after the <table> tag (pos + 1)
116
- decorations.push(Decoration.widget(pos + 1, handle));
117
- }
118
- });
119
- return DecorationSet.create(doc, decorations);
120
- },
121
- /**
122
- * Handle the mousedown event on a handle
123
- */ handleDOMEvents: {
124
- mousedown (view, event) {
125
- const target = event.target;
126
- // Only trigger when clicking on a handle
127
- if (!target.classList.contains('pm-col-resizer')) return false;
128
- event.preventDefault();
129
- const colIndex = parseInt(target.getAttribute('data-col-index') ?? '0', 10);
130
- const tablePos = parseInt(target.getAttribute('data-table-pos') ?? '0', 10);
131
- const tableNode = view.state.doc.nodeAt(tablePos);
132
- const colwidths = tableNode?.attrs['colwidths'];
133
- if (!tableNode || !colwidths) return false;
134
- // Get table DOM element (handles NodeView wrappers and nested tables)
135
- const tableDOM = getTableDOMFromView(view, tablePos);
136
- if (!tableDOM) return false;
137
- // Store the info needed for dragging
138
- dragInfo = {
139
- isDragging: true,
140
- startX: event.clientX,
141
- tablePos: tablePos,
142
- tableNode: tableNode,
143
- tableWidthPx: tableDOM.offsetWidth,
144
- leftColIndex: colIndex,
145
- initialLeftWidth: colwidths[colIndex],
146
- initialRightWidth: colwidths[colIndex + 1],
147
- view: view
148
- };
149
- const handleMouseMove = (event)=>handleMouseMoveEvent(event, dragInfo);
150
- const handleMouseUp = (event)=>{
151
- handleMouseUpEvent(event, dragInfo);
152
- window.removeEventListener('mousemove', handleMouseMove, true);
153
- window.removeEventListener('mouseup', handleMouseUp, true);
154
- };
155
- window.addEventListener('mousemove', handleMouseMove, true);
156
- window.addEventListener('mouseup', handleMouseUp, true);
157
- return true; // Event handled
158
- }
159
- }
160
- }
161
- })
162
- ];
163
- }
164
- });
@@ -1,481 +0,0 @@
1
- import { Extension } from '@tiptap/core';
2
- import { Table, TableCell, TableHeader, TableRow } from '@tiptap/extension-table';
3
- import { TextSelection } from '@tiptap/pm/state';
4
- import { addColumnAfter, addColumnBefore, deleteColumn, selectionCell, TableMap } from '@tiptap/pm/tables';
5
- import { MIN_NEW_COL_WIDTH } from '../constants/table.constant';
6
- import { getCursorCellInfo } from '../utils';
7
- import { createTableNodeView } from '../views';
8
- import { PercentageColumnResizing } from './table-resizing.extension';
9
- /**
10
- * Calculate new column widths when adding a column, ensuring no column goes below MIN_NEW_COL_WIDTH
11
- * @param oldColWidths - Array of current column widths
12
- * @returns Array of adjusted column widths (without the new column inserted yet)
13
- */ function calculateColumnWidthsForNewColumn(oldColWidths) {
14
- // Find columns that can contribute (above minimum)
15
- const columnsAboveMin = oldColWidths.filter((w)=>w > MIN_NEW_COL_WIDTH);
16
- // If not enough space, proportionally reduce all columns
17
- if (columnsAboveMin.length === 0) {
18
- const reduction = MIN_NEW_COL_WIDTH / oldColWidths.length;
19
- return oldColWidths.map((w)=>w - reduction);
20
- }
21
- // Calculate available space from columns above minimum
22
- const availableSpace = columnsAboveMin.reduce((sum, w)=>sum + (w - MIN_NEW_COL_WIDTH), 0);
23
- // If we have enough space, only tax columns above minimum
24
- if (availableSpace >= MIN_NEW_COL_WIDTH) {
25
- return oldColWidths.map((w)=>{
26
- if (w > MIN_NEW_COL_WIDTH) {
27
- const contribution = (w - MIN_NEW_COL_WIDTH) / availableSpace * MIN_NEW_COL_WIDTH;
28
- return Math.max(MIN_NEW_COL_WIDTH, w - contribution);
29
- }
30
- return w;
31
- });
32
- }
33
- // Not enough space even from columns above min, reduce proportionally
34
- const reduction = MIN_NEW_COL_WIDTH / oldColWidths.length;
35
- return oldColWidths.map((w)=>w - reduction);
36
- }
37
- export const TableDefaultAttributes = {
38
- border: {
39
- style: 'double',
40
- color: '#e5e7eb',
41
- width: '1px'
42
- },
43
- backgroundColor: null,
44
- colwidths: null,
45
- cellBorder: {
46
- style: 'solid',
47
- color: '#e5e7eb',
48
- width: '1px'
49
- },
50
- cellBackgroundColor: null,
51
- cellVerticalAlign: 'middle',
52
- cellTextAlign: null
53
- };
54
- // Extend TableCell and TableHeader to support styling attributes
55
- const commonCellAttributes = {
56
- backgroundColor: {
57
- default: null,
58
- renderHTML: (attrs)=>attrs.backgroundColor ? {
59
- style: `background-color: ${attrs.backgroundColor};`
60
- } : {},
61
- parseHTML: (element)=>element.style.getPropertyValue('background-color') || null
62
- },
63
- border: {
64
- default: TableDefaultAttributes.cellBorder,
65
- renderHTML: (attrs)=>{
66
- const border = attrs['border'];
67
- if (!border) return {};
68
- const styles = [];
69
- if (border.style) styles.push(`border-style: ${border.style}`);
70
- if (border.color) styles.push(`border-color: ${border.color}`);
71
- if (border.width) styles.push(`border-width: ${border.width}`);
72
- return styles.length ? {
73
- style: styles.join('; ') + ';'
74
- } : {};
75
- },
76
- parseHTML: (element)=>{
77
- const style = element.style.getPropertyValue('border-style');
78
- const color = element.style.getPropertyValue('border-color');
79
- const width = element.style.getPropertyValue('border-width');
80
- if (!style && !color && !width) return null;
81
- return {
82
- style: style || null,
83
- color: color || null,
84
- width: width || null
85
- };
86
- }
87
- },
88
- verticalAlign: {
89
- default: TableDefaultAttributes.cellVerticalAlign,
90
- renderHTML: (attrs)=>attrs.verticalAlign ? {
91
- style: `vertical-align: ${attrs.verticalAlign};`
92
- } : {},
93
- parseHTML: (element)=>element.style.getPropertyValue('vertical-align') || null
94
- },
95
- textAlign: {
96
- default: TableDefaultAttributes.cellTextAlign,
97
- renderHTML: (attrs)=>attrs.textAlign ? {
98
- style: `text-align: ${attrs.textAlign};`
99
- } : {},
100
- parseHTML: (element)=>element.style.getPropertyValue('text-align') || null
101
- }
102
- };
103
- export const StyledTableCell = TableCell.extend({
104
- addAttributes () {
105
- const parent = this.parent?.() ?? {};
106
- return {
107
- ...parent,
108
- ...commonCellAttributes
109
- };
110
- }
111
- });
112
- export const StyledTableHeader = TableHeader.extend({
113
- addAttributes () {
114
- const parent = this.parent?.() ?? {};
115
- return {
116
- ...parent,
117
- ...commonCellAttributes
118
- };
119
- }
120
- });
121
- // Extend Table node to support width (%) and alignment (left|center|right)
122
- export const StyledTable = Table.extend({
123
- // name: 'styledTable',
124
- // draggable: true,
125
- addOptions () {
126
- return {
127
- ...this.parent?.(),
128
- enableNodeView: true
129
- };
130
- },
131
- addAttributes () {
132
- const parent = this.parent?.() ?? {};
133
- return {
134
- ...parent,
135
- border: {
136
- default: null,
137
- renderHTML: (attrs)=>{
138
- const border = attrs.border;
139
- if (!border) return {};
140
- const styles = [];
141
- if (border.style) styles.push(`border-style: ${border.style}`);
142
- if (border.color) styles.push(`border-color: ${border.color}`);
143
- if (border.width) styles.push(`border-width: ${border.width}`);
144
- return styles.length ? {
145
- style: styles.join('; ') + ';'
146
- } : {};
147
- },
148
- parseHTML: (element)=>{
149
- const style = element.style.getPropertyValue('border-style');
150
- const color = element.style.getPropertyValue('border-color');
151
- const width = element.style.getPropertyValue('border-width');
152
- if (!style && !color && !width) return null;
153
- return {
154
- style: style || null,
155
- color: color || null,
156
- width: width || null
157
- };
158
- }
159
- },
160
- backgroundColor: {
161
- default: null,
162
- renderHTML: (attrs)=>attrs.backgroundColor ? {
163
- style: `background-color: ${attrs.backgroundColor};`
164
- } : {},
165
- parseHTML: (element)=>element.style.getPropertyValue('background-color') || null
166
- },
167
- colwidths: {
168
- default: null,
169
- renderHTML: (attributes)=>{
170
- if (!attributes['colwidths']) {
171
- return {}; // Nothing to render
172
- }
173
- return {
174
- 'data-colwidths': JSON.stringify(attributes['colwidths'])
175
- };
176
- },
177
- parseHTML: (element)=>{
178
- // Only get cols from direct child colgroup (not from nested tables)
179
- const colgroup = element.querySelector(':scope > colgroup');
180
- if (!colgroup) return null;
181
- const cols = colgroup.querySelectorAll('col');
182
- if (cols.length === 0) return null;
183
- const colwidths = [];
184
- cols.forEach((col)=>{
185
- const width = col.getAttribute('style');
186
- if (width) {
187
- const match = width.match(/width:\s*(\d+(?:\.\d+)?)%/);
188
- colwidths.push(match ? parseFloat(match[1]) : null);
189
- } else {
190
- colwidths.push(null);
191
- }
192
- });
193
- return colwidths.length > 0 ? colwidths : null;
194
- }
195
- }
196
- };
197
- },
198
- renderHTML ({ node, HTMLAttributes }) {
199
- const colwidths = node.attrs?.['colwidths'];
200
- const domNodes = [];
201
- // Render <colgroup> when colwidths are present
202
- if (colwidths) {
203
- const colgroup = [
204
- 'colgroup',
205
- {}
206
- ];
207
- colwidths.forEach((width)=>{
208
- colgroup.push([
209
- 'col',
210
- {
211
- style: width ? `width: ${width}%` : null
212
- }
213
- ]);
214
- });
215
- domNodes.push(colgroup);
216
- }
217
- // Add <tbody> (0 is the 'content hole')
218
- domNodes.push([
219
- 'tbody',
220
- 0
221
- ]);
222
- // Merge the HTML attributes (including global attributes)
223
- // HTMLAttributes has already been merged by Tiptap via getRenderedAttributes
224
- return [
225
- 'table',
226
- HTMLAttributes,
227
- ...domNodes
228
- ];
229
- },
230
- addCommands () {
231
- const parent = this.parent?.() ?? {};
232
- return {
233
- ...parent,
234
- // Table
235
- setTableBorder: (border)=>({ chain, state })=>{
236
- if (!border) return chain().focus().updateAttributes('table', {
237
- border: null
238
- }).run();
239
- // Get current table border and merge with new values
240
- // Find the table node
241
- const cellInfo = getCursorCellInfo(state);
242
- if (!cellInfo) return false;
243
- const currentBorder = cellInfo?.tableNode?.attrs?.['border'] || {};
244
- const mergedBorder = {
245
- style: border.style !== undefined ? border.style : currentBorder.style || null,
246
- color: border.color !== undefined ? border.color : currentBorder.color || null,
247
- width: border.width !== undefined ? border.width : currentBorder.width || null
248
- };
249
- return chain().focus().updateAttributes('table', {
250
- border: mergedBorder
251
- }).run();
252
- },
253
- setTableBackgroundColor: (color)=>({ chain })=>{
254
- return chain().focus().updateAttributes('table', {
255
- backgroundColor: color ?? null
256
- }).run();
257
- },
258
- insertTable: ({ rows, cols, withHeaderRow })=>({ tr, dispatch, editor })=>{
259
- const { schema } = editor.state;
260
- // --- STEP 1: Compute colWidths ---
261
- const calculatedColWidths = [];
262
- const defaultWidth = 100 / cols;
263
- for(let i = 0; i < cols; i += 1){
264
- calculatedColWidths.push(defaultWidth);
265
- }
266
- // Example: cols = 3 -> calculatedColWidths = [33.33, 33.33, 33.33]
267
- // --- STEP 2: Build the table content (rows, cells) ---
268
- // We have to build the child nodes ourselves
269
- const cellType = schema.nodes['tableCell'];
270
- const headerType = schema.nodes['tableHeader'];
271
- const rowType = schema.nodes['tableRow'];
272
- const tableRows = [];
273
- // Helper to create a single empty cell
274
- const createEmptyCell = (type)=>{
275
- const emptyParagraph = schema.nodes['paragraph'].create();
276
- return type.create(null, emptyParagraph);
277
- };
278
- // Create the rows
279
- for(let r = 0; r < rows; r += 1){
280
- const tableCells = [];
281
- for(let c = 0; c < cols; c += 1){
282
- let cellNode;
283
- // If this is the first row and 'withHeaderRow' is set
284
- if (r === 0 && withHeaderRow) {
285
- cellNode = createEmptyCell(headerType);
286
- } else {
287
- cellNode = createEmptyCell(cellType);
288
- }
289
- tableCells.push(cellNode);
290
- }
291
- // Add the new row to the rows array
292
- tableRows.push(rowType.create(null, tableCells));
293
- }
294
- // --- STEP 3: Create the main Table node WITH the 'colwidths' ATTRIBUTE ---
295
- const tableNode = schema.nodes['table'].create({
296
- // This is the key part: assign our percentage array here
297
- colwidths: calculatedColWidths
298
- }, tableRows // Content (the 'tableRow' nodes)
299
- );
300
- // --- STEP 4: Dispatch transaction (Original code)---
301
- if (dispatch) {
302
- const offset = tr.selection.from + 1;
303
- tr.replaceSelectionWith(tableNode).scrollIntoView().setSelection(TextSelection.near(tr.doc.resolve(offset)));
304
- }
305
- return true;
306
- },
307
- // 2. ADDCOLUMNAFTER COMMAND
308
- addColumnAfter: ()=>({ state, dispatch })=>{
309
- const $cell = selectionCell(state);
310
- if (!$cell) return false;
311
- const table = $cell.node(-1);
312
- const tablePos = $cell.start(-1) - 1;
313
- const oldColWidths = table.attrs['colwidths'];
314
- // Calculate column index
315
- const map = TableMap.get(table);
316
- const cellPosInTable = $cell.pos - $cell.start(-1);
317
- const colIndex = map.colCount(cellPosInTable);
318
- // Calculate new column widths - ensure no column goes below MIN_NEW_COL_WIDTH
319
- const newColWidths = calculateColumnWidthsForNewColumn(oldColWidths);
320
- // Insert new column width
321
- newColWidths.splice(colIndex + 1, 0, MIN_NEW_COL_WIDTH);
322
- if (dispatch) {
323
- // Use the prosemirror-tables function directly to add column
324
- // This modifies the transaction but doesn't dispatch it
325
- addColumnAfter(state, (modifiedTr)=>{
326
- // Add our colwidth changes to the same transaction
327
- modifiedTr.setNodeMarkup(tablePos, null, {
328
- ...table.attrs,
329
- colwidths: newColWidths
330
- });
331
- // Now dispatch the complete transaction
332
- dispatch(modifiedTr.scrollIntoView());
333
- });
334
- }
335
- return true;
336
- },
337
- // 3. ADDCOLUMNBEFORE COMMAND
338
- addColumnBefore: ()=>({ state, dispatch })=>{
339
- const $cell = selectionCell(state);
340
- if (!$cell) return false;
341
- const table = $cell.node(-1);
342
- const tablePos = $cell.start(-1) - 1;
343
- const oldColWidths = table.attrs['colwidths'];
344
- // Calculate column index
345
- const map = TableMap.get(table);
346
- const cellPosInTable = $cell.pos - $cell.start(-1);
347
- const colIndex = map.colCount(cellPosInTable);
348
- // Calculate new column widths - ensure no column goes below MIN_NEW_COL_WIDTH
349
- const newColWidths = calculateColumnWidthsForNewColumn(oldColWidths);
350
- // Insert new column width at current column index (before)
351
- newColWidths.splice(colIndex, 0, MIN_NEW_COL_WIDTH);
352
- if (dispatch) {
353
- addColumnBefore(state, (modifiedTr)=>{
354
- modifiedTr.setNodeMarkup(tablePos, null, {
355
- ...table.attrs,
356
- colwidths: newColWidths
357
- });
358
- dispatch(modifiedTr.scrollIntoView());
359
- });
360
- }
361
- return true;
362
- },
363
- // 4. DELETECOLUMN COMMAND
364
- deleteColumn: ()=>({ state, dispatch })=>{
365
- const $cell = selectionCell(state);
366
- if (!$cell) return false;
367
- const table = $cell.node(-1);
368
- const tablePos = $cell.start(-1) - 1;
369
- const oldColWidths = table.attrs['colwidths'];
370
- // Calculate column index
371
- const map = TableMap.get(table);
372
- const cellPosInTable = $cell.pos - $cell.start(-1);
373
- const colIndex = map.colCount(cellPosInTable);
374
- // Get width of column being removed
375
- const removedWidth = oldColWidths[colIndex];
376
- // Create array of remaining columns
377
- const remainingWidths = oldColWidths.filter((_, i)=>i !== colIndex);
378
- // Distribute removed width among remaining columns
379
- const gainPerCol = removedWidth / remainingWidths.length;
380
- const newColWidths = remainingWidths.map((w)=>w + gainPerCol);
381
- if (dispatch) {
382
- deleteColumn(state, (modifiedTr)=>{
383
- modifiedTr.setNodeMarkup(tablePos, null, {
384
- ...table.attrs,
385
- colwidths: newColWidths
386
- });
387
- dispatch(modifiedTr.scrollIntoView());
388
- });
389
- }
390
- return true;
391
- },
392
- // Cell
393
- setCellBorder: (border)=>({ chain, state })=>{
394
- if (!border) return chain().focus().updateAttributes('tableCell', {
395
- border: null
396
- }).updateAttributes('tableHeader', {
397
- border: null
398
- }).run();
399
- // Get current border and merge with new values
400
- const { selection } = state;
401
- const { $from } = selection;
402
- const cellPos = $from.node(-1);
403
- const currentBorder = cellPos?.attrs?.['border'] || {};
404
- const mergedBorder = {
405
- style: border.style ? border.style : currentBorder.style || null,
406
- color: border.color ? border.color : currentBorder.color || null,
407
- width: border.width ? border.width : currentBorder.width || null
408
- };
409
- return chain().focus().updateAttributes('tableCell', {
410
- border: mergedBorder
411
- }).updateAttributes('tableHeader', {
412
- border: mergedBorder
413
- }).run();
414
- },
415
- setCellVerticalAlign: (align)=>({ chain })=>{
416
- return chain().focus().updateAttributes('tableCell', {
417
- verticalAlign: align ?? null
418
- }).updateAttributes('tableHeader', {
419
- verticalAlign: align ?? null
420
- }).run();
421
- },
422
- setCellTextAlign: (align)=>({ chain })=>{
423
- return chain().focus().updateAttributes('tableCell', {
424
- textAlign: align ?? null
425
- }).updateAttributes('tableHeader', {
426
- textAlign: align ?? null
427
- }).run();
428
- },
429
- setCellBackgroundColor: (color)=>({ chain })=>{
430
- // Set all cells to the same background color
431
- return chain().focus().updateAttributes('tableCell', {
432
- backgroundColor: color ?? null
433
- }).updateAttributes('tableHeader', {
434
- backgroundColor: color ?? null
435
- }).run();
436
- }
437
- };
438
- },
439
- addNodeView () {
440
- const options = this.options;
441
- if (options.enableNodeView === false) {
442
- return null;
443
- }
444
- // Only use custom NodeView (with handle) when editor is editable
445
- return (props)=>{
446
- // Check if editor is editable
447
- if (!props.editor.isEditable) {
448
- // Return undefined to use default table rendering (no widget wrapper)
449
- return undefined;
450
- }
451
- // Editor is editable → use custom NodeView with handle
452
- return createTableNodeView(props);
453
- };
454
- }
455
- });
456
- // Convenience export for consumers who want the full set
457
- export const StyledTableKit = Extension.create({
458
- name: 'styledTableKit',
459
- addExtensions () {
460
- const extensions = [];
461
- if (this.options.table !== false) {
462
- extensions.push(StyledTable.configure({
463
- ...this.options.table,
464
- resizable: false
465
- }));
466
- }
467
- if (this.options.tableRow !== false) {
468
- extensions.push(TableRow.configure(this.options.tableRow));
469
- }
470
- if (this.options.tableHeader !== false) {
471
- extensions.push(StyledTableHeader.configure(this.options.tableHeader));
472
- }
473
- if (this.options.tableCell !== false) {
474
- extensions.push(StyledTableCell.configure(this.options.tableCell));
475
- }
476
- if (this.options.table?.resizable !== false) {
477
- extensions.push(PercentageColumnResizing);
478
- }
479
- return extensions;
480
- }
481
- });
@@ -1,39 +0,0 @@
1
- import { Extension } from '@tiptap/core';
2
- import { TextSelection } from '@tiptap/pm/state';
3
- export const TextCase = Extension.create({
4
- name: 'textCase',
5
- addOptions () {
6
- return {
7
- type: 'uppercase'
8
- };
9
- },
10
- addCommands () {
11
- return {
12
- textCase: (type)=>({ tr, state })=>{
13
- const { $from, $to, empty } = tr.selection;
14
- // If selection is empty, return false
15
- if (empty) return false;
16
- // If selection is not empty, get the text of the selection
17
- const text = state.doc.textBetween($from.pos, $to.pos, '\n', '\n');
18
- // If text is empty, return false
19
- if (text.length === 0) return false;
20
- let newText = text;
21
- // Change the text case
22
- switch(type){
23
- case 'uppercase':
24
- newText = text.toUpperCase();
25
- break;
26
- case 'lowercase':
27
- newText = text.toLowerCase();
28
- break;
29
- case 'capitalize':
30
- newText = text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
31
- break;
32
- }
33
- tr.replaceWith($from.pos, $to.pos, state.schema.text(newText));
34
- tr.setSelection(TextSelection.create(tr.doc, $from.pos, $from.pos + newText.length));
35
- return true;
36
- }
37
- };
38
- }
39
- });