feat: handle list boundary

pr675
Hufe921 3 years ago committed by Hufe
parent c2330a8c11
commit 406fca3590

@ -20,7 +20,7 @@ import { ITd } from '../../interface/table/Td'
import { ITr } from '../../interface/table/Tr'
import { IWatermark } from '../../interface/Watermark'
import { downloadFile, getUUID } from '../../utils'
import { formatElementList, isTextLikeElement } from '../../utils/element'
import { formatElementContext, formatElementList, isTextLikeElement } from '../../utils/element'
import { printImageBase64 } from '../../utils/print'
import { Control } from '../draw/control/Control'
import { Draw } from '../draw/Draw'
@ -543,12 +543,14 @@ export class CommandAdapt {
formatElementList([element], {
editorOptions: this.options
})
formatElementContext(elementList, [element], startIndex)
const curIndex = startIndex + 1
if (startIndex === endIndex) {
this.draw.spliceElementList(elementList, curIndex, 0, element)
} else {
this.draw.spliceElementList(elementList, curIndex, endIndex - startIndex, element)
}
this.draw.spliceElementList(
elementList,
curIndex,
startIndex === endIndex ? 0 : endIndex - startIndex,
element
)
this.range.setRange(curIndex, curIndex)
this.draw.render({ curIndex, isSetCursor: false })
}
@ -1156,11 +1158,13 @@ export class CommandAdapt {
}))
if (!newElementList) return
const start = startIndex + 1
if (startIndex === endIndex) {
this.draw.spliceElementList(elementList, start, 0, ...newElementList)
} else {
this.draw.spliceElementList(elementList, start, endIndex - startIndex, ...newElementList)
}
formatElementContext(elementList, newElementList, startIndex)
this.draw.spliceElementList(
elementList,
start,
startIndex === endIndex ? 0 : endIndex - startIndex,
...newElementList
)
const curIndex = start + newElementList.length - 1
this.range.setRange(curIndex, curIndex)
this.draw.render({ curIndex })
@ -1284,6 +1288,7 @@ export class CommandAdapt {
dashArray: payload
}
// 从行头增加分割线
formatElementContext(elementList, [newElement], startIndex)
if (startIndex !== 0 && elementList[startIndex].value === ZERO) {
this.draw.spliceElementList(elementList, startIndex, 1, newElement)
curIndex = startIndex - 1
@ -1355,11 +1360,13 @@ export class CommandAdapt {
type: ElementType.IMAGE
}
const curIndex = startIndex + 1
if (startIndex === endIndex) {
this.draw.spliceElementList(elementList, curIndex, 0, element)
} else {
this.draw.spliceElementList(elementList, curIndex, endIndex - startIndex, element)
}
formatElementContext(elementList, [element], startIndex)
this.draw.spliceElementList(
elementList,
curIndex,
startIndex === endIndex ? 0 : endIndex - startIndex,
element
)
this.range.setRange(curIndex, curIndex)
this.draw.render({ curIndex })
}
@ -1611,6 +1618,10 @@ export class CommandAdapt {
if (!payload.length) return
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
// 格式化上下文信息
const { startIndex } = this.range.getRange()
const elementList = this.draw.getElementList()
formatElementContext(elementList, payload, startIndex)
this.draw.insertElementList(payload)
}

