From b31da95c506c40ba812c533d87144c3c63977c15 Mon Sep 17 00:00:00 2001 From: d3m1d0v Date: Thu, 16 Oct 2025 18:52:48 +0300 Subject: [PATCH 1/2] fix(Table): dont split cell when pressing Enter --- src/extensions/markdown/Table/commands.ts | 5 +++++ src/extensions/markdown/Table/index.ts | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 src/extensions/markdown/Table/commands.ts diff --git a/src/extensions/markdown/Table/commands.ts b/src/extensions/markdown/Table/commands.ts new file mode 100644 index 000000000..d7ab7bd8a --- /dev/null +++ b/src/extensions/markdown/Table/commands.ts @@ -0,0 +1,5 @@ +import type {Command} from '#pm/state'; + +import {isIntoTable} from './helpers'; + +export const ignoreIfInTableCell: Command = isIntoTable; diff --git a/src/extensions/markdown/Table/index.ts b/src/extensions/markdown/Table/index.ts index 840010658..c7bb8cd3c 100644 --- a/src/extensions/markdown/Table/index.ts +++ b/src/extensions/markdown/Table/index.ts @@ -1,9 +1,10 @@ -import type {Action, ExtensionAuto} from '../../../core'; -import {goToNextCell} from '../../../table-utils'; +import type {Action, ExtensionAuto} from '#core'; +import {goToNextCell} from 'src/table-utils'; import {TableSpecs} from './TableSpecs'; import {createTableAction, deleteTableAction} from './actions/tableActions'; import * as TableActions from './actions/tableActions'; +import {ignoreIfInTableCell} from './commands'; import * as TableHelpers from './helpers'; import {tableCellContextPlugin} from './plugins/TableCellContextPlugin'; @@ -16,6 +17,7 @@ export const Table: ExtensionAuto = (builder) => { builder.addKeymap(() => ({ Tab: goToNextCell('next'), 'Shift-Tab': goToNextCell('prev'), + Enter: ignoreIfInTableCell, })); builder.addAction('createTable', createTableAction); From 63cfd68a98323dc949c97f5d8ca5b9685867d927 Mon Sep 17 00:00:00 2001 From: d3m1d0v Date: Fri, 17 Oct 2025 14:59:22 +0300 Subject: [PATCH 2/2] fix(Table): go to next row when pressing Enter of Shift+Enter --- src/extensions/markdown/Table/commands.ts | 87 ++++++++++++++++++++++- src/extensions/markdown/Table/helpers.ts | 26 +++++-- src/extensions/markdown/Table/index.ts | 5 +- 3 files changed, 109 insertions(+), 9 deletions(-) diff --git a/src/extensions/markdown/Table/commands.ts b/src/extensions/markdown/Table/commands.ts index d7ab7bd8a..91437c021 100644 --- a/src/extensions/markdown/Table/commands.ts +++ b/src/extensions/markdown/Table/commands.ts @@ -1,5 +1,86 @@ -import type {Command} from '#pm/state'; +import type {Node, ResolvedPos} from '#pm/model'; +import {type Command, type EditorState, TextSelection, type Transaction} from '#pm/state'; +import type {EditorView} from '#pm/view'; +import {pType} from 'src/extensions/base/specs'; +import {findParentTableFromPos} from 'src/table-utils'; +import {getChildByIndex} from 'src/utils/node-children'; -import {isIntoTable} from './helpers'; +import { + findParentBodyOnPos, + findParentCellOnPos, + findParentHeadOnPos, + findParentRowOnPos, +} from './helpers'; -export const ignoreIfInTableCell: Command = isIntoTable; +const moveToNextRow = ( + $pos: ResolvedPos, + state: EditorState, + dispatch: EditorView['dispatch'] | undefined, +) => { + const tableRes = findParentTableFromPos($pos); + if (!tableRes) return false; + + const tableHeadResult = findParentHeadOnPos($pos); + const tableBodyResult = findParentBodyOnPos($pos); + if (!tableHeadResult && !tableBodyResult) return false; + + const tableRowResult = findParentRowOnPos($pos); + const tableCellResult = findParentCellOnPos($pos); + if (!tableRowResult || !tableCellResult) return false; + + const cellIndex = $pos.index(tableCellResult.depth - 1); + const rowIndex = $pos.index(tableRowResult.depth - 1); + + if (!dispatch) return true; + + const goToNextRow = (tr: Transaction, nextRow: Node, nextRowPos: number) => { + const cell = getChildByIndex(nextRow, cellIndex); + if (cell) { + const cellPos = nextRowPos + 1 + cell.offset; + tr.setSelection(TextSelection.create(tr.doc, cellPos + cell.node.nodeSize - 1)); + } else { + // fallback if cell not found + const $rowEnd = tr.doc.resolve(nextRowPos + nextRow.nodeSize - 1); + tr.setSelection(TextSelection.near($rowEnd, -1)); + } + }; + + const exitTable = (tr: Transaction) => { + const afterTablePos = tableRes.pos + tableRes.node.nodeSize; + tr.insert(afterTablePos, pType(state.schema).create()); + tr.setSelection(TextSelection.create(tr.doc, afterTablePos + 1)); + }; + + const tr = state.tr; + + if (tableHeadResult) { + // in table head + const tableBody = getChildByIndex(tableRes.node, tableRes.node.childCount - 1); + if (tableBody && tableBody.node !== tableHeadResult.node && tableBody.node.firstChild) { + goToNextRow(tr, tableBody.node.firstChild, tableRes.start + tableBody.offset + 1); + } else { + // no table body or it is empty + exitTable(tr); + } + } else { + // in table body + const nextRow = getChildByIndex(tableBodyResult!.node, rowIndex + 1); + if (nextRow) { + goToNextRow(tr, nextRow.node, tableBodyResult!.start + nextRow.offset); + } else { + // pos in last row + exitTable(tr); + } + } + + dispatch(tr.scrollIntoView()); + + return true; +}; + +export const moveToNextRowCommand: Command = (state, dispatch) => { + return ( + moveToNextRow(state.selection.$head, state, dispatch) || + moveToNextRow(state.selection.$anchor, state, dispatch) + ); +}; diff --git a/src/extensions/markdown/Table/helpers.ts b/src/extensions/markdown/Table/helpers.ts index 0e75ca3b9..a4028946b 100644 --- a/src/extensions/markdown/Table/helpers.ts +++ b/src/extensions/markdown/Table/helpers.ts @@ -1,12 +1,12 @@ -import type {Node, Schema} from 'prosemirror-model'; -import type {EditorState} from 'prosemirror-state'; +import type {Node, ResolvedPos, Schema} from '#pm/model'; +import type {EditorState} from '#pm/state'; import { findChildren, findChildrenByType, findParentNodeOfType, + findParentNodeOfTypeClosestToPos, hasParentNodeOfType, - // @ts-ignore // TODO: fix cjs build -} from 'prosemirror-utils'; +} from '#pm/utils'; import {TableNode} from './const'; @@ -16,18 +16,36 @@ export const isIntoTable = (state: EditorState) => export const findParentTable = (state: EditorState) => findParentNodeOfType(state.schema.nodes[TableNode.Table])(state.selection); +export const findParentTableOnPosOnPos = ($pos: ResolvedPos) => + findParentNodeOfTypeClosestToPos($pos, $pos.doc.type.schema.nodes[TableNode.Table]); + +export const findParentHeadOnPos = ($pos: ResolvedPos) => + findParentNodeOfTypeClosestToPos($pos, $pos.doc.type.schema.nodes[TableNode.Head]); + export const findParentBody = (state: EditorState) => findParentNodeOfType(state.schema.nodes[TableNode.Body])(state.selection); +export const findParentBodyOnPos = ($pos: ResolvedPos) => + findParentNodeOfTypeClosestToPos($pos, $pos.doc.type.schema.nodes[TableNode.Body]); + export const findParentRow = (state: EditorState) => findParentNodeOfType(state.schema.nodes[TableNode.Row])(state.selection); +export const findParentRowOnPos = ($pos: ResolvedPos) => + findParentNodeOfTypeClosestToPos($pos, $pos.doc.type.schema.nodes[TableNode.Row]); + export const findParentCell = (state: EditorState) => findParentNodeOfType([ state.schema.nodes[TableNode.HeaderCell], state.schema.nodes[TableNode.DataCell], ])(state.selection); +export const findParentCellOnPos = ($pos: ResolvedPos) => + findParentNodeOfTypeClosestToPos($pos, [ + $pos.doc.type.schema.nodes[TableNode.HeaderCell], + $pos.doc.type.schema.nodes[TableNode.DataCell], + ]); + export const findTableRows = (table: Node, schema: Schema) => findChildrenByType(table, schema.nodes[TableNode.Row]); diff --git a/src/extensions/markdown/Table/index.ts b/src/extensions/markdown/Table/index.ts index c7bb8cd3c..d4f6dcc07 100644 --- a/src/extensions/markdown/Table/index.ts +++ b/src/extensions/markdown/Table/index.ts @@ -4,7 +4,7 @@ import {goToNextCell} from 'src/table-utils'; import {TableSpecs} from './TableSpecs'; import {createTableAction, deleteTableAction} from './actions/tableActions'; import * as TableActions from './actions/tableActions'; -import {ignoreIfInTableCell} from './commands'; +import {moveToNextRowCommand} from './commands'; import * as TableHelpers from './helpers'; import {tableCellContextPlugin} from './plugins/TableCellContextPlugin'; @@ -17,7 +17,8 @@ export const Table: ExtensionAuto = (builder) => { builder.addKeymap(() => ({ Tab: goToNextCell('next'), 'Shift-Tab': goToNextCell('prev'), - Enter: ignoreIfInTableCell, + Enter: moveToNextRowCommand, + 'Shift-Enter': moveToNextRowCommand, })); builder.addAction('createTable', createTableAction);