From 4fe3ad752b1ec9c2ef5440892f488df5ee0f665e Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 29 Sep 2026 17:18:20 +0200 Subject: [PATCH 1/4] chore(table): migrate Table node files to Typescript Assisted-by: ClaudeCode:claude-opus-5.5 Signed-off-by: Jonas --- cypress/e2e/nodes/Table.spec.js | 2 +- src/extensions/PlainTable.js | 2 +- src/extensions/RichText.ts | 4 +- .../{EditableTable.js => EditableTable.ts} | 21 +++-- src/nodes/{Table.js => Table.ts} | 2 +- src/nodes/Table/{Table.js => Table.ts} | 89 ++++++++++++------- .../{TableCaption.js => TableCaption.ts} | 0 .../Table/{TableCell.js => TableCell.ts} | 8 +- .../{TableHeadRow.js => TableHeadRow.ts} | 2 +- .../Table/{TableHeader.js => TableHeader.ts} | 0 src/nodes/Table/TableHeaderView.vue | 19 +--- src/nodes/Table/{TableRow.js => TableRow.ts} | 0 src/nodes/Table/TableRowView.vue | 19 +--- src/nodes/Table/TableView.vue | 19 +--- src/tests/extensions/Markdown.spec.js | 2 +- src/tests/nodes/Table.spec.js | 2 +- src/tests/nodes/TaskItem.spec.js | 2 +- 17 files changed, 89 insertions(+), 104 deletions(-) rename src/nodes/{EditableTable.js => EditableTable.ts} (56%) rename src/nodes/{Table.js => Table.ts} (80%) rename src/nodes/Table/{Table.js => Table.ts} (80%) rename src/nodes/Table/{TableCaption.js => TableCaption.ts} (100%) rename src/nodes/Table/{TableCell.js => TableCell.ts} (94%) rename src/nodes/Table/{TableHeadRow.js => TableHeadRow.ts} (93%) rename src/nodes/Table/{TableHeader.js => TableHeader.ts} (100%) rename src/nodes/Table/{TableRow.js => TableRow.ts} (100%) diff --git a/cypress/e2e/nodes/Table.spec.js b/cypress/e2e/nodes/Table.spec.js index cdecf605031..6a7deedb59e 100644 --- a/cypress/e2e/nodes/Table.spec.js +++ b/cypress/e2e/nodes/Table.spec.js @@ -8,7 +8,7 @@ import testData from '../../fixtures/Table.md?raw' import { initUserAndFiles, randUser } from '../../utils/index.js' import Markdown from './../../../src/extensions/Markdown.js' import markdownit from './../../../src/markdownit/index.ts' -import EditableTable from './../../../src/nodes/EditableTable.js' +import EditableTable from './../../../src/nodes/EditableTable.ts' import { createCustomEditor } from './../../support/components.js' import { expectMarkdown, runCommands } from './helpers.js' diff --git a/src/extensions/PlainTable.js b/src/extensions/PlainTable.js index 4d74d652f59..d2852a56c88 100644 --- a/src/extensions/PlainTable.js +++ b/src/extensions/PlainTable.js @@ -5,7 +5,7 @@ import { Extension } from '@tiptap/core' import Text from '@tiptap/extension-text' -import EditableTable from './../nodes/EditableTable.js' +import EditableTable from './../nodes/EditableTable.ts' import Paragraph from './../nodes/Paragraph.js' import PlainTableDocument from './../nodes/PlainTableDocument.js' import Keymap from './Keymap.js' diff --git a/src/extensions/RichText.ts b/src/extensions/RichText.ts index e1162e122ad..0c027081557 100644 --- a/src/extensions/RichText.ts +++ b/src/extensions/RichText.ts @@ -30,7 +30,7 @@ import Callouts from '../nodes/Callout.js' import CodeBlock from '../nodes/CodeBlock.js' import Comments from '../nodes/Comments.ts' import Details from '../nodes/Details.js' -import EditableTable from '../nodes/EditableTable.js' +import EditableTable from '../nodes/EditableTable.ts' import Footnotes from '../nodes/Footnotes.ts' import FrontMatter from '../nodes/FrontMatter.js' import HardBreak from '../nodes/HardBreak.js' @@ -42,7 +42,7 @@ import { MathBlock, MathInline } from '../nodes/Mathematics.js' import OrderedList from '../nodes/OrderedList.ts' import Paragraph from '../nodes/Paragraph.js' import Preview from '../nodes/Preview.js' -import Table from '../nodes/Table.js' +import Table from '../nodes/Table.ts' import TaskItem from '../nodes/TaskItem.ts' import TaskList from '../nodes/TaskList.ts' import TrailingNode from '../nodes/TrailingNode.js' diff --git a/src/nodes/EditableTable.js b/src/nodes/EditableTable.ts similarity index 56% rename from src/nodes/EditableTable.js rename to src/nodes/EditableTable.ts index 2ef4b62bb1a..3f7f9f1e5c9 100644 --- a/src/nodes/EditableTable.js +++ b/src/nodes/EditableTable.ts @@ -3,23 +3,26 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +import type { Node, NodeViewProps } from '@tiptap/vue-3' +import type { Component } from 'vue' + import { VueNodeViewRenderer } from '@tiptap/vue-3' import TableHeaderView from './Table/TableHeaderView.vue' import TableRowView from './Table/TableRowView.vue' import TableView from './Table/TableView.vue' -import Table from './Table/Table.js' -import TableCaption from './Table/TableCaption.js' -import TableCell from './Table/TableCell.js' -import TableHeader from './Table/TableHeader.js' -import TableHeadRow from './Table/TableHeadRow.js' -import TableRow from './Table/TableRow.js' +import Table from './Table/Table.ts' +import TableCaption from './Table/TableCaption.ts' +import TableCell from './Table/TableCell.ts' +import TableHeader from './Table/TableHeader.ts' +import TableHeadRow from './Table/TableHeadRow.ts' +import TableRow from './Table/TableRow.ts' /** * - * @param {object} node - the node to add the view to. - * @param {object} view - the node view to add to the node. + * @param node - the node to add the view to. + * @param view - the node view to add to the node. */ -function extendNodeWithView(node, view) { +function extendNodeWithView(node: Node, view: Component) { return node.extend({ addNodeView() { return VueNodeViewRenderer(view) diff --git a/src/nodes/Table.js b/src/nodes/Table.ts similarity index 80% rename from src/nodes/Table.js rename to src/nodes/Table.ts index 827768f17d7..9ebd80aded4 100644 --- a/src/nodes/Table.js +++ b/src/nodes/Table.ts @@ -3,6 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import Table from './Table/Table.js' +import Table from './Table/Table.ts' export default Table diff --git a/src/nodes/Table/Table.js b/src/nodes/Table/Table.ts similarity index 80% rename from src/nodes/Table/Table.js rename to src/nodes/Table/Table.ts index 3fcf9418e3b..33d960cfb50 100644 --- a/src/nodes/Table/Table.js +++ b/src/nodes/Table/Table.ts @@ -3,6 +3,9 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +import type { Node as PMNode, ResolvedPos, Schema } from '@tiptap/pm/model' +import type { Selection } from '@tiptap/pm/state' + import { mergeAttributes } from '@tiptap/core' import { Table } from '@tiptap/extension-table' import { TextSelection } from '@tiptap/pm/state' @@ -15,21 +18,43 @@ import { selectionCell, } from '@tiptap/pm/tables' import { tableToMarkdown } from './markdown.ts' -import TableCaption from './TableCaption.js' -import TableCell from './TableCell.js' -import TableHeader from './TableHeader.js' -import TableHeadRow from './TableHeadRow.js' -import TableRow from './TableRow.js' +import TableCaption from './TableCaption.ts' +import TableCell from './TableCell.ts' +import TableHeader from './TableHeader.ts' +import TableHeadRow from './TableHeadRow.ts' +import TableRow from './TableRow.ts' + +declare module '@tiptap/core' { + interface Commands { + text_table: { + /** + * Move the selection to the node after the table + */ + leaveTable: () => ReturnType + /** + * Move the selection to the same cell in the next row + */ + goToNextRow: () => ReturnType + /** + * Sort the body rows by the column of the given cell + * + * @param direction Sort direction + * @param cell Table cell or header node of the column to sort by + */ + sortColumn: (direction?: 'asc' | 'desc', cell?: PMNode | null) => ReturnType + } + } +} /** * - * @param {object} schema - schema of the editor - * @param {number} rowsCount - number of rows in the table - * @param {number} colsCount - number of cols in the table + * @param schema - schema of the editor + * @param rowsCount - number of rows in the table + * @param colsCount - number of cols in the table */ -function createTable(schema, rowsCount, colsCount) { - const headerCells = [] - const cells = [] +function createTable(schema: Schema, rowsCount: number, colsCount: number) { + const headerCells: PMNode[] = [] + const cells: PMNode[] = [] for (let index = 0; index < colsCount; index += 1) { const cell = schema.nodes.tableCell.createAndFill() if (cell) { @@ -50,9 +75,9 @@ function createTable(schema, rowsCount, colsCount) { /** * - * @param {object} $cell - resolved position of the current cell + * @param $cell - resolved position of the current cell */ -function findSameCellInNextRow($cell) { +function findSameCellInNextRow($cell: ResolvedPos) { if ($cell.index(-1) === $cell.node(-1).childCount - 1) { return null } @@ -69,14 +94,15 @@ function findSameCellInNextRow($cell) { } cellStart += rowNode.nodeSize } + return null } /** * Return the node type name if the selection sits inside a listItem/taskItem. * - * @param {object} selection - the editor selection + * @param selection - the editor selection */ -function findListItemAtSelection(selection) { +function findListItemAtSelection(selection: Selection) { const { $from } = selection for (let depth = $from.depth; depth > 0; depth--) { const name = $from.node(depth).type.name @@ -95,7 +121,7 @@ export default Table.extend({ addCommands() { return { - ...this.parent(), + ...this.parent?.(), addRowAfter: () => ({ chain, dispatch }) => { return chain() @@ -150,11 +176,11 @@ export default Table.extend({ }) .run(), insertTable: - () => ({ tr, dispatch, editor }) => { - if (isInTable(tr)) { + () => ({ state, tr, dispatch, editor }) => { + if (isInTable(state)) { return false } - const node = createTable(editor.schema, 3, 3, true) + const node = createTable(editor.schema, 3, 3) if (dispatch) { const offset = tr.selection.anchor + 1 tr.replaceSelectionWith(node) @@ -165,8 +191,8 @@ export default Table.extend({ }, // move to the next node after the table from the last cell leaveTable: - () => ({ tr, dispatch }) => { - if (!isInTable(tr)) { + () => ({ state, tr, dispatch }) => { + if (!isInTable(state)) { return false } const { $head, empty } = tr.selection @@ -183,13 +209,13 @@ export default Table.extend({ return true }, goToNextRow: - () => ({ tr, dispatch }) => { - if (!isInTable(tr)) { + () => ({ state, tr, dispatch }) => { + if (!isInTable(state)) { return false } - const cell = findSameCellInNextRow(selectionCell(tr)) + const cell = findSameCellInNextRow(selectionCell(state)) if (cell === null) { - return + return false } if (dispatch) { const $cell = tr.doc.resolve(cell) @@ -210,9 +236,10 @@ export default Table.extend({ return false } - // find the table, its position and the column index of the cell - let table = null - let tablePos = null + // Find the table, its position and the column index of the cell + // Assigned in the descendants() callback, which TypeScript cannot narrow through + let table = null as PMNode | null + let tablePos = null as number | null let columnIndex = -1 state.doc.descendants((node, pos) => { @@ -247,8 +274,8 @@ export default Table.extend({ return false } - const bodyRows = [] - const nonBodyChildren = [] + const bodyRows: PMNode[] = [] + const nonBodyChildren: PMNode[] = [] table.forEach((child) => { if (child.type.name === 'tableRow') { bodyRows.push(child) @@ -339,7 +366,7 @@ export default Table.extend({ addKeyboardShortcuts() { return { - ...this.parent(), + ...this.parent?.(), /** * inside a table cell * When inside a list, indent the list item. Otherwise jump to diff --git a/src/nodes/Table/TableCaption.js b/src/nodes/Table/TableCaption.ts similarity index 100% rename from src/nodes/Table/TableCaption.js rename to src/nodes/Table/TableCaption.ts diff --git a/src/nodes/Table/TableCell.js b/src/nodes/Table/TableCell.ts similarity index 94% rename from src/nodes/Table/TableCell.js rename to src/nodes/Table/TableCell.ts index 9a16820f837..4e254597458 100644 --- a/src/nodes/Table/TableCell.js +++ b/src/nodes/Table/TableCell.ts @@ -97,15 +97,15 @@ export default TableCell.extend({ ) { const tableChild = slice.content.firstChild.firstChild if ( - (tableChild.childCount === 1 + (tableChild?.childCount === 1 && tableChild.type.name === 'tableRow') - || tableChild.type.name === 'tableHeadRow' + || tableChild?.type.name === 'tableHeadRow' ) { const rowChild = tableChild.firstChild if ( - (rowChild.childCount === 1 + (rowChild?.childCount === 1 && rowChild.type.name === 'tableCell') - || rowChild.type.name === 'tableHeader' + || rowChild?.type.name === 'tableHeader' ) { return new Slice(rowChild.content, 0, 0) } diff --git a/src/nodes/Table/TableHeadRow.js b/src/nodes/Table/TableHeadRow.ts similarity index 93% rename from src/nodes/Table/TableHeadRow.js rename to src/nodes/Table/TableHeadRow.ts index 2166f25b596..774abbc021e 100644 --- a/src/nodes/Table/TableHeadRow.js +++ b/src/nodes/Table/TableHeadRow.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import TableRow from './TableRow.js' +import TableRow from './TableRow.ts' export default TableRow.extend({ name: 'tableHeadRow', diff --git a/src/nodes/Table/TableHeader.js b/src/nodes/Table/TableHeader.ts similarity index 100% rename from src/nodes/Table/TableHeader.js rename to src/nodes/Table/TableHeader.ts diff --git a/src/nodes/Table/TableHeaderView.vue b/src/nodes/Table/TableHeaderView.vue index 1819990cef8..506109b28db 100644 --- a/src/nodes/Table/TableHeaderView.vue +++ b/src/nodes/Table/TableHeaderView.vue @@ -104,7 +104,7 @@