diff --git a/src/editor/assets/css/index.css b/src/editor/assets/css/index.css
index 09b436e..8384f8d 100644
--- a/src/editor/assets/css/index.css
+++ b/src/editor/assets/css/index.css
@@ -193,4 +193,80 @@
z-index: 9;
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);
}
\ No newline at end of file
diff --git a/src/editor/assets/images/print.svg b/src/editor/assets/images/print.svg
new file mode 100644
index 0000000..5ee44a0
--- /dev/null
+++ b/src/editor/assets/images/print.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/editor/assets/images/submenu-dropdown.svg b/src/editor/assets/images/submenu-dropdown.svg
new file mode 100644
index 0000000..cbfb42e
--- /dev/null
+++ b/src/editor/assets/images/submenu-dropdown.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/editor/core/command/Command.ts b/src/editor/core/command/Command.ts
index 0f2355b..faded6f 100644
--- a/src/editor/core/command/Command.ts
+++ b/src/editor/core/command/Command.ts
@@ -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()
diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts
index eeee2a1..1199027 100644
--- a/src/editor/core/command/CommandAdapt.ts
+++ b/src/editor/core/command/CommandAdapt.ts
@@ -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
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() {
diff --git a/src/editor/core/contextmenu/ContextMenu.ts b/src/editor/core/contextmenu/ContextMenu.ts
new file mode 100644
index 0000000..d1a544a
--- /dev/null
+++ b/src/editor/core/contextmenu/ContextMenu.ts
@@ -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
+
+ 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()
+ }
+
+}
diff --git a/src/editor/core/contextmenu/menus/globalMenus.ts b/src/editor/core/contextmenu/menus/globalMenus.ts
new file mode 100644
index 0000000..72e3f89
--- /dev/null
+++ b/src/editor/core/contextmenu/menus/globalMenus.ts
@@ -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()
+ }
+ }
+]
\ No newline at end of file
diff --git a/src/editor/core/cursor/CursorAgent.ts b/src/editor/core/cursor/CursorAgent.ts
index 4ac669e..dd495aa 100644
--- a/src/editor/core/cursor/CursorAgent.ts
+++ b/src/editor/core/cursor/CursorAgent.ts
@@ -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() {
diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts
index dc90e8b..68e2f78 100644
--- a/src/editor/core/draw/Draw.ts
+++ b/src/editor/core/draw/Draw.ts
@@ -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
}
diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts
index 876cdcc..ade7d8d 100644
--- a/src/editor/core/event/CanvasEvent.ts
+++ b/src/editor/core/event/CanvasEvent.ts
@@ -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() {
diff --git a/src/editor/core/register/Register.ts b/src/editor/core/register/Register.ts
new file mode 100644
index 0000000..3a5e4b7
--- /dev/null
+++ b/src/editor/core/register/Register.ts
@@ -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)
+ }
+
+}
\ No newline at end of file
diff --git a/src/editor/dataset/enum/Editor.ts b/src/editor/dataset/enum/Editor.ts
index 9ec6035..e677f8d 100644
--- a/src/editor/dataset/enum/Editor.ts
+++ b/src/editor/dataset/enum/Editor.ts
@@ -1,7 +1,8 @@
export enum EditorComponent {
MENU = 'menu',
MAIN = 'main',
- FOOTER = 'footer'
+ FOOTER = 'footer',
+ CONTEXTMENU = 'contextmenu'
}
export enum EditorContext {
diff --git a/src/editor/dataset/enum/Event.ts b/src/editor/dataset/enum/Event.ts
new file mode 100644
index 0000000..823f6a0
--- /dev/null
+++ b/src/editor/dataset/enum/Event.ts
@@ -0,0 +1,5 @@
+export enum MouseEventButton {
+ LEFT = 0,
+ CENTER = 1,
+ RIGHT = 2
+}
\ No newline at end of file
diff --git a/src/editor/index.ts b/src/editor/index.ts
index 6599a75..51705a6 100644
--- a/src/editor/index.ts
+++ b/src/editor/index.ts
@@ -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 = {
@@ -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
}
\ No newline at end of file
diff --git a/src/editor/interface/contextmenu/ContextMenu.ts b/src/editor/interface/contextmenu/ContextMenu.ts
new file mode 100644
index 0000000..419c766
--- /dev/null
+++ b/src/editor/interface/contextmenu/ContextMenu.ts
@@ -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[];
+}
\ No newline at end of file