diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 578b754..6a355cf 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -183,35 +183,137 @@ export class CommandAdapt { } public sizeAdd() { - this.canvasEvent.sizeAdd() + const isReadonly = this.draw.isReadonly() + if (isReadonly) return + const selection = this.range.getSelection() + if (!selection) return + const lessThanMaxSizeIndex = selection.findIndex(s => !s.size || s.size + 2 <= 72) + const { defaultSize } = this.options + if (!~lessThanMaxSizeIndex) return + selection.forEach(el => { + if (!el.size) { + el.size = defaultSize + } + if (el.size + 2 > 72) return + el.size += 2 + }) + this.draw.render({ isSetCursor: false }) } public sizeMinus() { - this.canvasEvent.sizeMinus() + const isReadonly = this.draw.isReadonly() + if (isReadonly) return + const selection = this.range.getSelection() + if (!selection) return + const greaterThanMaxSizeIndex = selection.findIndex(s => !s.size || s.size - 2 >= 8) + if (!~greaterThanMaxSizeIndex) return + const { defaultSize } = this.options + selection.forEach(el => { + if (!el.size) { + el.size = defaultSize + } + if (el.size - 2 < 8) return + el.size -= 2 + }) + this.draw.render({ isSetCursor: false }) } public bold() { - this.canvasEvent.bold() + const isReadonly = this.draw.isReadonly() + if (isReadonly) return + const selection = this.range.getSelection() + if (!selection) return + const noBoldIndex = selection.findIndex(s => !s.bold) + selection.forEach(el => { + el.bold = !!~noBoldIndex + }) + this.draw.render({ isSetCursor: false }) } public italic() { - this.canvasEvent.italic() + const isReadonly = this.draw.isReadonly() + if (isReadonly) return + const selection = this.range.getSelection() + if (!selection) return + const noItalicIndex = selection.findIndex(s => !s.italic) + selection.forEach(el => { + el.italic = !!~noItalicIndex + }) + this.draw.render({ isSetCursor: false }) } public underline() { - this.canvasEvent.underline() + const isReadonly = this.draw.isReadonly() + if (isReadonly) return + const selection = this.range.getSelection() + if (!selection) return + const noUnderlineIndex = selection.findIndex(s => !s.underline) + selection.forEach(el => { + el.underline = !!~noUnderlineIndex + }) + this.draw.render({ isSetCursor: false }) } public strikeout() { - this.canvasEvent.strikeout() + const isReadonly = this.draw.isReadonly() + if (isReadonly) return + const selection = this.range.getSelection() + if (!selection) return + const noStrikeoutIndex = selection.findIndex(s => !s.strikeout) + selection.forEach(el => { + el.strikeout = !!~noStrikeoutIndex + }) + this.draw.render({ isSetCursor: false }) } public superscript() { - this.canvasEvent.superscript() + const isReadonly = this.draw.isReadonly() + if (isReadonly) return + const activeControl = this.control.getActiveControl() + if (activeControl) return + const selection = this.range.getSelection() + if (!selection) return + const superscriptIndex = selection.findIndex(s => s.type === ElementType.SUPERSCRIPT) + selection.forEach(el => { + // 取消上标 + if (~superscriptIndex) { + if (el.type === ElementType.SUPERSCRIPT) { + el.type = ElementType.TEXT + delete el.actualSize + } + } else { + // 设置上标 + if (!el.type || el.type === ElementType.TEXT || el.type === ElementType.SUBSCRIPT) { + el.type = ElementType.SUPERSCRIPT + } + } + }) + this.draw.render({ isSetCursor: false }) } public subscript() { - this.canvasEvent.subscript() + const isReadonly = this.draw.isReadonly() + if (isReadonly) return + const activeControl = this.control.getActiveControl() + if (activeControl) return + const selection = this.range.getSelection() + if (!selection) return + const subscriptIndex = selection.findIndex(s => s.type === ElementType.SUBSCRIPT) + selection.forEach(el => { + // 取消下标 + if (~subscriptIndex) { + if (el.type === ElementType.SUBSCRIPT) { + el.type = ElementType.TEXT + delete el.actualSize + } + } else { + // 设置下标 + if (!el.type || el.type === ElementType.TEXT || el.type === ElementType.SUPERSCRIPT) { + el.type = ElementType.SUBSCRIPT + } + } + }) + this.draw.render({ isSetCursor: false }) } public color(payload: string) { @@ -237,7 +339,29 @@ export class CommandAdapt { } public rowFlex(payload: RowFlex) { - this.canvasEvent.rowFlex(payload) + const isReadonly = this.draw.isReadonly() + if (isReadonly) return + const { startIndex, endIndex } = this.range.getRange() + if (!~startIndex && !~endIndex) return + const pageNo = this.draw.getPageNo() + const positionList = this.position.getPositionList() + // 开始/结束行号 + const startRowNo = positionList[startIndex].rowNo + const endRowNo = positionList[endIndex].rowNo + const elementList = this.draw.getElementList() + // 当前选区所在行 + for (let p = 0; p < positionList.length; p++) { + const position = positionList[p] + if (position.pageNo !== pageNo) continue + if (position.rowNo > endRowNo) break + if (position.rowNo >= startRowNo && position.rowNo <= endRowNo) { + elementList[p].rowFlex = payload + } + } + // 光标定位 + const isSetCursor = startIndex === endIndex + const curIndex = isSetCursor ? endIndex : startIndex + this.draw.render({ curIndex, isSetCursor }) } public rowMargin(payload: number) { diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index bd21c93..068c3a5 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -1,4 +1,4 @@ -import { ElementType, IEditorOption, RowFlex } from '../..' +import { ElementType, IEditorOption } from '../..' import { ZERO } from '../../dataset/constant/Common' import { EDITOR_ELEMENT_COPY_ATTR } from '../../dataset/constant/Element' import { ElementStyleKey } from '../../dataset/enum/ElementStyle' @@ -454,13 +454,8 @@ export class CanvasEvent { } else if (evt.ctrlKey && evt.key === KeyMap.C) { this.copy() evt.preventDefault() - } else if (evt.ctrlKey && evt.key.toLocaleLowerCase() === KeyMap.X) { - if (isReadonly) return - if (evt.shiftKey) { - this.strikeout() - } else { - this.cut() - } + } else if (evt.ctrlKey && evt.key === KeyMap.X) { + this.cut() evt.preventDefault() } else if (evt.ctrlKey && evt.key === KeyMap.A) { this.selectAll() @@ -471,39 +466,6 @@ export class CanvasEvent { this.listener.saved(this.draw.getValue()) } evt.preventDefault() - } else if (evt.ctrlKey && evt.key === KeyMap.LEFT_BRACKET) { - this.sizeAdd() - evt.preventDefault() - } else if (evt.ctrlKey && evt.key === KeyMap.RIGHT_BRACKET) { - this.sizeMinus() - evt.preventDefault() - } else if (evt.ctrlKey && evt.key === KeyMap.B) { - this.bold() - evt.preventDefault() - } else if (evt.ctrlKey && evt.key === KeyMap.I) { - this.italic() - evt.preventDefault() - } else if (evt.ctrlKey && evt.key === KeyMap.U) { - this.underline() - evt.preventDefault() - } else if (evt.ctrlKey && evt.shiftKey && evt.key === KeyMap.RIGHT_ANGLE_BRACKET) { - this.superscript() - evt.preventDefault() - } else if (evt.ctrlKey && evt.shiftKey && evt.key === KeyMap.LEFT_ANGLE_BRACKET) { - this.subscript() - evt.preventDefault() - } else if (evt.ctrlKey && evt.key === KeyMap.L) { - this.rowFlex(RowFlex.LEFT) - evt.preventDefault() - } else if (evt.ctrlKey && evt.key === KeyMap.E) { - this.rowFlex(RowFlex.CENTER) - evt.preventDefault() - } else if (evt.ctrlKey && evt.key === KeyMap.R) { - this.rowFlex(RowFlex.RIGHT) - evt.preventDefault() - } else if (evt.ctrlKey && evt.key === KeyMap.J) { - this.rowFlex(RowFlex.ALIGNMENT) - evt.preventDefault() } else if (evt.key === KeyMap.ESC) { this.clearPainterStyle() evt.preventDefault() @@ -711,166 +673,6 @@ export class CanvasEvent { }) } - public sizeAdd() { - const isReadonly = this.draw.isReadonly() - if (isReadonly) return - const selection = this.range.getSelection() - if (!selection) return - const lessThanMaxSizeIndex = selection.findIndex(s => !s.size || s.size + 2 <= 72) - const { defaultSize } = this.options - if (!~lessThanMaxSizeIndex) return - selection.forEach(el => { - if (!el.size) { - el.size = defaultSize - } - if (el.size + 2 > 72) return - el.size += 2 - }) - this.draw.render({ isSetCursor: false }) - } - - public sizeMinus() { - const isReadonly = this.draw.isReadonly() - if (isReadonly) return - const selection = this.range.getSelection() - if (!selection) return - const greaterThanMaxSizeIndex = selection.findIndex(s => !s.size || s.size - 2 >= 8) - if (!~greaterThanMaxSizeIndex) return - const { defaultSize } = this.options - selection.forEach(el => { - if (!el.size) { - el.size = defaultSize - } - if (el.size - 2 < 8) return - el.size -= 2 - }) - this.draw.render({ isSetCursor: false }) - } - - public bold() { - const isReadonly = this.draw.isReadonly() - if (isReadonly) return - const selection = this.range.getSelection() - if (!selection) return - const noBoldIndex = selection.findIndex(s => !s.bold) - selection.forEach(el => { - el.bold = !!~noBoldIndex - }) - this.draw.render({ isSetCursor: false }) - } - - public italic() { - const isReadonly = this.draw.isReadonly() - if (isReadonly) return - const selection = this.range.getSelection() - if (!selection) return - const noItalicIndex = selection.findIndex(s => !s.italic) - selection.forEach(el => { - el.italic = !!~noItalicIndex - }) - this.draw.render({ isSetCursor: false }) - } - - public underline() { - const isReadonly = this.draw.isReadonly() - if (isReadonly) return - const selection = this.range.getSelection() - if (!selection) return - const noUnderlineIndex = selection.findIndex(s => !s.underline) - selection.forEach(el => { - el.underline = !!~noUnderlineIndex - }) - this.draw.render({ isSetCursor: false }) - } - - public strikeout() { - const isReadonly = this.draw.isReadonly() - if (isReadonly) return - const selection = this.range.getSelection() - if (!selection) return - const noStrikeoutIndex = selection.findIndex(s => !s.strikeout) - selection.forEach(el => { - el.strikeout = !!~noStrikeoutIndex - }) - this.draw.render({ isSetCursor: false }) - } - - public superscript() { - const isReadonly = this.draw.isReadonly() - if (isReadonly) return - const activeControl = this.control.getActiveControl() - if (activeControl) return - const selection = this.range.getSelection() - if (!selection) return - const superscriptIndex = selection.findIndex(s => s.type === ElementType.SUPERSCRIPT) - selection.forEach(el => { - // 取消上标 - if (~superscriptIndex) { - if (el.type === ElementType.SUPERSCRIPT) { - el.type = ElementType.TEXT - delete el.actualSize - } - } else { - // 设置上标 - if (!el.type || el.type === ElementType.TEXT || el.type === ElementType.SUBSCRIPT) { - el.type = ElementType.SUPERSCRIPT - } - } - }) - this.draw.render({ isSetCursor: false }) - } - - public subscript() { - const isReadonly = this.draw.isReadonly() - if (isReadonly) return - const activeControl = this.control.getActiveControl() - if (activeControl) return - const selection = this.range.getSelection() - if (!selection) return - const subscriptIndex = selection.findIndex(s => s.type === ElementType.SUBSCRIPT) - selection.forEach(el => { - // 取消下标 - if (~subscriptIndex) { - if (el.type === ElementType.SUBSCRIPT) { - el.type = ElementType.TEXT - delete el.actualSize - } - } else { - // 设置下标 - if (!el.type || el.type === ElementType.TEXT || el.type === ElementType.SUPERSCRIPT) { - el.type = ElementType.SUBSCRIPT - } - } - }) - this.draw.render({ isSetCursor: false }) - } - - public rowFlex(payload: RowFlex) { - const isReadonly = this.draw.isReadonly() - if (isReadonly) return - const { startIndex, endIndex } = this.range.getRange() - if (!~startIndex && !~endIndex) return - const pageNo = this.draw.getPageNo() - const positionList = this.position.getPositionList() - // 开始/结束行号 - const startRowNo = positionList[startIndex].rowNo - const endRowNo = positionList[endIndex].rowNo - const elementList = this.draw.getElementList() - // 当前选区所在行 - for (let p = 0; p < positionList.length; p++) { - const position = positionList[p] - if (position.pageNo !== pageNo) continue - if (position.rowNo > endRowNo) break - if (position.rowNo >= startRowNo && position.rowNo <= endRowNo) { - elementList[p].rowFlex = payload - } - } - // 光标定位 - const isSetCursor = startIndex === endIndex - const curIndex = isSetCursor ? endIndex : startIndex - this.draw.render({ curIndex, isSetCursor }) - } - public compositionstart() { this.isCompositing = true } diff --git a/src/editor/core/register/Register.ts b/src/editor/core/register/Register.ts index 39788c2..51d0fe9 100644 --- a/src/editor/core/register/Register.ts +++ b/src/editor/core/register/Register.ts @@ -1,17 +1,22 @@ import { IRegisterContextMenu } from '../../interface/contextmenu/ContextMenu' +import { IRegisterShortcut } from '../../interface/shortcut/Shortcut' import { ContextMenu } from '../contextmenu/ContextMenu' +import { Shortcut } from '../shortcut/Shortcut' interface IRegisterPayload { - contextMenu: ContextMenu + contextMenu: ContextMenu; + shortcut: Shortcut; } export class Register { public contextMenuList: (payload: IRegisterContextMenu[]) => void + public shortcutList: (payload: IRegisterShortcut[]) => void constructor(payload: IRegisterPayload) { - const { contextMenu } = payload + const { contextMenu, shortcut } = payload this.contextMenuList = contextMenu.registerContextMenuList.bind(contextMenu) + this.shortcutList = shortcut.registerShortcutList.bind(shortcut) } } \ No newline at end of file diff --git a/src/editor/core/shortcut/Shortcut.ts b/src/editor/core/shortcut/Shortcut.ts new file mode 100644 index 0000000..fe82345 --- /dev/null +++ b/src/editor/core/shortcut/Shortcut.ts @@ -0,0 +1,68 @@ +import { richtextKeys } from '../../interface/shortcut/keys/richtextKeys' +import { IRegisterShortcut } from '../../interface/shortcut/Shortcut' +import { Command } from '../command/Command' +import { Draw } from '../draw/Draw' + +export class Shortcut { + + private command: Command + private globalShortcutList: IRegisterShortcut[] + private agentShortcutList: IRegisterShortcut[] + + constructor(draw: Draw, command: Command) { + this.command = command + this.globalShortcutList = [] + this.agentShortcutList = [] + // 内部快捷键 + this._addShortcutList([ + ...richtextKeys + ]) + // 全局快捷键 + document.addEventListener('keydown', this._globalKeydown.bind(this)) + // 编辑器快捷键 + const agentDom = draw.getCursor().getAgentDom() + agentDom.addEventListener('keydown', this._agentKeydown.bind(this)) + } + + private _addShortcutList(payload: IRegisterShortcut[]) { + for (let s = 0; s < payload.length; s++) { + const shortCut = payload[s] + if (shortCut.isGlobal) { + this.globalShortcutList.push(shortCut) + } else { + this.agentShortcutList.push(shortCut) + } + } + } + + public registerShortcutList(payload: IRegisterShortcut[]) { + this._addShortcutList(payload) + } + + private _globalKeydown(evt: KeyboardEvent) { + if (!this.globalShortcutList.length) return + this._execute(evt, this.globalShortcutList) + } + + private _agentKeydown(evt: KeyboardEvent) { + if (!this.agentShortcutList.length) return + this._execute(evt, this.agentShortcutList) + } + + private _execute(evt: KeyboardEvent, shortCutList: IRegisterShortcut[]) { + for (let s = 0; s < shortCutList.length; s++) { + const shortCut = shortCutList[s] + if ( + evt.ctrlKey === !!shortCut.ctrl && + evt.shiftKey === !!shortCut.shift && + evt.altKey === !!shortCut.alt && + evt.key === shortCut.key + ) { + shortCut.callback(this.command) + evt.preventDefault() + break + } + } + } + +} diff --git a/src/editor/dataset/enum/KeyMap.ts b/src/editor/dataset/enum/KeyMap.ts index 77169ba..857e57c 100644 --- a/src/editor/dataset/enum/KeyMap.ts +++ b/src/editor/dataset/enum/KeyMap.ts @@ -17,16 +17,53 @@ export enum KeyMap { A = 'a', B = 'b', C = 'c', + D = 'd', E = 'e', F = 'f', + G = 'g', + H = 'h', I = 'i', J = 'j', + K = 'k', L = 'l', + M = 'm', + N = 'n', + O = 'o', P = 'p', + Q = 'q', R = 'r', S = 's', + T = 't', U = 'u', + V = 'v', + W = 'w', X = 'x', Y = 'y', - Z = 'z' + Z = 'z', + A_UPPERCASE = 'A', + B_UPPERCASE = 'B', + C_UPPERCASE = 'C', + D_UPPERCASE = 'D', + E_UPPERCASE = 'E', + F_UPPERCASE = 'F', + G_UPPERCASE = 'G', + H_UPPERCASE = 'H', + I_UPPERCASE = 'I', + J_UPPERCASE = 'J', + K_UPPERCASE = 'K', + L_UPPERCASE = 'L', + M_UPPERCASE = 'M', + N_UPPERCASE = 'N', + O_UPPERCASE = 'O', + P_UPPERCASE = 'P', + Q_UPPERCASE = 'Q', + R_UPPERCASE = 'R', + S_UPPERCASE = 'S', + T_UPPERCASE = 'T', + U_UPPERCASE = 'U', + V_UPPERCASE = 'V', + W_UPPERCASE = 'W', + X_UPPERCASE = 'X', + Y_UPPERCASE = 'Y', + Z_UPPERCASE = 'Z', } \ No newline at end of file diff --git a/src/editor/index.ts b/src/editor/index.ts index db3d43e..2339e0a 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -24,6 +24,8 @@ import { ICheckboxOption } from './interface/Checkbox' import { defaultCheckboxOption } from './dataset/constant/Checkbox' import { DeepRequired } from './interface/Common' import { INavigateInfo } from './core/draw/interactive/Search' +import { Shortcut } from './core/shortcut/Shortcut' +import { KeyMap } from './dataset/enum/KeyMap' export default class Editor { @@ -100,9 +102,12 @@ export default class Editor { this.command = new Command(new CommandAdapt(draw)) // 菜单 const contextMenu = new ContextMenu(draw, this.command) + // 快捷键 + const shortcut = new Shortcut(draw, this.command) // 注册 this.register = new Register({ - contextMenu + contextMenu, + shortcut }) } @@ -119,7 +124,8 @@ export { EDITOR_COMPONENT, PageMode, ImageDisplay, - Command + Command, + KeyMap } // 对外类型 diff --git a/src/editor/interface/shortcut/Shortcut.ts b/src/editor/interface/shortcut/Shortcut.ts new file mode 100644 index 0000000..b4fe814 --- /dev/null +++ b/src/editor/interface/shortcut/Shortcut.ts @@ -0,0 +1,11 @@ +import { Command } from '../../core/command/Command' +import { KeyMap } from '../../dataset/enum/KeyMap' + +export interface IRegisterShortcut { + key: KeyMap; + ctrl?: boolean; + shift?: boolean; + alt?: boolean; + isGlobal?: boolean; + callback: (command: Command) => any; +} \ No newline at end of file diff --git a/src/editor/interface/shortcut/keys/richtextKeys.ts b/src/editor/interface/shortcut/keys/richtextKeys.ts new file mode 100644 index 0000000..1a17c63 --- /dev/null +++ b/src/editor/interface/shortcut/keys/richtextKeys.ts @@ -0,0 +1,93 @@ +import { Command } from '../../..' +import { KeyMap } from '../../../dataset/enum/KeyMap' +import { IRegisterShortcut } from '../Shortcut' + +export const richtextKeys: IRegisterShortcut[] = [ + { + key: KeyMap.X_UPPERCASE, + ctrl: true, + shift: true, + callback: (command: Command) => { + command.executeStrikeout() + } + }, + { + key: KeyMap.LEFT_BRACKET, + ctrl: true, + callback: (command: Command) => { + command.executeSizeAdd() + } + }, + { + key: KeyMap.RIGHT_BRACKET, + ctrl: true, + callback: (command: Command) => { + command.executeSizeMinus() + } + }, + { + key: KeyMap.B, + ctrl: true, + callback: (command: Command) => { + command.executeBold() + } + }, + { + key: KeyMap.I, + ctrl: true, + callback: (command: Command) => { + command.executeItalic() + } + }, + { + key: KeyMap.U, + ctrl: true, + callback: (command: Command) => { + command.executeUnderline() + } + }, + { + key: KeyMap.RIGHT_ANGLE_BRACKET, + ctrl: true, + shift: true, + callback: (command: Command) => { + command.executeSuperscript() + } + }, + { + key: KeyMap.LEFT_ANGLE_BRACKET, + ctrl: true, + shift: true, + callback: (command: Command) => { + command.executeSubscript() + } + }, + { + key: KeyMap.L, + ctrl: true, + callback: (command: Command) => { + command.executeLeft() + } + }, + { + key: KeyMap.E, + ctrl: true, + callback: (command: Command) => { + command.executeCenter() + } + }, + { + key: KeyMap.R, + ctrl: true, + callback: (command: Command) => { + command.executeRight() + } + }, + { + key: KeyMap.J, + ctrl: true, + callback: (command: Command) => { + command.executeAlignment() + } + } +] \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 2cc40f9..db975d9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ import { data, options } from './mock' import './style.css' import prism from 'prismjs' -import Editor, { Command, ControlType, EditorMode, ElementType, IElement, PageMode } from './editor' +import Editor, { Command, ControlType, EditorMode, ElementType, IElement, KeyMap, PageMode } from './editor' import { Dialog } from './components/dialog/Dialog' import { formatPrismToken } from './utils/prism' import { Signature } from './components/signature/Signature' @@ -972,4 +972,24 @@ window.onload = function () { } ]) + // 10. 快捷键注册 + instance.register.shortcutList([ + { + key: KeyMap.P, + ctrl: true, + isGlobal: true, + callback: (command: Command) => { + command.executePrint() + } + }, + { + key: KeyMap.F, + ctrl: true, + isGlobal: true, + callback: () => { + searchDom.click() + } + } + ]) + } \ No newline at end of file