359 lines
13 KiB
JavaScript
359 lines
13 KiB
JavaScript
import { findParentNodeClosestToPos, Node, mergeAttributes, callOrReturn, getExtensionField } from '@tiptap/core';
|
|
import { TextSelection } from '@tiptap/pm/state';
|
|
import { CellSelection, addColumnBefore, addColumnAfter, deleteColumn, addRowBefore, addRowAfter, deleteRow, deleteTable, mergeCells, splitCell, toggleHeader, toggleHeaderCell, setCellAttr, goToNextCell, fixTables, columnResizing, tableEditing } from '@tiptap/pm/tables';
|
|
|
|
function getColStyleDeclaration(minWidth, width) {
|
|
if (width) {
|
|
// apply the stored width unless it is below the configured minimum cell width
|
|
return ['width', `${Math.max(width, minWidth)}px`];
|
|
}
|
|
// set the minimum with on the column if it has no stored width
|
|
return ['min-width', `${minWidth}px`];
|
|
}
|
|
|
|
function updateColumns(node, colgroup, // <colgroup> has the same prototype as <col>
|
|
table, cellMinWidth, overrideCol, overrideValue) {
|
|
var _a;
|
|
let totalWidth = 0;
|
|
let fixedWidth = true;
|
|
let nextDOM = colgroup.firstChild;
|
|
const row = node.firstChild;
|
|
if (row !== null) {
|
|
for (let i = 0, col = 0; i < row.childCount; i += 1) {
|
|
const { colspan, colwidth } = row.child(i).attrs;
|
|
for (let j = 0; j < colspan; j += 1, col += 1) {
|
|
const hasWidth = overrideCol === col ? overrideValue : (colwidth && colwidth[j]);
|
|
const cssWidth = hasWidth ? `${hasWidth}px` : '';
|
|
totalWidth += hasWidth || cellMinWidth;
|
|
if (!hasWidth) {
|
|
fixedWidth = false;
|
|
}
|
|
if (!nextDOM) {
|
|
const colElement = document.createElement('col');
|
|
const [propertyKey, propertyValue] = getColStyleDeclaration(cellMinWidth, hasWidth);
|
|
colElement.style.setProperty(propertyKey, propertyValue);
|
|
colgroup.appendChild(colElement);
|
|
}
|
|
else {
|
|
if (nextDOM.style.width !== cssWidth) {
|
|
const [propertyKey, propertyValue] = getColStyleDeclaration(cellMinWidth, hasWidth);
|
|
nextDOM.style.setProperty(propertyKey, propertyValue);
|
|
}
|
|
nextDOM = nextDOM.nextSibling;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (nextDOM) {
|
|
const after = nextDOM.nextSibling;
|
|
(_a = nextDOM.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(nextDOM);
|
|
nextDOM = after;
|
|
}
|
|
if (fixedWidth) {
|
|
table.style.width = `${totalWidth}px`;
|
|
table.style.minWidth = '';
|
|
}
|
|
else {
|
|
table.style.width = '';
|
|
table.style.minWidth = `${totalWidth}px`;
|
|
}
|
|
}
|
|
class TableView {
|
|
constructor(node, cellMinWidth) {
|
|
this.node = node;
|
|
this.cellMinWidth = cellMinWidth;
|
|
this.dom = document.createElement('div');
|
|
this.dom.className = 'tableWrapper';
|
|
this.table = this.dom.appendChild(document.createElement('table'));
|
|
this.colgroup = this.table.appendChild(document.createElement('colgroup'));
|
|
updateColumns(node, this.colgroup, this.table, cellMinWidth);
|
|
this.contentDOM = this.table.appendChild(document.createElement('tbody'));
|
|
}
|
|
update(node) {
|
|
if (node.type !== this.node.type) {
|
|
return false;
|
|
}
|
|
this.node = node;
|
|
updateColumns(node, this.colgroup, this.table, this.cellMinWidth);
|
|
return true;
|
|
}
|
|
ignoreMutation(mutation) {
|
|
return (mutation.type === 'attributes'
|
|
&& (mutation.target === this.table || this.colgroup.contains(mutation.target)));
|
|
}
|
|
}
|
|
|
|
function createColGroup(node, cellMinWidth, overrideCol, overrideValue) {
|
|
let totalWidth = 0;
|
|
let fixedWidth = true;
|
|
const cols = [];
|
|
const row = node.firstChild;
|
|
if (!row) {
|
|
return {};
|
|
}
|
|
for (let i = 0, col = 0; i < row.childCount; i += 1) {
|
|
const { colspan, colwidth } = row.child(i).attrs;
|
|
for (let j = 0; j < colspan; j += 1, col += 1) {
|
|
const hasWidth = overrideCol === col ? overrideValue : colwidth && colwidth[j];
|
|
totalWidth += hasWidth || cellMinWidth;
|
|
if (!hasWidth) {
|
|
fixedWidth = false;
|
|
}
|
|
const [property, value] = getColStyleDeclaration(cellMinWidth, hasWidth);
|
|
cols.push([
|
|
'col',
|
|
{ style: `${property}: ${value}` },
|
|
]);
|
|
}
|
|
}
|
|
const tableWidth = fixedWidth ? `${totalWidth}px` : '';
|
|
const tableMinWidth = fixedWidth ? '' : `${totalWidth}px`;
|
|
const colgroup = ['colgroup', {}, ...cols];
|
|
return { colgroup, tableWidth, tableMinWidth };
|
|
}
|
|
|
|
function createCell(cellType, cellContent) {
|
|
if (cellContent) {
|
|
return cellType.createChecked(null, cellContent);
|
|
}
|
|
return cellType.createAndFill();
|
|
}
|
|
|
|
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, withHeaderRow, cellContent) {
|
|
const types = getTableNodeTypes(schema);
|
|
const headerCells = [];
|
|
const cells = [];
|
|
for (let index = 0; index < colsCount; index += 1) {
|
|
const cell = createCell(types.cell, cellContent);
|
|
if (cell) {
|
|
cells.push(cell);
|
|
}
|
|
if (withHeaderRow) {
|
|
const headerCell = createCell(types.header_cell, cellContent);
|
|
if (headerCell) {
|
|
headerCells.push(headerCell);
|
|
}
|
|
}
|
|
}
|
|
const rows = [];
|
|
for (let index = 0; index < rowsCount; index += 1) {
|
|
rows.push(types.row.createChecked(null, withHeaderRow && index === 0 ? headerCells : cells));
|
|
}
|
|
return types.table.createChecked(null, rows);
|
|
}
|
|
|
|
function isCellSelection(value) {
|
|
return value instanceof CellSelection;
|
|
}
|
|
|
|
const deleteTableWhenAllCellsSelected = ({ editor }) => {
|
|
const { selection } = editor.state;
|
|
if (!isCellSelection(selection)) {
|
|
return false;
|
|
}
|
|
let cellCount = 0;
|
|
const table = findParentNodeClosestToPos(selection.ranges[0].$from, node => {
|
|
return node.type.name === 'table';
|
|
});
|
|
table === null || table === void 0 ? void 0 : table.node.descendants(node => {
|
|
if (node.type.name === 'table') {
|
|
return false;
|
|
}
|
|
if (['tableCell', 'tableHeader'].includes(node.type.name)) {
|
|
cellCount += 1;
|
|
}
|
|
});
|
|
const allCellsSelected = cellCount === selection.ranges.length;
|
|
if (!allCellsSelected) {
|
|
return false;
|
|
}
|
|
editor.commands.deleteTable();
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* This extension allows you to create tables.
|
|
* @see https://www.tiptap.dev/api/nodes/table
|
|
*/
|
|
const Table = Node.create({
|
|
name: 'table',
|
|
// @ts-ignore
|
|
addOptions() {
|
|
return {
|
|
HTMLAttributes: {},
|
|
resizable: false,
|
|
handleWidth: 5,
|
|
cellMinWidth: 25,
|
|
// TODO: fix
|
|
View: TableView,
|
|
lastColumnResizable: true,
|
|
allowTableNodeSelection: false,
|
|
};
|
|
},
|
|
content: 'tableRow+',
|
|
tableRole: 'table',
|
|
isolating: true,
|
|
group: 'block',
|
|
parseHTML() {
|
|
return [{ tag: 'table' }];
|
|
},
|
|
renderHTML({ node, HTMLAttributes }) {
|
|
const { colgroup, tableWidth, tableMinWidth } = createColGroup(node, this.options.cellMinWidth);
|
|
const table = [
|
|
'table',
|
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
|
style: tableWidth
|
|
? `width: ${tableWidth}`
|
|
: `min-width: ${tableMinWidth}`,
|
|
}),
|
|
colgroup,
|
|
['tbody', 0],
|
|
];
|
|
return table;
|
|
},
|
|
addCommands() {
|
|
return {
|
|
insertTable: ({ rows = 3, cols = 3, withHeaderRow = true } = {}) => ({ tr, dispatch, editor }) => {
|
|
const node = createTable(editor.schema, rows, cols, withHeaderRow);
|
|
if (dispatch) {
|
|
const offset = tr.selection.from + 1;
|
|
tr.replaceSelectionWith(node)
|
|
.scrollIntoView()
|
|
.setSelection(TextSelection.near(tr.doc.resolve(offset)));
|
|
}
|
|
return true;
|
|
},
|
|
addColumnBefore: () => ({ state, dispatch }) => {
|
|
return addColumnBefore(state, dispatch);
|
|
},
|
|
addColumnAfter: () => ({ state, dispatch }) => {
|
|
return addColumnAfter(state, dispatch);
|
|
},
|
|
deleteColumn: () => ({ state, dispatch }) => {
|
|
return deleteColumn(state, dispatch);
|
|
},
|
|
addRowBefore: () => ({ state, dispatch }) => {
|
|
return addRowBefore(state, dispatch);
|
|
},
|
|
addRowAfter: () => ({ state, dispatch }) => {
|
|
return addRowAfter(state, dispatch);
|
|
},
|
|
deleteRow: () => ({ state, dispatch }) => {
|
|
return deleteRow(state, dispatch);
|
|
},
|
|
deleteTable: () => ({ state, dispatch }) => {
|
|
return deleteTable(state, dispatch);
|
|
},
|
|
mergeCells: () => ({ state, dispatch }) => {
|
|
return mergeCells(state, dispatch);
|
|
},
|
|
splitCell: () => ({ state, dispatch }) => {
|
|
return splitCell(state, dispatch);
|
|
},
|
|
toggleHeaderColumn: () => ({ state, dispatch }) => {
|
|
return toggleHeader('column')(state, dispatch);
|
|
},
|
|
toggleHeaderRow: () => ({ state, dispatch }) => {
|
|
return toggleHeader('row')(state, dispatch);
|
|
},
|
|
toggleHeaderCell: () => ({ state, dispatch }) => {
|
|
return toggleHeaderCell(state, dispatch);
|
|
},
|
|
mergeOrSplit: () => ({ state, dispatch }) => {
|
|
if (mergeCells(state, dispatch)) {
|
|
return true;
|
|
}
|
|
return splitCell(state, dispatch);
|
|
},
|
|
setCellAttribute: (name, value) => ({ state, dispatch }) => {
|
|
return setCellAttr(name, value)(state, dispatch);
|
|
},
|
|
goToNextCell: () => ({ state, dispatch }) => {
|
|
return goToNextCell(1)(state, dispatch);
|
|
},
|
|
goToPreviousCell: () => ({ state, dispatch }) => {
|
|
return goToNextCell(-1)(state, dispatch);
|
|
},
|
|
fixTables: () => ({ state, dispatch }) => {
|
|
if (dispatch) {
|
|
fixTables(state);
|
|
}
|
|
return true;
|
|
},
|
|
setCellSelection: position => ({ tr, dispatch }) => {
|
|
if (dispatch) {
|
|
const selection = CellSelection.create(tr.doc, position.anchorCell, position.headCell);
|
|
// @ts-ignore
|
|
tr.setSelection(selection);
|
|
}
|
|
return true;
|
|
},
|
|
};
|
|
},
|
|
addKeyboardShortcuts() {
|
|
return {
|
|
Tab: () => {
|
|
if (this.editor.commands.goToNextCell()) {
|
|
return true;
|
|
}
|
|
if (!this.editor.can().addRowAfter()) {
|
|
return false;
|
|
}
|
|
return this.editor.chain().addRowAfter().goToNextCell().run();
|
|
},
|
|
'Shift-Tab': () => this.editor.commands.goToPreviousCell(),
|
|
Backspace: deleteTableWhenAllCellsSelected,
|
|
'Mod-Backspace': deleteTableWhenAllCellsSelected,
|
|
Delete: deleteTableWhenAllCellsSelected,
|
|
'Mod-Delete': deleteTableWhenAllCellsSelected,
|
|
};
|
|
},
|
|
addProseMirrorPlugins() {
|
|
const isResizable = this.options.resizable && this.editor.isEditable;
|
|
return [
|
|
...(isResizable
|
|
? [
|
|
columnResizing({
|
|
handleWidth: this.options.handleWidth,
|
|
cellMinWidth: this.options.cellMinWidth,
|
|
defaultCellMinWidth: this.options.cellMinWidth,
|
|
View: this.options.View,
|
|
lastColumnResizable: this.options.lastColumnResizable,
|
|
}),
|
|
]
|
|
: []),
|
|
tableEditing({
|
|
allowTableNodeSelection: this.options.allowTableNodeSelection,
|
|
}),
|
|
];
|
|
},
|
|
extendNodeSchema(extension) {
|
|
const context = {
|
|
name: extension.name,
|
|
options: extension.options,
|
|
storage: extension.storage,
|
|
};
|
|
return {
|
|
tableRole: callOrReturn(getExtensionField(extension, 'tableRole', context)),
|
|
};
|
|
},
|
|
});
|
|
|
|
export { Table, TableView, createColGroup, createTable, Table as default, updateColumns };
|
|
//# sourceMappingURL=index.js.map
|