Merge pull request #4070 from techAPJ/revert-post
FEATURE: revert post to a specific revision
This commit is contained in:
commit
01f2f05f2d
|
@ -29,6 +29,25 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
Discourse.Post.showRevision(postId, postVersion).then(() => this.refresh(postId, postVersion));
|
||||
},
|
||||
|
||||
revert(post, postVersion) {
|
||||
post.revertToRevision(postVersion).then((result) => {
|
||||
this.refresh(post.get('id'), postVersion);
|
||||
if (result.topic) {
|
||||
post.set('topic.slug', result.topic.slug);
|
||||
post.set('topic.title', result.topic.title);
|
||||
post.set('topic.fancy_title', result.topic.fancy_title);
|
||||
}
|
||||
if (result.category_id) {
|
||||
post.set('topic.category', Discourse.Category.findById(result.category_id));
|
||||
}
|
||||
this.send("closeModal");
|
||||
}).catch(function(e) {
|
||||
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors && e.jqXHR.responseJSON.errors[0]) {
|
||||
bootbox.alert(e.jqXHR.responseJSON.errors[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@computed('model.created_at')
|
||||
createdAtDate(createdAt) {
|
||||
return moment(createdAt).format("LLLL");
|
||||
|
@ -69,6 +88,11 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
return !prevHidden && this.currentUser && this.currentUser.get('staff');
|
||||
},
|
||||
|
||||
@computed()
|
||||
displayRevert() {
|
||||
return this.currentUser && this.currentUser.get('staff');
|
||||
},
|
||||
|
||||
isEitherRevisionHidden: Ember.computed.or("model.previous_hidden", "model.current_hidden"),
|
||||
|
||||
@computed('model.previous_hidden', 'model.current_hidden', 'displayingInline')
|
||||
|
@ -142,6 +166,8 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
hideVersion() { this.hide(this.get("model.post_id"), this.get("model.current_revision")); },
|
||||
showVersion() { this.show(this.get("model.post_id"), this.get("model.current_revision")); },
|
||||
|
||||
revertToVersion() { this.revert(this.get("post"), this.get("model.current_revision")); },
|
||||
|
||||
displayInline() { this.set("viewMode", "inline"); },
|
||||
displaySideBySide() { this.set("viewMode", "side_by_side"); },
|
||||
displaySideBySideMarkdown() { this.set("viewMode", "side_by_side_markdown"); }
|
||||
|
|
|
@ -271,6 +271,10 @@ const Post = RestModel.extend({
|
|||
json = Post.munge(json);
|
||||
this.set('actions_summary', json.actions_summary);
|
||||
}
|
||||
},
|
||||
|
||||
revertToRevision(version) {
|
||||
return Discourse.ajax(`/posts/${this.get('id')}/revisions/${version}/revert`, { type: 'PUT' });
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -73,6 +73,7 @@ const TopicRoute = Discourse.Route.extend({
|
|||
showHistory(model) {
|
||||
showModal('history', { model });
|
||||
this.controllerFor('history').refresh(model.get("id"), "latest");
|
||||
this.controllerFor('history').set('post', model);
|
||||
this.controllerFor('modal').set('modalClass', 'history-modal');
|
||||
},
|
||||
|
||||
|
|
|
@ -10,12 +10,6 @@
|
|||
</div>
|
||||
{{d-button action="loadNextVersion" icon="forward" title="post.revisions.controls.next" disabled=loadNextDisabled}}
|
||||
{{d-button action="loadLastVersion" icon="fast-forward" title="post.revisions.controls.last" disabled=loadLastDisabled}}
|
||||
{{#if displayHide}}
|
||||
{{d-button action="hideVersion" icon="trash-o" title="post.revisions.controls.hide" class="btn-danger" disabled=loading}}
|
||||
{{/if}}
|
||||
{{#if displayShow}}
|
||||
{{d-button action="showVersion" icon="undo" title="post.revisions.controls.show" disabled=loading}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div id="display-modes">
|
||||
{{d-button action="displayInline" label="post.revisions.displays.inline.button" title="post.revisions.displays.inline.title" class=inlineClass}}
|
||||
|
@ -85,5 +79,15 @@
|
|||
<div class="row">
|
||||
{{{bodyDiff}}}
|
||||
</div>
|
||||
|
||||
{{#if displayRevert}}
|
||||
{{d-button action="revertToVersion" icon="undo" label="post.revisions.controls.revert" class="btn-danger" disabled=loading}}
|
||||
{{/if}}
|
||||
{{#if displayHide}}
|
||||
{{d-button action="hideVersion" icon="eye-slash" label="post.revisions.controls.hide" class="btn-danger" disabled=loading}}
|
||||
{{/if}}
|
||||
{{#if displayShow}}
|
||||
{{d-button action="showVersion" icon="eye" label="post.revisions.controls.show" disabled=loading}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -282,6 +282,55 @@ class PostsController < ApplicationController
|
|||
render nothing: true
|
||||
end
|
||||
|
||||
def revert
|
||||
raise Discourse::NotFound unless guardian.is_staff?
|
||||
|
||||
post_id = params[:id] || params[:post_id]
|
||||
revision = params[:revision].to_i
|
||||
raise Discourse::InvalidParameters.new(:revision) if revision < 2
|
||||
|
||||
post_revision = PostRevision.find_by(post_id: post_id, number: revision)
|
||||
raise Discourse::NotFound unless post_revision
|
||||
|
||||
post = find_post_from_params
|
||||
raise Discourse::NotFound if post.blank?
|
||||
|
||||
post_revision.post = post
|
||||
guardian.ensure_can_see!(post_revision)
|
||||
guardian.ensure_can_edit!(post)
|
||||
return render_json_error(I18n.t('revert_version_same')) if post_revision.modifications["raw"].blank? && post_revision.modifications["title"].blank? && post_revision.modifications["category_id"].blank?
|
||||
|
||||
topic = Topic.with_deleted.find(post.topic_id)
|
||||
|
||||
changes = {}
|
||||
changes[:raw] = post_revision.modifications["raw"][0] if post_revision.modifications["raw"].present? && post_revision.modifications["raw"][0] != post.raw
|
||||
if post.is_first_post?
|
||||
changes[:title] = post_revision.modifications["title"][0] if post_revision.modifications["title"].present? && post_revision.modifications["title"][0] != topic.title
|
||||
changes[:category_id] = post_revision.modifications["category_id"][0] if post_revision.modifications["category_id"].present? && post_revision.modifications["category_id"][0] != topic.category.id
|
||||
end
|
||||
return render_json_error(I18n.t('revert_version_same')) unless changes.length > 0
|
||||
changes[:edit_reason] = "reverted to version ##{post_revision.number.to_i - 1}"
|
||||
|
||||
revisor = PostRevisor.new(post, topic)
|
||||
revisor.revise!(current_user, changes)
|
||||
|
||||
return render_json_error(post) if post.errors.present?
|
||||
return render_json_error(topic) if topic.errors.present?
|
||||
|
||||
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
|
||||
post_serializer.draft_sequence = DraftSequence.current(current_user, topic.draft_key)
|
||||
link_counts = TopicLink.counts_for(guardian, topic, [post])
|
||||
post_serializer.single_post_link_counts = link_counts[post.id] if link_counts.present?
|
||||
|
||||
result = { post: post_serializer.as_json }
|
||||
if post.is_first_post?
|
||||
result[:topic] = BasicTopicSerializer.new(topic, scope: guardian, root: false).as_json if post_revision.modifications["title"].present?
|
||||
result[:category_id] = post_revision.modifications["category_id"][0] if post_revision.modifications["category_id"].present?
|
||||
end
|
||||
|
||||
render_json_dump(result)
|
||||
end
|
||||
|
||||
def bookmark
|
||||
post = find_post_from_params
|
||||
|
||||
|
|
|
@ -1639,6 +1639,7 @@ en:
|
|||
last: "Last revision"
|
||||
hide: "Hide revision"
|
||||
show: "Show revision"
|
||||
revert: "Revert to this revision"
|
||||
comparing_previous_to_current_out_of_total: "<strong>{{previous}}</strong> <i class='fa fa-arrows-h'></i> <strong>{{current}}</strong> / {{total}}"
|
||||
displays:
|
||||
inline:
|
||||
|
|
|
@ -205,6 +205,7 @@ en:
|
|||
top: "Top topics"
|
||||
posts: "Latest posts"
|
||||
too_late_to_edit: "That post was created too long ago. It can no longer be edited or deleted."
|
||||
revert_version_same: "The current version is same as the version you are trying to revert to."
|
||||
|
||||
excerpt_image: "image"
|
||||
|
||||
|
|
|
@ -386,6 +386,7 @@ Discourse::Application.routes.draw do
|
|||
get "revisions/:revision" => "posts#revisions", constraints: { revision: /\d+/ }
|
||||
put "revisions/:revision/hide" => "posts#hide_revision", constraints: { revision: /\d+/ }
|
||||
put "revisions/:revision/show" => "posts#show_revision", constraints: { revision: /\d+/ }
|
||||
put "revisions/:revision/revert" => "posts#revert", constraints: { revision: /\d+/ }
|
||||
put "recover"
|
||||
collection do
|
||||
delete "destroy_many"
|
||||
|
|
|
@ -830,6 +830,79 @@ describe PostsController do
|
|||
|
||||
end
|
||||
|
||||
describe 'revert post to a specific revision' do
|
||||
include_examples 'action requires login', :put, :revert, post_id: 123, revision: 2
|
||||
|
||||
let(:post) { Fabricate(:post, user: logged_in_as, raw: "Lorem ipsum dolor sit amet, cu nam libris tractatos, ancillae senserit ius ex") }
|
||||
let(:post_revision) { Fabricate(:post_revision, post: post, modifications: {"raw" => ["this is original post body.", "this is edited post body."]}) }
|
||||
let(:blank_post_revision) { Fabricate(:post_revision, post: post, modifications: {"edit_reason" => ["edit reason #1", "edit reason #2"]}) }
|
||||
let(:same_post_revision) { Fabricate(:post_revision, post: post, modifications: {"raw" => ["Lorem ipsum dolor sit amet, cu nam libris tractatos, ancillae senserit ius ex", "this is edited post body."]}) }
|
||||
|
||||
let(:revert_params) do
|
||||
{
|
||||
post_id: post.id,
|
||||
revision: post_revision.number
|
||||
}
|
||||
end
|
||||
let(:moderator) { Fabricate(:moderator) }
|
||||
|
||||
describe 'when logged in as a regular user' do
|
||||
let(:logged_in_as) { log_in }
|
||||
|
||||
it "does not work" do
|
||||
xhr :put, :revert, revert_params
|
||||
expect(response).to_not be_success
|
||||
end
|
||||
end
|
||||
|
||||
describe "when logged in as staff" do
|
||||
let(:logged_in_as) { log_in(:moderator) }
|
||||
|
||||
it "throws an exception when revision is < 2" do
|
||||
expect {
|
||||
xhr :put, :revert, post_id: post.id, revision: 1
|
||||
}.to raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
it "fails when post_revision record is not found" do
|
||||
xhr :put, :revert, post_id: post.id, revision: post_revision.number + 1
|
||||
expect(response).to_not be_success
|
||||
end
|
||||
|
||||
it "fails when post record is not found" do
|
||||
xhr :put, :revert, post_id: post.id + 1, revision: post_revision.number
|
||||
expect(response).to_not be_success
|
||||
end
|
||||
|
||||
it "fails when revision is blank" do
|
||||
xhr :put, :revert, post_id: post.id, revision: blank_post_revision.number
|
||||
|
||||
expect(response.status).to eq(422)
|
||||
expect(JSON.parse(response.body)['errors']).to include(I18n.t('revert_version_same'))
|
||||
end
|
||||
|
||||
it "fails when revised version is same as current version" do
|
||||
xhr :put, :revert, post_id: post.id, revision: same_post_revision.number
|
||||
|
||||
expect(response.status).to eq(422)
|
||||
expect(JSON.parse(response.body)['errors']).to include(I18n.t('revert_version_same'))
|
||||
end
|
||||
|
||||
it "works!" do
|
||||
xhr :put, :revert, revert_params
|
||||
expect(response).to be_success
|
||||
end
|
||||
|
||||
it "supports reverting posts in deleted topics" do
|
||||
first_post = post.topic.ordered_posts.first
|
||||
PostDestroyer.new(moderator, first_post).destroy
|
||||
|
||||
xhr :put, :revert, revert_params
|
||||
expect(response).to be_success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'expandable embedded posts' do
|
||||
let(:post) { Fabricate(:post) }
|
||||
|
||||
|
|
Loading…
Reference in New Issue