|
@@ -3,6 +3,7 @@ import { onClickOutsideUp, setCss, isMobileDevice } from '../ui_utils';
|
|
|
import copy from 'copy-to-clipboard';
|
|
|
|
|
|
export class TextEditor {
|
|
|
+ #editList = []
|
|
|
constructor({
|
|
|
eventBus,
|
|
|
contentContainer,
|
|
@@ -16,7 +17,8 @@ export class TextEditor {
|
|
|
messageHandler,
|
|
|
newAdd,
|
|
|
pageViewer,
|
|
|
- hidden
|
|
|
+ hidden,
|
|
|
+ isUpdate
|
|
|
}) {
|
|
|
this.eventBus = eventBus
|
|
|
this.contentContainer = contentContainer
|
|
@@ -31,9 +33,9 @@ export class TextEditor {
|
|
|
this.newAdd = newAdd
|
|
|
this.pageViewer = pageViewer
|
|
|
this.hidden = hidden
|
|
|
-
|
|
|
- this.type = 'text'
|
|
|
+ this.isUpdate = isUpdate
|
|
|
|
|
|
+ this.type = 'text'
|
|
|
this.frame = null
|
|
|
this.canvas = null
|
|
|
this.borderWidth = 2
|
|
@@ -47,6 +49,7 @@ export class TextEditor {
|
|
|
}
|
|
|
this.selectedRects = null
|
|
|
this.selectedCharRange = null
|
|
|
+ this.entireCharRange = null
|
|
|
this.state = 0 // 0 未选中;1 已选中;2 编辑状态
|
|
|
this.mouseDown = false
|
|
|
this.moving = false
|
|
@@ -62,6 +65,7 @@ export class TextEditor {
|
|
|
this.startPoint = null
|
|
|
this.endPoint = null
|
|
|
this.composing = false // 组合输入状态
|
|
|
+ this.needUndoRec = false // 执行操作后,需要添加撤销记录
|
|
|
|
|
|
this.isFirefox = navigator.userAgent.indexOf('Firefox') > -1
|
|
|
this.mousedown = isMobileDevice ? 'touchstart' : 'mousedown'
|
|
@@ -80,6 +84,7 @@ export class TextEditor {
|
|
|
this.onCompositionstart = this.handleCompositionstart.bind(this)
|
|
|
this.onCompositionend = this.handleCompositionend.bind(this)
|
|
|
this.onShowContentEditorType = this.handleShow.bind(this)
|
|
|
+ this.onHandlePopup = this.handlePopup.bind(this)
|
|
|
|
|
|
this.render()
|
|
|
}
|
|
@@ -87,6 +92,7 @@ export class TextEditor {
|
|
|
async render () {
|
|
|
this.eventBus._on('textPropertyChanged', this.onHandlePropertyPanelChanged)
|
|
|
this.eventBus._on('showContentEditorType', this.onShowContentEditorType)
|
|
|
+ this.eventBus._on('contentEditorPopupClicked', this.onHandlePopup)
|
|
|
|
|
|
await this.getRect()
|
|
|
this.canvas = document.createElement('canvas')
|
|
@@ -137,20 +143,25 @@ export class TextEditor {
|
|
|
height: '100%',
|
|
|
resize: 'none',
|
|
|
opacity: 0,
|
|
|
- touchAction: 'none',
|
|
|
- zIndex: 1
|
|
|
+ touchAction: 'none'
|
|
|
}
|
|
|
)
|
|
|
+ textarea.name = 'text-editor'
|
|
|
this.textContainer.append(textarea)
|
|
|
this.textarea = textarea
|
|
|
let text = await this.getText()
|
|
|
- this.textarea.innerHTML = text
|
|
|
+ this.textarea.value = text
|
|
|
|
|
|
- this.frameContainer.append(this.textContainer)
|
|
|
+ this.frameContainer?.append(this.textContainer)
|
|
|
|
|
|
// this.textContainer.addEventListener('click', this.onClick)
|
|
|
this.textContainer.addEventListener(this.mousedown, this.onMousedown)
|
|
|
this.textContainer.addEventListener(this.mouseup, this.onMouseup)
|
|
|
+ isMobileDevice && this.textarea.addEventListener('contextmenu', (event) => event.preventDefault())
|
|
|
+
|
|
|
+ this.outerLineContainer = document.createElement('div')
|
|
|
+ this.outerLineContainer.className = 'outline-container'
|
|
|
+ this.outerLineContainer.style.position = 'absolute'
|
|
|
|
|
|
const outerLine = createSvg('svg', {
|
|
|
class: 'outerline'
|
|
@@ -301,13 +312,18 @@ export class TextEditor {
|
|
|
this.outerLine.append(this.rightRect)
|
|
|
this.outerLine.append(this.topRightRect)
|
|
|
this.outerLine.append(this.topRect)
|
|
|
+ this.outerLineContainer.append(this.outerLine)
|
|
|
|
|
|
this.outerLine.addEventListener(this.mousedown, this.onMousedown)
|
|
|
this.outerLine.addEventListener(this.mouseup, this.onMouseup)
|
|
|
|
|
|
if (this.newAdd) {
|
|
|
this.goEditing()
|
|
|
+ this.addUndoHistory()
|
|
|
+ this.newAdd = false
|
|
|
}
|
|
|
+
|
|
|
+ this.isUpdate && await this.updateCanvas()
|
|
|
}
|
|
|
|
|
|
getActualRect (viewport, s, frame) {
|
|
@@ -387,6 +403,7 @@ export class TextEditor {
|
|
|
}
|
|
|
|
|
|
handleMouseDown (e) {
|
|
|
+ isMobileDevice && e.preventDefault()
|
|
|
if (e.button === 2 || this.hidden) return // 右键点击不执行
|
|
|
|
|
|
if (this.state === 1) {
|
|
@@ -460,9 +477,10 @@ export class TextEditor {
|
|
|
if (!this.mouseDown) return
|
|
|
this.mouseDown = false
|
|
|
this.moving = false
|
|
|
+ this.textContainer.removeEventListener(this.mousemove, this.onMousemove)
|
|
|
|
|
|
if (e.type === 'touchend') {
|
|
|
- document.body.style.overscrollBehavior = 'auto';
|
|
|
+ document.querySelector('.document-container').style.overflow = 'auto'
|
|
|
}
|
|
|
|
|
|
let flag = true
|
|
@@ -474,6 +492,10 @@ export class TextEditor {
|
|
|
|
|
|
this.contentContainer.selectedFrameIndex = this.editAreaIndex
|
|
|
this.eventBus.dispatch('changeRightPanelBtnDisabled', false)
|
|
|
+ this.eventBus.dispatch('contentBoxSelected', {
|
|
|
+ type: 'text',
|
|
|
+ pageNumber: this.pageViewer.pageIndex + 1,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
if (this.state === 1) {
|
|
@@ -494,7 +516,7 @@ export class TextEditor {
|
|
|
this.end = this.newEnd
|
|
|
}
|
|
|
|
|
|
- this.container.append(this.outerLine)
|
|
|
+ this.container.append(this.outerLineContainer)
|
|
|
|
|
|
if (this.mouseMoved) {
|
|
|
const { start, end } = this.getInitialPoint(this.start, this.end)
|
|
@@ -510,7 +532,8 @@ export class TextEditor {
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
rect
|
|
|
})
|
|
|
- this.updateCanvas({oldPoint, newPoint})
|
|
|
+ this.needUndoRec = true
|
|
|
+ await this.updateCanvas({oldPoint, newPoint})
|
|
|
|
|
|
} else if (!this.mouseMoved && flag) {
|
|
|
this.state = 2
|
|
@@ -518,14 +541,17 @@ export class TextEditor {
|
|
|
this.eventBus.dispatch('contentPropertyChange', { type: 'text', isOpen: true })
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ this.showPopup()
|
|
|
}
|
|
|
|
|
|
if (this.state === 2) {
|
|
|
- this.outerLine.remove()
|
|
|
+ this.outerLineContainer.remove()
|
|
|
this.frameContainer.classList.add('editing')
|
|
|
|
|
|
let endPoint
|
|
|
if (isMobileDevice) {
|
|
|
+ this.textarea.focus()
|
|
|
const offsetX = e.changedTouches[0].clientX - this.pageViewer.div.getBoundingClientRect().left
|
|
|
const offsetY = e.changedTouches[0].clientY - this.pageViewer.div.getBoundingClientRect().top
|
|
|
|
|
@@ -545,13 +571,13 @@ export class TextEditor {
|
|
|
|
|
|
if (this.mouseMoved) {
|
|
|
endPoint = this.endPoint
|
|
|
- this.cursor.style.display = 'block'
|
|
|
}
|
|
|
if (!this.startPoint) {
|
|
|
this.startPoint = endPoint
|
|
|
}
|
|
|
const { start, end } = await this.getCharsRange(this.startPoint, endPoint)
|
|
|
this.activeCharPlace = end
|
|
|
+ this.getTextStyle()
|
|
|
|
|
|
if (!this.cursor) {
|
|
|
const cursor = createSvg(
|
|
@@ -569,6 +595,8 @@ export class TextEditor {
|
|
|
)
|
|
|
this.cursor = cursor
|
|
|
this.textContainer.append(this.cursor)
|
|
|
+ } else {
|
|
|
+ this.cursor.style.display = 'block'
|
|
|
}
|
|
|
this.updateCursorLine()
|
|
|
|
|
@@ -581,24 +609,36 @@ export class TextEditor {
|
|
|
this.clearSelectText()
|
|
|
}
|
|
|
|
|
|
- if (!this.selectedRects) {
|
|
|
- this.getTextStyle()
|
|
|
- }
|
|
|
-
|
|
|
this.textarea.focus()
|
|
|
this.textarea.addEventListener('blur', this.onBlur)
|
|
|
this.textarea.addEventListener('keydown', this.onKeydown)
|
|
|
this.textarea.addEventListener(this.textInput, this.onTextInput)
|
|
|
- if (this.isFirefox) {
|
|
|
+ if (this.isFirefox || isMobileDevice) {
|
|
|
this.textarea.addEventListener('compositionstart', this.onCompositionstart)
|
|
|
this.textarea.addEventListener('compositionend', this.onCompositionend)
|
|
|
}
|
|
|
+ onClickOutsideUp([this.textContainer], this.handleTextOutside.bind(this))
|
|
|
+
|
|
|
+ this.hidePopup()
|
|
|
}
|
|
|
|
|
|
this.frameContainer.classList.add('selected')
|
|
|
|
|
|
if (this.state === 1 && !this.mouseMoved) {
|
|
|
- onClickOutsideUp([this.textContainer, this.outerLine, document.querySelector('.editor-panel'), document.getElementById('propertyPanelButton')], this.handleOutside.bind(this))
|
|
|
+ const elements = [this.textContainer, this.outerLine]
|
|
|
+
|
|
|
+ const editorPanel = document.querySelector('.editor-panel')
|
|
|
+ editorPanel && elements.push(editorPanel)
|
|
|
+ const propertyPanelButton = document.getElementById('propertyPanelButton')
|
|
|
+ propertyPanelButton && elements.push(propertyPanelButton)
|
|
|
+ const undo = document.getElementById('undo')
|
|
|
+ undo && elements.push(undo)
|
|
|
+ const redo = document.getElementById('redo')
|
|
|
+ redo && elements.push(redo)
|
|
|
+ const contentEditorPopup = document.querySelector('.content-editor-popup')
|
|
|
+ contentEditorPopup && elements.push(contentEditorPopup)
|
|
|
+
|
|
|
+ onClickOutsideUp(elements, this.handleOutside.bind(this))
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -606,9 +646,10 @@ export class TextEditor {
|
|
|
if (this.contentContainer.selectedFrameIndex !== -1 && this.contentContainer.selectedFrameIndex !== this.editAreaIndex && this.state === 0) return
|
|
|
|
|
|
this.moving = true
|
|
|
+ this.hidePopup()
|
|
|
|
|
|
if (e.type === 'touchmove') {
|
|
|
- document.body.style.overscrollBehavior = 'none';
|
|
|
+ document.querySelector('.document-container').style.overflow = 'hidden'
|
|
|
}
|
|
|
|
|
|
if (this.state === 1) {
|
|
@@ -779,23 +820,40 @@ export class TextEditor {
|
|
|
// console.log('blur')
|
|
|
}
|
|
|
|
|
|
+ handleTextOutside () {
|
|
|
+ this.cursor && (this.cursor.style.display = 'none')
|
|
|
+ }
|
|
|
+
|
|
|
async handleOutside () {
|
|
|
if (this.moving) {
|
|
|
- onClickOutsideUp([this.textContainer, this.outerLine, document.querySelector('.editor-panel'), document.getElementById('propertyPanelButton')], this.handleOutside.bind(this))
|
|
|
+ const elements = [this.textContainer, this.outerLine]
|
|
|
+
|
|
|
+ const editorPanel = document.querySelector('.editor-panel')
|
|
|
+ editorPanel && elements.push(editorPanel)
|
|
|
+ const propertyPanelButton = document.getElementById('propertyPanelButton')
|
|
|
+ propertyPanelButton && elements.push(propertyPanelButton)
|
|
|
+ const undo = document.getElementById('undo')
|
|
|
+ undo && elements.push(undo)
|
|
|
+ const redo = document.getElementById('redo')
|
|
|
+ redo && elements.push(redo)
|
|
|
+ const contentEditorPopup = document.querySelector('.content-editor-popup')
|
|
|
+ contentEditorPopup && elements.push(contentEditorPopup)
|
|
|
+
|
|
|
+ onClickOutsideUp(elements, this.handleOutside.bind(this))
|
|
|
return
|
|
|
}
|
|
|
|
|
|
this.state = this.state === 2 ? 1 : this.state === 1 ? 0 : this.state
|
|
|
|
|
|
- this.frameContainer.classList.remove('editing')
|
|
|
+ this.frameContainer?.classList.remove('editing')
|
|
|
|
|
|
this.clearSelectText()
|
|
|
this.cursor?.remove()
|
|
|
this.cursor = null
|
|
|
|
|
|
if (this.state === 0) {
|
|
|
- this.frameContainer.classList.remove('selected')
|
|
|
- this.outerLine.remove()
|
|
|
+ this.frameContainer?.classList.remove('selected')
|
|
|
+ this.outerLineContainer?.remove()
|
|
|
|
|
|
if (!this.removed) this.contentContainer.selectedFrameIndex = -1
|
|
|
|
|
@@ -804,8 +862,15 @@ export class TextEditor {
|
|
|
if (!hasItem) {
|
|
|
this.eventBus.dispatch('contentPropertyChange', { type: 'text', isOpen: false })
|
|
|
this.eventBus.dispatch('changeRightPanelBtnDisabled', true)
|
|
|
+
|
|
|
+ this.eventBus.dispatch('contentBoxDeselected', {
|
|
|
+ type: 'text',
|
|
|
+ pageNumber: this.pageViewer.pageIndex + 1,
|
|
|
+ })
|
|
|
}
|
|
|
}, 1)
|
|
|
+
|
|
|
+ this.hidePopup()
|
|
|
}
|
|
|
|
|
|
if (this.state === 1) {
|
|
@@ -818,15 +883,31 @@ export class TextEditor {
|
|
|
this.textContainer.removeEventListener(this.mousemove, this.onMousemove)
|
|
|
|
|
|
if (!this.hidden) {
|
|
|
- this.container.append(this.outerLine)
|
|
|
- onClickOutsideUp([this.textContainer, this.outerLine, document.querySelector('.editor-panel'), document.getElementById('propertyPanelButton')], this.handleOutside.bind(this))
|
|
|
+ this.container.append(this.outerLineContainer)
|
|
|
+
|
|
|
+ const elements = [this.textContainer, this.outerLine]
|
|
|
+
|
|
|
+ const editorPanel = document.querySelector('.editor-panel')
|
|
|
+ editorPanel && elements.push(editorPanel)
|
|
|
+ const propertyPanelButton = document.getElementById('propertyPanelButton')
|
|
|
+ propertyPanelButton && elements.push(propertyPanelButton)
|
|
|
+ const undo = document.getElementById('undo')
|
|
|
+ undo && elements.push(undo)
|
|
|
+ const redo = document.getElementById('redo')
|
|
|
+ redo && elements.push(redo)
|
|
|
+ const contentEditorPopup = document.querySelector('.content-editor-popup')
|
|
|
+ contentEditorPopup && elements.push(contentEditorPopup)
|
|
|
+
|
|
|
+ onClickOutsideUp(elements, this.handleOutside.bind(this))
|
|
|
+
|
|
|
+ this.showPopup()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
this.textarea.removeEventListener('blur', this.onBlur)
|
|
|
this.textarea.removeEventListener('keydown', this.onKeydown)
|
|
|
this.textarea.removeEventListener(this.textInput, this.onTextInput)
|
|
|
- if (this.isFirefox) {
|
|
|
+ if (this.isFirefox || isMobileDevice) {
|
|
|
this.textarea.removeEventListener('compositionstart', this.onCompositionstart)
|
|
|
this.textarea.removeEventListener('compositionend', this.onCompositionend)
|
|
|
}
|
|
@@ -840,7 +921,7 @@ export class TextEditor {
|
|
|
|
|
|
this.isToolKey = isToolKey
|
|
|
|
|
|
- if (keyCode === 8 || keyCode === 46) { // 8 delete键,46 backspace键
|
|
|
+ if (keyCode === 8 || keyCode === 46) { // 8 backspace键,46 delete键
|
|
|
if (this.activeCharPlace.SectionIndex === 0
|
|
|
&& this.activeCharPlace.LineIndex === 0
|
|
|
&& this.activeCharPlace.RunIndex === 0
|
|
@@ -858,8 +939,8 @@ export class TextEditor {
|
|
|
newChar = await this.getCharPlace('DeleteChar')
|
|
|
}
|
|
|
this.activeCharPlace = newChar
|
|
|
- const oldRect = this.rect
|
|
|
- await this.updateCanvas(null, oldRect)
|
|
|
+ this.needUndoRec = true
|
|
|
+ await this.updateCanvas(null, true)
|
|
|
this.clearSelectText()
|
|
|
this.updateCursorLine()
|
|
|
return
|
|
@@ -869,8 +950,8 @@ export class TextEditor {
|
|
|
const newChar = await this.getCharPlace('DeleteChars')
|
|
|
this.activeCharPlace = newChar
|
|
|
this.selectedCharRange = null
|
|
|
- const oldRect = this.rect
|
|
|
- await this.updateCanvas(null, oldRect)
|
|
|
+ this.needUndoRec = true
|
|
|
+ await this.updateCanvas(null, true)
|
|
|
this.updateCursorLine()
|
|
|
}
|
|
|
|
|
@@ -911,24 +992,33 @@ export class TextEditor {
|
|
|
|
|
|
let data = e.data
|
|
|
|
|
|
- if (this.isFirefox) {
|
|
|
+ if (this.isFirefox || isMobileDevice) {
|
|
|
if (e.inputType === 'insertLineBreak') {
|
|
|
data = '\n'
|
|
|
} else if (this.composing || data === null) {
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
+ this.#editList.push(data)
|
|
|
+ if (this.#editList.length > 1) return
|
|
|
+ this.handleInsertText()
|
|
|
+ }
|
|
|
|
|
|
+ async handleInsertText() {
|
|
|
const newChar = await this.messageHandler.sendWithPromise('InsertText', {
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
char: this.activeCharPlace,
|
|
|
- text: data
|
|
|
+ text: this.#editList[0]
|
|
|
})
|
|
|
this.activeCharPlace = newChar
|
|
|
|
|
|
- this.saveEdit()
|
|
|
+ this.addUndoHistory()
|
|
|
this.updateCanvas()
|
|
|
this.updateCursorLine()
|
|
|
+ this.#editList.length && this.#editList.shift()
|
|
|
+ if (this.#editList.length > 0) {
|
|
|
+ await this.handleInsertText()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 兼容FireFox 监听中文输入
|
|
@@ -943,7 +1033,7 @@ export class TextEditor {
|
|
|
})
|
|
|
this.activeCharPlace = newChar
|
|
|
|
|
|
- this.saveEdit()
|
|
|
+ this.needUndoRec = true
|
|
|
this.updateCanvas()
|
|
|
this.updateCursorLine()
|
|
|
|
|
@@ -964,6 +1054,7 @@ export class TextEditor {
|
|
|
this.start = start
|
|
|
this.end = end
|
|
|
|
|
|
+ this.oldRect = this.rect || null
|
|
|
const rect = this.rectCalc(start, end)
|
|
|
this.rect = rect
|
|
|
}
|
|
@@ -972,7 +1063,7 @@ export class TextEditor {
|
|
|
async updateCanvas(whole, oldRect) {
|
|
|
if (!this.removed) await this.getRect()
|
|
|
|
|
|
- if (this.frameContainer && this.outerLine) {
|
|
|
+ if (this.frameContainer && this.outerLineContainer) {
|
|
|
this.updateOutline({
|
|
|
start: this.start,
|
|
|
end: this.end
|
|
@@ -1028,7 +1119,7 @@ export class TextEditor {
|
|
|
this.canvas.height = this.rect.height * this.ratio
|
|
|
|
|
|
} else if (oldRect) {
|
|
|
- const wholeAreaRect = this.getEntireArea(oldRect, this.rect)
|
|
|
+ const wholeAreaRect = this.getEntireArea(this.oldRect, this.rect)
|
|
|
|
|
|
const imgRect = {
|
|
|
left: parseInt(wholeAreaRect.left * this.ratio),
|
|
@@ -1086,13 +1177,16 @@ export class TextEditor {
|
|
|
}
|
|
|
|
|
|
// 更新光标位置
|
|
|
- async updateCursorLine () {
|
|
|
+ async updateCursorLine (char) {
|
|
|
+ char && (this.activeCharPlace = char)
|
|
|
+
|
|
|
let cursorPoints = await this.messageHandler.sendWithPromise('GetTextCursorPoints', {
|
|
|
pagePtr: this.pagePtr,
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
char: this.activeCharPlace
|
|
|
})
|
|
|
|
|
|
+ if (!this.cursor) return
|
|
|
this.cursor.innerHTML = ''
|
|
|
const cursorLine = createSvg(
|
|
|
"line",
|
|
@@ -1115,22 +1209,26 @@ export class TextEditor {
|
|
|
char: this.activeCharPlace
|
|
|
})
|
|
|
|
|
|
+ const fontFamily = await this.messageHandler.sendWithPromise('GetBaseFontName', {
|
|
|
+ editAreaPtr: this.editAreaPtr,
|
|
|
+ char: this.activeCharPlace
|
|
|
+ })
|
|
|
+
|
|
|
const style = {
|
|
|
color: textStyle.color,
|
|
|
opacity: Math.round(textStyle.Transparency * 100),
|
|
|
fontStyle: textStyle.IsBold && textStyle.IsItalic ? 3 : !textStyle.IsBold && !textStyle.IsItalic ? 0 : textStyle.IsBold ? 1 : 2,
|
|
|
- fontSize: Math.round(textStyle.FontSize)
|
|
|
+ fontSize: Math.round(textStyle.FontSize),
|
|
|
+ fontFamily,
|
|
|
}
|
|
|
+ this.textStyle = style
|
|
|
|
|
|
- const fontName = await this.messageHandler.sendWithPromise('GetBaseFontName', {
|
|
|
+ const alignType = await this.messageHandler.sendWithPromise('GetTextSectionAlignType', {
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
char: this.activeCharPlace
|
|
|
})
|
|
|
- style.fontFamily = fontName
|
|
|
-
|
|
|
- this.textStyle = style
|
|
|
|
|
|
- this.eventBus.dispatch('contentPropertyChange', { type: 'text', ...style })
|
|
|
+ this.eventBus.dispatch('contentPropertyChange', { type: 'text', ...style, alignType })
|
|
|
}
|
|
|
|
|
|
// 某个操作之后,获取光标所在字符的位置
|
|
@@ -1161,6 +1259,10 @@ export class TextEditor {
|
|
|
// 保存编辑
|
|
|
saveEdit () {
|
|
|
this.messageHandler.sendWithPromise('EndEdit', this.editPagePtr)
|
|
|
+ if (this.needUndoRec) {
|
|
|
+ this.addUndoHistory()
|
|
|
+ this.needUndoRec = false
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 获取区域内的文本
|
|
@@ -1217,7 +1319,7 @@ export class TextEditor {
|
|
|
}
|
|
|
|
|
|
// 获取选中区域里文本的矩形rect
|
|
|
- async getCharsRect (startPoint, endPoint) {
|
|
|
+ async getCharsRect (startPoint, endPoint, isUpdate) {
|
|
|
if (
|
|
|
startPoint.SectionIndex === endPoint.SectionIndex &&
|
|
|
startPoint.LineIndex === endPoint.LineIndex &&
|
|
@@ -1252,8 +1354,11 @@ export class TextEditor {
|
|
|
this.scale,
|
|
|
rect
|
|
|
)
|
|
|
- this.start = start
|
|
|
- this.end = end
|
|
|
+
|
|
|
+ if (!isUpdate) {
|
|
|
+ this.start = start
|
|
|
+ this.end = end
|
|
|
+ }
|
|
|
|
|
|
const selectedRect = this.rectCalc(start, end, 0)
|
|
|
selectedRectList.push(selectedRect)
|
|
@@ -1339,107 +1444,20 @@ export class TextEditor {
|
|
|
this.selectedCharRange = null
|
|
|
}
|
|
|
|
|
|
- async setTextStyle(props) {
|
|
|
- for (const item in props) {
|
|
|
-
|
|
|
- if (item === 'alignType') {
|
|
|
- if (!this.selectedCharRange) {
|
|
|
- await this.messageHandler.sendWithPromise('SetTextAligningSection', {
|
|
|
- editAreaPtr: this.editAreaPtr,
|
|
|
- alignType: props.alignType,
|
|
|
- char: activeCharPlace = {
|
|
|
- SectionIndex: 0,
|
|
|
- LineIndex: 0,
|
|
|
- RunIndex: 0,
|
|
|
- CharIndex: -1
|
|
|
- }
|
|
|
- })
|
|
|
- } else {
|
|
|
- await this.messageHandler.sendWithPromise('SetTextAligningRange', {
|
|
|
- editAreaPtr: this.editAreaPtr,
|
|
|
- alignType: props.alignType,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!this.selectedCharRange && item !== 'alignType') {
|
|
|
- await this.selectAllText()
|
|
|
- }
|
|
|
-
|
|
|
- if (item === 'color') {
|
|
|
- await this.messageHandler.sendWithPromise('SetCharsFontColor', {
|
|
|
- editAreaPtr: this.editAreaPtr,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
- color: this.hexToRgb(props.color)
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- if (item === 'opacity') {
|
|
|
- await this.messageHandler.sendWithPromise('SetCharsFontTransparency', {
|
|
|
- editAreaPtr: this.editAreaPtr,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
- opacity: props.opacity / 100
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- if (item === 'fontSize') {
|
|
|
- await this.messageHandler.sendWithPromise('SetCharsFontSize', {
|
|
|
- editAreaPtr: this.editAreaPtr,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
- fontSize: props.fontSize
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- if (item === 'fontFamily') {
|
|
|
- await this.messageHandler.sendWithPromise('SetFontFromNativeTrueTypeFont', {
|
|
|
- editAreaPtr: this.editAreaPtr,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
- fontFamily: props.fontFamily
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- if (item === 'fontStyle') {
|
|
|
- const fontStyle = props.fontStyle
|
|
|
-
|
|
|
- await this.setCharsFontStyle('ClearCharsFontBold')
|
|
|
- await this.setCharsFontStyle('ClearCharsFontItalic')
|
|
|
-
|
|
|
- if (fontStyle === 1) {
|
|
|
- await this.setCharsFontStyle('SetCharsFontBold')
|
|
|
- } else if (fontStyle === 2) {
|
|
|
- await this.setCharsFontStyle('SetCharsFontItalic')
|
|
|
- } else if (fontStyle === 3) {
|
|
|
- await this.setCharsFontStyle('SetCharsFontBold')
|
|
|
- await this.setCharsFontStyle('SetCharsFontItalic')
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- const oldRect = this.rect
|
|
|
- await this.updateCanvas(null, oldRect)
|
|
|
- }
|
|
|
-
|
|
|
// 属性面板 修改属性
|
|
|
async handlePropertyPanelChanged (props) {
|
|
|
if (this.state === 0) return
|
|
|
|
|
|
let changed = false
|
|
|
|
|
|
- if (!this.selectedCharRange && this.state === 1) {
|
|
|
- await this.selectAllText()
|
|
|
- }
|
|
|
+ await this.getEntireCharPlace()
|
|
|
+ const charRange = this.selectedCharRange || this.entireCharRange
|
|
|
|
|
|
for (const item in props) {
|
|
|
if (props[item] === this.textStyle[item]) continue
|
|
|
|
|
|
if (item === 'alignType') {
|
|
|
- if (!this.selectedCharRange) {
|
|
|
+ if (!this.selectedCharRange && this.state === 2) {
|
|
|
await this.messageHandler.sendWithPromise('SetTextAligningSection', {
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
alignType: props.alignType,
|
|
@@ -1449,21 +1467,17 @@ export class TextEditor {
|
|
|
await this.messageHandler.sendWithPromise('SetTextAligningRange', {
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
alignType: props.alignType,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
+ start: charRange.start,
|
|
|
+ end: charRange.end,
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- if (!this.selectedCharRange && item !== 'alignType' && this.state === 2) {
|
|
|
- await this.selectAllText()
|
|
|
- }
|
|
|
|
|
|
if (item === 'color') {
|
|
|
await this.messageHandler.sendWithPromise('SetCharsFontColor', {
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
+ start: charRange.start,
|
|
|
+ end: charRange.end,
|
|
|
color: this.hexToRgb(props.color)
|
|
|
})
|
|
|
}
|
|
@@ -1471,8 +1485,8 @@ export class TextEditor {
|
|
|
if (item === 'opacity') {
|
|
|
await this.messageHandler.sendWithPromise('SetCharsFontTransparency', {
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
+ start: charRange.start,
|
|
|
+ end: charRange.end,
|
|
|
opacity: props.opacity / 100
|
|
|
})
|
|
|
}
|
|
@@ -1480,8 +1494,8 @@ export class TextEditor {
|
|
|
if (item === 'fontSize') {
|
|
|
await this.messageHandler.sendWithPromise('SetCharsFontSize', {
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
+ start: charRange.start,
|
|
|
+ end: charRange.end,
|
|
|
fontSize: props.fontSize
|
|
|
})
|
|
|
}
|
|
@@ -1489,8 +1503,8 @@ export class TextEditor {
|
|
|
if (item === 'fontFamily') {
|
|
|
await this.messageHandler.sendWithPromise('SetFontFromNativeTrueTypeFont', {
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
+ start: charRange.start,
|
|
|
+ end: charRange.end,
|
|
|
fontFamily: props.fontFamily
|
|
|
})
|
|
|
}
|
|
@@ -1499,7 +1513,9 @@ export class TextEditor {
|
|
|
const fontStyle = props.fontStyle
|
|
|
|
|
|
await this.setCharsFontStyle('ClearCharsFontBold')
|
|
|
+ await this.refreshSelecedRange()
|
|
|
await this.setCharsFontStyle('ClearCharsFontItalic')
|
|
|
+ await this.refreshSelecedRange()
|
|
|
|
|
|
if (fontStyle === 1) {
|
|
|
await this.setCharsFontStyle('SetCharsFontBold')
|
|
@@ -1513,26 +1529,15 @@ export class TextEditor {
|
|
|
|
|
|
this.textStyle[item] = props[item]
|
|
|
changed = true
|
|
|
+ this.needUndoRec = true
|
|
|
}
|
|
|
|
|
|
if (!changed) return
|
|
|
|
|
|
- const oldRect = this.rect
|
|
|
- await this.updateCanvas(null, oldRect)
|
|
|
+ await this.updateCanvas(null, true)
|
|
|
|
|
|
if (this.selectedCharRange && this.state === 2) {
|
|
|
- const { start, end } = await this.messageHandler.sendWithPromise('RefreshRange', {
|
|
|
- editAreaPtr: this.editAreaPtr,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
- })
|
|
|
- this.selectedCharRange = { start, end }
|
|
|
-
|
|
|
- if (this.activeCharPlace.CharIndex === start.CharIndex) {
|
|
|
- this.activeCharPlace = start
|
|
|
- } else {
|
|
|
- this.activeCharPlace = end
|
|
|
- }
|
|
|
+ await this.refreshSelecedRange()
|
|
|
}
|
|
|
|
|
|
if (this.state === 2) {
|
|
@@ -1543,10 +1548,11 @@ export class TextEditor {
|
|
|
|
|
|
// 设置文本样式
|
|
|
async setCharsFontStyle (action) {
|
|
|
+ const charRange = this.selectedCharRange || this.entireCharRange
|
|
|
await this.messageHandler.sendWithPromise('SetCharsFontStyle', {
|
|
|
editAreaPtr: this.editAreaPtr,
|
|
|
- start: this.selectedCharRange.start,
|
|
|
- end: this.selectedCharRange.end,
|
|
|
+ start: charRange.start,
|
|
|
+ end: charRange.end,
|
|
|
fontStyle: action
|
|
|
})
|
|
|
}
|
|
@@ -1638,18 +1644,35 @@ export class TextEditor {
|
|
|
this.textarea.addEventListener('blur', this.onBlur)
|
|
|
this.textarea.addEventListener('keydown', this.onKeydown)
|
|
|
this.textarea.addEventListener(this.textInput, this.onTextInput)
|
|
|
- if (this.isFirefox) {
|
|
|
+ if (this.isFirefox || isMobileDevice) {
|
|
|
this.textarea.addEventListener('compositionstart', this.onCompositionstart)
|
|
|
this.textarea.addEventListener('compositionend', this.onCompositionend)
|
|
|
}
|
|
|
|
|
|
- onClickOutsideUp([this.textContainer, this.outerLine, document.querySelector('.editor-panel'), document.getElementById('propertyPanelButton')], this.handleOutside.bind(this))
|
|
|
+ const elements = [this.textContainer, this.outerLine]
|
|
|
+
|
|
|
+ const editorPanel = document.querySelector('.editor-panel')
|
|
|
+ editorPanel && elements.push(editorPanel)
|
|
|
+ const propertyPanelButton = document.getElementById('propertyPanelButton')
|
|
|
+ propertyPanelButton && elements.push(propertyPanelButton)
|
|
|
+ const undo = document.getElementById('undo')
|
|
|
+ undo && elements.push(undo)
|
|
|
+ const redo = document.getElementById('redo')
|
|
|
+ redo && elements.push(redo)
|
|
|
+ const contentEditorPopup = document.querySelector('.content-editor-popup')
|
|
|
+ contentEditorPopup && elements.push(contentEditorPopup)
|
|
|
+
|
|
|
+ onClickOutsideUp(elements, this.handleOutside.bind(this))
|
|
|
}
|
|
|
|
|
|
// 删除区域
|
|
|
async remove () {
|
|
|
- await this.outerLine.remove()
|
|
|
+ this.state = 0
|
|
|
+ await this.handleOutside()
|
|
|
+
|
|
|
+ await this.outerLineContainer.remove()
|
|
|
this.frameContainer.remove()
|
|
|
+ this.frameContainer = null
|
|
|
this.contentContainer.selectedFrameIndex = -1
|
|
|
this.removed = true
|
|
|
|
|
@@ -1662,9 +1685,26 @@ export class TextEditor {
|
|
|
// editAreaPtr: this.editAreaPtr
|
|
|
// })
|
|
|
|
|
|
+ this.needUndoRec = true
|
|
|
this.updateCanvas()
|
|
|
this.contentContainer.removeEditor(this.editAreaIndex)
|
|
|
this.eventBus.dispatch('contentPropertyChange', { type: 'text', isOpen: false })
|
|
|
+
|
|
|
+ this.eventBus._off('textPropertyChanged', this.onHandlePropertyPanelChanged)
|
|
|
+ this.eventBus._off('showContentEditorType', this.onShowContentEditorType)
|
|
|
+ this.eventBus._off('contentEditorPopupClicked', this.onHandlePopup)
|
|
|
+ }
|
|
|
+
|
|
|
+ removeViewer () {
|
|
|
+ this.state = 0
|
|
|
+ // await this.handleOutside()
|
|
|
+
|
|
|
+ this.outerLineContainer?.remove()
|
|
|
+ this.frameContainer?.remove()
|
|
|
+ this.frameContainer = null
|
|
|
+ this.contentContainer.selectedFrameIndex = -1
|
|
|
+ this.removed = true
|
|
|
+ this.eventBus.dispatch('contentPropertyChange', { type: 'text', isOpen: false })
|
|
|
}
|
|
|
|
|
|
// 复制区域对象
|
|
@@ -1679,9 +1719,16 @@ export class TextEditor {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ // 获取整个区域的文本信息
|
|
|
+ async getEntireCharPlace () {
|
|
|
+ const { start, end } = await this.messageHandler.sendWithPromise('GetBeginAndEndCharPlace', this.editAreaPtr)
|
|
|
+ this.entireCharRange = { start, end }
|
|
|
+ return { start, end }
|
|
|
+ }
|
|
|
+
|
|
|
// 选中整个区域的文本
|
|
|
async selectAllText () {
|
|
|
- const { start, end } = await this.messageHandler.sendWithPromise('GetBeginAndEndCharPlace', this.editAreaPtr)
|
|
|
+ const { start, end } = await this.getEntireCharPlace()
|
|
|
this.selectedCharRange = { start, end }
|
|
|
if (this.state === 2) this.selectedRectList = await this.getCharsRect(start, end)
|
|
|
}
|
|
@@ -1699,4 +1746,136 @@ export class TextEditor {
|
|
|
this.frameContainer && (this.frameContainer.style.display = 'block')
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 添加undo记录
|
|
|
+ addUndoHistory () {
|
|
|
+ this.contentContainer.handleOperateList({
|
|
|
+ editPagePtr: this.editPagePtr,
|
|
|
+ editAreaIndex: this.editAreaIndex,
|
|
|
+ editAreaPtr: this.editAreaPtr,
|
|
|
+ pageIndex: this.pageViewer.pageIndex
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // undo/redo后 更新canvas图
|
|
|
+ async updateCanvasAfterUndoRedo() {
|
|
|
+ await this.getRect()
|
|
|
+
|
|
|
+ if (this.frameContainer && this.outerLineContainer) {
|
|
|
+ this.updateOutline({
|
|
|
+ start: this.start,
|
|
|
+ end: this.end
|
|
|
+ })
|
|
|
+
|
|
|
+ setCss(this.frameContainer, {
|
|
|
+ left: this.rect.left + 'px',
|
|
|
+ top: this.rect.top + 'px',
|
|
|
+ width: this.rect.width + 'px',
|
|
|
+ height: this.rect.height + 'px'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const wholeAreaRect = this.getEntireArea(this.oldRect, this.rect)
|
|
|
+
|
|
|
+ const imgRect = {
|
|
|
+ left: parseInt(wholeAreaRect.left * this.ratio),
|
|
|
+ top: parseInt(wholeAreaRect.top * this.ratio),
|
|
|
+ right: parseInt(wholeAreaRect.right * this.ratio),
|
|
|
+ bottom: parseInt(wholeAreaRect.bottom * this.ratio),
|
|
|
+ width: parseInt(wholeAreaRect.right * this.ratio) - parseInt(wholeAreaRect.left * this.ratio),
|
|
|
+ height: parseInt(wholeAreaRect.bottom * this.ratio) - parseInt(wholeAreaRect.top * this.ratio),
|
|
|
+ }
|
|
|
+ this.imgRect = imgRect
|
|
|
+
|
|
|
+ this.canvas.width = this.rect.width * this.ratio
|
|
|
+ this.canvas.height = this.rect.height * this.ratio
|
|
|
+
|
|
|
+ let { imageArray } = await this.messageHandler.sendWithPromise('PushRenderTask', {
|
|
|
+ pagePtr: this.pagePtr,
|
|
|
+ scale: this.scale * this.ratio,
|
|
|
+ left: this.imgRect.left,
|
|
|
+ right: this.imgRect.right,
|
|
|
+ bottom: this.imgRect.bottom,
|
|
|
+ top: this.imgRect.top
|
|
|
+ })
|
|
|
+ this.imageArray = imageArray
|
|
|
+
|
|
|
+ this.drawCanvas()
|
|
|
+ this.saveEdit()
|
|
|
+ if (this.contentContainer.selectedFrameIndex === this.editAreaIndex) {
|
|
|
+ this.getTextStyle()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 修改属性后,刷新选中范围的数据
|
|
|
+ async refreshSelecedRange() {
|
|
|
+ const charRange = this.selectedCharRange || this.entireCharRange
|
|
|
+ const { start, end } = await this.messageHandler.sendWithPromise('RefreshRange', {
|
|
|
+ editAreaPtr: this.editAreaPtr,
|
|
|
+ start: charRange.start,
|
|
|
+ end: charRange.end,
|
|
|
+ })
|
|
|
+
|
|
|
+ if (this.selectedCharRange) this.selectedCharRange = { start, end }
|
|
|
+ else this.entireCharRange = { start, end }
|
|
|
+
|
|
|
+ if (this.activeCharPlace.CharIndex === start.CharIndex) this.activeCharPlace = start
|
|
|
+ else this.activeCharPlace = end
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算坐标并显示文本悬浮窗
|
|
|
+ async showPopup() {
|
|
|
+ const pageRect = this.container.getBoundingClientRect()
|
|
|
+ const rect = {
|
|
|
+ left: this.rect.left + pageRect.left,
|
|
|
+ top: this.rect.top + pageRect.top,
|
|
|
+ right: this.rect.width + this.rect.left + pageRect.left,
|
|
|
+ bottom: this.rect.height + this.rect.top + pageRect.top
|
|
|
+ }
|
|
|
+ this.eventBus.dispatch('showContentEditorPopup', { rect })
|
|
|
+ this.isPopupShow = true
|
|
|
+
|
|
|
+ const textRects = []
|
|
|
+ const { start, end } = this.selectedCharRange || await this.getEntireCharPlace()
|
|
|
+ const oriTextRects = await this.getCharsRect(start, end, true)
|
|
|
+
|
|
|
+ for (let i = 0; i < oriTextRects.length; i++) {
|
|
|
+ const textRect = oriTextRects[i]
|
|
|
+ const { left, top, width, height } = textRect
|
|
|
+ textRects.push({
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ right: left + width,
|
|
|
+ bottom: top + height
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const selectedText = await this.getText()
|
|
|
+ this.eventBus.dispatch('contentSelected', {
|
|
|
+ selectedText,
|
|
|
+ pageNumber: this.pageViewer.pageIndex + 1,
|
|
|
+ textRects
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ hidePopup() {
|
|
|
+ if (!this.isPopupShow) return
|
|
|
+ this.isPopupShow = false
|
|
|
+ this.eventBus.dispatch('showContentEditorPopup')
|
|
|
+ }
|
|
|
+
|
|
|
+ async handlePopup(data) {
|
|
|
+ if (this.state === 0) return
|
|
|
+
|
|
|
+ switch (data) {
|
|
|
+ case 'copy':
|
|
|
+ const copyText = await this.getText()
|
|
|
+ copy(copyText)
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'delete':
|
|
|
+ this.remove()
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|