Skip to content

Commit 5cbdf0a

Browse files
authored
fix(Table): go to next row when pressing Enter or Shift+Enter (#871)
1 parent cd3cddd commit 5cbdf0a

File tree

3 files changed

+113
-6
lines changed

3 files changed

+113
-6
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import type {Node, ResolvedPos} from '#pm/model';
2+
import {type Command, type EditorState, TextSelection, type Transaction} from '#pm/state';
3+
import type {EditorView} from '#pm/view';
4+
import {pType} from 'src/extensions/base/specs';
5+
import {findParentTableFromPos} from 'src/table-utils';
6+
import {getChildByIndex} from 'src/utils/node-children';
7+
8+
import {
9+
findParentBodyOnPos,
10+
findParentCellOnPos,
11+
findParentHeadOnPos,
12+
findParentRowOnPos,
13+
} from './helpers';
14+
15+
const moveToNextRow = (
16+
$pos: ResolvedPos,
17+
state: EditorState,
18+
dispatch: EditorView['dispatch'] | undefined,
19+
) => {
20+
const tableRes = findParentTableFromPos($pos);
21+
if (!tableRes) return false;
22+
23+
const tableHeadResult = findParentHeadOnPos($pos);
24+
const tableBodyResult = findParentBodyOnPos($pos);
25+
if (!tableHeadResult && !tableBodyResult) return false;
26+
27+
const tableRowResult = findParentRowOnPos($pos);
28+
const tableCellResult = findParentCellOnPos($pos);
29+
if (!tableRowResult || !tableCellResult) return false;
30+
31+
const cellIndex = $pos.index(tableCellResult.depth - 1);
32+
const rowIndex = $pos.index(tableRowResult.depth - 1);
33+
34+
if (!dispatch) return true;
35+
36+
const goToNextRow = (tr: Transaction, nextRow: Node, nextRowPos: number) => {
37+
const cell = getChildByIndex(nextRow, cellIndex);
38+
if (cell) {
39+
const cellPos = nextRowPos + 1 + cell.offset;
40+
tr.setSelection(TextSelection.create(tr.doc, cellPos + cell.node.nodeSize - 1));
41+
} else {
42+
// fallback if cell not found
43+
const $rowEnd = tr.doc.resolve(nextRowPos + nextRow.nodeSize - 1);
44+
tr.setSelection(TextSelection.near($rowEnd, -1));
45+
}
46+
};
47+
48+
const exitTable = (tr: Transaction) => {
49+
const afterTablePos = tableRes.pos + tableRes.node.nodeSize;
50+
tr.insert(afterTablePos, pType(state.schema).create());
51+
tr.setSelection(TextSelection.create(tr.doc, afterTablePos + 1));
52+
};
53+
54+
const tr = state.tr;
55+
56+
if (tableHeadResult) {
57+
// in table head
58+
const tableBody = getChildByIndex(tableRes.node, tableRes.node.childCount - 1);
59+
if (tableBody && tableBody.node !== tableHeadResult.node && tableBody.node.firstChild) {
60+
goToNextRow(tr, tableBody.node.firstChild, tableRes.start + tableBody.offset + 1);
61+
} else {
62+
// no table body or it is empty
63+
exitTable(tr);
64+
}
65+
} else {
66+
// in table body
67+
const nextRow = getChildByIndex(tableBodyResult!.node, rowIndex + 1);
68+
if (nextRow) {
69+
goToNextRow(tr, nextRow.node, tableBodyResult!.start + nextRow.offset);
70+
} else {
71+
// pos in last row
72+
exitTable(tr);
73+
}
74+
}
75+
76+
dispatch(tr.scrollIntoView());
77+
78+
return true;
79+
};
80+
81+
export const moveToNextRowCommand: Command = (state, dispatch) => {
82+
return (
83+
moveToNextRow(state.selection.$head, state, dispatch) ||
84+
moveToNextRow(state.selection.$anchor, state, dispatch)
85+
);
86+
};

src/extensions/markdown/Table/helpers.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type {Node, Schema} from 'prosemirror-model';
2-
import type {EditorState} from 'prosemirror-state';
1+
import type {Node, ResolvedPos, Schema} from '#pm/model';
2+
import type {EditorState} from '#pm/state';
33
import {
44
findChildren,
55
findChildrenByType,
66
findParentNodeOfType,
7+
findParentNodeOfTypeClosestToPos,
78
hasParentNodeOfType,
8-
// @ts-ignore // TODO: fix cjs build
9-
} from 'prosemirror-utils';
9+
} from '#pm/utils';
1010

