diff --git a/app/assets/javascripts/discourse-common/addon/lib/discourse-template-map.js b/app/assets/javascripts/discourse-common/addon/lib/discourse-template-map.js new file mode 100644 index 0000000000..51ec2c6c3c --- /dev/null +++ b/app/assets/javascripts/discourse-common/addon/lib/discourse-template-map.js @@ -0,0 +1,102 @@ +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/", "wizard/", "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) { + for (const cache of this.prioritizedCaches) { + const val = cache.get(name); + if (val) { + return val[val.length - 1]; + } + } + } + + /** + * 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(); diff --git a/app/assets/javascripts/discourse-common/addon/lib/raw-templates.js b/app/assets/javascripts/discourse-common/addon/lib/raw-templates.js index 2b88dfc82b..b979d7c8ff 100644 --- a/app/assets/javascripts/discourse-common/addon/lib/raw-templates.js +++ b/app/assets/javascripts/discourse-common/addon/lib/raw-templates.js @@ -32,11 +32,25 @@ export function findRawTemplate(name) { export function buildRawConnectorCache(findOutlets) { let result = {}; - findOutlets(__DISCOURSE_RAW_TEMPLATES, (outletName, resource) => { - result[outletName] = result[outletName] || []; - result[outletName].push({ - template: __DISCOURSE_RAW_TEMPLATES[resource], - }); - }); + findOutlets( + Object.keys(__DISCOURSE_RAW_TEMPLATES), + (outletName, resource) => { + result[outletName] ??= []; + result[outletName].push({ + template: __DISCOURSE_RAW_TEMPLATES[resource], + }); + } + ); return result; } + +export function eagerLoadRawTemplateModules() { + for (const [key, value] of Object.entries(requirejs.entries)) { + if ( + key.includes("/templates/") && + value.deps.includes("discourse-common/lib/raw-templates") + ) { + require(key); + } + } +} diff --git a/app/assets/javascripts/discourse-common/addon/resolver.js b/app/assets/javascripts/discourse-common/addon/resolver.js index 079161f604..45c0655643 100644 --- a/app/assets/javascripts/discourse-common/addon/resolver.js +++ b/app/assets/javascripts/discourse-common/addon/resolver.js @@ -1,10 +1,10 @@ -import Ember from "ember"; import { dasherize, decamelize } from "@ember/string"; import deprecated from "discourse-common/lib/deprecated"; import { findHelper } from "discourse-common/lib/helpers"; import SuffixTrie from "discourse-common/lib/suffix-trie"; import Resolver from "ember-resolver"; import { buildResolver as buildLegacyResolver } from "discourse-common/lib/legacy-resolver"; +import DiscourseTemplateMap from "discourse-common/lib/discourse-template-map"; let _options = {}; let moduleSuffixTrie = null; @@ -287,21 +287,19 @@ export function buildResolver(baseName) { resolveTemplate(parsedName) { return ( - this.findPluginMobileTemplate(parsedName) || - this.findPluginTemplate(parsedName) || this.findMobileTemplate(parsedName) || this.findTemplate(parsedName) || this.findAdminTemplate(parsedName) || this.findWizardTemplate(parsedName) || this.findLoadingTemplate(parsedName) || this.findConnectorTemplate(parsedName) || - Ember.TEMPLATES.not_found + this.discourseTemplateModule("not_found") ); } findLoadingTemplate(parsedName) { if (parsedName.fullNameWithoutType.match(/loading$/)) { - return Ember.TEMPLATES.loading; + return this.discourseTemplateModule("loading"); } } @@ -312,17 +310,7 @@ export function buildResolver(baseName) { .replace("template:connectors/", "template:") .replace("components/", "") ); - return this.findTemplate(connectorParsedName, "javascripts/"); - } - } - - findPluginTemplate(parsedName) { - return this.findTemplate(parsedName, "javascripts/"); - } - - findPluginMobileTemplate(parsedName) { - if (_options.mobileView) { - return this.findTemplate(parsedName, "javascripts/mobile/"); + return this.findTemplate(connectorParsedName); } } @@ -332,31 +320,43 @@ export function buildResolver(baseName) { } } + /** + * Given a template path, this function will return a template, taking into account + * priority rules for theme and plugin overrides. See `lib/discourse-template-map.js` + */ + discourseTemplateModule(name) { + const resolvedName = DiscourseTemplateMap.resolve(name); + if (resolvedName) { + return require(resolvedName).default; + } + } + findTemplate(parsedName, prefix) { prefix = prefix || ""; const withoutType = parsedName.fullNameWithoutType, underscored = decamelize(withoutType).replace(/-/g, "_"), - segments = withoutType.split("/"), - templates = Ember.TEMPLATES; + segments = withoutType.split("/"); return ( // Convert dots and dashes to slashes - templates[prefix + withoutType.replace(/[\.-]/g, "/")] || + this.discourseTemplateModule( + prefix + withoutType.replace(/[\.-]/g, "/") + ) || // Default unmodified behavior of original resolveTemplate. - templates[prefix + withoutType] || + this.discourseTemplateModule(prefix + withoutType) || // Underscored without namespace - templates[prefix + underscored] || + this.discourseTemplateModule(prefix + underscored) || // Underscored with first segment as directory - templates[prefix + underscored.replace("_", "/")] || + this.discourseTemplateModule(prefix + underscored.replace("_", "/")) || // Underscore only the last segment - templates[ + this.discourseTemplateModule( `${prefix}${segments.slice(0, -1).join("/")}/${segments[ segments.length - 1 ].replace(/-/g, "_")}` - ] || + ) || // All dasherized - templates[prefix + withoutType.replace(/\//g, "-")] + this.discourseTemplateModule(prefix + withoutType.replace(/\//g, "-")) ); } @@ -364,17 +364,15 @@ export function buildResolver(baseName) { // (similar to how discourse lays out templates) findAdminTemplate(parsedName) { if (parsedName.fullNameWithoutType === "admin") { - return Ember.TEMPLATES["admin/templates/admin"]; + return this.discourseTemplateModule("admin/templates/admin"); } let namespaced, match; if (parsedName.fullNameWithoutType.startsWith("components/")) { return ( - // Built-in this.findTemplate(parsedName, "admin/templates/") || - // Plugin - this.findTemplate(parsedName, "javascripts/admin/") + this.findTemplate(parsedName, "admin/") // Nested under discourse/templates/admin (e.g. from plugins) ); } else if (/^admin[_\.-]/.test(parsedName.fullNameWithoutType)) { namespaced = parsedName.fullNameWithoutType.slice(6); @@ -389,11 +387,9 @@ export function buildResolver(baseName) { if (namespaced) { let adminParsedName = this.parseName(`template:${namespaced}`); resolved = - // Built-in this.findTemplate(adminParsedName, "admin/templates/") || this.findTemplate(parsedName, "admin/templates/") || - // Plugin - this.findTemplate(adminParsedName, "javascripts/admin/"); + this.findTemplate(adminParsedName, "admin/"); // Nested under discourse/templates/admin (e.g. from plugin) } return resolved; @@ -401,7 +397,7 @@ export function buildResolver(baseName) { findWizardTemplate(parsedName) { if (parsedName.fullNameWithoutType === "wizard") { - return Ember.TEMPLATES["wizard/templates/wizard"]; + return this.discourseTemplateModule("wizard/templates/wizard"); } let namespaced; @@ -415,10 +411,10 @@ export function buildResolver(baseName) { } if (namespaced) { - let adminParsedName = this.parseName( + let wizardParsedName = this.parseName( `template:wizard/templates/${namespaced}` ); - return this.findTemplate(adminParsedName); + return this.findTemplate(wizardParsedName); } } }; diff --git a/app/assets/javascripts/discourse/app/components/reviewable-item.js b/app/assets/javascripts/discourse/app/components/reviewable-item.js index fed71c9789..10cae579f6 100644 --- a/app/assets/javascripts/discourse/app/components/reviewable-item.js +++ b/app/assets/javascripts/discourse/app/components/reviewable-item.js @@ -9,7 +9,7 @@ import { popupAjaxError } from "discourse/lib/ajax-error"; import { action, set } from "@ember/object"; import showModal from "discourse/lib/show-modal"; import { inject as service } from "@ember/service"; -import Ember from "ember"; +import { getOwner } from "discourse-common/lib/get-owner"; let _components = {}; @@ -106,12 +106,11 @@ export default Component.extend({ return _components[type]; } - let dasherized = dasherize(type); - let templatePath = `components/${dasherized}`; - let template = - Ember.TEMPLATES[`${templatePath}`] || - Ember.TEMPLATES[`javascripts/${templatePath}`]; - _components[type] = template ? dasherized : null; + const dasherized = dasherize(type); + const componentExists = getOwner(this).hasRegistration( + `component:${dasherized}` + ); + _components[type] = componentExists ? dasherized : null; return _components[type]; }, diff --git a/app/assets/javascripts/discourse/app/initializers/eager-load-raw-templates.js b/app/assets/javascripts/discourse/app/initializers/eager-load-raw-templates.js new file mode 100644 index 0000000000..47be0b1303 --- /dev/null +++ b/app/assets/javascripts/discourse/app/initializers/eager-load-raw-templates.js @@ -0,0 +1,9 @@ +import { eagerLoadRawTemplateModules } from "discourse-common/lib/raw-templates"; + +export default { + name: "eager-load-raw-templates", + + initialize() { + eagerLoadRawTemplateModules(); + }, +}; diff --git a/app/assets/javascripts/discourse/app/initializers/populate-template-map.js b/app/assets/javascripts/discourse/app/initializers/populate-template-map.js new file mode 100644 index 0000000000..4d198fed51 --- /dev/null +++ b/app/assets/javascripts/discourse/app/initializers/populate-template-map.js @@ -0,0 +1,8 @@ +import discourseTemplateMap from "discourse-common/lib/discourse-template-map"; + +export default { + name: "populate-template-map", + initialize() { + discourseTemplateMap.setModuleNames(Object.keys(requirejs.entries)); + }, +}; diff --git a/app/assets/javascripts/discourse/app/lib/plugin-connectors.js b/app/assets/javascripts/discourse/app/lib/plugin-connectors.js index a276c1fe1b..0a9e07146c 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-connectors.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-connectors.js @@ -1,6 +1,6 @@ import { buildRawConnectorCache } from "discourse-common/lib/raw-templates"; import deprecated from "discourse-common/lib/deprecated"; -import Ember from "ember"; +import DiscourseTemplateMap from "discourse-common/lib/discourse-template-map"; let _connectorCache; let _rawConnectorCache; @@ -25,11 +25,11 @@ const DefaultConnectorClass = { teardownComponent() {}, }; -function findOutlets(collection, callback) { - Object.keys(collection).forEach(function (res) { - if (res.includes("/connectors/")) { - const segments = res.split("/"); - let outletName = segments[segments.length - 2]; +function findOutlets(keys, callback) { + keys.forEach(function (res) { + const segments = res.split("/"); + if (segments.includes("connectors")) { + const outletName = segments[segments.length - 2]; const uniqueName = segments[segments.length - 1]; callback(outletName, res, uniqueName); @@ -45,7 +45,7 @@ export function clearCache() { function findClass(outletName, uniqueName) { if (!_classPaths) { _classPaths = {}; - findOutlets(require._eak_seen, (outlet, res, un) => { + findOutlets(Object.keys(require._eak_seen), (outlet, res, un) => { const possibleConnectorClass = requirejs(res).default; if (possibleConnectorClass.__id) { // This is the template, not the connector class @@ -63,20 +63,31 @@ function findClass(outletName, uniqueName) { : DefaultConnectorClass; } +/** + * Clear the cache of connectors. Should only be used in tests when + * `requirejs.entries` is changed. + */ +export function expireConnectorCache() { + _connectorCache = null; +} + function buildConnectorCache() { _connectorCache = {}; - findOutlets(Ember.TEMPLATES, (outletName, resource, uniqueName) => { - _connectorCache[outletName] = _connectorCache[outletName] || []; + findOutlets( + DiscourseTemplateMap.keys(), + (outletName, resource, uniqueName) => { + _connectorCache[outletName] = _connectorCache[outletName] || []; - _connectorCache[outletName].push({ - outletName, - templateName: resource.replace("javascripts/", ""), - template: Ember.TEMPLATES[resource], - classNames: `${outletName}-outlet ${uniqueName}`, - connectorClass: findClass(outletName, uniqueName), - }); - }); + _connectorCache[outletName].push({ + outletName, + templateName: resource, + template: require(DiscourseTemplateMap.resolve(resource)).default, + classNames: `${outletName}-outlet ${uniqueName}`, + connectorClass: findClass(outletName, uniqueName), + }); + } + ); } export function connectorsFor(outletName) { diff --git a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js index d9d96d27b9..964fb0b5bf 100644 --- a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js +++ b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js @@ -3,50 +3,6 @@ throw "Unsupported browser detected"; } - // TODO: Remove this and have resolver find the templates - const discoursePrefix = "discourse/templates/"; - const adminPrefix = "admin/templates/"; - const wizardPrefix = "wizard/templates/"; - const discoursePrefixLength = discoursePrefix.length; - - const pluginRegex = /^discourse\/plugins\/([^\/]+)\//; - const themeRegex = /^discourse\/theme-([^\/]+)\//; - - Object.keys(requirejs.entries).forEach(function (key) { - let templateKey; - let pluginName; - let themeId; - if (key.startsWith(discoursePrefix)) { - templateKey = key.slice(discoursePrefixLength); - } else if (key.startsWith(adminPrefix) || key.startsWith(wizardPrefix)) { - templateKey = key; - } else if ( - (pluginName = key.match(pluginRegex)?.[1]) && - key.includes("/templates/") && - require(key).default.__id // really is a template - ) { - // This logic mimics the old sprockets compilation system which used to - // output templates directly to `Ember.TEMPLATES` with this naming logic - templateKey = key.slice(`discourse/plugins/${pluginName}/`.length); - templateKey = templateKey.replace("discourse/templates/", ""); - templateKey = `javascripts/${templateKey}`; - } else if ( - (themeId = key.match(themeRegex)?.[1]) && - key.includes("/templates/") - ) { - // And likewise for themes - this mimics the old logic - templateKey = key.slice(`discourse/theme-${themeId}/`.length); - templateKey = templateKey.replace("discourse/templates/", ""); - if (!templateKey.startsWith("javascripts/")) { - templateKey = `javascripts/${templateKey}`; - } - } - - if (templateKey) { - Ember.TEMPLATES[templateKey] = require(key).default; - } - }); - window.__widget_helpers = require("discourse-widget-hbs/helpers").default; // TODO: Eliminate this global diff --git a/app/assets/javascripts/discourse/tests/acceptance/custom-html-template-test.js b/app/assets/javascripts/discourse/tests/acceptance/custom-html-template-test.js index 117084efd7..c6825446a0 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/custom-html-template-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/custom-html-template-test.js @@ -2,15 +2,14 @@ import { acceptance, query } from "discourse/tests/helpers/qunit-helpers"; import { hbs } from "ember-cli-htmlbars"; import { test } from "qunit"; import { visit } from "@ember/test-helpers"; -import Ember from "ember"; +import { registerTemplateModule } from "discourse/tests/helpers/template-module-helper"; acceptance("CustomHTML template", function (needs) { needs.hooks.beforeEach(() => { - Ember.TEMPLATES["top"] = hbs`TOP`; - }); - - needs.hooks.afterEach(() => { - delete Ember.TEMPLATES["top"]; + registerTemplateModule( + "discourse/templates/top", + hbs`TOP` + ); }); test("renders custom template", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/acceptance/modal-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal-test.js index f2257ab9f3..398c18eda4 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/modal-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/modal-test.js @@ -10,7 +10,7 @@ import { test } from "qunit"; import I18n from "I18n"; import { hbs } from "ember-cli-htmlbars"; import showModal from "discourse/lib/show-modal"; -import Ember from "ember"; +import { registerTemplateModule } from "../helpers/template-module-helper"; acceptance("Modal", function (needs) { let _translations; @@ -54,9 +54,10 @@ acceptance("Modal", function (needs) { await triggerKeyEvent("#main-outlet", "keydown", "Escape"); assert.ok(!exists(".d-modal:visible"), "ESC should close the modal"); - Ember.TEMPLATES[ - "modal/not-dismissable" - ] = hbs`{{#d-modal-body title="" class="" dismissable=false}}test{{/d-modal-body}}`; + registerTemplateModule( + "discourse/templates/modal/not-dismissable", + hbs`{{#d-modal-body title="" class="" dismissable=false}}test{{/d-modal-body}}` + ); showModal("not-dismissable", {}); await settled(); @@ -78,7 +79,10 @@ acceptance("Modal", function (needs) { }); test("rawTitle in modal panels", async function (assert) { - Ember.TEMPLATES["modal/test-raw-title-panels"] = hbs``; + registerTemplateModule( + "discourse/templates/modal/test-raw-title-panels", + hbs`` + ); const panels = [ { id: "test1", rawTitle: "Test 1" }, { id: "test2", rawTitle: "Test 2" }, @@ -96,10 +100,11 @@ acceptance("Modal", function (needs) { }); test("modal title", async function (assert) { - Ember.TEMPLATES["modal/test-title"] = hbs``; - Ember.TEMPLATES[ - "modal/test-title-with-body" - ] = hbs`{{#d-modal-body}}test{{/d-modal-body}}`; + registerTemplateModule("discourse/templates/modal/test-title", hbs``); + registerTemplateModule( + "discourse/templates/modal/test-title-with-body", + hbs`{{#d-modal-body}}test{{/d-modal-body}}` + ); await visit("/"); diff --git a/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-connector-class-test.js b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-connector-class-test.js index 712a07fa13..89fb051f26 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-connector-class-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-connector-class-test.js @@ -9,9 +9,9 @@ import { action } from "@ember/object"; import { extraConnectorClass } from "discourse/lib/plugin-connectors"; import { hbs } from "ember-cli-htmlbars"; import { test } from "qunit"; -import Ember from "ember"; +import { registerTemplateModule } from "discourse/tests/helpers/template-module-helper"; -const PREFIX = "javascripts/single-test/connectors"; +const PREFIX = "discourse/plugins/some-plugin/templates/connectors"; acceptance("Plugin Outlet - Connector Class", function (needs) { needs.hooks.beforeEach(() => { @@ -49,25 +49,22 @@ acceptance("Plugin Outlet - Connector Class", function (needs) { }, }); - Ember.TEMPLATES[ - `${PREFIX}/user-profile-primary/hello` - ] = hbs`{{model.username}} + registerTemplateModule( + `${PREFIX}/user-profile-primary/hello`, + hbs`{{model.username}} - {{hello}}`; - Ember.TEMPLATES[ - `${PREFIX}/user-profile-primary/hi` - ] = hbs` - {{hi}}`; - Ember.TEMPLATES[ - `${PREFIX}/user-profile-primary/dont-render` - ] = hbs`I'm not rendered!`; - }); - - needs.hooks.afterEach(() => { - delete Ember.TEMPLATES[`${PREFIX}/user-profile-primary/hello`]; - delete Ember.TEMPLATES[`${PREFIX}/user-profile-primary/hi`]; - delete Ember.TEMPLATES[`${PREFIX}/user-profile-primary/dont-render`]; + {{hello}}` + ); + registerTemplateModule( + `${PREFIX}/user-profile-primary/hi`, + hbs` + {{hi}}` + ); + registerTemplateModule( + `${PREFIX}/user-profile-primary/dont-render`, + hbs`I'm not rendered!` + ); }); test("Renders a template into the outlet", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-decorator-test.js b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-decorator-test.js index c287449f09..e269882d8d 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-decorator-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-decorator-test.js @@ -7,16 +7,22 @@ import { hbs } from "ember-cli-htmlbars"; import { test } from "qunit"; import { visit } from "@ember/test-helpers"; import { withPluginApi } from "discourse/lib/plugin-api"; -import Ember from "ember"; +import { registerTemplateModule } from "../helpers/template-module-helper"; -const PREFIX = "javascripts/single-test/connectors"; +const PREFIX = "discourse/plugins/some-plugin/templates/connectors"; acceptance("Plugin Outlet - Decorator", function (needs) { needs.user(); needs.hooks.beforeEach(() => { - Ember.TEMPLATES[`${PREFIX}/discovery-list-container-top/foo`] = hbs`FOO`; - Ember.TEMPLATES[`${PREFIX}/discovery-list-container-top/bar`] = hbs`BAR`; + registerTemplateModule( + `${PREFIX}/discovery-list-container-top/foo`, + hbs`FOO` + ); + registerTemplateModule( + `${PREFIX}/discovery-list-container-top/bar`, + hbs`BAR` + ); withPluginApi("0.8.38", (api) => { api.decoratePluginOutlet( @@ -37,11 +43,6 @@ acceptance("Plugin Outlet - Decorator", function (needs) { }); }); - needs.hooks.afterEach(() => { - delete Ember.TEMPLATES[`${PREFIX}/discovery-list-container-top/foo`]; - delete Ember.TEMPLATES[`${PREFIX}/discovery-list-container-top/bar`]; - }); - test("Calls the plugin callback with the rendered outlet", async function (assert) { await visit("/"); diff --git a/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js index b5e70c3a5a..ba81c83a1f 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js @@ -6,21 +6,17 @@ import { import { hbs } from "ember-cli-htmlbars"; import { test } from "qunit"; import { visit } from "@ember/test-helpers"; -import Ember from "ember"; +import { registerTemplateModule } from "../helpers/template-module-helper"; -const HELLO = "javascripts/multi-test/connectors/user-profile-primary/hello"; +const HELLO = + "discourse/plugins/my-plugin/templates/connectors/user-profile-primary/hello"; const GOODBYE = - "javascripts/multi-test/connectors/user-profile-primary/goodbye"; + "discourse/plugins/my-plugin/templates/connectors/user-profile-primary/goodbye"; acceptance("Plugin Outlet - Multi Template", function (needs) { needs.hooks.beforeEach(() => { - Ember.TEMPLATES[HELLO] = hbs`Hello`; - Ember.TEMPLATES[GOODBYE] = hbs`Goodbye`; - }); - - needs.hooks.afterEach(() => { - delete Ember.TEMPLATES[HELLO]; - delete Ember.TEMPLATES[GOODBYE]; + registerTemplateModule(HELLO, hbs`Hello`); + registerTemplateModule(GOODBYE, hbs`Goodbye`); }); test("Renders a template into the outlet", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-single-template-test.js b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-single-template-test.js index 1f9e06d096..e1a0cc62c0 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-single-template-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-single-template-test.js @@ -6,20 +6,17 @@ import { import { hbs } from "ember-cli-htmlbars"; import { test } from "qunit"; import { visit } from "@ember/test-helpers"; -import Ember from "ember"; +import { registerTemplateModule } from "../helpers/template-module-helper"; -const CONNECTOR = - "javascripts/single-test/connectors/user-profile-primary/hello"; +const CONNECTOR_MODULE = + "discourse/theme-12/templates/connectors/user-profile-primary/hello"; acceptance("Plugin Outlet - Single Template", function (needs) { needs.hooks.beforeEach(() => { - Ember.TEMPLATES[ - CONNECTOR - ] = hbs`{{model.username}}`; - }); - - needs.hooks.afterEach(() => { - delete Ember.TEMPLATES[CONNECTOR]; + registerTemplateModule( + CONNECTOR_MODULE, + hbs`{{model.username}}` + ); }); test("Renders a template into the outlet", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js index 5c2c3a609b..da71ce3683 100644 --- a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js +++ b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js @@ -76,6 +76,7 @@ import { resetNotificationTypeRenderers } from "discourse/lib/notification-types import { resetUserMenuTabs } from "discourse/lib/user-menu/tab"; import { reset as resetLinkLookup } from "discourse/lib/link-lookup"; import { resetModelTransformers } from "discourse/lib/model-transformers"; +import { cleanupTemporaryTemplateRegistrations } from "./template-module-helper"; export function currentUser() { return User.create(sessionFixtures["/session/current.json"].current_user); @@ -207,6 +208,7 @@ export function testCleanup(container, app) { resetUserMenuTabs(); resetLinkLookup(); resetModelTransformers(); + cleanupTemporaryTemplateRegistrations(); } export function discourseModule(name, options) { diff --git a/app/assets/javascripts/discourse/tests/helpers/template-module-helper.js b/app/assets/javascripts/discourse/tests/helpers/template-module-helper.js new file mode 100644 index 0000000000..ae1930d805 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/helpers/template-module-helper.js @@ -0,0 +1,40 @@ +import DiscourseTemplateMap from "discourse-common/lib/discourse-template-map"; +import { expireConnectorCache } from "discourse/lib/plugin-connectors"; + +const modifications = []; + +function generateTemplateModule(template) { + return function (_exports) { + Object.defineProperty(_exports, "__esModule", { + value: true, + }); + _exports.default = template; + }; +} + +export function registerTemplateModule(moduleName, template) { + const modificationData = { + moduleName, + existingModule: requirejs.entries[moduleName], + }; + delete requirejs.entries[moduleName]; + define(moduleName, ["exports"], generateTemplateModule(template)); + modifications.push(modificationData); + expireConnectorCache(); + DiscourseTemplateMap.setModuleNames(Object.keys(requirejs.entries)); +} + +export function cleanupTemporaryTemplateRegistrations() { + for (const modificationData of modifications.reverse()) { + const { moduleName, existingModule } = modificationData; + delete requirejs.entries[moduleName]; + if (existingModule) { + requirejs.entries[moduleName] = existingModule; + } + } + if (modifications.length) { + expireConnectorCache(); + DiscourseTemplateMap.setModuleNames(Object.keys(requirejs.entries)); + } + modifications.clear(); +} diff --git a/app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js b/app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js index 14aafdb325..04e48df1a1 100644 --- a/app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js +++ b/app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js @@ -1,8 +1,8 @@ import { buildResolver, setResolverOption } from "discourse-common/resolver"; import { module, test } from "qunit"; -import Ember from "ember"; +import { registerTemplateModule } from "discourse/tests/helpers/template-module-helper"; +import DiscourseTemplateMap from "discourse-common/lib/discourse-template-map"; -let originalTemplates; let resolver; function lookupTemplate(assert, name, expectedTemplate, message) { @@ -11,57 +11,71 @@ function lookupTemplate(assert, name, expectedTemplate, message) { assert.strictEqual(result, expectedTemplate, message); } -function setTemplates(lookupTemplateStrings) { - lookupTemplateStrings.forEach(function (lookupTemplateString) { - Ember.TEMPLATES[lookupTemplateString] = lookupTemplateString; - }); +function setTemplates(templateModuleNames) { + for (const name of templateModuleNames) { + registerTemplateModule(name, name); + } } const DiscourseResolver = buildResolver("discourse"); module("Unit | Ember | resolver", function (hooks) { hooks.beforeEach(function () { - originalTemplates = Ember.TEMPLATES; - Ember.TEMPLATES = {}; - + DiscourseTemplateMap.setModuleNames(Object.keys(requirejs.entries)); resolver = DiscourseResolver.create({ namespace: { modulePrefix: "discourse" }, }); }); - hooks.afterEach(function () { - Ember.TEMPLATES = originalTemplates; - }); - test("finds templates in top level dir", function (assert) { - setTemplates(["foobar", "fooBar", "foo_bar", "foo.bar"]); + setTemplates([ + "discourse/templates/foobar", + "discourse/templates/fooBar", + "discourse/templates/foo_bar", + "discourse/templates/foo.bar", + ]); // Default unmodified behavior - lookupTemplate(assert, "template:foobar", "foobar", "by lowcased name"); + lookupTemplate( + assert, + "template:foobar", + "discourse/templates/foobar", + "by lowcased name" + ); // Default unmodified behavior - lookupTemplate(assert, "template:fooBar", "fooBar", "by camel cased name"); + lookupTemplate( + assert, + "template:fooBar", + "discourse/templates/fooBar", + "by camel cased name" + ); // Default unmodified behavior lookupTemplate( assert, "template:foo_bar", - "foo_bar", + "discourse/templates/foo_bar", "by underscored name" ); // Default unmodified behavior - lookupTemplate(assert, "template:foo.bar", "foo.bar", "by dotted name"); + lookupTemplate( + assert, + "template:foo.bar", + "discourse/templates/foo.bar", + "by dotted name" + ); }); test("finds templates in first-level subdir", function (assert) { - setTemplates(["foo/bar_baz"]); + setTemplates(["discourse/templates/foo/bar_baz"]); // Default unmodified behavior lookupTemplate( assert, "template:foo/bar_baz", - "foo/bar_baz", + "discourse/templates/foo/bar_baz", "with subdir defined by slash" ); @@ -69,7 +83,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:foo.bar_baz", - "foo/bar_baz", + "discourse/templates/foo/bar_baz", "with subdir defined by dot" ); @@ -77,7 +91,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:foo-bar_baz", - "foo/bar_baz", + "discourse/templates/foo/bar_baz", "with subdir defined by dash" ); @@ -85,7 +99,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:fooBarBaz", - "foo/bar_baz", + "discourse/templates/foo/bar_baz", "with subdir defined by first camel case and the rest of camel cases converted to underscores" ); @@ -93,19 +107,25 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:foo_bar_baz", - "foo/bar_baz", + "discourse/templates/foo/bar_baz", "with subdir defined by first underscore" ); }); test("resolves precedence between overlapping top level dir and first level subdir templates", function (assert) { - setTemplates(["fooBar", "foo_bar", "foo.bar", "foo/bar", "baz/qux"]); + setTemplates([ + "discourse/templates/fooBar", + "discourse/templates/foo_bar", + "discourse/templates/foo.bar", + "discourse/templates/foo/bar", + "discourse/templates/baz/qux", + ]); // Directories are prioritized when dotted lookupTemplate( assert, "template:foo.bar", - "foo/bar", + "discourse/templates/foo/bar", "preferring first level subdir for dotted name" ); @@ -113,7 +133,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:foo-bar", - "foo/bar", + "discourse/templates/foo/bar", "preferring first level subdir for dotted name" ); @@ -121,7 +141,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:fooBar", - "fooBar", + "discourse/templates/fooBar", "preferring top level dir for camel cased name" ); @@ -129,7 +149,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:foo_bar", - "foo_bar", + "discourse/templates/foo_bar", "preferring top level dir for underscored name" ); @@ -137,19 +157,19 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:baz-qux", - "baz/qux", + "discourse/templates/baz/qux", "fallback subdir for dashed name" ); }); test("finds templates in subdir deeper than one level", function (assert) { - setTemplates(["foo/bar/baz/qux"]); + setTemplates(["discourse/templates/foo/bar/baz/qux"]); // Default unmodified lookupTemplate( assert, "template:foo/bar/baz/qux", - "foo/bar/baz/qux", + "discourse/templates/foo/bar/baz/qux", "for subdirs defined by slashes" ); @@ -157,7 +177,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:foo.bar.baz.qux", - "foo/bar/baz/qux", + "discourse/templates/foo/bar/baz/qux", "for subdirs defined by dots" ); @@ -165,7 +185,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:foo/bar/bazQux", - "foo/bar/baz/qux", + "discourse/templates/foo/bar/baz/qux", "for subdirs defined by slashes plus one camel case" ); @@ -173,7 +193,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:foo/bar/baz_qux", - "foo/bar/baz/qux", + "discourse/templates/foo/bar/baz/qux", "for subdirs defined by slashes plus one underscore" ); @@ -211,7 +231,12 @@ module("Unit | Ember | resolver", function (hooks) { }); test("resolves mobile templates to 'mobile/' namespace", function (assert) { - setTemplates(["mobile/foo", "bar", "mobile/bar", "baz"]); + setTemplates([ + "discourse/templates/mobile/foo", + "discourse/templates/bar", + "discourse/templates/mobile/bar", + "discourse/templates/baz", + ]); setResolverOption("mobileView", true); @@ -219,7 +244,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:foo", - "mobile/foo", + "discourse/templates/mobile/foo", "finding mobile version even if normal one is not present" ); @@ -227,7 +252,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:bar", - "mobile/bar", + "discourse/templates/mobile/bar", "preferring mobile version when both mobile and normal versions are present" ); @@ -235,71 +260,87 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:baz", - "baz", + "discourse/templates/baz", "falling back to a normal version when mobile version is not present" ); }); - test("resolves plugin templates to 'javascripts/' namespace", function (assert) { - setTemplates(["javascripts/foo", "bar", "javascripts/bar", "baz"]); + test("resolves templates to plugin and theme namespaces", function (assert) { + setTemplates([ + "discourse/plugins/my-plugin/discourse/templates/foo", + "discourse/templates/bar", + "discourse/plugins/my-plugin/discourse/templates/bar", + "discourse/templates/baz", + "discourse/plugins/my-plugin/discourse/templates/baz", + "discourse/theme-12/discourse/templates/baz", + "discourse/templates/qux", + ]); - // Default with javascripts/ added + // Defined in plugin only lookupTemplate( assert, "template:foo", - "javascripts/foo", + "discourse/plugins/my-plugin/discourse/templates/foo", "finding plugin version even if normal one is not present" ); - // Default with javascripts/ added, takes precedence + // Defined in core and plugin lookupTemplate( assert, "template:bar", - "javascripts/bar", - "preferring plugin version when both versions are present" + "discourse/plugins/my-plugin/discourse/templates/bar", + "prefers plugin version over core" ); - // Default when javascripts version not present + // Defined in core and plugin and theme lookupTemplate( assert, "template:baz", - "baz", - "falling back to a normal version when plugin version is not present" + "discourse/theme-12/discourse/templates/baz", + "prefers theme version over plugin and core" + ); + + // Defined in core only + lookupTemplate( + assert, + "template:qux", + "discourse/templates/qux", + "uses core if there are no theme/plugin definitions" ); }); - test("resolves plugin mobile templates to 'javascripts/mobile/' namespace", function (assert) { + test("resolves plugin mobile templates", function (assert) { setTemplates([ - "javascripts/mobile/foo", - "javascripts/mobile/bar", - "javascripts/bar", - "javascripts/mobile/baz", - "mobile/baz", + "discourse/plugins/my-plugin/discourse/templates/mobile/foo", + "discourse/plugins/my-plugin/discourse/templates/mobile/bar", + "discourse/plugins/my-plugin/discourse/templates/bar", + "discourse/plugins/my-plugin/discourse/templates/mobile/baz", + "discourse/templates/mobile/baz", ]); setResolverOption("mobileView", true); - // Default with javascripts/mobile/ added + // Default with plugin template override lookupTemplate( assert, "template:foo", - "javascripts/mobile/foo", + "discourse/plugins/my-plugin/discourse/templates/mobile/foo", "finding plugin version even if normal one is not present" ); - // Default with javascripts/mobile added, takes precedence over non-mobile + // Default with plugin mobile added, takes precedence over non-mobile lookupTemplate( assert, "template:bar", - "javascripts/mobile/bar", + "discourse/plugins/my-plugin/discourse/templates/mobile/bar", "preferring plugin mobile version when both non-mobile plugin version is also present" ); - // Default with javascripts/mobile when non-plugin mobile version is present + // Default with when non-plugin mobile version is present lookupTemplate( assert, "template:baz", - "javascripts/mobile/baz", + "discourse/plugins/my-plugin/discourse/templates/mobile/baz", "preferring plugin mobile version over non-plugin mobile version" ); }); @@ -307,13 +348,13 @@ module("Unit | Ember | resolver", function (hooks) { test("resolves templates with 'admin' prefix", function (assert) { setTemplates([ "admin/templates/foo", - "adminBar", - "admin_bar", - "admin.bar", + "discourse/templates/adminBar", + "discourse/templates/admin_bar", + "discourse/templates/admin.bar", "admin/templates/bar", "admin/templates/dashboard_general", - "admin-baz-qux", - "javascripts/admin/plugin-template", + "discourse/templates/admin-baz-qux", + "discourse/plugins/my-plugin/discourse/templates/admin/plugin-template", "admin/templates/components/my-admin-component", ]); @@ -353,7 +394,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:adminBar", - "adminBar", + "discourse/templates/adminBar", "but not when template with the exact camel cased name exists" ); @@ -361,7 +402,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:admin_bar", - "admin_bar", + "discourse/templates/admin_bar", "but not when template with the exact underscored name exists" ); @@ -369,7 +410,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:admin.bar", - "admin.bar", + "discourse/templates/admin.bar", "but not when template with the exact dotted name exists" ); @@ -383,14 +424,14 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:admin-baz/qux", - "admin-baz-qux", + "discourse/templates/admin-baz-qux", "also tries dasherized" ); lookupTemplate( assert, "template:admin-plugin/template", - "javascripts/admin/plugin-template", + "discourse/plugins/my-plugin/discourse/templates/admin/plugin-template", "looks up templates in plugins" ); @@ -412,7 +453,7 @@ module("Unit | Ember | resolver", function (hooks) { test("resolves component templates with 'admin' prefix to 'admin/templates/' namespace", function (assert) { setTemplates([ "admin/templates/components/foo", - "components/bar", + "discourse/templates/components/bar", "admin/templates/components/bar", ]); @@ -428,7 +469,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:components/bar", - "components/bar", + "discourse/templates/components/bar", "uses standard match when both exist" ); }); @@ -437,54 +478,59 @@ module("Unit | Ember | resolver", function (hooks) { // close to Ember's default behavior. // See https://guides.emberjs.com/release/routing/loading-and-error-substates/ test("resolves loading templates", function (assert) { - setTemplates(["fooloading", "foo/loading", "foo_loading", "loading"]); + setTemplates([ + "discourse/templates/fooloading", + "discourse/templates/foo/loading", + "discourse/templates/foo_loading", + "discourse/templates/loading", + ]); lookupTemplate( assert, "template:fooloading", - "fooloading", + "discourse/templates/fooloading", "exact match without separator" ); lookupTemplate( assert, "template:foo/loading", - "foo/loading", + "discourse/templates/foo/loading", "exact match with slash" ); lookupTemplate( assert, "template:foo_loading", - "foo_loading", + "discourse/templates/foo_loading", "exact match underscore" ); lookupTemplate( assert, "template:barloading", - "loading", + "discourse/templates/loading", "fallback without separator" ); lookupTemplate( assert, "template:bar/loading", - "loading", + "discourse/templates/loading", "fallback with slash" ); lookupTemplate( assert, "template:bar.loading", - "loading", + "discourse/templates/loading", "fallback with dot" ); lookupTemplate( assert, "template:bar_loading", - "loading", + "discourse/templates/loading", "fallback underscore" ); @@ -493,61 +539,66 @@ module("Unit | Ember | resolver", function (hooks) { test("resolves connector templates", function (assert) { setTemplates([ - "javascripts/foo", - "javascripts/connectors/foo-bar/baz_qux", - "javascripts/connectors/foo-bar/camelCase", + "discourse/plugins/my-plugin/discourse/templates/foo", + "discourse/plugins/my-plugin/discourse/templates/connectors/foo-bar/baz_qux", + "discourse/plugins/my-plugin/discourse/templates/connectors/foo-bar/camelCase", ]); lookupTemplate( assert, "template:connectors/foo", - "javascripts/foo", - "looks up in javascripts/ namespace" + "discourse/plugins/my-plugin/discourse/templates/foo", + "looks up in plugin namespace" ); lookupTemplate( assert, "template:connectors/components/foo", - "javascripts/foo", + "discourse/plugins/my-plugin/discourse/templates/foo", "removes components segment" ); lookupTemplate( assert, "template:connectors/foo-bar/baz-qux", - "javascripts/connectors/foo-bar/baz_qux", + "discourse/plugins/my-plugin/discourse/templates/connectors/foo-bar/baz_qux", "underscores last segment" ); lookupTemplate( assert, "template:connectors/foo-bar/camelCase", - "javascripts/connectors/foo-bar/camelCase", + "discourse/plugins/my-plugin/discourse/templates/connectors/foo-bar/camelCase", "handles camelcase file names" ); lookupTemplate( assert, resolver.normalize("template:connectors/foo-bar/camelCase"), - "javascripts/connectors/foo-bar/camelCase", + "discourse/plugins/my-plugin/discourse/templates/connectors/foo-bar/camelCase", "handles camelcase file names when normalized" ); }); test("returns 'not_found' template when template name cannot be resolved", function (assert) { - setTemplates(["not_found"]); + setTemplates(["discourse/templates/not_found"]); - lookupTemplate(assert, "template:foo/bar/baz", "not_found", ""); + lookupTemplate( + assert, + "template:foo/bar/baz", + "discourse/templates/not_found", + "" + ); }); test("resolves templates with 'wizard' prefix", function (assert) { setTemplates([ "wizard/templates/foo", - "wizard_bar", - "wizard.bar", + "discourse/templates/wizard_bar", + "discourse/templates/wizard.bar", "wizard/templates/bar", "wizard/templates/dashboard_general", - "wizard-baz-qux", + "discourse/templates/wizard-baz-qux", "javascripts/wizard/plugin-template", ]); @@ -579,7 +630,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:wizard_bar", - "wizard_bar", + "discourse/templates/wizard_bar", "but not when template with the exact underscored name exists" ); @@ -587,7 +638,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:wizard.bar", - "wizard.bar", + "discourse/templates/wizard.bar", "but not when template with the exact dotted name exists" ); @@ -601,7 +652,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:wizard-baz/qux", - "wizard-baz-qux", + "discourse/templates/wizard-baz-qux", "also tries dasherized" ); }); @@ -609,7 +660,7 @@ module("Unit | Ember | resolver", function (hooks) { test("resolves component templates with 'wizard' prefix to 'wizard/templates/' namespace", function (assert) { setTemplates([ "wizard/templates/components/foo", - "components/bar", + "discourse/templates/components/bar", "wizard/templates/components/bar", ]); @@ -625,7 +676,7 @@ module("Unit | Ember | resolver", function (hooks) { lookupTemplate( assert, "template:components/bar", - "components/bar", + "discourse/templates/components/bar", "uses standard match when both exist" ); }); diff --git a/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js b/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js index 8b1610d07d..aac2b772ef 100644 --- a/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js +++ b/plugins/styleguide/assets/javascripts/discourse/lib/styleguide.js @@ -1,3 +1,4 @@ +import DiscourseTemplateMap from "discourse-common/lib/discourse-template-map"; let _allCategories = null; let _sectionsById = {}; let _notes = {}; @@ -30,7 +31,7 @@ export function allCategories() { // Find a list of sections based on what templates are available // eslint-disable-next-line no-undef - Object.keys(Ember.TEMPLATES).forEach((e) => { + DiscourseTemplateMap.keys().forEach((e) => { let regexp = new RegExp(`styleguide\/(${paths})\/(\\d+)?\\-?([^\\/]+)$`); let matches = e.match(regexp); if (matches) {