Refactor actions using builtin editor methods

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-03-25 18:43:55 +04:00
parent cf5ffc319a
commit 7897871659
3 changed files with 85 additions and 138 deletions

View File

@@ -235,70 +235,33 @@ export function TableCellMenu({ editor }: TableCellMenuProps) {
setMenuOpen(false);
};
const hasSpannedCell = (): boolean => {
const { selection } = editor.state;
if (selection instanceof CellSelection) {
let found = false;
selection.forEachCell((node) => {
if (node.attrs.colspan > 1 || node.attrs.rowspan > 1) {
found = true;
}
});
return found;
}
const $pos = editor.state.doc.resolve(selection.from);
const cell = cellAround($pos);
if (!cell) return false;
const cellNode = editor.state.doc.nodeAt(cell.pos);
if (!cellNode) return false;
return cellNode.attrs.colspan > 1 || cellNode.attrs.rowspan > 1;
};
const handleSplitCell = () => {
editor.chain().focus().splitCell().run();
setMenuOpen(false);
};
const handleClearContents = () => {
const { state, dispatch } = editor.view;
const { selection, schema } = state;
const { tr } = state;
let cursorTarget: number | null = null;
const { state } = editor.view;
if (selection instanceof CellSelection) {
selection.forEachCell((node, pos) => {
const start = pos + 1;
const end = pos + node.nodeSize - 1;
if (cursorTarget === null) {
cursorTarget = tr.mapping.map(start) + 1;
}
tr.replaceWith(
tr.mapping.map(start),
tr.mapping.map(end),
schema.nodes.paragraph.create(),
);
});
if (state.selection instanceof CellSelection) {
editor.commands.deleteSelection();
} else {
const $pos = state.doc.resolve(selection.from);
const { dispatch } = editor.view;
const { tr, schema } = state;
const $pos = state.doc.resolve(state.selection.from);
const cell = cellAround($pos);
if (cell) {
const cellNode = state.doc.nodeAt(cell.pos);
if (cellNode) {
const start = cell.pos + 1;
const end = cell.pos + cellNode.nodeSize - 1;
cursorTarget = start + 1;
tr.replaceWith(start, end, schema.nodes.paragraph.create());
tr.setSelection(TextSelection.create(tr.doc, start + 1));
dispatch(tr);
}
}
}
if (cursorTarget !== null) {
tr.setSelection(TextSelection.create(tr.doc, cursorTarget));
}
dispatch(tr);
setMenuOpen(false);
};
@@ -340,11 +303,13 @@ export function TableCellMenu({ editor }: TableCellMenuProps) {
onMouseDown={e => e.preventDefault()}
className={menu()}
>
<MenuButton onClick={handleMergeCells}>
<IntersectIcon size={16} weight="bold" />
Merge cells
</MenuButton>
{hasSpannedCell() && (
{editor.can().mergeCells() && (
<MenuButton onClick={handleMergeCells}>
<IntersectIcon size={16} weight="bold" />
Merge cells
</MenuButton>
)}
{editor.can().splitCell() && (
<MenuButton onClick={handleSplitCell}>
<SplitHorizontalIcon size={16} weight="bold" />
Split cells

View File

@@ -450,19 +450,17 @@ export function TableColumnMenu({ editor }: TableColumnMenuProps) {
if (!table) return;
const map = TableMap.get(table);
const { tr, schema } = editor.state;
const targetType = isHeaderColumn()
? schema.nodes.tableCell
: schema.nodes.tableHeader;
const cellPos = map.positionAt(0, 0, table) + tableStart;
for (let row = 0; row < map.height; row++) {
const cellPos = map.map[row * map.width] + tableStart;
const cellNode = editor.state.doc.nodeAt(cellPos);
if (!cellNode) continue;
tr.setNodeMarkup(cellPos, targetType, cellNode.attrs);
}
editor.view.dispatch(tr);
editor
.chain()
.focus()
.command(({ tr }) => {
tr.setSelection(TextSelection.create(tr.doc, cellPos + 1));
return true;
})
.toggleHeaderColumn()
.run();
} catch {
// table may have changed
}
@@ -503,26 +501,26 @@ export function TableColumnMenu({ editor }: TableColumnMenuProps) {
const { colIndex, tableStart } = currentCol;
try {
const tableNodePos = tableStart - 1;
const table = editor.state.doc.nodeAt(tableNodePos);
const table = editor.state.doc.nodeAt(tableStart - 1);
if (!table) return;
const rows: PMNode[] = [];
table.forEach((row) => {
const cells: PMNode[] = [];
row.forEach((cell, _offset, i) => {
cells.push(cell);
if (i === colIndex) {
cells.push(cell.copy(cell.content));
}
});
rows.push(row.type.create(row.attrs, cells));
});
const map = TableMap.get(table);
const newTable = table.type.create(table.attrs, rows);
const { tr } = editor.state;
tr.replaceWith(tableNodePos, tableNodePos + table.nodeSize, newTable);
editor.view.dispatch(tr);
editor
.chain()
.focus()
.command(({ tr }) => {
for (let row = map.height - 1; row >= 0; row--) {
const cellOffset = map.map[row * map.width + colIndex];
const cell = table.nodeAt(cellOffset);
if (!cell) continue;
const insertPos = cellOffset + tableStart + cell.nodeSize;
tr.insert(insertPos, cell);
}
return true;
})
.run();
} catch {
// table may have changed
}
@@ -593,25 +591,17 @@ export function TableColumnMenu({ editor }: TableColumnMenuProps) {
if (!table) return;
const map = TableMap.get(table);
const { tr } = editor.state;
const { schema } = editor.state;
const firstCellPos = map.map[colIndex] + tableStart;
const lastCellPos
= map.map[(map.height - 1) * map.width + colIndex] + tableStart;
for (let row = 0; row < map.height; row++) {
const cellPos = map.map[row * map.width + colIndex] + tableStart;
const cellNode = editor.state.doc.nodeAt(cellPos);
if (!cellNode) continue;
const $anchor = editor.state.doc.resolve(firstCellPos);
const $head = editor.state.doc.resolve(lastCellPos);
const start = cellPos + 1;
const end = cellPos + cellNode.nodeSize - 1;
tr.replaceWith(
tr.mapping.map(start),
tr.mapping.map(end),
schema.nodes.paragraph.create(),
);
}
editor.view.dispatch(tr);
editor.view.dispatch(
editor.state.tr.setSelection(new CellSelection($anchor, $head)),
);
editor.commands.deleteSelection();
} catch {
// table may have changed
}

View File

@@ -446,19 +446,17 @@ export function TableRowMenu({ editor }: TableRowMenuProps) {
if (!table) return;
const map = TableMap.get(table);
const { tr, schema } = editor.state;
const targetType = isHeaderRow()
? schema.nodes.tableCell
: schema.nodes.tableHeader;
const cellPos = map.positionAt(0, 0, table) + tableStart;
for (let col = 0; col < map.width; col++) {
const cellPos = map.map[col] + tableStart;
const cellNode = editor.state.doc.nodeAt(cellPos);
if (!cellNode) continue;
tr.setNodeMarkup(cellPos, targetType, cellNode.attrs);
}
editor.view.dispatch(tr);
editor
.chain()
.focus()
.command(({ tr }) => {
tr.setSelection(TextSelection.create(tr.doc, cellPos + 1));
return true;
})
.toggleHeaderRow()
.run();
} catch {
// table may have changed
}
@@ -499,22 +497,24 @@ export function TableRowMenu({ editor }: TableRowMenuProps) {
const { rowIndex, tableStart } = currentRow;
try {
const tableNodePos = tableStart - 1;
const table = editor.state.doc.nodeAt(tableNodePos);
const table = editor.state.doc.nodeAt(tableStart - 1);
if (!table) return;
const rows: PMNode[] = [];
table.forEach((row, _offset, i) => {
rows.push(row);
if (i === rowIndex) {
rows.push(row.copy(row.content));
}
});
const rowNode = table.child(rowIndex);
const newTable = table.type.create(table.attrs, rows);
const { tr } = editor.state;
tr.replaceWith(tableNodePos, tableNodePos + table.nodeSize, newTable);
editor.view.dispatch(tr);
let insertPos = tableStart;
for (let i = 0; i <= rowIndex; i++) {
insertPos += table.child(i).nodeSize;
}
editor
.chain()
.focus()
.command(({ tr }) => {
tr.insert(insertPos, rowNode);
return true;
})
.run();
} catch {
// table may have changed
}
@@ -585,25 +585,17 @@ export function TableRowMenu({ editor }: TableRowMenuProps) {
if (!table) return;
const map = TableMap.get(table);
const { tr } = editor.state;
const { schema } = editor.state;
const firstCellPos = map.map[rowIndex * map.width] + tableStart;
const lastCellPos
= map.map[rowIndex * map.width + (map.width - 1)] + tableStart;
for (let col = 0; col < map.width; col++) {
const cellPos = map.map[rowIndex * map.width + col] + tableStart;
const cellNode = editor.state.doc.nodeAt(cellPos);
if (!cellNode) continue;
const $anchor = editor.state.doc.resolve(firstCellPos);
const $head = editor.state.doc.resolve(lastCellPos);
const start = cellPos + 1;
const end = cellPos + cellNode.nodeSize - 1;
tr.replaceWith(
tr.mapping.map(start),
tr.mapping.map(end),
schema.nodes.paragraph.create(),
);
}
editor.view.dispatch(tr);
editor.view.dispatch(
editor.state.tr.setSelection(new CellSelection($anchor, $head)),
);
editor.commands.deleteSelection();
} catch {
// table may have changed
}