NeahNew/node_modules/quill-better-table/src/modules/table-operation-menu.js
2025-05-03 14:17:46 +02:00

412 lines
12 KiB
JavaScript

import Quill from 'quill'
import { css, getRelativeRect } from '../utils'
// svg icons
import operationIcon1 from '../assets/icons/icon_operation_1.svg'
import operationIcon2 from '../assets/icons/icon_operation_2.svg'
import operationIcon3 from '../assets/icons/icon_operation_3.svg'
import operationIcon4 from '../assets/icons/icon_operation_4.svg'
import operationIcon5 from '../assets/icons/icon_operation_5.svg'
import operationIcon6 from '../assets/icons/icon_operation_6.svg'
import operationIcon7 from '../assets/icons/icon_operation_7.svg'
import operationIcon8 from '../assets/icons/icon_operation_8.svg'
import operationIcon9 from '../assets/icons/icon_operation_9.svg'
const MENU_MIN_HEIHGT = 150
const MENU_WIDTH = 200
const ERROR_LIMIT = 5
const DEFAULT_CELL_COLORS = ['white', 'red', 'yellow', 'blue']
const DEFAULT_COLOR_SUBTITLE = 'Background Colors'
const MENU_ITEMS_DEFAULT = {
insertColumnRight: {
text: 'Insert column right',
iconSrc: operationIcon1,
handler () {
const tableContainer = Quill.find(this.table)
let colIndex = getColToolCellIndexByBoundary(
this.columnToolCells,
this.boundary,
(cellRect, boundary) => {
return Math.abs(cellRect.x + cellRect.width - boundary.x1) <= ERROR_LIMIT
},
this.quill.root.parentNode
)
const newColumn = tableContainer.insertColumn(
this.boundary,
colIndex,
true,
this.quill.root.parentNode)
this.tableColumnTool.updateToolCells()
this.quill.update(Quill.sources.USER)
this.quill.setSelection(
this.quill.getIndex(newColumn[0]),
0,
Quill.sources.SILENT
)
this.tableSelection.setSelection(
newColumn[0].domNode.getBoundingClientRect(),
newColumn[0].domNode.getBoundingClientRect()
)
}
},
insertColumnLeft: {
text: 'Insert column left',
iconSrc: operationIcon2,
handler () {
const tableContainer = Quill.find(this.table)
let colIndex = getColToolCellIndexByBoundary(
this.columnToolCells,
this.boundary,
(cellRect, boundary) => {
return Math.abs(cellRect.x - boundary.x) <= ERROR_LIMIT
},
this.quill.root.parentNode
)
const newColumn = tableContainer.insertColumn(
this.boundary,
colIndex,
false,
this.quill.root.parentNode)
this.tableColumnTool.updateToolCells()
this.quill.update(Quill.sources.USER)
this.quill.setSelection(
this.quill.getIndex(newColumn[0]),
0,
Quill.sources.SILENT
)
this.tableSelection.setSelection(
newColumn[0].domNode.getBoundingClientRect(),
newColumn[0].domNode.getBoundingClientRect()
)
}
},
insertRowUp: {
text: 'Insert row up',
iconSrc: operationIcon3,
handler () {
const tableContainer = Quill.find(this.table)
const affectedCells = tableContainer.insertRow(
this.boundary,
false,
this.quill.root.parentNode
)
this.quill.update(Quill.sources.USER)
this.quill.setSelection(
this.quill.getIndex(affectedCells[0]),
0,
Quill.sources.SILENT
)
this.tableSelection.setSelection(
affectedCells[0].domNode.getBoundingClientRect(),
affectedCells[0].domNode.getBoundingClientRect()
)
}
},
insertRowDown: {
text: 'Insert row down',
iconSrc: operationIcon4,
handler () {
const tableContainer = Quill.find(this.table)
const affectedCells = tableContainer.insertRow(
this.boundary,
true,
this.quill.root.parentNode
)
this.quill.update(Quill.sources.USER)
this.quill.setSelection(
this.quill.getIndex(affectedCells[0]),
0,
Quill.sources.SILENT
)
this.tableSelection.setSelection(
affectedCells[0].domNode.getBoundingClientRect(),
affectedCells[0].domNode.getBoundingClientRect()
)
}
},
mergeCells: {
text: 'Merge selected cells',
iconSrc: operationIcon5,
handler () {
const tableContainer = Quill.find(this.table)
// compute merged Cell rowspan, equal to length of selected rows
const rowspan = tableContainer.rows().reduce((sum, row) => {
let rowRect = getRelativeRect(
row.domNode.getBoundingClientRect(),
this.quill.root.parentNode
)
if (
rowRect.y > this.boundary.y - ERROR_LIMIT &&
rowRect.y + rowRect.height < this.boundary.y + this.boundary.height + ERROR_LIMIT
) {
sum += 1
}
return sum
}, 0)
// compute merged cell colspan, equal to length of selected cols
const colspan = this.columnToolCells.reduce((sum, cell) => {
let cellRect = getRelativeRect(
cell.getBoundingClientRect(),
this.quill.root.parentNode
)
if (
cellRect.x > this.boundary.x - ERROR_LIMIT &&
cellRect.x + cellRect.width < this.boundary.x + this.boundary.width + ERROR_LIMIT
) {
sum += 1
}
return sum
}, 0)
const mergedCell = tableContainer.mergeCells(
this.boundary,
this.selectedTds,
rowspan,
colspan,
this.quill.root.parentNode
)
this.quill.update(Quill.sources.USER)
this.tableSelection.setSelection(
mergedCell.domNode.getBoundingClientRect(),
mergedCell.domNode.getBoundingClientRect()
)
}
},
unmergeCells: {
text: 'Unmerge cells',
iconSrc: operationIcon6,
handler () {
const tableContainer = Quill.find(this.table)
tableContainer.unmergeCells(
this.selectedTds,
this.quill.root.parentNode
)
this.quill.update(Quill.sources.USER)
this.tableSelection.clearSelection()
}
},
deleteColumn: {
text: 'Delete selected columns',
iconSrc: operationIcon7,
handler () {
const tableContainer = Quill.find(this.table)
let colIndexes = getColToolCellIndexesByBoundary(
this.columnToolCells,
this.boundary,
(cellRect, boundary) => {
return cellRect.x + ERROR_LIMIT > boundary.x &&
cellRect.x + cellRect.width - ERROR_LIMIT < boundary.x1
},
this.quill.root.parentNode
)
let isDeleteTable = tableContainer.deleteColumns(
this.boundary,
colIndexes,
this.quill.root.parentNode
)
if (!isDeleteTable) {
this.tableColumnTool.updateToolCells()
this.quill.update(Quill.sources.USER)
this.tableSelection.clearSelection()
}
}
},
deleteRow: {
text: 'Delete selected rows',
iconSrc: operationIcon8,
handler () {
const tableContainer = Quill.find(this.table)
tableContainer.deleteRow(
this.boundary,
this.quill.root.parentNode
)
this.quill.update(Quill.sources.USER)
this.tableSelection.clearSelection()
}
},
deleteTable: {
text: 'Delete table',
iconSrc: operationIcon9,
handler () {
const betterTableModule = this.quill.getModule('better-table')
const tableContainer = Quill.find(this.table)
betterTableModule.hideTableTools()
tableContainer.remove()
this.quill.update(Quill.sources.USER)
}
}
}
export default class TableOperationMenu {
constructor (params, quill, options) {
const betterTableModule = quill.getModule('better-table')
this.tableSelection = betterTableModule.tableSelection
this.table = params.table
this.quill = quill
this.options = options
this.menuItems = Object.assign({}, MENU_ITEMS_DEFAULT, options.items)
this.tableColumnTool = betterTableModule.columnTool
this.boundary = this.tableSelection.boundary
this.selectedTds = this.tableSelection.selectedTds
this.destroyHandler = this.destroy.bind(this)
this.columnToolCells = this.tableColumnTool.colToolCells()
this.colorSubTitle = options.color && options.color.text ? options.color.text : DEFAULT_COLOR_SUBTITLE
this.cellColors = options.color && options.color.colors ? options.color.colors : DEFAULT_CELL_COLORS
this.menuInitial(params)
this.mount()
document.addEventListener("click", this.destroyHandler, false)
}
mount () {
document.body.appendChild(this.domNode)
}
destroy () {
this.domNode.remove()
document.removeEventListener("click", this.destroyHandler, false)
return null
}
menuInitial ({ table, left, top }) {
this.domNode = document.createElement('div')
this.domNode.classList.add('qlbt-operation-menu')
css(this.domNode, {
position: 'absolute',
left: `${left}px`,
top: `${top}px`,
'min-height': `${MENU_MIN_HEIHGT}px`,
width: `${MENU_WIDTH}px`
})
for (let name in this.menuItems) {
if (this.menuItems[name]) {
this.domNode.appendChild(
this.menuItemCreator(
Object.assign({}, MENU_ITEMS_DEFAULT[name], this.menuItems[name])
)
)
if (['insertRowDown', 'unmergeCells'].indexOf(name) > -1) {
this.domNode.appendChild(
dividingCreator()
)
}
}
}
// if colors option is false, disabled bg color
if (this.options.color && this.options.color !== false) {
this.domNode.appendChild(
dividingCreator()
)
this.domNode.appendChild(
subTitleCreator(this.colorSubTitle)
)
this.domNode.appendChild(this.colorsItemCreator(this.cellColors))
}
// create dividing line
function dividingCreator () {
const dividing = document.createElement('div')
dividing.classList.add('qlbt-operation-menu-dividing')
return dividing
}
// create subtitle for menu
function subTitleCreator (title) {
const subTitle = document.createElement('div')
subTitle.classList.add('qlbt-operation-menu-subtitle')
subTitle.innerText = title
return subTitle
}
}
colorsItemCreator (colors) {
const self = this
const node = document.createElement('div')
node.classList.add('qlbt-operation-color-picker')
colors.forEach(color => {
let colorBox = colorBoxCreator(color)
node.appendChild(colorBox)
})
function colorBoxCreator (color) {
const box = document.createElement('div')
box.classList.add('qlbt-operation-color-picker-item')
box.setAttribute('data-color', color)
box.style.backgroundColor = color
box.addEventListener('click', function () {
const selectedTds = self.tableSelection.selectedTds
if (selectedTds && selectedTds.length > 0) {
selectedTds.forEach(tableCell => {
tableCell.format('cell-bg', color)
})
}
}, false)
return box
}
return node
}
menuItemCreator ({ text, iconSrc, handler }) {
const node = document.createElement('div')
node.classList.add('qlbt-operation-menu-item')
const iconSpan = document.createElement('span')
iconSpan.classList.add('qlbt-operation-menu-icon')
iconSpan.innerHTML = iconSrc
const textSpan = document.createElement('span')
textSpan.classList.add('qlbt-operation-menu-text')
textSpan.innerText = text
node.appendChild(iconSpan)
node.appendChild(textSpan)
node.addEventListener('click', handler.bind(this), false)
return node
}
}
function getColToolCellIndexByBoundary (cells, boundary, conditionFn, container) {
return cells.reduce((findIndex, cell) => {
let cellRect = getRelativeRect(cell.getBoundingClientRect(), container)
if (conditionFn(cellRect, boundary)) {
findIndex = cells.indexOf(cell)
}
return findIndex
}, false)
}
function getColToolCellIndexesByBoundary (cells, boundary, conditionFn, container) {
return cells.reduce((findIndexes, cell) => {
let cellRect = getRelativeRect(
cell.getBoundingClientRect(),
container
)
if (conditionFn(cellRect, boundary)) {
findIndexes.push(cells.indexOf(cell))
}
return findIndexes
}, [])
}