You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
4.3 KiB
JavaScript

'use strict';
define('hooks', [], () => {
const Hooks = {
loaded: {},
temporary: new Set(),
runOnce: new Set(),
deprecated: {
},
logs: {
_collection: new Set(),
},
};
Hooks.logs.collect = () => {
if (Hooks.logs._collection) {
return;
}
Hooks.logs._collection = new Set();
};
Hooks.logs.log = (...args) => {
if (Hooks.logs._collection) {
Hooks.logs._collection.add(args);
} else {
console.debug.apply(console, args);
}
};
Hooks.logs.flush = () => {
if (Hooks.logs._collection && Hooks.logs._collection.size) {
Hooks.logs._collection.forEach((args) => {
console.debug.apply(console, args);
});
console.groupEnd();
}
delete Hooks.logs._collection;
};
Hooks.register = (hookName, method) => {
Hooks.loaded[hookName] = Hooks.loaded[hookName] || new Set();
Hooks.loaded[hookName].add(method);
if (Hooks.deprecated.hasOwnProperty(hookName)) {
const deprecated = Hooks.deprecated[hookName];
if (deprecated) {
console.groupCollapsed(`[hooks] Hook "${hookName}" is deprecated, please use "${deprecated}" instead.`);
} else {
console.groupCollapsed(`[hooks] Hook "${hookName}" is deprecated, there is no alternative.`);
}
console.info(method);
console.groupEnd();
}
Hooks.logs.log(`[hooks] Registered ${hookName}`, method);
return Hooks;
};
Hooks.on = Hooks.register;
Hooks.one = (hookName, method) => {
Hooks.runOnce.add({ hookName, method });
return Hooks.register(hookName, method);
};
// registerPage/onPage takes care of unregistering the listener on ajaxify
Hooks.registerPage = (hookName, method) => {
Hooks.temporary.add({ hookName, method });
return Hooks.register(hookName, method);
};
Hooks.onPage = Hooks.registerPage;
Hooks.register('action:ajaxify.start', () => {
Hooks.temporary.forEach((pair) => {
Hooks.unregister(pair.hookName, pair.method);
Hooks.temporary.delete(pair);
});
});
Hooks.unregister = (hookName, method) => {
if (Hooks.loaded[hookName] && Hooks.loaded[hookName].has(method)) {
Hooks.loaded[hookName].delete(method);
Hooks.logs.log(`[hooks] Unregistered ${hookName}`, method);
} else {
Hooks.logs.log(`[hooks] Unregistration of ${hookName} failed, passed-in method is not a registered listener or the hook itself has no listeners, currently.`);
}
return Hooks;
};
Hooks.off = Hooks.unregister;
Hooks.hasListeners = hookName => Hooks.loaded[hookName] && Hooks.loaded[hookName].size > 0;
const _onHookError = (e, listener, data) => {
console.warn(`[hooks] Exception encountered in ${listener.name ? listener.name : 'anonymous function'}, stack trace follows.`);
console.error(e);
return Promise.resolve(data);
};
const _fireFilterHook = (hookName, data) => {
if (!Hooks.hasListeners(hookName)) {
return Promise.resolve(data);
}
const listeners = Array.from(Hooks.loaded[hookName]);
return listeners.reduce((promise, listener) => promise.then((data) => {
const result = listener(data);
return utils.isPromise(result) ?
result.then(data => Promise.resolve(data)) :
result;
}), Promise.resolve(data));
};
const _fireActionHook = (hookName, data) => {
if (Hooks.hasListeners(hookName)) {
Hooks.loaded[hookName].forEach(listener => listener(data));
}
// Backwards compatibility (remove this when we eventually remove jQuery from NodeBB core)
$(window).trigger(hookName, data);
};
const _fireStaticHook = async (hookName, data) => {
if (!Hooks.hasListeners(hookName)) {
return Promise.resolve(data);
}
const listeners = Array.from(Hooks.loaded[hookName]);
await Promise.allSettled(listeners.map((listener) => {
try {
return listener(data);
} catch (e) {
return _onHookError(e, listener);
}
}));
return await Promise.resolve(data);
};
Hooks.fire = (hookName, data) => {
const type = hookName.split(':').shift();
let result;
switch (type) {
case 'filter':
result = _fireFilterHook(hookName, data);
break;
case 'action':
result = _fireActionHook(hookName, data);
break;
case 'static':
result = _fireStaticHook(hookName, data);
break;
}
Hooks.runOnce.forEach((pair) => {
if (pair.hookName === hookName) {
Hooks.unregister(hookName, pair.method);
Hooks.runOnce.delete(pair);
}
});
return result;
};
return Hooks;
});