diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 03dc0b3..ff877b7 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -1270,36 +1270,7 @@ export class CommandAdapt { if (!payload.length) return const isReadonly = this.draw.isReadonly() if (isReadonly) return - const activeControl = this.control.getActiveControl() - if (activeControl) return - const isPartRangeInControlOutside = this.control.isPartRangeInControlOutside() - if (isPartRangeInControlOutside) return - const { startIndex, endIndex } = this.range.getRange() - if (!~startIndex && !~endIndex) return - // 格式化element - formatElementList(payload, { - isHandleFirstElement: false, - editorOptions: this.options - }) - const elementList = this.draw.getElementList() - const isCollapsed = startIndex === endIndex - const start = startIndex + 1 - if (!isCollapsed) { - elementList.splice(start, endIndex - startIndex) - } - const positionContext = this.position.getPositionContext() - for (let i = 0; i < payload.length; i++) { - const element = payload[i] - if (positionContext.isTable) { - element.tdId = positionContext.tdId - element.trId = positionContext.trId - element.tableId = positionContext.tableId - } - elementList.splice(start + i, 0, element) - } - const curIndex = startIndex + payload.length - this.range.setRange(curIndex, curIndex) - this.draw.render({ curIndex }) + this.draw.insertElementList(payload) } } \ No newline at end of file diff --git a/src/editor/core/cursor/CursorAgent.ts b/src/editor/core/cursor/CursorAgent.ts index aacf472..0c76478 100644 --- a/src/editor/core/cursor/CursorAgent.ts +++ b/src/editor/core/cursor/CursorAgent.ts @@ -1,14 +1,17 @@ import { debounce } from '../../utils' +import { getElementListByHTML } from '../../utils/clipboard' import { Draw } from '../draw/Draw' import { CanvasEvent } from '../event/CanvasEvent' export class CursorAgent { + private draw: Draw private container: HTMLDivElement private agentCursorDom: HTMLTextAreaElement private canvasEvent: CanvasEvent constructor(draw: Draw, canvasEvent: CanvasEvent) { + this.draw = draw this.container = draw.getContainer() this.canvasEvent = canvasEvent // 代理光标绘制 @@ -40,9 +43,34 @@ export class CursorAgent { } private _paste(evt: ClipboardEvent) { - const text = evt.clipboardData?.getData('text') - if (text) { - this.canvasEvent.input(text) + const clipboardData = evt.clipboardData + if (!clipboardData) return + // 从粘贴板提取数据 + let isHTML = false + for (let i = 0; i < clipboardData.items.length; i++) { + const item = clipboardData.items[i] + if (item.type === 'text/html') { + isHTML = true + break + } + } + for (let i = 0; i < clipboardData.items.length; i++) { + const item = clipboardData.items[i] + if (item.kind !== 'string') continue + if (item.type === 'text/plain' && !isHTML) { + item.getAsString(plainText => { + const elementList = plainText.split('').map(value => ({ + value + })) + this.draw.insertElementList(elementList) + }) + } + if (item.type === 'text/html' && isHTML) { + item.getAsString(htmlText => { + const elementList = getElementListByHTML(htmlText) + this.draw.insertElementList(elementList) + }) + } } evt.preventDefault() } diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 099595a..746f439 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -40,6 +40,7 @@ import { zipElementList } from '../../utils/element' import { CheckboxParticle } from './particle/CheckboxParticle' import { DeepRequired } from '../../interface/Common' import { ControlComponent } from '../../dataset/enum/Control' +import { formatElementList } from '../../utils/element' export class Draw { @@ -285,6 +286,41 @@ export class Draw { return this.elementList } + public insertElementList(payload: IElement[]) { + if (!payload.length) return + const activeControl = this.control.getActiveControl() + if (activeControl) return + const isPartRangeInControlOutside = this.control.isPartRangeInControlOutside() + if (isPartRangeInControlOutside) return + const { startIndex, endIndex } = this.range.getRange() + if (!~startIndex && !~endIndex) return + formatElementList(payload, { + isHandleFirstElement: false, + editorOptions: this.options + }) + const elementList = this.getElementList() + const isCollapsed = startIndex === endIndex + const start = startIndex + 1 + if (!isCollapsed) { + elementList.splice(start, endIndex - startIndex) + } + const positionContext = this.position.getPositionContext() + for (let i = 0; i < payload.length; i++) { + const element = payload[i] + if (positionContext.isTable) { + element.tdId = positionContext.tdId + element.trId = positionContext.trId + element.tableId = positionContext.tableId + } + elementList.splice(start + i, 0, element) + } + const curIndex = startIndex + payload.length + this.range.setRange(curIndex, curIndex) + this.render({ + curIndex + }) + } + public getOriginalElementList() { return this.elementList } diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index 032283b..e27e378 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -6,7 +6,7 @@ import { MouseEventButton } from '../../dataset/enum/Event' import { KeyMap } from '../../dataset/enum/Keymap' import { IElement } from '../../interface/Element' import { ICurrentPosition } from '../../interface/Position' -import { writeTextByElementList } from '../../utils/clipboard' +import { writeElementList } from '../../utils/clipboard' import { Cursor } from '../cursor/Cursor' import { Draw } from '../draw/Draw' import { HyperlinkParticle } from '../draw/particle/HyperlinkParticle' @@ -571,7 +571,7 @@ export class CanvasEvent { const { startIndex, endIndex } = this.range.getRange() const elementList = this.draw.getElementList() if (startIndex !== endIndex) { - writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1)) + writeElementList(elementList.slice(startIndex + 1, endIndex + 1)) let curIndex: number if (activeControl) { curIndex = this.control.cut() @@ -588,7 +588,7 @@ export class CanvasEvent { const { startIndex, endIndex } = this.range.getRange() const elementList = this.draw.getElementList() if (startIndex !== endIndex) { - writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1)) + writeElementList(elementList.slice(startIndex + 1, endIndex + 1)) } } diff --git a/src/editor/utils/clipboard.ts b/src/editor/utils/clipboard.ts index 9b4b815..687ee4b 100644 --- a/src/editor/utils/clipboard.ts +++ b/src/editor/utils/clipboard.ts @@ -1,43 +1,135 @@ import { IElement } from '..' -import { HORIZON_TAB, WRAP, ZERO } from '../dataset/constant/Common' +import { ZERO } from '../dataset/constant/Common' import { TEXTLIKE_ELEMENT_TYPE } from '../dataset/constant/Element' import { ElementType } from '../dataset/enum/Element' +import { zipElementList } from './element' -export function writeText(text: string) { - if (!text) return - window.navigator.clipboard.writeText(text.replaceAll(ZERO, `\n`)) +export function writeClipboardItem(text: string, html: string) { + if (!text || !html) return + const plainText = new Blob([text], { type: 'text/plain' }) + const htmlText = new Blob([html], { type: 'text/html' }) + // @ts-ignore + const item = new ClipboardItem({ + [plainText.type]: plainText, + [htmlText.type]: htmlText + }) + window.navigator.clipboard.write([item]) } -export function writeTextByElementList(elementList: IElement[]) { - let text = `` - function pickTextFromElement(payload: IElement[]) { +export function writeElementList(elementList: IElement[]) { + const clipboardDom: HTMLDivElement = document.createElement('div') + function buildDomFromElementList(payload: IElement[]) { for (let e = 0; e < payload.length; e++) { const element = payload[e] + // 构造表格 if (element.type === ElementType.TABLE) { - if (e !== 0) { - text += WRAP - } + const tableDom: HTMLTableElement = document.createElement('table') const trList = element.trList! for (let t = 0; t < trList.length; t++) { + const trDom = document.createElement('tr') const tr = trList[t] for (let d = 0; d < tr.tdList.length; d++) { + const tdDom = document.createElement('td') const td = tr.tdList[d] - // 排除td首个元素 - pickTextFromElement(td.value.slice(1, td.value.length)) - if (d !== tr.tdList.length - 1) { - // td之间加水平制表符 - text += HORIZON_TAB - } + tdDom.innerText = td.value[0].value + trDom.append(tdDom) } - // tr后加换行符 - text += WRAP + tableDom.append(trDom) + } + clipboardDom.append(tableDom) + } else if (element.type === ElementType.HYPERLINK) { + const a = document.createElement('a') + a.innerText = element.valueList![0].value + if (element.url) { + a.href = element.url } + clipboardDom.append(a) } else if (!element.type || TEXTLIKE_ELEMENT_TYPE.includes(element.type)) { - text += element.value + const span = document.createElement('span') + let text = '' + if (element.type === ElementType.CONTROL) { + text = element.control!.value?.[0]?.value || '' + } else { + text = element.value + } + if (!text) continue + span.innerText = text.replace(new RegExp(`${ZERO}`, 'g'), '\n') + if (element.color) { + span.style.color = element.color + } + if (element.bold) { + span.style.fontWeight = '600' + } + if (element.italic) { + span.style.fontStyle = 'italic' + } + if (element.size) { + span.style.fontSize = `${element.size}px` + } + clipboardDom.append(span) } } } - pickTextFromElement(elementList) - if (!text) return - writeText(text.replace(new RegExp(`^${ZERO}`), '')) -} \ No newline at end of file + buildDomFromElementList(zipElementList(elementList)) + // 写入剪贴板 + const text = clipboardDom.innerText + const html = clipboardDom.innerHTML + if (!text || !html) return + writeClipboardItem(text, html) +} + +export function getElementListByHTML(htmlText: string): IElement[] { + const elementList: IElement[] = [] + function findTextNode(dom: Element | Node) { + if (dom.nodeType === 3) { + const style = window.getComputedStyle(dom.parentNode as Element) + const value = dom.textContent + if (value) { + elementList.push({ + value, + color: style.color, + bold: Number(style.fontWeight) > 500, + italic: style.fontStyle.includes('italic'), + size: Math.floor(Number(style.fontSize.replace('px', ''))) + }) + } + } else if (dom.nodeType === 1) { + const childNodes = dom.childNodes + for (let n = 0; n < childNodes.length; n++) { + const node = childNodes[n] + // br元素与display:block元素需换行 + if (node.nodeName === 'BR') { + elementList.push({ + value: '\n' + }) + } else { + findTextNode(node) + if (node.nodeType === 1 && n !== childNodes.length - 1) { + const display = window.getComputedStyle(node as Element).display + if (display === 'block') { + elementList.push({ + value: '\n' + }) + } + } + } + } + } + } + // 追加dom + const clipboardDom = document.createElement('div') + clipboardDom.innerHTML = htmlText + document.body.appendChild(clipboardDom) + const deleteNodes: ChildNode[] = [] + clipboardDom.childNodes.forEach(child => { + if (child.nodeType !== 1) { + deleteNodes.push(child) + } + }) + deleteNodes.forEach(node => node.remove()) + // 搜索文本节点 + findTextNode(clipboardDom) + // 移除dom + clipboardDom.remove() + return elementList +}