mirror of
https://github.com/codex-team/editor.js
synced 2024-06-28 02:10:31 +02:00
refactoring(modules): the listeners module now a util (#1572)
* listeners module goes util * fix listeners 'off' bug * improve garbage collecting
This commit is contained in:
parent
3bf30f0c1e
commit
2759b25d35
|
@ -13,6 +13,7 @@
|
||||||
- `Fix` - Fix an unstable block cut process [#1489](https://github.com/codex-team/editor.js/issues/1489).
|
- `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` - 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).
|
- `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).
|
- `Fix` - Editor Config now immutable [#1552](https://github.com/codex-team/editor.js/issues/1552).
|
||||||
|
|
||||||
### 2.19.1
|
### 2.19.1
|
||||||
|
|
|
@ -87,6 +87,7 @@ export default class EditorJS {
|
||||||
if (_.isFunction(moduleInstance.destroy)) {
|
if (_.isFunction(moduleInstance.destroy)) {
|
||||||
moduleInstance.destroy();
|
moduleInstance.destroy();
|
||||||
}
|
}
|
||||||
|
moduleInstance.listeners.removeAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
editor = null;
|
editor = null;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { EditorModules } from '../types-internal/editor-modules';
|
import { EditorModules } from '../types-internal/editor-modules';
|
||||||
import { EditorConfig } from '../../types';
|
import { EditorConfig } from '../../types';
|
||||||
import { ModuleConfig } from '../types-internal/module-config';
|
import { ModuleConfig } from '../types-internal/module-config';
|
||||||
|
import Listeners from './utils/listeners';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type <T> of the Module generic.
|
* The type <T> of the Module generic.
|
||||||
|
@ -38,6 +39,11 @@ export default class Module<T extends ModuleNodes = {}> {
|
||||||
*/
|
*/
|
||||||
protected config: EditorConfig;
|
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
|
* 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<T extends ModuleNodes = {}> {
|
||||||
handler: (event: Event) => void,
|
handler: (event: Event) => void,
|
||||||
options: boolean | AddEventListenerOptions = false
|
options: boolean | AddEventListenerOptions = false
|
||||||
): void => {
|
): void => {
|
||||||
const { Listeners } = this.Editor;
|
|
||||||
|
|
||||||
this.mutableListenerIds.push(
|
this.mutableListenerIds.push(
|
||||||
Listeners.on(element, eventType, handler, options)
|
this.listeners.on(element, eventType, handler, options)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -67,10 +71,8 @@ export default class Module<T extends ModuleNodes = {}> {
|
||||||
* Clears all mutable listeners
|
* Clears all mutable listeners
|
||||||
*/
|
*/
|
||||||
clearAll: (): void => {
|
clearAll: (): void => {
|
||||||
const { Listeners } = this.Editor;
|
|
||||||
|
|
||||||
for (const id of this.mutableListenerIds) {
|
for (const id of this.mutableListenerIds) {
|
||||||
Listeners.offById(id);
|
this.listeners.offById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mutableListenerIds = [];
|
this.mutableListenerIds = [];
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default class ListenersAPI extends Module {
|
||||||
* @param {boolean} useCapture - capture event or not
|
* @param {boolean} useCapture - capture event or not
|
||||||
*/
|
*/
|
||||||
public on(element: HTMLElement, eventType: string, handler: () => void, useCapture?: boolean): void {
|
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
|
* @param {boolean} useCapture - capture event or not
|
||||||
*/
|
*/
|
||||||
public off(element: Element, eventType: string, handler: () => void, useCapture?: boolean): void {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,7 @@ export default class BlockManager extends Module {
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Copy event */
|
/** Copy event */
|
||||||
this.Editor.Listeners.on(
|
this.listeners.on(
|
||||||
document,
|
document,
|
||||||
'copy',
|
'copy',
|
||||||
(e: ClipboardEvent) => this.Editor.BlockEvents.handleCommandC(e)
|
(e: ClipboardEvent) => this.Editor.BlockEvents.handleCommandC(e)
|
||||||
|
|
|
@ -23,9 +23,7 @@ export default class CrossBlockSelection extends Module {
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
public async prepare(): Promise<void> {
|
public async prepare(): Promise<void> {
|
||||||
const { Listeners } = this.Editor;
|
this.listeners.on(document, 'mousedown', (event: MouseEvent) => {
|
||||||
|
|
||||||
Listeners.on(document, 'mousedown', (event: MouseEvent) => {
|
|
||||||
this.enableCrossBlockSelection(event);
|
this.enableCrossBlockSelection(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -40,13 +38,13 @@ export default class CrossBlockSelection extends Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { BlockManager, Listeners } = this.Editor;
|
const { BlockManager } = this.Editor;
|
||||||
|
|
||||||
this.firstSelectedBlock = BlockManager.getBlock(event.target as HTMLElement);
|
this.firstSelectedBlock = BlockManager.getBlock(event.target as HTMLElement);
|
||||||
this.lastSelectedBlock = this.firstSelectedBlock;
|
this.lastSelectedBlock = this.firstSelectedBlock;
|
||||||
|
|
||||||
Listeners.on(document, 'mouseover', this.onMouseOver);
|
this.listeners.on(document, 'mouseover', this.onMouseOver);
|
||||||
Listeners.on(document, 'mouseup', this.onMouseUp);
|
this.listeners.on(document, 'mouseup', this.onMouseUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -176,10 +174,8 @@ export default class CrossBlockSelection extends Module {
|
||||||
* Removes the listeners
|
* Removes the listeners
|
||||||
*/
|
*/
|
||||||
private onMouseUp = (): void => {
|
private onMouseUp = (): void => {
|
||||||
const { Listeners } = this.Editor;
|
this.listeners.off(document, 'mouseover', this.onMouseOver);
|
||||||
|
this.listeners.off(document, 'mouseup', this.onMouseUp);
|
||||||
Listeners.off(document, 'mouseover', this.onMouseOver);
|
|
||||||
Listeners.off(document, 'mouseup', this.onMouseUp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -58,7 +58,7 @@ export default class ModificationsObserver extends Module {
|
||||||
this.observer.disconnect();
|
this.observer.disconnect();
|
||||||
}
|
}
|
||||||
this.observer = null;
|
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;
|
this.mutationDebouncer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,13 +163,13 @@ export default class ModificationsObserver extends Module {
|
||||||
private updateNativeInputs(): void {
|
private updateNativeInputs(): void {
|
||||||
if (this.nativeInputs) {
|
if (this.nativeInputs) {
|
||||||
this.nativeInputs.forEach((input) => {
|
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 = 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -263,18 +263,14 @@ export default class Paste extends Module {
|
||||||
* Set onPaste callback handler
|
* Set onPaste callback handler
|
||||||
*/
|
*/
|
||||||
private setCallback(): void {
|
private setCallback(): void {
|
||||||
const { Listeners } = this.Editor;
|
this.listeners.on(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
|
||||||
|
|
||||||
Listeners.on(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unset onPaste callback handler
|
* Unset onPaste callback handler
|
||||||
*/
|
*/
|
||||||
private unsetCallback(): void {
|
private unsetCallback(): void {
|
||||||
const { Listeners } = this.Editor;
|
this.listeners.off(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
|
||||||
|
|
||||||
Listeners.off(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -179,26 +179,25 @@ export default class RectangleSelection extends Module {
|
||||||
* Sets Module necessary event handlers
|
* Sets Module necessary event handlers
|
||||||
*/
|
*/
|
||||||
private enableModuleBindings(): void {
|
private enableModuleBindings(): void {
|
||||||
const { Listeners } = this.Editor;
|
|
||||||
const { container } = this.genHTML();
|
const { container } = this.genHTML();
|
||||||
|
|
||||||
Listeners.on(container, 'mousedown', (mouseEvent: MouseEvent) => {
|
this.listeners.on(container, 'mousedown', (mouseEvent: MouseEvent) => {
|
||||||
this.processMouseDown(mouseEvent);
|
this.processMouseDown(mouseEvent);
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
Listeners.on(document.body, 'mousemove', (mouseEvent: MouseEvent) => {
|
this.listeners.on(document.body, 'mousemove', (mouseEvent: MouseEvent) => {
|
||||||
this.processMouseMove(mouseEvent);
|
this.processMouseMove(mouseEvent);
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
Listeners.on(document.body, 'mouseleave', () => {
|
this.listeners.on(document.body, 'mouseleave', () => {
|
||||||
this.processMouseLeave();
|
this.processMouseLeave();
|
||||||
});
|
});
|
||||||
|
|
||||||
Listeners.on(window, 'scroll', (mouseEvent: MouseEvent) => {
|
this.listeners.on(window, 'scroll', (mouseEvent: MouseEvent) => {
|
||||||
this.processScroll(mouseEvent);
|
this.processScroll(mouseEvent);
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
Listeners.on(document.body, 'mouseup', () => {
|
this.listeners.on(document.body, 'mouseup', () => {
|
||||||
this.processMouseUp();
|
this.processMouseUp();
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,7 +325,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
||||||
$.append(this.nodes.tools, tool);
|
$.append(this.nodes.tools, tool);
|
||||||
this.tools[toolName] = tool;
|
this.tools[toolName] = tool;
|
||||||
|
|
||||||
this.Editor.Listeners.on(tool, 'click', async () => {
|
this.listeners.on(tool, 'click', async () => {
|
||||||
await this.replaceWithBlock(toolName);
|
await this.replaceWithBlock(toolName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,7 +357,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
this.nodes.actions = $.make('div', this.CSS.actionsWrapper);
|
this.nodes.actions = $.make('div', this.CSS.actionsWrapper);
|
||||||
|
|
||||||
// To prevent reset of a selection when click on the wrapper
|
// 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}`);
|
const isClickedOnActionsWrapper = (event.target as Element).closest(`.${this.CSS.actionsWrapper}`);
|
||||||
|
|
||||||
// If click is on actions wrapper,
|
// If click is on actions wrapper,
|
||||||
|
@ -479,7 +479,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
|
|
||||||
this.nodes.togglerAndButtonsWrapper.appendChild(this.nodes.conversionToggler);
|
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) => {
|
this.Editor.ConversionToolbar.toggle((conversionToolbarOpened) => {
|
||||||
/**
|
/**
|
||||||
* When ConversionToolbar is opening on activated InlineToolbar flipper
|
* When ConversionToolbar is opening on activated InlineToolbar flipper
|
||||||
|
@ -594,7 +594,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
*/
|
*/
|
||||||
private addTool(toolName: string, tool: InlineTool): void {
|
private addTool(toolName: string, tool: InlineTool): void {
|
||||||
const {
|
const {
|
||||||
Listeners,
|
|
||||||
Tools,
|
Tools,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} = this.Editor;
|
} = this.Editor;
|
||||||
|
@ -617,7 +616,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
this.nodes.actions.appendChild(actions);
|
this.nodes.actions.appendChild(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
Listeners.on(button, 'click', (event) => {
|
this.listeners.on(button, 'click', (event) => {
|
||||||
this.toolClicked(tool);
|
this.toolClicked(tool);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
|
@ -225,7 +225,7 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
||||||
/**
|
/**
|
||||||
* Add click listener
|
* Add click listener
|
||||||
*/
|
*/
|
||||||
this.Editor.Listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => {
|
this.listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => {
|
||||||
this.toolButtonActivate(event, toolName);
|
this.toolButtonActivate(event, toolName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import Module from '../__module';
|
|
||||||
import * as _ from '../utils';
|
import * as _ from '../utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,11 +35,9 @@ export interface ListenerData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor.js Listeners module
|
* Editor.js Listeners helper
|
||||||
*
|
*
|
||||||
* @module Listeners
|
* Decorator for event listeners assignment
|
||||||
*
|
|
||||||
* Module-decorator for event listeners assignment
|
|
||||||
*
|
*
|
||||||
* @author Codex Team
|
* @author Codex Team
|
||||||
* @version 2.0.0
|
* @version 2.0.0
|
||||||
|
@ -50,7 +47,7 @@ export interface ListenerData {
|
||||||
* @typedef {Listeners} Listeners
|
* @typedef {Listeners} Listeners
|
||||||
* @property {ListenerData[]} allListeners - listeners store
|
* @property {ListenerData[]} allListeners - listeners store
|
||||||
*/
|
*/
|
||||||
export default class Listeners extends Module {
|
export default class Listeners {
|
||||||
/**
|
/**
|
||||||
* Stores all listeners data to find/remove/process it
|
* Stores all listeners data to find/remove/process it
|
||||||
*
|
*
|
||||||
|
@ -114,7 +111,7 @@ export default class Listeners extends Module {
|
||||||
existingListeners.forEach((listener, i) => {
|
existingListeners.forEach((listener, i) => {
|
||||||
const index = this.allListeners.indexOf(existingListeners[i]);
|
const index = this.allListeners.indexOf(existingListeners[i]);
|
||||||
|
|
||||||
if (index > 0) {
|
if (index > -1) {
|
||||||
this.allListeners.splice(index, 1);
|
this.allListeners.splice(index, 1);
|
||||||
|
|
||||||
listener.element.removeEventListener(listener.eventType, listener.handler, listener.options);
|
listener.element.removeEventListener(listener.eventType, listener.handler, listener.options);
|
2
src/types-internal/editor-modules.d.ts
vendored
2
src/types-internal/editor-modules.d.ts
vendored
|
@ -1,6 +1,5 @@
|
||||||
import UI from '../components/modules/ui';
|
import UI from '../components/modules/ui';
|
||||||
import BlockEvents from '../components/modules/blockEvents';
|
import BlockEvents from '../components/modules/blockEvents';
|
||||||
import Listeners from '../components/modules/listeners';
|
|
||||||
import Toolbar from '../components/modules/toolbar/index';
|
import Toolbar from '../components/modules/toolbar/index';
|
||||||
import InlineToolbar from '../components/modules/toolbar/inline';
|
import InlineToolbar from '../components/modules/toolbar/inline';
|
||||||
import Toolbox from '../components/modules/toolbar/toolbox';
|
import Toolbox from '../components/modules/toolbar/toolbox';
|
||||||
|
@ -44,7 +43,6 @@ export interface EditorModules {
|
||||||
BlockEvents: BlockEvents;
|
BlockEvents: BlockEvents;
|
||||||
BlockSelection: BlockSelection;
|
BlockSelection: BlockSelection;
|
||||||
RectangleSelection: RectangleSelection;
|
RectangleSelection: RectangleSelection;
|
||||||
Listeners: Listeners;
|
|
||||||
Toolbar: Toolbar;
|
Toolbar: Toolbar;
|
||||||
InlineToolbar: InlineToolbar;
|
InlineToolbar: InlineToolbar;
|
||||||
Toolbox: Toolbox;
|
Toolbox: Toolbox;
|
||||||
|
|
Loading…
Reference in a new issue