discourse/app/assets/javascripts/discourse-common/addon/lib/discourse-template-map.js
David Taylor 216845e4c7
DEV: Deprecate template overrides (#29544)
Template overrides have been advised against for a long time, and are increasingly hard to maintain as Discourse's development accelerates. This commit officially deprecates this customization method, which will be removed in the not-too-distant future (likely in the first half of 2025).
2024-11-04 17:38:33 +00:00

121 lines
3.2 KiB
JavaScript

import deprecated from "./deprecated";
const pluginRegex = /^discourse\/plugins\/([^\/]+)\/(.*)$/;
const themeRegex = /^discourse\/theme-([^\/]+)\/(.*)$/;
function appendToCache(cache, key, value) {
let cachedValue = cache.get(key);
cachedValue ??= [];
cachedValue.push(value);
cache.set(key, cachedValue);
}
const NAMESPACES = ["discourse/", "admin/"];
function isInRecognisedNamespace(moduleName) {
for (const ns of NAMESPACES) {
if (moduleName.startsWith(ns)) {
return true;
}
}
return false;
}
function isTemplate(moduleName) {
return moduleName.includes("/templates/");
}
/**
* This class provides takes set of core/plugin/theme modules, finds the template modules,
* and makes an efficient lookup table for the resolver to use. It takes care of sourcing
* component/route templates from themes/plugins, and also handles template overrides.
*/
class DiscourseTemplateMap {
coreTemplates = new Map();
pluginTemplates = new Map();
themeTemplates = new Map();
prioritizedCaches = [
this.themeTemplates,
this.pluginTemplates,
this.coreTemplates,
];
/**
* Reset the TemplateMap to use the supplied module names. It is expected that the list
* will be generated using `Object.keys(requirejs.entries)`.
*/
setModuleNames(moduleNames) {
this.coreTemplates.clear();
this.pluginTemplates.clear();
this.themeTemplates.clear();
for (const moduleName of moduleNames) {
if (isInRecognisedNamespace(moduleName) && isTemplate(moduleName)) {
this.#add(moduleName);
}
}
}
#add(originalPath) {
let path = originalPath;
let pluginMatch, themeMatch, cache;
if ((pluginMatch = path.match(pluginRegex))) {
path = pluginMatch[2];
cache = this.pluginTemplates;
} else if ((themeMatch = path.match(themeRegex))) {
path = themeMatch[2];
cache = this.themeTemplates;
} else {
cache = this.coreTemplates;
}
path = path.replace(/^discourse\/templates\//, "");
appendToCache(cache, path, originalPath);
}
/**
* Resolve a template name to a module name, taking into account
* theme/plugin namespaces and overrides.
*/
resolve(name) {
const [themeMatch, pluginMatch, coreMatch] = this.prioritizedCaches.map(
(cache) => {
const val = cache.get(name);
if (val) {
return val[val.length - 1];
}
}
);
if ((themeMatch || pluginMatch) && coreMatch) {
deprecated(
`[${
themeMatch || pluginMatch
}] Overriding templates is deprecated, and will soon be disabled. Use plugin outlets, CSS, or other customization APIs instead.`,
{
id: "discourse.resolver-template-overrides",
url: "https://meta.discourse.org/t/247487",
}
);
}
return themeMatch || pluginMatch || coreMatch;
}
/**
* List all available template keys, after theme/plugin namespaces have
* been stripped.
*/
keys() {
const uniqueKeys = new Set([
...this.coreTemplates.keys(),
...this.pluginTemplates.keys(),
...this.themeTemplates.keys(),
]);
return [...uniqueKeys];
}
}
export default new DiscourseTemplateMap();