FEATURE: Add option to delete all replies of flagged post
This commit is contained in:
parent
035312d501
commit
ed4c0c4a63
|
@ -62,11 +62,74 @@ export default Post.extend({
|
|||
},
|
||||
|
||||
deferFlags(deletePost) {
|
||||
return ajax('/admin/flags/defer/' + this.id, { type: 'POST', cache: false, data: { delete_post: deletePost } }).catch(popupAjaxError);
|
||||
const action = () => {
|
||||
return ajax('/admin/flags/defer/' + this.id, {
|
||||
type: 'POST', cache: false, data: { delete_post: deletePost }
|
||||
});
|
||||
};
|
||||
|
||||
if (deletePost && this._hasDeletableReplies()) {
|
||||
return this._actOnFlagAndDeleteReplies(action);
|
||||
} else {
|
||||
return action().catch(popupAjaxError);
|
||||
}
|
||||
},
|
||||
|
||||
agreeFlags(actionOnPost) {
|
||||
return ajax('/admin/flags/agree/' + this.id, { type: 'POST', cache: false, data: { action_on_post: actionOnPost } }).catch(popupAjaxError);
|
||||
const action = () => {
|
||||
return ajax('/admin/flags/agree/' + this.id, {
|
||||
type: 'POST', cache: false, data: { action_on_post: actionOnPost }
|
||||
});
|
||||
};
|
||||
|
||||
if (actionOnPost === 'delete' && this._hasDeletableReplies()) {
|
||||
return this._actOnFlagAndDeleteReplies(action);
|
||||
} else {
|
||||
return action().catch(popupAjaxError);
|
||||
}
|
||||
},
|
||||
|
||||
_hasDeletableReplies() {
|
||||
return this.get('post_number') > 1 && this.get('reply_count') > 0;
|
||||
},
|
||||
|
||||
_actOnFlagAndDeleteReplies(action) {
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
return ajax(`/posts/${this.id}/reply-ids/all.json`).then(replies => {
|
||||
const buttons = [];
|
||||
|
||||
buttons.push({
|
||||
label: I18n.t('no_value'),
|
||||
callback() {
|
||||
action()
|
||||
.then(resolve)
|
||||
.catch(error => {
|
||||
popupAjaxError(error);
|
||||
reject();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
label: I18n.t('yes_value'),
|
||||
class: "btn-danger",
|
||||
callback() {
|
||||
Post.deleteMany(replies.map(r => r.id))
|
||||
.then(action)
|
||||
.then(resolve)
|
||||
.catch(error => {
|
||||
popupAjaxError(error);
|
||||
reject();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
bootbox.dialog(I18n.t("admin.flags.delete_replies", { count: replies.length }), buttons);
|
||||
}).catch(error => {
|
||||
popupAjaxError(error);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
postHidden: Ember.computed.alias('hidden'),
|
||||
|
|
|
@ -259,6 +259,11 @@ class PostsController < ApplicationController
|
|||
render json: post.reply_ids(guardian).to_json
|
||||
end
|
||||
|
||||
def all_reply_ids
|
||||
post = find_post_from_params
|
||||
render json: post.reply_ids(guardian, only_replies_to_single_post: false).to_json
|
||||
end
|
||||
|
||||
def destroy
|
||||
post = find_post_from_params
|
||||
RateLimiter.new(current_user, "delete_post", 3, 1.minute).performed! unless current_user.staff?
|
||||
|
|
|
@ -682,26 +682,39 @@ class Post < ActiveRecord::Base
|
|||
|
||||
MAX_REPLY_LEVEL ||= 1000
|
||||
|
||||
def reply_ids(guardian = nil)
|
||||
replies = Post.exec_sql("
|
||||
def reply_ids(guardian = nil, only_replies_to_single_post: true)
|
||||
builder = SqlBuilder.new(<<~SQL, Post)
|
||||
WITH RECURSIVE breadcrumb(id, level) AS (
|
||||
SELECT :post_id, 0
|
||||
UNION
|
||||
SELECT reply_id, level + 1
|
||||
FROM post_replies, breadcrumb
|
||||
WHERE post_id = id
|
||||
AND post_id <> reply_id
|
||||
AND level < #{MAX_REPLY_LEVEL}
|
||||
FROM post_replies AS r
|
||||
JOIN breadcrumb AS b ON (r.post_id = b.id)
|
||||
WHERE r.post_id <> r.reply_id
|
||||
AND b.level < :max_reply_level
|
||||
), breadcrumb_with_count AS (
|
||||
SELECT id, level, COUNT(*)
|
||||
FROM post_replies, breadcrumb
|
||||
WHERE reply_id = id
|
||||
AND reply_id <> post_id
|
||||
SELECT
|
||||
id,
|
||||
level,
|
||||
COUNT(*) AS count
|
||||
FROM post_replies AS r
|
||||
JOIN breadcrumb AS b ON (r.reply_id = b.id)
|
||||
WHERE r.reply_id <> r.post_id
|
||||
GROUP BY id, level
|
||||
)
|
||||
SELECT id, level FROM breadcrumb_with_count WHERE level > 0 AND count = 1 ORDER BY id
|
||||
", post_id: id).to_a
|
||||
SELECT id, level
|
||||
FROM breadcrumb_with_count
|
||||
/*where*/
|
||||
ORDER BY id
|
||||
SQL
|
||||
|
||||
builder.where("level > 0")
|
||||
|
||||
# ignore posts that aren't replies to exactly one post
|
||||
# for example it skips a post when it contains 2 quotes (which are replies) from different posts
|
||||
builder.where("count = 1") if only_replies_to_single_post
|
||||
|
||||
replies = builder.exec(post_id: id, max_reply_level: MAX_REPLY_LEVEL).to_a
|
||||
replies.map! { |r| { id: r["id"].to_i, level: r["level"].to_i } }
|
||||
|
||||
secured_ids = Post.secured(guardian).where(id: replies.map { |r| r[:id] }).pluck(:id).to_set
|
||||
|
|
|
@ -2812,6 +2812,9 @@ en:
|
|||
replies:
|
||||
one: "[1 reply]"
|
||||
other: "[%{count} replies]"
|
||||
delete_replies:
|
||||
one: "Also delete the %{count} reply to this post?"
|
||||
other: "Also delete the %{count} replies to this post?"
|
||||
|
||||
dispositions:
|
||||
agreed: "agreed"
|
||||
|
|
|
@ -451,6 +451,7 @@ Discourse::Application.routes.draw do
|
|||
get "posts/by_number/:topic_id/:post_number" => "posts#by_number"
|
||||
get "posts/:id/reply-history" => "posts#reply_history"
|
||||
get "posts/:id/reply-ids" => "posts#reply_ids"
|
||||
get "posts/:id/reply-ids/all" => "posts#all_reply_ids"
|
||||
get "posts/:username/deleted" => "posts#deleted_posts", constraints: { username: RouteFormat.username }
|
||||
get "posts/:username/flagged" => "posts#flagged_posts", constraints: { username: RouteFormat.username }
|
||||
|
||||
|
|
|
@ -863,6 +863,14 @@ describe Post do
|
|||
expect(p6.reply_ids).to be_empty # quotes itself
|
||||
end
|
||||
|
||||
it "does not skip any replies" do
|
||||
expect(p1.reply_ids(only_replies_to_single_post: false)).to eq([{ id: p2.id, level: 1 }, { id: p4.id, level: 2 }, { id: p5.id, level: 3 }, { id: p6.id, level: 2 }])
|
||||
expect(p2.reply_ids(only_replies_to_single_post: false)).to eq([{ id: p4.id, level: 1 }, { id: p5.id, level: 2 }, { id: p6.id, level: 1 }])
|
||||
expect(p3.reply_ids(only_replies_to_single_post: false)).to eq([{ id: p5.id, level: 1 }])
|
||||
expect(p4.reply_ids(only_replies_to_single_post: false)).to eq([{ id: p5.id, level: 1 }])
|
||||
expect(p5.reply_ids(only_replies_to_single_post: false)).to be_empty # has no replies
|
||||
expect(p6.reply_ids(only_replies_to_single_post: false)).to be_empty # quotes itself
|
||||
end
|
||||
end
|
||||
|
||||
describe 'urls' do
|
||||
|
|
Loading…
Reference in New Issue