mirror of
https://github.com/discourse/discourse.git
synced 2025-02-17 07:52:45 +08:00
DEV: Add A11Y-friendly dialog addon (#18028)
This adds a new framework for accessible dialogs that will eventually replace bootbox. Under the hood, it uses the a11y-dialog package and an in-repo Ember addon. See PR for usage details.
This commit is contained in:
parent
c3a93597c1
commit
4116bce902
|
@ -1,11 +1,15 @@
|
|||
import Controller from "@ember/controller";
|
||||
import I18n from "I18n";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import bootbox from "bootbox";
|
||||
import { empty } from "@ember/object/computed";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
|
||||
export default Controller.extend({
|
||||
dialog: service(),
|
||||
|
||||
/**
|
||||
Is the "send test email" button disabled?
|
||||
|
||||
|
@ -44,13 +48,17 @@ export default Controller.extend({
|
|||
)
|
||||
.catch((e) => {
|
||||
if (e.jqXHR.responseJSON?.errors) {
|
||||
bootbox.alert(
|
||||
I18n.t("admin.email.error", {
|
||||
server_error: e.jqXHR.responseJSON.errors[0],
|
||||
})
|
||||
);
|
||||
this.dialog.alert({
|
||||
message: htmlSafe(
|
||||
I18n.t("admin.email.error", {
|
||||
server_error: escapeExpression(
|
||||
e.jqXHR.responseJSON.errors[0]
|
||||
),
|
||||
})
|
||||
),
|
||||
});
|
||||
} else {
|
||||
bootbox.alert(I18n.t("admin.email.test_error"));
|
||||
this.dialog.alert({ message: I18n.t("admin.email.test_error") });
|
||||
}
|
||||
})
|
||||
.finally(() => this.set("sendingEmail", false));
|
||||
|
|
|
@ -2,12 +2,13 @@ import EmberObject, { action, computed } from "@ember/object";
|
|||
import Controller from "@ember/controller";
|
||||
import I18n from "I18n";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import bootbox from "bootbox";
|
||||
import { sort } from "@ember/object/computed";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
const ALL_FILTER = "all";
|
||||
|
||||
export default Controller.extend({
|
||||
dialog: service(),
|
||||
filter: null,
|
||||
sorting: null,
|
||||
|
||||
|
@ -72,19 +73,17 @@ export default Controller.extend({
|
|||
|
||||
@action
|
||||
destroyEmoji(emoji) {
|
||||
return bootbox.confirm(
|
||||
I18n.t("admin.emoji.delete_confirm", { name: emoji.get("name") }),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
(destroy) => {
|
||||
if (destroy) {
|
||||
return ajax("/admin/customize/emojis/" + emoji.get("name"), {
|
||||
type: "DELETE",
|
||||
}).then(() => {
|
||||
this.model.removeObject(emoji);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.emoji.delete_confirm", {
|
||||
name: emoji.get("name"),
|
||||
}),
|
||||
didConfirm: () => {
|
||||
return ajax("/admin/customize/emojis/" + emoji.get("name"), {
|
||||
type: "DELETE",
|
||||
}).then(() => {
|
||||
this.model.removeObject(emoji);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import Controller from "@ember/controller";
|
||||
import I18n from "I18n";
|
||||
import bootbox from "bootbox";
|
||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default Controller.extend(bufferedProperty("siteText"), {
|
||||
dialog: service(),
|
||||
saved: false,
|
||||
queryParams: ["locale"],
|
||||
|
||||
|
@ -14,35 +16,36 @@ export default Controller.extend(bufferedProperty("siteText"), {
|
|||
return this.siteText.value === value;
|
||||
},
|
||||
|
||||
actions: {
|
||||
saveChanges() {
|
||||
const attrs = this.buffered.getProperties("value");
|
||||
attrs.locale = this.locale;
|
||||
@action
|
||||
saveChanges() {
|
||||
const attrs = this.buffered.getProperties("value");
|
||||
attrs.locale = this.locale;
|
||||
|
||||
this.siteText
|
||||
.save(attrs)
|
||||
.then(() => {
|
||||
this.commitBuffer();
|
||||
this.set("saved", true);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
this.siteText
|
||||
.save(attrs)
|
||||
.then(() => {
|
||||
this.commitBuffer();
|
||||
this.set("saved", true);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
revertChanges() {
|
||||
this.set("saved", false);
|
||||
@action
|
||||
revertChanges() {
|
||||
this.set("saved", false);
|
||||
|
||||
bootbox.confirm(I18n.t("admin.site_text.revert_confirm"), (result) => {
|
||||
if (result) {
|
||||
this.siteText
|
||||
.revert(this.locale)
|
||||
.then((props) => {
|
||||
const buffered = this.buffered;
|
||||
buffered.setProperties(props);
|
||||
this.commitBuffer();
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
});
|
||||
},
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.site_text.revert_confirm"),
|
||||
didConfirm: () => {
|
||||
this.siteText
|
||||
.revert(this.locale)
|
||||
.then((props) => {
|
||||
const buffered = this.buffered;
|
||||
buffered.setProperties(props);
|
||||
this.commitBuffer();
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,17 +6,16 @@ import CanCheckEmails from "discourse/mixins/can-check-emails";
|
|||
import Controller from "@ember/controller";
|
||||
import I18n from "I18n";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import bootbox from "bootbox";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import { extractError, popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { inject as service } from "@ember/service";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
export default Controller.extend(CanCheckEmails, {
|
||||
router: service(),
|
||||
dialog: service(),
|
||||
adminTools: service(),
|
||||
originalPrimaryGroupId: null,
|
||||
customGroupIdsBuffer: null,
|
||||
|
@ -130,7 +129,7 @@ export default Controller.extend(CanCheckEmails, {
|
|||
groupAdded(added) {
|
||||
this.model
|
||||
.groupAdded(added)
|
||||
.catch(() => bootbox.alert(I18n.t("generic_error")));
|
||||
.catch(() => this.dialog.alert(I18n.t("generic_error")));
|
||||
},
|
||||
|
||||
groupRemoved(groupId) {
|
||||
|
@ -141,7 +140,7 @@ export default Controller.extend(CanCheckEmails, {
|
|||
this.set("originalPrimaryGroupId", null);
|
||||
}
|
||||
})
|
||||
.catch(() => bootbox.alert(I18n.t("generic_error")));
|
||||
.catch(() => this.dialog.alert(I18n.t("generic_error")));
|
||||
},
|
||||
|
||||
@discourseComputed("ssoLastPayload")
|
||||
|
@ -156,16 +155,16 @@ export default Controller.extend(CanCheckEmails, {
|
|||
.then(() => DiscourseURL.redirectTo("/"))
|
||||
.catch((e) => {
|
||||
if (e.status === 404) {
|
||||
bootbox.alert(I18n.t("admin.impersonate.not_found"));
|
||||
this.dialog.alert(I18n.t("admin.impersonate.not_found"));
|
||||
} else {
|
||||
bootbox.alert(I18n.t("admin.impersonate.invalid"));
|
||||
this.dialog.alert(I18n.t("admin.impersonate.invalid"));
|
||||
}
|
||||
});
|
||||
},
|
||||
logOut() {
|
||||
return this.model
|
||||
.logOut()
|
||||
.then(() => bootbox.alert(I18n.t("admin.user.logged_out")));
|
||||
.then(() => this.dialog.alert(I18n.t("admin.user.logged_out")));
|
||||
},
|
||||
resetBounceScore() {
|
||||
return this.model.resetBounceScore();
|
||||
|
@ -188,13 +187,15 @@ export default Controller.extend(CanCheckEmails, {
|
|||
const error = I18n.t("admin.user.deactivate_failed", {
|
||||
error: this._formatError(e),
|
||||
});
|
||||
bootbox.alert(error);
|
||||
this.dialog.alert(error);
|
||||
});
|
||||
},
|
||||
sendActivationEmail() {
|
||||
return this.model
|
||||
.sendActivationEmail()
|
||||
.then(() => bootbox.alert(I18n.t("admin.user.activation_email_sent")))
|
||||
.then(() =>
|
||||
this.dialog.alert(I18n.t("admin.user.activation_email_sent"))
|
||||
)
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
activate() {
|
||||
|
@ -210,7 +211,7 @@ export default Controller.extend(CanCheckEmails, {
|
|||
const error = I18n.t("admin.user.activate_failed", {
|
||||
error: this._formatError(e),
|
||||
});
|
||||
bootbox.alert(error);
|
||||
this.dialog.alert(error);
|
||||
});
|
||||
},
|
||||
revokeAdmin() {
|
||||
|
@ -221,7 +222,7 @@ export default Controller.extend(CanCheckEmails, {
|
|||
.grantAdmin()
|
||||
.then((result) => {
|
||||
if (result.email_confirmation_required) {
|
||||
bootbox.alert(I18n.t("admin.user.grant_admin_confirm"));
|
||||
this.dialog.alert(I18n.t("admin.user.grant_admin_confirm"));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
@ -255,7 +256,7 @@ export default Controller.extend(CanCheckEmails, {
|
|||
I18n.t("admin.user.trust_level_change_failed", {
|
||||
error: this._formatError(e),
|
||||
});
|
||||
bootbox.alert(error);
|
||||
this.dialog.alert(error);
|
||||
});
|
||||
},
|
||||
restoreTrustLevel() {
|
||||
|
@ -275,7 +276,7 @@ export default Controller.extend(CanCheckEmails, {
|
|||
I18n.t("admin.user.trust_level_change_failed", {
|
||||
error: this._formatError(e),
|
||||
});
|
||||
bootbox.alert(error);
|
||||
this.dialog.alert(error);
|
||||
});
|
||||
},
|
||||
unsilence() {
|
||||
|
@ -287,7 +288,6 @@ export default Controller.extend(CanCheckEmails, {
|
|||
|
||||
anonymize() {
|
||||
const user = this.model;
|
||||
const message = I18n.t("admin.user.anonymize_confirm");
|
||||
|
||||
const performAnonymize = () => {
|
||||
this.model
|
||||
|
@ -302,31 +302,32 @@ export default Controller.extend(CanCheckEmails, {
|
|||
document.location = getURL("/admin/users/list/active");
|
||||
}
|
||||
} else {
|
||||
bootbox.alert(I18n.t("admin.user.anonymize_failed"));
|
||||
this.dialog.alert(I18n.t("admin.user.anonymize_failed"));
|
||||
if (data.user) {
|
||||
user.setProperties(data.user);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => bootbox.alert(I18n.t("admin.user.anonymize_failed")));
|
||||
.catch(() =>
|
||||
this.dialog.alert(I18n.t("admin.user.anonymize_failed"))
|
||||
);
|
||||
};
|
||||
const buttons = [
|
||||
{
|
||||
label: I18n.t("composer.cancel"),
|
||||
class: "cancel",
|
||||
link: true,
|
||||
},
|
||||
{
|
||||
label: I18n.t("admin.user.anonymize_yes"),
|
||||
class: "btn btn-danger",
|
||||
icon: iconHTML("exclamation-triangle"),
|
||||
callback: () => {
|
||||
performAnonymize();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
bootbox.dialog(message, buttons, { classes: "delete-user-modal" });
|
||||
this.dialog.alert({
|
||||
message: I18n.t("admin.user.anonymize_confirm"),
|
||||
class: "delete-user-modal",
|
||||
buttons: [
|
||||
{
|
||||
icon: "exclamation-triangle",
|
||||
label: I18n.t("admin.user.anonymize_yes"),
|
||||
class: "btn-danger",
|
||||
action: () => performAnonymize(),
|
||||
},
|
||||
{
|
||||
label: I18n.t("composer.cancel"),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
|
||||
disableSecondFactor() {
|
||||
|
@ -345,11 +346,10 @@ export default Controller.extend(CanCheckEmails, {
|
|||
destroy() {
|
||||
const postCount = this.get("model.post_count");
|
||||
const maxPostCount = this.siteSettings.delete_all_posts_max;
|
||||
const message = I18n.t("admin.user.delete_confirm");
|
||||
const location = document.location.pathname;
|
||||
|
||||
const performDestroy = (block) => {
|
||||
bootbox.dialog(I18n.t("admin.user.deleting_user"));
|
||||
this.dialog.notice(I18n.t("admin.user.deleting_user"));
|
||||
let formData = { context: location };
|
||||
if (block) {
|
||||
formData["block_email"] = true;
|
||||
|
@ -369,38 +369,38 @@ export default Controller.extend(CanCheckEmails, {
|
|||
document.location = getURL("/admin/users/list/active");
|
||||
}
|
||||
} else {
|
||||
bootbox.alert(I18n.t("admin.user.delete_failed"));
|
||||
this.dialog.alert(I18n.t("admin.user.delete_failed"));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
bootbox.alert(I18n.t("admin.user.delete_failed"));
|
||||
this.dialog.alert(I18n.t("admin.user.delete_failed"));
|
||||
});
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: I18n.t("composer.cancel"),
|
||||
class: "btn",
|
||||
link: true,
|
||||
},
|
||||
{
|
||||
icon: iconHTML("exclamation-triangle"),
|
||||
label: I18n.t("admin.user.delete_and_block"),
|
||||
class: "btn btn-danger",
|
||||
callback: () => {
|
||||
performDestroy(true);
|
||||
this.dialog.alert({
|
||||
message: I18n.t("admin.user.delete_confirm"),
|
||||
class: "delete-user-modal",
|
||||
buttons: [
|
||||
{
|
||||
label: I18n.t("admin.user.delete_dont_block"),
|
||||
class: "btn-primary",
|
||||
action: () => {
|
||||
return performDestroy(true);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: I18n.t("admin.user.delete_dont_block"),
|
||||
class: "btn btn-primary",
|
||||
callback: () => {
|
||||
performDestroy(false);
|
||||
{
|
||||
icon: "exclamation-triangle",
|
||||
label: I18n.t("admin.user.delete_and_block"),
|
||||
class: "btn-danger",
|
||||
action: () => {
|
||||
return performDestroy(false);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
bootbox.dialog(message, buttons, { classes: "delete-user-modal" });
|
||||
{
|
||||
label: I18n.t("composer.cancel"),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
|
||||
promptTargetUser() {
|
||||
|
@ -439,12 +439,12 @@ export default Controller.extend(CanCheckEmails, {
|
|||
model: this.model,
|
||||
});
|
||||
} else {
|
||||
bootbox.alert(I18n.t("admin.user.merge_failed"));
|
||||
this.dialog.alert(I18n.t("admin.user.merge_failed"));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
AdminUser.find(user.id).then((u) => user.setProperties(u));
|
||||
bootbox.alert(I18n.t("admin.user.merge_failed"));
|
||||
this.dialog.alert(I18n.t("admin.user.merge_failed"));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -532,7 +532,7 @@ export default Controller.extend(CanCheckEmails, {
|
|||
data: { primary_group_id: primaryGroupId },
|
||||
})
|
||||
.then(() => this.set("originalPrimaryGroupId", primaryGroupId))
|
||||
.catch(() => bootbox.alert(I18n.t("generic_error")));
|
||||
.catch(() => this.dialog.alert(I18n.t("generic_error")));
|
||||
},
|
||||
|
||||
resetPrimaryGroup() {
|
||||
|
@ -540,16 +540,10 @@ export default Controller.extend(CanCheckEmails, {
|
|||
},
|
||||
|
||||
deleteSSORecord() {
|
||||
return bootbox.confirm(
|
||||
I18n.t("admin.user.discourse_connect.confirm_delete"),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.model.deleteSSORecord();
|
||||
}
|
||||
}
|
||||
);
|
||||
return this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.user.discourse_connect.confirm_delete"),
|
||||
didConfirm: () => this.model.deleteSSORecord(),
|
||||
});
|
||||
},
|
||||
|
||||
checkSsoEmail() {
|
||||
|
@ -607,7 +601,7 @@ export default Controller.extend(CanCheckEmails, {
|
|||
let error;
|
||||
AdminUser.find(user.get("id")).then((u) => user.setProperties(u));
|
||||
error = extractError(e) || I18n.t("admin.user.delete_posts_failed");
|
||||
bootbox.alert(error);
|
||||
this.dialog.alert(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import bootbox from "bootbox";
|
||||
import logout from "discourse/lib/logout";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { setLogoffCallback } from "discourse/lib/ajax";
|
||||
|
@ -14,6 +13,7 @@ export function addPluginDocumentTitleCounter(counterFunction) {
|
|||
export default Component.extend({
|
||||
tagName: "",
|
||||
documentTitle: service(),
|
||||
dialog: service(),
|
||||
_showingLogout: false,
|
||||
|
||||
didInsertElement() {
|
||||
|
@ -74,13 +74,12 @@ export default Component.extend({
|
|||
|
||||
this._showingLogout = true;
|
||||
this.messageBus.stop();
|
||||
bootbox.dialog(
|
||||
I18n.t("logout"),
|
||||
{ label: I18n.t("refresh"), callback: logout },
|
||||
{
|
||||
onEscape: () => logout(),
|
||||
backdrop: "static",
|
||||
}
|
||||
);
|
||||
|
||||
this.dialog.alert({
|
||||
message: I18n.t("logout"),
|
||||
confirmButtonLabel: "refresh",
|
||||
didConfirm: () => logout(),
|
||||
didCancel: () => logout(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import I18n from "I18n";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import bootbox from "bootbox";
|
||||
import { exportUserArchive } from "discourse/lib/export-csv";
|
||||
import { inject as service } from "@ember/service";
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Controller.extend({
|
||||
dialog: service(),
|
||||
application: controller(),
|
||||
user: controller(),
|
||||
userActionType: null,
|
||||
|
@ -44,12 +45,10 @@ export default Controller.extend({
|
|||
|
||||
actions: {
|
||||
exportUserArchive() {
|
||||
bootbox.confirm(
|
||||
I18n.t("user.download_archive.confirm"),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
(confirmed) => (confirmed ? exportUserArchive() : null)
|
||||
);
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("user.download_archive.confirm"),
|
||||
didConfirm: () => exportUserArchive(),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import I18n from "I18n";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import bootbox from "bootbox";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
|
||||
function exportEntityByType(type, entity, args) {
|
||||
return ajax("/export_csv/export_entity.json", {
|
||||
|
@ -11,9 +11,10 @@ function exportEntityByType(type, entity, args) {
|
|||
}
|
||||
|
||||
export function exportUserArchive() {
|
||||
const dialog = getOwner(this).lookup("service:dialog");
|
||||
return exportEntityByType("user", "user_archive")
|
||||
.then(function () {
|
||||
bootbox.alert(I18n.t("user.download_archive.success"));
|
||||
dialog.alert(I18n.t("user.download_archive.success"));
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
<PluginOutlet @name="below-footer" @connectorTagName="div" @args={{hash showFooter=this.showFooter}} />
|
||||
|
||||
{{outlet "modal"}}
|
||||
<DialogHolder />
|
||||
<TopicEntrance />
|
||||
{{outlet "composer"}}
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<div id="dialog-holder" class="dialog-container {{this.dialog.class}}" aria-labelledby={{this.dialog.titleElementId}} aria-hidden="true">
|
||||
<div class="dialog-overlay" data-a11y-dialog-hide></div>
|
||||
|
||||
{{#if this.dialog.type}}
|
||||
<div class="dialog-content" role="document">
|
||||
{{#if this.dialog.title}}
|
||||
<div class="dialog-header">
|
||||
<h3 id={{this.dialog.titleElementId}}>{{this.dialog.title}}</h3>
|
||||
<DButton @icon="times" @action={{action this.dialog.cancel}} @class="btn-flat dialog-close close" @title="modal.close" />
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.dialog.message}}
|
||||
<div class="dialog-body">
|
||||
{{this.dialog.message}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (notEq this.dialog.type "notice")}}
|
||||
<div class="dialog-footer">
|
||||
{{#each this.dialog.buttons as |button|}}
|
||||
<DButton @icon={{button.icon}} @class={{button.class}} @action={{action "handleButtonAction" button}} @translatedLabel={{button.label}} />
|
||||
{{else}}
|
||||
<DButton @class="btn-primary" @action={{this.dialog.didConfirmWrapped}} @icon={{this.dialog.confirmButtonIcon}} @label={{this.dialog.confirmButtonLabel}} />
|
||||
{{#if this.dialog.shouldDisplayCancel}}
|
||||
<DButton @class="btn-default" @action={{this.dialog.cancel}} @label={{this.dialog.cancelButtonLabel}} />
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -0,0 +1,16 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class DialogHolder extends Component {
|
||||
@service dialog;
|
||||
|
||||
@action
|
||||
async handleButtonAction(btn) {
|
||||
if (btn.action && typeof btn.action === "function") {
|
||||
await btn.action();
|
||||
}
|
||||
|
||||
this.dialog.cancel();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
import Service from "@ember/service";
|
||||
import A11yDialog from "a11y-dialog";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Service.extend({
|
||||
message: null,
|
||||
type: null,
|
||||
dialogInstance: null,
|
||||
|
||||
title: null,
|
||||
titleElementId: null,
|
||||
|
||||
confirmButtonIcon: null,
|
||||
confirmButtonLabel: null,
|
||||
cancelButtonLabel: null,
|
||||
shouldDisplayCancel: null,
|
||||
|
||||
didConfirm: null,
|
||||
didCancel: null,
|
||||
buttons: null,
|
||||
class: null,
|
||||
_confirming: false,
|
||||
|
||||
dialog(params) {
|
||||
const {
|
||||
message,
|
||||
type,
|
||||
title,
|
||||
|
||||
confirmButtonIcon,
|
||||
confirmButtonLabel = "ok_value",
|
||||
cancelButtonLabel = "cancel_value",
|
||||
shouldDisplayCancel,
|
||||
|
||||
didConfirm,
|
||||
didCancel,
|
||||
buttons,
|
||||
} = params;
|
||||
|
||||
const element = document.getElementById("dialog-holder");
|
||||
|
||||
this.setProperties({
|
||||
message,
|
||||
type,
|
||||
dialogInstance: new A11yDialog(element),
|
||||
|
||||
title,
|
||||
titleElementId: title !== null ? "dialog-title" : null,
|
||||
|
||||
confirmButtonLabel,
|
||||
confirmButtonIcon,
|
||||
cancelButtonLabel,
|
||||
shouldDisplayCancel,
|
||||
|
||||
didConfirm,
|
||||
didCancel,
|
||||
buttons,
|
||||
class: params.class,
|
||||
});
|
||||
|
||||
this.dialogInstance.show();
|
||||
|
||||
this.dialogInstance.on("hide", () => {
|
||||
if (!this._confirming && this.didCancel) {
|
||||
this.didCancel();
|
||||
}
|
||||
|
||||
this.reset();
|
||||
});
|
||||
},
|
||||
|
||||
alert(params) {
|
||||
// support string param for easier porting of bootbox.alert
|
||||
if (typeof params === "string") {
|
||||
return this.dialog({
|
||||
message: params,
|
||||
type: "alert",
|
||||
});
|
||||
}
|
||||
|
||||
return this.dialog({
|
||||
...params,
|
||||
type: "alert",
|
||||
});
|
||||
},
|
||||
|
||||
confirm(params) {
|
||||
return this.dialog({
|
||||
...params,
|
||||
shouldDisplayCancel: true,
|
||||
buttons: null,
|
||||
type: "confirm",
|
||||
});
|
||||
},
|
||||
|
||||
notice(message) {
|
||||
return this.dialog({
|
||||
message,
|
||||
type: "notice",
|
||||
});
|
||||
},
|
||||
|
||||
yesNoConfirm(params) {
|
||||
return this.confirm({
|
||||
...params,
|
||||
confirmButtonLabel: "yes_value",
|
||||
cancelButtonLabel: "no_value",
|
||||
});
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.setProperties({
|
||||
message: null,
|
||||
type: null,
|
||||
dialogInstance: null,
|
||||
|
||||
title: null,
|
||||
titleElementId: null,
|
||||
|
||||
confirmButtonLabel: null,
|
||||
confirmButtonIcon: null,
|
||||
cancelButtonLabel: null,
|
||||
shouldDisplayCancel: null,
|
||||
|
||||
didConfirm: null,
|
||||
didCancel: null,
|
||||
buttons: null,
|
||||
class: null,
|
||||
|
||||
_confirming: false,
|
||||
});
|
||||
},
|
||||
|
||||
willDestroy() {
|
||||
this.dialogInstance?.destroy();
|
||||
this.reset();
|
||||
},
|
||||
|
||||
@bind
|
||||
didConfirmWrapped() {
|
||||
if (this.didConfirm) {
|
||||
this.didConfirm();
|
||||
}
|
||||
this._confirming = true;
|
||||
this.dialogInstance.hide();
|
||||
},
|
||||
|
||||
@bind
|
||||
cancel() {
|
||||
this.dialogInstance.hide();
|
||||
},
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
export { default } from "dialog-holder/components/dialog-holder";
|
|
@ -0,0 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
name: require("./package").name,
|
||||
|
||||
isDevelopingAddon() {
|
||||
return true;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "dialog-holder",
|
||||
"keywords": [
|
||||
"ember-addon"
|
||||
],
|
||||
"dependencies": {
|
||||
"a11y-dialog": "7.5.0",
|
||||
"ember-auto-import": "^2.4.2",
|
||||
"ember-cli-babel": "^7.26.10",
|
||||
"ember-cli-htmlbars": "^6.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.73.0"
|
||||
}
|
||||
}
|
3659
app/assets/javascripts/discourse/lib/dialog-holder/yarn.lock
Normal file
3659
app/assets/javascripts/discourse/lib/dialog-holder/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -21,6 +21,7 @@
|
|||
"@discourse/itsatrap": "^2.0.10",
|
||||
"@ember/jquery": "^2.0.0",
|
||||
"@ember/optional-features": "^2.0.0",
|
||||
"@ember/render-modifiers": "^2.0.4",
|
||||
"@ember/test-helpers": "^2.8.1",
|
||||
"@glimmer/component": "^1.1.2",
|
||||
"@glimmer/syntax": "^0.84.2",
|
||||
|
@ -32,6 +33,7 @@
|
|||
"@uppy/drop-target": "^1.1.3",
|
||||
"@uppy/utils": "^4.1.0",
|
||||
"@uppy/xhr-upload": "^2.1.2",
|
||||
"a11y-dialog": "7.5.0",
|
||||
"admin": "^1.0.0",
|
||||
"discourse-plugins": "^1.0.0",
|
||||
"bootstrap": "3.4.1",
|
||||
|
@ -58,13 +60,12 @@
|
|||
"ember-exam": "^7.0.1",
|
||||
"ember-export-application-global": "^2.0.1",
|
||||
"ember-load-initializers": "^2.1.1",
|
||||
"ember-modifier": "^3.2.7",
|
||||
"ember-on-resize-modifier": "^1.1.0",
|
||||
"ember-qunit": "^5.1.5",
|
||||
"ember-rfc176-data": "^0.3.17",
|
||||
"ember-source": "~3.28.8",
|
||||
"ember-test-selectors": "^6.0.0",
|
||||
"ember-modifier": "^3.2.7",
|
||||
"ember-on-resize-modifier": "^1.1.0",
|
||||
"@ember/render-modifiers": "^2.0.4",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-qunit": "^6.2.0",
|
||||
"html-entities": "^2.3.3",
|
||||
|
@ -96,6 +97,7 @@
|
|||
},
|
||||
"ember-addon": {
|
||||
"paths": [
|
||||
"lib/dialog-holder",
|
||||
"lib/bootstrap-json"
|
||||
]
|
||||
},
|
||||
|
|
|
@ -51,7 +51,11 @@ acceptance("Admin - Emails", function (needs) {
|
|||
await fillIn(".admin-controls input", "test@example.com");
|
||||
await click(".btn-primary");
|
||||
|
||||
assert.ok(query(".bootbox.modal").innerText.includes("some error"));
|
||||
await click(".bootbox .btn-primary");
|
||||
assert.ok(query("#dialog-holder").innerText.includes("some error"));
|
||||
assert.ok(
|
||||
query("#dialog-holder .dialog-body b"),
|
||||
"Error message can contain html"
|
||||
);
|
||||
await click(".dialog-overlay");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -51,9 +51,9 @@ acceptance("Admin - Site Texts", function (needs) {
|
|||
// Revert the changes
|
||||
await click(".revert-site-text");
|
||||
|
||||
assert.ok(exists(".bootbox.modal"));
|
||||
assert.ok(exists("#dialog-holder .dialog-content"));
|
||||
|
||||
await click(".bootbox.modal .btn-primary");
|
||||
await click("#dialog-holder .btn-primary");
|
||||
|
||||
assert.ok(!exists(".saved"));
|
||||
assert.ok(!exists(".revert-site-text"));
|
||||
|
|
|
@ -195,12 +195,13 @@ acceptance("Admin - User Index", function (needs) {
|
|||
test("grant admin - shows the confirmation bootbox", async function (assert) {
|
||||
await visit("/admin/users/3/user1");
|
||||
await click(".grant-admin");
|
||||
assert.ok(exists(".bootbox"));
|
||||
assert.ok(exists(".dialog-content"));
|
||||
assert.strictEqual(
|
||||
I18n.t("admin.user.grant_admin_confirm"),
|
||||
query(".modal-body").textContent.trim()
|
||||
query(".dialog-body").textContent.trim()
|
||||
);
|
||||
await click(".bootbox .btn-primary");
|
||||
|
||||
await click(".dialog-footer .btn-primary");
|
||||
});
|
||||
|
||||
test("grant admin - redirects to the 2fa page", async function (assert) {
|
||||
|
|
|
@ -317,7 +317,10 @@ acceptance("Composer", function (needs) {
|
|||
await fillIn(".d-editor-input", "this is the content of the first reply");
|
||||
|
||||
await visit("/t/this-is-a-test-topic/9");
|
||||
assert.strictEqual(currentURL(), "/t/this-is-a-test-topic/9");
|
||||
assert.ok(
|
||||
currentURL().startsWith("/t/this-is-a-test-topic/9"),
|
||||
"moves to second topic"
|
||||
);
|
||||
await click("#topic-footer-buttons .btn.create");
|
||||
assert.ok(
|
||||
exists(".discard-draft-modal.modal"),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { acceptance, query } from "../helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { visit } from "@ember/test-helpers";
|
||||
import { click, visit } from "@ember/test-helpers";
|
||||
import I18n from "I18n";
|
||||
|
||||
acceptance("User Activity / Replies - empty state", function (needs) {
|
||||
|
@ -33,3 +33,39 @@ acceptance("User Activity / Replies - empty state", function (needs) {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("User Activity / Replies - Download All", function (needs) {
|
||||
const currentUser = "eviltrout";
|
||||
const anotherUser = "charlie";
|
||||
needs.user();
|
||||
needs.pretender((server, helper) => {
|
||||
server.post("/export_csv/export_entity.json", () => {
|
||||
return helper.response({});
|
||||
});
|
||||
});
|
||||
|
||||
test("Can see and trigger download for own data replies", async function (assert) {
|
||||
await visit(`/u/${currentUser}/activity`);
|
||||
|
||||
assert.ok(query(".user-additional-controls .btn"), "button exists");
|
||||
|
||||
await click(".user-additional-controls .btn");
|
||||
await click("#dialog-holder .btn-primary");
|
||||
|
||||
assert.equal(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
I18n.t("user.download_archive.success")
|
||||
);
|
||||
|
||||
await click("#dialog-holder .btn-primary");
|
||||
});
|
||||
|
||||
test("Cannot see 'Download All' button for another user", async function (assert) {
|
||||
await visit(`/u/${anotherUser}/activity`);
|
||||
|
||||
assert.notOk(
|
||||
query(".user-additional-controls .btn"),
|
||||
"download button is not present"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,367 @@
|
|||
import I18n from "I18n";
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { click, render, settled, triggerKeyEvent } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { query } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
module("Integration | Component | dialog-holder", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.dialog = this.container.lookup("service:dialog");
|
||||
});
|
||||
|
||||
test("basics", async function (assert) {
|
||||
await render(hbs`<DialogHolder />`);
|
||||
assert.ok(query("#dialog-holder"), "element is in DOM");
|
||||
assert.strictEqual(
|
||||
query("#dialog-holder").innerText.trim(),
|
||||
"",
|
||||
"dialog is empty by default"
|
||||
);
|
||||
|
||||
this.dialog.alert({
|
||||
message: "This is an error",
|
||||
});
|
||||
await settled();
|
||||
|
||||
assert.ok(
|
||||
query(".dialog-overlay").offsetWidth > 0,
|
||||
true,
|
||||
"overlay is visible"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
"This is an error",
|
||||
"dialog has error message"
|
||||
);
|
||||
|
||||
// dismiss by clicking on overlay
|
||||
await click(".dialog-overlay");
|
||||
|
||||
assert.ok(query("#dialog-holder"), "element is still in DOM");
|
||||
assert.strictEqual(
|
||||
query(".dialog-overlay").offsetWidth,
|
||||
0,
|
||||
"overlay is not visible"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query("#dialog-holder").innerText.trim(),
|
||||
"",
|
||||
"dialog is empty"
|
||||
);
|
||||
});
|
||||
|
||||
test("basics - dismiss using Esc", async function (assert) {
|
||||
let cancelCallbackCalled = false;
|
||||
await render(hbs`<DialogHolder />`);
|
||||
assert.ok(query("#dialog-holder"), "element is in DOM");
|
||||
assert.strictEqual(
|
||||
query("#dialog-holder").innerText.trim(),
|
||||
"",
|
||||
"dialog is empty by default"
|
||||
);
|
||||
|
||||
this.dialog.alert({
|
||||
message: "This is an error",
|
||||
didCancel: () => {
|
||||
cancelCallbackCalled = true;
|
||||
},
|
||||
});
|
||||
await settled();
|
||||
|
||||
assert.ok(
|
||||
query(".dialog-overlay").offsetWidth > 0,
|
||||
true,
|
||||
"overlay is visible"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
"This is an error",
|
||||
"dialog has error message"
|
||||
);
|
||||
|
||||
// dismiss by pressing Esc
|
||||
await triggerKeyEvent(document, "keydown", "Escape");
|
||||
|
||||
assert.ok(cancelCallbackCalled, "cancel callback called");
|
||||
assert.ok(query("#dialog-holder"), "element is still in DOM");
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-overlay").offsetWidth,
|
||||
0,
|
||||
"overlay is not visible"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
query("#dialog-holder").innerText.trim(),
|
||||
"",
|
||||
"dialog is empty"
|
||||
);
|
||||
});
|
||||
|
||||
test("alert with title", async function (assert) {
|
||||
await render(hbs`<DialogHolder />`);
|
||||
|
||||
this.dialog.alert({
|
||||
message: "This is a note.",
|
||||
title: "And this is a title",
|
||||
});
|
||||
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
query("#dialog-title").innerText.trim(),
|
||||
"And this is a title",
|
||||
"dialog has title"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query("#dialog-holder[aria-labelledby='dialog-title']"),
|
||||
"aria-labelledby is correctly set"
|
||||
);
|
||||
|
||||
assert.ok(query(".dialog-close"), "close button present");
|
||||
assert.ok(query("#dialog-holder"), "element is still in DOM");
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
"This is a note.",
|
||||
"dialog message is shown"
|
||||
);
|
||||
|
||||
await click(".dialog-close");
|
||||
|
||||
assert.ok(query("#dialog-holder"), "element is still in DOM");
|
||||
assert.strictEqual(
|
||||
query(".dialog-overlay").offsetWidth,
|
||||
0,
|
||||
"overlay is not visible"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query("#dialog-holder").innerText.trim(),
|
||||
"",
|
||||
"dialog is empty"
|
||||
);
|
||||
});
|
||||
|
||||
test("alert with a string parameter", async function (assert) {
|
||||
await render(hbs`<DialogHolder />`);
|
||||
|
||||
this.dialog.alert("An alert message");
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
"An alert message",
|
||||
"dialog message is shown"
|
||||
);
|
||||
});
|
||||
|
||||
test("confirm", async function (assert) {
|
||||
let confirmCallbackCalled = false;
|
||||
let cancelCallbackCalled = false;
|
||||
await render(hbs`<DialogHolder />`);
|
||||
|
||||
this.dialog.confirm({
|
||||
message: "A confirm message",
|
||||
didConfirm: () => {
|
||||
confirmCallbackCalled = true;
|
||||
},
|
||||
didCancel: () => {
|
||||
cancelCallbackCalled = true;
|
||||
},
|
||||
});
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
"A confirm message",
|
||||
"dialog message is shown"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-footer .btn-primary").innerText.trim(),
|
||||
I18n.t("ok_value"),
|
||||
"dialog primary button says Ok"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-footer .btn-default").innerText.trim(),
|
||||
I18n.t("cancel_value"),
|
||||
"dialog second button is present and says No"
|
||||
);
|
||||
|
||||
await click(".dialog-footer .btn-primary");
|
||||
|
||||
assert.ok(confirmCallbackCalled, "confirm callback called");
|
||||
assert.notOk(cancelCallbackCalled, "cancel callback NOT called");
|
||||
|
||||
assert.strictEqual(
|
||||
query("#dialog-holder").innerText.trim(),
|
||||
"",
|
||||
"dialog is empty"
|
||||
);
|
||||
});
|
||||
|
||||
test("cancel callback", async function (assert) {
|
||||
let confirmCallbackCalled = false;
|
||||
let cancelCallbackCalled = false;
|
||||
|
||||
await render(hbs`<DialogHolder />`);
|
||||
|
||||
this.dialog.confirm({
|
||||
message: "A confirm message",
|
||||
didConfirm: () => {
|
||||
confirmCallbackCalled = true;
|
||||
},
|
||||
didCancel: () => {
|
||||
cancelCallbackCalled = true;
|
||||
},
|
||||
});
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
"A confirm message",
|
||||
"dialog message is shown"
|
||||
);
|
||||
|
||||
await click(".dialog-footer .btn-default");
|
||||
assert.notOk(confirmCallbackCalled, "confirm callback NOT called");
|
||||
assert.ok(cancelCallbackCalled, "cancel callback called");
|
||||
|
||||
assert.strictEqual(
|
||||
query("#dialog-holder").innerText.trim(),
|
||||
"",
|
||||
"dialog has been dismissed"
|
||||
);
|
||||
});
|
||||
|
||||
test("yes/no confirm", async function (assert) {
|
||||
await render(hbs`<DialogHolder />`);
|
||||
|
||||
this.dialog.yesNoConfirm({ message: "A yes/no confirm message" });
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
"A yes/no confirm message",
|
||||
"dialog message is shown"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-footer .btn-primary").innerText.trim(),
|
||||
I18n.t("yes_value"),
|
||||
"dialog primary button says Yes"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-footer .btn-default").innerText.trim(),
|
||||
I18n.t("no_value"),
|
||||
"dialog second button is present and says No"
|
||||
);
|
||||
});
|
||||
|
||||
test("alert with custom buttons", async function (assert) {
|
||||
let customCallbackTriggered = false;
|
||||
await render(hbs`<DialogHolder />`);
|
||||
|
||||
this.dialog.alert({
|
||||
message: "An alert with custom buttons",
|
||||
buttons: [
|
||||
{
|
||||
icon: "cog",
|
||||
label: "Danger ahead",
|
||||
class: "btn-danger",
|
||||
action: () => {
|
||||
return new Promise((resolve) => {
|
||||
customCallbackTriggered = true;
|
||||
return resolve();
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
"An alert with custom buttons",
|
||||
"dialog message is shown"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-footer .btn-danger").innerText.trim(),
|
||||
"Danger ahead",
|
||||
"dialog custom button is present"
|
||||
);
|
||||
|
||||
assert.notOk(
|
||||
query(".dialog-footer .btn-primary"),
|
||||
"default confirm button is not present"
|
||||
);
|
||||
assert.notOk(
|
||||
query(".dialog-footer .btn-default"),
|
||||
"default cancel button is not present"
|
||||
);
|
||||
|
||||
await click(".dialog-footer .btn-danger");
|
||||
assert.ok(customCallbackTriggered, "custom action was triggered");
|
||||
|
||||
assert.strictEqual(
|
||||
query("#dialog-holder").innerText.trim(),
|
||||
"",
|
||||
"dialog has been dismissed"
|
||||
);
|
||||
});
|
||||
|
||||
test("alert with custom classes", async function (assert) {
|
||||
await render(hbs`<DialogHolder />`);
|
||||
|
||||
this.dialog.alert({
|
||||
message: "An alert with custom classes",
|
||||
class: "dialog-special dialog-super",
|
||||
});
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
"An alert with custom classes",
|
||||
"dialog message is shown"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query("#dialog-holder.dialog-special.dialog-super"),
|
||||
"additional classes are present"
|
||||
);
|
||||
|
||||
await click(".dialog-footer .btn-primary");
|
||||
|
||||
assert.notOk(
|
||||
query("#dialog-holder.dialog-special"),
|
||||
"additional class removed on dismissal"
|
||||
);
|
||||
|
||||
assert.notOk(
|
||||
query("#dialog-holder.dialog-super"),
|
||||
"additional class removed on dismissal"
|
||||
);
|
||||
});
|
||||
|
||||
test("notice", async function (assert) {
|
||||
await render(hbs`<DialogHolder />`);
|
||||
|
||||
this.dialog.notice("Noted!");
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
query(".dialog-body").innerText.trim(),
|
||||
"Noted!",
|
||||
"message is shown"
|
||||
);
|
||||
|
||||
assert.notOk(query(".dialog-footer"), "no footer");
|
||||
assert.notOk(query(".dialog-header"), "no header");
|
||||
});
|
||||
});
|
|
@ -1853,6 +1853,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
|
||||
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
|
||||
|
||||
a11y-dialog@7.5.0:
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/a11y-dialog/-/a11y-dialog-7.5.0.tgz#1540627b18e3b1e266e0dcbdb5d1e7ac52079fe1"
|
||||
integrity sha512-UF7cy4lfZQtvjRV5N4xdWFba+Pb1qW6FPp0p58dLjMTJ4PwIGGekTbmqUt3etBBRo9HbTqhlNsXQhzIuXeJpng==
|
||||
dependencies:
|
||||
focusable-selectors "^0.3.1"
|
||||
|
||||
abab@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
|
||||
|
@ -5783,6 +5790,11 @@ flush-write-stream@^1.0.0:
|
|||
inherits "^2.0.3"
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
focusable-selectors@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/focusable-selectors/-/focusable-selectors-0.3.1.tgz#7eacbca8dc6cc8d7f7563e5f5cc3699b91e20aaa"
|
||||
integrity sha512-5JLtr0e1YJIfmnVlpLiG+av07dd0Xkf/KfswsXcei5KmLfdwOysTQsjF058ynXniujb1fvev7nql1x+CkC5ikw==
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.13.3"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
@import "crawler_layout";
|
||||
@import "d-icon";
|
||||
@import "d-popover";
|
||||
@import "dialog";
|
||||
@import "directory";
|
||||
@import "discourse";
|
||||
@import "edit-category";
|
||||
|
|
78
app/assets/stylesheets/common/base/dialog.scss
Normal file
78
app/assets/stylesheets/common/base/dialog.scss
Normal file
|
@ -0,0 +1,78 @@
|
|||
.dialog-container,
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.dialog-container {
|
||||
z-index: z("modal", "overlay");
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the dialog container and all its descendants are not
|
||||
* visible and not focusable when it is hidden.
|
||||
*/
|
||||
.dialog-container[aria-hidden="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.dialog-overlay {
|
||||
background: rgba(var(--always-black-rgb), 0.65);
|
||||
animation: fade-in 250ms both;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
margin: auto;
|
||||
z-index: z("modal", "content");
|
||||
position: relative;
|
||||
background-color: var(--secondary);
|
||||
animation: fade-in 250ms both;
|
||||
box-shadow: shadow("card");
|
||||
min-width: 40vw;
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
overflow-y: auto;
|
||||
max-height: 400px;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid var(--primary-low);
|
||||
align-items: center;
|
||||
|
||||
h3 {
|
||||
font-size: var(--font-up-3);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
margin-left: auto;
|
||||
.d-icon {
|
||||
color: var(--primary-high);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: 14px 15px 10px;
|
||||
border-top: 1px solid var(--primary-low);
|
||||
--btn-bottom-margin: 0.3em;
|
||||
.btn {
|
||||
margin: 0 0.75em var(--btn-bottom-margin) 0;
|
||||
}
|
||||
}
|
|
@ -251,6 +251,8 @@ en:
|
|||
not_implemented: "That feature hasn't been implemented yet, sorry!"
|
||||
no_value: "No"
|
||||
yes_value: "Yes"
|
||||
ok_value: "OK"
|
||||
cancel_value: "Cancel"
|
||||
submit: "Submit"
|
||||
generic_error: "Sorry, an error has occurred."
|
||||
generic_error_with_reason: "An error occurred: %{error}"
|
||||
|
|
Loading…
Reference in New Issue
Block a user