UNPKG

ckeditor5-image-upload-base64

Version:

The development environment of CKEditor 5 – the best browser-based rich text editor.

276 lines (245 loc) 11.5 kB
/** * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ /** * @module utils/dom/emittermixin */ import { default as EmitterMixin, _getEmitterListenedTo, _setEmitterId } from '../emittermixin'; import uid from '../uid'; import isNode from './isnode'; import isWindow from './iswindow'; import { extend } from 'lodash-es'; /** * Mixin that injects the DOM events API into its host. It provides the API * compatible with {@link module:utils/emittermixin~EmitterMixin}. * * DOM emitter mixin is by default available in the {@link module:ui/view~View} class, * but it can also be mixed into any other class: * * import mix from '../utils/mix.js'; * import DomEmitterMixin from '../utils/dom/emittermixin.js'; * * class SomeView {} * mix( SomeView, DomEmitterMixin ); * * const view = new SomeView(); * view.listenTo( domElement, ( evt, domEvt ) => { * console.log( evt, domEvt ); * } ); * * @mixin EmitterMixin * @mixes module:utils/emittermixin~EmitterMixin * @implements module:utils/dom/emittermixin~Emitter */ const DomEmitterMixin = extend( {}, EmitterMixin, { /** * Registers a callback function to be executed when an event is fired in a specific Emitter or DOM Node. * It is backwards compatible with {@link module:utils/emittermixin~EmitterMixin#listenTo}. * * @param {module:utils/emittermixin~Emitter|Node} emitter The object that fires the event. * @param {String} event The name of the event. * @param {Function} callback The function to be called on event. * @param {Object} [options={}] Additional options. * @param {module:utils/priorities~PriorityString|Number} [options.priority='normal'] The priority of this event callback. The higher * the priority value the sooner the callback will be fired. Events having the same priority are called in the * order they were added. * @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered * listener before being dispatched to any EventTarget beneath it in the DOM tree. * @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault() * and prevents blocking browser's main thread by this event handler. */ listenTo( emitter, ...rest ) { // Check if emitter is an instance of DOM Node. If so, replace the argument with // corresponding ProxyEmitter (or create one if not existing). if ( isNode( emitter ) || isWindow( emitter ) ) { const proxy = this._getProxyEmitter( emitter ) || new ProxyEmitter( emitter ); proxy.attach( ...rest ); emitter = proxy; } // Execute parent class method with Emitter (or ProxyEmitter) instance. EmitterMixin.listenTo.call( this, emitter, ...rest ); }, /** * Stops listening for events. It can be used at different levels: * It is backwards compatible with {@link module:utils/emittermixin~EmitterMixin#listenTo}. * * * To stop listening to a specific callback. * * To stop listening to a specific event. * * To stop listening to all events fired by a specific object. * * To stop listening to all events fired by all object. * * @param {module:utils/emittermixin~Emitter|Node} [emitter] The object to stop listening to. If omitted, stops it for all objects. * @param {String} [event] (Requires the `emitter`) The name of the event to stop listening to. If omitted, stops it * for all events from `emitter`. * @param {Function} [callback] (Requires the `event`) The function to be removed from the call list for the given * `event`. */ stopListening( emitter, event, callback ) { // Check if emitter is an instance of DOM Node. If so, replace the argument with corresponding ProxyEmitter. if ( isNode( emitter ) || isWindow( emitter ) ) { const proxy = this._getProxyEmitter( emitter ); // Element has no listeners. if ( !proxy ) { return; } emitter = proxy; } // Execute parent class method with Emitter (or ProxyEmitter) instance. EmitterMixin.stopListening.call( this, emitter, event, callback ); if ( emitter instanceof ProxyEmitter ) { emitter.detach( event ); } }, /** * Retrieves ProxyEmitter instance for given DOM Node residing in this Host. * * @private * @param {Node} node DOM Node of the ProxyEmitter. * @returns {module:utils/dom/emittermixin~ProxyEmitter} ProxyEmitter instance or null. */ _getProxyEmitter( node ) { return _getEmitterListenedTo( this, getNodeUID( node ) ); } } ); export default DomEmitterMixin; /** * Creates a ProxyEmitter instance. Such an instance is a bridge between a DOM Node firing events * and any Host listening to them. It is backwards compatible with {@link module:utils/emittermixin~EmitterMixin#on}. * * listenTo( click, ... ) * +-----------------------------------------+ * | stopListening( ... ) | * +----------------------------+ | addEventListener( click, ... ) * | Host | | +---------------------------------------------+ * +----------------------------+ | | removeEventListener( click, ... ) | * | _listeningTo: { | +----------v-------------+ | * | UID: { | | ProxyEmitter | | * | emitter: ProxyEmitter, | +------------------------+ +------------v----------+ * | callbacks: { | | events: { | | Node (HTMLElement) | * | click: [ callbacks ] | | click: [ callbacks ] | +-----------------------+ * | } | | }, | | data-ck-expando: UID | * | } | | _domNode: Node, | +-----------------------+ * | } | | _domListeners: {}, | | * | +------------------------+ | | _emitterId: UID | | * | | DomEmitterMixin | | +--------------^---------+ | * | +------------------------+ | | | | * +--------------^-------------+ | +---------------------------------------------+ * | | click (DOM Event) * +-----------------------------------------+ * fire( click, DOM Event ) * * @mixes module:utils/emittermixin~EmitterMixin * @implements module:utils/dom/emittermixin~Emitter * @private */ class ProxyEmitter { /** * @param {Node} node DOM Node that fires events. * @returns {Object} ProxyEmitter instance bound to the DOM Node. */ constructor( node ) { // Set emitter ID to match DOM Node "expando" property. _setEmitterId( this, getNodeUID( node ) ); // Remember the DOM Node this ProxyEmitter is bound to. this._domNode = node; } } extend( ProxyEmitter.prototype, EmitterMixin, { /** * Collection of native DOM listeners. * * @private * @member {Object} module:utils/dom/emittermixin~ProxyEmitter#_domListeners */ /** * Registers a callback function to be executed when an event is fired. * * It attaches a native DOM listener to the DOM Node. When fired, * a corresponding Emitter event will also fire with DOM Event object as an argument. * * @method module:utils/dom/emittermixin~ProxyEmitter#attach * @param {String} event The name of the event. * @param {Function} callback The function to be called on event. * @param {Object} [options={}] Additional options. * @param {Boolean} [options.useCapture=false] Indicates that events of this type will be dispatched to the registered * listener before being dispatched to any EventTarget beneath it in the DOM tree. * @param {Boolean} [options.usePassive=false] Indicates that the function specified by listener will never call preventDefault() * and prevents blocking browser's main thread by this event handler. */ attach( event, callback, options = {} ) { // If the DOM Listener for given event already exist it is pointless // to attach another one. if ( this._domListeners && this._domListeners[ event ] ) { return; } const listenerOptions = { capture: !!options.useCapture, passive: !!options.usePassive }; const domListener = this._createDomListener( event, listenerOptions ); // Attach the native DOM listener to DOM Node. this._domNode.addEventListener( event, domListener, listenerOptions ); if ( !this._domListeners ) { this._domListeners = {}; } // Store the native DOM listener in this ProxyEmitter. It will be helpful // when stopping listening to the event. this._domListeners[ event ] = domListener; }, /** * Stops executing the callback on the given event. * * @method module:utils/dom/emittermixin~ProxyEmitter#detach * @param {String} event The name of the event. */ detach( event ) { let events; // Remove native DOM listeners which are orphans. If no callbacks // are awaiting given event, detach native DOM listener from DOM Node. // See: {@link attach}. if ( this._domListeners[ event ] && ( !( events = this._events[ event ] ) || !events.callbacks.length ) ) { this._domListeners[ event ].removeListener(); } }, /** * Creates a native DOM listener callback. When the native DOM event * is fired it will fire corresponding event on this ProxyEmitter. * Note: A native DOM Event is passed as an argument. * * @private * @method module:utils/dom/emittermixin~ProxyEmitter#_createDomListener * @param {String} event The name of the event. * @param {Object} [options] Additional options. * @param {Boolean} [options.capture=false] Indicates whether the listener was created for capturing event. * @param {Boolean} [options.passive=false] Indicates that the function specified by listener will never call preventDefault() * and prevents blocking browser's main thread by this event handler. * @returns {Function} The DOM listener callback. */ _createDomListener( event, options ) { const domListener = domEvt => { this.fire( event, domEvt ); }; // Supply the DOM listener callback with a function that will help // detach it from the DOM Node, when it is no longer necessary. // See: {@link detach}. domListener.removeListener = () => { this._domNode.removeEventListener( event, domListener, options ); delete this._domListeners[ event ]; }; return domListener; } } ); // Gets an unique DOM Node identifier. The identifier will be set if not defined. // // @private // @param {Node} node // @returns {String} UID for given DOM Node. function getNodeUID( node ) { return node[ 'data-ck-expando' ] || ( node[ 'data-ck-expando' ] = uid() ); } /** * Interface representing classes which mix in {@link module:utils/dom/emittermixin~EmitterMixin}. * * @interface Emitter */