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

View File

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

View File

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