diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b070c5b8..db93b36f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,7 @@ - `Fix` - Fix an unstable block cut process [#1489](https://github.com/codex-team/editor.js/issues/1489). - `Fix` - Type definition of the Sanitizer config: the sanitize function now contains param definition [#1491](https://github.com/codex-team/editor.js/pull/1491). - `Fix` - Fix unexpected behavior on an empty link pasting [#1348](https://github.com/codex-team/editor.js/issues/1348). +- `Refactoring` - The Listeners module now is a util. - `Fix` - Editor Config now immutable [#1552](https://github.com/codex-team/editor.js/issues/1552). ### 2.19.1 diff --git a/src/codex.ts b/src/codex.ts index a258edf2..29dc4b8f 100644 --- a/src/codex.ts +++ b/src/codex.ts @@ -87,6 +87,7 @@ export default class EditorJS { if (_.isFunction(moduleInstance.destroy)) { moduleInstance.destroy(); } + moduleInstance.listeners.removeAll(); }); editor = null; diff --git a/src/components/__module.ts b/src/components/__module.ts index d55b5760..bfb5b767 100644 --- a/src/components/__module.ts +++ b/src/components/__module.ts @@ -1,6 +1,7 @@ import { EditorModules } from '../types-internal/editor-modules'; import { EditorConfig } from '../../types'; import { ModuleConfig } from '../types-internal/module-config'; +import Listeners from './utils/listeners'; /** * The type of the Module generic. @@ -38,6 +39,11 @@ export default class Module { */ protected config: EditorConfig; + /** + * Util for bind/unbind DOM event listeners + */ + protected listeners: Listeners = new Listeners(); + /** * This object provides methods to push into set of listeners that being dropped when read-only mode is enabled */ @@ -56,10 +62,8 @@ export default class Module { handler: (event: Event) => void, options: boolean | AddEventListenerOptions = false ): void => { - const { Listeners } = this.Editor; - this.mutableListenerIds.push( - Listeners.on(element, eventType, handler, options) + this.listeners.on(element, eventType, handler, options) ); }, @@ -67,10 +71,8 @@ export default class Module { * Clears all mutable listeners */ clearAll: (): void => { - const { Listeners } = this.Editor; - for (const id of this.mutableListenerIds) { - Listeners.offById(id); + this.listeners.offById(id); } this.mutableListenerIds = []; diff --git a/src/components/modules/api/listeners.ts b/src/components/modules/api/listeners.ts index 0ec283bc..a3b403a3 100644 --- a/src/components/modules/api/listeners.ts +++ b/src/components/modules/api/listeners.ts @@ -27,7 +27,7 @@ export default class ListenersAPI extends Module { * @param {boolean} useCapture - capture event or not */ public on(element: HTMLElement, eventType: string, handler: () => void, useCapture?: boolean): void { - this.Editor.Listeners.on(element, eventType, handler, useCapture); + this.listeners.on(element, eventType, handler, useCapture); } /** @@ -39,6 +39,6 @@ export default class ListenersAPI extends Module { * @param {boolean} useCapture - capture event or not */ public off(element: Element, eventType: string, handler: () => void, useCapture?: boolean): void { - this.Editor.Listeners.off(element, eventType, handler, useCapture); + this.listeners.off(element, eventType, handler, useCapture); } } diff --git a/src/components/modules/blockManager.ts b/src/components/modules/blockManager.ts index 30d392b2..30636e57 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -184,7 +184,7 @@ export default class BlockManager extends Module { }); /** Copy event */ - this.Editor.Listeners.on( + this.listeners.on( document, 'copy', (e: ClipboardEvent) => this.Editor.BlockEvents.handleCommandC(e) diff --git a/src/components/modules/crossBlockSelection.ts b/src/components/modules/crossBlockSelection.ts index 76d31c67..fa8c4962 100644 --- a/src/components/modules/crossBlockSelection.ts +++ b/src/components/modules/crossBlockSelection.ts @@ -23,9 +23,7 @@ export default class CrossBlockSelection extends Module { * @returns {Promise} */ public async prepare(): Promise { - const { Listeners } = this.Editor; - - Listeners.on(document, 'mousedown', (event: MouseEvent) => { + this.listeners.on(document, 'mousedown', (event: MouseEvent) => { this.enableCrossBlockSelection(event); }); } @@ -40,13 +38,13 @@ export default class CrossBlockSelection extends Module { return; } - const { BlockManager, Listeners } = this.Editor; + const { BlockManager } = this.Editor; this.firstSelectedBlock = BlockManager.getBlock(event.target as HTMLElement); this.lastSelectedBlock = this.firstSelectedBlock; - Listeners.on(document, 'mouseover', this.onMouseOver); - Listeners.on(document, 'mouseup', this.onMouseUp); + this.listeners.on(document, 'mouseover', this.onMouseOver); + this.listeners.on(document, 'mouseup', this.onMouseUp); } /** @@ -176,10 +174,8 @@ export default class CrossBlockSelection extends Module { * Removes the listeners */ private onMouseUp = (): void => { - const { Listeners } = this.Editor; - - Listeners.off(document, 'mouseover', this.onMouseOver); - Listeners.off(document, 'mouseup', this.onMouseUp); + this.listeners.off(document, 'mouseover', this.onMouseOver); + this.listeners.off(document, 'mouseup', this.onMouseUp); } /** diff --git a/src/components/modules/modificationsObserver.ts b/src/components/modules/modificationsObserver.ts index c22f0b81..30f5797c 100644 --- a/src/components/modules/modificationsObserver.ts +++ b/src/components/modules/modificationsObserver.ts @@ -58,7 +58,7 @@ export default class ModificationsObserver extends Module { this.observer.disconnect(); } this.observer = null; - this.nativeInputs.forEach((input) => this.Editor.Listeners.off(input, 'input', this.mutationDebouncer)); + this.nativeInputs.forEach((input) => this.listeners.off(input, 'input', this.mutationDebouncer)); this.mutationDebouncer = null; } @@ -163,13 +163,13 @@ export default class ModificationsObserver extends Module { private updateNativeInputs(): void { if (this.nativeInputs) { this.nativeInputs.forEach((input) => { - this.Editor.Listeners.off(input, 'input'); + this.listeners.off(input, 'input'); }); } this.nativeInputs = Array.from(this.Editor.UI.nodes.redactor.querySelectorAll('textarea, input, select')); - this.nativeInputs.forEach((input) => this.Editor.Listeners.on(input, 'input', this.mutationDebouncer)); + this.nativeInputs.forEach((input) => this.listeners.on(input, 'input', this.mutationDebouncer)); } /** diff --git a/src/components/modules/paste.ts b/src/components/modules/paste.ts index 98f4530f..884cbdba 100644 --- a/src/components/modules/paste.ts +++ b/src/components/modules/paste.ts @@ -263,18 +263,14 @@ export default class Paste extends Module { * Set onPaste callback handler */ private setCallback(): void { - const { Listeners } = this.Editor; - - Listeners.on(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent); + this.listeners.on(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent); } /** * Unset onPaste callback handler */ private unsetCallback(): void { - const { Listeners } = this.Editor; - - Listeners.off(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent); + this.listeners.off(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent); } /** diff --git a/src/components/modules/rectangleSelection.ts b/src/components/modules/rectangleSelection.ts index 7f0e11d6..8b2ea151 100644 --- a/src/components/modules/rectangleSelection.ts +++ b/src/components/modules/rectangleSelection.ts @@ -179,26 +179,25 @@ export default class RectangleSelection extends Module { * Sets Module necessary event handlers */ private enableModuleBindings(): void { - const { Listeners } = this.Editor; const { container } = this.genHTML(); - Listeners.on(container, 'mousedown', (mouseEvent: MouseEvent) => { + this.listeners.on(container, 'mousedown', (mouseEvent: MouseEvent) => { this.processMouseDown(mouseEvent); }, false); - Listeners.on(document.body, 'mousemove', (mouseEvent: MouseEvent) => { + this.listeners.on(document.body, 'mousemove', (mouseEvent: MouseEvent) => { this.processMouseMove(mouseEvent); }, false); - Listeners.on(document.body, 'mouseleave', () => { + this.listeners.on(document.body, 'mouseleave', () => { this.processMouseLeave(); }); - Listeners.on(window, 'scroll', (mouseEvent: MouseEvent) => { + this.listeners.on(window, 'scroll', (mouseEvent: MouseEvent) => { this.processScroll(mouseEvent); }, false); - Listeners.on(document.body, 'mouseup', () => { + this.listeners.on(document.body, 'mouseup', () => { this.processMouseUp(); }, false); } diff --git a/src/components/modules/toolbar/conversion.ts b/src/components/modules/toolbar/conversion.ts index 49cf0ea6..a258bf2d 100644 --- a/src/components/modules/toolbar/conversion.ts +++ b/src/components/modules/toolbar/conversion.ts @@ -325,7 +325,7 @@ export default class ConversionToolbar extends Module { $.append(this.nodes.tools, tool); this.tools[toolName] = tool; - this.Editor.Listeners.on(tool, 'click', async () => { + this.listeners.on(tool, 'click', async () => { await this.replaceWithBlock(toolName); }); } diff --git a/src/components/modules/toolbar/inline.ts b/src/components/modules/toolbar/inline.ts index cfd9018c..5d7ba7ed 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -357,7 +357,7 @@ export default class InlineToolbar extends Module { this.nodes.actions = $.make('div', this.CSS.actionsWrapper); // To prevent reset of a selection when click on the wrapper - this.Editor.Listeners.on(this.nodes.wrapper, 'mousedown', (event) => { + this.listeners.on(this.nodes.wrapper, 'mousedown', (event) => { const isClickedOnActionsWrapper = (event.target as Element).closest(`.${this.CSS.actionsWrapper}`); // If click is on actions wrapper, @@ -479,7 +479,7 @@ export default class InlineToolbar extends Module { this.nodes.togglerAndButtonsWrapper.appendChild(this.nodes.conversionToggler); - this.Editor.Listeners.on(this.nodes.conversionToggler, 'click', () => { + this.listeners.on(this.nodes.conversionToggler, 'click', () => { this.Editor.ConversionToolbar.toggle((conversionToolbarOpened) => { /** * When ConversionToolbar is opening on activated InlineToolbar flipper @@ -594,7 +594,6 @@ export default class InlineToolbar extends Module { */ private addTool(toolName: string, tool: InlineTool): void { const { - Listeners, Tools, Tooltip, } = this.Editor; @@ -617,7 +616,7 @@ export default class InlineToolbar extends Module { this.nodes.actions.appendChild(actions); } - Listeners.on(button, 'click', (event) => { + this.listeners.on(button, 'click', (event) => { this.toolClicked(tool); event.preventDefault(); }); diff --git a/src/components/modules/toolbar/toolbox.ts b/src/components/modules/toolbar/toolbox.ts index 54ed807a..436abf81 100644 --- a/src/components/modules/toolbar/toolbox.ts +++ b/src/components/modules/toolbar/toolbox.ts @@ -225,7 +225,7 @@ export default class Toolbox extends Module { /** * Add click listener */ - this.Editor.Listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => { + this.listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => { this.toolButtonActivate(event, toolName); }); diff --git a/src/components/modules/listeners.ts b/src/components/utils/listeners.ts similarity index 96% rename from src/components/modules/listeners.ts rename to src/components/utils/listeners.ts index 663268da..e0e35609 100644 --- a/src/components/modules/listeners.ts +++ b/src/components/utils/listeners.ts @@ -1,4 +1,3 @@ -import Module from '../__module'; import * as _ from '../utils'; /** @@ -36,11 +35,9 @@ export interface ListenerData { } /** - * Editor.js Listeners module + * Editor.js Listeners helper * - * @module Listeners - * - * Module-decorator for event listeners assignment + * Decorator for event listeners assignment * * @author Codex Team * @version 2.0.0 @@ -50,7 +47,7 @@ export interface ListenerData { * @typedef {Listeners} Listeners * @property {ListenerData[]} allListeners - listeners store */ -export default class Listeners extends Module { +export default class Listeners { /** * Stores all listeners data to find/remove/process it * @@ -114,7 +111,7 @@ export default class Listeners extends Module { existingListeners.forEach((listener, i) => { const index = this.allListeners.indexOf(existingListeners[i]); - if (index > 0) { + if (index > -1) { this.allListeners.splice(index, 1); listener.element.removeEventListener(listener.eventType, listener.handler, listener.options); diff --git a/src/types-internal/editor-modules.d.ts b/src/types-internal/editor-modules.d.ts index 09b98015..55cb7677 100644 --- a/src/types-internal/editor-modules.d.ts +++ b/src/types-internal/editor-modules.d.ts @@ -1,6 +1,5 @@ import UI from '../components/modules/ui'; import BlockEvents from '../components/modules/blockEvents'; -import Listeners from '../components/modules/listeners'; import Toolbar from '../components/modules/toolbar/index'; import InlineToolbar from '../components/modules/toolbar/inline'; import Toolbox from '../components/modules/toolbar/toolbox'; @@ -44,7 +43,6 @@ export interface EditorModules { BlockEvents: BlockEvents; BlockSelection: BlockSelection; RectangleSelection: RectangleSelection; - Listeners: Listeners; Toolbar: Toolbar; InlineToolbar: InlineToolbar; Toolbox: Toolbox;