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) {
|
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) {
|
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'),
|
postHidden: Ember.computed.alias('hidden'),
|
||||||
|
|
|
@ -259,6 +259,11 @@ class PostsController < ApplicationController
|
||||||
render json: post.reply_ids(guardian).to_json
|
render json: post.reply_ids(guardian).to_json
|
||||||
end
|
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
|
def destroy
|
||||||
post = find_post_from_params
|
post = find_post_from_params
|
||||||
RateLimiter.new(current_user, "delete_post", 3, 1.minute).performed! unless current_user.staff?
|
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
|
MAX_REPLY_LEVEL ||= 1000
|
||||||
|
|
||||||
def reply_ids(guardian = nil)
|
def reply_ids(guardian = nil, only_replies_to_single_post: true)
|
||||||
replies = Post.exec_sql("
|
builder = SqlBuilder.new(<<~SQL, Post)
|
||||||
WITH RECURSIVE breadcrumb(id, level) AS (
|
WITH RECURSIVE breadcrumb(id, level) AS (
|
||||||
SELECT :post_id, 0
|
SELECT :post_id, 0
|
||||||
UNION
|
UNION
|
||||||
SELECT reply_id, level + 1
|
SELECT reply_id, level + 1
|
||||||
FROM post_replies, breadcrumb
|
FROM post_replies AS r
|
||||||
WHERE post_id = id
|
JOIN breadcrumb AS b ON (r.post_id = b.id)
|
||||||
AND post_id <> reply_id
|
WHERE r.post_id <> r.reply_id
|
||||||
AND level < #{MAX_REPLY_LEVEL}
|
AND b.level < :max_reply_level
|
||||||
), breadcrumb_with_count AS (
|
), breadcrumb_with_count AS (
|
||||||
SELECT id, level, COUNT(*)
|
SELECT
|
||||||
FROM post_replies, breadcrumb
|
id,
|
||||||
WHERE reply_id = id
|
level,
|
||||||
AND reply_id <> post_id
|
COUNT(*) AS count
|
||||||
GROUP BY id, level
|
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
|
SELECT id, level
|
||||||
", post_id: id).to_a
|
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 } }
|
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
|
secured_ids = Post.secured(guardian).where(id: replies.map { |r| r[:id] }).pluck(:id).to_set
|
||||||
|
|
|
@ -2812,6 +2812,9 @@ en:
|
||||||
replies:
|
replies:
|
||||||
one: "[1 reply]"
|
one: "[1 reply]"
|
||||||
other: "[%{count} replies]"
|
other: "[%{count} replies]"
|
||||||
|
delete_replies:
|
||||||
|
one: "Also delete the %{count} reply to this post?"
|
||||||
|
other: "Also delete the %{count} replies to this post?"
|
||||||
|
|
||||||
dispositions:
|
dispositions:
|
||||||
agreed: "agreed"
|
agreed: "agreed"
|
||||||
|
|
|
@ -451,6 +451,7 @@ Discourse::Application.routes.draw do
|
||||||
get "posts/by_number/:topic_id/:post_number" => "posts#by_number"
|
get "posts/by_number/:topic_id/:post_number" => "posts#by_number"
|
||||||
get "posts/:id/reply-history" => "posts#reply_history"
|
get "posts/:id/reply-history" => "posts#reply_history"
|
||||||
get "posts/:id/reply-ids" => "posts#reply_ids"
|
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/deleted" => "posts#deleted_posts", constraints: { username: RouteFormat.username }
|
||||||
get "posts/:username/flagged" => "posts#flagged_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
|
expect(p6.reply_ids).to be_empty # quotes itself
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe 'urls' do
|
describe 'urls' do
|
||||||
|
|
Loading…
Reference in New Issue