1111
import {TableNode} from './const';
1212

@@ -16,18 +16,36 @@ export const isIntoTable = (state: EditorState) =>
1616
export const findParentTable = (state: EditorState) =>
1717
findParentNodeOfType(state.schema.nodes[TableNode.Table])(state.selection);
1818

19+
export const findParentTableOnPosOnPos = ($pos: ResolvedPos) =>
20+
findParentNodeOfTypeClosestToPos($pos, $pos.doc.type.schema.nodes[TableNode.Table]);
21+
22+
export const findParentHeadOnPos = ($pos: ResolvedPos) =>
23+
findParentNodeOfTypeClosestToPos($pos, $pos.doc.type.schema.nodes[TableNode.Head]);
24+
1925
export const findParentBody = (state: EditorState) =>
2026
findParentNodeOfType(state.schema.nodes[TableNode.Body])(state.selection);
2127

28+
export const findParentBodyOnPos = ($pos: ResolvedPos) =>
29+
findParentNodeOfTypeClosestToPos($pos, $pos.doc.type.schema.nodes[TableNode.Body]);
30+
2231
export const findParentRow = (state: EditorState) =>
2332
findParentNodeOfType(state.schema.nodes[TableNode.Row])(state.selection);
2433

34+
export const findParentRowOnPos = ($pos: ResolvedPos) =>
35+
findParentNodeOfTypeClosestToPos($pos, $pos.doc.type.schema.nodes[TableNode.Row]);
36+
2537
export const findParentCell = (state: EditorState) =>
2638
findParentNodeOfType([
2739
state.schema.nodes[TableNode.HeaderCell],
2840
state.schema.nodes[TableNode.DataCell],
2941
])(state.selection);
3042

43+
export const findParentCellOnPos = ($pos: ResolvedPos) =>
44+
findParentNodeOfTypeClosestToPos($pos, [
45+
$pos.doc.type.schema.nodes[TableNode.HeaderCell],
46+
$pos.doc.type.schema.nodes[TableNode.DataCell],
47+
]);
48+
3149
export const findTableRows = (table: Node, schema: Schema) =>
3250
findChildrenByType(table, schema.nodes[TableNode.Row]);
3351

src/extensions/markdown/Table/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import type {Action, ExtensionAuto} from '../../../core';
2-
import {goToNextCell} from '../../../table-utils';
1+
import type {Action, ExtensionAuto} from '#core';
2+
import {goToNextCell} from 'src/table-utils';
33

44
import {TableSpecs} from './TableSpecs';
55
import {createTableAction, deleteTableAction} from './actions/tableActions';
66
import * as TableActions from './actions/tableActions';
7+
import {moveToNextRowCommand} from './commands';
78
import * as TableHelpers from './helpers';
89
import {tableCellContextPlugin} from './plugins/TableCellContextPlugin';
910

@@ -16,6 +17,8 @@ export const Table: ExtensionAuto = (builder) => {
1617
builder.addKeymap(() => ({
1718
Tab: goToNextCell('next'),
1819
'Shift-Tab': goToNextCell('prev'),
20+
Enter: moveToNextRowCommand,
21+
'Shift-Enter': moveToNextRowCommand,
1922
}));
2023

2124
builder.addAction('createTable', createTableAction);

0 commit comments

Comments
 (0)