Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions blocks/edit/prose/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
handleUndo,
handleRedo,
} from './plugins/keyHandlers.js';
import tableHeaderFix from './plugins/tableHeaderFix.js';

let sendUpdates = false;
let hasChanged = 0;
Expand Down Expand Up @@ -290,6 +291,7 @@ export default function initProse({ path, permissions }) {
}),
gapCursor(),
tableEditing(),
tableHeaderFix(),
];

if (canWrite) plugins.push(menu);
Expand Down
67 changes: 67 additions & 0 deletions blocks/edit/prose/plugins/tableHeaderFix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Plugin, TableMap } from 'da-y-wrapper';

// auto-fix table header colspan when columns are added
export default function tableHeaderFix() {
return new Plugin({
appendTransaction(transactions, oldState, newState) {
if (!transactions.some((tr) => tr.docChanged)) return null;

const tablesToCheck = new Map();
newState.doc.descendants((node, pos) => {
if (node.type.name === 'table') {
const newMap = TableMap.get(node);
const newColCount = newMap.width;

try {
const oldNode = oldState.doc.nodeAt(pos);
if (oldNode && oldNode.type.name === 'table') {
const oldMap = TableMap.get(oldNode);
const oldColCount = oldMap.width;

if (oldColCount < newColCount) {
tablesToCheck.set(pos, node);
}
}
} catch (e) {
// no op
}
}
});

if (tablesToCheck.size === 0) return null;

const { tr } = newState;
tablesToCheck.forEach((table, tablePos) => {
const firstRow = table.child(0);
const map = TableMap.get(table);
const totalCols = map.width;

let blockName;
for (let i = 0; i < firstRow.childCount && !blockName; i += 1) {
const cell = firstRow.child(i);
if (cell.textContent) {
blockName = cell.textContent;
}
}

if (blockName) {
const cellType = firstRow.child(0).type;
const para = newState.schema.nodes.paragraph.create(
null,
newState.schema.text(blockName),
);
const newCell = cellType.create({ colspan: totalCols, rowspan: 1 }, para);
const newRow = newState.schema.nodes.table_row.create(null, newCell);

// Map position through previous transaction steps
const mappedTablePos = tr.mapping.map(tablePos);
const firstCellPos = mappedTablePos + 1;
const firstRowEnd = firstCellPos + firstRow.nodeSize - 1;
tr.replaceWith(firstCellPos, firstRowEnd, newRow);
}
});

return tr.docChanged ? tr : null;
},
});
}
10 changes: 5 additions & 5 deletions deps/da-y-wrapper/dist/index.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions deps/da-y-wrapper/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
splitCell,
deleteTable,
isInTable,
TableMap,
} from 'prosemirror-tables';

// yjs
Expand Down Expand Up @@ -81,6 +82,7 @@ export {
mergeCells,
splitCell,
deleteTable,
TableMap,
gapCursor,
MenuItem,
Dropdown,
Expand Down
140 changes: 140 additions & 0 deletions test/unit/blocks/edit/prose/table-modifications.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { expect } from '@esm-bundle/chai';
import {
addColumnBefore,
addColumnAfter,
tableEditing,
} from 'da-y-wrapper';
import { createTestEditor, destroyEditor } from './test-helpers.js';
import insertTable from '../../../../../blocks/edit/prose/table.js';
import tableHeaderFix from '../../../../../blocks/edit/prose/plugins/tableHeaderFix.js';

describe('Table Modifications', () => {
let editor;

afterEach(() => {
if (editor) {
destroyEditor(editor);
editor = null;
}
});

describe('Column Operations - Header Row Colspan', () => {
it('should maintain header colspan when adding column before', async () => {
editor = await createTestEditor({ additionalPlugins: [tableEditing(), tableHeaderFix()] });
const { view } = editor;

// Insert a table (starts with 2 columns)
insertTable(view.state, view.dispatch);

// Change the block name to a custom value by replacing the header cell content
const customBlockName = 'my-custom-block';
const table = view.state.doc.firstChild;
const headerCell = table.child(0).child(0);
const cellStart = 3; // Position of the table cell content
const para = view.state.schema.nodes.paragraph.create(
null,
view.state.schema.text(customBlockName),
);
const updateNameTr = view.state.tr.replaceWith(
cellStart + 1,
cellStart + 1 + headerCell.content.size,
para,
);
view.dispatch(updateNameTr);

// Move cursor to second row, first cell
const table2 = view.state.doc.firstChild;
const headerSize = table2.child(0).nodeSize;
const secondRowPos = view.state.doc.resolve(1 + headerSize + 1);
const tr = view.state.tr.setSelection(
view.state.selection.constructor.create(view.state.doc, secondRowPos.pos),
);
view.dispatch(tr);

// Add a column before
addColumnBefore(view.state, view.dispatch);

// Check that header row still has 1 cell and colspan is now 3
const updatedTable = view.state.doc.firstChild;
const updatedFirstRow = updatedTable.child(0);

expect(updatedFirstRow.childCount).to.equal(
1,
'Header row should still have 1 cell after adding column',
);
expect(updatedFirstRow.child(0).attrs.colspan).to.equal(
3,
'Header should span 3 columns after adding one',
);
expect(updatedFirstRow.child(0).textContent).to.equal(
customBlockName,
'Block name should be preserved',
);
});

it('should maintain header colspan when adding column after', async () => {
editor = await createTestEditor({ additionalPlugins: [tableEditing(), tableHeaderFix()] });
const { view } = editor;

// Insert a table (starts with 2 columns)
insertTable(view.state, view.dispatch);

// Change the block name to a custom value by replacing the header cell content
const customBlockName = 'hero-banner';
const table = view.state.doc.firstChild;
const headerCell = table.child(0).child(0);
const cellStart = 3; // Position of the table cell content
const para = view.state.schema.nodes.paragraph.create(
null,
view.state.schema.text(customBlockName),
);
const updateNameTr = view.state.tr.replaceWith(
cellStart + 1,
cellStart + 1 + headerCell.content.size,
para,
);
view.dispatch(updateNameTr);

// Move cursor to second row, last cell
const table2 = view.state.doc.firstChild;
const headerSize = table2.child(0).nodeSize;
const secondRowPos = view.state.doc.resolve(1 + headerSize + 2);
const tr = view.state.tr.setSelection(
view.state.selection.constructor.create(view.state.doc, secondRowPos.pos),
);
view.dispatch(tr);

// Add a column after
addColumnAfter(view.state, view.dispatch);

// Check that header row still has 1 cell and colspan is now 3
const updatedTable = view.state.doc.firstChild;
const updatedFirstRow = updatedTable.child(0);

expect(updatedFirstRow.childCount).to.equal(
1,
'Header row should still have 1 cell after adding column',
);
expect(updatedFirstRow.child(0).attrs.colspan).to.equal(
3,
'Header should span 3 columns after adding one',
);
expect(updatedFirstRow.child(0).textContent).to.equal(
customBlockName,
'Block name should be preserved',
);
});
});
});
Loading