@ -1,7 +1,11 @@
import { ZERO } from '../../dataset/constant/Common'
import { EDITOR_PREFIX } from '../../dataset/constant/Editor'
import { VIRTUAL_ELEMENT_TYPE } from '../../dataset/constant/Element'
import { ElementType } from '../../dataset/enum/Element'
import { IElement } from '../../interface/Element'
import { debounce } from '../../utils'
import { getElementListByHTML } from '../../utils/clipboard'
import { formatElementContext } from '../../utils/element'
import { Draw } from '../draw/Draw'
import { CanvasEvent } from '../event/CanvasEvent'
@ -47,6 +51,9 @@ export class CursorAgent {
private _paste(evt: ClipboardEvent) {
const clipboardData = evt.clipboardData
if (!clipboardData) return
const rangeManager = this.draw.getRange()
const { startIndex } = rangeManager.getRange()
const elementList = this.draw.getElementList()
// 从粘贴板提取数据
let isHTML = false
for (let i = 0; i < clipboardData.items.length; i++) {
@ -66,10 +73,34 @@ export class CursorAgent {
}
if (item.type === 'text/html' && isHTML) {
item.getAsString(htmlText => {
const elementList = getElementListByHTML(htmlText, {
const pasteElementList = getElementListByHTML(htmlText, {
innerWidth: this.draw.getOriginalInnerWidth()
})
this.draw.insertElementList(elementList)
if (~startIndex) {
// 如果是复制到虚拟元素里,则粘贴列表的虚拟元素需扁平化处理
const anchorElement = elementList[startIndex]
if (anchorElement?.titleId || anchorElement?.listId) {
let start = 0
while (start < pasteElementList.length) {
const pasteElement = pasteElementList[start]
if (VIRTUAL_ELEMENT_TYPE.includes(pasteElement.type!)) {
pasteElementList.splice(start, 1)
if (pasteElement.valueList) {
for (let v = 0; v < pasteElement.valueList.length; v++) {
const element = pasteElement.valueList[v]
if (element.value === ZERO || element.value === '\n') continue
pasteElementList.splice(start, 0, element)
start++
}
}
start--
}
start++
}
}
formatElementContext(elementList, pasteElementList, startIndex)
}
this.draw.insertElementList(pasteElementList)
})
}
} else if (item.kind === 'file') {
@ -84,12 +115,16 @@ export class CursorAgent {
const value = fileReader.result as string
image.src = value
image.onload = () => {
this.draw.insertElementList([{
const imageElement: IElement = {
value,
type: ElementType.IMAGE,
width: image.width,
height: image.height
}])
}
if (~startIndex) {
formatElementContext(elementList, [imageElement], startIndex)
}
this.draw.insertElementList([imageElement])
}
}
}

@ -52,7 +52,7 @@ import { I18n } from '../i18n/I18n'
import { ImageObserver } from '../observer/ImageObserver'
import { Zone } from '../zone/Zone'
import { Footer } from './frame/Footer'
import { EDITOR_ELEMENT_CONTEXT_ATTR, INLINE_ELEMENT_TYPE } from '../../dataset/constant/Element'
import { INLINE_ELEMENT_TYPE } from '../../dataset/constant/Element'
import { ListParticle } from './particle/ListParticle'
export class Draw {
@ -438,18 +438,6 @@ export class Draw {
return this.footerElementList
}
public formatElementContext(anchorElement: IElement, targetElement: IElement) {
for (let i = 0; i < EDITOR_ELEMENT_CONTEXT_ATTR.length; i++) {
const attr = EDITOR_ELEMENT_CONTEXT_ATTR[i]
const value = anchorElement[attr] as never
if (value !== undefined) {
targetElement[attr] = value
} else {
delete targetElement[attr]
}
}
}
public insertElementList(payload: IElement[]) {
if (!payload.length) return
const isPartRangeInControlOutside = this.control.isPartRangeInControlOutside()
@ -501,20 +489,6 @@ export class Draw {
}
}
}
if (items.length) {
// 格式化新增数据
const endIndex = start + deleteCount - 1
const endElement = elementList[endIndex]
const endNextElement = elementList[endIndex + 1]
const anchorElement = endElement?.value === ZERO && endNextElement && endNextElement.value !== ZERO
? endNextElement
: endElement
if (anchorElement) {
for (let i = 0; i < items.length; i++) {
this.formatElementContext(anchorElement, items[i])
}
}
}
elementList.splice(start, deleteCount, ...items)
}

@ -4,7 +4,7 @@ import { IControl, IControlInitOption, IControlInstance, IControlOption } from '
import { IElement, IElementPosition } from '../../../interface/Element'
import { IRange } from '../../../interface/Range'
import { deepClone, splitText } from '../../../utils'
import { pickElementAttr, zipElementList } from '../../../utils/element'
import { formatElementContext, pickElementAttr, zipElementList } from '../../../utils/element'
import { Listener } from '../../listener/Listener'
import { RangeManager } from '../../range/RangeManager'
import { Draw } from '../Draw'
@ -293,14 +293,16 @@ export class Control {
const placeholderStrList = splitText(control.placeholder)
for (let p = 0; p < placeholderStrList.length; p++) {
const value = placeholderStrList[p]
this.draw.spliceElementList(elementList, startIndex + p + 1, 0, {
const newElement: IElement = {
value,
controlId: startElement.controlId,
type: ElementType.CONTROL,
control: startElement.control,
controlComponent: ControlComponent.PLACEHOLDER,
color: this.options.placeholderColor
})
}
formatElementContext(elementList, [newElement], startIndex)
this.draw.spliceElementList(elementList, startIndex + p + 1, 0, newElement)
}
}

@ -5,6 +5,7 @@ import { KeyMap } from '../../../../dataset/enum/KeyMap'
import { IControlInstance } from '../../../../interface/Control'
import { IElement } from '../../../../interface/Element'
import { splitText } from '../../../../utils'
import { formatElementContext } from '../../../../utils/element'
import { Control } from '../Control'
export class SelectControl implements IControlInstance {
@ -189,11 +190,13 @@ export class SelectControl implements IControlInstance {
const data = splitText(valueSet.value)
const draw = this.control.getDraw()
for (let i = 0; i < data.length; i++) {
draw.spliceElementList(elementList, start + i, 0, {
const newElement: IElement = {
...startElement,
value: data[i],
controlComponent: ControlComponent.VALUE
})
}
formatElementContext(elementList, [newElement], startIndex)
draw.spliceElementList(elementList, start + i, 0, newElement)
}
// render
const newIndex = start + data.length - 1

@ -2,6 +2,7 @@ import { ControlComponent } from '../../../../dataset/enum/Control'
import { KeyMap } from '../../../../dataset/enum/KeyMap'
import { IControlInstance } from '../../../../interface/Control'
import { IElement } from '../../../../interface/Element'
import { formatElementContext } from '../../../../utils/element'
import { Control } from '../Control'
export class TextControl implements IControlInstance {
@ -74,11 +75,13 @@ export class TextControl implements IControlInstance {
const startElement = elementList[startIndex]
const start = range.startIndex + 1
for (let i = 0; i < data.length; i++) {
draw.spliceElementList(elementList, start + i, 0, {
const newElement: IElement = {
...startElement,
...data[i],
controlComponent: ControlComponent.VALUE
})
}
formatElementContext(elementList, [newElement], startIndex)
draw.spliceElementList(elementList, start + i, 0, newElement)
}
return start + data.length - 1
}

@ -78,7 +78,7 @@ export class ListParticle {
let text = ''
if (startElement.listType === ListType.UL) {
if (startElement.listStyle) {
text = ulStyleMapping[<UlStyle><unknown>startElement.listStyle]
text = ulStyleMapping[<UlStyle><unknown>startElement.listStyle] || ulStyleMapping[UlStyle.DISC]
}
} else {
text = `${listIndex! + 1}${KeyMap.PERIOD}`

@ -3,6 +3,7 @@ import { EDITOR_ELEMENT_COPY_ATTR } from '../../../dataset/constant/Element'
import { ElementType } from '../../../dataset/enum/Element'
import { IElement } from '../../../interface/Element'
import { splitText } from '../../../utils'
import { formatElementContext } from '../../../utils/element'
import { CanvasEvent } from '../CanvasEvent'
export function input(data: string, host: CanvasEvent) {
@ -72,6 +73,7 @@ export function input(data: string, host: CanvasEvent) {
if (startIndex !== endIndex) {
draw.spliceElementList(elementList, start, endIndex - startIndex)
}
formatElementContext(elementList, inputData, startIndex)
draw.spliceElementList(elementList, start, 0, ...inputData)
curIndex = startIndex + inputData.length
}

@ -3,6 +3,7 @@ import { ZERO } from '../../../dataset/constant/Common'
import { ElementType } from '../../../dataset/enum/Element'
import { KeyMap } from '../../../dataset/enum/KeyMap'
import { IElement, IElementPosition } from '../../../interface/Element'
import { formatElementContext } from '../../../utils/element'
import { isMod } from '../../../utils/hotkey'
import { CanvasEvent } from '../CanvasEvent'
@ -76,6 +77,7 @@ export function keydown(evt: KeyboardEvent, host: CanvasEvent) {
const enterText: IElement = {
value: ZERO
}
formatElementContext(elementList, [enterText], startIndex)
let curIndex: number
if (activeControl && !control.isRangInPostfix()) {
curIndex = control.setValue([enterText])

@ -3,7 +3,7 @@ import { ControlComponent, ControlType } from '../../../dataset/enum/Control'
import { ElementType } from '../../../dataset/enum/Element'
import { IElement } from '../../../interface/Element'
import { deepClone, getUUID } from '../../../utils'
import { formatElementList } from '../../../utils/element'
import { formatElementContext, formatElementList } from '../../../utils/element'
import { CanvasEvent } from '../CanvasEvent'
type IDragElement = IElement & { dragId: string }
@ -103,6 +103,7 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) {
return newElement
}
})
formatElementContext(elementList, replaceElementList, range.startIndex)
// 缓存拖拽选区开始结束id
const cacheRangeStartId = createDragId(cacheElementList[cacheRange.startIndex])
const cacheRangeEndId = createDragId(cacheElementList[cacheRange.endIndex])

@ -95,10 +95,29 @@ export class RangeManager {
if (positionList[start]?.value === ZERO) break
start--
}
// 中间选择
if (startIndex !== endIndex) {
let middle = startIndex + 1
while (middle < endIndex) {
const { pageNo, rowNo } = positionList[middle]
let rowArray = rangeRow.get(pageNo)
if (!rowArray) {
rowArray = []
rangeRow.set(pageNo, rowArray)
}
if (!rowArray.includes(rowNo)) {
rowArray.push(rowNo)
}
middle++
}
}
// 向下查找
let end = startIndex + 1
let end = endIndex
while (end < positionList.length) {
if (elementList[end].titleId !== elementList[end + 1]?.titleId) break
if (
positionList[end].value === ZERO ||
elementList[end].titleId !== elementList[end + 1]?.titleId
) break
const { pageNo, rowNo } = positionList[end]
let rowArray = rangeRow.get(pageNo)
if (!rowArray) {
@ -108,7 +127,6 @@ export class RangeManager {
if (!rowArray.includes(rowNo)) {
rowArray.push(rowNo)
}
if (positionList[end].value === ZERO) break
end++
}
return rangeRow

@ -26,9 +26,7 @@ export const EDITOR_ELEMENT_COPY_ATTR: Array<keyof IElement> = [
'url',
'hyperlinkId',
'dateId',
'dateFormat',
'level',
'titleId'
'dateFormat'
]
export const EDITOR_ELEMENT_ZIP_ATTR: Array<keyof IElement> = [
@ -64,6 +62,8 @@ export const EDITOR_ELEMENT_CONTEXT_ATTR: Array<keyof IElement> = [
'tdId',
'trId',
'tableId',
'level',
'titleId',
'listId',
'listType',
'listStyle'
@ -87,5 +87,12 @@ export const INLINE_ELEMENT_TYPE: ElementType[] = [
export const INLINE_NODE_NAME: string[] = [
'HR',
'TABLE'
'TABLE',
'UL',
'OL'
]
export const VIRTUAL_ELEMENT_TYPE: ElementType[] = [
ElementType.TITLE,
ElementType.LIST
]

@ -1,7 +1,19 @@
import { UlStyle } from '../enum/List'
import { ListStyle, ListType, UlStyle } from '../enum/List'
export const ulStyleMapping: Record<UlStyle, string> = {
[UlStyle.DISC]: '•',
[UlStyle.CIRCLE]: '◦',
[UlStyle.SQUARE]: '▫︎'
}
export const listTypeElementMapping: Record<ListType, string> = {
[ListType.OL]: 'ol',
[ListType.UL]: 'ul'
}
export const listStyleCSSMapping: Record<ListStyle, string> = {
[ListStyle.DISC]: 'disc',
[ListStyle.CIRCLE]: 'circle',
[ListStyle.SQUARE]: 'square',
[ListStyle.DECIMAL]: 'decimal'
}

@ -1,6 +1,7 @@
import { IEditorOption, IElement, RowFlex } from '..'
import { IEditorOption, IElement, ListStyle, ListType, RowFlex } from '..'
import { ZERO } from '../dataset/constant/Common'
import { INLINE_NODE_NAME, TEXTLIKE_ELEMENT_TYPE } from '../dataset/constant/Element'
import { listStyleCSSMapping, listTypeElementMapping } from '../dataset/constant/List'
import { titleNodeNameMapping, titleOrderNumberMapping } from '../dataset/constant/Title'
import { ControlComponent } from '../dataset/enum/Control'
import { ElementType } from '../dataset/enum/Element'
@ -53,9 +54,7 @@ export function convertElementToDom(element: IElement, options: DeepRequired<IEd
if (element.italic) {
dom.style.fontStyle = 'italic'
}
if (element.size) {
dom.style.fontSize = `${element.size}px`
}
dom.style.fontSize = `${element.size || options.defaultSize}px`
if (element.highlight) {
dom.style.backgroundColor = element.highlight
}
@ -100,6 +99,38 @@ export function writeElementList(elementList: IElement[], options: DeepRequired<
const childDom = buildDomFromElementList(zipElementList(element.valueList!))
h.innerHTML = childDom.innerHTML
clipboardDom.append(h)
} else if (element.type === ElementType.LIST) {
const list = document.createElement(listTypeElementMapping[element.listType!])
if (element.listStyle) {
list.style.listStyleType = listStyleCSSMapping[element.listStyle]
}
// 按照换行符拆分
let curListIndex = 0
const listElementListMap: Map<number, IElement[]> = new Map()
const zipList = zipElementList(element.valueList!)
for (let z = 0; z < zipList.length; z++) {
const zipElement = zipList[z]
const zipValueList = zipElement.value.split('\n')
for (let c = 0; c < zipValueList.length; c++) {
if (c > 0) {
curListIndex += 1
}
const value = zipValueList[c]
const listElementList = listElementListMap.get(curListIndex) || []
listElementList.push({
...zipElement,
value,
})
listElementListMap.set(curListIndex, listElementList)
}
}
listElementListMap.forEach(listElementList => {
const li = document.createElement('li')
const childDom = buildDomFromElementList(listElementList)
li.innerHTML = childDom.innerHTML
list.append(li)
})
clipboardDom.append(list)
} else if (element.type === ElementType.IMAGE) {
const img = document.createElement('img')
if (element.value) {
@ -218,6 +249,27 @@ export function getElementListByHTML(htmlText: string, options: IGetElementListB
value: '\n'
})
}
} else if (node.nodeName === 'UL' || node.nodeName === 'OL') {
const listNode = node as HTMLOListElement | HTMLUListElement
const listElement: IElement = {
value: '',
type: ElementType.LIST,
valueList: []
}
if (node.nodeName === 'OL') {
listElement.listType = ListType.OL
} else {
listElement.listType = ListType.UL
listElement.listStyle = <ListStyle><unknown>listNode.style.listStyleType
}
listNode.querySelectorAll('li').forEach(li => {
const liValueList = getElementListByHTML(li.innerHTML, options)
liValueList.unshift({
value: '\n'
})
listElement.valueList!.push(...liValueList)
})
elementList.push(listElement)
} else if (node.nodeName === 'HR') {
elementList.push({
value: '\n',

@ -4,7 +4,7 @@ import { LaTexParticle } from '../core/draw/particle/latex/LaTexParticle'
import { defaultCheckboxOption } from '../dataset/constant/Checkbox'
import { ZERO } from '../dataset/constant/Common'
import { defaultControlOption } from '../dataset/constant/Control'
import { EDITOR_ELEMENT_ZIP_ATTR, TEXTLIKE_ELEMENT_TYPE } from '../dataset/constant/Element'
import { EDITOR_ELEMENT_CONTEXT_ATTR, EDITOR_ELEMENT_ZIP_ATTR, TEXTLIKE_ELEMENT_TYPE } from '../dataset/constant/Element'
import { titleSizeMapping } from '../dataset/constant/Title'
import { ControlComponent, ControlType } from '../dataset/enum/Control'
import { ITd } from '../interface/table/Td'
@ -31,7 +31,8 @@ export function formatElementList(elementList: IElement[], options: IFormatEleme
isHandleFirstElement: true,
...options
}
if (isHandleFirstElement && elementList[0]?.value !== ZERO) {
const startElement = elementList[0]
if (isHandleFirstElement && startElement?.value !== ZERO && startElement?.value !== '\n') {
elementList.unshift({
value: ZERO
})
@ -540,3 +541,24 @@ export function getElementRowFlex(node: HTMLElement) {
export function isTextLikeElement(element: IElement): boolean {
return !element.type || TEXTLIKE_ELEMENT_TYPE.includes(element.type)
}
export function formatElementContext(sourceElementList: IElement[], formatElementList: IElement[], anchorIndex: number) {
const anchorElement = sourceElementList[anchorIndex]
const anchorNextElement = sourceElementList[anchorIndex + 1]
const copyElement = anchorElement?.value === ZERO && anchorNextElement && anchorNextElement.value !== ZERO
? anchorNextElement
: anchorElement
if (!copyElement) return
for (let e = 0; e < formatElementList.length; e++) {
const targetElement = formatElementList[e]
for (let i = 0; i < EDITOR_ELEMENT_CONTEXT_ATTR.length; i++) {
const attr = EDITOR_ELEMENT_CONTEXT_ATTR[i]
const value = copyElement[attr] as never
if (value !== undefined) {
targetElement[attr] = value
} else {
delete targetElement[attr]
}
}
}
}
Loading…
Cancel
Save