diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cb13dce3..37c68c76 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -16,6 +16,7 @@ - `Fix` - Fix SanitizerConfig type definition [#1513](https://github.com/codex-team/editor.js/issues/1513) - `Refactoring` - The Listeners module now is a util. - `Fix` - Editor Config now immutable [#1552](https://github.com/codex-team/editor.js/issues/1552). +- `Refactoring` - Shortcuts module is util now ### 2.19.1 diff --git a/example/example-multiple.html b/example/example-multiple.html index 3cdb64c1..15470420 100644 --- a/example/example-multiple.html +++ b/example/example-multiple.html @@ -42,6 +42,7 @@ + diff --git a/src/components/modules/blockSelection.ts b/src/components/modules/blockSelection.ts index 8f437785..1c05d239 100644 --- a/src/components/modules/blockSelection.ts +++ b/src/components/modules/blockSelection.ts @@ -9,6 +9,7 @@ import Module from '../__module'; import Block from '../block'; import * as _ from '../utils'; import $ from '../dom'; +import Shortcuts from '../utils/shortcuts'; import SelectionUtils from '../selection'; import { SanitizerConfig } from '../../../types/configs'; @@ -145,8 +146,6 @@ export default class BlockSelection extends Module { * to select all and copy them */ public prepare(): void { - const { Shortcuts } = this.Editor; - this.selection = new SelectionUtils(); /** @@ -181,6 +180,7 @@ export default class BlockSelection extends Module { this.handleCommandA(event); }, + on: this.Editor.UI.nodes.redactor, }); } @@ -361,10 +361,8 @@ export default class BlockSelection extends Module { * De-registers Shortcut CMD+A */ public destroy(): void { - const { Shortcuts } = this.Editor; - /** Selection shortcut */ - Shortcuts.remove('CMD+A'); + Shortcuts.remove(this.Editor.UI.nodes.redactor, 'CMD+A'); } /** diff --git a/src/components/modules/shortcuts.ts b/src/components/modules/shortcuts.ts deleted file mode 100644 index d790ca57..00000000 --- a/src/components/modules/shortcuts.ts +++ /dev/null @@ -1,74 +0,0 @@ -import Shortcut from '@codexteam/shortcuts'; - -/** - * Contains keyboard and mouse events binded on each Block by Block Manager - */ -import Module from '../__module'; - -/** - * ShortcutData interface - * Each shortcut must have name and handler - * `name` is a shortcut, like 'CMD+K', 'CMD+B' etc - * `handler` is a callback - * - * @interface ShortcutData - */ -export interface ShortcutData { - - /** - * Shortcut name - * Ex. CMD+I, CMD+B .... - */ - name: string; - - /** - * Shortcut handler - */ - handler(event): void; -} - -/** - * @class Shortcut - * @classdesc Allows to register new shortcut - * - * Internal Shortcuts Module - */ -export default class Shortcuts extends Module { - /** - * All registered shortcuts - * - * @type {Shortcut[]} - */ - private registeredShortcuts: Shortcut[] = []; - - /** - * Register shortcut - * - * @param {ShortcutData} shortcut - shortcut options - */ - public add(shortcut: ShortcutData): void { - const newShortcut = new Shortcut({ - name: shortcut.name, - on: this.Editor.UI.nodes.redactor, - callback: shortcut.handler, - }); - - this.registeredShortcuts.push(newShortcut); - } - - /** - * Remove shortcut - * - * @param {string} shortcut - shortcut name - */ - public remove(shortcut: string): void { - const index = this.registeredShortcuts.findIndex((shc) => shc.name === shortcut); - - if (index === -1 || !this.registeredShortcuts[index]) { - return; - } - - this.registeredShortcuts[index].remove(); - this.registeredShortcuts.splice(index, 1); - } -} diff --git a/src/components/modules/toolbar/inline.ts b/src/components/modules/toolbar/inline.ts index 5d7ba7ed..d8bc8308 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -6,6 +6,8 @@ import { InlineTool, InlineToolConstructable, ToolConstructable, ToolSettings } import Flipper from '../../flipper'; import I18n from '../../i18n'; import { I18nInternalNS } from '../../i18n/namespace-internal'; +import Shortcuts from '../../utils/shortcuts'; +import { EditorModules } from '../../../types-internal/editor-modules'; /** * Inline Toolbar elements @@ -87,6 +89,38 @@ export default class InlineToolbar extends Module { */ private flipper: Flipper = null; + /** + * Internal inline tools: Link, Bold, Italic + */ + private internalTools: {[name: string]: InlineToolConstructable} = {}; + + /** + * Editor modules setter + * + * @param {EditorModules} Editor - Editor's Modules + */ + public set state(Editor: EditorModules) { + this.Editor = Editor; + + const { Tools } = Editor; + + /** + * Set internal inline tools + */ + Object + .entries(Tools.internalTools) + .filter(([, toolClass]: [string, ToolConstructable | ToolSettings]) => { + if (_.isFunction(toolClass)) { + return toolClass[Tools.INTERNAL_SETTINGS.IS_INLINE]; + } + + return (toolClass as ToolSettings).class[Tools.INTERNAL_SETTINGS.IS_INLINE]; + }) + .map(([name, toolClass]: [string, InlineToolConstructable | ToolSettings]) => { + this.internalTools[name] = _.isFunction(toolClass) ? toolClass : (toolClass as ToolSettings).class; + }); + } + /** * Toggles read-only mode * @@ -185,7 +219,13 @@ export default class InlineToolbar extends Module { } this.nodes.wrapper.classList.remove(this.CSS.inlineToolbarShowed); - this.toolsInstances.forEach((toolInstance) => { + Array.from(this.toolsInstances.entries()).forEach(([name, toolInstance]) => { + const shortcut = this.getToolShortcut(name); + + if (shortcut) { + Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut); + } + /** * @todo replace 'clear' with 'destroy' */ @@ -621,40 +661,7 @@ export default class InlineToolbar extends Module { event.preventDefault(); }); - /** - * Enable shortcuts - * Ignore tool that doesn't have shortcut or empty string - */ - const toolSettings = Tools.getToolSettings(toolName); - - let shortcut = null; - - /** - * Get internal inline tools - */ - const internalTools: string[] = Object - .entries(Tools.internalTools) - .filter(([, toolClass]: [string, ToolConstructable | ToolSettings]) => { - if (_.isFunction(toolClass)) { - return toolClass[Tools.INTERNAL_SETTINGS.IS_INLINE]; - } - - return (toolClass as ToolSettings).class[Tools.INTERNAL_SETTINGS.IS_INLINE]; - }) - .map(([ name ]: [string, InlineToolConstructable | ToolSettings]) => name); - - /** - * 1) For internal tools, check public getter 'shortcut' - * 2) For external tools, check tool's settings - * 3) If shortcut is not set in settings, check Tool's public property - */ - if (internalTools.includes(toolName)) { - shortcut = this.inlineTools[toolName][Tools.INTERNAL_SETTINGS.SHORTCUT]; - } else if (toolSettings && toolSettings[Tools.USER_SETTINGS.SHORTCUT]) { - shortcut = toolSettings[Tools.USER_SETTINGS.SHORTCUT]; - } else if (tool.shortcut) { - shortcut = tool.shortcut; - } + const shortcut = this.getToolShortcut(toolName); if (shortcut) { this.enableShortcuts(tool, shortcut); @@ -683,6 +690,35 @@ export default class InlineToolbar extends Module { }); } + /** + * Get shortcut name for tool + * + * @param toolName — Tool name + */ + private getToolShortcut(toolName): string | void { + const { Tools } = this.Editor; + + /** + * Enable shortcuts + * Ignore tool that doesn't have shortcut or empty string + */ + const toolSettings = Tools.getToolSettings(toolName); + const tool = this.toolsInstances.get(toolName); + + /** + * 1) For internal tools, check public getter 'shortcut' + * 2) For external tools, check tool's settings + * 3) If shortcut is not set in settings, check Tool's public property + */ + if (Object.keys(this.internalTools).includes(toolName)) { + return this.inlineTools[toolName][Tools.INTERNAL_SETTINGS.SHORTCUT]; + } else if (toolSettings && toolSettings[Tools.USER_SETTINGS.SHORTCUT]) { + return toolSettings[Tools.USER_SETTINGS.SHORTCUT]; + } else if (tool.shortcut) { + return tool.shortcut; + } + } + /** * Enable Tool shortcut with Editor Shortcuts Module * @@ -690,7 +726,7 @@ export default class InlineToolbar extends Module { * @param {string} shortcut - shortcut according to the ShortcutData Module format */ private enableShortcuts(tool: InlineTool, shortcut: string): void { - this.Editor.Shortcuts.add({ + Shortcuts.add({ name: shortcut, handler: (event) => { const { currentBlock } = this.Editor.BlockManager; @@ -718,6 +754,7 @@ export default class InlineToolbar extends Module { event.preventDefault(); this.toolClicked(tool); }, + on: this.Editor.UI.nodes.redactor, }); } diff --git a/src/components/modules/toolbar/toolbox.ts b/src/components/modules/toolbar/toolbox.ts index 436abf81..add3e9e7 100644 --- a/src/components/modules/toolbar/toolbox.ts +++ b/src/components/modules/toolbar/toolbox.ts @@ -6,6 +6,7 @@ import Flipper from '../../flipper'; import { BlockToolAPI } from '../../block'; import I18n from '../../i18n'; import { I18nInternalNS } from '../../i18n/namespace-internal'; +import Shortcuts from '../../utils/shortcuts'; /** * HTMLElements used for Toolbox UI @@ -306,12 +307,13 @@ export default class Toolbox extends Module { * @param {string} shortcut - shortcut according to the ShortcutData Module format */ private enableShortcut(tool: BlockToolConstructable, toolName: string, shortcut: string): void { - this.Editor.Shortcuts.add({ + Shortcuts.add({ name: shortcut, handler: (event: KeyboardEvent) => { event.preventDefault(); this.insertNewBlock(tool, toolName); }, + on: this.Editor.UI.nodes.redactor, }); } @@ -327,7 +329,7 @@ export default class Toolbox extends Module { const shortcut = this.getToolShortcut(toolName, tools[toolName]); if (shortcut) { - this.Editor.Shortcuts.remove(shortcut); + Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut); } } } diff --git a/src/components/utils/shortcuts.ts b/src/components/utils/shortcuts.ts new file mode 100644 index 00000000..3e45210f --- /dev/null +++ b/src/components/utils/shortcuts.ts @@ -0,0 +1,107 @@ +import Shortcut from '@codexteam/shortcuts'; + +/** + * Contains keyboard and mouse events binded on each Block by Block Manager + */ + +/** + * ShortcutData interface + * Each shortcut must have name and handler + * `name` is a shortcut, like 'CMD+K', 'CMD+B' etc + * `handler` is a callback + * + * @interface ShortcutData + */ +export interface ShortcutData { + + /** + * Shortcut name + * Ex. CMD+I, CMD+B .... + */ + name: string; + + /** + * Shortcut handler + */ + handler(event): void; + + /** + * Element handler should be added for + */ + on: HTMLElement; +} + +/** + * @class Shortcut + * @classdesc Allows to register new shortcut + * + * Internal Shortcuts Module + */ +class Shortcuts { + /** + * All registered shortcuts + * + * @type {Map} + */ + private registeredShortcuts: Map = new Map(); + + /** + * Register shortcut + * + * @param shortcut - shortcut options + */ + public add(shortcut: ShortcutData): void { + const foundShortcut = this.findShortcut(shortcut.on, shortcut.name); + + if (foundShortcut) { + throw Error( + `Shortcut ${shortcut.name} is already registered for ${shortcut.on}. Please remove it before add a new handler.` + ); + } + + const newShortcut = new Shortcut({ + name: shortcut.name, + on: shortcut.on, + callback: shortcut.handler, + }); + const shortcuts = this.registeredShortcuts.get(shortcut.on) || []; + + this.registeredShortcuts.set(shortcut.on, [...shortcuts, newShortcut]); + } + + /** + * Remove shortcut + * + * @param element - Element shortcut is set for + * @param name - shortcut name + */ + public remove(element: Element, name: string): void { + const shortcut = this.findShortcut(element, name); + + if (!shortcut) { + return; + } + + shortcut.remove(); + + const shortcuts = this.registeredShortcuts.get(element); + + this.registeredShortcuts.set(element, shortcuts.filter(el => el !== shortcut)); + } + + /** + * Get Shortcut instance if exist + * + * @param element - Element shorcut is set for + * @param shortcut - shortcut name + * + * @returns {number} index - shortcut index if exist + */ + private findShortcut(element: Element, shortcut: string): Shortcut | void { + const shortcuts = this.registeredShortcuts.get(element) || []; + + return shortcuts.find(({ name }) => name === shortcut); + } +} + +export default new Shortcuts(); diff --git a/src/types-internal/editor-modules.d.ts b/src/types-internal/editor-modules.d.ts index 55cb7677..c57008f0 100644 --- a/src/types-internal/editor-modules.d.ts +++ b/src/types-internal/editor-modules.d.ts @@ -5,7 +5,7 @@ import InlineToolbar from '../components/modules/toolbar/inline'; import Toolbox from '../components/modules/toolbar/toolbox'; import BlockSettings from '../components/modules/toolbar/blockSettings'; import Events from '../components/modules/events'; -import Shortcuts from '../components/modules/shortcuts'; +import Shortcuts from '../components/utils/shortcuts'; import Paste from '../components/modules/paste'; import Notifier from '../components/modules/notifier'; import Tooltip from '../components/modules/tooltip';