diff --git a/app/assets/javascripts/discourse/app/controllers/delete-topic-confirm.js b/app/assets/javascripts/discourse/app/controllers/delete-topic-confirm.js new file mode 100644 index 00000000000..f3e06c43f31 --- /dev/null +++ b/app/assets/javascripts/discourse/app/controllers/delete-topic-confirm.js @@ -0,0 +1,38 @@ +import I18n from "I18n"; +import { action } from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; +import Controller, { inject } from "@ember/controller"; +import ModalFunctionality from "discourse/mixins/modal-functionality"; + +// Modal that displays confirmation text when user deletes a topic +// The modal will display only if the topic exceeds a certain amount of views +export default Controller.extend(ModalFunctionality, { + topicController: inject("topic"), + deletingTopic: false, + + @discourseComputed("deletingTopic") + buttonTitle(deletingTopic) { + return deletingTopic + ? I18n.t("deleting") + : I18n.t("post.controls.delete_topic_confirm_modal_yes"); + }, + + onShow() { + this.set("deletingTopic", false); + }, + + @action + deleteTopic() { + this.set("deletingTopic", true); + + this.topicController.model + .destroy(this.currentUser) + .then(() => this.send("closeModal")) + .catch(() => { + this.flash(I18n.t("post.controls.delete_topic_error"), "alert-error"); + this.set("deletingTopic", false); + }); + + return false; + }, +}); diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js index 8937823e4e5..b5e7e400dbf 100644 --- a/app/assets/javascripts/discourse/app/controllers/topic.js +++ b/app/assets/javascripts/discourse/app/controllers/topic.js @@ -1295,7 +1295,20 @@ export default Controller.extend(bufferedProperty("model"), { }, deleteTopic() { - this.model.destroy(this.currentUser); + if ( + this.model.views > this.siteSettings.min_topic_views_for_delete_confirm + ) { + this.deleteTopicModal(); + } else { + this.model.destroy(this.currentUser); + } + }, + + deleteTopicModal() { + showModal("delete-topic-confirm", { + model: this.model, + title: "topic.actions.delete", + }); }, retryOnRateLimit(times, promise, topicId) { diff --git a/app/assets/javascripts/discourse/app/templates/modal/delete-topic-confirm.hbs b/app/assets/javascripts/discourse/app/templates/modal/delete-topic-confirm.hbs new file mode 100644 index 00000000000..3184aad17f0 --- /dev/null +++ b/app/assets/javascripts/discourse/app/templates/modal/delete-topic-confirm.hbs @@ -0,0 +1,13 @@ +{{#d-modal-body}} +

{{i18n "post.controls.delete_topic_confirm_modal" minViews=siteSettings.min_topic_views_for_delete_confirm}}

+{{/d-modal-body}} + + diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-test.js index 8dd35f47e09..4b98805a81c 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/topic-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-test.js @@ -4,7 +4,7 @@ import { test } from "qunit"; import I18n from "I18n"; import { withPluginApi } from "discourse/lib/plugin-api"; import selectKit from "discourse/tests/helpers/select-kit-helper"; -import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import { acceptance, visible } from "discourse/tests/helpers/qunit-helpers"; import { IMAGE_VERSION as v } from "pretty-text/emoji/version"; function selectText(selector) { @@ -209,6 +209,35 @@ acceptance("Topic", function (needs) { ); }); + test("Deleting a topic", async (assert) => { + await visit("/t/internationalization-localization/280"); + await click(".topic-post:eq(0) button.show-more-actions"); + await click(".widget-button.delete"); + await click(".toggle-admin-menu"); + assert.ok(exists(".topic-admin-recover"), "it shows the recover button"); + }); + + test("Deleting a popular topic displays confirmation modal", async function (assert) { + this.siteSettings.min_topic_views_for_delete_confirm = 10; + await visit("/t/internationalization-localization/280"); + await click(".topic-post:eq(0) button.show-more-actions"); + await click(".widget-button.delete"); + assert.ok( + visible(".delete-topic-confirm-modal"), + "it shows the delete confirmation modal" + ); + + await click(".delete-topic-confirm-modal .btn-primary"); + assert.ok( + !visible(".delete-topic-confirm-modal"), + "it hides the delete confirmation modal" + ); + await click(".widget-button.delete"); + await click(".delete-topic-confirm-modal .btn-danger"); + await click(".toggle-admin-menu"); + assert.ok(exists(".topic-admin-recover"), "it shows the recover button"); + }); + test("Group category moderator posts", async (assert) => { await visit("/t/topic-for-group-moderators/2480"); diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js index d552ef1f7be..b68ad5bfdcd 100644 --- a/app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js @@ -62,6 +62,34 @@ test("editTopic", function (assert) { ); }); +test("deleteTopic", function (assert) { + const model = Topic.create(); + let destroyed = false; + let modalDisplayed = false; + model.destroy = () => { + destroyed = true; + return Promise.resolve(); + }; + const controller = this.subject({ + model, + siteSettings: { + min_topic_views_for_delete_confirm: 5, + }, + deleteTopicModal: () => { + modalDisplayed = true; + }, + }); + + model.set("views", 10000); + controller.send("deleteTopic"); + assert.not(destroyed, "don't destroy popular topic"); + assert.ok(modalDisplayed, "display confirmation modal for popular topic"); + + model.set("views", 3); + controller.send("deleteTopic"); + assert.ok(destroyed, "destroy not popular topic"); +}); + test("toggleMultiSelect", function (assert) { const model = Topic.create(); const controller = this.subject({ model }); diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index ad5914f494d..99b364c7c92 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -360,6 +360,8 @@ en: preview: "preview" cancel: "cancel" + deleting: "Deleting..." + save: "Save Changes" saving: "Saving..." saved: "Saved!" @@ -2813,6 +2815,10 @@ en: unlock_post_description: "allow the poster to edit this post" delete_topic_disallowed_modal: "You don't have permission to delete this topic. If you really want it to be deleted, submit a flag for moderator attention together with reasoning." delete_topic_disallowed: "you don't have permission to delete this topic" + delete_topic_confirm_modal: "This topic currently has over %{minViews} views and may be a popular search destination. Are you sure you want to delete this topic entirely, instead of editing it to improve it?" + delete_topic_confirm_modal_yes: "Yes, delete this topic" + delete_topic_confirm_modal_no: "No, keep this topic" + delete_topic_error: "An error occurred while deleting this topic" delete_topic: "delete topic" add_post_notice: "Add Staff Notice" remove_post_notice: "Remove Staff Notice" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index e05bf4ff74c..47b635c5f21 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1425,6 +1425,7 @@ en: max_post_length: "Maximum allowed post length in characters" topic_featured_link_enabled: "Enable posting a link with topics." show_topic_featured_link_in_digest: "Show the topic featured link in the digest email." + min_topic_views_for_delete_confirm: "Minimum amount of views a topic must have for a confirmation popup to appear when it gets deleted" min_topic_title_length: "Minimum allowed topic title length in characters" max_topic_title_length: "Maximum allowed topic title length in characters" min_personal_message_title_length: "Minimum allowed title length for a message in characters" diff --git a/config/site_settings.yml b/config/site_settings.yml index 7073f13238e..3882784e157 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -700,6 +700,9 @@ posting: ja: 3 zh_CN: 3 zh_TW: 3 + min_topic_views_for_delete_confirm: + client: true + default: 5000 min_topic_title_length: client: true default: 15