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
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;
|
|
});
|