Files
outline/shared/editor/nodes/Table.ts
T
Tom Moor bc63aba1d1 fix: place cursor at start of inserted table row/column (#12610)
* fix: place cursor at start of inserted table row/column

When using Insert before for a table row or column, the selection was
collapsed onto the mapped previous selection — landing at the bottom of the
shifted neighbouring column rather than in the newly inserted cell. Move the
cursor to the start of the first cell of the inserted row/column instead.

* feat: Inline editor menu (#12611)

* wip

* Mobile support

* Address review feedback on inline menu

- Mark selection-restore transaction as not added to history
- Only open desktop inline menu when an anchor is available

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix: place cursor at start of inserted table row/column

When using Insert before for a table row or column, the selection was
collapsed onto the mapped previous selection — landing at the bottom of the
shifted neighbouring column rather than in the newly inserted cell. Move the
cursor to the start of the first cell of the inserted row/column instead.

* Add handling for After variants
Add lint rule

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-07 11:58:48 -04:00

174 lines
4.3 KiB
TypeScript

import { chainCommands } from "prosemirror-commands";
import { InputRule } from "prosemirror-inputrules";
import type { NodeSpec, Node as ProsemirrorNode } from "prosemirror-model";
import { TextSelection } from "prosemirror-state";
import {
columnResizing,
deleteColumn,
deleteRow,
deleteTable,
goToNextCell,
moveTableColumn,
moveTableRow,
tableEditing,
toggleHeader,
} from "prosemirror-tables";
import {
addRowBefore,
addRowAfter,
addColumnBefore,
addColumnAfter,
addRowAndMoveSelection,
setColumnAttr,
createTable,
exportTable,
distributeColumns,
sortTable,
setTableAttr,
deleteColSelection,
deleteRowSelection,
deleteCellSelection,
moveOutOfTable,
createTableInner,
deleteTableIfSelected,
splitCellAndCollapse,
mergeCellsAndCollapse,
toggleColumnBackground,
toggleRowBackground,
toggleCellSelectionBackground,
toggleCellSelectionBackgroundAndCollapseSelection,
toggleRowBackgroundAndCollapseSelection,
toggleColumnBackgroundAndCollapseSelection,
} from "../commands/table";
import type { MarkdownSerializerState } from "../lib/markdown/serializer";
import { FixTablesPlugin } from "../plugins/FixTablesPlugin";
import { TableLayoutPlugin } from "../plugins/TableLayoutPlugin";
import tablesRule from "../rules/tables";
import { EditorStyleHelper } from "../styles/EditorStyleHelper";
import type { TableLayout } from "../types";
import Node from "./Node";
import { TableView } from "./TableView";
export type TableAttrs = {
layout: TableLayout | null;
};
export default class Table extends Node {
get name() {
return "table";
}
get schema(): NodeSpec {
return {
content: "tr+",
tableRole: "table",
isolating: true,
group: "block",
parseDOM: [{ tag: "table" }],
attrs: {
layout: {
default: null,
},
},
toDOM() {
// Note: This is overridden by TableView
return [
"div",
{ class: EditorStyleHelper.table },
["table", {}, ["tbody", 0]],
];
},
};
}
get rulePlugins() {
return [tablesRule];
}
commands() {
return {
createTable,
setColumnAttr,
setTableAttr,
sortTable,
addColumnBefore,
addColumnAfter,
deleteColumn: () => deleteColumn,
addRowBefore,
addRowAfter,
moveTableRow,
moveTableColumn,
deleteRow: () => deleteRow,
deleteTable: () => deleteTable,
exportTable,
distributeColumns,
toggleHeaderColumn: () => toggleHeader("column"),
toggleHeaderRow: () => toggleHeader("row"),
mergeCells: () => mergeCellsAndCollapse(),
splitCell: () => splitCellAndCollapse(),
toggleRowBackground,
toggleRowBackgroundAndCollapseSelection,
toggleColumnBackground,
toggleColumnBackgroundAndCollapseSelection,
toggleCellSelectionBackground,
toggleCellSelectionBackgroundAndCollapseSelection,
};
}
keys() {
return {
Tab: chainCommands(goToNextCell(1), addRowAndMoveSelection()),
"Shift-Tab": goToNextCell(-1),
"Mod-Enter": addRowAndMoveSelection(),
"Mod-Backspace": chainCommands(
deleteCellSelection,
deleteColSelection(),
deleteRowSelection(),
deleteTableIfSelected()
),
Backspace: chainCommands(
deleteCellSelection,
deleteColSelection(),
deleteRowSelection(),
deleteTableIfSelected()
),
ArrowDown: moveOutOfTable(1),
ArrowUp: moveOutOfTable(-1),
};
}
inputRules() {
return [
new InputRule(/^(\|--)$/, (state, _, start, end) => {
const nodes = createTableInner(state, 2, 2);
const tr = state.tr.replaceWith(start - 1, end, nodes).scrollIntoView();
const resolvedPos = tr.doc.resolve(start + 1);
tr.setSelection(TextSelection.near(resolvedPos));
return tr;
}),
];
}
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
state.renderTable(node);
state.closeBlock(node);
}
parseMarkdown() {
return { block: "table" };
}
get plugins() {
return [
// Note: Important to register columnResizing before tableEditing
columnResizing({
View: TableView,
defaultCellMinWidth: 25,
}),
tableEditing(),
new FixTablesPlugin(),
new TableLayoutPlugin(),
];
}
}