diff --git a/app/assets/javascripts/discourse/app/components/d-modal-body.js b/app/assets/javascripts/discourse/app/components/d-modal-body.js
index bb6f9c857f4..6a3ffe4115b 100644
--- a/app/assets/javascripts/discourse/app/components/d-modal-body.js
+++ b/app/assets/javascripts/discourse/app/components/d-modal-body.js
@@ -3,7 +3,6 @@ import { disableImplicitInjections } from "discourse/lib/implicit-injections";
import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
-import { getOwner } from "@ember/application";
function pick(object, keys) {
const result = {};
@@ -18,6 +17,7 @@ function pick(object, keys) {
@disableImplicitInjections
export default class DModalBody extends Component {
@service appEvents;
+ @service modal;
@tracked fixed = false;
@@ -29,7 +29,7 @@ export default class DModalBody extends Component {
if (fixedParent) {
this.fixed = true;
$(fixedParent).modal("show");
- getOwner(this).lookup("controller:modal").hidden = false;
+ this.modal.hidden = false;
}
this.appEvents.trigger(
diff --git a/app/assets/javascripts/discourse/app/components/d-modal.js b/app/assets/javascripts/discourse/app/components/d-modal.js
index c7ace74afe2..7890e49a07d 100644
--- a/app/assets/javascripts/discourse/app/components/d-modal.js
+++ b/app/assets/javascripts/discourse/app/components/d-modal.js
@@ -6,11 +6,11 @@ import { disableImplicitInjections } from "discourse/lib/implicit-injections";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";
-import { getOwner } from "@ember/application";
@disableImplicitInjections
export default class DModal extends Component {
@service appEvents;
+ @service modal;
@tracked wrapperElement;
@tracked modalBodyData = {};
@@ -147,7 +147,7 @@ export default class DModal extends Component {
}
if (data.fixed) {
- getOwner(this).lookup("controller:modal").hidden = false;
+ this.modal.hidden = false;
}
this.modalBodyData = data;
diff --git a/app/assets/javascripts/discourse/app/components/modal-container.hbs b/app/assets/javascripts/discourse/app/components/modal-container.hbs
new file mode 100644
index 00000000000..f822de045ca
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/modal-container.hbs
@@ -0,0 +1,17 @@
+
+ {{outlet "modalBody"}}
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/components/modal-container.js b/app/assets/javascripts/discourse/app/components/modal-container.js
new file mode 100644
index 00000000000..a12b79783c8
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/modal-container.js
@@ -0,0 +1,12 @@
+import Component from "@glimmer/component";
+import { inject as service } from "@ember/service";
+import { action } from "@ember/object";
+
+export default class ModalContainer extends Component {
+ @service modal;
+
+ @action
+ closeModal(initiatedBy) {
+ this.modal.close(initiatedBy);
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/controllers/modal.js b/app/assets/javascripts/discourse/app/controllers/modal.js
deleted file mode 100644
index 18cc7d77425..00000000000
--- a/app/assets/javascripts/discourse/app/controllers/modal.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import Controller from "@ember/controller";
-import { tracked } from "@glimmer/tracking";
-
-export default class ModalController extends Controller {
- @tracked hidden = true;
-}
diff --git a/app/assets/javascripts/discourse/app/mixins/modal-functionality.js b/app/assets/javascripts/discourse/app/mixins/modal-functionality.js
index 6287199a755..ee302c08fc1 100644
--- a/app/assets/javascripts/discourse/app/mixins/modal-functionality.js
+++ b/app/assets/javascripts/discourse/app/mixins/modal-functionality.js
@@ -16,8 +16,7 @@ export default Mixin.create({
actions: {
closeModal() {
- this.modal.send("closeModal");
- this.set("panels", []);
+ this.modal.close();
},
},
});
diff --git a/app/assets/javascripts/discourse/app/routes/application.js b/app/assets/javascripts/discourse/app/routes/application.js
index e162cbaf230..d5403812d83 100644
--- a/app/assets/javascripts/discourse/app/routes/application.js
+++ b/app/assets/javascripts/discourse/app/routes/application.js
@@ -212,11 +212,6 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
},
},
- renderTemplate() {
- this.render("application");
- this.render("modal", { into: "application", outlet: "modal" });
- },
-
handleShowLogin() {
if (this.siteSettings.enable_discourse_connect) {
const returnPath = encodeURIComponent(window.location.pathname);
diff --git a/app/assets/javascripts/discourse/app/services/modal.js b/app/assets/javascripts/discourse/app/services/modal.js
index 4cf00307f5e..b6290aa74c5 100644
--- a/app/assets/javascripts/discourse/app/services/modal.js
+++ b/app/assets/javascripts/discourse/app/services/modal.js
@@ -3,28 +3,66 @@ import { getOwner } from "@ember/application";
import I18n from "I18n";
import { dasherize } from "@ember/string";
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
+import { tracked } from "@glimmer/tracking";
@disableImplicitInjections
export default class ModalService extends Service {
@service appEvents;
+ @tracked name;
+ @tracked opts = {};
+ @tracked selectedPanel;
+ @tracked hidden = true;
+
+ @tracked titleOverride;
+ @tracked modalClassOverride;
+ @tracked onSelectPanel;
+
+ get title() {
+ if (this.titleOverride) {
+ return this.titleOverride;
+ } else if (this.opts.titleTranslated) {
+ return this.opts.titleTranslated;
+ } else if (this.opts.title) {
+ return I18n.t(this.opts.title);
+ } else {
+ return null;
+ }
+ }
+
+ set title(value) {
+ this.titleOverride = value;
+ }
+
+ get modalClass() {
+ if (!this.#isRendered) {
+ return null;
+ }
+
+ return (
+ this.modalClassOverride ||
+ this.opts.modalClass ||
+ `${dasherize(this.name.replace(/^modals\//, "")).toLowerCase()}-modal`
+ );
+ }
+
+ set modalClass(value) {
+ this.modalClassOverride = value;
+ }
+
show(name, opts = {}) {
const container = getOwner(this);
const route = container.lookup("route:application");
- const modalController = route.controllerFor("modal");
- modalController.set(
- "modalClass",
- opts.modalClass || `${dasherize(name).toLowerCase()}-modal`
- );
+ this.opts = opts;
const controllerName = opts.admin ? `modals/${name}` : name;
- modalController.set("name", controllerName);
+ this.name = controllerName;
let controller = container.lookup("controller:" + controllerName);
const templateName = opts.templateName || dasherize(name);
- const renderArgs = { into: "modal", outlet: "modalBody" };
+ const renderArgs = { into: "application", outlet: "modalBody" };
if (controller) {
renderArgs.controller = controllerName;
} else {
@@ -40,40 +78,12 @@ export default class ModalService extends Service {
const modalName = `modal/${templateName}`;
const fullName = opts.admin ? `admin/templates/${modalName}` : modalName;
route.render(fullName, renderArgs);
- if (opts.title) {
- modalController.set("title", I18n.t(opts.title));
- } else if (opts.titleTranslated) {
- modalController.set("title", opts.titleTranslated);
- } else {
- modalController.set("title", null);
+
+ if (controller.actions.onSelectPanel) {
+ this.onSelectPanel = controller.actions.onSelectPanel.bind(controller);
}
- if (opts.titleAriaElementId) {
- modalController.set("titleAriaElementId", opts.titleAriaElementId);
- }
-
- if (opts.panels) {
- modalController.setProperties({
- panels: opts.panels,
- selectedPanel: opts.panels[0],
- });
-
- if (controller.actions.onSelectPanel) {
- modalController.set(
- "onSelectPanel",
- controller.actions.onSelectPanel.bind(controller)
- );
- }
-
- modalController.set(
- "modalClass",
- `${modalController.get("modalClass")} has-tabs`
- );
- } else {
- modalController.setProperties({ panels: [], selectedPanel: null });
- }
-
- controller.set("modal", modalController);
+ controller.set("modal", this);
const model = opts.model;
if (model) {
controller.set("model", model);
@@ -87,44 +97,44 @@ export default class ModalService extends Service {
}
close(initiatedBy) {
- const route = getOwner(this).lookup("route:application");
- let modalController = route.controllerFor("modal");
- const controllerName = modalController.get("name");
+ const controllerName = this.name;
+ const controller = controllerName
+ ? getOwner(this).lookup(`controller:${controllerName}`)
+ : null;
- if (controllerName) {
- const controller = getOwner(this).lookup(`controller:${controllerName}`);
- if (controller && controller.beforeClose) {
- if (false === controller.beforeClose()) {
- return;
- }
- }
+ if (controller?.beforeClose?.() === false) {
+ return;
}
getOwner(this)
.lookup("route:application")
- .render("hide-modal", { into: "modal", outlet: "modalBody" });
+ .render("hide-modal", { into: "application", outlet: "modalBody" });
$(".d-modal.fixed-modal").modal("hide");
- if (controllerName) {
- const controller = getOwner(this).lookup(`controller:${controllerName}`);
+ if (controller) {
+ this.appEvents.trigger("modal:closed", {
+ name: controllerName,
+ controller,
+ });
- if (controller) {
- this.appEvents.trigger("modal:closed", {
- name: controllerName,
- controller,
+ if (controller.onClose) {
+ controller.onClose({
+ initiatedByCloseButton: initiatedBy === "initiatedByCloseButton",
+ initiatedByClickOut: initiatedBy === "initiatedByClickOut",
+ initiatedByESC: initiatedBy === "initiatedByESC",
});
-
- if (controller.onClose) {
- controller.onClose({
- initiatedByCloseButton: initiatedBy === "initiatedByCloseButton",
- initiatedByClickOut: initiatedBy === "initiatedByClickOut",
- initiatedByESC: initiatedBy === "initiatedByESC",
- });
- }
}
- modalController.set("name", null);
}
- modalController.hidden = true;
+ this.hidden = true;
+
+ this.name =
+ this.selectedPanel =
+ this.modalClassOverride =
+ this.titleOverride =
+ this.onSelectPanel =
+ null;
+
+ this.opts = {};
}
hide() {
@@ -134,4 +144,8 @@ export default class ModalService extends Service {
reopen() {
$(".d-modal.fixed-modal").modal("show");
}
+
+ get #isRendered() {
+ return !!this.name;
+ }
}
diff --git a/app/assets/javascripts/discourse/app/templates/application.hbs b/app/assets/javascripts/discourse/app/templates/application.hbs
index 623f253f224..e4152a96937 100644
--- a/app/assets/javascripts/discourse/app/templates/application.hbs
+++ b/app/assets/javascripts/discourse/app/templates/application.hbs
@@ -82,7 +82,7 @@
@outletArgs={{hash showFooter=this.showFooter}}
/>
- {{outlet "modal"}}
+
diff --git a/app/assets/javascripts/discourse/app/templates/modal.hbs b/app/assets/javascripts/discourse/app/templates/modal.hbs
deleted file mode 100644
index 782924904f7..00000000000
--- a/app/assets/javascripts/discourse/app/templates/modal.hbs
+++ /dev/null
@@ -1,14 +0,0 @@
-
- {{outlet "modalBody"}}
-
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/tests/acceptance/do-not-disturb-test.js b/app/assets/javascripts/discourse/tests/acceptance/do-not-disturb-test.js
index 94f066cdeff..29da79004cc 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/do-not-disturb-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/do-not-disturb-test.js
@@ -38,7 +38,7 @@ acceptance("Do not disturb", function (needs) {
await click(tiles[0]);
- assert.ok(query(".do-not-disturb-modal.hidden"), "modal is hidden");
+ assert.ok(query(".d-modal.hidden"), "modal is hidden");
assert.ok(
exists(".header-dropdown-toggle .do-not-disturb-background .d-icon-moon"),
@@ -69,7 +69,7 @@ acceptance("Do not disturb", function (needs) {
);
assert.ok(
- query(".do-not-disturb-modal.hidden"),
+ query(".d-modal.hidden"),
"DND modal is hidden after making a choice"
);
diff --git a/app/assets/javascripts/discourse/tests/acceptance/modal-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal-test.js
index 91fc61547b1..b27e6ab25d2 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/modal-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/modal-test.js
@@ -38,15 +38,15 @@ acceptance("Modal", function (needs) {
await click(".login-button");
assert.strictEqual(count(".d-modal:visible"), 1, "modal should appear");
- const controller = getOwner(this).lookup("controller:modal");
- assert.strictEqual(controller.name, "login");
+ const service = getOwner(this).lookup("service:modal");
+ assert.strictEqual(service.name, "login");
await click(".modal-outer-container");
assert.ok(
!exists(".d-modal:visible"),
"modal should disappear when you click outside"
);
- assert.strictEqual(controller.name, null);
+ assert.strictEqual(service.name, null);
await click(".login-button");
assert.strictEqual(count(".d-modal:visible"), 1, "modal should reappear");