Skip to content

Commit

Permalink
feat: copy and paste original elements #397 (#426)
Browse files Browse the repository at this point in the history
  • Loading branch information
baseWalker authored Jan 26, 2024
1 parent 3cf911c commit 2fc16de
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 19 deletions.
44 changes: 30 additions & 14 deletions src/editor/core/event/handlers/paste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,40 @@ import { VIRTUAL_ELEMENT_TYPE } from '../../../dataset/constant/Element'
import { ElementType } from '../../../dataset/enum/Element'
import { IElement } from '../../../interface/Element'
import { IPasteOption } from '../../../interface/Event'
import { getClipboardData, removeClipboardData } from '../../../utils/clipboard'
import {
formatElementContext,
getElementListByHTML
} from '../../../utils/element'
import { CanvasEvent } from '../CanvasEvent'

export function pastHTML(host: CanvasEvent, htmlText: string) {
export function pasteElement(host: CanvasEvent, elementList: IElement[]) {
const draw = host.getDraw()
const isReadonly = draw.isReadonly()
if (isReadonly) return
const rangeManager = draw.getRange()
const { startIndex } = rangeManager.getRange()
const elementList = draw.getElementList()
const pasteElementList = getElementListByHTML(htmlText, {
innerWidth: draw.getOriginalInnerWidth()
})
const originalElementList = draw.getElementList()
// 全选粘贴无需格式化上下文
if (~startIndex && !rangeManager.getIsSelectAll()) {
// 如果是复制到虚拟元素里,则粘贴列表的虚拟元素需扁平化处理,避免产生新的虚拟元素
const anchorElement = elementList[startIndex]
const anchorElement = originalElementList[startIndex]
if (anchorElement?.titleId || anchorElement?.listId) {
let start = 0
while (start < pasteElementList.length) {
const pasteElement = pasteElementList[start]
while (start < elementList.length) {
const pasteElement = elementList[start]
if (anchorElement.titleId && /^\n/.test(pasteElement.value)) {
break
}
if (VIRTUAL_ELEMENT_TYPE.includes(pasteElement.type!)) {
pasteElementList.splice(start, 1)
elementList.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)
elementList.splice(start, 0, element)
start++
}
}
Expand All @@ -47,11 +45,21 @@ export function pastHTML(host: CanvasEvent, htmlText: string) {
start++
}
}
formatElementContext(elementList, pasteElementList, startIndex, {
formatElementContext(originalElementList, elementList, startIndex, {
isBreakWhenWrap: true
})
}
draw.insertElementList(pasteElementList)
draw.insertElementList(elementList)
}

export function pasteHTML(host: CanvasEvent, htmlText: string) {
const draw = host.getDraw()
const isReadonly = draw.isReadonly()
if (isReadonly) return
const elementList = getElementListByHTML(htmlText, {
innerWidth: draw.getOriginalInnerWidth()
})
pasteElement(host, elementList)
}

export function pasteImage(host: CanvasEvent, file: File | Blob) {
Expand Down Expand Up @@ -96,6 +104,14 @@ export function pasteByEvent(host: CanvasEvent, evt: ClipboardEvent) {
paste(evt)
return
}
// 优先读取编辑器内部粘贴板数据
const clipboardText = clipboardData.getData('text')
const editorClipboardData = getClipboardData()
if (clipboardText === editorClipboardData?.text) {
pasteElement(host, editorClipboardData.elementList)
return
}
removeClipboardData()
// 从粘贴板提取数据
let isHTML = false
for (let i = 0; i < clipboardData.items.length; i++) {
Expand All @@ -116,7 +132,7 @@ export function pasteByEvent(host: CanvasEvent, evt: ClipboardEvent) {
}
if (item.type === 'text/html' && isHTML) {
item.getAsString(htmlText => {
pastHTML(host, htmlText)
pasteHTML(host, htmlText)
})
break
}
Expand Down Expand Up @@ -166,7 +182,7 @@ export async function pasteByApi(host: CanvasEvent, options?: IPasteOption) {
const htmlTextBlob = await item.getType('text/html')
const htmlText = await htmlTextBlob.text()
if (htmlText) {
pastHTML(host, htmlText)
pasteHTML(host, htmlText)
}
} else if (item.types.some(type => type.startsWith('image/'))) {
const type = item.types.find(type => type.startsWith('image/'))!
Expand Down
1 change: 1 addition & 0 deletions src/editor/dataset/constant/Editor.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const EDITOR_COMPONENT = 'editor-component'
export const EDITOR_PREFIX = 'ce'
export const EDITOR_CLIPBOARD = `${EDITOR_PREFIX}-clipboard`
41 changes: 36 additions & 5 deletions src/editor/utils/clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
import { IEditorOption, IElement } from '..'
import { EDITOR_CLIPBOARD } from '../dataset/constant/Editor'
import { DeepRequired } from '../interface/Common'
import { createDomFromElementList } from './element'
import { createDomFromElementList, zipElementList } from './element'

export function writeClipboardItem(text: string, html: string) {
if (!text || !html) return
export interface IClipboardData {
text: string
elementList: IElement[]
}

export function setClipboardData(data: IClipboardData) {
localStorage.setItem(
EDITOR_CLIPBOARD,
JSON.stringify({
text: data.text,
elementList: data.elementList
})
)
}

export function getClipboardData(): IClipboardData | null {
const clipboardText = localStorage.getItem(EDITOR_CLIPBOARD)
return clipboardText ? JSON.parse(clipboardText) : null
}

export function removeClipboardData() {
localStorage.removeItem(EDITOR_CLIPBOARD)
}

export function writeClipboardItem(
text: string,
html: string,
elementList: IElement[]
) {
if (!text && !html && !elementList.length) return
const plainText = new Blob([text], { type: 'text/plain' })
const htmlText = new Blob([html], { type: 'text/html' })
if (window.ClipboardItem) {
Expand All @@ -27,6 +56,8 @@ export function writeClipboardItem(text: string, html: string) {
document.execCommand('copy')
fakeElement.remove()
}
// 编辑器结构化数据
setClipboardData({ text, elementList })
}

export function writeElementList(
Expand All @@ -40,6 +71,6 @@ export function writeElementList(
// 先追加后移除,否则innerText无法解析换行符
clipboardDom.remove()
const html = clipboardDom.innerHTML
if (!text || !html) return
writeClipboardItem(text, html)
if (!text && !html && !elementList.length) return
writeClipboardItem(text, html, zipElementList(elementList))
}

0 comments on commit 2fc16de

Please sign in to comment.