import { random } from '@opentiny/utils'
import { isNull } from '@opentiny/utils'
function cellName() {
return random().toString(36).slice(2)
}
function addMatcherTable({ _quill, oper }) {
_quill.clipboard.addMatcher('TABLE', function (node, delta) {
const id = cellName()
oper.setTableId(id)
return delta
})
}
function addMatcherTr({ _quill, oper }) {
_quill.clipboard.addMatcher('TR', function (node, delta) {
const id = cellName()
oper.setRowId(id)
return delta
})
}
function addMatcherTdOrTh({ _quill, oper, Delta, tagName }) {
_quill.clipboard.addMatcher(tagName, function (node, delta) {
const tableId = oper.getTableId()
const rowId = oper.getRowId()
const cellId = cellName()
if (delta.ops.length) {
const lastOp = delta.ops[delta.ops.length - 1]
if (typeof lastOp.insert === 'string' && !lastOp.insert.endsWith('\n')) {
lastOp.insert += '\n'
}
} else {
delta.insert('\n')
}
const attributes = { td: tableId + '|' + rowId + '|' + cellId }
if (node.rowSpan) {
attributes.rowSpan = node.rowSpan
}
if (node.colSpan) {
attributes.colSpan = node.colSpan
}
if (node.width) {
attributes.width = node.width
}
if (node.height) {
attributes.height = node.height
}
return delta.compose(new Delta().retain(delta.length(), attributes))
})
}
function addMatcher3(_quill) {
_quill.clipboard.addMatcher(3, function (node, delta) {
if (['TABLE', 'TR'].includes(node.parentNode.tagName)) {
return { ops: [] }
} else {
return delta
}
})
}
function addMatcherFont(_quill) {
_quill.clipboard.addMatcher('FONT', function (node, delta) {
if (node.innerText === '') {
return { ops: [] }
} else {
return delta
}
})
}
function addMatcher({ qil: _quill, Delta }) {
let tableId = cellName()
let rowId = cellName()
let oper = {
getTableId() {
return tableId
},
setTableId(id) {
tableId = id
},
getRowId() {
return rowId
},
setRowId(id) {
rowId = id
}
}
addMatcherTable({ _quill, oper })
addMatcherTr({ _quill, oper })
addMatcherTdOrTh({ _quill, oper, Delta, tagName: 'TD' })
addMatcherTdOrTh({ _quill, oper, Delta, tagName: 'TH' })
addMatcher3(_quill)
addMatcherFont(_quill)
}
let quill = Object.create(null)
function setQuill(q) {
quill = q
}
function getQuill() {
return quill
}
function getLeafFromQuill() {
let q = getQuill()
return q ? q.getLeaf(q.getSelection().index) : []
}
function provideTableTrick({ Parchment }) {
class TableTrick {
static findTd(what) {
const leaf = getLeafFromQuill()
let blot = leaf[0]
while (!isNull(blot) && blot.statics.blotName !== what) {
blot = blot.parent
}
return blot
}
static appendCol() {
const td = TableTrick.findTd('td')
if (td) {
const table = td.parent.parent
const tableId = table.domNode.getAttribute('table_id')
td.parent.parent.children.forEach(function (tr) {
const rowId = tr.domNode.getAttribute('row_id')
const cellId = cellName()
const tdNew = Parchment.create('td', tableId + '|' + rowId + '|' + cellId)
tr.appendChild(tdNew)
})
}
}
static appendRow() {
const td = TableTrick.findTd('td')
if (td) {
const colCount = td.parent.children.length
const table = td.parent.parent
const newRow = td.parent.clone()
const tableId = table.domNode.getAttribute('table_id')
const rowId = cellName()
newRow.domNode.setAttribute('row_id', rowId)
for (let i = colCount - 1; i >= 0; i--) {
const cellId = cellName()
const tdNew = Parchment.create('td', tableId + '|' + rowId + '|' + cellId)
newRow.appendChild(tdNew)
}
table.appendChild(newRow)
}
}
}
return { TableTrick }
}
function createTable({ value, Container, Parchment, Scroll }) {
let node = null
const sizes = value.split('_')
const rowCount = Number.parseInt(sizes[1])
const colCount = Number.parseInt(sizes[2])
const tableId = cellName()
const table = Parchment.create('table', tableId)
for (let ri = 0; ri < rowCount; ri++) {
const rowId = cellName()
const tr = Parchment.create('tr', rowId)
table.appendChild(tr)
for (let ci = 0; ci < colCount; ci++) {
const cellId = cellName()
value = tableId + '|' + rowId + '|' + cellId
const td = Parchment.create('td', value)
const p = Parchment.create('block')
const br = Parchment.create('break')
tr.appendChild(td)
td.appendChild(p)
p.appendChild(br)
node = p
}
}
const leaf = getLeafFromQuill()
let blot = leaf[0]
let topBranch = null
while (!isNull(blot) && !(blot instanceof Container || blot instanceof Scroll)) {
topBranch = blot
blot = blot.parent
}
blot && blot.insertBefore(table, topBranch)
return node
}
function provideTable({ Container, Parchment, Scroll, TableTrick, TableRow }) {
class Table extends Container {
static create(value) {
if (value === 'append-row') {
return TableTrick.appendRow()
} else if (value === 'append-col') {
return TableTrick.appendCol()
} else if (value.includes('newtable_')) {
return createTable({ value, Container, Parchment, Scroll })
} else {
const tagName = 'table'
const node = super.create(tagName)
node.setAttribute('table_id', value)
return node
}
}
optimize() {
super.optimize()
const next = this.next
if (
!isNull(next) &&
next.prev === this &&
next.statics.blotName === this.statics.blotName &&
next.domNode.tagName === this.domNode.tagName &&
next.domNode.getAttribute('table_id') === this.domNode.getAttribute('table_id')
) {
next.moveChildren(this)
next.remove()
}
}
}
Table.blotName = 'table'
Table.tagName = 'table'
Table.scope = Parchment.Scope.BLOCK_BLOT
Table.defaultChild = 'tr'
Table.allowedChildren = [TableRow]
return { Table }
}
function provideTableModule({ Container, Parchment, Module, Quill, Scroll, Delta, ContainBlot, TableRow, TableCell }) {
const { TableTrick } = provideTableTrick({ Parchment })
const { Table } = provideTable({
Container,
Parchment,
Scroll,
TableTrick,
TableRow
})
class TableModule extends Module {
constructor(qil, opt) {
super(qil, opt)
addMatcher({ qil, Delta })
setQuill(qil)
}
static register() {
Quill.register(ContainBlot, true)
Quill.register(TableRow, true)
Quill.register(Table, true)
Quill.register(TableCell, true)
}
}
return { TableModule }
}
function provideContainBlot({ Container, Parchment, Block, BlockEmbed }) {
class ContainBlot extends Container {
static create() {
return super.create('contain')
}
static formats(domNode) {
return domNode.tagName
}
insertBefore(blot, ref) {
if (blot.statics.blotName === this.statics.blotName) {
super.insertBefore(blot.children.head, ref)
} else {
super.insertBefore(blot, ref)
}
}
formats() {
return { [this.statics.blotName]: this.statics.formats(this.domNode) }
}
replace(target) {
if (target.statics.blotName !== this.statics.blotName) {
const item = Parchment.create(this.statics.defaultChild)
target.moveChildren(item)
this.appendChild(item)
}
if (isNull(target.parent)) return
super.replace(target)
}
}
ContainBlot.blotName = 'contain'
ContainBlot.tagName = 'contain'
ContainBlot.scope = Parchment.Scope.BLOCK_BLOT
ContainBlot.defaultChild = 'block'
ContainBlot.allowedChildren = [Block, BlockEmbed, Container]
return { ContainBlot }
}
function provideTableRow({ Container, Parchment }) {
class TableRow extends Container {
static create(value) {
const node = super.create('tr')
node.setAttribute('row_id', value)
return node
}
optimize() {
super.optimize()
const next = this.next
if (
!isNull(next) &&
next.prev === this &&
next.statics.blotName === this.statics.blotName &&
next.domNode.tagName === this.domNode.tagName &&
next.domNode.getAttribute('row_id') === this.domNode.getAttribute('row_id')
) {
next.moveChildren(this)
next.remove()
}
}
}
TableRow.blotName = 'tr'
TableRow.tagName = 'tr'
TableRow.scope = Parchment.Scope.BLOCK_BLOT
TableRow.defaultChild = 'td'
return { TableRow }
}
function setAttrs({ node, value }) {
const ids = value.split('|')
node.setAttribute('table_id', ids[0])
node.setAttribute('row_id', ids[1])
node.setAttribute('cell_id', ids[2])
return node
}
function provideTableCell({ Container, Parchment, Block, BlockEmbed, ContainBlot }) {
class TableCell extends ContainBlot {
static create(value) {
return setAttrs({ node: super.create('td'), value })
}
formatAt(index, length, name, value) {
super.formatAt(index, length, name, value)
const attrWhitelist = ['colSpan', 'rowSpan', 'width', 'height']
attrWhitelist.includes(name) && this.domNode.setAttribute(name, value)
}
formats() {
const domNode = this.domNode
const blotName = this.statics.blotName
const attr = (domNode, attr) => domNode.getAttribute(attr)
return {
[blotName]: `${attr(domNode, 'table_id')}|${attr(domNode, 'row_id')}|${attr(domNode, 'row_id')}`
}
}
optimize() {
super.optimize()
if (!isNull(this.parent) && this.parent.statics.blotName !== 'tr') {
const mark = Parchment.create('block')
this.parent.insertBefore(mark, this.next)
const table = Parchment.create('table', this.domNode.getAttribute('table_id'))
const tr = Parchment.create('tr', this.domNode.getAttribute('row_id'))
table.appendChild(tr)
tr.appendChild(this)
table.replace(mark)
}
if (
!isNull(this.next) &&
this.next.prev === this &&
this.next.statics.blotName === this.statics.blotName &&
this.next.domNode.tagName === this.domNode.tagName &&
this.next.domNode.getAttribute('cell_id') === this.domNode.getAttribute('cell_id')
) {
this.next.moveChildren(this)
this.next.remove()
}
}
}
TableCell.blotName = 'td'
TableCell.tagName = 'td'
TableCell.scope = Parchment.Scope.BLOCK_BLOT
TableCell.defaultChild = 'block'
TableCell.allowedChildren = [Block, BlockEmbed, Container]
return { TableCell }
}
function tableModule(Quill) {
const Container = Quill.import('blots/container')
const Scroll = Quill.import('blots/scroll')
const Module = Quill.import('core/module')
const Block = Quill.import('blots/block')
const Delta = Quill.import('delta')
const Parchment = Quill.import('parchment')
const BlockEmbed = Quill.import('blots/block/embed')
const { ContainBlot } = provideContainBlot({
Container,
Parchment,
Block,
BlockEmbed
})
const { TableRow } = provideTableRow({ Container, Parchment })
const { TableCell } = provideTableCell({
Container,
Parchment,
Block,
BlockEmbed,
ContainBlot
})
const params = {
Container,
Parchment,
Module,
Quill,
Scroll,
Delta,
ContainBlot,
TableRow,
TableCell
}
const { TableModule } = provideTableModule(params)
TableRow.allowedChildren = [TableCell]
Container.order = ['list', 'contain', 'td', 'tr', 'table']
Quill.register('modules/tableModule', TableModule, true)
}
export default tableModule