feat:add table component
This commit is contained in:
@@ -21,6 +21,7 @@ export class Command {
|
||||
private static center: Function
|
||||
private static right: Function
|
||||
private static rowMargin: Function
|
||||
private static insertTable: Function
|
||||
private static image: Function
|
||||
private static search: Function
|
||||
private static print: Function
|
||||
@@ -46,6 +47,7 @@ export class Command {
|
||||
Command.center = adapt.rowFlex.bind(adapt)
|
||||
Command.right = adapt.rowFlex.bind(adapt)
|
||||
Command.rowMargin = adapt.rowMargin.bind(adapt)
|
||||
Command.insertTable = adapt.insertTable.bind(adapt)
|
||||
Command.image = adapt.image.bind(adapt)
|
||||
Command.search = adapt.search.bind(adapt)
|
||||
Command.print = adapt.print.bind(adapt)
|
||||
@@ -124,7 +126,11 @@ export class Command {
|
||||
return Command.rowMargin(payload)
|
||||
}
|
||||
|
||||
// 图片上传、搜索、打印
|
||||
// 表格、图片上传、搜索、打印
|
||||
public executeInsertTable(row: number, col: number) {
|
||||
return Command.insertTable(row, col)
|
||||
}
|
||||
|
||||
public executeImage(payload: IDrawImagePayload) {
|
||||
return Command.image(payload)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,11 @@ import { RowFlex } from "../../dataset/enum/Row"
|
||||
import { IDrawImagePayload } from "../../interface/Draw"
|
||||
import { IEditorOption } from "../../interface/Editor"
|
||||
import { IElement, IElementStyle } from "../../interface/Element"
|
||||
import { IColgroup } from "../../interface/table/Colgroup"
|
||||
import { ITd } from "../../interface/table/Td"
|
||||
import { ITr } from "../../interface/table/Tr"
|
||||
import { getUUID } from "../../utils"
|
||||
import { formatElementList } from "../../utils/element"
|
||||
import { printImageBase64 } from "../../utils/print"
|
||||
import { Draw } from "../draw/Draw"
|
||||
import { HistoryManager } from "../history/HistoryManager"
|
||||
@@ -214,6 +218,55 @@ export class CommandAdapt {
|
||||
this.draw.render({ curIndex, isSetCursor })
|
||||
}
|
||||
|
||||
public insertTable(row: number, col: number) {
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
if (startIndex === 0 && endIndex === 0) return
|
||||
const elementList = this.draw.getElementList()
|
||||
const { width, margins } = this.options
|
||||
const innerWidth = width - margins[1] - margins[3]
|
||||
// colgroup
|
||||
const colgroup: IColgroup[] = []
|
||||
const colWidth = innerWidth / col
|
||||
for (let c = 0; c < col; c++) {
|
||||
colgroup.push({
|
||||
width: colWidth
|
||||
})
|
||||
}
|
||||
// trlist
|
||||
const trList: ITr[] = []
|
||||
for (let r = 0; r < row; r++) {
|
||||
const tdList: ITd[] = []
|
||||
const tr: ITr = {
|
||||
height: 40,
|
||||
tdList
|
||||
}
|
||||
for (let c = 0; c < col; c++) {
|
||||
tdList.push({
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
value: [{ value: ZERO, size: 16 }]
|
||||
})
|
||||
}
|
||||
trList.push(tr)
|
||||
}
|
||||
const element: IElement = {
|
||||
type: ElementType.TABLE,
|
||||
value: ZERO,
|
||||
colgroup,
|
||||
trList
|
||||
}
|
||||
// 格式化element
|
||||
formatElementList([element])
|
||||
const curIndex = startIndex + 1
|
||||
if (startIndex === endIndex) {
|
||||
elementList.splice(curIndex, 0, element)
|
||||
} else {
|
||||
elementList.splice(curIndex, endIndex - startIndex, element)
|
||||
}
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex, isSetCursor: false })
|
||||
}
|
||||
|
||||
public image(payload: IDrawImagePayload) {
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
if (startIndex === 0 && endIndex === 0) return
|
||||
|
||||
+160
-37
@@ -1,6 +1,6 @@
|
||||
import { ZERO } from "../../dataset/constant/Common"
|
||||
import { RowFlex } from "../../dataset/enum/Row"
|
||||
import { IDrawOption } from "../../interface/Draw"
|
||||
import { IDrawOption, IDrawRowPayload, IDrawRowResult } from "../../interface/Draw"
|
||||
import { IEditorOption } from "../../interface/Editor"
|
||||
import { IElement, IElementMetrics, IElementPosition, IElementStyle } from "../../interface/Element"
|
||||
import { IRow, IRowElement } from "../../interface/Row"
|
||||
@@ -23,6 +23,7 @@ import { ImageParticle } from "./particle/ImageParticle"
|
||||
import { TextParticle } from "./particle/TextParticle"
|
||||
import { PageNumber } from "./frame/PageNumber"
|
||||
import { GlobalObserver } from "../observer/GlobalObserver"
|
||||
import { TableParticle } from "./particle/table/TableParticle"
|
||||
|
||||
export class Draw {
|
||||
|
||||
@@ -47,6 +48,7 @@ export class Draw {
|
||||
private historyManager: HistoryManager
|
||||
private imageParticle: ImageParticle
|
||||
private textParticle: TextParticle
|
||||
private tableParticle: TableParticle
|
||||
private pageNumber: PageNumber
|
||||
|
||||
private rowList: IRow[]
|
||||
@@ -83,6 +85,7 @@ export class Draw {
|
||||
this.highlight = new Highlight(this)
|
||||
this.imageParticle = new ImageParticle(this)
|
||||
this.textParticle = new TextParticle(this)
|
||||
this.tableParticle = new TableParticle(this)
|
||||
this.pageNumber = new PageNumber(this)
|
||||
new GlobalObserver(this)
|
||||
|
||||
@@ -135,6 +138,10 @@ export class Draw {
|
||||
return this.options.defaultBasicRowMarginHeight * this.options.scale
|
||||
}
|
||||
|
||||
public getTdPadding(): number {
|
||||
return this.options.tdPadding * this.options.scale
|
||||
}
|
||||
|
||||
public getContainer(): HTMLDivElement {
|
||||
return this.container
|
||||
}
|
||||
@@ -202,6 +209,15 @@ export class Draw {
|
||||
}
|
||||
|
||||
public getElementList(): IElement[] {
|
||||
const positionContext = this.position.getPositionContext()
|
||||
if (positionContext.isTable) {
|
||||
const { index, trIndex, tdIndex } = positionContext
|
||||
return this.elementList[index!].trList![trIndex!].tdList[tdIndex!].value
|
||||
}
|
||||
return this.elementList
|
||||
}
|
||||
|
||||
public getOriginalElementList() {
|
||||
return this.elementList
|
||||
}
|
||||
|
||||
@@ -306,25 +322,25 @@ export class Draw {
|
||||
return `${el.italic ? 'italic ' : ''}${el.bold ? 'bold ' : ''}${(el.size || defaultSize) * scale}px ${el.font || defaultFont}`
|
||||
}
|
||||
|
||||
private _computeRowList() {
|
||||
const { defaultSize, defaultRowMargin, scale } = this.options
|
||||
const innerWidth = this.getInnerWidth()
|
||||
private _computeRowList(innerWidth: number, elementList: IElement[]) {
|
||||
const { defaultSize, defaultRowMargin, scale, tdPadding } = this.options
|
||||
const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight()
|
||||
const tdGap = tdPadding * 2
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
|
||||
const rowList: IRow[] = []
|
||||
if (this.elementList.length) {
|
||||
if (elementList.length) {
|
||||
rowList.push({
|
||||
width: 0,
|
||||
height: 0,
|
||||
ascent: 0,
|
||||
elementList: [],
|
||||
rowFlex: this.elementList?.[1]?.rowFlex
|
||||
rowFlex: elementList?.[1]?.rowFlex
|
||||
})
|
||||
}
|
||||
for (let i = 0; i < this.elementList.length; i++) {
|
||||
for (let i = 0; i < elementList.length; i++) {
|
||||
const curRow: IRow = rowList[rowList.length - 1]
|
||||
const element = this.elementList[i]
|
||||
const element = elementList[i]
|
||||
const rowMargin = defaultBasicRowMarginHeight * (element.rowMargin || defaultRowMargin)
|
||||
let metrics: IElementMetrics = {
|
||||
width: 0,
|
||||
@@ -350,6 +366,40 @@ export class Draw {
|
||||
metrics.boundingBoxDescent = elementHeight
|
||||
}
|
||||
metrics.boundingBoxAscent = 0
|
||||
} else if (element.type === ElementType.TABLE) {
|
||||
// 计算表格行列
|
||||
this.tableParticle.computeRowColInfo(element)
|
||||
// 计算表格内元素信息
|
||||
const trList = element.trList!
|
||||
for (let t = 0; t < trList.length; t++) {
|
||||
const tr = trList[t]
|
||||
let maxTrHeight = 0
|
||||
for (let d = 0; d < tr.tdList.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
const rowList = this._computeRowList((td.width! - tdGap) * scale, td.value)
|
||||
const rowHeight = rowList.reduce((pre, cur) => pre + cur.height, 0)
|
||||
td.rowList = rowList
|
||||
// 移除缩放导致的行高变化-渲染时会进行缩放调整
|
||||
const curTrHeight = (rowHeight + tdGap) / scale
|
||||
if (maxTrHeight < curTrHeight) {
|
||||
maxTrHeight = curTrHeight
|
||||
}
|
||||
}
|
||||
tr.height = maxTrHeight
|
||||
}
|
||||
// 需要重新计算表格内值
|
||||
this.tableParticle.computeRowColInfo(element)
|
||||
// 计算出表格高度
|
||||
const tableHeight = trList.reduce((pre, cur) => pre + cur.height, 0)
|
||||
const tableWidth = element.colgroup!.reduce((pre, cur) => pre + cur.width, 0)
|
||||
element.width = tableWidth
|
||||
element.height = tableHeight
|
||||
const elementWidth = tableWidth * scale
|
||||
const elementHeight = tableHeight * scale
|
||||
metrics.width = elementWidth
|
||||
metrics.height = elementHeight
|
||||
metrics.boundingBoxDescent = elementHeight
|
||||
metrics.boundingBoxAscent = 0
|
||||
} else {
|
||||
metrics.height = (element.size || this.options.defaultSize) * scale
|
||||
ctx.font = this._getFont(element)
|
||||
@@ -367,7 +417,12 @@ export class Draw {
|
||||
style: this._getFont(element, scale)
|
||||
}
|
||||
// 超过限定宽度
|
||||
if (curRow.width + metrics.width > innerWidth || (i !== 0 && element.value === ZERO)) {
|
||||
const preElement = elementList[i - 1]
|
||||
if (
|
||||
(preElement && preElement.type === ElementType.TABLE)
|
||||
|| curRow.width + metrics.width > innerWidth
|
||||
|| (i !== 0 && element.value === ZERO)
|
||||
) {
|
||||
rowList.push({
|
||||
width: metrics.width,
|
||||
height,
|
||||
@@ -388,35 +443,29 @@ export class Draw {
|
||||
curRow.elementList.push(rowElement)
|
||||
}
|
||||
}
|
||||
this.rowList = rowList
|
||||
return rowList
|
||||
}
|
||||
|
||||
private _drawElement(positionList: IElementPosition[], rowList: IRow[], pageNo: number) {
|
||||
const width = this.getWidth()
|
||||
const height = this.getHeight()
|
||||
const margins = this.getMargins()
|
||||
const ctx = this.ctxList[pageNo]
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
// 绘制背景
|
||||
this.background.render(ctx)
|
||||
// 绘制页边距
|
||||
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
||||
this.margin.render(ctx)
|
||||
// 渲染元素
|
||||
let x = leftTopPoint[0]
|
||||
let y = leftTopPoint[1]
|
||||
let index = positionList.length
|
||||
private _drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload): IDrawRowResult {
|
||||
const { positionList, rowList, pageNo, startX, startY, startIndex, innerWidth } = payload
|
||||
const { scale, tdPadding } = this.options
|
||||
const tdGap = tdPadding * 2
|
||||
let x = startX
|
||||
let y = startY
|
||||
let index = startIndex
|
||||
for (let i = 0; i < rowList.length; i++) {
|
||||
const curRow = rowList[i]
|
||||
// 计算行偏移量(行居左、居中、居右)
|
||||
if (curRow.rowFlex && curRow.rowFlex !== RowFlex.LEFT) {
|
||||
const canvasInnerWidth = width - margins[1] - margins[3]
|
||||
if (curRow.rowFlex === RowFlex.CENTER) {
|
||||
x += (canvasInnerWidth - curRow.width) / 2
|
||||
x += (innerWidth - curRow.width) / 2
|
||||
} else {
|
||||
x += canvasInnerWidth - curRow.width
|
||||
x += innerWidth - curRow.width
|
||||
}
|
||||
}
|
||||
// 当前td所在位置
|
||||
let tablePreX = x
|
||||
let tablePreY = y
|
||||
for (let j = 0; j < curRow.elementList.length; j++) {
|
||||
const element = curRow.elementList[j]
|
||||
const metrics = element.metrics
|
||||
@@ -456,25 +505,89 @@ export class Draw {
|
||||
if (element.type === ElementType.IMAGE) {
|
||||
this.textParticle.complete()
|
||||
this.imageParticle.render(ctx, element, x, y + offsetY)
|
||||
} else if (element.type === ElementType.TABLE) {
|
||||
this.tableParticle.render(ctx, element, x, y)
|
||||
} else {
|
||||
this.textParticle.record(ctx, element, x, y + offsetY)
|
||||
}
|
||||
// 选区绘制
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
if (startIndex !== endIndex && startIndex < index && index <= endIndex) {
|
||||
let rangeWidth = metrics.width
|
||||
if (rangeWidth === 0 && curRow.elementList.length === 1) {
|
||||
rangeWidth = this.options.rangeMinWidth
|
||||
const positionContext = this.position.getPositionContext()
|
||||
// 表格需限定上下文
|
||||
if (
|
||||
(!positionContext.isTable && !element.tdId)
|
||||
|| positionContext.tdId === element.tdId
|
||||
) {
|
||||
let rangeWidth = metrics.width
|
||||
if (rangeWidth === 0 && curRow.elementList.length === 1) {
|
||||
rangeWidth = this.options.rangeMinWidth
|
||||
}
|
||||
this.range.render(ctx, x, y, rangeWidth, curRow.height)
|
||||
}
|
||||
this.range.render(ctx, x, y, rangeWidth, curRow.height)
|
||||
}
|
||||
index++
|
||||
x += metrics.width
|
||||
// 绘制表格内元素
|
||||
if (element.type === ElementType.TABLE) {
|
||||
for (let t = 0; t < element.trList!.length; t++) {
|
||||
const tr = element.trList![t]
|
||||
for (let d = 0; d < tr.tdList!.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
td.positionList = []
|
||||
const drawRowResult = this._drawRow(ctx, {
|
||||
positionList: td.positionList,
|
||||
rowList: td.rowList!,
|
||||
pageNo,
|
||||
startIndex: 0,
|
||||
startX: (td.x! + tdPadding) * scale + tablePreX,
|
||||
startY: td.y! * scale + tablePreY,
|
||||
innerWidth: (td.width! - tdGap) * scale
|
||||
})
|
||||
x = drawRowResult.x
|
||||
y = drawRowResult.y
|
||||
}
|
||||
}
|
||||
// 恢复初始x、y
|
||||
x = tablePreX
|
||||
y = tablePreY
|
||||
}
|
||||
}
|
||||
this.textParticle.complete()
|
||||
x = leftTopPoint[0]
|
||||
x = startX
|
||||
y += curRow.height
|
||||
}
|
||||
return { x, y, index }
|
||||
}
|
||||
|
||||
private _drawPage(positionList: IElementPosition[], rowList: IRow[], pageNo: number) {
|
||||
const width = this.getWidth()
|
||||
const height = this.getHeight()
|
||||
const margins = this.getMargins()
|
||||
const innerWidth = this.getInnerWidth()
|
||||
const ctx = this.ctxList[pageNo]
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
// 绘制背景
|
||||
this.background.render(ctx)
|
||||
// 绘制页边距
|
||||
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
||||
this.margin.render(ctx)
|
||||
// 渲染元素
|
||||
let x = leftTopPoint[0]
|
||||
let y = leftTopPoint[1]
|
||||
let index = positionList.length
|
||||
const drawRowResult = this._drawRow(ctx, {
|
||||
positionList,
|
||||
rowList,
|
||||
pageNo,
|
||||
startIndex: index,
|
||||
startX: x,
|
||||
startY: y,
|
||||
innerWidth
|
||||
})
|
||||
x = drawRowResult.x
|
||||
y = drawRowResult.y
|
||||
index = drawRowResult.index
|
||||
// 绘制页码
|
||||
this.pageNumber.render(ctx, pageNo)
|
||||
// 搜索匹配绘制
|
||||
@@ -491,14 +604,15 @@ export class Draw {
|
||||
isComputeRowList = true
|
||||
} = payload || {}
|
||||
const height = this.getHeight()
|
||||
const innerWidth = this.getInnerWidth()
|
||||
// 计算行信息
|
||||
if (isComputeRowList) {
|
||||
this._computeRowList()
|
||||
this.rowList = this._computeRowList(innerWidth, this.elementList)
|
||||
}
|
||||
// 清除光标等副作用
|
||||
this.cursor.recoveryCursor()
|
||||
this.position.setPositionList([])
|
||||
const positionList = this.position.getPositionList()
|
||||
const positionList = this.position.getOriginalPositionList()
|
||||
// 按页渲染
|
||||
const margins = this.getMargins()
|
||||
const marginHeight = margins[0] + margins[2]
|
||||
@@ -522,7 +636,7 @@ export class Draw {
|
||||
this._createPage(i)
|
||||
}
|
||||
const rowList = pageRowList[i]
|
||||
this._drawElement(positionList, rowList, i)
|
||||
this._drawPage(positionList, rowList, i)
|
||||
}
|
||||
// 移除多余页
|
||||
setTimeout(() => {
|
||||
@@ -540,7 +654,14 @@ export class Draw {
|
||||
if (curIndex === undefined) {
|
||||
curIndex = positionList.length - 1
|
||||
}
|
||||
this.position.setCursorPosition(positionList[curIndex!] || null)
|
||||
const positionContext = this.position.getPositionContext()
|
||||
if (positionContext.isTable) {
|
||||
const { index, trIndex, tdIndex } = positionContext
|
||||
const tablePosition = this.elementList[index!].trList?.[trIndex!].tdList[tdIndex!].positionList?.[curIndex!]
|
||||
this.position.setCursorPosition(tablePosition || null)
|
||||
} else {
|
||||
this.position.setCursorPosition(positionList[curIndex!] || null)
|
||||
}
|
||||
this.cursor.drawCursor()
|
||||
}
|
||||
// 历史记录用于undo、redo
|
||||
@@ -549,8 +670,10 @@ export class Draw {
|
||||
const oldElementList = deepClone(this.elementList)
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
const pageNo = this.pageNo
|
||||
const oldPositionContext = deepClone(this.position.getPositionContext())
|
||||
this.historyManager.execute(function () {
|
||||
self.setPageNo(pageNo)
|
||||
self.position.setPositionContext(oldPositionContext)
|
||||
self.range.setRange(startIndex, endIndex)
|
||||
self.elementList = deepClone(oldElementList)
|
||||
self.render({ curIndex, isSubmitHistory: false })
|
||||
|
||||
@@ -18,7 +18,7 @@ export class Search {
|
||||
const searchMatch = this.draw.getSearchMatch()
|
||||
if (!searchMatch || !searchMatch.length) return
|
||||
const searchMatchList = searchMatch.flat()
|
||||
const positionList = this.position.getPositionList()
|
||||
const positionList = this.position.getOriginalPositionList()
|
||||
ctx.save()
|
||||
ctx.globalAlpha = this.options.searchMatchAlpha
|
||||
ctx.fillStyle = this.options.searchMatchColor
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { IElement } from "../../../.."
|
||||
import { IEditorOption } from "../../../../interface/Editor"
|
||||
import { Draw } from "../../Draw"
|
||||
|
||||
export class TableParticle {
|
||||
|
||||
private options: Required<IEditorOption>
|
||||
|
||||
constructor(draw: Draw) {
|
||||
this.options = draw.getOptions()
|
||||
}
|
||||
|
||||
private _drawBorder(ctx: CanvasRenderingContext2D, startX: number, startY: number, width: number, height: number) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(startX, startY)
|
||||
ctx.lineTo(startX + width, startY)
|
||||
ctx.lineTo(startX + width, startY + height)
|
||||
ctx.lineTo(startX, startY + height)
|
||||
ctx.closePath()
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
private _drawRange(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number) {
|
||||
const { rangeAlpha, rangeColor } = this.options
|
||||
ctx.save()
|
||||
ctx.globalAlpha = rangeAlpha
|
||||
ctx.fillStyle = rangeColor
|
||||
ctx.fillRect(x, y, width, height)
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
public computeRowColInfo(element: IElement) {
|
||||
const { colgroup, trList } = element
|
||||
if (!colgroup || !trList) return
|
||||
let x = 0
|
||||
let y = 0
|
||||
for (let t = 0; t < trList.length; t++) {
|
||||
const tr = trList[t]
|
||||
// 表格最后一行
|
||||
const isLastTr = trList.length - 1 === t
|
||||
// 当前行最小高度
|
||||
let rowMinHeight = 0
|
||||
for (let d = 0; d < tr.tdList.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
// 计算之前行x轴偏移量
|
||||
let offsetXIndex = 0
|
||||
if (trList.length > 1 && t !== 0) {
|
||||
for (let pT = 0; pT < t; pT++) {
|
||||
const pTr = trList[pT]
|
||||
// 相同x轴是否存在跨行
|
||||
for (let pD = 0; pD < pTr.tdList.length; pD++) {
|
||||
const pTd = pTr.tdList[pD]
|
||||
const pTdX = pTd.x!
|
||||
const pTdY = pTd.y!
|
||||
const pTdWidth = pTd.width!
|
||||
const pTdHeight = pTd.height!
|
||||
// 小于
|
||||
if (pTdX < x) continue
|
||||
if (pTdX > x) break
|
||||
if (pTd.x === x && pTdY + pTdHeight > y) {
|
||||
x += pTdWidth
|
||||
offsetXIndex += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 计算格列数
|
||||
let colIndex = 0
|
||||
const preTd = tr.tdList[d - 1]
|
||||
if (preTd) {
|
||||
colIndex = preTd.colIndex! + (offsetXIndex || 1)
|
||||
if (preTd.colspan > 1) {
|
||||
colIndex += preTd.colspan - 1
|
||||
}
|
||||
} else {
|
||||
colIndex += offsetXIndex
|
||||
}
|
||||
// 计算格宽高
|
||||
let width = 0
|
||||
for (let col = 0; col < td.colspan; col++) {
|
||||
width += colgroup[col + colIndex].width
|
||||
}
|
||||
let height = 0
|
||||
for (let row = 0; row < td.rowspan; row++) {
|
||||
height += trList[row + t].height
|
||||
}
|
||||
// y偏移量
|
||||
if (rowMinHeight === 0 || rowMinHeight > height) {
|
||||
rowMinHeight = height
|
||||
}
|
||||
// 当前行最后一个td
|
||||
const isLastRowTd = tr.tdList.length - 1 === d
|
||||
// 当前列最后一个td
|
||||
let isLastColTd = isLastTr
|
||||
if (!isLastColTd) {
|
||||
if (td.rowspan > 1) {
|
||||
const nextTrLength = trList.length - 1 - t
|
||||
isLastColTd = td.rowspan - 1 === nextTrLength
|
||||
}
|
||||
}
|
||||
// 当前表格最后一个td
|
||||
const isLastTd = isLastTr && isLastRowTd
|
||||
td.isLastRowTd = isLastRowTd
|
||||
td.isLastColTd = isLastColTd
|
||||
td.isLastTd = isLastTd
|
||||
// 修改当前格clientBox
|
||||
td.x = x
|
||||
td.y = y
|
||||
td.width = width
|
||||
td.height = height
|
||||
td.rowIndex = t
|
||||
td.colIndex = colIndex
|
||||
// 当前列x轴累加
|
||||
x += width
|
||||
// 一行中的最后td
|
||||
if (isLastRowTd && !isLastTd) {
|
||||
x = 0
|
||||
y += rowMinHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public render(ctx: CanvasRenderingContext2D, element: IElement, startX: number, startY: number) {
|
||||
const { colgroup, trList } = element
|
||||
if (!colgroup || !trList) return
|
||||
const { scale } = this.options
|
||||
const tableWidth = element.width! * scale
|
||||
const tableHeight = element.height! * scale
|
||||
ctx.save()
|
||||
// 渲染边框
|
||||
this._drawBorder(ctx, startX, startY, tableWidth, tableHeight)
|
||||
// 渲染表格
|
||||
for (let t = 0; t < trList.length; t++) {
|
||||
const tr = trList[t]
|
||||
for (let d = 0; d < tr.tdList.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
const { isLastRowTd, isLastColTd, isLastTd } = td
|
||||
const width = td.width! * scale
|
||||
const height = td.height! * scale
|
||||
const x = td.x! * scale + startX + width
|
||||
const y = td.y! * scale + startY
|
||||
// 绘制线条
|
||||
ctx.beginPath()
|
||||
if (isLastRowTd && !isLastTd) {
|
||||
// 是否跨行到底
|
||||
if (y + height < startY + tableHeight) {
|
||||
ctx.moveTo(x, y + height)
|
||||
ctx.lineTo(x - width, y + height)
|
||||
}
|
||||
} else if (isLastColTd && !isLastTd) {
|
||||
ctx.moveTo(x, y)
|
||||
ctx.lineTo(x, y + height)
|
||||
} else if (!isLastRowTd && !isLastColTd && !isLastTd) {
|
||||
ctx.moveTo(x, y)
|
||||
ctx.lineTo(x, y + height)
|
||||
ctx.lineTo(x - width, y + height)
|
||||
} else if (isLastTd) {
|
||||
// 是否跨列到最右
|
||||
if (x + width === startX + tableWidth) {
|
||||
ctx.moveTo(x, y)
|
||||
ctx.lineTo(x, y + height)
|
||||
}
|
||||
}
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -80,7 +80,12 @@ export class CanvasEvent {
|
||||
this.draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
// 结束位置
|
||||
const { index: endIndex } = this.position.getPositionByXY(evt.offsetX, evt.offsetY)
|
||||
const positionResult = this.position.getPositionByXY({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
const { index, isTable, tdValueIndex } = positionResult
|
||||
let endIndex = isTable ? tdValueIndex! : index
|
||||
let end = ~endIndex ? endIndex : 0
|
||||
// 开始位置
|
||||
let start = this.mouseDownStartIndex
|
||||
@@ -105,15 +110,46 @@ export class CanvasEvent {
|
||||
this.draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
this.isAllowDrag = true
|
||||
const { index, isDirectHit, isImage } = this.position.getPositionByXY(evt.offsetX, evt.offsetY)
|
||||
const positionResult = this.position.getPositionByXY({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
const {
|
||||
index,
|
||||
isDirectHit,
|
||||
isImage,
|
||||
isTable,
|
||||
trIndex,
|
||||
tdIndex,
|
||||
tdValueIndex,
|
||||
tdId,
|
||||
trId,
|
||||
tableId
|
||||
} = positionResult
|
||||
// 设置位置上下文
|
||||
this.position.setPositionContext({
|
||||
isTable: isTable || false,
|
||||
index,
|
||||
trIndex,
|
||||
tdIndex,
|
||||
tdId,
|
||||
trId,
|
||||
tableId
|
||||
})
|
||||
// 记录选区开始位置
|
||||
this.mouseDownStartIndex = index
|
||||
this.mouseDownStartIndex = isTable ? tdValueIndex! : index
|
||||
// 绘制
|
||||
const isDirectHitImage = isDirectHit && isImage
|
||||
if (~index) {
|
||||
this.range.setRange(index, index)
|
||||
let curIndex = index
|
||||
if (isTable) {
|
||||
this.range.setRange(tdValueIndex!, tdValueIndex!)
|
||||
curIndex = tdValueIndex!
|
||||
} else {
|
||||
this.range.setRange(index, index)
|
||||
}
|
||||
this.draw.render({
|
||||
curIndex: index,
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: !isDirectHitImage,
|
||||
isComputeRowList: false
|
||||
@@ -124,7 +160,8 @@ export class CanvasEvent {
|
||||
if (isDirectHitImage) {
|
||||
const elementList = this.draw.getElementList()
|
||||
const positionList = this.position.getPositionList()
|
||||
this.imageParticle.drawResizer(elementList[index], positionList[index])
|
||||
const curIndex = isTable ? tdValueIndex! : index
|
||||
this.imageParticle.drawResizer(elementList[curIndex], positionList[curIndex])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +204,16 @@ export class CanvasEvent {
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex })
|
||||
} else if (evt.key === KeyMap.Enter) {
|
||||
// 表格需要上下文信息
|
||||
const positionContext = this.position.getPositionContext()
|
||||
let restArg = {}
|
||||
if (positionContext.isTable) {
|
||||
const { tdId, trId, tableId } = positionContext
|
||||
restArg = { tdId, trId, tableId }
|
||||
}
|
||||
const enterText: IElement = {
|
||||
value: ZERO
|
||||
value: ZERO,
|
||||
...restArg
|
||||
}
|
||||
if (isCollspace) {
|
||||
elementList.splice(index + 1, 0, enterText)
|
||||
@@ -277,8 +322,16 @@ export class CanvasEvent {
|
||||
const { index } = cursorPosition
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
const isCollspace = startIndex === endIndex
|
||||
// 表格需要上下文信息
|
||||
const positionContext = this.position.getPositionContext()
|
||||
let restArg = {}
|
||||
if (positionContext.isTable) {
|
||||
const { tdId, trId, tableId } = positionContext
|
||||
restArg = { tdId, trId, tableId }
|
||||
}
|
||||
const inputData: IElement[] = data.split('').map(value => ({
|
||||
value
|
||||
value,
|
||||
...restArg
|
||||
}))
|
||||
let start = 0
|
||||
if (isCollspace) {
|
||||
|
||||
@@ -1,29 +1,41 @@
|
||||
import { ElementType } from "../.."
|
||||
import { ZERO } from "../../dataset/constant/Common"
|
||||
import { IEditorOption } from "../../interface/Editor"
|
||||
import { IElement, IElementPosition } from "../../interface/Element"
|
||||
import { ICurrentPosition } from "../../interface/Position"
|
||||
import { IElementPosition } from "../../interface/Element"
|
||||
import { ICurrentPosition, IGetPositionByXYPayload, IPositionContext } from "../../interface/Position"
|
||||
import { Draw } from "../draw/Draw"
|
||||
|
||||
export class Position {
|
||||
|
||||
private cursorPosition: IElementPosition | null
|
||||
private positionContext: IPositionContext
|
||||
private positionList: IElementPosition[]
|
||||
private elementList: IElement[]
|
||||
|
||||
private draw: Draw
|
||||
private options: Required<IEditorOption>
|
||||
|
||||
constructor(draw: Draw) {
|
||||
this.positionList = []
|
||||
this.elementList = []
|
||||
this.cursorPosition = null
|
||||
this.positionContext = {
|
||||
isTable: false
|
||||
}
|
||||
|
||||
this.draw = draw
|
||||
this.options = draw.getOptions()
|
||||
}
|
||||
|
||||
public getOriginalPositionList(): IElementPosition[] {
|
||||
return this.positionList
|
||||
}
|
||||
|
||||
public getPositionList(): IElementPosition[] {
|
||||
const { isTable } = this.positionContext
|
||||
if (isTable) {
|
||||
const { index, trIndex, tdIndex } = this.positionContext
|
||||
const elementList = this.draw.getOriginalElementList()
|
||||
return elementList[index!].trList![trIndex!].tdList[tdIndex!].positionList || []
|
||||
}
|
||||
return this.positionList
|
||||
}
|
||||
|
||||
@@ -31,7 +43,7 @@ export class Position {
|
||||
this.positionList = payload
|
||||
}
|
||||
|
||||
public setCursorPosition(position: IElementPosition) {
|
||||
public setCursorPosition(position: IElementPosition | null) {
|
||||
this.cursorPosition = position
|
||||
}
|
||||
|
||||
@@ -39,22 +51,69 @@ export class Position {
|
||||
return this.cursorPosition
|
||||
}
|
||||
|
||||
public getPositionByXY(x: number, y: number): ICurrentPosition {
|
||||
this.elementList = this.draw.getElementList()
|
||||
public getPositionContext(): IPositionContext {
|
||||
return this.positionContext
|
||||
}
|
||||
|
||||
public setPositionContext(payload: IPositionContext) {
|
||||
this.positionContext = payload
|
||||
}
|
||||
|
||||
public getPositionByXY(payload: IGetPositionByXYPayload): ICurrentPosition {
|
||||
const { x, y, isTable } = payload
|
||||
let { elementList, positionList } = payload
|
||||
if (!elementList) {
|
||||
elementList = this.draw.getOriginalElementList()
|
||||
}
|
||||
if (!positionList) {
|
||||
positionList = this.positionList
|
||||
}
|
||||
const curPageNo = this.draw.getPageNo()
|
||||
for (let j = 0; j < this.positionList.length; j++) {
|
||||
const { index, pageNo, coordinate: { leftTop, rightTop, leftBottom } } = this.positionList[j]
|
||||
for (let j = 0; j < positionList.length; j++) {
|
||||
const { index, pageNo, coordinate: { leftTop, rightTop, leftBottom } } = positionList[j]
|
||||
if (curPageNo !== pageNo) continue
|
||||
// 命中元素
|
||||
if (leftTop[0] <= x && rightTop[0] >= x && leftTop[1] <= y && leftBottom[1] >= y) {
|
||||
let curPostionIndex = j
|
||||
const element = this.elementList[j]
|
||||
const element = elementList[j]
|
||||
// 表格被命中
|
||||
if (element.type === ElementType.TABLE) {
|
||||
for (let t = 0; t < element.trList!.length; t++) {
|
||||
const tr = element.trList![t]
|
||||
for (let d = 0; d < tr.tdList.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
const tablePosition = this.getPositionByXY({
|
||||
x,
|
||||
y,
|
||||
td,
|
||||
tablePosition: positionList[j],
|
||||
isTable: true,
|
||||
elementList: td.value,
|
||||
positionList: td.positionList
|
||||
})
|
||||
if (~tablePosition.index) {
|
||||
return {
|
||||
index,
|
||||
isImage: tablePosition.isImage,
|
||||
isDirectHit: tablePosition.isDirectHit,
|
||||
isTable: true,
|
||||
tdIndex: d,
|
||||
trIndex: t,
|
||||
tdValueIndex: tablePosition.index,
|
||||
tdId: td.id,
|
||||
trId: tr.id,
|
||||
tableId: element.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 图片区域均为命中
|
||||
if (element.type === ElementType.IMAGE) {
|
||||
return { index: curPostionIndex, isDirectHit: true, isImage: true }
|
||||
}
|
||||
// 判断是否在文字中间前后
|
||||
if (this.elementList[index].value !== ZERO) {
|
||||
if (elementList[index].value !== ZERO) {
|
||||
const valueWidth = rightTop[0] - leftTop[0]
|
||||
if (x < leftTop[0] + valueWidth / 2) {
|
||||
curPostionIndex = j - 1
|
||||
@@ -66,8 +125,22 @@ export class Position {
|
||||
// 非命中区域
|
||||
let isLastArea = false
|
||||
let curPostionIndex = -1
|
||||
// 判断是否在表格内
|
||||
if (isTable) {
|
||||
const { td, tablePosition } = payload
|
||||
if (td && tablePosition) {
|
||||
const { leftTop } = tablePosition.coordinate
|
||||
const tdX = td.x! + leftTop[0]
|
||||
const tdY = td.y! + leftTop[1]
|
||||
const tdWidth = td.width!
|
||||
const tdHeight = td.height!
|
||||
if (!(tdX < x && x < tdX + tdWidth && tdY < y && y < tdY + tdHeight)) {
|
||||
return { index: curPostionIndex }
|
||||
}
|
||||
}
|
||||
}
|
||||
// 判断所属行是否存在元素
|
||||
const firstLetterList = this.positionList.filter(p => p.isLastLetter && p.pageNo === curPageNo)
|
||||
const firstLetterList = positionList.filter(p => p.isLastLetter && p.pageNo === curPageNo)
|
||||
for (let j = 0; j < firstLetterList.length; j++) {
|
||||
const { index, pageNo, coordinate: { leftTop, leftBottom } } = firstLetterList[j]
|
||||
if (curPageNo !== pageNo) continue
|
||||
@@ -75,7 +148,7 @@ export class Position {
|
||||
const isHead = x < this.options.margins[3]
|
||||
// 是否在头部
|
||||
if (isHead) {
|
||||
const headIndex = this.positionList.findIndex(p => p.rowNo === firstLetterList[j].rowNo)
|
||||
const headIndex = positionList.findIndex(p => p.rowNo === firstLetterList[j].rowNo)
|
||||
curPostionIndex = ~headIndex ? headIndex : index
|
||||
} else {
|
||||
curPostionIndex = index
|
||||
@@ -86,7 +159,7 @@ export class Position {
|
||||
}
|
||||
if (!isLastArea) {
|
||||
// 当前页最后一行
|
||||
return { index: firstLetterList[firstLetterList.length - 1]?.index || this.positionList.length - 1 }
|
||||
return { index: firstLetterList[firstLetterList.length - 1]?.index || positionList.length - 1 }
|
||||
}
|
||||
return { index: curPostionIndex }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum ElementType {
|
||||
TEXT = 'text',
|
||||
IMAGE = 'image'
|
||||
IMAGE = 'image',
|
||||
TABLE = 'table'
|
||||
}
|
||||
+4
-16
@@ -1,5 +1,4 @@
|
||||
import './assets/css/index.css'
|
||||
import { ZERO } from './dataset/constant/Common'
|
||||
import { IEditorOption } from './interface/Editor'
|
||||
import { IElement } from './interface/Element'
|
||||
import { Draw } from './core/draw/Draw'
|
||||
@@ -7,8 +6,8 @@ import { Command } from './core/command/Command'
|
||||
import { CommandAdapt } from './core/command/CommandAdapt'
|
||||
import { Listener } from './core/listener/Listener'
|
||||
import { RowFlex } from './dataset/enum/Row'
|
||||
import { getUUID } from './utils'
|
||||
import { ElementType } from './dataset/enum/Element'
|
||||
import { formatElementList } from './utils/element'
|
||||
|
||||
export default class Editor {
|
||||
|
||||
@@ -42,22 +41,11 @@ export default class Editor {
|
||||
marginIndicatorSize: 35,
|
||||
marginIndicatorColor: '#BABABA',
|
||||
margins: [100, 120, 100, 120],
|
||||
tdPadding: 5,
|
||||
defaultTdHeight: 40,
|
||||
...options
|
||||
}
|
||||
if (elementList[0]?.value !== ZERO) {
|
||||
elementList.unshift({
|
||||
value: ZERO
|
||||
})
|
||||
}
|
||||
for (let i = 0; i < elementList.length; i++) {
|
||||
const el = elementList[i]
|
||||
if (el.value === '\n') {
|
||||
el.value = ZERO
|
||||
}
|
||||
if (el.type === ElementType.IMAGE) {
|
||||
el.id = getUUID()
|
||||
}
|
||||
}
|
||||
formatElementList(elementList)
|
||||
// 监听
|
||||
this.listener = new Listener()
|
||||
// 启动
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { IElementPosition } from "./Element"
|
||||
import { IRow } from "./Row"
|
||||
|
||||
export interface IDrawOption {
|
||||
curIndex?: number;
|
||||
isSetCursor?: boolean;
|
||||
@@ -16,4 +19,20 @@ export interface IImageParticleCreateResult {
|
||||
resizerHandleList: HTMLDivElement[];
|
||||
resizerImageContainer: HTMLDivElement;
|
||||
resizerImage: HTMLImageElement;
|
||||
}
|
||||
|
||||
export interface IDrawRowPayload {
|
||||
positionList: IElementPosition[];
|
||||
rowList: IRow[];
|
||||
pageNo: number;
|
||||
startIndex: number;
|
||||
startX: number;
|
||||
startY: number;
|
||||
innerWidth: number;
|
||||
}
|
||||
|
||||
export interface IDrawRowResult {
|
||||
x: number;
|
||||
y: number;
|
||||
index: number;
|
||||
}
|
||||
@@ -23,5 +23,7 @@ export interface IEditorOption {
|
||||
resizerSize?: number;
|
||||
marginIndicatorSize?: number;
|
||||
marginIndicatorColor?: string,
|
||||
margins?: [top: number, right: number, bootom: number, left: number]
|
||||
margins?: [top: number, right: number, bootom: number, left: number],
|
||||
tdPadding?: number;
|
||||
defaultTdHeight?: number;
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { ElementType } from "../dataset/enum/Element"
|
||||
import { RowFlex } from "../dataset/enum/Row"
|
||||
import { IColgroup } from "./table/Colgroup"
|
||||
import { ITr } from "./table/Tr"
|
||||
|
||||
export interface IElementMetrics {
|
||||
width: number;
|
||||
height: number;
|
||||
boundingBoxAscent: number;
|
||||
boundingBoxDescent: number;
|
||||
export interface IElementBasic {
|
||||
id?: string;
|
||||
type?: ElementType;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IElementStyle {
|
||||
@@ -23,13 +24,27 @@ export interface IElementStyle {
|
||||
rowMargin?: number;
|
||||
}
|
||||
|
||||
export interface IElementBasic {
|
||||
id?: string;
|
||||
type?: ElementType;
|
||||
value: string;
|
||||
export interface ITableAttr {
|
||||
colgroup?: IColgroup[];
|
||||
trList?: ITr[];
|
||||
}
|
||||
|
||||
export type IElement = IElementBasic & IElementStyle
|
||||
export interface ITableElement {
|
||||
tdId?: string;
|
||||
trId?: string;
|
||||
tableId?: string;
|
||||
}
|
||||
|
||||
export type ITable = ITableAttr & ITableElement
|
||||
|
||||
export type IElement = IElementBasic & IElementStyle & ITable
|
||||
|
||||
export interface IElementMetrics {
|
||||
width: number;
|
||||
height: number;
|
||||
boundingBoxAscent: number;
|
||||
boundingBoxDescent: number;
|
||||
}
|
||||
|
||||
export interface IElementPosition {
|
||||
pageNo: number;
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
import { IElement } from ".."
|
||||
import { IElementPosition } from "./Element"
|
||||
import { ITd } from "./table/Td"
|
||||
|
||||
export interface ICurrentPosition {
|
||||
index: number;
|
||||
isImage?: boolean;
|
||||
isTable?: boolean;
|
||||
isDirectHit?: boolean;
|
||||
trIndex?: number;
|
||||
tdIndex?: number;
|
||||
tdValueIndex?: number;
|
||||
tdId?: string;
|
||||
trId?: string;
|
||||
tableId?: string;
|
||||
}
|
||||
|
||||
export interface IGetPositionByXYPayload {
|
||||
x: number;
|
||||
y: number;
|
||||
isTable?: boolean;
|
||||
td?: ITd;
|
||||
tablePosition?: IElementPosition;
|
||||
elementList?: IElement[];
|
||||
positionList?: IElementPosition[];
|
||||
}
|
||||
|
||||
export interface IPositionContext {
|
||||
isTable: boolean;
|
||||
index?: number;
|
||||
trIndex?: number;
|
||||
tdIndex?: number;
|
||||
tdId?: string;
|
||||
trId?: string;
|
||||
tableId?: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface IColgroup {
|
||||
id?: string;
|
||||
width: number;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IElement, IElementPosition } from "../Element"
|
||||
import { IRow } from "../Row"
|
||||
|
||||
export interface ITd {
|
||||
id?: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
colspan: number;
|
||||
rowspan: number;
|
||||
value: IElement[];
|
||||
isLastRowTd?: boolean;
|
||||
isLastColTd?: boolean;
|
||||
isLastTd?: boolean;
|
||||
rowIndex?: number;
|
||||
colIndex?: number;
|
||||
rowList?: IRow[];
|
||||
positionList?: IElementPosition[];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ITd } from "./Td"
|
||||
|
||||
export interface ITr {
|
||||
id?: string;
|
||||
height: number;
|
||||
tdList: ITd[];
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { getUUID } from "."
|
||||
import { ElementType, IElement } from ".."
|
||||
import { ZERO } from "../dataset/constant/Common"
|
||||
|
||||
export function formatElementList(elementList: IElement[]) {
|
||||
if (elementList[0]?.value !== ZERO) {
|
||||
elementList.unshift({
|
||||
value: ZERO
|
||||
})
|
||||
}
|
||||
for (let i = 0; i < elementList.length; i++) {
|
||||
const el = elementList[i]
|
||||
if (el.type === ElementType.TABLE) {
|
||||
const tableId = getUUID()
|
||||
el.id = tableId
|
||||
if (el.trList) {
|
||||
for (let t = 0; t < el.trList.length; t++) {
|
||||
const tr = el.trList[t]
|
||||
const trId = getUUID()
|
||||
tr.id = trId
|
||||
for (let d = 0; d < tr.tdList.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
const tdId = getUUID()
|
||||
td.id = tdId
|
||||
formatElementList(td.value)
|
||||
for (let v = 0; v < td.value.length; v++) {
|
||||
const value = td.value[v]
|
||||
value.tdId = tdId
|
||||
value.trId = trId
|
||||
value.tableId = tableId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (el.value === '\n') {
|
||||
el.value = ZERO
|
||||
}
|
||||
if (el.type === ElementType.IMAGE) {
|
||||
el.id = getUUID()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user