jodit
Version:
Jodit is an awesome and useful wysiwyg editor with filebrowser
482 lines (481 loc) • 17.5 kB
JavaScript
/*!
* Jodit Editor (https://xdsoft.net/jodit/)
* Released under MIT see LICENSE.txt in the project root for license information.
* Copyright (c) 2013-2025 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
*/
import { globalDocument, PASSIVE_EVENTS } from "../constants.js";
import { splitArray } from "../helpers/array/split-array.js";
import { isArray } from "../helpers/checker/is-array.js";
import { isFunction } from "../helpers/checker/is-function.js";
import { isString, isStringArray } from "../helpers/checker/is-string.js";
import { error } from "../helpers/utils/error/index.js";
import { defaultNameSpace, EventHandlersStore } from "./store.js";
/**
* The module editor's event manager
*/
export class EventEmitter {
mute(event) {
this.__mutedEvents.add(event !== null && event !== void 0 ? event : '*');
return this;
}
isMuted(event) {
if (event && this.__mutedEvents.has(event)) {
return true;
}
return this.__mutedEvents.has('*');
}
unmute(event) {
this.__mutedEvents.delete(event !== null && event !== void 0 ? event : '*');
return this;
}
__eachEvent(events, callback) {
const eventParts = splitArray(events).map(e => e.trim());
eventParts.forEach(eventNameSpace => {
const eventAndNameSpace = eventNameSpace.split('.');
const namespace = eventAndNameSpace[1] || defaultNameSpace;
callback.call(this, eventAndNameSpace[0], namespace);
});
}
__getStore(subject) {
if (!subject) {
throw error('Need subject');
}
if (subject[this.__key] === undefined) {
const store = new EventHandlersStore();
Object.defineProperty(subject, this.__key, {
enumerable: false,
configurable: true,
writable: true,
value: store
});
}
return subject[this.__key];
}
__removeStoreFromSubject(subject) {
if (subject[this.__key] !== undefined) {
Object.defineProperty(subject, this.__key, {
enumerable: false,
configurable: true,
writable: true,
value: undefined
});
}
}
__triggerNativeEvent(element, event) {
const evt = this.__doc.createEvent('HTMLEvents');
if (isString(event)) {
evt.initEvent(event, true, true);
}
else {
evt.initEvent(event.type, event.bubbles, event.cancelable);
[
'screenX',
'screenY',
'clientX',
'clientY',
'target',
'srcElement',
'currentTarget',
'timeStamp',
'which',
'keyCode'
].forEach(property => {
Object.defineProperty(evt, property, {
value: event[property],
enumerable: true
});
});
Object.defineProperty(evt, 'originalEvent', {
value: event,
enumerable: true
});
}
element.dispatchEvent(evt);
}
/**
* Get current event name
*
* @example
* ```javascript
* parent.e.on('openDialog closeDialog', function () {
* if (parent.e.current === 'closeDialog') {
* alert('Dialog was closed');
* } else {
* alert('Dialog was opened');
* }
* });
* ```
*/
get current() {
return this.currents[this.currents.length - 1];
}
on(eventsOrSubjects, callbackOrEvents, optionsOrCallback, opts) {
let subjects;
let events;
let callback;
let options;
if (isString(eventsOrSubjects) || isStringArray(eventsOrSubjects)) {
subjects = this;
events = eventsOrSubjects;
callback = callbackOrEvents;
options = optionsOrCallback;
}
else {
subjects = eventsOrSubjects;
events = callbackOrEvents;
callback = optionsOrCallback;
options = opts;
}
if (!(isString(events) || isStringArray(events)) ||
events.length === 0) {
throw error('Need events names');
}
if (!isFunction(callback)) {
throw error('Need event handler');
}
if (isArray(subjects)) {
subjects.forEach(subj => {
this.on(subj, events, callback, options);
});
return this;
}
const subject = subjects;
const store = this.__getStore(subject);
const self = this;
let syntheticCallback = function (event, ...args) {
if (self.isMuted(event)) {
return;
}
return callback && callback.call(this, ...args);
};
if (isDOMElement(subject)) {
syntheticCallback = function (event) {
if (self.isMuted(event.type)) {
return;
}
self.__prepareEvent(event);
if (callback && callback.call(this, event) === false) {
event.preventDefault();
event.stopImmediatePropagation();
return false;
}
return;
};
}
this.__eachEvent(events, (event, namespace) => {
var _a, _b;
if (event.length === 0) {
throw error('Need event name');
}
if (store.indexOf(event, namespace, callback) === false) {
const block = {
event,
originalCallback: callback,
syntheticCallback
};
store.set(event, namespace, block, options === null || options === void 0 ? void 0 : options.top);
if (isDOMElement(subject)) {
const eOpts = PASSIVE_EVENTS.has(event)
? {
passive: true,
capture: (_a = options === null || options === void 0 ? void 0 : options.capture) !== null && _a !== void 0 ? _a : false
}
: ((_b = options === null || options === void 0 ? void 0 : options.capture) !== null && _b !== void 0 ? _b : false);
syntheticCallback.options = eOpts;
subject.addEventListener(event, syntheticCallback, eOpts);
this.__memoryDOMSubjectToHandler(subject, syntheticCallback);
}
}
});
return this;
}
__memoryDOMSubjectToHandler(subject, syntheticCallback) {
const callbackStore = this.__domEventsMap.get(subject) || new Set();
callbackStore.add(syntheticCallback);
this.__domEventsMap.set(subject, callbackStore);
}
__unmemoryDOMSubjectToHandler(subject, syntheticCallback) {
const m = this.__domEventsMap;
const callbackStore = m.get(subject) || new Set();
callbackStore.delete(syntheticCallback);
if (callbackStore.size) {
m.set(subject, callbackStore);
}
else {
m.delete(subject);
}
}
one(eventsOrSubjects, callbackOrEvents, optionsOrCallback, opts) {
let subjects;
let events;
let callback;
let options;
if (isString(eventsOrSubjects) || isStringArray(eventsOrSubjects)) {
subjects = this;
events = eventsOrSubjects;
callback = callbackOrEvents;
options = optionsOrCallback;
}
else {
subjects = eventsOrSubjects;
events = callbackOrEvents;
callback = optionsOrCallback;
options = opts;
}
const newCallback = (...args) => {
this.off(subjects, events, newCallback);
return callback(...args);
};
this.on(subjects, events, newCallback, options);
return this;
}
off(eventsOrSubjects, callbackOrEvents, handler) {
let subjects;
let events;
let callback;
if (isString(eventsOrSubjects) || isStringArray(eventsOrSubjects)) {
subjects = this;
events = eventsOrSubjects;
callback = callbackOrEvents;
}
else {
subjects = eventsOrSubjects;
events = callbackOrEvents;
callback = handler;
}
if (isArray(subjects)) {
subjects.forEach(subj => {
this.off(subj, events, callback);
});
return this;
}
const subject = subjects;
const store = this.__getStore(subject);
if (!(isString(events) || isStringArray(events)) ||
events.length === 0) {
store.namespaces().forEach((namespace) => {
this.off(subject, '.' + namespace);
});
this.__removeStoreFromSubject(subject);
return this;
}
const removeEventListener = (block) => {
var _a;
if (isDOMElement(subject)) {
subject.removeEventListener(block.event, block.syntheticCallback, (_a = block.syntheticCallback.options) !== null && _a !== void 0 ? _a : false);
this.__unmemoryDOMSubjectToHandler(subject, block.syntheticCallback);
}
}, removeCallbackFromNameSpace = (event, namespace) => {
if (event === '') {
store.events(namespace).forEach((eventName) => {
if (eventName !== '') {
removeCallbackFromNameSpace(eventName, namespace);
}
});
return;
}
const blocks = store.get(event, namespace);
if (!blocks || !blocks.length) {
return;
}
if (!isFunction(callback)) {
blocks.forEach(removeEventListener);
blocks.length = 0;
store.clearEvents(namespace, event);
}
else {
const index = store.indexOf(event, namespace, callback);
if (index !== false) {
removeEventListener(blocks[index]);
blocks.splice(index, 1);
if (!blocks.length) {
store.clearEvents(namespace, event);
}
}
}
};
this.__eachEvent(events, (event, namespace) => {
if (namespace === defaultNameSpace) {
store.namespaces().forEach(namespace => {
removeCallbackFromNameSpace(event, namespace);
});
}
else {
removeCallbackFromNameSpace(event, namespace);
}
});
if (store.isEmpty()) {
this.__removeStoreFromSubject(subject);
}
return this;
}
stopPropagation(subjectOrEvents, eventsList) {
const subject = isString(subjectOrEvents)
? this
: subjectOrEvents;
const events = isString(subjectOrEvents)
? subjectOrEvents
: eventsList;
if (typeof events !== 'string') {
throw error('Need event names');
}
const store = this.__getStore(subject);
this.__eachEvent(events, (event, namespace) => {
const blocks = store.get(event, namespace);
if (blocks) {
this.__stopped.push(blocks);
}
if (namespace === defaultNameSpace) {
store
.namespaces(true)
.forEach(ns => this.stopPropagation(subject, event + '.' + ns));
}
});
}
__removeStop(currentBlocks) {
if (currentBlocks) {
const index = this.__stopped.indexOf(currentBlocks);
index !== -1 && this.__stopped.splice(0, index + 1);
}
}
__isStopped(currentBlocks) {
return (currentBlocks !== undefined &&
this.__stopped.indexOf(currentBlocks) !== -1);
}
fire(subjectOrEvents, eventsList, ...args) {
let result, result_value;
const subject = isString(subjectOrEvents)
? this
: subjectOrEvents;
const events = isString(subjectOrEvents)
? subjectOrEvents
: eventsList;
const argumentsList = isString(subjectOrEvents)
? [eventsList, ...args]
: args;
if (!isDOMElement(subject) && !isString(events)) {
throw error('Need events names');
}
const store = this.__getStore(subject);
if (!isString(events) && isDOMElement(subject)) {
this.__triggerNativeEvent(subject, eventsList);
}
else {
this.__eachEvent(events, (event, namespace) => {
if (isDOMElement(subject)) {
this.__triggerNativeEvent(subject, event);
}
else {
const blocks = store.get(event, namespace);
if (blocks) {
try {
[...blocks].every((block) => {
if (this.__isStopped(blocks)) {
return false;
}
this.currents.push(event);
result_value =
block.syntheticCallback.call(subject, event, ...argumentsList);
this.currents.pop();
if (result_value !== undefined) {
result = result_value;
}
return true;
});
}
finally {
this.__removeStop(blocks);
}
}
if (namespace === defaultNameSpace &&
!isDOMElement(subject)) {
store
.namespaces()
.filter(ns => ns !== namespace)
.forEach((ns) => {
const result_second = this.fire.apply(this, [
subject,
event + '.' + ns,
...argumentsList
]);
if (result_second !== undefined) {
result = result_second;
}
});
}
}
});
}
return result;
}
constructor(doc) {
this.__domEventsMap = new Map();
this.__mutedEvents = new Set();
this.__key = '__JoditEventEmitterNamespaces';
this.__doc = globalDocument;
this.__prepareEvent = (e) => {
if (e.cancelBubble) {
return;
}
// for Shadow Dom
if (e.composed && isFunction(e.composedPath) && e.composedPath()[0]) {
Object.defineProperty(e, 'target', {
value: e.composedPath()[0],
configurable: true,
enumerable: true
});
}
if (e.type.match(/^touch/) &&
e.changedTouches &&
e.changedTouches.length) {
['clientX', 'clientY', 'pageX', 'pageY'].forEach((key) => {
Object.defineProperty(e, key, {
value: e.changedTouches[0][key],
configurable: true,
enumerable: true
});
});
}
if (!e.originalEvent) {
e.originalEvent = e;
}
if (e.type === 'paste' &&
e.clipboardData === undefined &&
this.__doc.defaultView.clipboardData) {
Object.defineProperty(e, 'clipboardData', {
get: () => {
return this.__doc.defaultView.clipboardData;
},
configurable: true,
enumerable: true
});
}
};
this.currents = [];
this.__stopped = [];
this.__isDestructed = false;
if (doc) {
this.__doc = doc;
}
this.__key += new Date().getTime();
}
destruct() {
if (this.__isDestructed) {
return;
}
this.__isDestructed = true;
this.__domEventsMap.forEach((set, elm) => {
this.off(elm);
});
this.__domEventsMap.clear();
this.__mutedEvents.clear();
this.currents.length = 0;
this.__stopped.length = 0;
this.off(this);
this.__getStore(this).clear();
this.__removeStoreFromSubject(this);
}
}
function isDOMElement(subject) {
return isFunction(subject.addEventListener);
}