import { Node } from '@tiptap/core'; import type { Node as PMNode, Schema } from 'prosemirror-model'; import { addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, deleteColumn, deleteRow, deleteTable, tableEditing, toggleHeaderRow, } from 'prosemirror-tables'; import { attributesFromSpec, extendWithTableRole, nodeSpec, passthroughNodeIO, } from '../spec-utils'; declare module '@tiptap/core' { interface Commands { documentTable: { insertTable: (options?: { rows?: number; cols?: number; withHeaderRow?: boolean; }) => ReturnType; addColumnBefore: () => ReturnType; addColumnAfter: () => ReturnType; deleteColumn: () => ReturnType; addRowBefore: () => ReturnType; addRowAfter: () => ReturnType; deleteRow: () => ReturnType; deleteTable: () => ReturnType; toggleHeaderRow: () => ReturnType; }; } } /** `type.createAndFill()` picks a default child (an empty paragraph) that * satisfies the cell's `block+` content expression. */ function buildTableRow(schema: Schema, cols: number, header: boolean): PMNode { const cellType = schema.nodes[header ? 'table_header' : 'table_cell']!; const cells = Array.from({ length: cols }, () => cellType.createAndFill()!); return schema.nodes.table_row!.create(null, cells); } function buildTable(schema: Schema, rows: number, cols: number, withHeaderRow: boolean): PMNode { const rowNodes = Array.from({ length: rows }, (_, index) => buildTableRow(schema, cols, withHeaderRow && index === 0), ); return schema.nodes.table!.create(null, rowNodes); } const tableSpec = nodeSpec('table'); export const Table = Node.create({ name: 'table', group: tableSpec.group, content: tableSpec.content, isolating: tableSpec.isolating, ...passthroughNodeIO(tableSpec), ...extendWithTableRole('table', 'table'), addProseMirrorPlugins() { return [tableEditing()]; }, addCommands() { return { insertTable: ({ rows = 3, cols = 3, withHeaderRow = true } = {}) => ({ chain, editor }) => chain() .insertContent(buildTable(editor.schema, rows, cols, withHeaderRow).toJSON()) .run(), addColumnBefore: () => ({ state, dispatch }) => addColumnBefore(state, dispatch), addColumnAfter: () => ({ state, dispatch }) => addColumnAfter(state, dispatch), deleteColumn: () => ({ state, dispatch }) => deleteColumn(state, dispatch), addRowBefore: () => ({ state, dispatch }) => addRowBefore(state, dispatch), addRowAfter: () => ({ state, dispatch }) => addRowAfter(state, dispatch), deleteRow: () => ({ state, dispatch }) => deleteRow(state, dispatch), deleteTable: () => ({ state, dispatch }) => deleteTable(state, dispatch), toggleHeaderRow: () => ({ state, dispatch }) => toggleHeaderRow(state, dispatch), }; }, }); const tableRowSpec = nodeSpec('table_row'); export const TableRow = Node.create({ name: 'table_row', content: tableRowSpec.content, ...passthroughNodeIO(tableRowSpec), ...extendWithTableRole('table_row', 'row'), }); const tableCellSpec = nodeSpec('table_cell'); export const TableCell = Node.create({ name: 'table_cell', content: tableCellSpec.content, isolating: tableCellSpec.isolating, addAttributes() { return attributesFromSpec(tableCellSpec); }, ...passthroughNodeIO(tableCellSpec), ...extendWithTableRole('table_cell', 'cell'), }); const tableHeaderSpec = nodeSpec('table_header'); export const TableHeader = Node.create({ name: 'table_header', content: tableHeaderSpec.content, isolating: tableHeaderSpec.isolating, addAttributes() { return attributesFromSpec(tableHeaderSpec); }, ...passthroughNodeIO(tableHeaderSpec), ...extendWithTableRole('table_header', 'header_cell'), });