DEV: Migrate discourse core to Ember initializers (#22095)
https://meta.discourse.org/t/updating-our-initializer-naming-patterns/241919 For historical reasons, Discourse has different initializers conventions than standard Ember: ``` | Ember | Discourse | | | initializers | pre-initializers | runs once per app load | | instance-initializers | (api-)initializers | runs once per app boot | ``` In addition, the arguments to the initialize function is different – Ember initializers get either the `Application` or `ApplicationInstance` as the only argument, but the "Discourse style" gets an extra container argument preceding that. This is confusing, but it also causes problems with Ember addons, which expects the standard naming and argument conventions: 1. Typically, V1 addons will define their (app, instance) initializers in the `addon/(instance-)initializers/*`, which appears as `ember-some-addon-package-name/(instance-)initializers/*` in the require registry. 2. Just having those modules defined isn't supposed to do anything, so typically they also re-export them in `app/(instance-)initializers/*`, which gets merged into `discourse/(instance-)initializers/*` in the require registry. 3. The `ember-cli-load-initializers` package supplies a function called `loadInitializers`, which typically gets called in `app.js` to load the initializers according to the conventions above. Since we don't follow the same conventions, we can't use this function and instead have custom code in `app.js`, loosely based on official version but attempts to account for the different conventions. The custom code that loads initializers is written with Discourse core and plug-ins/themes in mind, but does not take into account the fact that addons can also bring initializers, which causes the following problems: * It does not check for the `discourse/` module prefix, so initializers in the `addon/` folders (point 1 above) get picked up as well. This means the initializer code is probably registered twice (once from the `addon/` folder, once from the `app/` re-export). This either causes a dev mode assertion (if they have the same name) or causes the code to run twice (if they have different names somehow). * In modern Ember blueprints, it is customary to omit the `"name"` of the initializer since `ember-cli-load-initializers` can infer it from the module name. Our custom code does not do this and causes a dev mode assertion instead. * It runs what then addon intends to be application initializers as instance initializers due to the naming difference. There is at least one known case of this where the `ember-export-application-global` application initialize is currently incorrectly registered as an instance initializer. (It happens to not use the `/addon` folder convention and explicitly names the initializer, so it does not trigger the previous error scenarios.) * It runs the initializers with the wrong arguments. If all the addon initializer does is lookup stuff from the container, it happens to work, otherwise... ??? * It does not check for the `/instance-initializers/` module path so any instance initializers introduced by addons are silently ignored. These issues were discovered when trying to install an addon that brings an application initializer in #22023. To resolve these issues, this commit: * Migrates Discourse core to use the standard Ember conventions – both in the naming and the arguments of the initialize function * Updates the custom code for loading initializers: * For Discourse core, it essentially does the same thing as `ember-cli-load-initializers` * For plugins and themes, it preserves the existing Discourse conventions and semantics (to be revisited at a later time) This ensures that going forward, Ember addons will function correctly.
This commit is contained in:
parent
79a260a6bb
commit
fa509224f0
|
@ -1,5 +1,6 @@
|
|||
import "./global-compat";
|
||||
|
||||
import require from "require";
|
||||
import Application from "@ember/application";
|
||||
import { buildResolver } from "discourse-common/resolver";
|
||||
import { isTesting } from "discourse-common/config/environment";
|
||||
|
@ -19,40 +20,6 @@ const Discourse = Application.extend({
|
|||
|
||||
Resolver: buildResolver("discourse"),
|
||||
|
||||
_prepareInitializer(moduleName) {
|
||||
const themeId = moduleThemeId(moduleName);
|
||||
let module = null;
|
||||
|
||||
try {
|
||||
module = requirejs(moduleName, null, null, true);
|
||||
|
||||
if (!module) {
|
||||
throw new Error(moduleName + " must export an initializer.");
|
||||
}
|
||||
} catch (error) {
|
||||
if (!themeId || isTesting()) {
|
||||
throw error;
|
||||
}
|
||||
fireThemeErrorEvent({ themeId, error });
|
||||
return;
|
||||
}
|
||||
|
||||
const init = module.default;
|
||||
const oldInitialize = init.initialize;
|
||||
init.initialize = (app) => {
|
||||
try {
|
||||
return oldInitialize.call(init, app.__container__, app);
|
||||
} catch (error) {
|
||||
if (!themeId || isTesting()) {
|
||||
throw error;
|
||||
}
|
||||
fireThemeErrorEvent({ themeId, error });
|
||||
}
|
||||
};
|
||||
|
||||
return init;
|
||||
},
|
||||
|
||||
// Start up the Discourse application by running all the initializers we've defined.
|
||||
start() {
|
||||
document.querySelector("noscript")?.remove();
|
||||
|
@ -66,30 +33,7 @@ const Discourse = Application.extend({
|
|||
Error.stackTraceLimit = Infinity;
|
||||
}
|
||||
|
||||
Object.keys(requirejs._eak_seen).forEach((key) => {
|
||||
if (/\/pre\-initializers\//.test(key)) {
|
||||
const initializer = this._prepareInitializer(key);
|
||||
if (initializer) {
|
||||
this.initializer(initializer);
|
||||
}
|
||||
} else if (/\/(api\-)?initializers\//.test(key)) {
|
||||
const initializer = this._prepareInitializer(key);
|
||||
if (initializer) {
|
||||
this.instanceInitializer(initializer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Plugins that are registered via `<script>` tags.
|
||||
const withPluginApi = requirejs("discourse/lib/plugin-api").withPluginApi;
|
||||
let initCount = 0;
|
||||
_pluginCallbacks.forEach((cb) => {
|
||||
this.instanceInitializer({
|
||||
name: `_discourse_plugin_${++initCount}`,
|
||||
after: "inject-objects",
|
||||
initialize: () => withPluginApi(cb.version, cb.code),
|
||||
});
|
||||
});
|
||||
loadInitializers(this);
|
||||
},
|
||||
|
||||
_registerPluginCode(version, code) {
|
||||
|
@ -129,4 +73,136 @@ export function getAndClearUnhandledThemeErrors() {
|
|||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic for loading initializers. Similar to ember-cli-load-initializers, but
|
||||
* has some discourse-specific logic to handle loading initializers from
|
||||
* plugins and themes.
|
||||
*/
|
||||
function loadInitializers(app) {
|
||||
let initializers = [];
|
||||
let instanceInitializers = [];
|
||||
|
||||
let discourseInitializers = [];
|
||||
let discourseInstanceInitializers = [];
|
||||
|
||||
for (let moduleName of Object.keys(requirejs._eak_seen)) {
|
||||
if (moduleName.startsWith("discourse/") && !moduleName.endsWith("-test")) {
|
||||
// In discourse core, initializers follow standard Ember conventions
|
||||
if (moduleName.startsWith("discourse/initializers/")) {
|
||||
initializers.push(moduleName);
|
||||
} else if (moduleName.startsWith("discourse/instance-initializers/")) {
|
||||
instanceInitializers.push(moduleName);
|
||||
} else {
|
||||
// https://meta.discourse.org/t/updating-our-initializer-naming-patterns/241919
|
||||
//
|
||||
// For historical reasons, the naming conventions in plugins and themes
|
||||
// differs from Ember:
|
||||
//
|
||||
// | Ember | Discourse | |
|
||||
// | initializers | pre-initializers | runs once per app load |
|
||||
// | instance-initializers | (api-)initializers | runs once per app boot |
|
||||
//
|
||||
// In addition, the arguments to the initialize function is different –
|
||||
// Ember initializers get either the `Application` or `ApplicationInstance`
|
||||
// as the only argument, but the "discourse style" gets an extra container
|
||||
// argument preceding that.
|
||||
|
||||
const themeId = moduleThemeId(moduleName);
|
||||
|
||||
if (
|
||||
themeId !== undefined ||
|
||||
moduleName.startsWith("discourse/plugins/")
|
||||
) {
|
||||
if (moduleName.includes("/pre-initializers/")) {
|
||||
discourseInitializers.push([moduleName, themeId]);
|
||||
} else if (
|
||||
moduleName.includes("/initializers/") ||
|
||||
moduleName.includes("/api-initializers/")
|
||||
) {
|
||||
discourseInstanceInitializers.push([moduleName, themeId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let moduleName of initializers) {
|
||||
app.initializer(resolveInitializer(moduleName));
|
||||
}
|
||||
|
||||
for (let moduleName of instanceInitializers) {
|
||||
app.instanceInitializer(resolveInitializer(moduleName));
|
||||
}
|
||||
|
||||
for (let [moduleName, themeId] of discourseInitializers) {
|
||||
app.initializer(resolveDiscourseInitializer(moduleName, themeId));
|
||||
}
|
||||
|
||||
for (let [moduleName, themeId] of discourseInstanceInitializers) {
|
||||
app.instanceInitializer(resolveDiscourseInitializer(moduleName, themeId));
|
||||
}
|
||||
|
||||
// Plugins that are registered via `<script>` tags.
|
||||
const { withPluginApi } = require("discourse/lib/plugin-api");
|
||||
|
||||
for (let [i, callback] of _pluginCallbacks.entries()) {
|
||||
app.instanceInitializer({
|
||||
name: `_discourse_plugin_${i}`,
|
||||
after: "inject-objects",
|
||||
initialize: () => withPluginApi(callback.version, callback.code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resolveInitializer(moduleName) {
|
||||
const module = require(moduleName, null, null, true);
|
||||
|
||||
if (!module) {
|
||||
throw new Error(moduleName + " must export an initializer.");
|
||||
}
|
||||
|
||||
const initializer = module["default"];
|
||||
|
||||
if (!initializer) {
|
||||
throw new Error(moduleName + " must have a default export");
|
||||
}
|
||||
|
||||
if (!initializer.name) {
|
||||
initializer.name = moduleName.slice(moduleName.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
return initializer;
|
||||
}
|
||||
|
||||
function resolveDiscourseInitializer(moduleName, themeId) {
|
||||
let initializer;
|
||||
|
||||
try {
|
||||
initializer = resolveInitializer(moduleName);
|
||||
} catch (error) {
|
||||
if (!themeId || isTesting()) {
|
||||
throw error;
|
||||
} else {
|
||||
fireThemeErrorEvent({ themeId, error });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const oldInitialize = initializer.initialize;
|
||||
|
||||
initializer.initialize = (app) => {
|
||||
try {
|
||||
return oldInitialize.call(initializer, app.__container__, app);
|
||||
} catch (error) {
|
||||
if (!themeId || isTesting()) {
|
||||
throw error;
|
||||
} else {
|
||||
fireThemeErrorEvent({ themeId, error });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return initializer;
|
||||
}
|
||||
|
||||
export default Discourse;
|
||||
|
|
|
@ -16,16 +16,14 @@ import runloop from "@ember/runloop";
|
|||
import { DEBUG } from "@glimmer/env";
|
||||
|
||||
export default {
|
||||
name: "discourse-bootstrap",
|
||||
|
||||
// The very first initializer to run
|
||||
initialize(container) {
|
||||
initialize(app) {
|
||||
if (DEBUG) {
|
||||
runloop._backburner.ASYNC_STACKS = true;
|
||||
}
|
||||
|
||||
setURLContainer(container);
|
||||
setDefaultOwner(container);
|
||||
setURLContainer(app.__container__);
|
||||
setDefaultOwner(app.__container__);
|
||||
|
||||
// Our test environment has its own bootstrap code
|
||||
if (isTesting()) {
|
|
@ -8,9 +8,8 @@ import { dasherize } from "@ember/string";
|
|||
|
||||
export default {
|
||||
after: "inject-discourse-objects",
|
||||
name: "dynamic-route-builders",
|
||||
|
||||
initialize(_container, app) {
|
||||
initialize(app) {
|
||||
app.register(
|
||||
"controller:discovery.category",
|
||||
DiscoverySortableController.extend()
|
|
@ -9,11 +9,10 @@ import User from "discourse/models/user";
|
|||
import { registerDiscourseImplicitInjections } from "discourse/lib/implicit-injections";
|
||||
|
||||
export default {
|
||||
name: "inject-discourse-objects",
|
||||
after: "discourse-bootstrap",
|
||||
|
||||
initialize(container, app) {
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
initialize(app) {
|
||||
const siteSettings = app.__container__.lookup("service:site-settings");
|
||||
|
||||
const currentUser = User.current();
|
||||
|
||||
|
@ -22,7 +21,7 @@ export default {
|
|||
app.register("service:current-user", currentUser, { instantiate: false });
|
||||
|
||||
this.topicTrackingState = TopicTrackingState.create({
|
||||
messageBus: container.lookup("service:message-bus"),
|
||||
messageBus: app.__container__.lookup("service:message-bus"),
|
||||
siteSettings,
|
||||
currentUser,
|
||||
});
|
|
@ -1,10 +1,9 @@
|
|||
import { mapRoutes } from "discourse/mapping-router";
|
||||
|
||||
export default {
|
||||
name: "map-routes",
|
||||
after: "inject-discourse-objects",
|
||||
|
||||
initialize(_, app) {
|
||||
initialize(app) {
|
||||
this.routerClass = mapRoutes();
|
||||
app.register("router:main", this.routerClass);
|
||||
},
|
|
@ -1,10 +0,0 @@
|
|||
import { registerServiceWorker } from "discourse/lib/register-service-worker";
|
||||
|
||||
export default {
|
||||
name: "register-service-worker",
|
||||
|
||||
initialize(container) {
|
||||
let { serviceWorkerURL } = container.lookup("service:session");
|
||||
registerServiceWorker(container, serviceWorkerURL);
|
||||
},
|
||||
};
|
|
@ -31,8 +31,6 @@ function animatedImgs() {
|
|||
}
|
||||
|
||||
export default {
|
||||
name: "animated-images-pause-on-click",
|
||||
|
||||
initialize() {
|
||||
withPluginApi("0.8.7", (api) => {
|
||||
function _cleanUp() {
|
|
@ -1,8 +1,8 @@
|
|||
import { next } from "@ember/runloop";
|
||||
|
||||
export default {
|
||||
name: "auth-complete",
|
||||
after: "inject-objects",
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
let lastAuthResult;
|
||||
|
||||
if (document.getElementById("data-authentication")) {
|
||||
|
@ -12,14 +12,14 @@ export default {
|
|||
}
|
||||
|
||||
if (lastAuthResult) {
|
||||
const router = container.lookup("router:main");
|
||||
const router = owner.lookup("router:main");
|
||||
|
||||
router.one("didTransition", () => {
|
||||
const controllerName =
|
||||
router.currentPath === "invites.show" ? "invites-show" : "login";
|
||||
|
||||
next(() => {
|
||||
let controller = container.lookup(`controller:${controllerName}`);
|
||||
let controller = owner.lookup(`controller:${controllerName}`);
|
||||
controller.authenticationComplete(JSON.parse(lastAuthResult));
|
||||
});
|
||||
});
|
|
@ -7,7 +7,7 @@ import RawHandlebars from "discourse-common/lib/raw-handlebars";
|
|||
import { registerRawHelpers } from "discourse-common/lib/raw-handlebars-helpers";
|
||||
import { setOwner } from "@ember/application";
|
||||
|
||||
export function autoLoadModules(container, registry) {
|
||||
export function autoLoadModules(owner, registry) {
|
||||
Object.keys(requirejs.entries).forEach((entry) => {
|
||||
if (/\/helpers\//.test(entry) && !/-test/.test(entry)) {
|
||||
requirejs(entry, null, null, true);
|
||||
|
@ -18,16 +18,16 @@ export function autoLoadModules(container, registry) {
|
|||
});
|
||||
|
||||
let context = {
|
||||
siteSettings: container.lookup("service:site-settings"),
|
||||
keyValueStore: container.lookup("service:key-value-store"),
|
||||
capabilities: container.lookup("service:capabilities"),
|
||||
currentUser: container.lookup("service:current-user"),
|
||||
site: container.lookup("service:site"),
|
||||
session: container.lookup("service:session"),
|
||||
topicTrackingState: container.lookup("service:topic-tracking-state"),
|
||||
siteSettings: owner.lookup("service:site-settings"),
|
||||
keyValueStore: owner.lookup("service:key-value-store"),
|
||||
capabilities: owner.lookup("service:capabilities"),
|
||||
currentUser: owner.lookup("service:current-user"),
|
||||
site: owner.lookup("service:site"),
|
||||
session: owner.lookup("service:session"),
|
||||
topicTrackingState: owner.lookup("service:topic-tracking-state"),
|
||||
registry,
|
||||
};
|
||||
setOwner(context, container);
|
||||
setOwner(context, owner);
|
||||
|
||||
createHelperContext(context);
|
||||
registerHelpers(registry);
|
||||
|
@ -35,7 +35,8 @@ export function autoLoadModules(container, registry) {
|
|||
}
|
||||
|
||||
export default {
|
||||
name: "auto-load-modules",
|
||||
after: "inject-objects",
|
||||
initialize: (container) => autoLoadModules(container, container.registry),
|
||||
initialize: (owner) => {
|
||||
autoLoadModules(owner, owner.__container__.registry);
|
||||
},
|
||||
};
|
|
@ -1,19 +1,18 @@
|
|||
// Updates the PWA badging if available
|
||||
export default {
|
||||
name: "badging",
|
||||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
if (!navigator.setAppBadge) {
|
||||
return;
|
||||
} // must have the Badging API
|
||||
|
||||
const user = container.lookup("service:current-user");
|
||||
const user = owner.lookup("service:current-user");
|
||||
if (!user) {
|
||||
return;
|
||||
} // must be logged in
|
||||
|
||||
const appEvents = container.lookup("service:app-events");
|
||||
const appEvents = owner.lookup("service:app-events");
|
||||
appEvents.on("notifications:changed", () => {
|
||||
let notifications;
|
||||
notifications = user.all_unread_notifications_count;
|
|
@ -3,12 +3,11 @@ import { bind } from "discourse-common/utils/decorators";
|
|||
import PreloadStore from "discourse/lib/preload-store";
|
||||
|
||||
export default {
|
||||
name: "banner",
|
||||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
this.site = container.lookup("service:site");
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
initialize(owner) {
|
||||
this.site = owner.lookup("service:site");
|
||||
this.messageBus = owner.lookup("service:message-bus");
|
||||
|
||||
const banner = EmberObject.create(PreloadStore.get("banner") || {});
|
||||
this.site.set("banner", banner);
|
|
@ -1,5 +1,4 @@
|
|||
export default {
|
||||
name: "category-color-css-generator",
|
||||
after: "register-hashtag-types",
|
||||
|
||||
/**
|
||||
|
@ -9,8 +8,8 @@ export default {
|
|||
* It is also used when styling hashtag icons, since they are colored
|
||||
* based on the category color.
|
||||
*/
|
||||
initialize(container) {
|
||||
this.site = container.lookup("service:site");
|
||||
initialize(owner) {
|
||||
this.site = owner.lookup("service:site");
|
||||
|
||||
// If the site is login_required and the user is anon there will be no categories preloaded.
|
||||
if (!this.site.categories) {
|
|
@ -33,18 +33,17 @@ function _clean(transition) {
|
|||
}
|
||||
|
||||
export default {
|
||||
name: "clean-dom-on-route-change",
|
||||
after: "inject-objects",
|
||||
|
||||
initialize(container) {
|
||||
const router = container.lookup("router:main");
|
||||
initialize(owner) {
|
||||
const router = owner.lookup("router:main");
|
||||
|
||||
router.on("routeDidChange", (transition) => {
|
||||
if (transition.isAborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleOnce("afterRender", container, _clean, transition);
|
||||
scheduleOnce("afterRender", owner, _clean, transition);
|
||||
});
|
||||
},
|
||||
};
|
|
@ -2,9 +2,8 @@ import DiscourseURL from "discourse/lib/url";
|
|||
import interceptClick from "discourse/lib/intercept-click";
|
||||
|
||||
export default {
|
||||
name: "click-interceptor",
|
||||
initialize(container, app) {
|
||||
this.selector = app.rootElement;
|
||||
initialize(owner) {
|
||||
this.selector = owner.rootElement;
|
||||
$(this.selector).on("click.discourse", "a", interceptClick);
|
||||
window.addEventListener("hashchange", this.hashChanged);
|
||||
},
|
|
@ -5,10 +5,8 @@ import CodeblockButtons from "discourse/lib/codeblock-buttons";
|
|||
let _codeblockButtons = [];
|
||||
|
||||
export default {
|
||||
name: "codeblock-buttons",
|
||||
|
||||
initialize(container) {
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
initialize(owner) {
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
|
||||
withPluginApi("0.8.7", (api) => {
|
||||
function _cleanUp() {
|
|
@ -14,11 +14,10 @@ GlimmerManager.getComponentTemplate = (component) => {
|
|||
};
|
||||
|
||||
export default {
|
||||
name: "colocated-template-overrides",
|
||||
after: ["populate-template-map", "mobile"],
|
||||
|
||||
initialize(container) {
|
||||
this.site = container.lookup("service:site");
|
||||
initialize(owner) {
|
||||
this.site = owner.lookup("service:site");
|
||||
|
||||
this.eachThemePluginTemplate((templateKey, moduleNames, mobile) => {
|
||||
if (!mobile && DiscourseTemplateMap.coreTemplates.has(templateKey)) {
|
||||
|
@ -32,9 +31,7 @@ export default {
|
|||
}
|
||||
componentName = componentName.slice("components/".length);
|
||||
|
||||
const component = container.owner.resolveRegistration(
|
||||
`component:${componentName}`
|
||||
);
|
||||
const component = owner.resolveRegistration(`component:${componentName}`);
|
||||
|
||||
if (component && originalGetTemplate(component)) {
|
||||
const finalOverrideModuleName = moduleNames[moduleNames.length - 1];
|
|
@ -4,10 +4,9 @@ let installed = false;
|
|||
let callbacks = $.Callbacks();
|
||||
|
||||
export default {
|
||||
name: "csrf-token",
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
// Add a CSRF token to all AJAX requests
|
||||
let session = container.lookup("service:session");
|
||||
let session = owner.lookup("service:session");
|
||||
session.set(
|
||||
"csrfToken",
|
||||
document.head.querySelector("meta[name=csrf-token]")?.content
|
|
@ -1,8 +1,6 @@
|
|||
import { showPopover } from "discourse/lib/d-popover";
|
||||
|
||||
export default {
|
||||
name: "d-popover",
|
||||
|
||||
initialize() {
|
||||
["click", "mouseover"].forEach((eventType) => {
|
||||
document.addEventListener(eventType, (e) => {
|
|
@ -1,8 +1,6 @@
|
|||
import { eagerLoadRawTemplateModules } from "discourse-common/lib/raw-templates";
|
||||
|
||||
export default {
|
||||
name: "eager-load-raw-templates",
|
||||
|
||||
initialize() {
|
||||
eagerLoadRawTemplateModules();
|
||||
},
|
|
@ -3,10 +3,8 @@ import { registerEmoji } from "pretty-text/emoji";
|
|||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
|
||||
export default {
|
||||
name: "enable-emoji",
|
||||
|
||||
initialize(container) {
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
initialize(owner) {
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
if (!siteSettings.enable_emoji) {
|
||||
return;
|
||||
}
|
|
@ -7,8 +7,6 @@ import { isTesting } from "discourse-common/config/environment";
|
|||
const DELAY = isTesting() ? 0 : 5000;
|
||||
|
||||
export default {
|
||||
name: "handle-cookies",
|
||||
|
||||
initialize() {
|
||||
// No need to block boot for this housekeeping - we can defer it a few seconds
|
||||
later(() => {
|
|
@ -1,7 +1,6 @@
|
|||
import { getHashtagTypeClasses } from "discourse/lib/hashtag-autocomplete";
|
||||
|
||||
export default {
|
||||
name: "hashtag-css-generator",
|
||||
after: "category-color-css-generator",
|
||||
|
||||
/**
|
||||
|
@ -13,8 +12,8 @@ export default {
|
|||
* with the hastag type via api.registerHashtagType. The default
|
||||
* ones in core are CategoryHashtagType and TagHashtagType.
|
||||
*/
|
||||
initialize(container) {
|
||||
this.site = container.lookup("service:site");
|
||||
initialize(owner) {
|
||||
this.site = owner.lookup("service:site");
|
||||
|
||||
// If the site is login_required and the user is anon there will be no categories
|
||||
// preloaded, so there will be no category color CSS variables generated by
|
|
@ -2,12 +2,11 @@ import { withPluginApi } from "discourse/lib/plugin-api";
|
|||
import { replaceHashtagIconPlaceholder } from "discourse/lib/hashtag-autocomplete";
|
||||
|
||||
export default {
|
||||
name: "hashtag-post-decorations",
|
||||
after: "hashtag-css-generator",
|
||||
|
||||
initialize(container) {
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
const site = container.lookup("service:site");
|
||||
initialize(owner) {
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
const site = owner.lookup("service:site");
|
||||
|
||||
withPluginApi("0.8.7", (api) => {
|
||||
if (siteSettings.enable_experimental_hashtag_autocomplete) {
|
|
@ -11,8 +11,6 @@ import { withPluginApi } from "discourse/lib/plugin-api";
|
|||
// the lifetime of all `<img` elements.
|
||||
|
||||
export default {
|
||||
name: "image-aspect-ratio",
|
||||
|
||||
initWithApi(api) {
|
||||
const supportsAspectRatio = CSS.supports("aspect-ratio: 1");
|
||||
|
|
@ -4,14 +4,13 @@ import Site from "discourse/models/site";
|
|||
import deprecated from "discourse-common/lib/deprecated";
|
||||
|
||||
export default {
|
||||
name: "inject-objects",
|
||||
after: "sniff-capabilities",
|
||||
|
||||
initialize(container, app) {
|
||||
initialize(owner) {
|
||||
// This is required for Ember CLI tests to work
|
||||
setDefaultOwner(app.__container__);
|
||||
setDefaultOwner(owner.__container__);
|
||||
|
||||
Object.defineProperty(app, "SiteSettings", {
|
||||
Object.defineProperty(owner, "SiteSettings", {
|
||||
get() {
|
||||
deprecated(
|
||||
`use injected siteSettings instead of Discourse.SiteSettings`,
|
||||
|
@ -21,10 +20,10 @@ export default {
|
|||
id: "discourse.global.site-settings",
|
||||
}
|
||||
);
|
||||
return container.lookup("service:site-settings");
|
||||
return owner.lookup("service:site-settings");
|
||||
},
|
||||
});
|
||||
Object.defineProperty(app, "User", {
|
||||
Object.defineProperty(owner, "User", {
|
||||
get() {
|
||||
deprecated(
|
||||
`import discourse/models/user instead of using Discourse.User`,
|
||||
|
@ -37,7 +36,7 @@ export default {
|
|||
return User;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(app, "Site", {
|
||||
Object.defineProperty(owner, "Site", {
|
||||
get() {
|
||||
deprecated(
|
||||
`import discourse/models/site instead of using Discourse.Site`,
|
|
@ -6,7 +6,6 @@ import deprecated from "discourse-common/lib/deprecated";
|
|||
let jqueryPluginsConfigured = false;
|
||||
|
||||
export default {
|
||||
name: "jquery-plugins",
|
||||
initialize() {
|
||||
if (jqueryPluginsConfigured) {
|
||||
return;
|
|
@ -2,10 +2,8 @@ import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts";
|
|||
import ItsATrap from "@discourse/itsatrap";
|
||||
|
||||
export default {
|
||||
name: "keyboard-shortcuts",
|
||||
|
||||
initialize(container) {
|
||||
KeyboardShortcuts.init(ItsATrap, container);
|
||||
initialize(owner) {
|
||||
KeyboardShortcuts.init(ItsATrap, owner);
|
||||
KeyboardShortcuts.bindEvents();
|
||||
},
|
||||
|
|
@ -5,11 +5,9 @@ import { bind } from "discourse-common/utils/decorators";
|
|||
|
||||
// Use the message bus for live reloading of components for faster development.
|
||||
export default {
|
||||
name: "live-development",
|
||||
|
||||
initialize(container) {
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
const session = container.lookup("service:session");
|
||||
initialize(owner) {
|
||||
this.messageBus = owner.lookup("service:message-bus");
|
||||
const session = owner.lookup("service:session");
|
||||
|
||||
// Preserve preview_theme_id=## and pp=async-flamegraph parameters across pages
|
||||
const params = new URLSearchParams(window.location.search);
|
|
@ -2,11 +2,10 @@ import I18n from "I18n";
|
|||
import bootbox from "bootbox";
|
||||
|
||||
export default {
|
||||
name: "localization",
|
||||
after: "inject-objects",
|
||||
|
||||
isVerboseLocalizationEnabled(container) {
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
isVerboseLocalizationEnabled(owner) {
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
if (siteSettings.verbose_localization) {
|
||||
return true;
|
||||
}
|
||||
|
@ -18,8 +17,8 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
initialize(container) {
|
||||
if (this.isVerboseLocalizationEnabled(container)) {
|
||||
initialize(owner) {
|
||||
if (this.isVerboseLocalizationEnabled(owner)) {
|
||||
I18n.enableVerboseLocalization();
|
||||
}
|
||||
|
|
@ -6,13 +6,12 @@ let _showingLogout = false;
|
|||
|
||||
// Subscribe to "logout" change events via the Message Bus
|
||||
export default {
|
||||
name: "logout",
|
||||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
this.dialog = container.lookup("service:dialog");
|
||||
this.currentUser = container.lookup("service:current-user");
|
||||
initialize(owner) {
|
||||
this.messageBus = owner.lookup("service:message-bus");
|
||||
this.dialog = owner.lookup("service:dialog");
|
||||
this.currentUser = owner.lookup("service:current-user");
|
||||
|
||||
if (this.currentUser) {
|
||||
this.messageBus.subscribe(
|
|
@ -3,18 +3,17 @@ import Singleton from "discourse/mixins/singleton";
|
|||
let initializedOnce = false;
|
||||
|
||||
export default {
|
||||
name: "logs-notice",
|
||||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
if (initializedOnce) {
|
||||
return;
|
||||
}
|
||||
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
const messageBus = container.lookup("service:message-bus");
|
||||
const keyValueStore = container.lookup("service:key-value-store");
|
||||
const currentUser = container.lookup("service:current-user");
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
const messageBus = owner.lookup("service:message-bus");
|
||||
const keyValueStore = owner.lookup("service:key-value-store");
|
||||
const currentUser = owner.lookup("service:current-user");
|
||||
LogsNotice.reopenClass(Singleton, {
|
||||
createCurrent() {
|
||||
return this.create({
|
|
@ -25,22 +25,19 @@ function ajax(opts, messageBusConnectivity, appState) {
|
|||
}
|
||||
|
||||
export default {
|
||||
name: "message-bus",
|
||||
after: "inject-objects",
|
||||
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
// We don't use the message bus in testing
|
||||
if (isTesting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageBus = container.lookup("service:message-bus"),
|
||||
user = container.lookup("service:current-user"),
|
||||
siteSettings = container.lookup("service:site-settings"),
|
||||
appState = container.lookup("service:app-state"),
|
||||
messageBusConnectivity = container.lookup(
|
||||
"service:message-bus-connectivity"
|
||||
);
|
||||
const messageBus = owner.lookup("service:message-bus"),
|
||||
user = owner.lookup("service:current-user"),
|
||||
siteSettings = owner.lookup("service:site-settings"),
|
||||
appState = owner.lookup("service:app-state"),
|
||||
messageBusConnectivity = owner.lookup("service:message-bus-connectivity");
|
||||
|
||||
messageBus.alwaysLongPoll = !isProduction();
|
||||
messageBus.shouldLongPollCallback = () =>
|
|
@ -1,12 +1,11 @@
|
|||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default {
|
||||
name: "mobile-keyboard",
|
||||
after: "mobile",
|
||||
|
||||
initialize(container) {
|
||||
const site = container.lookup("service:site");
|
||||
this.capabilities = container.lookup("service:capabilities");
|
||||
initialize(owner) {
|
||||
const site = owner.lookup("service:site");
|
||||
this.capabilities = owner.lookup("service:capabilities");
|
||||
|
||||
if (!this.capabilities.isIpadOS && !site.mobileView) {
|
||||
return;
|
|
@ -3,12 +3,11 @@ import { setResolverOption } from "discourse-common/resolver";
|
|||
|
||||
// Initializes the `Mobile` helper object.
|
||||
export default {
|
||||
name: "mobile",
|
||||
after: "inject-objects",
|
||||
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
Mobile.init();
|
||||
const site = container.lookup("service:site");
|
||||
const site = owner.lookup("service:site");
|
||||
|
||||
site.set("mobileView", Mobile.mobileView);
|
||||
site.set("desktopView", !Mobile.mobileView);
|
|
@ -1,5 +1,4 @@
|
|||
export default {
|
||||
name: "moment",
|
||||
after: "message-bus",
|
||||
|
||||
initialize() {
|
|
@ -1,19 +1,17 @@
|
|||
import NarrowDesktop from "discourse/lib/narrow-desktop";
|
||||
|
||||
export default {
|
||||
name: "narrow-desktop",
|
||||
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
NarrowDesktop.init();
|
||||
let site;
|
||||
if (!container.isDestroyed) {
|
||||
site = container.lookup("service:site");
|
||||
if (!owner.isDestroyed) {
|
||||
site = owner.lookup("service:site");
|
||||
site.set("narrowDesktopView", NarrowDesktop.narrowDesktopView);
|
||||
}
|
||||
|
||||
if ("ResizeObserver" in window) {
|
||||
this._resizeObserver = new ResizeObserver((entries) => {
|
||||
if (container.isDestroyed) {
|
||||
if (owner.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
for (let entry of entries) {
|
||||
|
@ -22,7 +20,7 @@ export default {
|
|||
entry.contentRect.width
|
||||
);
|
||||
if (oldNarrowDesktopView !== newNarrowDesktopView) {
|
||||
const applicationController = container.lookup(
|
||||
const applicationController = owner.lookup(
|
||||
"controller:application"
|
||||
);
|
||||
site.set("narrowDesktopView", newNarrowDesktopView);
|
|
@ -38,8 +38,6 @@ function _cleanUp() {
|
|||
}
|
||||
|
||||
export default {
|
||||
name: "onebox-decorators",
|
||||
|
||||
initialize() {
|
||||
withPluginApi("0.8.42", (api) => {
|
||||
api.decorateCookedElement(
|
|
@ -1,9 +1,7 @@
|
|||
import { getAbsoluteURL } from "discourse-common/lib/get-url";
|
||||
|
||||
export default {
|
||||
name: "opengraph-tag-updater",
|
||||
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
// workaround for Safari on iOS 14.3
|
||||
// seems it has started using opengraph tags when sharing
|
||||
const ogTitle = document.querySelector("meta[property='og:title']");
|
||||
|
@ -17,7 +15,7 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
const appEvents = container.lookup("service:app-events");
|
||||
const appEvents = owner.lookup("service:app-events");
|
||||
appEvents.on("page:changed", ({ title, url }) => {
|
||||
ogTitle.setAttribute("content", title);
|
||||
ogUrl.setAttribute("content", getAbsoluteURL(url));
|
|
@ -6,16 +6,15 @@ import {
|
|||
import { viewTrackingRequired } from "discourse/lib/ajax";
|
||||
|
||||
export default {
|
||||
name: "page-tracking",
|
||||
after: "inject-objects",
|
||||
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
// Tell our AJAX system to track a page transition
|
||||
const router = container.lookup("router:main");
|
||||
const router = owner.lookup("router:main");
|
||||
router.on("routeWillChange", viewTrackingRequired);
|
||||
|
||||
let appEvents = container.lookup("service:app-events");
|
||||
let documentTitle = container.lookup("service:document-title");
|
||||
let appEvents = owner.lookup("service:app-events");
|
||||
let documentTitle = owner.lookup("service:document-title");
|
||||
|
||||
startPageTracking(router, appEvents, documentTitle);
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import discourseTemplateMap from "discourse-common/lib/discourse-template-map";
|
||||
|
||||
export default {
|
||||
name: "populate-template-map",
|
||||
initialize() {
|
||||
discourseTemplateMap.setModuleNames(Object.keys(requirejs.entries));
|
||||
},
|
|
@ -12,12 +12,11 @@ import { create } from "virtual-dom";
|
|||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
export default {
|
||||
name: "post-decorations",
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
withPluginApi("0.1", (api) => {
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
const session = container.lookup("service:session");
|
||||
const site = container.lookup("service:site");
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
const session = owner.lookup("service:session");
|
||||
const site = owner.lookup("service:site");
|
||||
api.decorateCookedElement(
|
||||
(elem) => {
|
||||
return highlightSyntax(elem, siteSettings, session);
|
||||
|
@ -78,7 +77,7 @@ export default {
|
|||
{ id: "discourse-audio" }
|
||||
);
|
||||
|
||||
const caps = container.lookup("service:capabilities");
|
||||
const caps = owner.lookup("service:capabilities");
|
||||
if (caps.isSafari || caps.isIOS) {
|
||||
api.decorateCookedElement(
|
||||
(elem) => {
|
|
@ -2,12 +2,11 @@ import { bind } from "discourse-common/utils/decorators";
|
|||
|
||||
// Subscribe to "read-only" status change events via the Message Bus
|
||||
export default {
|
||||
name: "read-only",
|
||||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
this.site = container.lookup("service:site");
|
||||
initialize(owner) {
|
||||
this.messageBus = owner.lookup("service:message-bus");
|
||||
this.site = owner.lookup("service:site");
|
||||
|
||||
this.messageBus.subscribe("/site/read-only", this.onMessage);
|
||||
},
|
|
@ -3,13 +3,12 @@ import CategoryHashtagType from "discourse/lib/hashtag-types/category";
|
|||
import TagHashtagType from "discourse/lib/hashtag-types/tag";
|
||||
|
||||
export default {
|
||||
name: "register-hashtag-types",
|
||||
before: "hashtag-css-generator",
|
||||
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
withPluginApi("0.8.7", (api) => {
|
||||
api.registerHashtagType("category", new CategoryHashtagType(container));
|
||||
api.registerHashtagType("tag", new TagHashtagType(container));
|
||||
api.registerHashtagType("category", new CategoryHashtagType(owner));
|
||||
api.registerHashtagType("tag", new TagHashtagType(owner));
|
||||
});
|
||||
},
|
||||
};
|
|
@ -3,11 +3,9 @@ import UppyMediaOptimization from "discourse/lib/uppy-media-optimization-plugin"
|
|||
import { Promise } from "rsvp";
|
||||
|
||||
export default {
|
||||
name: "register-media-optimization-upload-processor",
|
||||
|
||||
initialize(container) {
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
const capabilities = container.lookup("service:capabilities");
|
||||
initialize(owner) {
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
const capabilities = owner.lookup("service:capabilities");
|
||||
|
||||
if (siteSettings.composer_media_optimization_image_enabled) {
|
||||
// NOTE: There are various performance issues with the Canvas
|
||||
|
@ -32,11 +30,11 @@ export default {
|
|||
({ isMobileDevice }) => {
|
||||
return {
|
||||
optimizeFn: (data, opts) => {
|
||||
if (container.isDestroyed || container.isDestroying) {
|
||||
if (owner.isDestroyed || owner.isDestroying) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return container
|
||||
return owner
|
||||
.lookup("service:media-optimization-worker")
|
||||
.optimizeImage(data, opts);
|
||||
},
|
|
@ -0,0 +1,8 @@
|
|||
import { registerServiceWorker } from "discourse/lib/register-service-worker";
|
||||
|
||||
export default {
|
||||
initialize(owner) {
|
||||
let { serviceWorkerURL } = owner.lookup("service:session");
|
||||
registerServiceWorker(serviceWorkerURL);
|
||||
},
|
||||
};
|
|
@ -2,8 +2,6 @@ import { updateRelativeAge } from "discourse/lib/formatter";
|
|||
|
||||
// Updates the relative ages of dates on the screen.
|
||||
export default {
|
||||
name: "relative-ages",
|
||||
|
||||
initialize() {
|
||||
this._interval = setInterval(function () {
|
||||
updateRelativeAge(document.querySelectorAll(".relative-date"));
|
|
@ -2,10 +2,8 @@ import I18n from "I18n";
|
|||
import Sharing from "discourse/lib/sharing";
|
||||
|
||||
export default {
|
||||
name: "sharing-sources",
|
||||
|
||||
initialize(container) {
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
initialize(owner) {
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
|
||||
Sharing.addSource({
|
||||
id: "twitter",
|
|
@ -1,9 +1,7 @@
|
|||
export default {
|
||||
name: "show-footer",
|
||||
|
||||
initialize(container) {
|
||||
const router = container.lookup("router:main");
|
||||
const application = container.lookup("controller:application");
|
||||
initialize(owner) {
|
||||
const router = owner.lookup("router:main");
|
||||
const application = owner.lookup("controller:application");
|
||||
|
||||
// only take care of hiding the footer here
|
||||
// controllers MUST take care of displaying it
|
|
@ -6,15 +6,13 @@ const ONE_DAY = 24 * 60 * 60 * 1000;
|
|||
const PROMPT_HIDE_DURATION = ONE_DAY;
|
||||
|
||||
export default {
|
||||
name: "signup-cta",
|
||||
|
||||
initialize(container) {
|
||||
const screenTrack = container.lookup("service:screen-track");
|
||||
initialize(owner) {
|
||||
const screenTrack = owner.lookup("service:screen-track");
|
||||
const session = Session.current();
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
const keyValueStore = container.lookup("service:key-value-store");
|
||||
const user = container.lookup("service:current-user");
|
||||
const appEvents = container.lookup("service:app-events");
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
const keyValueStore = owner.lookup("service:key-value-store");
|
||||
const user = owner.lookup("service:current-user");
|
||||
const appEvents = owner.lookup("service:app-events");
|
||||
|
||||
// Preconditions
|
||||
if (user) {
|
|
@ -1,9 +1,6 @@
|
|||
export default {
|
||||
name: "sniff-capabilities",
|
||||
after: "export-application-global",
|
||||
|
||||
initialize(container) {
|
||||
const caps = container.lookup("service:capabilities");
|
||||
initialize(owner) {
|
||||
const caps = owner.lookup("service:capabilities");
|
||||
const html = document.documentElement;
|
||||
|
||||
if (caps.touch) {
|
|
@ -1,11 +1,10 @@
|
|||
import StickyAvatars from "discourse/lib/sticky-avatars";
|
||||
|
||||
export default {
|
||||
name: "sticky-avatars",
|
||||
after: "inject-objects",
|
||||
|
||||
initialize(container) {
|
||||
this._stickyAvatars = StickyAvatars.init(container);
|
||||
initialize(owner) {
|
||||
this._stickyAvatars = StickyAvatars.init(owner);
|
||||
},
|
||||
|
||||
teardown() {
|
|
@ -1,6 +1,4 @@
|
|||
export default {
|
||||
name: "strip-mobile-app-url-params",
|
||||
|
||||
initialize() {
|
||||
let queryStrings = window.location.search;
|
||||
|
|
@ -15,23 +15,22 @@ import Notification from "discourse/models/notification";
|
|||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default {
|
||||
name: "subscribe-user-notifications",
|
||||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
this.currentUser = container.lookup("service:current-user");
|
||||
initialize(owner) {
|
||||
this.currentUser = owner.lookup("service:current-user");
|
||||
|
||||
if (!this.currentUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
this.store = container.lookup("service:store");
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
this.appEvents = container.lookup("service:app-events");
|
||||
this.siteSettings = container.lookup("service:site-settings");
|
||||
this.site = container.lookup("service:site");
|
||||
this.router = container.lookup("router:main");
|
||||
this.messageBus = owner.lookup("service:message-bus");
|
||||
this.store = owner.lookup("service:store");
|
||||
this.messageBus = owner.lookup("service:message-bus");
|
||||
this.appEvents = owner.lookup("service:app-events");
|
||||
this.siteSettings = owner.lookup("service:site-settings");
|
||||
this.site = owner.lookup("service:site");
|
||||
this.router = owner.lookup("router:main");
|
||||
|
||||
this.reviewableCountsChannel = `/reviewable_counts/${this.currentUser.id}`;
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
import { loadSprites } from "discourse/lib/svg-sprite-loader";
|
||||
|
||||
export default {
|
||||
name: "svg-sprite-fontawesome",
|
||||
after: "export-application-global",
|
||||
|
||||
initialize(container) {
|
||||
const session = container.lookup("service:session");
|
||||
initialize(owner) {
|
||||
const session = owner.lookup("service:session");
|
||||
|
||||
if (session.svgSpritePath) {
|
||||
loadSprites(session.svgSpritePath, "fontawesome");
|
|
@ -13,15 +13,12 @@ import Ember from "ember";
|
|||
const showingErrors = new Set();
|
||||
|
||||
export default {
|
||||
name: "theme-errors-handler",
|
||||
after: "export-application-global",
|
||||
|
||||
initialize(container) {
|
||||
initialize(owner) {
|
||||
if (isTesting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentUser = container.lookup("service:current-user");
|
||||
this.currentUser = owner.lookup("service:current-user");
|
||||
|
||||
getAndClearUnhandledThemeErrors().forEach((e) => this.reportThemeError(e));
|
||||
|
|
@ -13,8 +13,6 @@ const FLAG_PRIORITY = 700;
|
|||
const DEFER_PRIORITY = 500;
|
||||
|
||||
export default {
|
||||
name: "topic-footer-buttons",
|
||||
|
||||
initialize() {
|
||||
registerTopicFooterButton({
|
||||
id: "share-and-invite",
|
|
@ -3,11 +3,10 @@ import { initializeDefaultHomepage } from "discourse/lib/utilities";
|
|||
import escapeRegExp from "discourse-common/utils/escape-regexp";
|
||||
|
||||
export default {
|
||||
name: "url-redirects",
|
||||
after: "inject-objects",
|
||||
|
||||
initialize(container) {
|
||||
const currentUser = container.lookup("service:current-user");
|
||||
initialize(owner) {
|
||||
const currentUser = owner.lookup("service:current-user");
|
||||
if (currentUser) {
|
||||
const username = currentUser.get("username");
|
||||
const escapedUsername = escapeRegExp(username);
|
||||
|
@ -23,11 +22,11 @@ export default {
|
|||
DiscourseURL.rewrite(/^\/groups\//, "/g/");
|
||||
|
||||
// Initialize default homepage
|
||||
let siteSettings = container.lookup("service:site-settings");
|
||||
let siteSettings = owner.lookup("service:site-settings");
|
||||
initializeDefaultHomepage(siteSettings);
|
||||
|
||||
let defaultUserRoute = siteSettings.view_user_route || "summary";
|
||||
if (!container.lookup(`route:user.${defaultUserRoute}`)) {
|
||||
if (!owner.lookup(`route:user.${defaultUserRoute}`)) {
|
||||
defaultUserRoute = "summary";
|
||||
}
|
||||
|
|
@ -1,17 +1,16 @@
|
|||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default {
|
||||
name: "user-tips",
|
||||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
this.currentUser = container.lookup("service:current-user");
|
||||
initialize(owner) {
|
||||
this.currentUser = owner.lookup("service:current-user");
|
||||
if (!this.currentUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
this.site = container.lookup("service:site");
|
||||
this.messageBus = owner.lookup("service:message-bus");
|
||||
this.site = owner.lookup("service:site");
|
||||
|
||||
this.messageBus.subscribe(
|
||||
`/user-tips/${this.currentUser.id}`,
|
|
@ -3,11 +3,10 @@ import discourseLater from "discourse-common/lib/later";
|
|||
|
||||
// Send bg color to webview so iOS status bar matches site theme
|
||||
export default {
|
||||
name: "webview-background",
|
||||
after: "inject-objects",
|
||||
|
||||
initialize(container) {
|
||||
const caps = container.lookup("service:capabilities");
|
||||
initialize(owner) {
|
||||
const caps = owner.lookup("service:capabilities");
|
||||
if (caps.isAppWebview) {
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
|
@ -1,8 +1,8 @@
|
|||
import { setOwner } from "@ember/application";
|
||||
|
||||
export default class HashtagTypeBase {
|
||||
constructor(container) {
|
||||
setOwner(this, container);
|
||||
constructor(owner) {
|
||||
setOwner(this, owner);
|
||||
}
|
||||
|
||||
get type() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { bind } from "discourse-common/utils/decorators";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { getOwner, setOwner } from "@ember/application";
|
||||
import { run, throttle } from "@ember/runloop";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import {
|
||||
|
@ -121,7 +122,9 @@ function preventKeyboardEvent(event) {
|
|||
}
|
||||
|
||||
export default {
|
||||
init(keyTrapper, container) {
|
||||
init(keyTrapper, owner) {
|
||||
setOwner(this, owner);
|
||||
|
||||
// Sometimes the keyboard shortcut initializer is not torn down. This makes sure
|
||||
// we clear any previous test state.
|
||||
if (this.keyTrapper) {
|
||||
|
@ -130,14 +133,13 @@ export default {
|
|||
}
|
||||
|
||||
this.keyTrapper = new keyTrapper();
|
||||
this.container = container;
|
||||
this._stopCallback();
|
||||
|
||||
this.searchService = this.container.lookup("service:search");
|
||||
this.appEvents = this.container.lookup("service:app-events");
|
||||
this.currentUser = this.container.lookup("service:current-user");
|
||||
this.siteSettings = this.container.lookup("service:site-settings");
|
||||
this.site = this.container.lookup("service:site");
|
||||
this.searchService = owner.lookup("service:search");
|
||||
this.appEvents = owner.lookup("service:app-events");
|
||||
this.currentUser = owner.lookup("service:current-user");
|
||||
this.siteSettings = owner.lookup("service:site-settings");
|
||||
this.site = owner.lookup("service:site");
|
||||
|
||||
// Disable the shortcut if private messages are disabled
|
||||
if (!this.currentUser?.can_send_private_messages) {
|
||||
|
@ -158,11 +160,10 @@ export default {
|
|||
|
||||
this.keyTrapper?.destroy();
|
||||
this.keyTrapper = null;
|
||||
this.container = null;
|
||||
},
|
||||
|
||||
isTornDown() {
|
||||
return this.keyTrapper == null || this.container == null;
|
||||
return this.keyTrapper == null;
|
||||
},
|
||||
|
||||
bindKey(key, binding = null) {
|
||||
|
@ -298,12 +299,12 @@ export default {
|
|||
const topic = this.currentTopic();
|
||||
if (topic && document.querySelectorAll(".posts-wrapper").length) {
|
||||
preventKeyboardEvent(event);
|
||||
this.container.lookup("controller:topic").send("toggleBookmark");
|
||||
getOwner(this).lookup("controller:topic").send("toggleBookmark");
|
||||
}
|
||||
},
|
||||
|
||||
logout() {
|
||||
this.container.lookup("route:application").send("logout");
|
||||
getOwner(this).lookup("route:application").send("logout");
|
||||
},
|
||||
|
||||
quoteReply() {
|
||||
|
@ -354,7 +355,7 @@ export default {
|
|||
if (el) {
|
||||
el.click();
|
||||
} else {
|
||||
const controller = this.container.lookup("controller:topic");
|
||||
const controller = getOwner(this).lookup("controller:topic");
|
||||
// Only the last page contains list of suggested topics.
|
||||
const url = `/t/${controller.get("model.id")}/last.json`;
|
||||
ajax(url).then((result) => {
|
||||
|
@ -383,7 +384,7 @@ export default {
|
|||
|
||||
_jumpTo(direction) {
|
||||
if (document.querySelector(".container.posts")) {
|
||||
this.container.lookup("controller:topic").send(direction);
|
||||
getOwner(this).lookup("controller:topic").send(direction);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -426,7 +427,7 @@ export default {
|
|||
run(() => {
|
||||
if (document.querySelector(".container.posts")) {
|
||||
event.preventDefault(); // We need to stop printing the current page in Firefox
|
||||
this.container.lookup("controller:topic").print();
|
||||
getOwner(this).lookup("controller:topic").print();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -445,14 +446,14 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
this.container.lookup("service:composer").open({
|
||||
getOwner(this).lookup("service:composer").open({
|
||||
action: Composer.CREATE_TOPIC,
|
||||
draftKey: Composer.NEW_TOPIC_KEY,
|
||||
});
|
||||
},
|
||||
|
||||
focusComposer(event) {
|
||||
const composer = this.container.lookup("service:composer");
|
||||
const composer = getOwner(this).lookup("service:composer");
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
@ -461,14 +462,14 @@ export default {
|
|||
},
|
||||
|
||||
fullscreenComposer() {
|
||||
const composer = this.container.lookup("service:composer");
|
||||
const composer = getOwner(this).lookup("service:composer");
|
||||
if (composer.get("model")) {
|
||||
composer.toggleFullscreen();
|
||||
}
|
||||
},
|
||||
|
||||
pinUnpinTopic() {
|
||||
this.container.lookup("controller:topic").togglePinnedState();
|
||||
getOwner(this).lookup("controller:topic").togglePinnedState();
|
||||
},
|
||||
|
||||
goToPost(event) {
|
||||
|
@ -497,7 +498,7 @@ export default {
|
|||
},
|
||||
|
||||
showHelpModal() {
|
||||
this.container
|
||||
getOwner(this)
|
||||
.lookup("controller:application")
|
||||
.send("showKeyboardShortcutsHelp");
|
||||
},
|
||||
|
@ -531,7 +532,7 @@ export default {
|
|||
sendToTopicListItemView(action, elem) {
|
||||
elem = elem || document.querySelector("tr.selected.topic-list-item");
|
||||
if (elem) {
|
||||
const registry = this.container.lookup("-view-registry:main");
|
||||
const registry = getOwner(this).lookup("-view-registry:main");
|
||||
if (registry) {
|
||||
const view = registry[elem.id];
|
||||
view.send(action);
|
||||
|
@ -540,7 +541,7 @@ export default {
|
|||
},
|
||||
|
||||
currentTopic() {
|
||||
const topicController = this.container.lookup("controller:topic");
|
||||
const topicController = getOwner(this).lookup("controller:topic");
|
||||
if (topicController) {
|
||||
const topic = topicController.get("model");
|
||||
if (topic) {
|
||||
|
@ -550,7 +551,7 @@ export default {
|
|||
},
|
||||
|
||||
isPostTextSelected() {
|
||||
const topicController = this.container.lookup("controller:topic");
|
||||
const topicController = getOwner(this).lookup("controller:topic");
|
||||
return !!topicController?.get("quoteState")?.postId;
|
||||
},
|
||||
|
||||
|
@ -565,7 +566,7 @@ export default {
|
|||
}
|
||||
|
||||
if (selectedPostId) {
|
||||
const topicController = this.container.lookup("controller:topic");
|
||||
const topicController = getOwner(this).lookup("controller:topic");
|
||||
const post = topicController
|
||||
.get("model.postStream.posts")
|
||||
.findBy("id", selectedPostId);
|
||||
|
@ -574,7 +575,7 @@ export default {
|
|||
|
||||
let actionMethod = topicController.actions[action];
|
||||
if (!actionMethod) {
|
||||
const topicRoute = this.container.lookup("route:topic");
|
||||
const topicRoute = getOwner(this).lookup("route:topic");
|
||||
actionMethod = topicRoute.actions[action];
|
||||
}
|
||||
|
||||
|
@ -849,7 +850,7 @@ export default {
|
|||
},
|
||||
|
||||
_replyToPost() {
|
||||
this.container.lookup("controller:topic").send("replyToPost");
|
||||
getOwner(this).lookup("controller:topic").send("replyToPost");
|
||||
},
|
||||
|
||||
_getSelectedPost() {
|
||||
|
@ -861,7 +862,7 @@ export default {
|
|||
},
|
||||
|
||||
deferTopic() {
|
||||
this.container.lookup("controller:topic").send("deferTopic");
|
||||
getOwner(this).lookup("controller:topic").send("deferTopic");
|
||||
},
|
||||
|
||||
toggleAdminActions() {
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import getAbsoluteURL, { isAbsoluteURL } from "discourse-common/lib/get-url";
|
||||
|
||||
export function registerServiceWorker(
|
||||
container,
|
||||
serviceWorkerURL,
|
||||
registerOptions = {}
|
||||
) {
|
||||
export function registerServiceWorker(serviceWorkerURL, registerOptions = {}) {
|
||||
if (window.isSecureContext && "serviceWorker" in navigator) {
|
||||
if (serviceWorkerURL) {
|
||||
navigator.serviceWorker.getRegistrations().then((registrations) => {
|
||||
|
|
|
@ -2,11 +2,12 @@ import { addWidgetCleanCallback } from "discourse/components/mount-widget";
|
|||
import Site from "discourse/models/site";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import { headerOffset } from "discourse/lib/offset-calculator";
|
||||
import { getOwner, setOwner } from "@ember/application";
|
||||
import { schedule } from "@ember/runloop";
|
||||
|
||||
export default class StickyAvatars {
|
||||
static init(container) {
|
||||
return new this(container).init();
|
||||
static init(owner) {
|
||||
return new this(owner).init();
|
||||
}
|
||||
|
||||
stickyClass = "sticky-avatar";
|
||||
|
@ -15,8 +16,8 @@ export default class StickyAvatars {
|
|||
direction = "⬇️";
|
||||
prevOffset = -1;
|
||||
|
||||
constructor(container) {
|
||||
this.container = container;
|
||||
constructor(owner) {
|
||||
setOwner(this, owner);
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -24,7 +25,7 @@ export default class StickyAvatars {
|
|||
return;
|
||||
}
|
||||
|
||||
const appEvents = this.container.lookup("service:app-events");
|
||||
const appEvents = getOwner(this).lookup("service:app-events");
|
||||
appEvents.on("topic:current-post-scrolled", this._handlePostNodes);
|
||||
appEvents.on("topic:scrolled", this._handleScroll);
|
||||
appEvents.on("page:topic-loaded", this._initIntersectionObserver);
|
||||
|
@ -34,9 +35,7 @@ export default class StickyAvatars {
|
|||
return this;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.container = null;
|
||||
}
|
||||
destroy() {}
|
||||
|
||||
@bind
|
||||
_handleScroll(offset) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import Session from "discourse/models/session";
|
|||
import Site from "discourse/models/site";
|
||||
import TopicTrackingState from "discourse/models/topic-tracking-state";
|
||||
import User from "discourse/models/user";
|
||||
import { autoLoadModules } from "discourse/initializers/auto-load-modules";
|
||||
import { autoLoadModules } from "discourse/instance-initializers/auto-load-modules";
|
||||
import QUnit, { test } from "qunit";
|
||||
import { setupRenderingTest as emberSetupRenderingTest } from "ember-qunit";
|
||||
import { currentSettings } from "discourse/tests/helpers/site-settings";
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import { module, test } from "qunit";
|
||||
import I18n from "I18n";
|
||||
import LocalizationInitializer from "discourse/initializers/localization";
|
||||
import { getApplication } from "@ember/test-helpers";
|
||||
import LocalizationInitializer from "discourse/instance-initializers/localization";
|
||||
import { setupTest } from "ember-qunit";
|
||||
|
||||
module("initializer:localization", {
|
||||
_locale: I18n.locale,
|
||||
_translations: I18n.translations,
|
||||
_extras: I18n.extras,
|
||||
_compiledMFs: I18n._compiledMFs,
|
||||
_overrides: I18n._overrides,
|
||||
_mfOverrides: I18n._mfOverrides,
|
||||
module("initializer:localization", function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this._locale = I18n.locale;
|
||||
this._translations = I18n.translations;
|
||||
this._extras = I18n.extras;
|
||||
this._compiledMFs = I18n._compiledMFs;
|
||||
this._overrides = I18n._overrides;
|
||||
this._mfOverrides = I18n._mfOverrides;
|
||||
|
||||
beforeEach() {
|
||||
I18n.locale = "fr";
|
||||
|
||||
I18n.translations = {
|
||||
|
@ -59,19 +61,18 @@ module("initializer:localization", {
|
|||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
afterEach() {
|
||||
hooks.afterEach(function () {
|
||||
I18n.locale = this._locale;
|
||||
I18n.translations = this._translations;
|
||||
I18n.extras = this._extras;
|
||||
I18n._compiledMFs = this._compiledMFs;
|
||||
I18n._overrides = this._overrides;
|
||||
I18n._mfOverrides = this._mfOverrides;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("translation overrides", function (assert) {
|
||||
test("translation overrides", function (assert) {
|
||||
I18n._overrides = {
|
||||
fr: {
|
||||
"js.composer.both_languages1": "composer.both_languages1 (FR override)",
|
||||
|
@ -82,7 +83,7 @@ test("translation overrides", function (assert) {
|
|||
"js.composer.only_english1": "composer.only_english1 (EN override)",
|
||||
},
|
||||
};
|
||||
LocalizationInitializer.initialize(getApplication());
|
||||
LocalizationInitializer.initialize(this.owner);
|
||||
|
||||
assert.strictEqual(
|
||||
I18n.t("composer.both_languages1"),
|
||||
|
@ -107,9 +108,9 @@ test("translation overrides", function (assert) {
|
|||
"composer.both_languages2 (FR)",
|
||||
"prefers translation in current locale over override in fallback locale"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("translation overrides (admin_js)", function (assert) {
|
||||
test("translation overrides (admin_js)", function (assert) {
|
||||
I18n._overrides = {
|
||||
fr: {
|
||||
"admin_js.admin.api.both_languages1":
|
||||
|
@ -125,7 +126,7 @@ test("translation overrides (admin_js)", function (assert) {
|
|||
"admin.api.only_english1 (EN override)",
|
||||
},
|
||||
};
|
||||
LocalizationInitializer.initialize(getApplication());
|
||||
LocalizationInitializer.initialize(this.owner);
|
||||
|
||||
assert.strictEqual(
|
||||
I18n.t("admin.api.both_languages1"),
|
||||
|
@ -156,34 +157,35 @@ test("translation overrides (admin_js)", function (assert) {
|
|||
"type_to_filter (FR override)",
|
||||
"correctly changes the translation key by removing `admin_js`"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("translation overrides for MessageFormat strings", function (assert) {
|
||||
test("translation overrides for MessageFormat strings", function (assert) {
|
||||
I18n._mfOverrides = {
|
||||
"js.user.messages.some_key_MF": () =>
|
||||
"user.messages.some_key_MF (FR override)",
|
||||
};
|
||||
|
||||
LocalizationInitializer.initialize(getApplication());
|
||||
LocalizationInitializer.initialize(this.owner);
|
||||
|
||||
assert.strictEqual(
|
||||
I18n.messageFormat("user.messages.some_key_MF", {}),
|
||||
"user.messages.some_key_MF (FR override)",
|
||||
"overrides existing MessageFormat string"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("skip translation override if parent node is not an object", function (assert) {
|
||||
test("skip translation override if parent node is not an object", function (assert) {
|
||||
I18n._overrides = {
|
||||
fr: {
|
||||
"js.composer.both_languages1.foo":
|
||||
"composer.both_languages1.foo (FR override)",
|
||||
},
|
||||
};
|
||||
LocalizationInitializer.initialize(getApplication());
|
||||
LocalizationInitializer.initialize(this.owner);
|
||||
|
||||
assert.strictEqual(
|
||||
I18n.t("composer.both_languages1.foo"),
|
||||
"[fr.composer.both_languages1.foo]"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { decorateGithubOneboxBody } from "discourse/initializers/onebox-decorators";
|
||||
import { decorateGithubOneboxBody } from "discourse/instance-initializers/onebox-decorators";
|
||||
import { replaceHashtagIconPlaceholder } from "discourse/lib/hashtag-autocomplete";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||
|
|
Loading…
Reference in New Issue