diff --git a/css/icons.scss b/css/icons.scss index 255d6fc3882..6c36e66fbb2 100644 --- a/css/icons.scss +++ b/css/icons.scss @@ -8,6 +8,7 @@ @include icon-black-white('ol', 'text', 1); @include icon-black-white('ul', 'text', 1); @include icon-black-white('tasklist', 'text', 1); +@include icon-black-white('table', 'text', 1); @include icon-black-white('hr', 'text', 1); @include icon-black-white('quote', 'text', 1); @include icon-black-white('paragraph', 'text', 1); diff --git a/src/mixins/menubar.js b/src/mixins/menubar.js index 23124958ecc..44a52ebf965 100644 --- a/src/mixins/menubar.js +++ b/src/mixins/menubar.js @@ -213,6 +213,14 @@ export default [ return command.toggleCodeBlock() }, }, + { + label: t('text', 'Table'), + class: 'icon-table', + isActive: 'table', + action: (command) => { + return command.insertTable() + }, + }, { label: t('text', 'Emoji picker'), class: 'icon-emoji', diff --git a/src/nodes/Table.js b/src/nodes/Table.js index a4d93ed85d9..d2f55614b4c 100644 --- a/src/nodes/Table.js +++ b/src/nodes/Table.js @@ -1,5 +1,6 @@ import { Table } from '@tiptap/extension-table' import { Node, mergeAttributes } from '@tiptap/core' +import { TextSelection } from 'prosemirror-state' /* * Markdown tables do not include captions. @@ -26,8 +27,53 @@ const tableCaption = Node.create({ { tag: 'table caption', priority: 90 }, ] }, + }) +function getTableNodeTypes(schema) { + if (schema.cached.tableNodeTypes) { + return schema.cached.tableNodeTypes + } + + const roles = {} + + Object.keys(schema.nodes).forEach(type => { + const nodeType = schema.nodes[type] + + if (nodeType.spec.tableRole) { + roles[nodeType.spec.tableRole] = nodeType + } + }) + + schema.cached.tableNodeTypes = roles + + return roles +} + +function createTable(schema, rowsCount, colsCount, cellContent) { + const types = getTableNodeTypes(schema) + const headerCells = [] + const cells = [] + for (let index = 0; index < colsCount; index += 1) { + const cell = types.cell.createAndFill() + if (cell) { + cells.push(cell) + } + const headerCell = types.header_cell.createAndFill() + if (headerCell) { + headerCells.push(headerCell) + } + } + const headRow = types.headRow.createChecked(null, headerCells) + const rows = [] + for (let index = 1; index < rowsCount; index += 1) { + rows.push(types.row.createChecked(null, cells)) + } + const head = types.head.createChecked(null, headRow) + const body = types.body.createChecked(null, rows) + return types.table.createChecked(null, [head, body]) +} + export default Table.extend({ content: 'tableCaption? tableHead tableBody', @@ -37,6 +83,22 @@ export default Table.extend({ ] }, + addCommands() { + return { + ...this.parent(), + insertTable: () => ({ tr, dispatch, editor }) => { + const node = createTable(editor.schema, 3, 3, true) + if (dispatch) { + const offset = tr.selection.anchor + 1 + tr.replaceSelectionWith(node) + .scrollIntoView() + .setSelection(TextSelection.near(tr.doc.resolve(offset))) + } + return true + }, + } + }, + renderHTML({ HTMLAttributes }) { return ['table', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0] }, diff --git a/src/nodes/TableBody.js b/src/nodes/TableBody.js index 5a267342438..529b6e54b8c 100644 --- a/src/nodes/TableBody.js +++ b/src/nodes/TableBody.js @@ -3,6 +3,7 @@ import { Node, mergeAttributes } from '@tiptap/core' export default Node.create({ name: 'tableBody', content: 'tableRow*', + tableRole: 'body', addOptions() { return { diff --git a/src/nodes/TableHead.js b/src/nodes/TableHead.js index 0cd7af7887a..e5acdc2694e 100644 --- a/src/nodes/TableHead.js +++ b/src/nodes/TableHead.js @@ -3,6 +3,7 @@ import { Node, mergeAttributes } from '@tiptap/core' const tableHeadRow = Node.create({ name: 'tableHeadRow', content: 'tableHeader*', + tableRole: 'headRow', addOptions() { return { @@ -31,6 +32,7 @@ const tableHeadRow = Node.create({ export default Node.create({ name: 'tableHead', content: 'tableHeadRow', + tableRole: 'head', addOptions() { return {