Merge pull request #43 from Hufe921/feature/clipboard
Feature/clipboard
This commit is contained in:
@@ -1270,36 +1270,7 @@ export class CommandAdapt {
|
|||||||
if (!payload.length) return
|
if (!payload.length) return
|
||||||
const isReadonly = this.draw.isReadonly()
|
const isReadonly = this.draw.isReadonly()
|
||||||
if (isReadonly) return
|
if (isReadonly) return
|
||||||
const activeControl = this.control.getActiveControl()
|
this.draw.insertElementList(payload)
|
||||||
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 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import { debounce } from '../../utils'
|
import { debounce } from '../../utils'
|
||||||
|
import { getElementListByHTML } from '../../utils/clipboard'
|
||||||
import { Draw } from '../draw/Draw'
|
import { Draw } from '../draw/Draw'
|
||||||
import { CanvasEvent } from '../event/CanvasEvent'
|
import { CanvasEvent } from '../event/CanvasEvent'
|
||||||
|
|
||||||
export class CursorAgent {
|
export class CursorAgent {
|
||||||
|
|
||||||
|
private draw: Draw
|
||||||
private container: HTMLDivElement
|
private container: HTMLDivElement
|
||||||
private agentCursorDom: HTMLTextAreaElement
|
private agentCursorDom: HTMLTextAreaElement
|
||||||
private canvasEvent: CanvasEvent
|
private canvasEvent: CanvasEvent
|
||||||
|
|
||||||
constructor(draw: Draw, canvasEvent: CanvasEvent) {
|
constructor(draw: Draw, canvasEvent: CanvasEvent) {
|
||||||
|
this.draw = draw
|
||||||
this.container = draw.getContainer()
|
this.container = draw.getContainer()
|
||||||
this.canvasEvent = canvasEvent
|
this.canvasEvent = canvasEvent
|
||||||
// 代理光标绘制
|
// 代理光标绘制
|
||||||
@@ -40,9 +43,34 @@ export class CursorAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _paste(evt: ClipboardEvent) {
|
private _paste(evt: ClipboardEvent) {
|
||||||
const text = evt.clipboardData?.getData('text')
|
const clipboardData = evt.clipboardData
|
||||||
if (text) {
|
if (!clipboardData) return
|
||||||
this.canvasEvent.input(text)
|
// 从粘贴板提取数据
|
||||||
|
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()
|
evt.preventDefault()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import { zipElementList } from '../../utils/element'
|
|||||||
import { CheckboxParticle } from './particle/CheckboxParticle'
|
import { CheckboxParticle } from './particle/CheckboxParticle'
|
||||||
import { DeepRequired } from '../../interface/Common'
|
import { DeepRequired } from '../../interface/Common'
|
||||||
import { ControlComponent } from '../../dataset/enum/Control'
|
import { ControlComponent } from '../../dataset/enum/Control'
|
||||||
|
import { formatElementList } from '../../utils/element'
|
||||||
|
|
||||||
export class Draw {
|
export class Draw {
|
||||||
|
|
||||||
@@ -285,6 +286,41 @@ export class Draw {
|
|||||||
return this.elementList
|
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() {
|
public getOriginalElementList() {
|
||||||
return this.elementList
|
return this.elementList
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { MouseEventButton } from '../../dataset/enum/Event'
|
|||||||
import { KeyMap } from '../../dataset/enum/Keymap'
|
import { KeyMap } from '../../dataset/enum/Keymap'
|
||||||
import { IElement } from '../../interface/Element'
|
import { IElement } from '../../interface/Element'
|
||||||
import { ICurrentPosition } from '../../interface/Position'
|
import { ICurrentPosition } from '../../interface/Position'
|
||||||
import { writeTextByElementList } from '../../utils/clipboard'
|
import { writeElementList } from '../../utils/clipboard'
|
||||||
import { Cursor } from '../cursor/Cursor'
|
import { Cursor } from '../cursor/Cursor'
|
||||||
import { Draw } from '../draw/Draw'
|
import { Draw } from '../draw/Draw'
|
||||||
import { HyperlinkParticle } from '../draw/particle/HyperlinkParticle'
|
import { HyperlinkParticle } from '../draw/particle/HyperlinkParticle'
|
||||||
@@ -571,7 +571,7 @@ export class CanvasEvent {
|
|||||||
const { startIndex, endIndex } = this.range.getRange()
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
const elementList = this.draw.getElementList()
|
const elementList = this.draw.getElementList()
|
||||||
if (startIndex !== endIndex) {
|
if (startIndex !== endIndex) {
|
||||||
writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
writeElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
||||||
let curIndex: number
|
let curIndex: number
|
||||||
if (activeControl) {
|
if (activeControl) {
|
||||||
curIndex = this.control.cut()
|
curIndex = this.control.cut()
|
||||||
@@ -588,7 +588,7 @@ export class CanvasEvent {
|
|||||||
const { startIndex, endIndex } = this.range.getRange()
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
const elementList = this.draw.getElementList()
|
const elementList = this.draw.getElementList()
|
||||||
if (startIndex !== endIndex) {
|
if (startIndex !== endIndex) {
|
||||||
writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
writeElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+114
-22
@@ -1,43 +1,135 @@
|
|||||||
import { IElement } from '..'
|
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 { TEXTLIKE_ELEMENT_TYPE } from '../dataset/constant/Element'
|
||||||
import { ElementType } from '../dataset/enum/Element'
|
import { ElementType } from '../dataset/enum/Element'
|
||||||
|
import { zipElementList } from './element'
|
||||||
|
|
||||||
export function writeText(text: string) {
|
export function writeClipboardItem(text: string, html: string) {
|
||||||
if (!text) return
|
if (!text || !html) return
|
||||||
window.navigator.clipboard.writeText(text.replaceAll(ZERO, `\n`))
|
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[]) {
|
export function writeElementList(elementList: IElement[]) {
|
||||||
let text = ``
|
const clipboardDom: HTMLDivElement = document.createElement('div')
|
||||||
function pickTextFromElement(payload: IElement[]) {
|
function buildDomFromElementList(payload: IElement[]) {
|
||||||
for (let e = 0; e < payload.length; e++) {
|
for (let e = 0; e < payload.length; e++) {
|
||||||
const element = payload[e]
|
const element = payload[e]
|
||||||
|
// 构造表格
|
||||||
if (element.type === ElementType.TABLE) {
|
if (element.type === ElementType.TABLE) {
|
||||||
if (e !== 0) {
|
const tableDom: HTMLTableElement = document.createElement('table')
|
||||||
text += WRAP
|
|
||||||
}
|
|
||||||
const trList = element.trList!
|
const trList = element.trList!
|
||||||
for (let t = 0; t < trList.length; t++) {
|
for (let t = 0; t < trList.length; t++) {
|
||||||
|
const trDom = document.createElement('tr')
|
||||||
const tr = trList[t]
|
const tr = trList[t]
|
||||||
for (let d = 0; d < tr.tdList.length; d++) {
|
for (let d = 0; d < tr.tdList.length; d++) {
|
||||||
|
const tdDom = document.createElement('td')
|
||||||
const td = tr.tdList[d]
|
const td = tr.tdList[d]
|
||||||
// 排除td首个元素
|
tdDom.innerText = td.value[0].value
|
||||||
pickTextFromElement(td.value.slice(1, td.value.length))
|
trDom.append(tdDom)
|
||||||
if (d !== tr.tdList.length - 1) {
|
|
||||||
// td之间加水平制表符
|
|
||||||
text += HORIZON_TAB
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// tr后加换行符
|
tableDom.append(trDom)
|
||||||
text += WRAP
|
|
||||||
}
|
}
|
||||||
|
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)) {
|
} 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)
|
buildDomFromElementList(zipElementList(elementList))
|
||||||
if (!text) return
|
// 写入剪贴板
|
||||||
writeText(text.replace(new RegExp(`^${ZERO}`), ''))
|
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
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user