From c290a6e855e9586e4d6e215cdc0f3ca07074388b Mon Sep 17 00:00:00 2001 From: chris <kokosias@yahoo.gr> Date: Mon, 28 Aug 2023 17:32:20 +0300 Subject: [PATCH] add new tables src --- editors/demo/package.json | 1 - editors/demo/src/Editoria/config/config.js | 4 +- .../demo/src/Editoria/config/configMobile.js | 3 +- editors/demo/src/HHMI/config/config.js | 3 +- editors/demo/src/OEN/config/config.js | 4 +- wax-prosemirror-core/rollup.config.js | 1 - wax-prosemirror-services/package.json | 1 - wax-prosemirror-services/rollup.config.js | 1 - .../InsertTableService/InsertTableService.js | 2 +- .../TablesService/components/TableDropDown.js | 2 +- .../src/TablesService/tableSrc/index.js | 2519 +++++++++++++++++ yarn.lock | 21 +- 12 files changed, 2531 insertions(+), 31 deletions(-) create mode 100644 wax-prosemirror-services/src/TablesService/tableSrc/index.js diff --git a/editors/demo/package.json b/editors/demo/package.json index c4b6a5adf..3751aee52 100644 --- a/editors/demo/package.json +++ b/editors/demo/package.json @@ -6,7 +6,6 @@ "@guardian/prosemirror-invisibles": "^1.2.0", "@pubsweet/ui-toolkit": "^2.3.1", "fontsource-merriweather": "^3.0.9", - "prosemirror-tables": "1.3.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-panelgroup": "^1.0.10", diff --git a/editors/demo/src/Editoria/config/config.js b/editors/demo/src/Editoria/config/config.js index 7b700096a..b21a5a8be 100644 --- a/editors/demo/src/Editoria/config/config.js +++ b/editors/demo/src/Editoria/config/config.js @@ -1,6 +1,6 @@ import { emDash, ellipsis } from 'prosemirror-inputrules'; import { debounce } from 'lodash'; -import { columnResizing, tableEditing } from 'prosemirror-tables'; + import { InlineAnnotationsService, AnnotationToolGroupService, @@ -150,8 +150,6 @@ export default { }, }, PmPlugins: [ - columnResizing(), - tableEditing(), invisibles([hardBreak()]), disallowPasteImagesPlugin(() => onWarning( diff --git a/editors/demo/src/Editoria/config/configMobile.js b/editors/demo/src/Editoria/config/configMobile.js index f873786b5..3db3eb6c7 100644 --- a/editors/demo/src/Editoria/config/configMobile.js +++ b/editors/demo/src/Editoria/config/configMobile.js @@ -1,5 +1,4 @@ import { emDash, ellipsis } from 'prosemirror-inputrules'; -import { columnResizing, tableEditing } from 'prosemirror-tables'; import { AnnotationToolGroupService, ImageService, @@ -79,7 +78,7 @@ export default { TitleService: { updateTitle }, EnableTrackChangeService: { enabled: false }, - PmPlugins: [columnResizing(), tableEditing(), invisibles([hardBreak()])], + PmPlugins: [invisibles([hardBreak()])], CustomTagService: { tags: [ { label: 'custom-tag-label-1', tagType: 'inline' }, diff --git a/editors/demo/src/HHMI/config/config.js b/editors/demo/src/HHMI/config/config.js index 0d12462bb..1ba9568d1 100644 --- a/editors/demo/src/HHMI/config/config.js +++ b/editors/demo/src/HHMI/config/config.js @@ -1,5 +1,4 @@ import { emDash, ellipsis } from 'prosemirror-inputrules'; -import { columnResizing, tableEditing } from 'prosemirror-tables'; import { InlineAnnotationsService, AnnotationToolGroupService, @@ -130,7 +129,7 @@ export default { RulesService: [emDash, ellipsis], ImageService: { showAlt: true }, - PmPlugins: [columnResizing(), tableEditing(), invisibles([hardBreak()])], + PmPlugins: [invisibles([hardBreak()])], services: [ // new ExternalAPIContentService(), new QuestionsService(), diff --git a/editors/demo/src/OEN/config/config.js b/editors/demo/src/OEN/config/config.js index 0a148460a..b2a987205 100644 --- a/editors/demo/src/OEN/config/config.js +++ b/editors/demo/src/OEN/config/config.js @@ -1,6 +1,6 @@ import { emDash, ellipsis } from 'prosemirror-inputrules'; import { debounce } from 'lodash'; -import { columnResizing, tableEditing } from 'prosemirror-tables'; + import { InlineAnnotationsService, AnnotationToolGroupService, @@ -191,7 +191,7 @@ export default { reject: true, }, }, - PmPlugins: [columnResizing(), tableEditing(), invisibles([hardBreak()])], + PmPlugins: [invisibles([hardBreak()])], CustomTagService: { tags: [ { label: 'custom-tag-label-1', tagType: 'inline' }, diff --git a/wax-prosemirror-core/rollup.config.js b/wax-prosemirror-core/rollup.config.js index 8d739e1e7..1eb417eb1 100644 --- a/wax-prosemirror-core/rollup.config.js +++ b/wax-prosemirror-core/rollup.config.js @@ -34,7 +34,6 @@ export default { 'react-dropdown', 'uuid', 'prop-types', - 'prosemirror-tables', 'wax-prosemirror-services', ], }; diff --git a/wax-prosemirror-services/package.json b/wax-prosemirror-services/package.json index a13ea0bdc..00f9eded2 100644 --- a/wax-prosemirror-services/package.json +++ b/wax-prosemirror-services/package.json @@ -29,7 +29,6 @@ "prosemirror-model": "1.19.0", "prosemirror-schema-list": "1.3.0", "prosemirror-state": "1.4.2", - "prosemirror-tables": "1.3.4", "prosemirror-transform": "1.7.1", "prosemirror-view": "1.30.2", "react-dropdown": "^1.6.2", diff --git a/wax-prosemirror-services/rollup.config.js b/wax-prosemirror-services/rollup.config.js index 4fda92e21..f4d5cd16f 100644 --- a/wax-prosemirror-services/rollup.config.js +++ b/wax-prosemirror-services/rollup.config.js @@ -42,7 +42,6 @@ export default { 'react-dom', 'react-dropdown', 'lodash', - 'prosemirror-tables', 'prosemirror-schema-list', 'prosemirror-model', 'prosemirror-inputrules', diff --git a/wax-prosemirror-services/src/TablesService/InsertTableService/InsertTableService.js b/wax-prosemirror-services/src/TablesService/InsertTableService/InsertTableService.js index beed96a32..ce9c7f423 100644 --- a/wax-prosemirror-services/src/TablesService/InsertTableService/InsertTableService.js +++ b/wax-prosemirror-services/src/TablesService/InsertTableService/InsertTableService.js @@ -1,5 +1,5 @@ import { Service } from 'wax-prosemirror-core'; -import { tableNodes, goToNextCell } from 'prosemirror-tables'; +import { tableNodes, goToNextCell } from '../tableSrc'; import Table from './Table'; class InsertTableService extends Service { diff --git a/wax-prosemirror-services/src/TablesService/components/TableDropDown.js b/wax-prosemirror-services/src/TablesService/components/TableDropDown.js index e7736d78f..903075b7a 100644 --- a/wax-prosemirror-services/src/TablesService/components/TableDropDown.js +++ b/wax-prosemirror-services/src/TablesService/components/TableDropDown.js @@ -8,8 +8,8 @@ import React, { createRef, } from 'react'; import styled from 'styled-components'; -import * as tablesFn from 'prosemirror-tables'; import { WaxContext, Icon, useOnClickOutside } from 'wax-prosemirror-core'; +import * as tablesFn from '../tableSrc'; const Wrapper = styled.div` opacity: ${props => (props.disabled ? '0.4' : '1')}; diff --git a/wax-prosemirror-services/src/TablesService/tableSrc/index.js b/wax-prosemirror-services/src/TablesService/tableSrc/index.js new file mode 100644 index 000000000..e5b83c099 --- /dev/null +++ b/wax-prosemirror-services/src/TablesService/tableSrc/index.js @@ -0,0 +1,2519 @@ +/* eslint-disable */ + +// src/index.ts +import { Plugin as Plugin2 } from 'prosemirror-state'; + +// src/cellselection.ts +import { Fragment, Slice } from 'prosemirror-model'; +import { + NodeSelection as NodeSelection2, + Selection, + SelectionRange, + TextSelection, +} from 'prosemirror-state'; +import { Decoration, DecorationSet } from 'prosemirror-view'; + +// src/tablemap.ts +var readFromCache; +var addToCache; +if (typeof WeakMap != 'undefined') { + let cache = /* @__PURE__ */ new WeakMap(); + readFromCache = key => cache.get(key); + addToCache = (key, value) => { + cache.set(key, value); + return value; + }; +} else { + const cache = []; + const cacheSize = 10; + let cachePos = 0; + readFromCache = key => { + for (let i = 0; i < cache.length; i += 2) + if (cache[i] == key) return cache[i + 1]; + }; + addToCache = (key, value) => { + if (cachePos == cacheSize) cachePos = 0; + cache[cachePos++] = key; + return (cache[cachePos++] = value); + }; +} +var TableMap = class { + constructor(width, height, map, problems) { + this.width = width; + this.height = height; + this.map = map; + this.problems = problems; + } + // Find the dimensions of the cell at the given position. + findCell(pos) { + for (let i = 0; i < this.map.length; i++) { + const curPos = this.map[i]; + if (curPos != pos) continue; + const left = i % this.width; + const top = (i / this.width) | 0; + let right = left + 1; + let bottom = top + 1; + for (let j = 1; right < this.width && this.map[i + j] == curPos; j++) { + right++; + } + for ( + let j = 1; + bottom < this.height && this.map[i + this.width * j] == curPos; + j++ + ) { + bottom++; + } + return { left, top, right, bottom }; + } + throw new RangeError(`No cell with offset ${pos} found`); + } + // Find the left side of the cell at the given position. + colCount(pos) { + for (let i = 0; i < this.map.length; i++) { + if (this.map[i] == pos) { + return i % this.width; + } + } + throw new RangeError(`No cell with offset ${pos} found`); + } + // Find the next cell in the given direction, starting from the cell + // at `pos`, if any. + nextCell(pos, axis, dir) { + const { left, right, top, bottom } = this.findCell(pos); + if (axis == 'horiz') { + if (dir < 0 ? left == 0 : right == this.width) return null; + return this.map[top * this.width + (dir < 0 ? left - 1 : right)]; + } else { + if (dir < 0 ? top == 0 : bottom == this.height) return null; + return this.map[left + this.width * (dir < 0 ? top - 1 : bottom)]; + } + } + // Get the rectangle spanning the two given cells. + rectBetween(a, b) { + const { + left: leftA, + right: rightA, + top: topA, + bottom: bottomA, + } = this.findCell(a); + const { + left: leftB, + right: rightB, + top: topB, + bottom: bottomB, + } = this.findCell(b); + return { + left: Math.min(leftA, leftB), + top: Math.min(topA, topB), + right: Math.max(rightA, rightB), + bottom: Math.max(bottomA, bottomB), + }; + } + // Return the position of all cells that have the top left corner in + // the given rectangle. + cellsInRect(rect) { + const result = []; + const seen = {}; + for (let row = rect.top; row < rect.bottom; row++) { + for (let col = rect.left; col < rect.right; col++) { + const index = row * this.width + col; + const pos = this.map[index]; + if (seen[pos]) continue; + seen[pos] = true; + if ( + (col == rect.left && col && this.map[index - 1] == pos) || + (row == rect.top && row && this.map[index - this.width] == pos) + ) { + continue; + } + result.push(pos); + } + } + return result; + } + // Return the position at which the cell at the given row and column + // starts, or would start, if a cell started there. + positionAt(row, col, table) { + for (let i = 0, rowStart = 0; ; i++) { + const rowEnd = rowStart + table.child(i).nodeSize; + if (i == row) { + let index = col + row * this.width; + const rowEndIndex = (row + 1) * this.width; + while (index < rowEndIndex && this.map[index] < rowStart) index++; + return index == rowEndIndex ? rowEnd - 1 : this.map[index]; + } + rowStart = rowEnd; + } + } + // Find the table map for the given table node. + static get(table) { + return readFromCache(table) || addToCache(table, computeMap(table)); + } +}; +function computeMap(table) { + if (table.type.spec.tableRole != 'table') + throw new RangeError('Not a table node: ' + table.type.name); + const width = findWidth(table), + height = table.childCount; + const map = []; + let mapPos = 0; + let problems = null; + const colWidths = []; + for (let i = 0, e = width * height; i < e; i++) map[i] = 0; + for (let row = 0, pos = 0; row < height; row++) { + const rowNode = table.child(row); + pos++; + for (let i = 0; ; i++) { + while (mapPos < map.length && map[mapPos] != 0) mapPos++; + if (i == rowNode.childCount) break; + const cellNode = rowNode.child(i); + const { colspan, rowspan, colwidth } = cellNode.attrs; + for (let h = 0; h < rowspan; h++) { + if (h + row >= height) { + (problems || (problems = [])).push({ + type: 'overlong_rowspan', + pos, + n: rowspan - h, + }); + break; + } + const start = mapPos + h * width; + for (let w = 0; w < colspan; w++) { + if (map[start + w] == 0) map[start + w] = pos; + else + (problems || (problems = [])).push({ + type: 'collision', + row, + pos, + n: colspan - w, + }); + const colW = colwidth && colwidth[w]; + if (colW) { + const widthIndex = ((start + w) % width) * 2, + prev = colWidths[widthIndex]; + if ( + prev == null || + (prev != colW && colWidths[widthIndex + 1] == 1) + ) { + colWidths[widthIndex] = colW; + colWidths[widthIndex + 1] = 1; + } else if (prev == colW) { + colWidths[widthIndex + 1]++; + } + } + } + } + mapPos += colspan; + pos += cellNode.nodeSize; + } + const expectedPos = (row + 1) * width; + let missing = 0; + while (mapPos < expectedPos) if (map[mapPos++] == 0) missing++; + if (missing) + (problems || (problems = [])).push({ type: 'missing', row, n: missing }); + pos++; + } + const tableMap = new TableMap(width, height, map, problems); + let badWidths = false; + for (let i = 0; !badWidths && i < colWidths.length; i += 2) + if (colWidths[i] != null && colWidths[i + 1] < height) badWidths = true; + if (badWidths) findBadColWidths(tableMap, colWidths, table); + return tableMap; +} +function findWidth(table) { + let width = -1; + let hasRowSpan = false; + for (let row = 0; row < table.childCount; row++) { + const rowNode = table.child(row); + let rowWidth = 0; + if (hasRowSpan) + for (let j = 0; j < row; j++) { + const prevRow = table.child(j); + for (let i = 0; i < prevRow.childCount; i++) { + const cell = prevRow.child(i); + if (j + cell.attrs.rowspan > row) rowWidth += cell.attrs.colspan; + } + } + for (let i = 0; i < rowNode.childCount; i++) { + const cell = rowNode.child(i); + rowWidth += cell.attrs.colspan; + if (cell.attrs.rowspan > 1) hasRowSpan = true; + } + if (width == -1) width = rowWidth; + else if (width != rowWidth) width = Math.max(width, rowWidth); + } + return width; +} +function findBadColWidths(map, colWidths, table) { + if (!map.problems) map.problems = []; + const seen = {}; + for (let i = 0; i < map.map.length; i++) { + const pos = map.map[i]; + if (seen[pos]) continue; + seen[pos] = true; + const node = table.nodeAt(pos); + if (!node) { + throw new RangeError(`No cell with offset ${pos} found`); + } + let updated = null; + const attrs = node.attrs; + for (let j = 0; j < attrs.colspan; j++) { + const col = (i + j) % map.width; + const colWidth = colWidths[col * 2]; + if ( + colWidth != null && + (!attrs.colwidth || attrs.colwidth[j] != colWidth) + ) + (updated || (updated = freshColWidth(attrs)))[j] = colWidth; + } + if (updated) + map.problems.unshift({ + type: 'colwidth mismatch', + pos, + colwidth: updated, + }); + } +} +function freshColWidth(attrs) { + if (attrs.colwidth) return attrs.colwidth.slice(); + const result = []; + for (let i = 0; i < attrs.colspan; i++) result.push(0); + return result; +} + +// src/util.ts +import { PluginKey } from 'prosemirror-state'; + +// src/schema.ts +function getCellAttrs(dom, extraAttrs) { + if (typeof dom === 'string') { + return {}; + } + const widthAttr = dom.getAttribute('data-colwidth'); + const widths = + widthAttr && /^\d+(,\d+)*$/.test(widthAttr) + ? widthAttr.split(',').map(s => Number(s)) + : null; + const colspan = Number(dom.getAttribute('colspan') || 1); + const result = { + colspan, + rowspan: Number(dom.getAttribute('rowspan') || 1), + colwidth: widths && widths.length == colspan ? widths : null, + }; + for (const prop in extraAttrs) { + const getter = extraAttrs[prop].getFromDOM; + const value = getter && getter(dom); + if (value != null) { + result[prop] = value; + } + } + return result; +} +function setCellAttrs(node, extraAttrs) { + const attrs = {}; + if (node.attrs.colspan != 1) attrs.colspan = node.attrs.colspan; + if (node.attrs.rowspan != 1) attrs.rowspan = node.attrs.rowspan; + if (node.attrs.colwidth) + attrs['data-colwidth'] = node.attrs.colwidth.join(','); + for (const prop in extraAttrs) { + const setter = extraAttrs[prop].setDOMAttr; + if (setter) setter(node.attrs[prop], attrs); + } + return attrs; +} +function tableNodes(options) { + const extraAttrs = options.cellAttributes || {}; + const cellAttrs = { + colspan: { default: 1 }, + rowspan: { default: 1 }, + colwidth: { default: null }, + }; + for (const prop in extraAttrs) + cellAttrs[prop] = { default: extraAttrs[prop].default }; + return { + table: { + content: 'table_row+', + tableRole: 'table', + isolating: true, + group: options.tableGroup, + parseDOM: [{ tag: 'table' }], + toDOM() { + return ['table', ['tbody', 0]]; + }, + }, + table_row: { + content: '(table_cell | table_header)*', + tableRole: 'row', + parseDOM: [{ tag: 'tr' }], + toDOM() { + return ['tr', 0]; + }, + }, + table_cell: { + content: options.cellContent, + attrs: cellAttrs, + tableRole: 'cell', + isolating: true, + parseDOM: [{ tag: 'td', getAttrs: dom => getCellAttrs(dom, extraAttrs) }], + toDOM(node) { + return ['td', setCellAttrs(node, extraAttrs), 0]; + }, + }, + table_header: { + content: options.cellContent, + attrs: cellAttrs, + tableRole: 'header_cell', + isolating: true, + parseDOM: [{ tag: 'th', getAttrs: dom => getCellAttrs(dom, extraAttrs) }], + toDOM(node) { + return ['th', setCellAttrs(node, extraAttrs), 0]; + }, + }, + }; +} +function tableNodeTypes(schema) { + let result = schema.cached.tableNodeTypes; + if (!result) { + result = schema.cached.tableNodeTypes = {}; + for (const name in schema.nodes) { + const type = schema.nodes[name], + role = type.spec.tableRole; + if (role) result[role] = type; + } + } + return result; +} + +// src/util.ts +var tableEditingKey = new PluginKey('selectingCells'); +function cellAround($pos) { + for (let d = $pos.depth - 1; d > 0; d--) + if ($pos.node(d).type.spec.tableRole == 'row') + return $pos.node(0).resolve($pos.before(d + 1)); + return null; +} +function cellWrapping($pos) { + for (let d = $pos.depth; d > 0; d--) { + const role = $pos.node(d).type.spec.tableRole; + if (role === 'cell' || role === 'header_cell') return $pos.node(d); + } + return null; +} +function isInTable(state) { + const $head = state.selection.$head; + for (let d = $head.depth; d > 0; d--) + if ($head.node(d).type.spec.tableRole == 'row') return true; + return false; +} +function selectionCell(state) { + const sel = state.selection; + if ('$anchorCell' in sel && sel.$anchorCell) { + return sel.$anchorCell.pos > sel.$headCell.pos + ? sel.$anchorCell + : sel.$headCell; + } else if ( + 'node' in sel && + sel.node && + sel.node.type.spec.tableRole == 'cell' + ) { + return sel.$anchor; + } + const $cell = cellAround(sel.$head) || cellNear(sel.$head); + if ($cell) { + return $cell; + } + throw new RangeError(`No cell found around position ${sel.head}`); +} +function cellNear($pos) { + for ( + let after = $pos.nodeAfter, pos = $pos.pos; + after; + after = after.firstChild, pos++ + ) { + const role = after.type.spec.tableRole; + if (role == 'cell' || role == 'header_cell') return $pos.doc.resolve(pos); + } + for ( + let before = $pos.nodeBefore, pos = $pos.pos; + before; + before = before.lastChild, pos-- + ) { + const role = before.type.spec.tableRole; + if (role == 'cell' || role == 'header_cell') + return $pos.doc.resolve(pos - before.nodeSize); + } +} +function pointsAtCell($pos) { + return $pos.parent.type.spec.tableRole == 'row' && !!$pos.nodeAfter; +} +function moveCellForward($pos) { + return $pos.node(0).resolve($pos.pos + $pos.nodeAfter.nodeSize); +} +function inSameTable($cellA, $cellB) { + return ( + $cellA.depth == $cellB.depth && + $cellA.pos >= $cellB.start(-1) && + $cellA.pos <= $cellB.end(-1) + ); +} +function findCell($pos) { + return TableMap.get($pos.node(-1)).findCell($pos.pos - $pos.start(-1)); +} +function colCount($pos) { + return TableMap.get($pos.node(-1)).colCount($pos.pos - $pos.start(-1)); +} +function nextCell($pos, axis, dir) { + const table = $pos.node(-1); + const map = TableMap.get(table); + const tableStart = $pos.start(-1); + const moved = map.nextCell($pos.pos - tableStart, axis, dir); + return moved == null ? null : $pos.node(0).resolve(tableStart + moved); +} +function removeColSpan(attrs, pos, n = 1) { + const result = { ...attrs, colspan: attrs.colspan - n }; + if (result.colwidth) { + result.colwidth = result.colwidth.slice(); + result.colwidth.splice(pos, n); + if (!result.colwidth.some(w => w > 0)) result.colwidth = null; + } + return result; +} +function addColSpan(attrs, pos, n = 1) { + const result = { ...attrs, colspan: attrs.colspan + n }; + if (result.colwidth) { + result.colwidth = result.colwidth.slice(); + for (let i = 0; i < n; i++) result.colwidth.splice(pos, 0, 0); + } + return result; +} +function columnIsHeader(map, table, col) { + const headerCell = tableNodeTypes(table.type.schema).header_cell; + for (let row = 0; row < map.height; row++) + if (table.nodeAt(map.map[col + row * map.width]).type != headerCell) + return false; + return true; +} + +// src/cellselection.ts +var CellSelection = class extends Selection { + // A table selection is identified by its anchor and head cells. The + // positions given to this constructor should point _before_ two + // cells in the same table. They may be the same, to select a single + // cell. + constructor($anchorCell, $headCell = $anchorCell) { + const table = $anchorCell.node(-1); + const map = TableMap.get(table); + const tableStart = $anchorCell.start(-1); + const rect = map.rectBetween( + $anchorCell.pos - tableStart, + $headCell.pos - tableStart, + ); + const doc = $anchorCell.node(0); + const cells = map + .cellsInRect(rect) + .filter(p => p != $headCell.pos - tableStart); + cells.unshift($headCell.pos - tableStart); + const ranges = cells.map(pos => { + const cell = table.nodeAt(pos); + if (!cell) { + throw RangeError(`No cell with offset ${pos} found`); + } + const from = tableStart + pos + 1; + return new SelectionRange( + doc.resolve(from), + doc.resolve(from + cell.content.size), + ); + }); + super(ranges[0].$from, ranges[0].$to, ranges); + this.$anchorCell = $anchorCell; + this.$headCell = $headCell; + } + map(doc, mapping) { + const $anchorCell = doc.resolve(mapping.map(this.$anchorCell.pos)); + const $headCell = doc.resolve(mapping.map(this.$headCell.pos)); + if ( + pointsAtCell($anchorCell) && + pointsAtCell($headCell) && + inSameTable($anchorCell, $headCell) + ) { + const tableChanged = this.$anchorCell.node(-1) != $anchorCell.node(-1); + if (tableChanged && this.isRowSelection()) + return CellSelection.rowSelection($anchorCell, $headCell); + else if (tableChanged && this.isColSelection()) + return CellSelection.colSelection($anchorCell, $headCell); + else return new CellSelection($anchorCell, $headCell); + } + return TextSelection.between($anchorCell, $headCell); + } + // Returns a rectangular slice of table rows containing the selected + // cells. + content() { + const table = this.$anchorCell.node(-1); + const map = TableMap.get(table); + const tableStart = this.$anchorCell.start(-1); + const rect = map.rectBetween( + this.$anchorCell.pos - tableStart, + this.$headCell.pos - tableStart, + ); + const seen = {}; + const rows = []; + for (let row = rect.top; row < rect.bottom; row++) { + const rowContent = []; + for ( + let index = row * map.width + rect.left, col = rect.left; + col < rect.right; + col++, index++ + ) { + const pos = map.map[index]; + if (seen[pos]) continue; + seen[pos] = true; + const cellRect = map.findCell(pos); + let cell = table.nodeAt(pos); + if (!cell) { + throw RangeError(`No cell with offset ${pos} found`); + } + const extraLeft = rect.left - cellRect.left; + const extraRight = cellRect.right - rect.right; + if (extraLeft > 0 || extraRight > 0) { + let attrs = cell.attrs; + if (extraLeft > 0) { + attrs = removeColSpan(attrs, 0, extraLeft); + } + if (extraRight > 0) { + attrs = removeColSpan( + attrs, + attrs.colspan - extraRight, + extraRight, + ); + } + if (cellRect.left < rect.left) { + cell = cell.type.createAndFill(attrs); + if (!cell) { + throw RangeError( + `Could not create cell with attrs ${JSON.stringify(attrs)}`, + ); + } + } else { + cell = cell.type.create(attrs, cell.content); + } + } + if (cellRect.top < rect.top || cellRect.bottom > rect.bottom) { + const attrs = { + ...cell.attrs, + rowspan: + Math.min(cellRect.bottom, rect.bottom) - + Math.max(cellRect.top, rect.top), + }; + if (cellRect.top < rect.top) { + cell = cell.type.createAndFill(attrs); + } else { + cell = cell.type.create(attrs, cell.content); + } + } + rowContent.push(cell); + } + rows.push(table.child(row).copy(Fragment.from(rowContent))); + } + const fragment = + this.isColSelection() && this.isRowSelection() ? table : rows; + return new Slice(Fragment.from(fragment), 1, 1); + } + replace(tr, content = Slice.empty) { + const mapFrom = tr.steps.length, + ranges = this.ranges; + for (let i = 0; i < ranges.length; i++) { + const { $from, $to } = ranges[i], + mapping = tr.mapping.slice(mapFrom); + tr.replace( + mapping.map($from.pos), + mapping.map($to.pos), + i ? Slice.empty : content, + ); + } + const sel = Selection.findFrom( + tr.doc.resolve(tr.mapping.slice(mapFrom).map(this.to)), + -1, + ); + if (sel) tr.setSelection(sel); + } + replaceWith(tr, node) { + this.replace(tr, new Slice(Fragment.from(node), 0, 0)); + } + forEachCell(f) { + const table = this.$anchorCell.node(-1); + const map = TableMap.get(table); + const tableStart = this.$anchorCell.start(-1); + const cells = map.cellsInRect( + map.rectBetween( + this.$anchorCell.pos - tableStart, + this.$headCell.pos - tableStart, + ), + ); + for (let i = 0; i < cells.length; i++) { + f(table.nodeAt(cells[i]), tableStart + cells[i]); + } + } + // True if this selection goes all the way from the top to the + // bottom of the table. + isColSelection() { + const anchorTop = this.$anchorCell.index(-1); + const headTop = this.$headCell.index(-1); + if (Math.min(anchorTop, headTop) > 0) return false; + const anchorBottom = anchorTop + this.$anchorCell.nodeAfter.attrs.rowspan; + const headBottom = headTop + this.$headCell.nodeAfter.attrs.rowspan; + return ( + Math.max(anchorBottom, headBottom) == this.$headCell.node(-1).childCount + ); + } + // Returns the smallest column selection that covers the given anchor + // and head cell. + static colSelection($anchorCell, $headCell = $anchorCell) { + const table = $anchorCell.node(-1); + const map = TableMap.get(table); + const tableStart = $anchorCell.start(-1); + const anchorRect = map.findCell($anchorCell.pos - tableStart); + const headRect = map.findCell($headCell.pos - tableStart); + const doc = $anchorCell.node(0); + if (anchorRect.top <= headRect.top) { + if (anchorRect.top > 0) + $anchorCell = doc.resolve(tableStart + map.map[anchorRect.left]); + if (headRect.bottom < map.height) + $headCell = doc.resolve( + tableStart + + map.map[map.width * (map.height - 1) + headRect.right - 1], + ); + } else { + if (headRect.top > 0) + $headCell = doc.resolve(tableStart + map.map[headRect.left]); + if (anchorRect.bottom < map.height) + $anchorCell = doc.resolve( + tableStart + + map.map[map.width * (map.height - 1) + anchorRect.right - 1], + ); + } + return new CellSelection($anchorCell, $headCell); + } + // True if this selection goes all the way from the left to the + // right of the table. + isRowSelection() { + const table = this.$anchorCell.node(-1); + const map = TableMap.get(table); + const tableStart = this.$anchorCell.start(-1); + const anchorLeft = map.colCount(this.$anchorCell.pos - tableStart); + const headLeft = map.colCount(this.$headCell.pos - tableStart); + if (Math.min(anchorLeft, headLeft) > 0) return false; + const anchorRight = anchorLeft + this.$anchorCell.nodeAfter.attrs.colspan; + const headRight = headLeft + this.$headCell.nodeAfter.attrs.colspan; + return Math.max(anchorRight, headRight) == map.width; + } + eq(other) { + return ( + other instanceof CellSelection && + other.$anchorCell.pos == this.$anchorCell.pos && + other.$headCell.pos == this.$headCell.pos + ); + } + // Returns the smallest row selection that covers the given anchor + // and head cell. + static rowSelection($anchorCell, $headCell = $anchorCell) { + const table = $anchorCell.node(-1); + const map = TableMap.get(table); + const tableStart = $anchorCell.start(-1); + const anchorRect = map.findCell($anchorCell.pos - tableStart); + const headRect = map.findCell($headCell.pos - tableStart); + const doc = $anchorCell.node(0); + if (anchorRect.left <= headRect.left) { + if (anchorRect.left > 0) + $anchorCell = doc.resolve( + tableStart + map.map[anchorRect.top * map.width], + ); + if (headRect.right < map.width) + $headCell = doc.resolve( + tableStart + map.map[map.width * (headRect.top + 1) - 1], + ); + } else { + if (headRect.left > 0) + $headCell = doc.resolve(tableStart + map.map[headRect.top * map.width]); + if (anchorRect.right < map.width) + $anchorCell = doc.resolve( + tableStart + map.map[map.width * (anchorRect.top + 1) - 1], + ); + } + return new CellSelection($anchorCell, $headCell); + } + toJSON() { + return { + type: 'cell', + anchor: this.$anchorCell.pos, + head: this.$headCell.pos, + }; + } + static fromJSON(doc, json) { + return new CellSelection(doc.resolve(json.anchor), doc.resolve(json.head)); + } + static create(doc, anchorCell, headCell = anchorCell) { + return new CellSelection(doc.resolve(anchorCell), doc.resolve(headCell)); + } + getBookmark() { + return new CellBookmark(this.$anchorCell.pos, this.$headCell.pos); + } +}; +CellSelection.prototype.visible = false; +Selection.jsonID('cell', CellSelection); +var CellBookmark = class { + constructor(anchor, head) { + this.anchor = anchor; + this.head = head; + } + map(mapping) { + return new CellBookmark(mapping.map(this.anchor), mapping.map(this.head)); + } + resolve(doc) { + const $anchorCell = doc.resolve(this.anchor), + $headCell = doc.resolve(this.head); + if ( + $anchorCell.parent.type.spec.tableRole == 'row' && + $headCell.parent.type.spec.tableRole == 'row' && + $anchorCell.index() < $anchorCell.parent.childCount && + $headCell.index() < $headCell.parent.childCount && + inSameTable($anchorCell, $headCell) + ) + return new CellSelection($anchorCell, $headCell); + else return Selection.near($headCell, 1); + } +}; +function drawCellSelection(state) { + if (!(state.selection instanceof CellSelection)) return null; + const cells = []; + state.selection.forEachCell((node, pos) => { + cells.push( + Decoration.node(pos, pos + node.nodeSize, { class: 'selectedCell' }), + ); + }); + return DecorationSet.create(state.doc, cells); +} +function isCellBoundarySelection({ $from, $to }) { + if ($from.pos == $to.pos || $from.pos < $from.pos - 6) return false; + let afterFrom = $from.pos; + let beforeTo = $to.pos; + let depth = $from.depth; + for (; depth >= 0; depth--, afterFrom++) + if ($from.after(depth + 1) < $from.end(depth)) break; + for (let d = $to.depth; d >= 0; d--, beforeTo--) + if ($to.before(d + 1) > $to.start(d)) break; + return ( + afterFrom == beforeTo && + /row|table/.test($from.node(depth).type.spec.tableRole) + ); +} +function isTextSelectionAcrossCells({ $from, $to }) { + let fromCellBoundaryNode; + let toCellBoundaryNode; + for (let i = $from.depth; i > 0; i--) { + const node = $from.node(i); + if ( + node.type.spec.tableRole === 'cell' || + node.type.spec.tableRole === 'header_cell' + ) { + fromCellBoundaryNode = node; + break; + } + } + for (let i = $to.depth; i > 0; i--) { + const node = $to.node(i); + if ( + node.type.spec.tableRole === 'cell' || + node.type.spec.tableRole === 'header_cell' + ) { + toCellBoundaryNode = node; + break; + } + } + return fromCellBoundaryNode !== toCellBoundaryNode && $to.parentOffset === 0; +} +function normalizeSelection(state, tr, allowTableNodeSelection) { + const sel = (tr || state).selection; + const doc = (tr || state).doc; + let normalize; + let role; + if (sel instanceof NodeSelection2 && (role = sel.node.type.spec.tableRole)) { + if (role == 'cell' || role == 'header_cell') { + normalize = CellSelection.create(doc, sel.from); + } else if (role == 'row') { + const $cell = doc.resolve(sel.from + 1); + normalize = CellSelection.rowSelection($cell, $cell); + } else if (!allowTableNodeSelection) { + const map = TableMap.get(sel.node); + const start = sel.from + 1; + const lastCell = start + map.map[map.width * map.height - 1]; + normalize = CellSelection.create(doc, start + 1, lastCell); + } + } else if (sel instanceof TextSelection && isCellBoundarySelection(sel)) { + normalize = TextSelection.create(doc, sel.from); + } else if (sel instanceof TextSelection && isTextSelectionAcrossCells(sel)) { + normalize = TextSelection.create(doc, sel.$from.start(), sel.$from.end()); + } + if (normalize) (tr || (tr = state.tr)).setSelection(normalize); + return tr; +} + +// src/fixtables.ts +import { PluginKey as PluginKey2 } from 'prosemirror-state'; +var fixTablesKey = new PluginKey2('fix-tables'); +function changedDescendants(old, cur, offset, f) { + const oldSize = old.childCount, + curSize = cur.childCount; + outer: for (let i = 0, j = 0; i < curSize; i++) { + const child = cur.child(i); + for (let scan = j, e = Math.min(oldSize, i + 3); scan < e; scan++) { + if (old.child(scan) == child) { + j = scan + 1; + offset += child.nodeSize; + continue outer; + } + } + f(child, offset); + if (j < oldSize && old.child(j).sameMarkup(child)) + changedDescendants(old.child(j), child, offset + 1, f); + else child.nodesBetween(0, child.content.size, f, offset + 1); + offset += child.nodeSize; + } +} +function fixTables(state, oldState) { + let tr; + const check = (node, pos) => { + if (node.type.spec.tableRole == 'table') + tr = fixTable(state, node, pos, tr); + }; + if (!oldState) state.doc.descendants(check); + else if (oldState.doc != state.doc) + changedDescendants(oldState.doc, state.doc, 0, check); + return tr; +} +function fixTable(state, table, tablePos, tr) { + const map = TableMap.get(table); + if (!map.problems) return tr; + if (!tr) tr = state.tr; + const mustAdd = []; + for (let i = 0; i < map.height; i++) mustAdd.push(0); + for (let i = 0; i < map.problems.length; i++) { + const prob = map.problems[i]; + if (prob.type == 'collision') { + const cell = table.nodeAt(prob.pos); + if (!cell) continue; + const attrs = cell.attrs; + for (let j = 0; j < attrs.rowspan; j++) mustAdd[prob.row + j] += prob.n; + tr.setNodeMarkup( + tr.mapping.map(tablePos + 1 + prob.pos), + null, + removeColSpan(attrs, attrs.colspan - prob.n, prob.n), + ); + } else if (prob.type == 'missing') { + mustAdd[prob.row] += prob.n; + } else if (prob.type == 'overlong_rowspan') { + const cell = table.nodeAt(prob.pos); + if (!cell) continue; + tr.setNodeMarkup(tr.mapping.map(tablePos + 1 + prob.pos), null, { + ...cell.attrs, + rowspan: cell.attrs.rowspan - prob.n, + }); + } else if (prob.type == 'colwidth mismatch') { + const cell = table.nodeAt(prob.pos); + if (!cell) continue; + tr.setNodeMarkup(tr.mapping.map(tablePos + 1 + prob.pos), null, { + ...cell.attrs, + colwidth: prob.colwidth, + }); + } + } + let first, last; + for (let i = 0; i < mustAdd.length; i++) + if (mustAdd[i]) { + if (first == null) first = i; + last = i; + } + for (let i = 0, pos = tablePos + 1; i < map.height; i++) { + const row = table.child(i); + const end = pos + row.nodeSize; + const add = mustAdd[i]; + if (add > 0) { + let role = 'cell'; + if (row.firstChild) { + role = row.firstChild.type.spec.tableRole; + } + const nodes = []; + for (let j = 0; j < add; j++) { + const node = tableNodeTypes(state.schema)[role].createAndFill(); + if (node) nodes.push(node); + } + const side = (i == 0 || first == i - 1) && last == i ? pos + 1 : end - 1; + tr.insert(tr.mapping.map(side), nodes); + } + pos = end; + } + return tr.setMeta(fixTablesKey, { fixTables: true }); +} + +// src/input.ts +import { Fragment as Fragment3, Slice as Slice3 } from 'prosemirror-model'; +import { + Selection as Selection2, + TextSelection as TextSelection2, +} from 'prosemirror-state'; +import { keydownHandler } from 'prosemirror-keymap'; + +// src/copypaste.ts +import { Fragment as Fragment2, Slice as Slice2 } from 'prosemirror-model'; +import { Transform } from 'prosemirror-transform'; +function pastedCells(slice) { + if (!slice.size) return null; + let { content, openStart, openEnd } = slice; + while ( + content.childCount == 1 && + ((openStart > 0 && openEnd > 0) || + content.child(0).type.spec.tableRole == 'table') + ) { + openStart--; + openEnd--; + content = content.child(0).content; + } + const first = content.child(0); + const role = first.type.spec.tableRole; + const schema = first.type.schema, + rows = []; + if (role == 'row') { + for (let i = 0; i < content.childCount; i++) { + let cells = content.child(i).content; + const left = i ? 0 : Math.max(0, openStart - 1); + const right = i < content.childCount - 1 ? 0 : Math.max(0, openEnd - 1); + if (left || right) + cells = fitSlice( + tableNodeTypes(schema).row, + new Slice2(cells, left, right), + ).content; + rows.push(cells); + } + } else if (role == 'cell' || role == 'header_cell') { + rows.push( + openStart || openEnd + ? fitSlice( + tableNodeTypes(schema).row, + new Slice2(content, openStart, openEnd), + ).content + : content, + ); + } else { + return null; + } + return ensureRectangular(schema, rows); +} +function ensureRectangular(schema, rows) { + const widths = []; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + for (let j = row.childCount - 1; j >= 0; j--) { + const { rowspan, colspan } = row.child(j).attrs; + for (let r = i; r < i + rowspan; r++) + widths[r] = (widths[r] || 0) + colspan; + } + } + let width = 0; + for (let r = 0; r < widths.length; r++) width = Math.max(width, widths[r]); + for (let r = 0; r < widths.length; r++) { + if (r >= rows.length) rows.push(Fragment2.empty); + if (widths[r] < width) { + const empty = tableNodeTypes(schema).cell.createAndFill(); + const cells = []; + for (let i = widths[r]; i < width; i++) { + cells.push(empty); + } + rows[r] = rows[r].append(Fragment2.from(cells)); + } + } + return { height: rows.length, width, rows }; +} +function fitSlice(nodeType, slice) { + const node = nodeType.createAndFill(); + const tr = new Transform(node).replace(0, node.content.size, slice); + return tr.doc; +} +function clipCells({ width, height, rows }, newWidth, newHeight) { + if (width != newWidth) { + const added = []; + const newRows = []; + for (let row = 0; row < rows.length; row++) { + const frag = rows[row], + cells = []; + for (let col = added[row] || 0, i = 0; col < newWidth; i++) { + let cell = frag.child(i % frag.childCount); + if (col + cell.attrs.colspan > newWidth) + cell = cell.type.createChecked( + removeColSpan( + cell.attrs, + cell.attrs.colspan, + col + cell.attrs.colspan - newWidth, + ), + cell.content, + ); + cells.push(cell); + col += cell.attrs.colspan; + for (let j = 1; j < cell.attrs.rowspan; j++) + added[row + j] = (added[row + j] || 0) + cell.attrs.colspan; + } + newRows.push(Fragment2.from(cells)); + } + rows = newRows; + width = newWidth; + } + if (height != newHeight) { + const newRows = []; + for (let row = 0, i = 0; row < newHeight; row++, i++) { + const cells = [], + source = rows[i % height]; + for (let j = 0; j < source.childCount; j++) { + let cell = source.child(j); + if (row + cell.attrs.rowspan > newHeight) + cell = cell.type.create( + { + ...cell.attrs, + rowspan: Math.max(1, newHeight - cell.attrs.rowspan), + }, + cell.content, + ); + cells.push(cell); + } + newRows.push(Fragment2.from(cells)); + } + rows = newRows; + height = newHeight; + } + return { width, height, rows }; +} +function growTable(tr, map, table, start, width, height, mapFrom) { + const schema = tr.doc.type.schema; + const types = tableNodeTypes(schema); + let empty; + let emptyHead; + if (width > map.width) { + for (let row = 0, rowEnd = 0; row < map.height; row++) { + const rowNode = table.child(row); + rowEnd += rowNode.nodeSize; + const cells = []; + let add; + if (rowNode.lastChild == null || rowNode.lastChild.type == types.cell) + add = empty || (empty = types.cell.createAndFill()); + else add = emptyHead || (emptyHead = types.header_cell.createAndFill()); + for (let i = map.width; i < width; i++) cells.push(add); + tr.insert(tr.mapping.slice(mapFrom).map(rowEnd - 1 + start), cells); + } + } + if (height > map.height) { + const cells = []; + for ( + let i = 0, start2 = (map.height - 1) * map.width; + i < Math.max(map.width, width); + i++ + ) { + const header = + i >= map.width + ? false + : table.nodeAt(map.map[start2 + i]).type == types.header_cell; + cells.push( + header + ? emptyHead || (emptyHead = types.header_cell.createAndFill()) + : empty || (empty = types.cell.createAndFill()), + ); + } + const emptyRow = types.row.create(null, Fragment2.from(cells)), + rows = []; + for (let i = map.height; i < height; i++) rows.push(emptyRow); + tr.insert(tr.mapping.slice(mapFrom).map(start + table.nodeSize - 2), rows); + } + return !!(empty || emptyHead); +} +function isolateHorizontal(tr, map, table, start, left, right, top, mapFrom) { + if (top == 0 || top == map.height) return false; + let found = false; + for (let col = left; col < right; col++) { + const index = top * map.width + col, + pos = map.map[index]; + if (map.map[index - map.width] == pos) { + found = true; + const cell = table.nodeAt(pos); + const { top: cellTop, left: cellLeft } = map.findCell(pos); + tr.setNodeMarkup(tr.mapping.slice(mapFrom).map(pos + start), null, { + ...cell.attrs, + rowspan: top - cellTop, + }); + tr.insert( + tr.mapping.slice(mapFrom).map(map.positionAt(top, cellLeft, table)), + cell.type.createAndFill({ + ...cell.attrs, + rowspan: cellTop + cell.attrs.rowspan - top, + }), + ); + col += cell.attrs.colspan - 1; + } + } + return found; +} +function isolateVertical(tr, map, table, start, top, bottom, left, mapFrom) { + if (left == 0 || left == map.width) return false; + let found = false; + for (let row = top; row < bottom; row++) { + const index = row * map.width + left, + pos = map.map[index]; + if (map.map[index - 1] == pos) { + found = true; + const cell = table.nodeAt(pos); + const cellLeft = map.colCount(pos); + const updatePos = tr.mapping.slice(mapFrom).map(pos + start); + tr.setNodeMarkup( + updatePos, + null, + removeColSpan( + cell.attrs, + left - cellLeft, + cell.attrs.colspan - (left - cellLeft), + ), + ); + tr.insert( + updatePos + cell.nodeSize, + cell.type.createAndFill(removeColSpan(cell.attrs, 0, left - cellLeft)), + ); + row += cell.attrs.rowspan - 1; + } + } + return found; +} +function insertCells(state, dispatch, tableStart, rect, cells) { + let table = tableStart ? state.doc.nodeAt(tableStart - 1) : state.doc; + if (!table) { + throw new Error('No table found'); + } + let map = TableMap.get(table); + const { top, left } = rect; + const right = left + cells.width, + bottom = top + cells.height; + const tr = state.tr; + let mapFrom = 0; + function recomp() { + table = tableStart ? tr.doc.nodeAt(tableStart - 1) : tr.doc; + if (!table) { + throw new Error('No table found'); + } + map = TableMap.get(table); + mapFrom = tr.mapping.maps.length; + } + if (growTable(tr, map, table, tableStart, right, bottom, mapFrom)) recomp(); + if (isolateHorizontal(tr, map, table, tableStart, left, right, top, mapFrom)) + recomp(); + if ( + isolateHorizontal(tr, map, table, tableStart, left, right, bottom, mapFrom) + ) + recomp(); + if (isolateVertical(tr, map, table, tableStart, top, bottom, left, mapFrom)) + recomp(); + if (isolateVertical(tr, map, table, tableStart, top, bottom, right, mapFrom)) + recomp(); + for (let row = top; row < bottom; row++) { + const from = map.positionAt(row, left, table), + to = map.positionAt(row, right, table); + tr.replace( + tr.mapping.slice(mapFrom).map(from + tableStart), + tr.mapping.slice(mapFrom).map(to + tableStart), + new Slice2(cells.rows[row - top], 0, 0), + ); + } + recomp(); + tr.setSelection( + new CellSelection( + tr.doc.resolve(tableStart + map.positionAt(top, left, table)), + tr.doc.resolve(tableStart + map.positionAt(bottom - 1, right - 1, table)), + ), + ); + dispatch(tr); +} + +// src/input.ts +var handleKeyDown = keydownHandler({ + ArrowLeft: arrow('horiz', -1), + ArrowRight: arrow('horiz', 1), + ArrowUp: arrow('vert', -1), + ArrowDown: arrow('vert', 1), + 'Shift-ArrowLeft': shiftArrow('horiz', -1), + 'Shift-ArrowRight': shiftArrow('horiz', 1), + 'Shift-ArrowUp': shiftArrow('vert', -1), + 'Shift-ArrowDown': shiftArrow('vert', 1), + Backspace: deleteCellSelection, + 'Mod-Backspace': deleteCellSelection, + Delete: deleteCellSelection, + 'Mod-Delete': deleteCellSelection, +}); +function maybeSetSelection(state, dispatch, selection) { + if (selection.eq(state.selection)) return false; + if (dispatch) dispatch(state.tr.setSelection(selection).scrollIntoView()); + return true; +} +function arrow(axis, dir) { + return (state, dispatch, view) => { + if (!view) return false; + const sel = state.selection; + if (sel instanceof CellSelection) { + return maybeSetSelection( + state, + dispatch, + Selection2.near(sel.$headCell, dir), + ); + } + if (axis != 'horiz' && !sel.empty) return false; + const end = atEndOfCell(view, axis, dir); + if (end == null) return false; + if (axis == 'horiz') { + return maybeSetSelection( + state, + dispatch, + Selection2.near(state.doc.resolve(sel.head + dir), dir), + ); + } else { + const $cell = state.doc.resolve(end); + const $next = nextCell($cell, axis, dir); + let newSel; + if ($next) newSel = Selection2.near($next, 1); + else if (dir < 0) + newSel = Selection2.near(state.doc.resolve($cell.before(-1)), -1); + else newSel = Selection2.near(state.doc.resolve($cell.after(-1)), 1); + return maybeSetSelection(state, dispatch, newSel); + } + }; +} +function shiftArrow(axis, dir) { + return (state, dispatch, view) => { + if (!view) return false; + const sel = state.selection; + let cellSel; + if (sel instanceof CellSelection) { + cellSel = sel; + } else { + const end = atEndOfCell(view, axis, dir); + if (end == null) return false; + cellSel = new CellSelection(state.doc.resolve(end)); + } + const $head = nextCell(cellSel.$headCell, axis, dir); + if (!$head) return false; + return maybeSetSelection( + state, + dispatch, + new CellSelection(cellSel.$anchorCell, $head), + ); + }; +} +function deleteCellSelection(state, dispatch) { + const sel = state.selection; + if (!(sel instanceof CellSelection)) return false; + if (dispatch) { + const tr = state.tr; + const baseContent = tableNodeTypes(state.schema).cell.createAndFill() + .content; + sel.forEachCell((cell, pos) => { + if (!cell.content.eq(baseContent)) + tr.replace( + tr.mapping.map(pos + 1), + tr.mapping.map(pos + cell.nodeSize - 1), + new Slice3(baseContent, 0, 0), + ); + }); + if (tr.docChanged) dispatch(tr); + } + return true; +} +function handleTripleClick(view, pos) { + const doc = view.state.doc, + $cell = cellAround(doc.resolve(pos)); + if (!$cell) return false; + view.dispatch(view.state.tr.setSelection(new CellSelection($cell))); + return true; +} +function handlePaste(view, _, slice) { + if (!isInTable(view.state)) return false; + let cells = pastedCells(slice); + const sel = view.state.selection; + if (sel instanceof CellSelection) { + if (!cells) + cells = { + width: 1, + height: 1, + rows: [ + Fragment3.from( + fitSlice(tableNodeTypes(view.state.schema).cell, slice), + ), + ], + }; + const table = sel.$anchorCell.node(-1); + const start = sel.$anchorCell.start(-1); + const rect = TableMap.get(table).rectBetween( + sel.$anchorCell.pos - start, + sel.$headCell.pos - start, + ); + cells = clipCells(cells, rect.right - rect.left, rect.bottom - rect.top); + insertCells(view.state, view.dispatch, start, rect, cells); + return true; + } else if (cells) { + const $cell = selectionCell(view.state); + const start = $cell.start(-1); + insertCells( + view.state, + view.dispatch, + start, + TableMap.get($cell.node(-1)).findCell($cell.pos - start), + cells, + ); + return true; + } else { + return false; + } +} +function handleMouseDown(view, startEvent) { + var _a; + if (startEvent.ctrlKey || startEvent.metaKey) return; + const startDOMCell = domInCell(view, startEvent.target); + let $anchor; + if (startEvent.shiftKey && view.state.selection instanceof CellSelection) { + setCellSelection(view.state.selection.$anchorCell, startEvent); + startEvent.preventDefault(); + } else if ( + startEvent.shiftKey && + startDOMCell && + ($anchor = cellAround(view.state.selection.$anchor)) != null && + ((_a = cellUnderMouse(view, startEvent)) == null ? void 0 : _a.pos) != + $anchor.pos + ) { + setCellSelection($anchor, startEvent); + startEvent.preventDefault(); + } else if (!startDOMCell) { + return; + } + function setCellSelection($anchor2, event) { + let $head = cellUnderMouse(view, event); + const starting = tableEditingKey.getState(view.state) == null; + if (!$head || !inSameTable($anchor2, $head)) { + if (starting) $head = $anchor2; + else return; + } + const selection = new CellSelection($anchor2, $head); + if (starting || !view.state.selection.eq(selection)) { + const tr = view.state.tr.setSelection(selection); + if (starting) tr.setMeta(tableEditingKey, $anchor2.pos); + view.dispatch(tr); + } + } + function stop() { + view.root.removeEventListener('mouseup', stop); + view.root.removeEventListener('dragstart', stop); + view.root.removeEventListener('mousemove', move); + if (tableEditingKey.getState(view.state) != null) + view.dispatch(view.state.tr.setMeta(tableEditingKey, -1)); + } + function move(_event) { + const event = _event; + const anchor = tableEditingKey.getState(view.state); + let $anchor2; + if (anchor != null) { + $anchor2 = view.state.doc.resolve(anchor); + } else if (domInCell(view, event.target) != startDOMCell) { + $anchor2 = cellUnderMouse(view, startEvent); + if (!$anchor2) return stop(); + } + if ($anchor2) setCellSelection($anchor2, event); + } + view.root.addEventListener('mouseup', stop); + view.root.addEventListener('dragstart', stop); + view.root.addEventListener('mousemove', move); +} +function atEndOfCell(view, axis, dir) { + if (!(view.state.selection instanceof TextSelection2)) return null; + const { $head } = view.state.selection; + for (let d = $head.depth - 1; d >= 0; d--) { + const parent = $head.node(d), + index = dir < 0 ? $head.index(d) : $head.indexAfter(d); + if (index != (dir < 0 ? 0 : parent.childCount)) return null; + if ( + parent.type.spec.tableRole == 'cell' || + parent.type.spec.tableRole == 'header_cell' + ) { + const cellPos = $head.before(d); + const dirStr = + axis == 'vert' ? (dir > 0 ? 'down' : 'up') : dir > 0 ? 'right' : 'left'; + return view.endOfTextblock(dirStr) ? cellPos : null; + } + } + return null; +} +function domInCell(view, dom) { + for (; dom && dom != view.dom; dom = dom.parentNode) { + if (dom.nodeName == 'TD' || dom.nodeName == 'TH') { + return dom; + } + } + return null; +} +function cellUnderMouse(view, event) { + const mousePos = view.posAtCoords({ + left: event.clientX, + top: event.clientY, + }); + if (!mousePos) return null; + return mousePos ? cellAround(view.state.doc.resolve(mousePos.pos)) : null; +} + +// src/columnresizing.ts +import { Plugin, PluginKey as PluginKey3 } from 'prosemirror-state'; +import { + Decoration as Decoration2, + DecorationSet as DecorationSet2, +} from 'prosemirror-view'; + +// src/tableview.ts +var TableView = class { + 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')); + updateColumnsOnResize(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; + updateColumnsOnResize(node, this.colgroup, this.table, this.cellMinWidth); + return true; + } + ignoreMutation(record) { + return ( + record.type == 'attributes' && + (record.target == this.table || this.colgroup.contains(record.target)) + ); + } +}; +function updateColumnsOnResize( + node, + colgroup, + table, + cellMinWidth, + overrideCol, + overrideValue, +) { + var _a; + let totalWidth = 0; + let fixedWidth = true; + let nextDOM = colgroup.firstChild; + const row = node.firstChild; + if (!row) return; + for (let i = 0, col = 0; i < row.childCount; i++) { + const { colspan, colwidth } = row.child(i).attrs; + for (let j = 0; j < colspan; j++, col++) { + const hasWidth = + overrideCol == col ? overrideValue : colwidth && colwidth[j]; + const cssWidth = hasWidth ? hasWidth + 'px' : ''; + totalWidth += hasWidth || cellMinWidth; + if (!hasWidth) fixedWidth = false; + if (!nextDOM) { + colgroup.appendChild( + document.createElement('col'), + ).style.width = cssWidth; + } else { + if (nextDOM.style.width != cssWidth) nextDOM.style.width = cssWidth; + nextDOM = nextDOM.nextSibling; + } + } + } + while (nextDOM) { + const after = nextDOM.nextSibling; + (_a = nextDOM.parentNode) == null ? 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'; + } +} + +// src/columnresizing.ts +var columnResizingPluginKey = new PluginKey3('tableColumnResizing'); +function columnResizing({ + handleWidth = 5, + cellMinWidth = 25, + View = TableView, + lastColumnResizable = true, +} = {}) { + const plugin = new Plugin({ + key: columnResizingPluginKey, + state: { + init(_, state) { + plugin.spec.props.nodeViews[tableNodeTypes(state.schema).table.name] = ( + node, + view, + ) => new View(node, cellMinWidth, view); + return new ResizeState(-1, false); + }, + apply(tr, prev) { + return prev.apply(tr); + }, + }, + props: { + attributes: state => { + const pluginState = columnResizingPluginKey.getState(state); + return pluginState && pluginState.activeHandle > -1 + ? { class: 'resize-cursor' } + : {}; + }, + handleDOMEvents: { + mousemove: (view, event) => { + handleMouseMove( + view, + event, + handleWidth, + cellMinWidth, + lastColumnResizable, + ); + }, + mouseleave: view => { + handleMouseLeave(view); + }, + mousedown: (view, event) => { + handleMouseDown2(view, event, cellMinWidth); + }, + }, + decorations: state => { + const pluginState = columnResizingPluginKey.getState(state); + if (pluginState && pluginState.activeHandle > -1) { + return handleDecorations(state, pluginState.activeHandle); + } + }, + nodeViews: {}, + }, + }); + return plugin; +} +var ResizeState = class { + constructor(activeHandle, dragging) { + this.activeHandle = activeHandle; + this.dragging = dragging; + } + apply(tr) { + const state = this; + const action = tr.getMeta(columnResizingPluginKey); + if (action && action.setHandle != null) + return new ResizeState(action.setHandle, false); + if (action && action.setDragging !== void 0) + return new ResizeState(state.activeHandle, action.setDragging); + if (state.activeHandle > -1 && tr.docChanged) { + let handle = tr.mapping.map(state.activeHandle, -1); + if (!pointsAtCell(tr.doc.resolve(handle))) { + handle = -1; + } + return new ResizeState(handle, state.dragging); + } + return state; + } +}; +function handleMouseMove( + view, + event, + handleWidth, + cellMinWidth, + lastColumnResizable, +) { + const pluginState = columnResizingPluginKey.getState(view.state); + if (!pluginState) return; + if (!pluginState.dragging) { + const target = domCellAround(event.target); + let cell = -1; + if (target) { + const { left, right } = target.getBoundingClientRect(); + if (event.clientX - left <= handleWidth) + cell = edgeCell(view, event, 'left', handleWidth); + else if (right - event.clientX <= handleWidth) + cell = edgeCell(view, event, 'right', handleWidth); + } + if (cell != pluginState.activeHandle) { + if (!lastColumnResizable && cell !== -1) { + const $cell = view.state.doc.resolve(cell); + const table = $cell.node(-1); + const map = TableMap.get(table); + const tableStart = $cell.start(-1); + const col = + map.colCount($cell.pos - tableStart) + + $cell.nodeAfter.attrs.colspan - + 1; + if (col == map.width - 1) { + return; + } + } + updateHandle(view, cell); + } + } +} +function handleMouseLeave(view) { + const pluginState = columnResizingPluginKey.getState(view.state); + if (pluginState && pluginState.activeHandle > -1 && !pluginState.dragging) + updateHandle(view, -1); +} +function handleMouseDown2(view, event, cellMinWidth) { + const pluginState = columnResizingPluginKey.getState(view.state); + if (!pluginState || pluginState.activeHandle == -1 || pluginState.dragging) + return false; + const cell = view.state.doc.nodeAt(pluginState.activeHandle); + const width = currentColWidth(view, pluginState.activeHandle, cell.attrs); + view.dispatch( + view.state.tr.setMeta(columnResizingPluginKey, { + setDragging: { startX: event.clientX, startWidth: width }, + }), + ); + function finish(event2) { + window.removeEventListener('mouseup', finish); + window.removeEventListener('mousemove', move); + const pluginState2 = columnResizingPluginKey.getState(view.state); + if (pluginState2 == null ? void 0 : pluginState2.dragging) { + updateColumnWidth( + view, + pluginState2.activeHandle, + draggedWidth(pluginState2.dragging, event2, cellMinWidth), + ); + view.dispatch( + view.state.tr.setMeta(columnResizingPluginKey, { setDragging: null }), + ); + } + } + function move(event2) { + if (!event2.which) return finish(event2); + const pluginState2 = columnResizingPluginKey.getState(view.state); + if (!pluginState2) return; + if (pluginState2.dragging) { + const dragged = draggedWidth(pluginState2.dragging, event2, cellMinWidth); + displayColumnWidth( + view, + pluginState2.activeHandle, + dragged, + cellMinWidth, + ); + } + } + window.addEventListener('mouseup', finish); + window.addEventListener('mousemove', move); + event.preventDefault(); + return true; +} +function currentColWidth(view, cellPos, { colspan, colwidth }) { + const width = colwidth && colwidth[colwidth.length - 1]; + if (width) return width; + const dom = view.domAtPos(cellPos); + const node = dom.node.childNodes[dom.offset]; + let domWidth = node.offsetWidth, + parts = colspan; + if (colwidth) { + for (let i = 0; i < colspan; i++) + if (colwidth[i]) { + domWidth -= colwidth[i]; + parts--; + } + } + return domWidth / parts; +} +function domCellAround(target) { + while (target && target.nodeName != 'TD' && target.nodeName != 'TH') + target = + target.classList && target.classList.contains('ProseMirror') + ? null + : target.parentNode; + return target; +} +function edgeCell(view, event, side, handleWidth) { + const offset = side == 'right' ? -handleWidth : handleWidth; + const found = view.posAtCoords({ + left: event.clientX + offset, + top: event.clientY, + }); + if (!found) return -1; + const { pos } = found; + const $cell = cellAround(view.state.doc.resolve(pos)); + if (!$cell) return -1; + if (side == 'right') return $cell.pos; + const map = TableMap.get($cell.node(-1)), + start = $cell.start(-1); + const index = map.map.indexOf($cell.pos - start); + return index % map.width == 0 ? -1 : start + map.map[index - 1]; +} +function draggedWidth(dragging, event, cellMinWidth) { + const offset = event.clientX - dragging.startX; + return Math.max(cellMinWidth, dragging.startWidth + offset); +} +function updateHandle(view, value) { + view.dispatch( + view.state.tr.setMeta(columnResizingPluginKey, { setHandle: value }), + ); +} +function updateColumnWidth(view, cell, width) { + const $cell = view.state.doc.resolve(cell); + const table = $cell.node(-1), + map = TableMap.get(table), + start = $cell.start(-1); + const col = + map.colCount($cell.pos - start) + $cell.nodeAfter.attrs.colspan - 1; + const tr = view.state.tr; + for (let row = 0; row < map.height; row++) { + const mapIndex = row * map.width + col; + if (row && map.map[mapIndex] == map.map[mapIndex - map.width]) continue; + const pos = map.map[mapIndex]; + const attrs = table.nodeAt(pos).attrs; + const index = attrs.colspan == 1 ? 0 : col - map.colCount(pos); + if (attrs.colwidth && attrs.colwidth[index] == width) continue; + const colwidth = attrs.colwidth + ? attrs.colwidth.slice() + : zeroes(attrs.colspan); + colwidth[index] = width; + tr.setNodeMarkup(start + pos, null, { ...attrs, colwidth }); + } + if (tr.docChanged) view.dispatch(tr); +} +function displayColumnWidth(view, cell, width, cellMinWidth) { + const $cell = view.state.doc.resolve(cell); + const table = $cell.node(-1), + start = $cell.start(-1); + const col = + TableMap.get(table).colCount($cell.pos - start) + + $cell.nodeAfter.attrs.colspan - + 1; + let dom = view.domAtPos($cell.start(-1)).node; + while (dom && dom.nodeName != 'TABLE') { + dom = dom.parentNode; + } + if (!dom) return; + updateColumnsOnResize(table, dom.firstChild, dom, cellMinWidth, col, width); +} +function zeroes(n) { + return Array(n).fill(0); +} +function handleDecorations(state, cell) { + const decorations = []; + const $cell = state.doc.resolve(cell); + const table = $cell.node(-1); + if (!table) { + return DecorationSet2.empty; + } + const map = TableMap.get(table); + const start = $cell.start(-1); + const col = map.colCount($cell.pos - start) + $cell.nodeAfter.attrs.colspan; + for (let row = 0; row < map.height; row++) { + const index = col + row * map.width - 1; + if ( + (col == map.width || map.map[index] != map.map[index + 1]) && + (row == 0 || map.map[index] != map.map[index - map.width]) + ) { + const cellPos = map.map[index]; + const pos = start + cellPos + table.nodeAt(cellPos).nodeSize - 1; + const dom = document.createElement('div'); + dom.className = 'column-resize-handle'; + decorations.push(Decoration2.widget(pos, dom)); + } + } + return DecorationSet2.create(state.doc, decorations); +} + +// src/commands.ts +import { Fragment as Fragment4 } from 'prosemirror-model'; +import { TextSelection as TextSelection3 } from 'prosemirror-state'; +function selectedRect(state) { + const sel = state.selection; + const $pos = selectionCell(state); + const table = $pos.node(-1); + const tableStart = $pos.start(-1); + const map = TableMap.get(table); + const rect = + sel instanceof CellSelection + ? map.rectBetween( + sel.$anchorCell.pos - tableStart, + sel.$headCell.pos - tableStart, + ) + : map.findCell($pos.pos - tableStart); + return { ...rect, tableStart, map, table }; +} +function addColumn(tr, { map, tableStart, table }, col) { + let refColumn = col > 0 ? -1 : 0; + if (columnIsHeader(map, table, col + refColumn)) { + refColumn = col == 0 || col == map.width ? null : 0; + } + for (let row = 0; row < map.height; row++) { + const index = row * map.width + col; + if (col > 0 && col < map.width && map.map[index - 1] == map.map[index]) { + const pos = map.map[index]; + const cell = table.nodeAt(pos); + tr.setNodeMarkup( + tr.mapping.map(tableStart + pos), + null, + addColSpan(cell.attrs, col - map.colCount(pos)), + ); + row += cell.attrs.rowspan - 1; + } else { + const type = + refColumn == null + ? tableNodeTypes(table.type.schema).cell + : table.nodeAt(map.map[index + refColumn]).type; + const pos = map.positionAt(row, col, table); + tr.insert(tr.mapping.map(tableStart + pos), type.createAndFill()); + } + } + return tr; +} +function addColumnBefore(state, dispatch) { + if (!isInTable(state)) return false; + if (dispatch) { + const rect = selectedRect(state); + dispatch(addColumn(state.tr, rect, rect.left)); + } + return true; +} +function addColumnAfter(state, dispatch) { + if (!isInTable(state)) return false; + if (dispatch) { + const rect = selectedRect(state); + dispatch(addColumn(state.tr, rect, rect.right)); + } + return true; +} +function removeColumn(tr, { map, table, tableStart }, col) { + const mapStart = tr.mapping.maps.length; + for (let row = 0; row < map.height; ) { + const index = row * map.width + col; + const pos = map.map[index]; + const cell = table.nodeAt(pos); + const attrs = cell.attrs; + if ( + (col > 0 && map.map[index - 1] == pos) || + (col < map.width - 1 && map.map[index + 1] == pos) + ) { + tr.setNodeMarkup( + tr.mapping.slice(mapStart).map(tableStart + pos), + null, + removeColSpan(attrs, col - map.colCount(pos)), + ); + } else { + const start = tr.mapping.slice(mapStart).map(tableStart + pos); + tr.delete(start, start + cell.nodeSize); + } + row += attrs.rowspan; + } +} +function deleteColumn(state, dispatch) { + if (!isInTable(state)) return false; + if (dispatch) { + const rect = selectedRect(state); + const tr = state.tr; + if (rect.left == 0 && rect.right == rect.map.width) return false; + for (let i = rect.right - 1; ; i--) { + removeColumn(tr, rect, i); + if (i == rect.left) break; + const table = rect.tableStart + ? tr.doc.nodeAt(rect.tableStart - 1) + : tr.doc; + if (!table) { + throw RangeError('No table found'); + } + rect.table = table; + rect.map = TableMap.get(table); + } + dispatch(tr); + } + return true; +} +function rowIsHeader(map, table, row) { + var _a; + const headerCell = tableNodeTypes(table.type.schema).header_cell; + for (let col = 0; col < map.width; col++) + if ( + ((_a = table.nodeAt(map.map[col + row * map.width])) == null + ? void 0 + : _a.type) != headerCell + ) + return false; + return true; +} +function addRow(tr, { map, tableStart, table }, row) { + var _a; + let rowPos = tableStart; + for (let i = 0; i < row; i++) rowPos += table.child(i).nodeSize; + const cells = []; + let refRow = row > 0 ? -1 : 0; + if (rowIsHeader(map, table, row + refRow)) + refRow = row == 0 || row == map.height ? null : 0; + for (let col = 0, index = map.width * row; col < map.width; col++, index++) { + if ( + row > 0 && + row < map.height && + map.map[index] == map.map[index - map.width] + ) { + const pos = map.map[index]; + const attrs = table.nodeAt(pos).attrs; + tr.setNodeMarkup(tableStart + pos, null, { + ...attrs, + rowspan: attrs.rowspan + 1, + }); + col += attrs.colspan - 1; + } else { + const type = + refRow == null + ? tableNodeTypes(table.type.schema).cell + : (_a = table.nodeAt(map.map[index + refRow * map.width])) == null + ? void 0 + : _a.type; + const node = type == null ? void 0 : type.createAndFill(); + if (node) cells.push(node); + } + } + tr.insert(rowPos, tableNodeTypes(table.type.schema).row.create(null, cells)); + return tr; +} +function addRowBefore(state, dispatch) { + if (!isInTable(state)) return false; + if (dispatch) { + const rect = selectedRect(state); + dispatch(addRow(state.tr, rect, rect.top)); + } + return true; +} +function addRowAfter(state, dispatch) { + if (!isInTable(state)) return false; + if (dispatch) { + const rect = selectedRect(state); + dispatch(addRow(state.tr, rect, rect.bottom)); + } + return true; +} +function removeRow(tr, { map, table, tableStart }, row) { + let rowPos = 0; + for (let i = 0; i < row; i++) rowPos += table.child(i).nodeSize; + const nextRow = rowPos + table.child(row).nodeSize; + const mapFrom = tr.mapping.maps.length; + tr.delete(rowPos + tableStart, nextRow + tableStart); + for (let col = 0, index = row * map.width; col < map.width; col++, index++) { + const pos = map.map[index]; + if (row > 0 && pos == map.map[index - map.width]) { + const attrs = table.nodeAt(pos).attrs; + tr.setNodeMarkup(tr.mapping.slice(mapFrom).map(pos + tableStart), null, { + ...attrs, + rowspan: attrs.rowspan - 1, + }); + col += attrs.colspan - 1; + } else if (row < map.width && pos == map.map[index + map.width]) { + const cell = table.nodeAt(pos); + const attrs = cell.attrs; + const copy = cell.type.create( + { ...attrs, rowspan: cell.attrs.rowspan - 1 }, + cell.content, + ); + const newPos = map.positionAt(row + 1, col, table); + tr.insert(tr.mapping.slice(mapFrom).map(tableStart + newPos), copy); + col += attrs.colspan - 1; + } + } +} +function deleteRow(state, dispatch) { + if (!isInTable(state)) return false; + if (dispatch) { + const rect = selectedRect(state), + tr = state.tr; + if (rect.top == 0 && rect.bottom == rect.map.height) return false; + for (let i = rect.bottom - 1; ; i--) { + removeRow(tr, rect, i); + if (i == rect.top) break; + const table = rect.tableStart + ? tr.doc.nodeAt(rect.tableStart - 1) + : tr.doc; + if (!table) { + throw RangeError('No table found'); + } + rect.table = table; + rect.map = TableMap.get(rect.table); + } + dispatch(tr); + } + return true; +} +function isEmpty(cell) { + const c = cell.content; + return ( + c.childCount == 1 && c.child(0).isTextblock && c.child(0).childCount == 0 + ); +} +function cellsOverlapRectangle({ width, height, map }, rect) { + let indexTop = rect.top * width + rect.left, + indexLeft = indexTop; + let indexBottom = (rect.bottom - 1) * width + rect.left, + indexRight = indexTop + (rect.right - rect.left - 1); + for (let i = rect.top; i < rect.bottom; i++) { + if ( + (rect.left > 0 && map[indexLeft] == map[indexLeft - 1]) || + (rect.right < width && map[indexRight] == map[indexRight + 1]) + ) + return true; + indexLeft += width; + indexRight += width; + } + for (let i = rect.left; i < rect.right; i++) { + if ( + (rect.top > 0 && map[indexTop] == map[indexTop - width]) || + (rect.bottom < height && map[indexBottom] == map[indexBottom + width]) + ) + return true; + indexTop++; + indexBottom++; + } + return false; +} +function mergeCells(state, dispatch) { + const sel = state.selection; + if ( + !(sel instanceof CellSelection) || + sel.$anchorCell.pos == sel.$headCell.pos + ) + return false; + const rect = selectedRect(state), + { map } = rect; + if (cellsOverlapRectangle(map, rect)) return false; + if (dispatch) { + const tr = state.tr; + const seen = {}; + let content = Fragment4.empty; + let mergedPos; + let mergedCell; + for (let row = rect.top; row < rect.bottom; row++) { + for (let col = rect.left; col < rect.right; col++) { + const cellPos = map.map[row * map.width + col]; + const cell = rect.table.nodeAt(cellPos); + if (seen[cellPos] || !cell) continue; + seen[cellPos] = true; + if (mergedPos == null) { + mergedPos = cellPos; + mergedCell = cell; + } else { + if (!isEmpty(cell)) content = content.append(cell.content); + const mapped = tr.mapping.map(cellPos + rect.tableStart); + tr.delete(mapped, mapped + cell.nodeSize); + } + } + } + if (mergedPos == null || mergedCell == null) { + return true; + } + tr.setNodeMarkup(mergedPos + rect.tableStart, null, { + ...addColSpan( + mergedCell.attrs, + mergedCell.attrs.colspan, + rect.right - rect.left - mergedCell.attrs.colspan, + ), + rowspan: rect.bottom - rect.top, + }); + if (content.size) { + const end = mergedPos + 1 + mergedCell.content.size; + const start = isEmpty(mergedCell) ? mergedPos + 1 : end; + tr.replaceWith(start + rect.tableStart, end + rect.tableStart, content); + } + tr.setSelection( + new CellSelection(tr.doc.resolve(mergedPos + rect.tableStart)), + ); + dispatch(tr); + } + return true; +} +function splitCell(state, dispatch) { + const nodeTypes = tableNodeTypes(state.schema); + return splitCellWithType(({ node }) => { + return nodeTypes[node.type.spec.tableRole]; + })(state, dispatch); +} +function splitCellWithType(getCellType) { + return (state, dispatch) => { + var _a; + const sel = state.selection; + let cellNode; + let cellPos; + if (!(sel instanceof CellSelection)) { + cellNode = cellWrapping(sel.$from); + if (!cellNode) return false; + cellPos = (_a = cellAround(sel.$from)) == null ? void 0 : _a.pos; + } else { + if (sel.$anchorCell.pos != sel.$headCell.pos) return false; + cellNode = sel.$anchorCell.nodeAfter; + cellPos = sel.$anchorCell.pos; + } + if (cellNode == null || cellPos == null) { + return false; + } + if (cellNode.attrs.colspan == 1 && cellNode.attrs.rowspan == 1) { + return false; + } + if (dispatch) { + let baseAttrs = cellNode.attrs; + const attrs = []; + const colwidth = baseAttrs.colwidth; + if (baseAttrs.rowspan > 1) baseAttrs = { ...baseAttrs, rowspan: 1 }; + if (baseAttrs.colspan > 1) baseAttrs = { ...baseAttrs, colspan: 1 }; + const rect = selectedRect(state), + tr = state.tr; + for (let i = 0; i < rect.right - rect.left; i++) + attrs.push( + colwidth + ? { + ...baseAttrs, + colwidth: colwidth && colwidth[i] ? [colwidth[i]] : null, + } + : baseAttrs, + ); + let lastCell; + for (let row = rect.top; row < rect.bottom; row++) { + let pos = rect.map.positionAt(row, rect.left, rect.table); + if (row == rect.top) pos += cellNode.nodeSize; + for (let col = rect.left, i = 0; col < rect.right; col++, i++) { + if (col == rect.left && row == rect.top) continue; + tr.insert( + (lastCell = tr.mapping.map(pos + rect.tableStart, 1)), + getCellType({ node: cellNode, row, col }).createAndFill(attrs[i]), + ); + } + } + tr.setNodeMarkup( + cellPos, + getCellType({ node: cellNode, row: rect.top, col: rect.left }), + attrs[0], + ); + if (sel instanceof CellSelection) + tr.setSelection( + new CellSelection( + tr.doc.resolve(sel.$anchorCell.pos), + lastCell ? tr.doc.resolve(lastCell) : void 0, + ), + ); + dispatch(tr); + } + return true; + }; +} +function setCellAttr(name, value) { + return function (state, dispatch) { + if (!isInTable(state)) return false; + const $cell = selectionCell(state); + if ($cell.nodeAfter.attrs[name] === value) return false; + if (dispatch) { + const tr = state.tr; + if (state.selection instanceof CellSelection) + state.selection.forEachCell((node, pos) => { + if (node.attrs[name] !== value) + tr.setNodeMarkup(pos, null, { + ...node.attrs, + [name]: value, + }); + }); + else + tr.setNodeMarkup($cell.pos, null, { + ...$cell.nodeAfter.attrs, + [name]: value, + }); + dispatch(tr); + } + return true; + }; +} +function deprecated_toggleHeader(type) { + return function (state, dispatch) { + if (!isInTable(state)) return false; + if (dispatch) { + const types = tableNodeTypes(state.schema); + const rect = selectedRect(state), + tr = state.tr; + const cells = rect.map.cellsInRect( + type == 'column' + ? { + left: rect.left, + top: 0, + right: rect.right, + bottom: rect.map.height, + } + : type == 'row' + ? { + left: 0, + top: rect.top, + right: rect.map.width, + bottom: rect.bottom, + } + : rect, + ); + const nodes = cells.map(pos => rect.table.nodeAt(pos)); + for (let i = 0; i < cells.length; i++) + if (nodes[i].type == types.header_cell) + tr.setNodeMarkup( + rect.tableStart + cells[i], + types.cell, + nodes[i].attrs, + ); + if (tr.steps.length == 0) + for (let i = 0; i < cells.length; i++) + tr.setNodeMarkup( + rect.tableStart + cells[i], + types.header_cell, + nodes[i].attrs, + ); + dispatch(tr); + } + return true; + }; +} +function isHeaderEnabledByType(type, rect, types) { + const cellPositions = rect.map.cellsInRect({ + left: 0, + top: 0, + right: type == 'row' ? rect.map.width : 1, + bottom: type == 'column' ? rect.map.height : 1, + }); + for (let i = 0; i < cellPositions.length; i++) { + const cell = rect.table.nodeAt(cellPositions[i]); + if (cell && cell.type !== types.header_cell) { + return false; + } + } + return true; +} +function toggleHeader(type, options) { + options = options || { useDeprecatedLogic: false }; + if (options.useDeprecatedLogic) return deprecated_toggleHeader(type); + return function (state, dispatch) { + if (!isInTable(state)) return false; + if (dispatch) { + const types = tableNodeTypes(state.schema); + const rect = selectedRect(state), + tr = state.tr; + const isHeaderRowEnabled = isHeaderEnabledByType('row', rect, types); + const isHeaderColumnEnabled = isHeaderEnabledByType( + 'column', + rect, + types, + ); + const isHeaderEnabled = + type === 'column' + ? isHeaderRowEnabled + : type === 'row' + ? isHeaderColumnEnabled + : false; + const selectionStartsAt = isHeaderEnabled ? 1 : 0; + const cellsRect = + type == 'column' + ? { + left: 0, + top: selectionStartsAt, + right: 1, + bottom: rect.map.height, + } + : type == 'row' + ? { + left: selectionStartsAt, + top: 0, + right: rect.map.width, + bottom: 1, + } + : rect; + const newType = + type == 'column' + ? isHeaderColumnEnabled + ? types.cell + : types.header_cell + : type == 'row' + ? isHeaderRowEnabled + ? types.cell + : types.header_cell + : types.cell; + rect.map.cellsInRect(cellsRect).forEach(relativeCellPos => { + const cellPos = relativeCellPos + rect.tableStart; + const cell = tr.doc.nodeAt(cellPos); + if (cell) { + tr.setNodeMarkup(cellPos, newType, cell.attrs); + } + }); + dispatch(tr); + } + return true; + }; +} +var toggleHeaderRow = toggleHeader('row', { + useDeprecatedLogic: true, +}); +var toggleHeaderColumn = toggleHeader('column', { + useDeprecatedLogic: true, +}); +var toggleHeaderCell = toggleHeader('cell', { + useDeprecatedLogic: true, +}); +function findNextCell($cell, dir) { + if (dir < 0) { + const before = $cell.nodeBefore; + if (before) return $cell.pos - before.nodeSize; + for ( + let row = $cell.index(-1) - 1, rowEnd = $cell.before(); + row >= 0; + row-- + ) { + const rowNode = $cell.node(-1).child(row); + const lastChild = rowNode.lastChild; + if (lastChild) { + return rowEnd - 1 - lastChild.nodeSize; + } + rowEnd -= rowNode.nodeSize; + } + } else { + if ($cell.index() < $cell.parent.childCount - 1) { + return $cell.pos + $cell.nodeAfter.nodeSize; + } + const table = $cell.node(-1); + for ( + let row = $cell.indexAfter(-1), rowStart = $cell.after(); + row < table.childCount; + row++ + ) { + const rowNode = table.child(row); + if (rowNode.childCount) return rowStart + 1; + rowStart += rowNode.nodeSize; + } + } + return null; +} +function goToNextCell(direction) { + return function (state, dispatch) { + if (!isInTable(state)) return false; + const cell = findNextCell(selectionCell(state), direction); + if (cell == null) return false; + if (dispatch) { + const $cell = state.doc.resolve(cell); + dispatch( + state.tr + .setSelection(TextSelection3.between($cell, moveCellForward($cell))) + .scrollIntoView(), + ); + } + return true; + }; +} +function deleteTable(state, dispatch) { + const $pos = state.selection.$anchor; + for (let d = $pos.depth; d > 0; d--) { + const node = $pos.node(d); + if (node.type.spec.tableRole == 'table') { + if (dispatch) + dispatch( + state.tr.delete($pos.before(d), $pos.after(d)).scrollIntoView(), + ); + return true; + } + } + return false; +} + +// src/index.ts +function tableEditing({ allowTableNodeSelection = false } = {}) { + return new Plugin2({ + key: tableEditingKey, + // This piece of state is used to remember when a mouse-drag + // cell-selection is happening, so that it can continue even as + // transactions (which might move its anchor cell) come in. + state: { + init() { + return null; + }, + apply(tr, cur) { + const set = tr.getMeta(tableEditingKey); + if (set != null) return set == -1 ? null : set; + if (cur == null || !tr.docChanged) return cur; + const { deleted, pos } = tr.mapping.mapResult(cur); + return deleted ? null : pos; + }, + }, + props: { + decorations: drawCellSelection, + handleDOMEvents: { + mousedown: handleMouseDown, + }, + createSelectionBetween(view) { + return tableEditingKey.getState(view.state) != null + ? view.state.selection + : null; + }, + handleTripleClick, + handleKeyDown, + handlePaste, + }, + appendTransaction(_, oldState, state) { + return normalizeSelection( + state, + fixTables(state, oldState), + allowTableNodeSelection, + ); + }, + }); +} +export { + CellBookmark, + CellSelection, + ResizeState, + TableMap, + TableView, + clipCells as __clipCells, + insertCells as __insertCells, + pastedCells as __pastedCells, + addColSpan, + addColumn, + addColumnAfter, + addColumnBefore, + addRow, + addRowAfter, + addRowBefore, + cellAround, + colCount, + columnIsHeader, + columnResizing, + columnResizingPluginKey, + deleteColumn, + deleteRow, + deleteTable, + findCell, + fixTables, + fixTablesKey, + goToNextCell, + handlePaste, + inSameTable, + isInTable, + mergeCells, + moveCellForward, + nextCell, + pointsAtCell, + removeColSpan, + removeColumn, + removeRow, + rowIsHeader, + selectedRect, + selectionCell, + setCellAttr, + splitCell, + splitCellWithType, + tableEditing, + tableEditingKey, + tableNodeTypes, + tableNodes, + toggleHeader, + toggleHeaderCell, + toggleHeaderColumn, + toggleHeaderRow, + updateColumnsOnResize, +}; diff --git a/yarn.lock b/yarn.lock index 3704c0308..e4804eabb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12053,7 +12053,7 @@ prosemirror-keymap@1.2.1: prosemirror-state "^1.0.0" w3c-keyname "^2.2.0" -prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2: +prosemirror-keymap@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.1.4.tgz#8b481bf8389a5ac40d38dbd67ec3da2c7eac6a6d" integrity sha512-Al8cVUOnDFL4gcI5IDlG6xbZ0aOD/i3B17VT+1JbHWDguCgt/lBHVTHUBcKvvbSg6+q/W4Nj1Fu6bwZSca3xjg== @@ -12061,7 +12061,7 @@ prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2: prosemirror-state "^1.0.0" w3c-keyname "^2.2.0" -prosemirror-model@1.19.0, prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.2.0, prosemirror-model@^1.8.1: +prosemirror-model@1.19.0, prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.2.0: version "1.19.0" resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.19.0.tgz#d7ad9a65ada0bb12196f64fe0dd4fc392c841c29" integrity sha512-/CvFGJnwc41EJSfDkQLly1cAJJJmBpZwwUJtwZPTjY2RqZJfM8HVbCreOY/jti8wTRbVyjagcylyGoeJH/g/3w== @@ -12092,7 +12092,7 @@ prosemirror-schema-list@^1.0.0: prosemirror-model "^1.0.0" prosemirror-transform "^1.0.0" -prosemirror-state@1.4.2, prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1: +prosemirror-state@1.4.2, prosemirror-state@^1.0.0, prosemirror-state@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.2.tgz#f93bd8a33a4454efab917ba9b738259d828db7e5" integrity sha512-puuzLD2mz/oTdfgd8msFbe0A42j5eNudKAAPDB0+QJRw8cO1ygjLmhLrg9RvDpf87Dkd6D4t93qdef00KKNacQ== @@ -12101,17 +12101,6 @@ prosemirror-state@1.4.2, prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, pro prosemirror-transform "^1.0.0" prosemirror-view "^1.27.0" -prosemirror-tables@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.3.4.tgz#0b7cc16d49f90c5b834c9f29291c545478ce9ab0" - integrity sha512-z6uLSQ1BLC3rgbGwZmpfb+xkdvD7W/UOsURDfognZFYaTtc0gsk7u/t71Yijp2eLflVpffMk6X0u0+u+MMDvIw== - dependencies: - prosemirror-keymap "^1.1.2" - prosemirror-model "^1.8.1" - prosemirror-state "^1.3.1" - prosemirror-transform "^1.2.1" - prosemirror-view "^1.13.3" - prosemirror-test-builder@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/prosemirror-test-builder/-/prosemirror-test-builder-1.0.3.tgz#9706b6e021efe82830b479797923d835f4ca12a3" @@ -12121,7 +12110,7 @@ prosemirror-test-builder@^1.0.1: prosemirror-schema-basic "^1.0.0" prosemirror-schema-list "^1.0.0" -prosemirror-transform@1.7.1, prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.2.1: +prosemirror-transform@1.7.1, prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0: version "1.7.1" resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.7.1.tgz#b516e818c3add0bdf960f4ca8ccb9d057a3ba21b" integrity sha512-VteoifAfpt46z0yEt6Fc73A5OID9t/y2QIeR5MgxEwTuitadEunD/V0c9jQW8ziT8pbFM54uTzRLJ/nLuQjMxg== @@ -12135,7 +12124,7 @@ prosemirror-transform@^1.7.3: dependencies: prosemirror-model "^1.0.0" -prosemirror-view@1.30.2, prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.5.1: +prosemirror-view@1.30.2, prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.5.1: version "1.30.2" resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.30.2.tgz#57a9d15c5baa454f0d0f4a3028ddbd9be1e8ed9b" integrity sha512-nTNzZvalQf9kHeEyO407LiV6DoOs/pXsid88UqW9Vvybo4ozJW2PJhkfZUxCUF1hR/9vJLdhxX84wuw9P9HsXA== -- GitLab