DEV: Support using Ember components in dialog service (#20230)
We often have the need to use rich HTML in dialog messages (to show lists, icons, etc.). Previously, our only option was to wrap the message in an `htmlSafe()` call. This PR adds the ability to pass a component name and model to the dialog, which means that we can write the HTML in regular Ember components. Example, whereas previously we would do this: ``` this.dialog.deleteConfirm({ message: htmlSafe(`<li>Some text</li>`), }); ``` instead we can now do this: ```javascript import SecondFactorConfirmPhrase from "discourse/components/dialog-messages/second-factor-confirm-phrase"; ... this.dialog.deleteConfirm({ title: I18n.t("user.second_factor.disable_confirm"), bodyComponent: SecondFactorConfirmPhrase, bodyComponentModel: model, }) ``` The model passed to the component is optional and will be available as `@model` in the Handlebars template.
This commit is contained in:
parent
6e8e4430dd
commit
4072786f5b
|
@ -20,19 +20,14 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (or this.dialog.message this.dialog.confirmPhrase)}}
|
||||
{{#if (or this.dialog.message this.dialog.bodyComponent)}}
|
||||
<div class="dialog-body">
|
||||
{{#if this.dialog.message}}
|
||||
<p>{{this.dialog.message}}</p>
|
||||
{{/if}}
|
||||
{{#if this.dialog.confirmPhrase}}
|
||||
<TextField
|
||||
@value={{this.dialog.confirmPhraseInput}}
|
||||
{{on "input" this.dialog.onConfirmPhraseInput}}
|
||||
@id="confirm-phrase"
|
||||
@autocorrect="off"
|
||||
@autocapitalize="off"
|
||||
{{#if this.dialog.bodyComponent}}
|
||||
<this.dialog.bodyComponent
|
||||
@model={{this.dialog.bodyComponentModel}}
|
||||
/>
|
||||
{{else if this.dialog.message}}
|
||||
<p>{{this.dialog.message}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import Service from "@ember/service";
|
||||
import A11yDialog from "a11y-dialog";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import { isBlank } from "@ember/utils";
|
||||
|
||||
export default Service.extend({
|
||||
message: null,
|
||||
type: null,
|
||||
dialogInstance: null,
|
||||
|
||||
message: null,
|
||||
title: null,
|
||||
titleElementId: null,
|
||||
type: null,
|
||||
|
||||
bodyComponent: null,
|
||||
bodyComponentModel: null,
|
||||
|
||||
confirmButtonIcon: null,
|
||||
confirmButtonLabel: null,
|
||||
confirmButtonClass: null,
|
||||
confirmPhrase: null,
|
||||
confirmPhraseInput: null,
|
||||
confirmButtonDisabled: false,
|
||||
cancelButtonLabel: null,
|
||||
cancelButtonClass: null,
|
||||
shouldDisplayCancel: null,
|
||||
|
@ -29,15 +29,18 @@ export default Service.extend({
|
|||
dialog(params) {
|
||||
const {
|
||||
message,
|
||||
bodyComponent,
|
||||
bodyComponentModel,
|
||||
type,
|
||||
title,
|
||||
|
||||
confirmButtonClass = "btn-primary",
|
||||
confirmButtonIcon,
|
||||
confirmButtonLabel = "ok_value",
|
||||
confirmButtonClass = "btn-primary",
|
||||
cancelButtonLabel = "cancel_value",
|
||||
confirmButtonDisabled = false,
|
||||
|
||||
cancelButtonClass = "btn-default",
|
||||
confirmPhrase,
|
||||
cancelButtonLabel = "cancel_value",
|
||||
shouldDisplayCancel,
|
||||
|
||||
didConfirm,
|
||||
|
@ -45,25 +48,25 @@ export default Service.extend({
|
|||
buttons,
|
||||
} = params;
|
||||
|
||||
let confirmButtonDisabled = !isBlank(confirmPhrase);
|
||||
|
||||
const element = document.getElementById("dialog-holder");
|
||||
|
||||
this.setProperties({
|
||||
message,
|
||||
bodyComponent,
|
||||
bodyComponentModel,
|
||||
type,
|
||||
dialogInstance: new A11yDialog(element),
|
||||
|
||||
title,
|
||||
titleElementId: title !== null ? "dialog-title" : null,
|
||||
|
||||
confirmButtonDisabled,
|
||||
confirmButtonClass,
|
||||
confirmButtonLabel,
|
||||
confirmButtonDisabled,
|
||||
confirmButtonIcon,
|
||||
confirmPhrase,
|
||||
cancelButtonLabel,
|
||||
confirmButtonLabel,
|
||||
|
||||
cancelButtonClass,
|
||||
cancelButtonLabel,
|
||||
shouldDisplayCancel,
|
||||
|
||||
didConfirm,
|
||||
|
@ -133,19 +136,21 @@ export default Service.extend({
|
|||
reset() {
|
||||
this.setProperties({
|
||||
message: null,
|
||||
bodyComponent: null,
|
||||
bodyComponentModel: null,
|
||||
type: null,
|
||||
dialogInstance: null,
|
||||
|
||||
title: null,
|
||||
titleElementId: null,
|
||||
|
||||
confirmButtonLabel: null,
|
||||
confirmButtonDisabled: false,
|
||||
confirmButtonIcon: null,
|
||||
cancelButtonLabel: null,
|
||||
confirmButtonLabel: null,
|
||||
|
||||
cancelButtonClass: null,
|
||||
cancelButtonLabel: null,
|
||||
shouldDisplayCancel: null,
|
||||
confirmPhrase: null,
|
||||
confirmPhraseInput: null,
|
||||
|
||||
didConfirm: null,
|
||||
didCancel: null,
|
||||
|
@ -176,10 +181,7 @@ export default Service.extend({
|
|||
},
|
||||
|
||||
@bind
|
||||
onConfirmPhraseInput() {
|
||||
this.set(
|
||||
"confirmButtonDisabled",
|
||||
this.confirmPhrase && this.confirmPhraseInput !== this.confirmPhrase
|
||||
);
|
||||
enableConfirmButton() {
|
||||
this.set("confirmButtonDisabled", false);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{{i18n "admin.groups.delete_with_messages_confirm" count=@model.message_count}}
|
|
@ -0,0 +1,32 @@
|
|||
{{i18n "user.second_factor.delete_confirm_header"}}
|
||||
|
||||
<ul>
|
||||
{{#each @model.totps as |totp|}}
|
||||
<li>{{totp.name}}</li>
|
||||
{{/each}}
|
||||
|
||||
{{#each @model.security_keys as |sk|}}
|
||||
<li>{{sk.name}}</li>
|
||||
{{/each}}
|
||||
|
||||
{{#if this.currentUser.second_factor_backup_enabled}}
|
||||
<li>{{i18n "user.second_factor_backup.title"}}</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
{{html-safe
|
||||
(i18n
|
||||
"user.second_factor.delete_confirm_instruction"
|
||||
confirm=this.disabledString
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
|
||||
<TextField
|
||||
@value={{this.confirmPhraseInput}}
|
||||
{{on "input" this.onConfirmPhraseInput}}
|
||||
@id="confirm-phrase"
|
||||
@autocorrect="off"
|
||||
@autocapitalize="off"
|
||||
/>
|
|
@ -0,0 +1,22 @@
|
|||
import Component from "@glimmer/component";
|
||||
import I18n from "I18n";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
|
||||
export default class SecondFactorConfirmPhrase extends Component {
|
||||
@service dialog;
|
||||
@service currentUser;
|
||||
|
||||
@tracked confirmPhraseInput = "";
|
||||
disabledString = I18n.t("user.second_factor.disable");
|
||||
|
||||
@action
|
||||
onConfirmPhraseInput() {
|
||||
if (this.confirmPhraseInput === this.disabledString) {
|
||||
this.dialog.set("confirmButtonDisabled", false);
|
||||
} else {
|
||||
this.dialog.set("confirmButtonDisabled", true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import I18n from "I18n";
|
|||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { capitalize } from "@ember/string";
|
||||
import { inject as service } from "@ember/service";
|
||||
import GroupDeleteDialog from "discourse/components/dialog-messages/group-delete";
|
||||
|
||||
const Tab = EmberObject.extend({
|
||||
init() {
|
||||
|
@ -138,17 +139,16 @@ export default Controller.extend({
|
|||
|
||||
const model = this.model;
|
||||
const title = I18n.t("admin.groups.delete_confirm");
|
||||
let message = null;
|
||||
let bodyComponent = null;
|
||||
|
||||
if (model.has_messages && model.message_count > 0) {
|
||||
message = I18n.t("admin.groups.delete_with_messages_confirm", {
|
||||
count: model.message_count,
|
||||
});
|
||||
bodyComponent = GroupDeleteDialog;
|
||||
}
|
||||
|
||||
this.dialog.deleteConfirm({
|
||||
title,
|
||||
message,
|
||||
bodyComponent,
|
||||
bodyComponentModel: model,
|
||||
didConfirm: () => {
|
||||
model
|
||||
.destroy()
|
||||
|
|
|
@ -10,7 +10,7 @@ import { findAll } from "discourse/models/login-method";
|
|||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import SecondFactorConfirmPhrase from "discourse/components/dialog-messages/second-factor-confirm-phrase";
|
||||
|
||||
export default Controller.extend(CanCheckEmails, {
|
||||
dialog: service(),
|
||||
|
@ -114,29 +114,6 @@ export default Controller.extend(CanCheckEmails, {
|
|||
.finally(() => this.set("resetPasswordLoading", false));
|
||||
},
|
||||
|
||||
disableAllMessage() {
|
||||
let templateElements = [I18n.t("user.second_factor.delete_confirm_header")];
|
||||
templateElements.push("<ul>");
|
||||
this.totps.forEach((totp) => {
|
||||
templateElements.push(`<li>${totp.name}</li>`);
|
||||
});
|
||||
this.security_keys.forEach((key) => {
|
||||
templateElements.push(`<li>${key.name}</li>`);
|
||||
});
|
||||
if (this.currentUser.second_factor_backup_enabled) {
|
||||
templateElements.push(
|
||||
`<li>${I18n.t("user.second_factor_backup.title")}</li>`
|
||||
);
|
||||
}
|
||||
templateElements.push("</ul>");
|
||||
templateElements.push(
|
||||
I18n.t("user.second_factor.delete_confirm_instruction", {
|
||||
confirm: I18n.t("user.second_factor.disable"),
|
||||
})
|
||||
);
|
||||
return htmlSafe(templateElements.join(""));
|
||||
},
|
||||
|
||||
actions: {
|
||||
confirmPassword() {
|
||||
if (!this.password) {
|
||||
|
@ -154,9 +131,13 @@ export default Controller.extend(CanCheckEmails, {
|
|||
|
||||
this.dialog.deleteConfirm({
|
||||
title: I18n.t("user.second_factor.disable_confirm"),
|
||||
message: this.disableAllMessage(),
|
||||
bodyComponent: SecondFactorConfirmPhrase,
|
||||
bodyComponentModel: {
|
||||
totps: this.totps,
|
||||
security_keys: this.security_keys,
|
||||
},
|
||||
confirmButtonLabel: "user.second_factor.disable",
|
||||
confirmPhrase: I18n.t("user.second_factor.disable"),
|
||||
confirmButtonDisabled: true,
|
||||
confirmButtonIcon: "ban",
|
||||
cancelButtonClass: "btn-flat",
|
||||
didConfirm: () => {
|
||||
|
|
|
@ -10,6 +10,8 @@ import {
|
|||
} from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { query } from "discourse/tests/helpers/qunit-helpers";
|
||||
import GroupDeleteDialogMessage from "discourse/components/dialog-messages/group-delete";
|
||||
import SecondFactorConfirmPhrase from "discourse/components/dialog-messages/second-factor-confirm-phrase";
|
||||
|
||||
module("Integration | Component | dialog-holder", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
@ -394,17 +396,41 @@ module("Integration | Component | dialog-holder", function (hooks) {
|
|||
".btn-primary element is not present in the dialog"
|
||||
);
|
||||
});
|
||||
test("delete confirm with confirmation phase", async function (assert) {
|
||||
|
||||
test("delete confirm with confirmation phrase component", async function (assert) {
|
||||
await render(hbs`<DialogHolder />`);
|
||||
|
||||
this.dialog.deleteConfirm({
|
||||
message: "A delete confirm message",
|
||||
confirmPhrase: "test",
|
||||
bodyComponent: SecondFactorConfirmPhrase,
|
||||
confirmButtonDisabled: true,
|
||||
});
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(query(".btn-danger").disabled, true);
|
||||
await fillIn("#confirm-phrase", "test");
|
||||
await fillIn("#confirm-phrase", "Disa");
|
||||
assert.strictEqual(query(".btn-danger").disabled, true);
|
||||
await fillIn("#confirm-phrase", "Disable");
|
||||
assert.strictEqual(query(".btn-danger").disabled, false);
|
||||
});
|
||||
|
||||
test("delete confirm with a component and model", async function (assert) {
|
||||
await render(hbs`<DialogHolder />`);
|
||||
const message_count = 5;
|
||||
|
||||
this.dialog.deleteConfirm({
|
||||
bodyComponent: GroupDeleteDialogMessage,
|
||||
bodyComponentModel: {
|
||||
message_count,
|
||||
},
|
||||
});
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
I18n.t("admin.groups.delete_with_messages_confirm", {
|
||||
count: message_count,
|
||||
}),
|
||||
"correct message is shown in dialog"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue