refactoring(modules): shortcuts module is util now (#1578)

* (refactoring)shortcuts module is util now

* Fix eslint

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
This commit is contained in:
George Berezhnoy 2021-03-05 00:32:55 +03:00 committed by GitHub
parent 2a2a6f11ad
commit 2f5da52d1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 202 additions and 118 deletions

View file

@ -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

View file

@ -42,6 +42,7 @@
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js" onload="document.getElementById('hint-core').hidden = true"></script>
<script src="./tools/header/dist/bundle.js"></script><!-- Header -->
<!-- Initialization -->
<script>
@ -50,6 +51,12 @@
*/
var editor1 = new EditorJS({
holder: 'editorjs1',
tools: {
header: {
class: Header,
shortcut: 'CMD+SHIFT+H'
}
}
});
/**
@ -57,6 +64,12 @@
*/
var editor2 = new EditorJS({
holder: 'editorjs2',
tools: {
header: {
class: Header,
shortcut: 'CMD+SHIFT+H'
}
}
});
</script>
</body>

View file

@ -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');
}
/**

View file

@ -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);
}
}

View file

@ -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<InlineToolbarNodes> {
*/
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<InlineToolbarNodes> {
}
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<InlineToolbarNodes> {
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<InlineToolbarNodes> {
});
}
/**
* 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<InlineToolbarNodes> {
* @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<InlineToolbarNodes> {
event.preventDefault();
this.toolClicked(tool);
},
on: this.Editor.UI.nodes.redactor,
});
}

View file

@ -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<ToolboxNodes> {
* @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<ToolboxNodes> {
const shortcut = this.getToolShortcut(toolName, tools[toolName]);
if (shortcut) {
this.Editor.Shortcuts.remove(shortcut);
Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut);
}
}
}

View file

@ -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<Element, Shortcut[]>}
*/
private registeredShortcuts: Map<Element, Shortcut[]> = 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();

View file

@ -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';