feat:add context menu
This commit is contained in:
@@ -194,3 +194,79 @@
|
||||
position: absolute;
|
||||
border: 1px dotted #000000;
|
||||
}
|
||||
|
||||
.contextmenu-container {
|
||||
z-index: 9;
|
||||
position: fixed;
|
||||
display: none;
|
||||
padding: 4px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgb(56 56 56 / 20%);
|
||||
border: 1px solid #e2e6ed;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.contextmenu-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.contextmenu-content .contextmenu-sub-item::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
right: 12px;
|
||||
background: url(../images/submenu-dropdown.svg);
|
||||
}
|
||||
|
||||
.contextmenu-content .contextmenu-item {
|
||||
width: 180px;
|
||||
padding: 0 32px 0 16px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contextmenu-content .contextmenu-item.hover {
|
||||
background: rgba(25, 55, 88, .04);
|
||||
}
|
||||
|
||||
.contextmenu-content .contextmenu-item span {
|
||||
font-size: 12px;
|
||||
color: #3d4757;
|
||||
}
|
||||
|
||||
.contextmenu-content .contextmenu-item span.shortcut {
|
||||
color: #767c85;
|
||||
height: 30px;
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.contextmenu-content .contextmenu-item i {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.contextmenu-divider {
|
||||
background-color: #e2e6ed;
|
||||
margin: 4px 16px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.contextmenu-print {
|
||||
background-image: url(../../assets/images/print.svg);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path d="M12 4h-1V2H5v2H4V2a1 1 0 011-1h6a1 1 0 011 1v2zm0 5v4a1 1 0 01-1 1H5a1 1 0 01-1-1V9h1v4h6V9h1z"/><path d="M12 12v-1h2V5H2v6h2v1H2a1 1 0 01-1-1V5a1 1 0 011-1h12a1 1 0 011 1v6a1 1 0 01-1 1h-2z"/><path d="M3 8h10v1H3zm8-2h2v1h-2z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 369 B |
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M0 0h16v16H0z"/><g fill="#767C85"><path d="M7 12.243l-.707-.707 4.243-4.243.707.707z"/><path d="M6.293 4.464L7 3.757 11.243 8l-.707.707z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 260 B |
@@ -4,6 +4,10 @@ import { CommandAdapt } from "./CommandAdapt"
|
||||
|
||||
export class Command {
|
||||
|
||||
private static cut: Function
|
||||
private static copy: Function
|
||||
private static paste: Function
|
||||
private static selectAll: Function
|
||||
private static undo: Function
|
||||
private static redo: Function
|
||||
private static painter: Function
|
||||
@@ -30,6 +34,10 @@ export class Command {
|
||||
private static pageScaleAdd: Function
|
||||
|
||||
constructor(adapt: CommandAdapt) {
|
||||
Command.cut = adapt.cut.bind(adapt)
|
||||
Command.copy = adapt.copy.bind(adapt)
|
||||
Command.paste = adapt.paste.bind(adapt)
|
||||
Command.selectAll = adapt.selectAll.bind(adapt)
|
||||
Command.undo = adapt.undo.bind(adapt)
|
||||
Command.redo = adapt.redo.bind(adapt)
|
||||
Command.painter = adapt.painter.bind(adapt)
|
||||
@@ -56,6 +64,23 @@ export class Command {
|
||||
Command.pageScaleAdd = adapt.pageScaleAdd.bind(adapt)
|
||||
}
|
||||
|
||||
// 全局命令
|
||||
public executeCut() {
|
||||
return Command.cut()
|
||||
}
|
||||
|
||||
public executeCopy() {
|
||||
return Command.copy()
|
||||
}
|
||||
|
||||
public executePaste() {
|
||||
return Command.paste()
|
||||
}
|
||||
|
||||
public executeSelectAll() {
|
||||
return Command.selectAll()
|
||||
}
|
||||
|
||||
// 撤销、重做、格式刷、清除格式
|
||||
public executeUndo() {
|
||||
return Command.undo()
|
||||
|
||||
@@ -15,6 +15,7 @@ import { getUUID } from "../../utils"
|
||||
import { formatElementList } from "../../utils/element"
|
||||
import { printImageBase64 } from "../../utils/print"
|
||||
import { Draw } from "../draw/Draw"
|
||||
import { CanvasEvent } from "../event/CanvasEvent"
|
||||
import { HistoryManager } from "../history/HistoryManager"
|
||||
import { Position } from "../position/Position"
|
||||
import { RangeManager } from "../range/RangeManager"
|
||||
@@ -25,6 +26,7 @@ export class CommandAdapt {
|
||||
private range: RangeManager
|
||||
private position: Position
|
||||
private historyManager: HistoryManager
|
||||
private canvasEvent: CanvasEvent
|
||||
private options: Required<IEditorOption>
|
||||
|
||||
constructor(draw: Draw) {
|
||||
@@ -32,15 +34,35 @@ export class CommandAdapt {
|
||||
this.range = draw.getRange()
|
||||
this.position = draw.getPosition()
|
||||
this.historyManager = draw.getHistoryManager()
|
||||
this.canvasEvent = draw.getCanvasEvent()
|
||||
this.options = draw.getOptions()
|
||||
}
|
||||
|
||||
public cut() {
|
||||
this.canvasEvent.cut()
|
||||
}
|
||||
|
||||
public copy() {
|
||||
this.canvasEvent.copy()
|
||||
}
|
||||
|
||||
public async paste() {
|
||||
const text = await navigator.clipboard.readText()
|
||||
if (text) {
|
||||
this.canvasEvent.input(text)
|
||||
}
|
||||
}
|
||||
|
||||
public selectAll() {
|
||||
this.canvasEvent.selectAll()
|
||||
}
|
||||
|
||||
public undo() {
|
||||
return this.historyManager.undo()
|
||||
this.historyManager.undo()
|
||||
}
|
||||
|
||||
public redo() {
|
||||
return this.historyManager.redo()
|
||||
this.historyManager.redo()
|
||||
}
|
||||
|
||||
public painter() {
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
import { EDITOR_COMPONENT } from "../../dataset/constant/Editor"
|
||||
import { EditorComponent } from "../../dataset/enum/Editor"
|
||||
import { IContextMenuContext, IRegisterContextMenu } from "../../interface/contextmenu/ContextMenu"
|
||||
import { findParent } from "../../utils"
|
||||
import { Command } from "../command/Command"
|
||||
import { Draw } from "../draw/Draw"
|
||||
import { Position } from "../position/Position"
|
||||
import { RangeManager } from "../range/RangeManager"
|
||||
|
||||
interface IRenderPayload {
|
||||
contextMenuList: IRegisterContextMenu[];
|
||||
left: number;
|
||||
top: number;
|
||||
parentMenuConatiner?: HTMLDivElement;
|
||||
}
|
||||
|
||||
export class ContextMenu {
|
||||
|
||||
private command: Command
|
||||
private range: RangeManager
|
||||
private position: Position
|
||||
private container: HTMLDivElement
|
||||
private contextMenuList: IRegisterContextMenu[]
|
||||
private contextMenuContainerList: HTMLDivElement[]
|
||||
private contextMenuRelationShip: Map<HTMLDivElement, HTMLDivElement>
|
||||
|
||||
constructor(draw: Draw, command: Command) {
|
||||
this.command = command
|
||||
this.range = draw.getRange()
|
||||
this.position = draw.getPosition()
|
||||
this.container = draw.getContainer()
|
||||
this.contextMenuList = []
|
||||
this.contextMenuContainerList = []
|
||||
this.contextMenuRelationShip = new Map()
|
||||
// 接管菜单权限
|
||||
document.addEventListener('contextmenu', this._proxyContextMenuEvent.bind(this))
|
||||
// 副作用处理
|
||||
document.addEventListener('mousedown', this._handleEffect.bind(this))
|
||||
}
|
||||
|
||||
private _proxyContextMenuEvent(evt: MouseEvent) {
|
||||
const context = this._getContext()
|
||||
let renderList: IRegisterContextMenu[] = []
|
||||
let isRegisterContextMenu = false
|
||||
for (let c = 0; c < this.contextMenuList.length; c++) {
|
||||
const menu = this.contextMenuList[c]
|
||||
if (menu.isDivider) {
|
||||
renderList.push(menu)
|
||||
} else {
|
||||
const isMatch = menu.when?.(context)
|
||||
if (isMatch) {
|
||||
renderList.push(menu)
|
||||
isRegisterContextMenu = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isRegisterContextMenu) {
|
||||
this.dispose()
|
||||
this._render({
|
||||
contextMenuList: renderList,
|
||||
left: evt.x,
|
||||
top: evt.y,
|
||||
})
|
||||
}
|
||||
evt.preventDefault()
|
||||
}
|
||||
|
||||
private _handleEffect(evt: MouseEvent) {
|
||||
if (this.contextMenuContainerList.length) {
|
||||
// 点击非右键菜单内
|
||||
const contextMenuDom = findParent(
|
||||
evt.target as Element,
|
||||
(node: Node & Element) => !!node && node.nodeType === 1
|
||||
&& node.getAttribute(EDITOR_COMPONENT) === EditorComponent.CONTEXTMENU,
|
||||
true
|
||||
)
|
||||
if (!contextMenuDom) {
|
||||
this.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getContext(): IContextMenuContext {
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
// 是否存在焦点
|
||||
const editorTextFocus = startIndex !== 0 || endIndex !== 0
|
||||
// 是否存在选区
|
||||
const editorHasSelection = editorTextFocus && startIndex !== endIndex
|
||||
// 是否在表格内
|
||||
const positionContext = this.position.getPositionContext()
|
||||
const isInTable = positionContext.isTable
|
||||
return { editorHasSelection, editorTextFocus, isInTable }
|
||||
}
|
||||
|
||||
private _createContextMenuContainer(): HTMLDivElement {
|
||||
const contextMenuContainer = document.createElement('div')
|
||||
contextMenuContainer.classList.add('contextmenu-container')
|
||||
contextMenuContainer.setAttribute(EDITOR_COMPONENT, EditorComponent.CONTEXTMENU)
|
||||
this.container.append(contextMenuContainer)
|
||||
return contextMenuContainer
|
||||
}
|
||||
|
||||
private _render(payload: IRenderPayload): HTMLDivElement {
|
||||
const { contextMenuList, left, top, parentMenuConatiner } = payload
|
||||
const contextMenuContainer = this._createContextMenuContainer()
|
||||
const contextMenuContent = document.createElement('div')
|
||||
contextMenuContent.classList.add('contextmenu-content')
|
||||
// 直接子菜单
|
||||
let childMenuContainer: HTMLDivElement | null = null
|
||||
// 父菜单添加子菜单映射关系
|
||||
if (parentMenuConatiner) {
|
||||
this.contextMenuRelationShip.set(parentMenuConatiner, contextMenuContainer)
|
||||
}
|
||||
for (let c = 0; c < contextMenuList.length; c++) {
|
||||
const menu = contextMenuList[c]
|
||||
if (menu.isDivider) {
|
||||
// 首尾分隔符不渲染
|
||||
if (c !== 0 && c !== contextMenuList.length - 1) {
|
||||
const divider = document.createElement('div')
|
||||
divider.classList.add('contextmenu-divider')
|
||||
contextMenuContent.append(divider)
|
||||
}
|
||||
} else {
|
||||
const menuItem = document.createElement('div')
|
||||
menuItem.classList.add('contextmenu-item')
|
||||
// 菜单事件
|
||||
if (menu.childMenus) {
|
||||
menuItem.classList.add('contextmenu-sub-item')
|
||||
menuItem.onmouseenter = () => {
|
||||
this._setHoverStatus(menuItem, true)
|
||||
this._removeSubMenu(contextMenuContainer)
|
||||
// 子菜单
|
||||
const subMenuRect = menuItem.getBoundingClientRect()
|
||||
const left = subMenuRect.left + subMenuRect.width
|
||||
const top = subMenuRect.top
|
||||
childMenuContainer = this._render({
|
||||
contextMenuList: menu.childMenus!,
|
||||
left,
|
||||
top,
|
||||
parentMenuConatiner: contextMenuContainer
|
||||
})
|
||||
}
|
||||
menuItem.onmouseleave = (evt) => {
|
||||
// 移动到子菜单选项选中状态不变化
|
||||
if (!childMenuContainer || !childMenuContainer.contains(evt.relatedTarget as Node)) {
|
||||
this._setHoverStatus(menuItem, false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
menuItem.onmouseenter = () => {
|
||||
this._setHoverStatus(menuItem, true)
|
||||
this._removeSubMenu(contextMenuContainer)
|
||||
}
|
||||
menuItem.onmouseleave = () => {
|
||||
this._setHoverStatus(menuItem, false)
|
||||
}
|
||||
menuItem.onclick = () => {
|
||||
if (menu.callback) {
|
||||
menu.callback(this.command)
|
||||
}
|
||||
this.dispose()
|
||||
}
|
||||
}
|
||||
// 图标
|
||||
const icon = document.createElement('i')
|
||||
menuItem.append(icon)
|
||||
if (menu.icon) {
|
||||
icon.classList.add(`contextmenu-${menu.icon}`)
|
||||
}
|
||||
// 文本
|
||||
const span = document.createElement('span')
|
||||
span.append(document.createTextNode(menu.name!))
|
||||
menuItem.append(span)
|
||||
// 快捷方式提示
|
||||
if (menu.shortCut) {
|
||||
const span = document.createElement('span')
|
||||
span.classList.add('shortcut')
|
||||
span.append(document.createTextNode(menu.shortCut))
|
||||
menuItem.append(span)
|
||||
}
|
||||
contextMenuContent.append(menuItem)
|
||||
}
|
||||
}
|
||||
contextMenuContainer.append(contextMenuContent)
|
||||
contextMenuContainer.style.display = 'block'
|
||||
contextMenuContainer.style.left = `${left}px`
|
||||
contextMenuContainer.style.top = `${top}px`
|
||||
this.contextMenuContainerList.push(contextMenuContainer)
|
||||
return contextMenuContainer
|
||||
}
|
||||
|
||||
private _removeSubMenu(payload: HTMLDivElement) {
|
||||
const childMenu = this.contextMenuRelationShip.get(payload)
|
||||
if (childMenu) {
|
||||
this._removeSubMenu(childMenu)
|
||||
childMenu.remove()
|
||||
this.contextMenuRelationShip.delete(payload)
|
||||
}
|
||||
}
|
||||
|
||||
private _setHoverStatus(payload: HTMLDivElement, status: boolean) {
|
||||
if (status) {
|
||||
payload.parentNode?.querySelectorAll('.contextmenu-item')
|
||||
.forEach(child => child.classList.remove('hover'))
|
||||
payload.classList.add('hover')
|
||||
} else {
|
||||
payload.classList.remove('hover')
|
||||
}
|
||||
}
|
||||
|
||||
public registerContextMenuList(payload: IRegisterContextMenu[]) {
|
||||
this.contextMenuList.push(...payload)
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.contextMenuContainerList.forEach(child => child.remove())
|
||||
this.contextMenuContainerList = []
|
||||
this.contextMenuRelationShip.clear()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { IRegisterContextMenu } from "../../../interface/contextmenu/ContextMenu"
|
||||
import { Command } from "../../command/Command"
|
||||
|
||||
export const globalMenus: IRegisterContextMenu[] = [
|
||||
{
|
||||
name: '剪切',
|
||||
shortCut: 'Ctrl + X',
|
||||
when: (payload) => {
|
||||
return payload.editorHasSelection
|
||||
},
|
||||
callback: (command: Command) => {
|
||||
command.executeCut()
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '复制',
|
||||
shortCut: 'Ctrl + C',
|
||||
when: (payload) => {
|
||||
return payload.editorHasSelection
|
||||
},
|
||||
callback: (command: Command) => {
|
||||
command.executeCopy()
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '粘贴',
|
||||
shortCut: 'Ctrl + V',
|
||||
when: (payload) => {
|
||||
return payload.editorTextFocus
|
||||
},
|
||||
callback: (command: Command) => {
|
||||
command.executePaste()
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '全选',
|
||||
shortCut: 'Ctrl + A',
|
||||
when: (payload) => {
|
||||
return payload.editorTextFocus
|
||||
},
|
||||
callback: (command: Command) => {
|
||||
command.executeSelectAll()
|
||||
}
|
||||
},
|
||||
{
|
||||
isDivider: true
|
||||
},
|
||||
{
|
||||
icon: 'print',
|
||||
name: '打印',
|
||||
when: () => true,
|
||||
callback: (command: Command) => {
|
||||
command.executePrint()
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -40,7 +40,11 @@ export class CursorAgent {
|
||||
}
|
||||
|
||||
private _paste(evt: ClipboardEvent) {
|
||||
this.canvasEvent.paste(evt)
|
||||
const text = evt.clipboardData?.getData('text')
|
||||
if (text) {
|
||||
this.canvasEvent.input(text)
|
||||
}
|
||||
evt.preventDefault()
|
||||
}
|
||||
|
||||
private _compositionstart() {
|
||||
|
||||
@@ -39,6 +39,7 @@ export class Draw {
|
||||
private elementList: IElement[]
|
||||
private listener: Listener
|
||||
|
||||
private canvasEvent: CanvasEvent
|
||||
private cursor: Cursor
|
||||
private range: RangeManager
|
||||
private margin: Margin
|
||||
@@ -93,10 +94,10 @@ export class Draw {
|
||||
this.pageNumber = new PageNumber(this)
|
||||
new GlobalObserver(this)
|
||||
|
||||
const canvasEvent = new CanvasEvent(this)
|
||||
this.cursor = new Cursor(this, canvasEvent)
|
||||
canvasEvent.register()
|
||||
const globalEvent = new GlobalEvent(this, canvasEvent)
|
||||
this.canvasEvent = new CanvasEvent(this)
|
||||
this.cursor = new Cursor(this, this.canvasEvent)
|
||||
this.canvasEvent.register()
|
||||
const globalEvent = new GlobalEvent(this, this.canvasEvent)
|
||||
globalEvent.register()
|
||||
|
||||
this.rowList = []
|
||||
@@ -225,6 +226,10 @@ export class Draw {
|
||||
return this.elementList
|
||||
}
|
||||
|
||||
public getCanvasEvent(): CanvasEvent {
|
||||
return this.canvasEvent
|
||||
}
|
||||
|
||||
public getListener(): Listener {
|
||||
return this.listener
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ZERO } from "../../dataset/constant/Common"
|
||||
import { ElementStyleKey } from "../../dataset/enum/ElementStyle"
|
||||
import { MouseEventButton } from "../../dataset/enum/Event"
|
||||
import { KeyMap } from "../../dataset/enum/Keymap"
|
||||
import { IElement } from "../../interface/Element"
|
||||
import { writeTextByElementList } from "../../utils/clipboard"
|
||||
@@ -106,6 +107,7 @@ export class CanvasEvent {
|
||||
}
|
||||
|
||||
public mousedown(evt: MouseEvent) {
|
||||
if (evt.button === MouseEventButton.RIGHT) return
|
||||
const target = evt.target as HTMLDivElement
|
||||
const pageIndex = target.dataset.index
|
||||
// 设置pageNo
|
||||
@@ -301,24 +303,11 @@ export class CanvasEvent {
|
||||
this.historyManager.redo()
|
||||
evt.preventDefault()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.C) {
|
||||
if (!isCollspace) {
|
||||
writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
||||
}
|
||||
this.copy()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.X) {
|
||||
if (!isCollspace) {
|
||||
writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||
const curIndex = startIndex
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex })
|
||||
}
|
||||
this.cut()
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.A) {
|
||||
this.range.setRange(0, position.length - 1)
|
||||
this.draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
this.selectAll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +315,7 @@ export class CanvasEvent {
|
||||
if (!this.cursor) return
|
||||
const cursorPosition = this.position.getCursorPosition()
|
||||
if (!data || !cursorPosition || this.isCompositing) return
|
||||
const text = data.replaceAll(`\n`, ZERO)
|
||||
const elementList = this.draw.getElementList()
|
||||
const agentDom = this.cursor.getAgentDom()
|
||||
agentDom.value = ''
|
||||
@@ -339,7 +329,7 @@ export class CanvasEvent {
|
||||
const { tdId, trId, tableId } = positionContext
|
||||
restArg = { tdId, trId, tableId }
|
||||
}
|
||||
const inputData: IElement[] = data.split('').map(value => ({
|
||||
const inputData: IElement[] = text.split('').map(value => ({
|
||||
value,
|
||||
...restArg
|
||||
}))
|
||||
@@ -359,10 +349,34 @@ export class CanvasEvent {
|
||||
this.draw.render({ curIndex })
|
||||
}
|
||||
|
||||
public paste(evt: ClipboardEvent) {
|
||||
const text = evt.clipboardData?.getData('text')
|
||||
this.input(text?.replaceAll(`\n`, ZERO) || '')
|
||||
evt.preventDefault()
|
||||
public cut() {
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
const elementList = this.draw.getElementList()
|
||||
if (startIndex !== endIndex) {
|
||||
writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||
const curIndex = startIndex
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex })
|
||||
}
|
||||
}
|
||||
|
||||
public copy() {
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
const elementList = this.draw.getElementList()
|
||||
if (startIndex !== endIndex) {
|
||||
writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
||||
}
|
||||
}
|
||||
|
||||
public selectAll() {
|
||||
const position = this.position.getPositionList()
|
||||
this.range.setRange(0, position.length - 1)
|
||||
this.draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
|
||||
public compositionstart() {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { IRegisterContextMenu } from "../../interface/contextmenu/ContextMenu"
|
||||
import { ContextMenu } from "../contextmenu/ContextMenu"
|
||||
|
||||
interface IRegisterPayload {
|
||||
contextMenu: ContextMenu
|
||||
}
|
||||
|
||||
export class Register {
|
||||
|
||||
public contextMenuList: (payload: IRegisterContextMenu[]) => void
|
||||
|
||||
constructor(payload: IRegisterPayload) {
|
||||
const { contextMenu } = payload
|
||||
this.contextMenuList = contextMenu.registerContextMenuList.bind(contextMenu)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
export enum EditorComponent {
|
||||
MENU = 'menu',
|
||||
MAIN = 'main',
|
||||
FOOTER = 'footer'
|
||||
FOOTER = 'footer',
|
||||
CONTEXTMENU = 'contextmenu'
|
||||
}
|
||||
|
||||
export enum EditorContext {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum MouseEventButton {
|
||||
LEFT = 0,
|
||||
CENTER = 1,
|
||||
RIGHT = 2
|
||||
}
|
||||
+14
-1
@@ -8,11 +8,16 @@ import { Listener } from './core/listener/Listener'
|
||||
import { RowFlex } from './dataset/enum/Row'
|
||||
import { ElementType } from './dataset/enum/Element'
|
||||
import { formatElementList } from './utils/element'
|
||||
import { Register } from './core/register/Register'
|
||||
import { globalMenus } from './core/contextmenu/menus/GlobalMenus'
|
||||
import { ContextMenu } from './core/contextmenu/ContextMenu'
|
||||
import { IRegisterContextMenu } from './interface/contextmenu/ContextMenu'
|
||||
|
||||
export default class Editor {
|
||||
|
||||
public command: Command
|
||||
public listener: Listener
|
||||
public register: Register
|
||||
|
||||
constructor(container: HTMLDivElement, elementList: IElement[], options: IEditorOption = {}) {
|
||||
const editorOptions: Required<IEditorOption> = {
|
||||
@@ -52,6 +57,13 @@ export default class Editor {
|
||||
const draw = new Draw(container, editorOptions, elementList, this.listener)
|
||||
// 命令
|
||||
this.command = new Command(new CommandAdapt(draw))
|
||||
// 菜单
|
||||
const contextMenu = new ContextMenu(draw, this.command)
|
||||
// 注册
|
||||
this.register = new Register({
|
||||
contextMenu
|
||||
})
|
||||
this.register.contextMenuList(globalMenus)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -65,5 +77,6 @@ export {
|
||||
|
||||
// 对外类型
|
||||
export type {
|
||||
IElement
|
||||
IElement,
|
||||
IRegisterContextMenu
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface IContextMenuContext {
|
||||
editorHasSelection: boolean;
|
||||
editorTextFocus: boolean;
|
||||
isInTable: boolean;
|
||||
}
|
||||
|
||||
export interface IRegisterContextMenu {
|
||||
isDivider?: boolean;
|
||||
icon?: string;
|
||||
name?: string;
|
||||
shortCut?: string;
|
||||
when?: (payload: IContextMenuContext) => boolean;
|
||||
callback?: Function;
|
||||
childMenus?: IRegisterContextMenu[];
|
||||
}
|
||||
Reference in New Issue
Block a user