From a1d67122b1b5dde9bdfad2249a1746463bf23e7a Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Fri, 7 Oct 2022 11:38:27 -0400 Subject: [PATCH] DEV: Refactor the last few bootbox dialogs (#18416) --- .../admin/addon/components/embeddable-host.js | 12 +-- .../admin-customize-email-templates-edit.js | 32 +++---- .../admin-customize-themes-show.js | 13 +-- .../addon/controllers/admin-user-fields.js | 26 +++--- .../admin/addon/mixins/penalty-controller.js | 13 +-- .../admin-customize-email-style-edit.js | 23 +++-- .../routes/admin-customize-themes-edit.js | 25 +++--- .../routes/admin-customize-themes-show.js | 28 +++--- .../addon/routes/admin-customize-themes.js | 15 ++-- .../admin/addon/services/admin-tools.js | 87 +++++++++---------- .../discourse/app/components/bookmark-list.js | 13 ++- .../discourse/app/components/bookmark.js | 8 +- .../app/components/reviewable-item.js | 14 +-- .../app/components/shared-draft-controls.js | 12 +-- .../discourse/app/components/tag-info.js | 43 +++++---- .../app/controllers/preferences/profile.js | 27 +++--- .../controllers/preferences/second-factor.js | 45 ++++------ .../discourse/app/controllers/tag-show.js | 18 ++-- .../discourse/app/controllers/tags-index.js | 24 +++-- .../app/controllers/topic-bulk-actions.js | 13 +-- .../app/controllers/user-invited-show.js | 21 +++-- .../app/widgets/private-message-map.js | 32 ++++--- .../addon/components/dialog-holder.hbs | 2 +- .../dialog-holder/addon/services/dialog.js | 11 +++ .../acceptance/admin-suspend-user-test.js | 10 +-- .../tests/acceptance/bookmarks-test.js | 12 +-- .../tests/acceptance/composer-actions-test.js | 6 +- .../tests/acceptance/composer-test.js | 2 +- .../tests/acceptance/flag-post-test.js | 17 ++-- .../tests/acceptance/shared-drafts-test.js | 2 +- .../tests/acceptance/user-bookmarks-test.js | 8 +- .../components/dialog-holder-test.js | 24 +++++ .../addon/components/composer-actions.js | 18 ++-- config/locales/client.en.yml | 8 +- 34 files changed, 331 insertions(+), 333 deletions(-) diff --git a/app/assets/javascripts/admin/addon/components/embeddable-host.js b/app/assets/javascripts/admin/addon/components/embeddable-host.js index 328ad06e1ea..320b173bfb5 100644 --- a/app/assets/javascripts/admin/addon/components/embeddable-host.js +++ b/app/assets/javascripts/admin/addon/components/embeddable-host.js @@ -1,9 +1,9 @@ import Category from "discourse/models/category"; import Component from "@ember/component"; import I18n from "I18n"; -import bootbox from "bootbox"; import { bufferedProperty } from "discourse/mixins/buffered-content"; import discourseComputed from "discourse-common/utils/decorators"; +import { inject as service } from "@ember/service"; import { isEmpty } from "@ember/utils"; import { or } from "@ember/object/computed"; import { popupAjaxError } from "discourse/lib/ajax-error"; @@ -13,6 +13,7 @@ export default Component.extend(bufferedProperty("host"), { tagName: "tr", categoryId: null, category: null, + dialog: service(), editing: or("host.isNew", "editToggled"), @@ -61,12 +62,13 @@ export default Component.extend(bufferedProperty("host"), { }, delete() { - bootbox.confirm(I18n.t("admin.embedding.confirm_delete"), (result) => { - if (result) { - this.host.destroyRecord().then(() => { + return this.dialog.confirm({ + message: I18n.t("admin.embedding.confirm_delete"), + didConfirm: () => { + return this.host.destroyRecord().then(() => { this.deleteHost(this.host); }); - } + }, }); }, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates-edit.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates-edit.js index d20f812ca3d..c784deb0ec5 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates-edit.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates-edit.js @@ -1,13 +1,14 @@ import Controller, { inject as controller } from "@ember/controller"; import I18n from "I18n"; import { action } from "@ember/object"; -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 { inject as service } from "@ember/service"; export default Controller.extend(bufferedProperty("emailTemplate"), { adminCustomizeEmailTemplates: controller(), + dialog: service(), emailTemplate: null, saved: false, @@ -42,20 +43,19 @@ export default Controller.extend(bufferedProperty("emailTemplate"), { @action revertChanges() { this.set("saved", false); - bootbox.confirm( - I18n.t("admin.customize.email_templates.revert_confirm"), - (result) => { - if (result) { - this.emailTemplate - .revert() - .then((props) => { - const buffered = this.buffered; - buffered.setProperties(props); - this.commitBuffer(); - }) - .catch(popupAjaxError); - } - } - ); + + this.dialog.yesNoConfirm({ + title: I18n.t("admin.customize.email_templates.revert_confirm"), + didConfirm: () => { + return this.emailTemplate + .revert() + .then((props) => { + const buffered = this.buffered; + buffered.setProperties(props); + this.commitBuffer(); + }) + .catch(popupAjaxError); + }, + }); }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js index 61ffda925e3..45d19733059 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js @@ -10,7 +10,6 @@ import Controller from "@ember/controller"; import EmberObject from "@ember/object"; import I18n from "I18n"; import ThemeSettings from "admin/models/theme-settings"; -import bootbox from "bootbox"; import discourseComputed from "discourse-common/utils/decorators"; import { makeArray } from "discourse-common/lib/helpers"; import { popupAjaxError } from "discourse/lib/ajax-error"; @@ -306,14 +305,10 @@ export default Controller.extend({ editTheme() { if (this.get("model.remote_theme.is_git")) { - bootbox.confirm( - I18n.t("admin.customize.theme.edit_confirm"), - (result) => { - if (result) { - this.transitionToEditRoute(); - } - } - ); + this.dialog.confirm({ + message: I18n.t("admin.customize.theme.edit_confirm"), + didConfirm: () => this.transitionToEditRoute(), + }); } else { this.transitionToEditRoute(); } diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js b/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js index bafcc02937e..619c1cf6293 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js @@ -1,12 +1,13 @@ import { gte, sort } from "@ember/object/computed"; import Controller from "@ember/controller"; import I18n from "I18n"; -import bootbox from "bootbox"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import { inject as service } from "@ember/service"; const MAX_FIELDS = 30; export default Controller.extend({ + dialog: service(), fieldTypes: null, createDisabled: gte("model.length", MAX_FIELDS), sortedFields: sort("model", "fieldSortOrder"), @@ -53,18 +54,17 @@ export default Controller.extend({ // Only confirm if we already been saved if (f.get("id")) { - bootbox.confirm( - I18n.t("admin.user_fields.delete_confirm"), - function (result) { - if (result) { - f.destroyRecord() - .then(function () { - model.removeObject(f); - }) - .catch(popupAjaxError); - } - } - ); + this.dialog.yesNoConfirm({ + message: I18n.t("admin.user_fields.delete_confirm"), + didConfirm: () => { + return f + .destroyRecord() + .then(function () { + model.removeObject(f); + }) + .catch(popupAjaxError); + }, + }); } else { model.removeObject(f); } diff --git a/app/assets/javascripts/admin/addon/mixins/penalty-controller.js b/app/assets/javascripts/admin/addon/mixins/penalty-controller.js index 55514dfe486..eb5d56e8cfc 100644 --- a/app/assets/javascripts/admin/addon/mixins/penalty-controller.js +++ b/app/assets/javascripts/admin/addon/mixins/penalty-controller.js @@ -2,11 +2,12 @@ import I18n from "I18n"; import Mixin from "@ember/object/mixin"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { Promise } from "rsvp"; -import bootbox from "bootbox"; import { extractError } from "discourse/lib/ajax-error"; import { next } from "@ember/runloop"; +import { inject as service } from "@ember/service"; export default Mixin.create(ModalFunctionality, { + dialog: service(), errorMessage: null, reason: null, message: null, @@ -40,15 +41,15 @@ export default Mixin.create(ModalFunctionality, { (this.message && this.message.length > 1)) ) { this.send("hideModal"); - bootbox.confirm(I18n.t("admin.user.confirm_cancel_penalty"), (result) => { - if (result) { + this.dialog.confirm({ + message: I18n.t("admin.user.confirm_cancel_penalty"), + didConfirm: () => { next(() => { this.set("confirmClose", true); this.send("closeModal"); }); - } else { - next(() => this.send("reopenModal")); - } + }, + didCancel: () => this.send("reopenModal"), }); return false; } diff --git a/app/assets/javascripts/admin/addon/routes/admin-customize-email-style-edit.js b/app/assets/javascripts/admin/addon/routes/admin-customize-email-style-edit.js index 4453dbe672e..847b21a75c9 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-customize-email-style-edit.js +++ b/app/assets/javascripts/admin/addon/routes/admin-customize-email-style-edit.js @@ -1,8 +1,9 @@ import I18n from "I18n"; import Route from "@ember/routing/route"; -import bootbox from "bootbox"; +import { inject as service } from "@ember/service"; export default Route.extend({ + dialog: service(), model(params) { return { model: this.modelFor("adminCustomizeEmailStyle"), @@ -26,17 +27,15 @@ export default Route.extend({ transition.intent.name !== this.routeName ) { transition.abort(); - bootbox.confirm( - I18n.t("admin.customize.theme.unsaved_changes_alert"), - I18n.t("admin.customize.theme.discard"), - I18n.t("admin.customize.theme.stay"), - (result) => { - if (!result) { - this._shouldAlertUnsavedChanges = false; - transition.retry(); - } - } - ); + this.dialog.confirm({ + message: I18n.t("admin.customize.theme.unsaved_changes_alert"), + confirmButtonLabel: "admin.customize.theme.discard", + cancelButtonLabel: "admin.customize.theme.stay", + didConfirm: () => { + this._shouldAlertUnsavedChanges = false; + transition.retry(); + }, + }); } }, }, diff --git a/app/assets/javascripts/admin/addon/routes/admin-customize-themes-edit.js b/app/assets/javascripts/admin/addon/routes/admin-customize-themes-edit.js index 0d55baa6cee..c72e9d07311 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-customize-themes-edit.js +++ b/app/assets/javascripts/admin/addon/routes/admin-customize-themes-edit.js @@ -1,8 +1,10 @@ import I18n from "I18n"; import Route from "@ember/routing/route"; -import bootbox from "bootbox"; +import { inject as service } from "@ember/service"; export default Route.extend({ + dialog: service(), + model(params) { const all = this.modelFor("adminCustomizeThemes"); const model = all.findBy("id", parseInt(params.theme_id, 10)); @@ -56,17 +58,16 @@ export default Route.extend({ transition.intent.name !== this.routeName ) { transition.abort(); - bootbox.confirm( - I18n.t("admin.customize.theme.unsaved_changes_alert"), - I18n.t("admin.customize.theme.discard"), - I18n.t("admin.customize.theme.stay"), - (result) => { - if (!result) { - this.set("shouldAlertUnsavedChanges", false); - transition.retry(); - } - } - ); + + this.dialog.confirm({ + message: I18n.t("admin.customize.theme.unsaved_changes_alert"), + confirmButtonLabel: "admin.customize.theme.discard", + cancelButtonLabel: "admin.customize.theme.stay", + didConfirm: () => { + this.set("shouldAlertUnsavedChanges", false); + transition.retry(); + }, + }); } }, }, diff --git a/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js b/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js index 6c23a361098..156dbf9dd0c 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js +++ b/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js @@ -2,23 +2,11 @@ import { COMPONENTS, THEMES } from "admin/models/theme"; import I18n from "I18n"; import Route from "@ember/routing/route"; import { scrollTop } from "discourse/mixins/scroll-top"; -import bootbox from "bootbox"; - -export function showUnassignedComponentWarning(theme, callback) { - bootbox.confirm( - I18n.t("admin.customize.theme.unsaved_parent_themes"), - I18n.t("admin.customize.theme.discard"), - I18n.t("admin.customize.theme.stay"), - (result) => { - if (!result) { - theme.set("recentlyInstalled", false); - } - callback(result); - } - ); -} +import { inject as service } from "@ember/service"; export default Route.extend({ + dialog: service(), + serialize(model) { return { theme_id: model.get("id") }; }, @@ -72,10 +60,14 @@ export default Route.extend({ const model = this.controller.model; if (model.warnUnassignedComponent) { transition.abort(); - showUnassignedComponentWarning(model, (result) => { - if (!result) { + + this.dialog.yesNoConfirm({ + message: I18n.t("admin.customize.theme.unsaved_parent_themes"), + didConfirm: () => { + model.set("recentlyInstalled", false); transition.retry(); - } + }, + didCancel: () => model.set("recentlyInstalled", false), }); } }, diff --git a/app/assets/javascripts/admin/addon/routes/admin-customize-themes.js b/app/assets/javascripts/admin/addon/routes/admin-customize-themes.js index 8734675eff2..22878ded22e 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-customize-themes.js +++ b/app/assets/javascripts/admin/addon/routes/admin-customize-themes.js @@ -1,9 +1,12 @@ import Route from "@ember/routing/route"; import showModal from "discourse/lib/show-modal"; +import I18n from "I18n"; import { next } from "@ember/runloop"; -import { showUnassignedComponentWarning } from "admin/routes/admin-customize-themes-show"; +import { inject as service } from "@ember/service"; export default Route.extend({ + dialog: service(), + queryParams: { repoUrl: null, repoName: null, @@ -35,11 +38,13 @@ export default Route.extend({ const currentTheme = this.controllerFor( "adminCustomizeThemes.show" ).model; - if (currentTheme && currentTheme.warnUnassignedComponent) { - showUnassignedComponentWarning(currentTheme, (result) => { - if (!result) { + if (currentTheme?.warnUnassignedComponent) { + this.dialog.yesNoConfirm({ + message: I18n.t("admin.customize.theme.unsaved_parent_themes"), + didConfirm: () => { + currentTheme.set("recentlyInstalled", false); showModal("admin-install-theme", { admin: true }); - } + }, }); } else { showModal("admin-install-theme", { admin: true }); diff --git a/app/assets/javascripts/admin/addon/services/admin-tools.js b/app/assets/javascripts/admin/addon/services/admin-tools.js index 3d06845508a..5fce78cb33d 100644 --- a/app/assets/javascripts/admin/addon/services/admin-tools.js +++ b/app/assets/javascripts/admin/addon/services/admin-tools.js @@ -3,10 +3,9 @@ import I18n from "I18n"; import { Promise } from "rsvp"; import Service, { inject as service } from "@ember/service"; import { ajax } from "discourse/lib/ajax"; -import bootbox from "bootbox"; import { getOwner } from "discourse-common/lib/get-owner"; -import { iconHTML } from "discourse-common/lib/icon-library"; import showModal from "discourse/lib/show-modal"; +import { htmlSafe } from "@ember/template"; // A service that can act as a bridge between the front end Discourse application // and the admin application. Use this if you need front end code to access admin @@ -79,58 +78,50 @@ export default Service.extend({ : Promise.resolve(); return tryEmail.then(() => { - let message = I18n.messageFormat("flagging.delete_confirm_MF", { - POSTS: adminUser.get("post_count"), - TOPICS: adminUser.get("topic_count"), - email: - adminUser.get("email") || I18n.t("flagging.hidden_email_address"), - ip_address: - adminUser.get("ip_address") || I18n.t("flagging.ip_address_missing"), - }); + let message = htmlSafe( + I18n.messageFormat("flagging.delete_confirm_MF", { + POSTS: adminUser.get("post_count"), + TOPICS: adminUser.get("topic_count"), + email: + adminUser.get("email") || I18n.t("flagging.hidden_email_address"), + ip_address: + adminUser.get("ip_address") || + I18n.t("flagging.ip_address_missing"), + }) + ); let userId = adminUser.get("id"); return new Promise((resolve, reject) => { - const buttons = [ - { - label: I18n.t("composer.cancel"), - class: "d-modal-cancel", - link: true, - }, - { - label: - `${iconHTML("exclamation-triangle")} ` + - I18n.t("flagging.yes_delete_spammer"), - class: "btn btn-danger confirm-delete", - callback() { - return ajax(`/admin/users/${userId}.json`, { - type: "DELETE", - data: { - delete_posts: true, - block_email: true, - block_urls: true, - block_ip: true, - delete_as_spammer: true, - context: window.location.pathname, - }, + this.dialog.deleteConfirm({ + message, + class: "flagging-delete-spammer", + confirmButtonLabel: "flagging.yes_delete_spammer", + confirmButtonIcon: "exclamation-triangle", + didConfirm: () => { + return ajax(`/admin/users/${userId}.json`, { + type: "DELETE", + data: { + delete_posts: true, + block_email: true, + block_urls: true, + block_ip: true, + delete_as_spammer: true, + context: window.location.pathname, + }, + }) + .then((result) => { + if (result.deleted) { + resolve(); + } else { + throw new Error("failed to delete"); + } }) - .then((result) => { - if (result.deleted) { - resolve(); - } else { - throw new Error("failed to delete"); - } - }) - .catch(() => { - this.dialog.alert(I18n.t("admin.user.delete_failed")); - reject(); - }); - }, + .catch(() => { + this.dialog.alert(I18n.t("admin.user.delete_failed")); + reject(); + }); }, - ]; - - bootbox.dialog(message, buttons, { - classes: "flagging-delete-spammer", }); }); }); diff --git a/app/assets/javascripts/discourse/app/components/bookmark-list.js b/app/assets/javascripts/discourse/app/components/bookmark-list.js index 594bf4ff110..6ea4c9cb634 100644 --- a/app/assets/javascripts/discourse/app/components/bookmark-list.js +++ b/app/assets/javascripts/discourse/app/components/bookmark-list.js @@ -1,7 +1,6 @@ import Component from "@ember/component"; import { action } from "@ember/object"; import { next, schedule } from "@ember/runloop"; -import bootbox from "bootbox"; import { openBookmarkModal } from "discourse/controllers/bookmark"; import { ajax } from "discourse/lib/ajax"; import { @@ -11,8 +10,10 @@ import { import Scrolling from "discourse/mixins/scrolling"; import I18n from "I18n"; import { Promise } from "rsvp"; +import { inject as service } from "@ember/service"; export default Component.extend(Scrolling, { + dialog: service(), classNames: ["bookmark-list-wrapper"], didInsertElement() { @@ -64,12 +65,10 @@ export default Component.extend(Scrolling, { if (!bookmark.reminder_at) { return deleteBookmark(); } - bootbox.confirm(I18n.t("bookmarks.confirm_delete"), (result) => { - if (result) { - deleteBookmark(); - } else { - resolve(false); - } + this.dialog.deleteConfirm({ + message: I18n.t("bookmarks.confirm_delete"), + didConfirm: () => deleteBookmark(), + didCancel: () => resolve(false), }); }); }, diff --git a/app/assets/javascripts/discourse/app/components/bookmark.js b/app/assets/javascripts/discourse/app/components/bookmark.js index 14997c5aad9..879ef027fca 100644 --- a/app/assets/javascripts/discourse/app/components/bookmark.js +++ b/app/assets/javascripts/discourse/app/components/bookmark.js @@ -11,7 +11,6 @@ import { } from "discourse/lib/time-shortcut"; import { action } from "@ember/object"; import { ajax } from "discourse/lib/ajax"; -import bootbox from "bootbox"; import discourseComputed, { bind } from "discourse-common/utils/decorators"; import { formattedReminderTime } from "discourse/lib/bookmark"; import { and, notEmpty } from "@ember/object/computed"; @@ -377,10 +376,9 @@ export default Component.extend({ }; if (this.existingBookmarkHasReminder) { - bootbox.confirm(I18n.t("bookmarks.confirm_delete"), (result) => { - if (result) { - deleteAction(); - } + this.dialog.deleteConfirm({ + message: I18n.t("bookmarks.confirm_delete"), + didConfirm: () => deleteAction(), }); } else { deleteAction(); diff --git a/app/assets/javascripts/discourse/app/components/reviewable-item.js b/app/assets/javascripts/discourse/app/components/reviewable-item.js index 73c90d665a9..2ce56fd43b4 100644 --- a/app/assets/javascripts/discourse/app/components/reviewable-item.js +++ b/app/assets/javascripts/discourse/app/components/reviewable-item.js @@ -2,13 +2,13 @@ import Category from "discourse/models/category"; import Component from "@ember/component"; import I18n from "I18n"; import { ajax } from "discourse/lib/ajax"; -import bootbox from "bootbox"; import { classify, dasherize } from "@ember/string"; import discourseComputed, { bind } from "discourse-common/utils/decorators"; import optionalService from "discourse/lib/optional-service"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { action, set } from "@ember/object"; import showModal from "discourse/lib/show-modal"; +import { inject as service } from "@ember/service"; let _components = {}; @@ -22,6 +22,7 @@ export function addPluginReviewableParam(reviewableType, param) { export default Component.extend({ adminTools: optionalService(), + dialog: service(), tagName: "", updating: null, editing: false, @@ -269,14 +270,13 @@ export default Component.extend({ return; } - let msg = performableAction.get("confirm_message"); + const message = performableAction.get("confirm_message"); let requireRejectReason = performableAction.get("require_reject_reason"); let customModal = performableAction.get("custom_modal"); - if (msg) { - bootbox.confirm(msg, (answer) => { - if (answer) { - return this._performConfirmed(performableAction); - } + if (message) { + this.dialog.confirm({ + message, + didConfirm: () => this._performConfirmed(performableAction), }); } else if (requireRejectReason) { showModal("reject-reason-reviewable", { diff --git a/app/assets/javascripts/discourse/app/components/shared-draft-controls.js b/app/assets/javascripts/discourse/app/components/shared-draft-controls.js index a68fe3c1ebd..465edcbb6dd 100644 --- a/app/assets/javascripts/discourse/app/components/shared-draft-controls.js +++ b/app/assets/javascripts/discourse/app/components/shared-draft-controls.js @@ -1,10 +1,11 @@ import Component from "@ember/component"; import I18n from "I18n"; -import bootbox from "bootbox"; import discourseComputed from "discourse-common/utils/decorators"; +import { inject as service } from "@ember/service"; export default Component.extend({ tagName: "", + dialog: service(), publishing: false, @discourseComputed("topic.destination_category_id") @@ -18,11 +19,12 @@ export default Component.extend({ }, publish() { - bootbox.confirm(I18n.t("shared_drafts.confirm_publish"), (result) => { - if (result) { + this.dialog.yesNoConfirm({ + message: I18n.t("shared_drafts.confirm_publish"), + didConfirm: () => { this.set("publishing", true); const destinationCategoryId = this.topic.destination_category_id; - this.topic + return this.topic .publish() .then(() => { this.topic.setProperties({ @@ -34,7 +36,7 @@ export default Component.extend({ .finally(() => { this.set("publishing", false); }); - } + }, }); }, }, diff --git a/app/assets/javascripts/discourse/app/components/tag-info.js b/app/assets/javascripts/discourse/app/components/tag-info.js index 21da9a92f1b..201dec9ba6f 100644 --- a/app/assets/javascripts/discourse/app/components/tag-info.js +++ b/app/assets/javascripts/discourse/app/components/tag-info.js @@ -2,12 +2,12 @@ import { and, reads } from "@ember/object/computed"; import Component from "@ember/component"; import I18n from "I18n"; import { ajax } from "discourse/lib/ajax"; -import bootbox from "bootbox"; import discourseComputed from "discourse-common/utils/decorators"; import { isEmpty } from "@ember/utils"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { inject as service } from "@ember/service"; import { action } from "@ember/object"; +import { htmlSafe } from "@ember/template"; export default Component.extend({ dialog: service(), @@ -100,19 +100,18 @@ export default Component.extend({ @action deleteSynonym(tag, event) { event?.preventDefault(); - bootbox.confirm( - I18n.t("tagging.delete_synonym_confirm", { tag_name: tag.text }), - (result) => { - if (!result) { - return; - } - tag + this.dialog.yesNoConfirm({ + message: I18n.t("tagging.delete_synonym_confirm", { + tag_name: tag.text, + }), + didConfirm: () => { + return tag .destroyRecord() .then(() => this.tagInfo.synonyms.removeObject(tag)) .catch(popupAjaxError); - } - ); + }, + }); }, actions: { @@ -146,17 +145,15 @@ export default Component.extend({ }, addSynonyms() { - bootbox.confirm( - I18n.t("tagging.add_synonyms_explanation", { - count: this.newSynonyms.length, - tag_name: this.tagInfo.name, - }), - (result) => { - if (!result) { - return; - } - - ajax(`/tag/${this.tagInfo.name}/synonyms`, { + this.dialog.confirm({ + message: htmlSafe( + I18n.t("tagging.add_synonyms_explanation", { + count: this.newSynonyms.length, + tag_name: this.tagInfo.name, + }) + ), + didConfirm: () => { + return ajax(`/tag/${this.tagInfo.name}/synonyms`, { type: "POST", data: { synonyms: this.newSynonyms, @@ -177,8 +174,8 @@ export default Component.extend({ } }) .catch(popupAjaxError); - } - ); + }, + }); }, }, }); diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/profile.js b/app/assets/javascripts/discourse/app/controllers/preferences/profile.js index a3ea290db20..e396e3e4dfa 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/profile.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/profile.js @@ -2,15 +2,16 @@ import Controller from "@ember/controller"; import EmberObject from "@ember/object"; import I18n from "I18n"; import { ajax } from "discourse/lib/ajax"; -import bootbox from "bootbox"; import { cookAsync } from "discourse/lib/text"; import discourseComputed from "discourse-common/utils/decorators"; import { isEmpty } from "@ember/utils"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { readOnly } from "@ember/object/computed"; import showModal from "discourse/lib/show-modal"; +import { inject as service } from "@ember/service"; export default Controller.extend({ + dialog: service(), init() { this._super(...arguments); this.saveAttrNames = [ @@ -79,20 +80,18 @@ export default Controller.extend({ }, clearFeaturedTopicFromProfile() { - bootbox.confirm( - I18n.t("user.feature_topic_on_profile.clear.warning"), - (result) => { - if (result) { - ajax(`/u/${this.model.username}/clear-featured-topic`, { - type: "PUT", + this.dialog.yesNoConfirm({ + message: I18n.t("user.feature_topic_on_profile.clear.warning"), + didConfirm: () => { + return ajax(`/u/${this.model.username}/clear-featured-topic`, { + type: "PUT", + }) + .then(() => { + this.model.set("featured_topic", null); }) - .then(() => { - this.model.set("featured_topic", null); - }) - .catch(popupAjaxError); - } - } - ); + .catch(popupAjaxError); + }, + }); }, useCurrentTimezone() { diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/second-factor.js b/app/assets/javascripts/discourse/app/controllers/preferences/second-factor.js index fc5293054a6..91f77db8a0c 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/second-factor.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/second-factor.js @@ -5,14 +5,14 @@ import I18n from "I18n"; import { SECOND_FACTOR_METHODS } from "discourse/models/user"; import { action } from "@ember/object"; import { alias } from "@ember/object/computed"; -import bootbox from "bootbox"; import discourseComputed from "discourse-common/utils/decorators"; import { findAll } from "discourse/models/login-method"; -import { iconHTML } from "discourse-common/lib/icon-library"; import { popupAjaxError } from "discourse/lib/ajax-error"; import showModal from "discourse/lib/show-modal"; +import { inject as service } from "@ember/service"; export default Controller.extend(CanCheckEmails, { + dialog: service(), loading: false, dirty: false, resetPasswordLoading: false, @@ -127,34 +127,21 @@ export default Controller.extend(CanCheckEmails, { if (this.loading) { return; } - const message = I18n.t("user.second_factor.disable_confirm"); - const buttons = [ - { - label: I18n.t("cancel"), - class: "d-modal-cancel", - link: true, - }, - { - icon: iconHTML("ban"), - label: I18n.t("user.second_factor.disable"), - class: "btn-danger btn-icon-text", - callback: () => { - this.model - .disableAllSecondFactors() - .then(() => { - const usernameLower = this.model.username.toLowerCase(); - DiscourseURL.redirectTo( - userPath(`${usernameLower}/preferences`) - ); - }) - .catch((e) => this.handleError(e)) - .finally(() => this.set("loading", false)); - }, - }, - ]; - bootbox.dialog(message, buttons, { - classes: "disable-second-factor-modal", + this.dialog.deleteConfirm({ + title: I18n.t("user.second_factor.disable_confirm"), + confirmButtonLabel: "user.second_factor.disable", + confirmButtonIcon: "ban", + didConfirm: () => { + this.model + .disableAllSecondFactors() + .then(() => { + const usernameLower = this.model.username.toLowerCase(); + DiscourseURL.redirectTo(userPath(`${usernameLower}/preferences`)); + }) + .catch((e) => this.handleError(e)) + .finally(() => this.set("loading", false)); + }, }); }, diff --git a/app/assets/javascripts/discourse/app/controllers/tag-show.js b/app/assets/javascripts/discourse/app/controllers/tag-show.js index 2f9c4935165..cc32873bbc8 100644 --- a/app/assets/javascripts/discourse/app/controllers/tag-show.js +++ b/app/assets/javascripts/discourse/app/controllers/tag-show.js @@ -7,7 +7,6 @@ import I18n from "I18n"; import NavItem from "discourse/models/nav-item"; import Topic from "discourse/models/topic"; import { readOnly } from "@ember/object/computed"; -import bootbox from "bootbox"; import { endWith } from "discourse/lib/computed"; import { action } from "@ember/object"; import { inject as service } from "@ember/service"; @@ -167,15 +166,14 @@ export default DiscoverySortableController.extend( }); } - bootbox.confirm(confirmText, (result) => { - if (!result) { - return; - } - - this.tag - .destroyRecord() - .then(() => this.transitionToRoute("tags.index")) - .catch(() => this.dialog.alert(I18n.t("generic_error"))); + this.dialog.deleteConfirm({ + message: confirmText, + didConfirm: () => { + return this.tag + .destroyRecord() + .then(() => this.transitionToRoute("tags.index")) + .catch(() => this.dialog.alert(I18n.t("generic_error"))); + }, }); }, diff --git a/app/assets/javascripts/discourse/app/controllers/tags-index.js b/app/assets/javascripts/discourse/app/controllers/tags-index.js index eff2f1d30bc..09d71e89c84 100644 --- a/app/assets/javascripts/discourse/app/controllers/tags-index.js +++ b/app/assets/javascripts/discourse/app/controllers/tags-index.js @@ -3,7 +3,6 @@ import { alias, notEmpty } from "@ember/object/computed"; 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 { popupAjaxError } from "discourse/lib/ajax-error"; import showModal from "discourse/lib/show-modal"; @@ -91,23 +90,20 @@ export default Controller.extend({ tags: joinedTags, }); - const string = I18n.t("tagging.delete_unused_confirmation", { + const message = I18n.t("tagging.delete_unused_confirmation", { count: tags.length, tags: tagsString, }); - bootbox.confirm( - string, - I18n.t("tagging.cancel_delete_unused"), - I18n.t("tagging.delete_unused"), - (proceed) => { - if (proceed) { - ajax("/tags/unused", { type: "DELETE" }) - .then(() => this.send("triggerRefresh")) - .catch(popupAjaxError); - } - } - ); + this.dialog.deleteConfirm({ + message, + confirmButtonLabel: "tagging.delete_unused", + didConfirm: () => { + return ajax("/tags/unused", { type: "DELETE" }) + .then(() => this.send("triggerRefresh")) + .catch(popupAjaxError); + }, + }); }) .catch(popupAjaxError); }, diff --git a/app/assets/javascripts/discourse/app/controllers/topic-bulk-actions.js b/app/assets/javascripts/discourse/app/controllers/topic-bulk-actions.js index 7efe21e1376..205cc147a44 100644 --- a/app/assets/javascripts/discourse/app/controllers/topic-bulk-actions.js +++ b/app/assets/javascripts/discourse/app/controllers/topic-bulk-actions.js @@ -4,7 +4,6 @@ import I18n from "I18n"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { Promise } from "rsvp"; import Topic from "discourse/models/topic"; -import bootbox from "bootbox"; import { inject as service } from "@ember/service"; @@ -320,16 +319,12 @@ export default Controller.extend(ModalFunctionality, { }, removeTags() { - bootbox.confirm( - I18n.t("topics.bulk.confirm_remove_tags", { + this.dialog.deleteConfirm({ + message: I18n.t("topics.bulk.confirm_remove_tags", { count: this.get("model.topics").length, }), - (result) => { - if (result) { - this.performAndRefresh({ type: "remove_tags" }); - } - } - ); + didConfirm: () => this.performAndRefresh({ type: "remove_tags" }), + }); }, }, }); diff --git a/app/assets/javascripts/discourse/app/controllers/user-invited-show.js b/app/assets/javascripts/discourse/app/controllers/user-invited-show.js index 008a4b5946c..7ec24665f57 100644 --- a/app/assets/javascripts/discourse/app/controllers/user-invited-show.js +++ b/app/assets/javascripts/discourse/app/controllers/user-invited-show.js @@ -1,7 +1,6 @@ import Controller from "@ember/controller"; import { action } from "@ember/object"; import { equal, reads } from "@ember/object/computed"; -import bootbox from "bootbox"; import { INPUT_DELAY } from "discourse-common/config/environment"; import discourseDebounce from "discourse-common/lib/debounce"; import discourseComputed, { observes } from "discourse-common/utils/decorators"; @@ -9,8 +8,10 @@ import { popupAjaxError } from "discourse/lib/ajax-error"; import showModal from "discourse/lib/show-modal"; import Invite from "discourse/models/invite"; import I18n from "I18n"; +import { inject as service } from "@ember/service"; export default Controller.extend({ + dialog: service(), user: null, model: null, filter: null, @@ -93,15 +94,16 @@ export default Controller.extend({ @action destroyAllExpired() { - bootbox.confirm(I18n.t("user.invited.remove_all_confirm"), (confirm) => { - if (confirm) { - Invite.destroyAllExpired() + this.dialog.deleteConfirm({ + message: I18n.t("user.invited.remove_all_confirm"), + didConfirm: () => { + return Invite.destroyAllExpired() .then(() => { this.set("removedAll", true); this.send("triggerRefresh"); }) .catch(popupAjaxError); - } + }, }); }, @@ -113,12 +115,13 @@ export default Controller.extend({ @action reinviteAll() { - bootbox.confirm(I18n.t("user.invited.reinvite_all_confirm"), (confirm) => { - if (confirm) { - Invite.reinviteAll() + this.dialog.yesNoConfirm({ + message: I18n.t("user.invited.reinvite_all_confirm"), + didConfirm: () => { + return Invite.reinviteAll() .then(() => this.set("reinvitedAll", true)) .catch(popupAjaxError); - } + }, }); }, diff --git a/app/assets/javascripts/discourse/app/widgets/private-message-map.js b/app/assets/javascripts/discourse/app/widgets/private-message-map.js index 435df6c0dee..781d2a8a3c0 100644 --- a/app/assets/javascripts/discourse/app/widgets/private-message-map.js +++ b/app/assets/javascripts/discourse/app/widgets/private-message-map.js @@ -1,6 +1,5 @@ import { avatarFor, avatarImg } from "discourse/widgets/post"; import I18n from "I18n"; -import bootbox from "bootbox"; import { createWidget } from "discourse/widgets/widget"; import getURL from "discourse-common/lib/get-url"; import { h } from "virtual-dom"; @@ -10,18 +9,16 @@ import { makeArray } from "discourse-common/lib/helpers"; createWidget("pm-remove-group-link", { tagName: "a.remove-invited.no-text.btn-icon.btn", template: hbs`{{d-icon "times"}}`, + services: ["dialog"], click() { - bootbox.confirm( - I18n.t("private_message_info.remove_allowed_group", { + this.dialog.deleteConfirm({ + message: I18n.t("private_message_info.remove_allowed_group", { name: this.attrs.name, }), - (confirmed) => { - if (confirmed) { - this.sendWidgetAction("removeAllowedGroup", this.attrs); - } - } - ); + confirmButtonLabel: "private_message_info.remove_group", + didConfirm: () => this.sendWidgetAction("removeAllowedGroup", this.attrs), + }); }, }); @@ -48,22 +45,23 @@ createWidget("pm-map-user-group", { createWidget("pm-remove-link", { tagName: "a.remove-invited.no-text.btn-icon.btn", template: hbs`{{d-icon "times"}}`, + services: ["dialog"], click() { const messageKey = this.attrs.isCurrentUser ? "leave_message" : "remove_allowed_user"; - bootbox.confirm( - I18n.t(`private_message_info.${messageKey}`, { + this.dialog.deleteConfirm({ + message: I18n.t(`private_message_info.${messageKey}`, { name: this.attrs.user.username, }), - (confirmed) => { - if (confirmed) { - this.sendWidgetAction("removeAllowedUser", this.attrs.user); - } - } - ); + confirmButtonLabel: this.attrs.isCurrentUser + ? "private_message_info.leave" + : "private_message_info.remove_user", + didConfirm: () => + this.sendWidgetAction("removeAllowedUser", this.attrs.user), + }); }, }); diff --git a/app/assets/javascripts/discourse/lib/dialog-holder/addon/components/dialog-holder.hbs b/app/assets/javascripts/discourse/lib/dialog-holder/addon/components/dialog-holder.hbs index 46e2cd5aaf8..7a1729f5b10 100644 --- a/app/assets/javascripts/discourse/lib/dialog-holder/addon/components/dialog-holder.hbs +++ b/app/assets/javascripts/discourse/lib/dialog-holder/addon/components/dialog-holder.hbs @@ -21,7 +21,7 @@ {{#each this.dialog.buttons as |button|}} {{else}} - + {{#if this.dialog.shouldDisplayCancel}} {{/if}} diff --git a/app/assets/javascripts/discourse/lib/dialog-holder/addon/services/dialog.js b/app/assets/javascripts/discourse/lib/dialog-holder/addon/services/dialog.js index fa072595ed8..824e6e02d31 100644 --- a/app/assets/javascripts/discourse/lib/dialog-holder/addon/services/dialog.js +++ b/app/assets/javascripts/discourse/lib/dialog-holder/addon/services/dialog.js @@ -12,6 +12,7 @@ export default Service.extend({ confirmButtonIcon: null, confirmButtonLabel: null, + confirmButtonClass: null, cancelButtonLabel: null, shouldDisplayCancel: null, @@ -29,6 +30,7 @@ export default Service.extend({ confirmButtonIcon, confirmButtonLabel = "ok_value", + confirmButtonClass = "btn-primary", cancelButtonLabel = "cancel_value", shouldDisplayCancel, @@ -47,6 +49,7 @@ export default Service.extend({ title, titleElementId: title !== null ? "dialog-title" : null, + confirmButtonClass, confirmButtonLabel, confirmButtonIcon, cancelButtonLabel, @@ -108,6 +111,14 @@ export default Service.extend({ }); }, + deleteConfirm(params) { + return this.confirm({ + ...params, + confirmButtonClass: "btn-danger", + confirmButtonLabel: params.confirmButtonLabel || "delete", + }); + }, + reset() { this.setProperties({ message: null, diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js index c60d3170cf4..73918cd5e26 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-suspend-user-test.js @@ -54,9 +54,9 @@ acceptance("Admin - Suspend User", function (needs) { await click(".d-modal-cancel"); - assert.strictEqual(count(".bootbox.modal:visible"), 1); + assert.strictEqual(count(".dialog-body:visible"), 1); - await click(".modal-footer .btn-default"); + await click(".dialog-footer .btn-default"); assert.strictEqual(count(".suspend-user-modal:visible"), 1); assert.strictEqual( query(".suspend-message").value, @@ -64,11 +64,11 @@ acceptance("Admin - Suspend User", function (needs) { ); await click(".d-modal-cancel"); - assert.strictEqual(count(".bootbox.modal:visible"), 1); + assert.strictEqual(count(".dialog-body:visible"), 1); assert.ok(!exists(".suspend-user-modal:visible")); - await click(".modal-footer .btn-primary"); - assert.ok(!exists(".bootbox.modal:visible")); + await click(".dialog-footer .btn-primary"); + assert.ok(!exists(".dialog-body:visible")); }); test("suspend, then unsuspend a user", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js b/app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js index e46c80057c4..e9fb662fd6e 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js @@ -215,15 +215,15 @@ acceptance("Bookmarking", function (needs) { await click("#delete-bookmark"); - assert.ok(exists(".bootbox.modal"), "it asks for delete confirmation"); + assert.ok(exists(".dialog-body"), "it asks for delete confirmation"); assert.ok( - query(".bootbox.modal").innerText.includes( + query(".dialog-body").innerText.includes( I18n.t("bookmarks.confirm_delete") ), "it shows delete confirmation message" ); - await click(".bootbox.modal .btn-primary"); + await click(".dialog-footer .btn-danger"); assert.notOk( exists(".topic-post:first-child button.bookmark.bookmarked"), @@ -444,15 +444,15 @@ acceptance("Bookmarking", function (needs) { await click("#topic-footer-button-bookmark"); await click("#delete-bookmark"); - assert.ok(exists(".bootbox.modal"), "it asks for delete confirmation"); + assert.ok(exists(".dialog-body"), "it asks for delete confirmation"); assert.ok( - query(".bootbox.modal").innerText.includes( + query(".dialog-body").innerText.includes( I18n.t("bookmarks.confirm_delete") ), "it shows delete confirmation message" ); - await click(".bootbox.modal .btn-primary"); + await click(".dialog-footer .btn-danger"); assert.strictEqual( query("#topic-footer-button-bookmark").innerText, diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js index a2002935f0a..8be69c4bd6c 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js @@ -150,7 +150,7 @@ acceptance("Composer Actions", function (needs) { const composerActions = selectKit(".composer-actions"); await composerActions.expand(); await composerActions.selectRowByValue("reply_as_new_topic"); - assert.ok(!exists(".bootbox")); + assert.ok(!exists(".dialog-body")); }); test("reply_as_new_group_message", async function (assert) { @@ -471,10 +471,10 @@ acceptance("Composer Actions With New Topic Draft", function (needs) { await composerActions.selectRowByValue("reply_as_new_topic"); assert.strictEqual( - query(".bootbox .modal-body").innerText, + query(".dialog-body").innerText.trim(), I18n.t("composer.composer_actions.reply_as_new_topic.confirm") ); - await click(".modal-footer .btn.btn-primary"); + await click(".dialog-footer .btn-primary"); assert.ok( query(".d-editor-input").value.startsWith( diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js index 98ada74b6de..ab11f39043c 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js @@ -223,7 +223,7 @@ acceptance("Composer", function (needs) { assert.ok(exists(".d-modal"), "it pops up a confirmation dialog"); await click(".modal-footer .discard-draft"); - assert.ok(!exists(".bootbox.modal"), "the confirmation can be cancelled"); + assert.ok(!exists(".modal-body"), "the confirmation can be cancelled"); }); test("Create a topic with server side errors", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/acceptance/flag-post-test.js b/app/assets/javascripts/discourse/tests/acceptance/flag-post-test.js index e4cc526dddf..a3b4b048424 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/flag-post-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/flag-post-test.js @@ -1,6 +1,5 @@ import { acceptance, - count, exists, query, } from "discourse/tests/helpers/qunit-helpers"; @@ -123,9 +122,11 @@ acceptance("flagging", function (needs) { const silenceUntilCombobox = selectKit(".silence-until .combobox"); await silenceUntilCombobox.expand(); await silenceUntilCombobox.selectRowByValue("tomorrow"); + assert.ok(exists(".modal-body")); await fillIn(".silence-reason", "for breaking the rules"); + await click(".perform-silence"); - assert.ok(!exists(".bootbox.modal:visible")); + assert.ok(!exists(".modal-body")); }); test("Gets dismissable warning from canceling incomplete silence from take action", async function (assert) { @@ -140,17 +141,17 @@ acceptance("flagging", function (needs) { await silenceUntilCombobox.selectRowByValue("tomorrow"); await fillIn(".silence-reason", "for breaking the rules"); await click(".d-modal-cancel"); - assert.strictEqual(count(".bootbox.modal:visible"), 1); + assert.ok(exists(".dialog-body")); - await click(".modal-footer .btn-default"); - assert.ok(!exists(".bootbox.modal:visible")); + await click(".dialog-footer .btn-default"); + assert.ok(!exists(".dialog-body")); assert.ok(exists(".silence-user-modal"), "it shows the silence modal"); await click(".d-modal-cancel"); - assert.strictEqual(count(".bootbox.modal:visible"), 1); + assert.ok(exists(".dialog-body")); - await click(".modal-footer .btn-primary"); - assert.ok(!exists(".bootbox.modal:visible")); + await click(".dialog-footer .btn-primary"); + assert.ok(!exists(".dialog-body")); }); test("CTRL + ENTER accepts the modal", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/acceptance/shared-drafts-test.js b/app/assets/javascripts/discourse/tests/acceptance/shared-drafts-test.js index f21315b6e85..bbe7bc804b3 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/shared-drafts-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/shared-drafts-test.js @@ -15,7 +15,7 @@ acceptance("Shared Drafts", function () { assert.strictEqual(categoryChooser.header().value(), "3"); await click(".publish-shared-draft"); - await click(".bootbox .btn-primary"); + await click(".dialog-footer .btn-primary"); assert.ok(!exists(".shared-draft-controls")); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-bookmarks-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-bookmarks-test.js index 691ecd9ef59..d958162f7b9 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/user-bookmarks-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-bookmarks-test.js @@ -20,7 +20,7 @@ acceptance("User's bookmarks", function (needs) { await dropdown.expand(); await dropdown.selectRowByValue("remove"); - assert.notOk(exists(".bootbox.modal"), "it should not show the modal"); + assert.notOk(exists(".dialog-body"), "it should not show the modal"); }); test("it renders search controls if there are bookmarks", async function (assert) { @@ -51,10 +51,10 @@ acceptance("User's bookmarks - reminder", function (needs) { await dropdown.expand(); await dropdown.selectRowByValue("remove"); - assert.ok(exists(".bootbox.modal"), "it asks for delete confirmation"); + assert.ok(exists(".dialog-body"), "it asks for delete confirmation"); - await click(".bootbox.modal a.btn-primary"); - assert.notOk(exists(".bootbox.modal")); + await click(".dialog-footer .btn-danger"); + assert.notOk(exists(".dialog-body")); }); test("bookmarks with reminders have a clear reminder option", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/integration/components/dialog-holder-test.js b/app/assets/javascripts/discourse/tests/integration/components/dialog-holder-test.js index c83889e20ff..251e8ac4e2a 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/dialog-holder-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/dialog-holder-test.js @@ -364,4 +364,28 @@ module("Integration | Component | dialog-holder", function (hooks) { assert.notOk(query(".dialog-footer"), "no footer"); assert.notOk(query(".dialog-header"), "no header"); }); + + test("delete confirm", async function (assert) { + await render(hbs``); + + this.dialog.deleteConfirm({ message: "A delete confirm message" }); + await settled(); + + assert.strictEqual( + query(".dialog-body").innerText.trim(), + "A delete confirm message", + "dialog message is shown" + ); + + assert.strictEqual( + query(".dialog-footer .btn-danger").innerText.trim(), + I18n.t("delete"), + "dialog primary button use danger class and label is Delete" + ); + + assert.notOk( + query(".dialog-footer .btn-primary"), + ".btn-primary element is not present in the dialog" + ); + }); }); diff --git a/app/assets/javascripts/select-kit/addon/components/composer-actions.js b/app/assets/javascripts/select-kit/addon/components/composer-actions.js index da00b80e316..20b515605e9 100644 --- a/app/assets/javascripts/select-kit/addon/components/composer-actions.js +++ b/app/assets/javascripts/select-kit/addon/components/composer-actions.js @@ -9,10 +9,10 @@ import discourseComputed from "discourse-common/utils/decorators"; import Draft from "discourse/models/draft"; import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; import I18n from "I18n"; -import bootbox from "bootbox"; import { camelize } from "@ember/string"; import { equal, gt } from "@ember/object/computed"; import { isEmpty } from "@ember/utils"; +import { inject as service } from "@ember/service"; // Component can get destroyed and lose state let _topicSnapshot = null; @@ -26,6 +26,7 @@ export function _clearSnapshots() { } export default DropdownSelectBoxComponent.extend({ + dialog: service(), seq: 0, pluginApiIdentifiers: ["composer-actions"], classNames: ["composer-actions"], @@ -283,14 +284,13 @@ export default DropdownSelectBoxComponent.extend({ replyAsNewTopicSelected(options) { Draft.get("new_topic").then((response) => { if (response.draft) { - bootbox.confirm( - I18n.t("composer.composer_actions.reply_as_new_topic.confirm"), - (result) => { - if (result) { - this._replyAsNewTopicSelect(options); - } - } - ); + this.dialog.confirm({ + message: I18n.t( + "composer.composer_actions.reply_as_new_topic.confirm" + ), + confirmButtonLabel: "composer.ok_proceed", + didConfirm: () => this._replyAsNewTopicSelect(options), + }); } else { this._replyAsNewTopicSelect(options); } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index b8321bc94e9..bf30b07949b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -254,6 +254,7 @@ en: ok_value: "OK" cancel_value: "Cancel" submit: "Submit" + delete: "Delete" generic_error: "Sorry, an error has occurred." generic_error_with_reason: "An error occurred: %{error}" sign_up: "Sign Up" @@ -1918,6 +1919,9 @@ en: leave_message: "Do you really want to leave this message?" remove_allowed_user: "Do you really want to remove %{name} from this message?" remove_allowed_group: "Do you really want to remove %{name} from this message?" + leave: "Leave" + remove_group: "Remove group" + remove_user: "Remove user" email: "Email" username: "Username" @@ -2169,6 +2173,7 @@ en: edit_conflict: "edit conflict" esc: "esc" esc_label: "Click or press Esc to dismiss" + ok_proceed: "Ok, proceed" group_mentioned_limit: one: "Warning! You mentioned %{group}, however this group has more members than the administrator configured mention limit of %{count} user. Nobody will be notified." @@ -2669,7 +2674,7 @@ en: remove_tags: "Remove All Tags" confirm_remove_tags: one: "All tags will be removed from this topic. Are you sure?" - other: "All tags will be removed from %{count} topics. Are you sure?" + other: "All tags will be removed from %{count} topics. Are you sure?" progress: one: "Progress: %{count} topic" other: "Progress: %{count} topics" @@ -3998,7 +4003,6 @@ en: tag_list_joiner: ", " delete_unused: "Delete Unused Tags" delete_unused_description: "Delete all tags which are not attached to any topics or personal messages" - cancel_delete_unused: "Cancel" filters: without_category: "%{filter} %{tag} topics" with_category: "%{filter} %{tag} topics in %{category}"