FEATURE: Ask user to confirm topic deletion when views exceed a certain amount (#11006)

Before deleting a topic that has a high number of views (default of 5000), the user will be prompted with a confirmation popup. This works for all delete buttons on the topic located in: topic-timeline, topic-admin-menu, topic-footer-buttons, and post-menu if the post's ID is 1.

The delete button will be disabled while deletion is in progress, to prevent any unwanted behavior.

A site setting is also available to change the minimum amount of views required to display the confirmation popup.

All kudos are going to @RickyC0626. I only rebased with master and added few qunit tests to ensure that this feature works as expected.

Original PR: #10459
This commit is contained in:
Krzysztof Kotlarek 2020-10-29 14:18:43 +11:00 committed by GitHub
parent 72810853ea
commit 8e7c746e56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 133 additions and 2 deletions

View File

@ -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;
},
});

View File

@ -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) {

View File

@ -0,0 +1,13 @@
{{#d-modal-body}}
<p>{{i18n "post.controls.delete_topic_confirm_modal" minViews=siteSettings.min_topic_views_for_delete_confirm}}</p>
{{/d-modal-body}}
<div class="modal-footer">
{{d-button action=(action "deleteTopic")
disabled=deletingTopic
translatedLabel=buttonTitle
class="btn-danger"}}
{{d-button action=(route-action "closeModal")
label="post.controls.delete_topic_confirm_modal_no"
class="btn-primary"}}
</div>

View File

@ -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");

View File

@ -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 });

View File

@ -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"

View File

@ -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"

View File

@ -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