| Server IP : 34.87.99.72 / Your IP : 216.73.217.31 Web Server : Apache/2.4.46 (Unix) OpenSSL/1.1.1n System : Linux zlock-bitnami-lamp-prod-v2-vm 4.19.0-16-cloud-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64 User : bitnami ( 1000) PHP Version : 7.4.18 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /home/bitnami/htdocs/app/node_modules/prosemirror-tables/src/ |
Upload File : |
// Utilities used for copy/paste handling.
//
// This module handles pasting cell content into tables, or pasting
// anything into a cell selection, as replacing a block of cells with
// the content of the selection. When pasting cells into a cell, that
// involves placing the block of pasted content so that its top left
// aligns with the selection cell, optionally extending the table to
// the right or bottom to make sure it is large enough. Pasting into a
// cell selection is different, here the cells in the selection are
// clipped to the selection's rectangle, optionally repeating the
// pasted cells when they are smaller than the selection.
import {Slice, Fragment} from "prosemirror-model"
import {Transform} from "prosemirror-transform"
import {setAttr, removeColSpan} from "./util"
import {TableMap} from "./tablemap"
import {CellSelection} from "./cellselection"
import {tableNodeTypes} from "./schema"
// Utilities to help with copying and pasting table cells
// : (Slice) → ?{width: number, height: number, rows: [Fragment]}
// Get a rectangular area of cells from a slice, or null if the outer
// nodes of the slice aren't table cells or rows.
export function pastedCells(slice) {
if (!slice.size) return null
let {content, openStart, openEnd} = slice
while (content.childCount == 1 && (openStart > 0 && openEnd > 0 || content.firstChild.type.spec.tableRole == "table")) {
openStart--
openEnd--
content = content.firstChild.content
}
let first = content.firstChild, role = first.type.spec.tableRole
let schema = first.type.schema, rows = []
if (role == "row") {
for (let i = 0; i < content.childCount; i++) {
let cells = content.child(i).content
let left = i ? 0 : Math.max(0, openStart - 1)
let right = i < content.childCount - 1 ? 0 : Math.max(0, openEnd - 1)
if (left || right) cells = fitSlice(tableNodeTypes(schema).row, new Slice(cells, left, right)).content
rows.push(cells)
}
} else if (role == "cell" || role == "header_cell") {
rows.push(openStart || openEnd ? fitSlice(tableNodeTypes(schema).row, new Slice(content, openStart, openEnd)).content : content)
} else {
return null
}
return ensureRectangular(schema, rows)
}
// : (Schema, [Fragment]) → {width: number, height: number, rows: [Fragment]}
// Compute the width and height of a set of cells, and make sure each
// row has the same number of cells.
function ensureRectangular(schema, rows) {
let widths = []
for (let i = 0; i < rows.length; i++) {
let row = rows[i]
for (let j = row.childCount - 1; j >= 0; j--) {
let {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(Fragment.empty)
if (widths[r] < width) {
let empty = tableNodeTypes(schema).cell.createAndFill(), cells = []
for (let i = widths[r]; i < width; i++) cells.push(empty)
rows[r] = rows[r].append(Fragment.from(cells))
}
}
return {height: rows.length, width, rows}
}
export function fitSlice(nodeType, slice) {
let node = nodeType.createAndFill()
let tr = new Transform(node).replace(0, node.content.size, slice)
return tr.doc
}
// : ({width: number, height: number, rows: [Fragment]}, number, number) → {width: number, height: number, rows: [Fragment]}
// Clip or extend (repeat) the given set of cells to cover the given
// width and height. Will clip rowspan/colspan cells at the edges when
// they stick out.
export function clipCells({width, height, rows}, newWidth, newHeight) {
if (width != newWidth) {
let added = [], newRows = []
for (let row = 0; row < rows.length; row++) {
let 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.create(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(Fragment.from(cells))
}
rows = newRows
width = newWidth
}
if (height != newHeight) {
let newRows = []
for (let row = 0, i = 0; row < newHeight; row++, i++) {
let 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(setAttr(cell.attrs, "rowspan", Math.max(1, newHeight - cell.attrs.rowspan)), cell.content)
cells.push(cell)
}
newRows.push(Fragment.from(cells))
}
rows = newRows
height = newHeight
}
return {width, height, rows}
}
// Make sure a table has at least the given width and height. Return
// true if something was changed.
function growTable(tr, map, table, start, width, height, mapFrom) {
let schema = tr.doc.type.schema, types = tableNodeTypes(schema), empty, emptyHead
if (width > map.width) {
for (let row = 0, rowEnd = 0; row < map.height; row++) {
let rowNode = table.child(row)
rowEnd += rowNode.nodeSize
let cells = [], 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) {
let cells = []
for (let i = 0, start = (map.height - 1) * map.width; i < Math.max(map.width, width); i++) {
let header = i >= map.width ? false :
table.nodeAt(map.map[start + i]).type == types.header_cell
cells.push(header
? (emptyHead || (emptyHead = types.header_cell.createAndFill()))
: (empty || (empty = types.cell.createAndFill())))
}
let emptyRow = types.row.create(null, Fragment.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)
}
// Make sure the given line (left, top) to (right, top) doesn't cross
// any rowspan cells by splitting cells that cross it. Return true if
// something changed.
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++) {
let index = top * map.width + col, pos = map.map[index]
if (map.map[index - map.width] == pos) {
found = true
let cell = table.nodeAt(pos)
let {top: cellTop, left: cellLeft} = map.findCell(pos)
tr.setNodeMarkup(tr.mapping.slice(mapFrom).map(pos + start), null, setAttr(cell.attrs, "rowspan", top - cellTop))
tr.insert(tr.mapping.slice(mapFrom).map(map.positionAt(top, cellLeft, table)),
cell.type.createAndFill(setAttr(cell.attrs, "rowspan", (cellTop + cell.attrs.rowspan) - top)))
col += cell.attrs.colspan - 1
}
}
return found
}
// Make sure the given line (left, top) to (left, bottom) doesn't
// cross any colspan cells by splitting cells that cross it. Return
// true if something changed.
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++) {
let index = row * map.width + left, pos = map.map[index]
if (map.map[index - 1] == pos) {
found = true
let cell = table.nodeAt(pos), cellLeft = map.colCount(pos)
let 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
}
// Insert the given set of cells (as returned by `pastedCells`) into a
// table, at the position pointed at by rect.
export function insertCells(state, dispatch, tableStart, rect, cells) {
let table = tableStart ? state.doc.nodeAt(tableStart - 1) : state.doc, map = TableMap.get(table)
let {top, left} = rect
let right = left + cells.width, bottom = top + cells.height
let tr = state.tr, mapFrom = 0
function recomp() {
table = tableStart ? tr.doc.nodeAt(tableStart - 1) : tr.doc
map = TableMap.get(table)
mapFrom = tr.mapping.maps.length
}
// Prepare the table to be large enough and not have any cells
// crossing the boundaries of the rectangle that we want to
// insert into. If anything about it changes, recompute the table
// map so that subsequent operations can see the current shape.
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++) {
let 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 Slice(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)
}