mirror of
https://github.com/discourse/discourse.git
synced 2025-03-09 14:34:35 +00:00
DEV: Modernize Ember Resolver (#17353)
This switches us to use the modern ember resolver package, and re-implements a number of our custom resolution rules within it. The legacy resolver remains for now, and is used as a fallback if the modern resolver is unable to resolve a package. When this happens, a warning will be printed to the console. Co-authored-by: Peter Wagenet <peter.wagenet@gmail.com>
This commit is contained in:
parent
5c4c8d26c7
commit
fc36ac6cde
@ -156,7 +156,9 @@ export default Component.extend({
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._darkModeListener.removeListener(this.setAceTheme);
|
||||
if (this._darkModeListener) {
|
||||
this._darkModeListener.removeListener(this.setAceTheme);
|
||||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
|
@ -0,0 +1,3 @@
|
||||
import Controller from "@ember/controller";
|
||||
|
||||
export default Controller.extend();
|
@ -0,0 +1,336 @@
|
||||
/* global Ember */
|
||||
import { classify, dasherize, decamelize } from "@ember/string";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
import { findHelper } from "discourse-common/lib/helpers";
|
||||
import { get } from "@ember/object";
|
||||
import SuffixTrie from "discourse-common/lib/suffix-trie";
|
||||
|
||||
let _options = {};
|
||||
let moduleSuffixTrie = null;
|
||||
|
||||
export function setResolverOption(name, value) {
|
||||
_options[name] = value;
|
||||
}
|
||||
|
||||
export function getResolverOption(name) {
|
||||
return _options[name];
|
||||
}
|
||||
|
||||
export function clearResolverOptions() {
|
||||
_options = {};
|
||||
}
|
||||
|
||||
function parseName(fullName) {
|
||||
const nameParts = fullName.split(":");
|
||||
const type = nameParts[0];
|
||||
let fullNameWithoutType = nameParts[1];
|
||||
const namespace = get(this, "namespace");
|
||||
const root = namespace;
|
||||
|
||||
return {
|
||||
fullName,
|
||||
type,
|
||||
fullNameWithoutType,
|
||||
name: fullNameWithoutType,
|
||||
root,
|
||||
resolveMethodName: "resolve" + classify(type),
|
||||
};
|
||||
}
|
||||
|
||||
function lookupModuleBySuffix(suffix) {
|
||||
if (!moduleSuffixTrie) {
|
||||
moduleSuffixTrie = new SuffixTrie("/");
|
||||
Object.keys(requirejs.entries).forEach((name) => {
|
||||
if (!name.includes("/templates/")) {
|
||||
moduleSuffixTrie.add(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
return moduleSuffixTrie.withSuffix(suffix, 1)[0];
|
||||
}
|
||||
|
||||
export function buildResolver(baseName) {
|
||||
return Ember.DefaultResolver.extend({
|
||||
parseName,
|
||||
|
||||
resolveRouter(parsedName) {
|
||||
const routerPath = `${baseName}/router`;
|
||||
if (requirejs.entries[routerPath]) {
|
||||
const module = requirejs(routerPath, null, null, true);
|
||||
return module.default;
|
||||
}
|
||||
return this._super(parsedName);
|
||||
},
|
||||
|
||||
normalize(fullName) {
|
||||
if (fullName === "app-events:main") {
|
||||
deprecated(
|
||||
"`app-events:main` has been replaced with `service:app-events`",
|
||||
{ since: "2.4.0", dropFrom: "2.9.0.beta1" }
|
||||
);
|
||||
return "service:app-events";
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries({
|
||||
"controller:discovery.categoryWithID": "controller:discovery.category",
|
||||
"controller:discovery.parentCategory": "controller:discovery.category",
|
||||
"controller:tags-show": "controller:tag-show",
|
||||
"controller:tags.show": "controller:tag.show",
|
||||
"controller:tagsShow": "controller:tagShow",
|
||||
"route:discovery.categoryWithID": "route:discovery.category",
|
||||
"route:discovery.parentCategory": "route:discovery.category",
|
||||
"route:tags-show": "route:tag-show",
|
||||
"route:tags.show": "route:tag.show",
|
||||
"route:tagsShow": "route:tagShow",
|
||||
})) {
|
||||
if (fullName === key) {
|
||||
deprecated(`${key} was replaced with ${value}`, { since: "2.6.0" });
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
const split = fullName.split(":");
|
||||
if (split.length > 1) {
|
||||
const appBase = `${baseName}/${split[0]}s/`;
|
||||
const adminBase = "admin/" + split[0] + "s/";
|
||||
const wizardBase = "wizard/" + split[0] + "s/";
|
||||
|
||||
// Allow render 'admin/templates/xyz' too
|
||||
split[1] = split[1].replace(".templates", "").replace("/templates", "");
|
||||
|
||||
// Try slashes
|
||||
let dashed = dasherize(split[1].replace(/\./g, "/"));
|
||||
if (
|
||||
requirejs.entries[appBase + dashed] ||
|
||||
requirejs.entries[adminBase + dashed] ||
|
||||
requirejs.entries[wizardBase + dashed]
|
||||
) {
|
||||
return split[0] + ":" + dashed;
|
||||
}
|
||||
|
||||
// Try with dashes instead of slashes
|
||||
dashed = dasherize(split[1].replace(/\./g, "-"));
|
||||
if (
|
||||
requirejs.entries[appBase + dashed] ||
|
||||
requirejs.entries[adminBase + dashed] ||
|
||||
requirejs.entries[wizardBase + dashed]
|
||||
) {
|
||||
return split[0] + ":" + dashed;
|
||||
}
|
||||
}
|
||||
return this._super(fullName);
|
||||
},
|
||||
|
||||
customResolve(parsedName) {
|
||||
// If we end with the name we want, use it. This allows us to define components within plugins.
|
||||
const suffix = parsedName.type + "s/" + parsedName.fullNameWithoutType,
|
||||
dashed = dasherize(suffix),
|
||||
moduleName = lookupModuleBySuffix(dashed);
|
||||
|
||||
let module;
|
||||
if (moduleName) {
|
||||
module = requirejs(moduleName, null, null, true /* force sync */);
|
||||
if (module && module["default"]) {
|
||||
module = module["default"];
|
||||
}
|
||||
}
|
||||
return module;
|
||||
},
|
||||
|
||||
resolveWidget(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveAdapter(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveModel(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveView(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveHelper(parsedName) {
|
||||
return (
|
||||
findHelper(parsedName.fullNameWithoutType) ||
|
||||
this.customResolve(parsedName) ||
|
||||
this._super(parsedName)
|
||||
);
|
||||
},
|
||||
|
||||
resolveController(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveComponent(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveService(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveRawView(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveRoute(parsedName) {
|
||||
if (parsedName.fullNameWithoutType === "basic") {
|
||||
return requirejs("discourse/routes/discourse", null, null, true)
|
||||
.default;
|
||||
}
|
||||
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
findLoadingTemplate(parsedName) {
|
||||
if (parsedName.fullNameWithoutType.match(/loading$/)) {
|
||||
return Ember.TEMPLATES.loading;
|
||||
}
|
||||
},
|
||||
|
||||
findConnectorTemplate(parsedName) {
|
||||
const full = parsedName.fullNameWithoutType.replace("components/", "");
|
||||
if (full.indexOf("connectors") === 0) {
|
||||
return Ember.TEMPLATES[`javascripts/${full}`];
|
||||
}
|
||||
},
|
||||
|
||||
resolveTemplate(parsedName) {
|
||||
return (
|
||||
this.findPluginMobileTemplate(parsedName) ||
|
||||
this.findPluginTemplate(parsedName) ||
|
||||
this.findMobileTemplate(parsedName) ||
|
||||
this.findTemplate(parsedName) ||
|
||||
this.findLoadingTemplate(parsedName) ||
|
||||
this.findConnectorTemplate(parsedName) ||
|
||||
Ember.TEMPLATES.not_found
|
||||
);
|
||||
},
|
||||
|
||||
findPluginTemplate(parsedName) {
|
||||
const pluginParsedName = this.parseName(
|
||||
parsedName.fullName.replace("template:", "template:javascripts/")
|
||||
);
|
||||
return this.findTemplate(pluginParsedName);
|
||||
},
|
||||
|
||||
findPluginMobileTemplate(parsedName) {
|
||||
if (_options.mobileView) {
|
||||
let pluginParsedName = this.parseName(
|
||||
parsedName.fullName.replace(
|
||||
"template:",
|
||||
"template:javascripts/mobile/"
|
||||
)
|
||||
);
|
||||
return this.findTemplate(pluginParsedName);
|
||||
}
|
||||
},
|
||||
|
||||
findMobileTemplate(parsedName) {
|
||||
if (_options.mobileView) {
|
||||
let mobileParsedName = this.parseName(
|
||||
parsedName.fullName.replace("template:", "template:mobile/")
|
||||
);
|
||||
return this.findTemplate(mobileParsedName);
|
||||
}
|
||||
},
|
||||
|
||||
findTemplate(parsedName) {
|
||||
const withoutType = parsedName.fullNameWithoutType,
|
||||
slashedType = withoutType.replace(/\./g, "/"),
|
||||
decamelized = decamelize(withoutType),
|
||||
dashed = decamelized.replace(/\./g, "-").replace(/\_/g, "-"),
|
||||
templates = Ember.TEMPLATES;
|
||||
|
||||
return (
|
||||
this._super(parsedName) ||
|
||||
templates[slashedType] ||
|
||||
templates[withoutType] ||
|
||||
templates[withoutType.replace(/\.raw$/, "")] ||
|
||||
templates[dashed] ||
|
||||
templates[decamelized.replace(/\./, "/")] ||
|
||||
templates[decamelized.replace(/\_/, "/")] ||
|
||||
templates[`${baseName}/templates/${withoutType}`] ||
|
||||
this.findAdminTemplate(parsedName) ||
|
||||
this.findWizardTemplate(parsedName) ||
|
||||
this.findUnderscoredTemplate(parsedName)
|
||||
);
|
||||
},
|
||||
|
||||
findUnderscoredTemplate(parsedName) {
|
||||
let decamelized = decamelize(parsedName.fullNameWithoutType);
|
||||
let underscored = decamelized.replace(/\-/g, "_");
|
||||
return Ember.TEMPLATES[underscored];
|
||||
},
|
||||
|
||||
// Try to find a template within a special admin namespace, e.g. adminEmail => admin/templates/email
|
||||
// (similar to how discourse lays out templates)
|
||||
findAdminTemplate(parsedName) {
|
||||
let decamelized = decamelize(parsedName.fullNameWithoutType);
|
||||
if (decamelized.indexOf("components") === 0) {
|
||||
let comPath = `admin/templates/${decamelized}`;
|
||||
const compTemplate =
|
||||
Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath];
|
||||
if (compTemplate) {
|
||||
return compTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
if (decamelized === "javascripts/admin") {
|
||||
return Ember.TEMPLATES["admin/templates/admin"];
|
||||
}
|
||||
|
||||
if (
|
||||
decamelized.indexOf("admin") === 0 ||
|
||||
decamelized.indexOf("javascripts/admin") === 0
|
||||
) {
|
||||
decamelized = decamelized.replace(/^admin\_/, "admin/templates/");
|
||||
decamelized = decamelized.replace(/^admin\./, "admin/templates/");
|
||||
decamelized = decamelized.replace(/\./g, "_");
|
||||
|
||||
const dashed = decamelized.replace(/_/g, "-");
|
||||
return (
|
||||
Ember.TEMPLATES[decamelized] ||
|
||||
Ember.TEMPLATES[dashed] ||
|
||||
Ember.TEMPLATES[dashed.replace("admin-", "admin/")]
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
findWizardTemplate(parsedName) {
|
||||
let decamelized = decamelize(parsedName.fullNameWithoutType);
|
||||
if (decamelized.startsWith("components")) {
|
||||
let comPath = `wizard/templates/${decamelized}`;
|
||||
const compTemplate =
|
||||
Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath];
|
||||
if (compTemplate) {
|
||||
return compTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
if (decamelized === "javascripts/wizard") {
|
||||
return Ember.TEMPLATES["wizard/templates/wizard"];
|
||||
}
|
||||
|
||||
if (
|
||||
decamelized.startsWith("wizard") ||
|
||||
decamelized.startsWith("javascripts/wizard")
|
||||
) {
|
||||
decamelized = decamelized.replace(/^wizard\_/, "wizard/templates/");
|
||||
decamelized = decamelized.replace(/^wizard\./, "wizard/templates/");
|
||||
decamelized = decamelized.replace(/\./g, "_");
|
||||
|
||||
const dashed = decamelized.replace(/_/g, "-");
|
||||
return (
|
||||
Ember.TEMPLATES[decamelized] ||
|
||||
Ember.TEMPLATES[dashed] ||
|
||||
Ember.TEMPLATES[dashed.replace("wizard-", "wizard/")]
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
/* eslint-disable no-undef */
|
||||
import { classify, dasherize, decamelize } from "@ember/string";
|
||||
/* global Ember */
|
||||
import { dasherize, decamelize } from "@ember/string";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
import { findHelper } from "discourse-common/lib/helpers";
|
||||
import { get } from "@ember/object";
|
||||
import SuffixTrie from "discourse-common/lib/suffix-trie";
|
||||
import Resolver from "ember-resolver";
|
||||
import { buildResolver as buildLegacyResolver } from "discourse-common/lib/legacy-resolver";
|
||||
|
||||
let _options = {};
|
||||
let moduleSuffixTrie = null;
|
||||
@ -20,23 +21,6 @@ export function clearResolverOptions() {
|
||||
_options = {};
|
||||
}
|
||||
|
||||
function parseName(fullName) {
|
||||
const nameParts = fullName.split(":");
|
||||
const type = nameParts[0];
|
||||
let fullNameWithoutType = nameParts[1];
|
||||
const namespace = get(this, "namespace");
|
||||
const root = namespace;
|
||||
|
||||
return {
|
||||
fullName,
|
||||
type,
|
||||
fullNameWithoutType,
|
||||
name: fullNameWithoutType,
|
||||
root,
|
||||
resolveMethodName: "resolve" + classify(type),
|
||||
};
|
||||
}
|
||||
|
||||
function lookupModuleBySuffix(suffix) {
|
||||
if (!moduleSuffixTrie) {
|
||||
moduleSuffixTrie = new SuffixTrie("/");
|
||||
@ -50,25 +34,32 @@ function lookupModuleBySuffix(suffix) {
|
||||
}
|
||||
|
||||
export function buildResolver(baseName) {
|
||||
return Ember.DefaultResolver.extend({
|
||||
parseName,
|
||||
let LegacyResolver = buildLegacyResolver(baseName);
|
||||
|
||||
resolveRouter(parsedName) {
|
||||
return class extends Resolver {
|
||||
LegacyResolver = LegacyResolver;
|
||||
|
||||
init(props) {
|
||||
super.init(props);
|
||||
this.legacyResolver = this.LegacyResolver.create(props);
|
||||
}
|
||||
|
||||
resolveRouter(/* parsedName */) {
|
||||
const routerPath = `${baseName}/router`;
|
||||
if (requirejs.entries[routerPath]) {
|
||||
const module = requirejs(routerPath, null, null, true);
|
||||
return module.default;
|
||||
}
|
||||
return this._super(parsedName);
|
||||
},
|
||||
}
|
||||
|
||||
normalize(fullName) {
|
||||
// We overwrite this instead of `normalize` so we still get the benefits of the cache.
|
||||
_normalize(fullName) {
|
||||
if (fullName === "app-events:main") {
|
||||
deprecated(
|
||||
"`app-events:main` has been replaced with `service:app-events`",
|
||||
{ since: "2.4.0", dropFrom: "2.9.0.beta1" }
|
||||
);
|
||||
return "service:app-events";
|
||||
fullName = "service:app-events";
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries({
|
||||
@ -85,119 +76,110 @@ export function buildResolver(baseName) {
|
||||
})) {
|
||||
if (fullName === key) {
|
||||
deprecated(`${key} was replaced with ${value}`, { since: "2.6.0" });
|
||||
return value;
|
||||
fullName = value;
|
||||
}
|
||||
}
|
||||
|
||||
let normalized = super._normalize(fullName);
|
||||
|
||||
// This is code that we don't really want to keep long term. The main situation where we need it is for
|
||||
// doing stuff like `controllerFor('adminWatchedWordsAction')` where the real route name
|
||||
// is actually `adminWatchedWords.action`. The default behavior for the former is to
|
||||
// normalize to `adminWatchedWordsAction` where the latter becomes `adminWatchedWords.action`.
|
||||
// While these end up looking up the same file ultimately, they are treated as different
|
||||
// items and so we can end up with two distinct version of the controller!
|
||||
const split = fullName.split(":");
|
||||
if (split.length > 1) {
|
||||
const appBase = `${baseName}/${split[0]}s/`;
|
||||
const adminBase = "admin/" + split[0] + "s/";
|
||||
const wizardBase = "wizard/" + split[0] + "s/";
|
||||
const type = split[0];
|
||||
if (
|
||||
split.length > 1 &&
|
||||
(type === "controller" || type === "route" || type === "template")
|
||||
) {
|
||||
let corrected;
|
||||
// This should only apply when there's a dot or slash in the name
|
||||
if (split[1].includes(".") || split[1].includes("/")) {
|
||||
// Check to see if the dasherized version exists. If it does we want to
|
||||
// normalize to that eagerly so the normalized versions of the dotted/slashed and
|
||||
// dotless/slashless match.
|
||||
const dashed = dasherize(split[1].replace(/[\.\/]/g, "-"));
|
||||
|
||||
// Allow render 'admin/templates/xyz' too
|
||||
split[1] = split[1].replace(".templates", "").replace("/templates", "");
|
||||
|
||||
// Try slashes
|
||||
let dashed = dasherize(split[1].replace(/\./g, "/"));
|
||||
if (
|
||||
requirejs.entries[appBase + dashed] ||
|
||||
requirejs.entries[adminBase + dashed] ||
|
||||
requirejs.entries[wizardBase + dashed]
|
||||
) {
|
||||
return split[0] + ":" + dashed;
|
||||
const adminBase = `admin/${type}s/`;
|
||||
const wizardBase = `wizard/${type}s/`;
|
||||
if (
|
||||
lookupModuleBySuffix(`${type}s/${dashed}`) ||
|
||||
requirejs.entries[adminBase + dashed] ||
|
||||
requirejs.entries[adminBase + dashed.replace(/^admin[-]/, "")] ||
|
||||
requirejs.entries[
|
||||
adminBase + dashed.replace(/^admin[-]/, "").replace(/-/g, "_")
|
||||
] ||
|
||||
requirejs.entries[wizardBase + dashed] ||
|
||||
requirejs.entries[wizardBase + dashed.replace(/^wizard[-]/, "")] ||
|
||||
requirejs.entries[
|
||||
wizardBase + dashed.replace(/^wizard[-]/, "").replace(/-/g, "_")
|
||||
]
|
||||
) {
|
||||
corrected = type + ":" + dashed;
|
||||
}
|
||||
}
|
||||
|
||||
// Try with dashes instead of slashes
|
||||
dashed = dasherize(split[1].replace(/\./g, "-"));
|
||||
if (
|
||||
requirejs.entries[appBase + dashed] ||
|
||||
requirejs.entries[adminBase + dashed] ||
|
||||
requirejs.entries[wizardBase + dashed]
|
||||
) {
|
||||
return split[0] + ":" + dashed;
|
||||
if (corrected && corrected !== normalized) {
|
||||
normalized = corrected;
|
||||
}
|
||||
}
|
||||
return this._super(fullName);
|
||||
},
|
||||
|
||||
customResolve(parsedName) {
|
||||
// If we end with the name we want, use it. This allows us to define components within plugins.
|
||||
const suffix = parsedName.type + "s/" + parsedName.fullNameWithoutType,
|
||||
dashed = dasherize(suffix),
|
||||
moduleName = lookupModuleBySuffix(dashed);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
let module;
|
||||
if (moduleName) {
|
||||
module = requirejs(moduleName, null, null, true /* force sync */);
|
||||
if (module && module["default"]) {
|
||||
module = module["default"];
|
||||
chooseModuleName(moduleName, parsedName) {
|
||||
let resolved = super.chooseModuleName(moduleName, parsedName);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
const standard = parsedName.fullNameWithoutType;
|
||||
|
||||
let variants = [standard];
|
||||
|
||||
if (standard.includes("/")) {
|
||||
variants.push(parsedName.fullNameWithoutType.replace(/\//g, "-"));
|
||||
}
|
||||
|
||||
for (let name of variants) {
|
||||
// If we end with the name we want, use it. This allows us to define components within plugins.
|
||||
const suffix = parsedName.type + "s/" + name;
|
||||
resolved = lookupModuleBySuffix(dasherize(suffix));
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return module;
|
||||
},
|
||||
}
|
||||
|
||||
resolveWidget(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveAdapter(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveModel(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveView(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
resolveOther(parsedName) {
|
||||
let resolved = super.resolveOther(parsedName);
|
||||
if (!resolved) {
|
||||
let legacyParsedName = this.legacyResolver.parseName(
|
||||
`${parsedName.type}:${parsedName.fullName}`
|
||||
);
|
||||
resolved = this.legacyResolver.resolveOther(legacyParsedName);
|
||||
if (resolved) {
|
||||
deprecated(
|
||||
`Unable to resolve with new resolver, but resolved with legacy resolver: ${parsedName.fullName}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
resolveHelper(parsedName) {
|
||||
return (
|
||||
findHelper(parsedName.fullNameWithoutType) ||
|
||||
this.customResolve(parsedName) ||
|
||||
this._super(parsedName)
|
||||
);
|
||||
},
|
||||
|
||||
resolveController(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveComponent(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveService(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
resolveRawView(parsedName) {
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
return findHelper(parsedName.fullNameWithoutType);
|
||||
}
|
||||
|
||||
// If no match is found here, the resolver falls back to `resolveOther`.
|
||||
resolveRoute(parsedName) {
|
||||
if (parsedName.fullNameWithoutType === "basic") {
|
||||
return requirejs("discourse/routes/discourse", null, null, true)
|
||||
.default;
|
||||
}
|
||||
|
||||
return this.customResolve(parsedName) || this._super(parsedName);
|
||||
},
|
||||
|
||||
findLoadingTemplate(parsedName) {
|
||||
if (parsedName.fullNameWithoutType.match(/loading$/)) {
|
||||
return Ember.TEMPLATES.loading;
|
||||
}
|
||||
},
|
||||
|
||||
findConnectorTemplate(parsedName) {
|
||||
const full = parsedName.fullNameWithoutType.replace("components/", "");
|
||||
if (full.indexOf("connectors") === 0) {
|
||||
return Ember.TEMPLATES[`javascripts/${full}`];
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
resolveTemplate(parsedName) {
|
||||
return (
|
||||
@ -205,132 +187,135 @@ export function buildResolver(baseName) {
|
||||
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
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
findLoadingTemplate(parsedName) {
|
||||
if (parsedName.fullNameWithoutType.match(/loading$/)) {
|
||||
return Ember.TEMPLATES.loading;
|
||||
}
|
||||
}
|
||||
|
||||
findConnectorTemplate(parsedName) {
|
||||
if (parsedName.fullName.startsWith("template:connectors/")) {
|
||||
const connectorParsedName = this.parseName(
|
||||
parsedName.fullName
|
||||
.replace("template:connectors/", "template:")
|
||||
.replace("components/", "")
|
||||
);
|
||||
return this.findTemplate(connectorParsedName, "javascripts/");
|
||||
}
|
||||
}
|
||||
|
||||
findPluginTemplate(parsedName) {
|
||||
const pluginParsedName = this.parseName(
|
||||
parsedName.fullName.replace("template:", "template:javascripts/")
|
||||
);
|
||||
return this.findTemplate(pluginParsedName);
|
||||
},
|
||||
return this.findTemplate(parsedName, "javascripts/");
|
||||
}
|
||||
|
||||
findPluginMobileTemplate(parsedName) {
|
||||
if (_options.mobileView) {
|
||||
let pluginParsedName = this.parseName(
|
||||
parsedName.fullName.replace(
|
||||
"template:",
|
||||
"template:javascripts/mobile/"
|
||||
)
|
||||
);
|
||||
return this.findTemplate(pluginParsedName);
|
||||
return this.findTemplate(parsedName, "javascripts/mobile/");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
findMobileTemplate(parsedName) {
|
||||
if (_options.mobileView) {
|
||||
let mobileParsedName = this.parseName(
|
||||
parsedName.fullName.replace("template:", "template:mobile/")
|
||||
);
|
||||
return this.findTemplate(mobileParsedName);
|
||||
return this.findTemplate(parsedName, "mobile/");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
findTemplate(parsedName, prefix) {
|
||||
prefix = prefix || "";
|
||||
|
||||
findTemplate(parsedName) {
|
||||
const withoutType = parsedName.fullNameWithoutType,
|
||||
slashedType = withoutType.replace(/\./g, "/"),
|
||||
decamelized = decamelize(withoutType),
|
||||
dashed = decamelized.replace(/\./g, "-").replace(/\_/g, "-"),
|
||||
underscored = decamelize(withoutType).replace(/-/g, "_"),
|
||||
segments = withoutType.split("/"),
|
||||
templates = Ember.TEMPLATES;
|
||||
|
||||
return (
|
||||
this._super(parsedName) ||
|
||||
templates[slashedType] ||
|
||||
templates[withoutType] ||
|
||||
templates[withoutType.replace(/\.raw$/, "")] ||
|
||||
templates[dashed] ||
|
||||
templates[decamelized.replace(/\./, "/")] ||
|
||||
templates[decamelized.replace(/\_/, "/")] ||
|
||||
templates[`${baseName}/templates/${withoutType}`] ||
|
||||
this.findAdminTemplate(parsedName) ||
|
||||
this.findWizardTemplate(parsedName) ||
|
||||
this.findUnderscoredTemplate(parsedName)
|
||||
// Convert dots and dashes to slashes
|
||||
templates[prefix + withoutType.replace(/[\.-]/g, "/")] ||
|
||||
// Default unmodified behavior of original resolveTemplate.
|
||||
templates[prefix + withoutType] ||
|
||||
// Underscored without namespace
|
||||
templates[prefix + underscored] ||
|
||||
// Underscored with first segment as directory
|
||||
templates[prefix + underscored.replace("_", "/")] ||
|
||||
// Underscore only the last segment
|
||||
templates[
|
||||
`${prefix}${segments.slice(0, -1).join("/")}/${segments[
|
||||
segments.length - 1
|
||||
].replace(/-/g, "_")}`
|
||||
] ||
|
||||
// All dasherized
|
||||
templates[prefix + withoutType.replace(/\//g, "-")]
|
||||
);
|
||||
},
|
||||
|
||||
findUnderscoredTemplate(parsedName) {
|
||||
let decamelized = decamelize(parsedName.fullNameWithoutType);
|
||||
let underscored = decamelized.replace(/\-/g, "_");
|
||||
return Ember.TEMPLATES[underscored];
|
||||
},
|
||||
}
|
||||
|
||||
// Try to find a template within a special admin namespace, e.g. adminEmail => admin/templates/email
|
||||
// (similar to how discourse lays out templates)
|
||||
findAdminTemplate(parsedName) {
|
||||
let decamelized = decamelize(parsedName.fullNameWithoutType);
|
||||
if (decamelized.indexOf("components") === 0) {
|
||||
let comPath = `admin/templates/${decamelized}`;
|
||||
const compTemplate =
|
||||
Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath];
|
||||
if (compTemplate) {
|
||||
return compTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
if (decamelized === "javascripts/admin") {
|
||||
if (parsedName.fullNameWithoutType === "admin") {
|
||||
return Ember.TEMPLATES["admin/templates/admin"];
|
||||
}
|
||||
|
||||
if (
|
||||
decamelized.indexOf("admin") === 0 ||
|
||||
decamelized.indexOf("javascripts/admin") === 0
|
||||
) {
|
||||
decamelized = decamelized.replace(/^admin\_/, "admin/templates/");
|
||||
decamelized = decamelized.replace(/^admin\./, "admin/templates/");
|
||||
decamelized = decamelized.replace(/\./g, "_");
|
||||
let namespaced, match;
|
||||
|
||||
const dashed = decamelized.replace(/_/g, "-");
|
||||
return (
|
||||
Ember.TEMPLATES[decamelized] ||
|
||||
Ember.TEMPLATES[dashed] ||
|
||||
Ember.TEMPLATES[dashed.replace("admin-", "admin/")]
|
||||
);
|
||||
if (parsedName.fullNameWithoutType.startsWith("components/")) {
|
||||
// Look up components as-is
|
||||
} else if (/^admin[_\.-]/.test(parsedName.fullNameWithoutType)) {
|
||||
namespaced = parsedName.fullNameWithoutType.slice(6);
|
||||
} else if (
|
||||
(match = parsedName.fullNameWithoutType.match(/^admin([A-Z])(.+)$/))
|
||||
) {
|
||||
namespaced = `${match[1].toLowerCase()}${match[2]}`;
|
||||
}
|
||||
},
|
||||
|
||||
let resolved;
|
||||
|
||||
if (namespaced) {
|
||||
let adminParsedName = this.parseName(`template:${namespaced}`);
|
||||
resolved =
|
||||
// Built-in
|
||||
this.findTemplate(adminParsedName, "admin/templates/") ||
|
||||
// Plugin
|
||||
this.findTemplate(adminParsedName, "javascripts/admin/");
|
||||
}
|
||||
|
||||
resolved ??=
|
||||
// Built-in
|
||||
this.findTemplate(parsedName, "admin/templates/") ||
|
||||
// Plugin
|
||||
this.findTemplate(parsedName, "javascripts/admin/");
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
findWizardTemplate(parsedName) {
|
||||
let decamelized = decamelize(parsedName.fullNameWithoutType);
|
||||
if (decamelized.startsWith("components")) {
|
||||
let comPath = `wizard/templates/${decamelized}`;
|
||||
const compTemplate =
|
||||
Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath];
|
||||
if (compTemplate) {
|
||||
return compTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
if (decamelized === "javascripts/wizard") {
|
||||
if (parsedName.fullNameWithoutType === "wizard") {
|
||||
return Ember.TEMPLATES["wizard/templates/wizard"];
|
||||
}
|
||||
|
||||
if (
|
||||
decamelized.startsWith("wizard") ||
|
||||
decamelized.startsWith("javascripts/wizard")
|
||||
) {
|
||||
decamelized = decamelized.replace(/^wizard\_/, "wizard/templates/");
|
||||
decamelized = decamelized.replace(/^wizard\./, "wizard/templates/");
|
||||
decamelized = decamelized.replace(/\./g, "_");
|
||||
let namespaced;
|
||||
|
||||
const dashed = decamelized.replace(/_/g, "-");
|
||||
return (
|
||||
Ember.TEMPLATES[decamelized] ||
|
||||
Ember.TEMPLATES[dashed] ||
|
||||
Ember.TEMPLATES[dashed.replace("wizard-", "wizard/")]
|
||||
);
|
||||
if (parsedName.fullNameWithoutType.startsWith("components/")) {
|
||||
// Look up components as-is
|
||||
namespaced = parsedName.fullNameWithoutType;
|
||||
} else if (/^wizard[_\.-]/.test(parsedName.fullNameWithoutType)) {
|
||||
// This may only get hit for the loading routes and may be removable.
|
||||
namespaced = parsedName.fullNameWithoutType.slice(7);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (namespaced) {
|
||||
let adminParsedName = this.parseName(
|
||||
`template:wizard/templates/${namespaced}`
|
||||
);
|
||||
return this.findTemplate(adminParsedName);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
"ember-auto-import": "^2.4.2",
|
||||
"ember-cli-babel": "^7.23.1",
|
||||
"ember-cli-htmlbars": "^6.0.1",
|
||||
"ember-resolver": "^8.0.3",
|
||||
"handlebars": "^4.7.0",
|
||||
"truth-helpers": "^1.0.0",
|
||||
"webpack": "^5.73.0"
|
||||
|
@ -6,6 +6,8 @@ const _pluginCallbacks = [];
|
||||
let _unhandledThemeErrors = [];
|
||||
|
||||
const Discourse = Application.extend({
|
||||
modulePrefix: "discourse",
|
||||
|
||||
rootElement: "#main",
|
||||
|
||||
customEvents: {
|
||||
|
@ -4,73 +4,120 @@ import TagShowRoute from "discourse/routes/tag-show";
|
||||
import User from "discourse/models/user";
|
||||
import buildCategoryRoute from "discourse/routes/build-category-route";
|
||||
import buildTopicRoute from "discourse/routes/build-topic-route";
|
||||
import { capitalize } from "@ember/string";
|
||||
|
||||
export default {
|
||||
after: "inject-discourse-objects",
|
||||
name: "dynamic-route-builders",
|
||||
|
||||
initialize(registry, app) {
|
||||
app.DiscoveryCategoryController = DiscoverySortableController.extend();
|
||||
app.DiscoveryCategoryNoneController = DiscoverySortableController.extend();
|
||||
app.DiscoveryCategoryAllController = DiscoverySortableController.extend();
|
||||
app.register(
|
||||
"controller:discovery.category",
|
||||
DiscoverySortableController.extend()
|
||||
);
|
||||
app.register(
|
||||
"controller:discovery.category-none",
|
||||
DiscoverySortableController.extend()
|
||||
);
|
||||
app.register(
|
||||
"controller:discovery.category-all",
|
||||
DiscoverySortableController.extend()
|
||||
);
|
||||
|
||||
app.DiscoveryCategoryRoute = buildCategoryRoute("default");
|
||||
app.DiscoveryCategoryNoneRoute = buildCategoryRoute("default", {
|
||||
no_subcategories: true,
|
||||
});
|
||||
app.DiscoveryCategoryAllRoute = buildCategoryRoute("default", {
|
||||
no_subcategories: false,
|
||||
});
|
||||
app.register("route:discovery.category", buildCategoryRoute("default"));
|
||||
app.register(
|
||||
"route:discovery.category-none",
|
||||
buildCategoryRoute("default", {
|
||||
no_subcategories: true,
|
||||
})
|
||||
);
|
||||
app.register(
|
||||
"route:discovery.category-all",
|
||||
buildCategoryRoute("default", {
|
||||
no_subcategories: false,
|
||||
})
|
||||
);
|
||||
|
||||
const site = Site.current();
|
||||
site.get("filters").forEach((filter) => {
|
||||
const filterCapitalized = capitalize(filter);
|
||||
app[`Discovery${filterCapitalized}Controller`] =
|
||||
DiscoverySortableController.extend();
|
||||
app[`Discovery${filterCapitalized}CategoryController`] =
|
||||
DiscoverySortableController.extend();
|
||||
app[`Discovery${filterCapitalized}CategoryNoneController`] =
|
||||
DiscoverySortableController.extend();
|
||||
const filterDasherized = filter.dasherize();
|
||||
app.register(
|
||||
`controller:discovery.${filterDasherized}`,
|
||||
DiscoverySortableController.extend()
|
||||
);
|
||||
app.register(
|
||||
`controller:discovery.${filterDasherized}-category`,
|
||||
DiscoverySortableController.extend()
|
||||
);
|
||||
app.register(
|
||||
`controller:discovery.${filterDasherized}-category-none`,
|
||||
DiscoverySortableController.extend()
|
||||
);
|
||||
|
||||
if (filter === "top") {
|
||||
app.DiscoveryTopRoute = buildTopicRoute("top", {
|
||||
actions: {
|
||||
willTransition() {
|
||||
User.currentProp("should_be_redirected_to_top", false);
|
||||
User.currentProp("redirected_to_top.reason", null);
|
||||
return this._super(...arguments);
|
||||
app.register(
|
||||
"route:discovery.top",
|
||||
buildTopicRoute("top", {
|
||||
actions: {
|
||||
willTransition() {
|
||||
User.currentProp("should_be_redirected_to_top", false);
|
||||
User.currentProp("redirected_to_top.reason", null);
|
||||
return this._super(...arguments);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
} else {
|
||||
app[`Discovery${filterCapitalized}Route`] = buildTopicRoute(filter);
|
||||
app.register(
|
||||
`route:discovery.${filterDasherized}`,
|
||||
buildTopicRoute(filter)
|
||||
);
|
||||
}
|
||||
|
||||
app[`Discovery${filterCapitalized}CategoryRoute`] =
|
||||
buildCategoryRoute(filter);
|
||||
app[`Discovery${filterCapitalized}CategoryNoneRoute`] =
|
||||
buildCategoryRoute(filter, { no_subcategories: true });
|
||||
app.register(
|
||||
`route:discovery.${filterDasherized}-category`,
|
||||
buildCategoryRoute(filter)
|
||||
);
|
||||
app.register(
|
||||
`route:discovery.${filterDasherized}-category-none`,
|
||||
buildCategoryRoute(filter, { no_subcategories: true })
|
||||
);
|
||||
});
|
||||
|
||||
app["TagsShowCategoryRoute"] = TagShowRoute.extend();
|
||||
app["TagsShowCategoryNoneRoute"] = TagShowRoute.extend({
|
||||
noSubcategories: true,
|
||||
});
|
||||
app["TagsShowCategoryAllRoute"] = TagShowRoute.extend({
|
||||
noSubcategories: false,
|
||||
});
|
||||
app.register("route:tags.show-category", TagShowRoute.extend());
|
||||
app.register(
|
||||
"route:tags.show-category-none",
|
||||
TagShowRoute.extend({
|
||||
noSubcategories: true,
|
||||
})
|
||||
);
|
||||
app.register(
|
||||
"route:tags.show-category-all",
|
||||
TagShowRoute.extend({
|
||||
noSubcategories: false,
|
||||
})
|
||||
);
|
||||
|
||||
site.get("filters").forEach(function (filter) {
|
||||
app["TagShow" + capitalize(filter) + "Route"] = TagShowRoute.extend({
|
||||
navMode: filter,
|
||||
});
|
||||
app["TagsShowCategory" + capitalize(filter) + "Route"] =
|
||||
TagShowRoute.extend({ navMode: filter });
|
||||
app["TagsShowCategoryNone" + capitalize(filter) + "Route"] =
|
||||
TagShowRoute.extend({ navMode: filter, noSubcategories: true });
|
||||
app["TagsShowCategoryAll" + capitalize(filter) + "Route"] =
|
||||
TagShowRoute.extend({ navMode: filter, noSubcategories: false });
|
||||
const filterDasherized = filter.dasherize();
|
||||
|
||||
app.register(
|
||||
`route:tag.show-${filterDasherized}`,
|
||||
TagShowRoute.extend({
|
||||
navMode: filter,
|
||||
})
|
||||
);
|
||||
app.register(
|
||||
`route:tag.show-${filterDasherized}-category`,
|
||||
TagShowRoute.extend({ navMode: filter })
|
||||
);
|
||||
app.register(
|
||||
`route:tag.show-${filterDasherized}-category-none`,
|
||||
TagShowRoute.extend({ navMode: filter, noSubcategories: true })
|
||||
);
|
||||
app.register(
|
||||
`route:tag.show-${filterDasherized}-category-all`,
|
||||
TagShowRoute.extend({ navMode: filter, noSubcategories: false })
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -12,7 +12,9 @@ const CatAdapter = RestAdapter.extend({
|
||||
});
|
||||
|
||||
export default function (customLookup = () => {}) {
|
||||
const resolver = buildResolver("discourse").create();
|
||||
const resolver = buildResolver("discourse").create({
|
||||
namespace: { modulePrefix: "discourse" },
|
||||
});
|
||||
|
||||
// Normally this would happen in inject-discourse-objects.
|
||||
// However, `create-store` is used by unit tests which do not init the application.
|
||||
@ -54,9 +56,10 @@ export default function (customLookup = () => {}) {
|
||||
|
||||
lookupFactory(type) {
|
||||
const split = type.split(":");
|
||||
return resolver.customResolve({
|
||||
return resolver.resolveOther({
|
||||
type: split[0],
|
||||
fullNameWithoutType: split[1],
|
||||
root: {},
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -26,7 +26,9 @@ module("Unit | Ember | resolver", function (hooks) {
|
||||
// eslint-disable-next-line no-undef
|
||||
Ember.TEMPLATES = {};
|
||||
|
||||
resolver = DiscourseResolver.create();
|
||||
resolver = DiscourseResolver.create({
|
||||
namespace: { modulePrefix: "discourse" },
|
||||
});
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
@ -37,38 +39,60 @@ module("Unit | Ember | resolver", function (hooks) {
|
||||
test("finds templates in top level dir", function (assert) {
|
||||
setTemplates(["foobar", "fooBar", "foo_bar", "foo.bar"]);
|
||||
|
||||
// Default unmodified behavior
|
||||
lookupTemplate(assert, "template:foobar", "foobar", "by lowcased name");
|
||||
|
||||
// Default unmodified behavior
|
||||
lookupTemplate(assert, "template:fooBar", "fooBar", "by camel cased name");
|
||||
|
||||
// Default unmodified behavior
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo_bar",
|
||||
"foo_bar",
|
||||
"by underscored name"
|
||||
);
|
||||
|
||||
// Default unmodified behavior
|
||||
lookupTemplate(assert, "template:foo.bar", "foo.bar", "by dotted name");
|
||||
});
|
||||
|
||||
test("finds templates in first-level subdir", function (assert) {
|
||||
setTemplates(["foo/bar_baz"]);
|
||||
|
||||
// Default unmodified behavior
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo/bar_baz",
|
||||
"foo/bar_baz",
|
||||
"with subdir defined by slash"
|
||||
);
|
||||
|
||||
// Convert dots to slash
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo.bar_baz",
|
||||
"foo/bar_baz",
|
||||
"with subdir defined by dot"
|
||||
);
|
||||
|
||||
// Convert dashes to slash
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo-bar_baz",
|
||||
"foo/bar_baz",
|
||||
"with subdir defined by dash"
|
||||
);
|
||||
|
||||
// Underscored with first segment as directory
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:fooBarBaz",
|
||||
"foo/bar_baz",
|
||||
"with subdir defined by first camel case and the rest of camel cases converted to underscores"
|
||||
);
|
||||
|
||||
// Already underscored with first segment as directory
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo_bar_baz",
|
||||
@ -78,49 +102,77 @@ module("Unit | Ember | resolver", function (hooks) {
|
||||
});
|
||||
|
||||
test("resolves precedence between overlapping top level dir and first level subdir templates", function (assert) {
|
||||
setTemplates(["fooBar", "foo_bar", "foo.bar", "foo/bar"]);
|
||||
setTemplates(["fooBar", "foo_bar", "foo.bar", "foo/bar", "baz/qux"]);
|
||||
|
||||
// Directories are prioritized when dotted
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo.bar",
|
||||
"foo/bar",
|
||||
"preferring first level subdir for dotted name"
|
||||
);
|
||||
|
||||
// Directories are prioritized when dashed
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo-bar",
|
||||
"foo/bar",
|
||||
"preferring first level subdir for dotted name"
|
||||
);
|
||||
|
||||
// Default unmodified before directories, except when dotted
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:fooBar",
|
||||
"fooBar",
|
||||
"preferring top level dir for camel cased name"
|
||||
);
|
||||
|
||||
// Default unmodified before directories, except when dotted
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo_bar",
|
||||
"foo_bar",
|
||||
"preferring top level dir for underscored name"
|
||||
);
|
||||
|
||||
// Use directory version if top-level isn't found
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:baz-qux",
|
||||
"baz/qux",
|
||||
"fallback subdir for dashed name"
|
||||
);
|
||||
});
|
||||
|
||||
test("finds templates in subdir deeper than one level", function (assert) {
|
||||
setTemplates(["foo/bar/baz/qux"]);
|
||||
|
||||
// Default unmodified
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo/bar/baz/qux",
|
||||
"foo/bar/baz/qux",
|
||||
"for subdirs defined by slashes"
|
||||
);
|
||||
|
||||
// Converts dotted to slashed
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo.bar.baz.qux",
|
||||
"foo/bar/baz/qux",
|
||||
"for subdirs defined by dots"
|
||||
);
|
||||
|
||||
// Converts first camelized segment to slashed
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo/bar/bazQux",
|
||||
"foo/bar/baz/qux",
|
||||
"for subdirs defined by slashes plus one camel case"
|
||||
);
|
||||
|
||||
// Converts first underscore to slashed
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo/bar/baz_qux",
|
||||
@ -128,24 +180,31 @@ module("Unit | Ember | resolver", function (hooks) {
|
||||
"for subdirs defined by slashes plus one underscore"
|
||||
);
|
||||
|
||||
// Only converts first camelized segment to slashed so this isn't matched
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:fooBarBazQux",
|
||||
undefined,
|
||||
"but not for subdirs defined by more than one camel case"
|
||||
);
|
||||
|
||||
// Only converts first underscored segment to slashed so this isn't matched
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo_bar_baz_qux",
|
||||
undefined,
|
||||
"but not for subdirs defined by more than one underscore"
|
||||
);
|
||||
|
||||
// Only converts dots to slashes OR first camelized segment. This has both so isn't matched.
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo.bar.bazQux",
|
||||
undefined,
|
||||
"but not for subdirs defined by dots plus one camel case"
|
||||
);
|
||||
|
||||
// Only converts dots to slashes OR first underscored segment. This has both so isn't matched.
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo.bar.baz_qux",
|
||||
@ -159,18 +218,23 @@ module("Unit | Ember | resolver", function (hooks) {
|
||||
|
||||
setResolverOption("mobileView", true);
|
||||
|
||||
// Default with mobile/ added
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo",
|
||||
"mobile/foo",
|
||||
"finding mobile version even if normal one is not present"
|
||||
);
|
||||
|
||||
// Default with mobile preferred
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:bar",
|
||||
"mobile/bar",
|
||||
"preferring mobile version when both mobile and normal versions are present"
|
||||
);
|
||||
|
||||
// Default when mobile not present
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:baz",
|
||||
@ -182,18 +246,23 @@ module("Unit | Ember | resolver", function (hooks) {
|
||||
test("resolves plugin templates to 'javascripts/' namespace", function (assert) {
|
||||
setTemplates(["javascripts/foo", "bar", "javascripts/bar", "baz"]);
|
||||
|
||||
// Default with javascripts/ added
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo",
|
||||
"javascripts/foo",
|
||||
"finding plugin version even if normal one is not present"
|
||||
);
|
||||
|
||||
// Default with javascripts/ added, takes precedence
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:bar",
|
||||
"javascripts/bar",
|
||||
"preferring plugin version when both versions are present"
|
||||
);
|
||||
|
||||
// Default when javascripts version not present
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:baz",
|
||||
@ -202,27 +271,71 @@ module("Unit | Ember | resolver", function (hooks) {
|
||||
);
|
||||
});
|
||||
|
||||
test("resolves templates with 'admin' prefix to 'admin/templates/' namespace", function (assert) {
|
||||
test("resolves plugin mobile templates to 'javascripts/mobile/' namespace", function (assert) {
|
||||
setTemplates([
|
||||
"javascripts/mobile/foo",
|
||||
"javascripts/mobile/bar",
|
||||
"javascripts/bar",
|
||||
"javascripts/mobile/baz",
|
||||
"mobile/baz",
|
||||
]);
|
||||
|
||||
setResolverOption("mobileView", true);
|
||||
|
||||
// Default with javascripts/mobile/ added
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo",
|
||||
"javascripts/mobile/foo",
|
||||
"finding plugin version even if normal one is not present"
|
||||
);
|
||||
|
||||
// Default with javascripts/mobile added, takes precedence over non-mobile
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:bar",
|
||||
"javascripts/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
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:baz",
|
||||
"javascripts/mobile/baz",
|
||||
"preferring plugin mobile version over non-plugin mobile version"
|
||||
);
|
||||
});
|
||||
|
||||
test("resolves templates with 'admin' prefix", function (assert) {
|
||||
setTemplates([
|
||||
"admin/templates/foo",
|
||||
"adminBar",
|
||||
"admin_bar",
|
||||
"admin.bar",
|
||||
"admin/templates/bar",
|
||||
"admin/templates/dashboard_general",
|
||||
"admin-baz-qux",
|
||||
"javascripts/admin/plugin-template",
|
||||
]);
|
||||
|
||||
// Switches prefix to admin/templates when camelized
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:adminFoo",
|
||||
"admin/templates/foo",
|
||||
"when prefix is separated by camel case"
|
||||
);
|
||||
|
||||
// Switches prefix to admin/templates when underscored
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:admin_foo",
|
||||
"admin/templates/foo",
|
||||
"when prefix is separated by underscore"
|
||||
);
|
||||
|
||||
// Switches prefix to admin/templates when dotted
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:admin.foo",
|
||||
@ -230,30 +343,165 @@ module("Unit | Ember | resolver", function (hooks) {
|
||||
"when prefix is separated by dot"
|
||||
);
|
||||
|
||||
// Doesn't match unseparated prefix
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:adminfoo",
|
||||
undefined,
|
||||
"but not when prefix is not separated in any way"
|
||||
);
|
||||
|
||||
// Prioritized the default match when camelized
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:adminBar",
|
||||
"adminBar",
|
||||
"but not when template with the exact camel cased name exists"
|
||||
);
|
||||
|
||||
// Prioritized the default match when underscored
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:admin_bar",
|
||||
"admin_bar",
|
||||
"but not when template with the exact underscored name exists"
|
||||
);
|
||||
|
||||
// Prioritized the default match when dotted
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:admin.bar",
|
||||
"admin.bar",
|
||||
"but not when template with the exact dotted name exists"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:admin-dashboard-general",
|
||||
"admin/templates/dashboard_general",
|
||||
"finds namespaced and underscored version"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:admin-baz/qux",
|
||||
"admin-baz-qux",
|
||||
"also tries dasherized"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:admin-plugin/template",
|
||||
"javascripts/admin/plugin-template",
|
||||
"looks up templates in plugins"
|
||||
);
|
||||
});
|
||||
|
||||
test("resolves component templates with 'admin' prefix to 'admin/templates/' namespace", function (assert) {
|
||||
setTemplates([
|
||||
"admin/templates/components/foo",
|
||||
"components/bar",
|
||||
"admin/templates/components/bar",
|
||||
]);
|
||||
|
||||
// Looks for components in admin/templates
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:components/foo",
|
||||
"admin/templates/components/foo",
|
||||
"uses admin template component when no standard match"
|
||||
);
|
||||
|
||||
// Prioritized non-admin component
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:components/bar",
|
||||
"components/bar",
|
||||
"uses standard match when both exist"
|
||||
);
|
||||
});
|
||||
|
||||
// We can probably remove this in the future since this behavior seems pretty
|
||||
// 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"]);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:fooloading",
|
||||
"fooloading",
|
||||
"exact match without separator"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo/loading",
|
||||
"foo/loading",
|
||||
"exact match with slash"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:foo_loading",
|
||||
"foo_loading",
|
||||
"exact match underscore"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:barloading",
|
||||
"loading",
|
||||
"fallback without separator"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:bar/loading",
|
||||
"loading",
|
||||
"fallback with slash"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:bar.loading",
|
||||
"loading",
|
||||
"fallback with dot"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:bar_loading",
|
||||
"loading",
|
||||
"fallback underscore"
|
||||
);
|
||||
|
||||
// TODO: Maybe test precedence
|
||||
});
|
||||
|
||||
test("resolves connector templates", function (assert) {
|
||||
setTemplates(["javascripts/foo", "javascripts/connectors/foo-bar/baz_qux"]);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:connectors/foo",
|
||||
"javascripts/foo",
|
||||
"looks up in javascripts/ namespace"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:connectors/components/foo",
|
||||
"javascripts/foo",
|
||||
"removes components segment"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:connectors/foo-bar/baz-qux",
|
||||
"javascripts/connectors/foo-bar/baz_qux",
|
||||
"underscores last segment"
|
||||
);
|
||||
});
|
||||
|
||||
test("returns 'not_found' template when template name cannot be resolved", function (assert) {
|
||||
@ -261,4 +509,94 @@ module("Unit | Ember | resolver", function (hooks) {
|
||||
|
||||
lookupTemplate(assert, "template:foo/bar/baz", "not_found", "");
|
||||
});
|
||||
|
||||
test("resolves templates with 'wizard' prefix", function (assert) {
|
||||
setTemplates([
|
||||
"wizard/templates/foo",
|
||||
"wizard_bar",
|
||||
"wizard.bar",
|
||||
"wizard/templates/bar",
|
||||
"wizard/templates/dashboard_general",
|
||||
"wizard-baz-qux",
|
||||
"javascripts/wizard/plugin-template",
|
||||
]);
|
||||
|
||||
// Switches prefix to wizard/templates when underscored
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:wizard_foo",
|
||||
"wizard/templates/foo",
|
||||
"when prefix is separated by underscore"
|
||||
);
|
||||
|
||||
// Switches prefix to wizard/templates when dotted
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:wizard.foo",
|
||||
"wizard/templates/foo",
|
||||
"when prefix is separated by dot"
|
||||
);
|
||||
|
||||
// Doesn't match unseparated prefix
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:wizardfoo",
|
||||
undefined,
|
||||
"but not when prefix is not separated in any way"
|
||||
);
|
||||
|
||||
// Prioritized the default match when underscored
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:wizard_bar",
|
||||
"wizard_bar",
|
||||
"but not when template with the exact underscored name exists"
|
||||
);
|
||||
|
||||
// Prioritized the default match when dotted
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:wizard.bar",
|
||||
"wizard.bar",
|
||||
"but not when template with the exact dotted name exists"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:wizard-dashboard-general",
|
||||
"wizard/templates/dashboard_general",
|
||||
"finds namespaced and underscored version"
|
||||
);
|
||||
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:wizard-baz/qux",
|
||||
"wizard-baz-qux",
|
||||
"also tries dasherized"
|
||||
);
|
||||
});
|
||||
|
||||
test("resolves component templates with 'wizard' prefix to 'wizard/templates/' namespace", function (assert) {
|
||||
setTemplates([
|
||||
"wizard/templates/components/foo",
|
||||
"components/bar",
|
||||
"wizard/templates/components/bar",
|
||||
]);
|
||||
|
||||
// Looks for components in wizard/templates
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:components/foo",
|
||||
"wizard/templates/components/foo",
|
||||
"uses wizard template component when no standard match"
|
||||
);
|
||||
|
||||
// Prioritized non-wizard component
|
||||
lookupTemplate(
|
||||
assert,
|
||||
"template:components/bar",
|
||||
"components/bar",
|
||||
"uses standard match when both exist"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -4963,6 +4963,18 @@ ember-resolver@^7.0.0:
|
||||
ember-cli-version-checker "^3.1.3"
|
||||
resolve "^1.14.0"
|
||||
|
||||
ember-resolver@^8.0.3:
|
||||
version "8.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ember-resolver/-/ember-resolver-8.0.3.tgz#40f243aa58281bf195c695fe84a6b291e204690a"
|
||||
integrity sha512-fA53fxfG821BRqNiB9mQDuzZpzSRcSAYZTYBlRQOHsJwoYdjyE7idz4YcytbSsa409G5J2kP6B+PiKOBh0odlw==
|
||||
dependencies:
|
||||
babel-plugin-debug-macros "^0.3.4"
|
||||
broccoli-funnel "^3.0.8"
|
||||
broccoli-merge-trees "^4.2.0"
|
||||
ember-cli-babel "^7.26.6"
|
||||
ember-cli-version-checker "^5.1.2"
|
||||
resolve "^1.20.0"
|
||||
|
||||
ember-rfc176-data@^0.3.17:
|
||||
version "0.3.17"
|
||||
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.17.tgz#d4fc6c33abd6ef7b3440c107a28e04417b49860a"
|
||||
|
Loading…
x
Reference in New Issue
Block a user