Merge pull request #2873 from mcwumbly/hide-post-revisions
FEATURE: ability to hide/show specific post revisions
This commit is contained in:
commit
1f42e85e0d
|
@ -25,6 +25,20 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hide: function(postId, postVersion) {
|
||||||
|
var self = this;
|
||||||
|
Discourse.Post.hideRevision(postId, postVersion).then(function (result) {
|
||||||
|
self.refresh(postId, postVersion);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function(postId, postVersion) {
|
||||||
|
var self = this;
|
||||||
|
Discourse.Post.showRevision(postId, postVersion).then(function (result) {
|
||||||
|
self.refresh(postId, postVersion);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
createdAtDate: function() { return moment(this.get("created_at")).format("LLLL"); }.property("created_at"),
|
createdAtDate: function() { return moment(this.get("created_at")).format("LLLL"); }.property("created_at"),
|
||||||
|
|
||||||
previousVersion: function() { return this.get("version") - 1; }.property("version"),
|
previousVersion: function() { return this.get("version") - 1; }.property("version"),
|
||||||
|
@ -35,6 +49,9 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||||
displayGoToNext: function() { return this.get("version") < this.get("revisions_count"); }.property("version", "revisions_count"),
|
displayGoToNext: function() { return this.get("version") < this.get("revisions_count"); }.property("version", "revisions_count"),
|
||||||
displayGoToLast: function() { return this.get("version") < this.get("revisions_count") - 1; }.property("version", "revisions_count"),
|
displayGoToLast: function() { return this.get("version") < this.get("revisions_count") - 1; }.property("version", "revisions_count"),
|
||||||
|
|
||||||
|
displayShow: function() { return this.get("hidden") && Discourse.User.currentProp('staff'); }.property("hidden"),
|
||||||
|
displayHide: function() { return !this.get("hidden") && Discourse.User.currentProp('staff'); }.property("hidden"),
|
||||||
|
|
||||||
displayingInline: Em.computed.equal("viewMode", "inline"),
|
displayingInline: Em.computed.equal("viewMode", "inline"),
|
||||||
displayingSideBySide: Em.computed.equal("viewMode", "side_by_side"),
|
displayingSideBySide: Em.computed.equal("viewMode", "side_by_side"),
|
||||||
displayingSideBySideMarkdown: Em.computed.equal("viewMode", "side_by_side_markdown"),
|
displayingSideBySideMarkdown: Em.computed.equal("viewMode", "side_by_side_markdown"),
|
||||||
|
@ -118,6 +135,9 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||||
loadNextVersion: function() { this.refresh(this.get("post_id"), this.get("version") + 1); },
|
loadNextVersion: function() { this.refresh(this.get("post_id"), this.get("version") + 1); },
|
||||||
loadLastVersion: function() { this.refresh(this.get("post_id"), this.get("revisions_count")); },
|
loadLastVersion: function() { this.refresh(this.get("post_id"), this.get("revisions_count")); },
|
||||||
|
|
||||||
|
hideVersion: function() { this.hide(this.get("post_id"), this.get("version")); },
|
||||||
|
showVersion: function() { this.show(this.get("post_id"), this.get("version")); },
|
||||||
|
|
||||||
displayInline: function() { this.set("viewMode", "inline"); },
|
displayInline: function() { this.set("viewMode", "inline"); },
|
||||||
displaySideBySide: function() { this.set("viewMode", "side_by_side"); },
|
displaySideBySide: function() { this.set("viewMode", "side_by_side"); },
|
||||||
displaySideBySideMarkdown: function() { this.set("viewMode", "side_by_side_markdown"); }
|
displaySideBySideMarkdown: function() { this.set("viewMode", "side_by_side_markdown"); }
|
||||||
|
|
|
@ -471,6 +471,14 @@ Discourse.Post.reopenClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hideRevision: function(postId, version) {
|
||||||
|
return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/hide", { type: 'PUT' });
|
||||||
|
},
|
||||||
|
|
||||||
|
showRevision: function(postId, version) {
|
||||||
|
return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/show", { type: 'PUT' });
|
||||||
|
},
|
||||||
|
|
||||||
loadQuote: function(postId) {
|
loadQuote: function(postId) {
|
||||||
return Discourse.ajax("/posts/" + postId + ".json").then(function (result) {
|
return Discourse.ajax("/posts/" + postId + ".json").then(function (result) {
|
||||||
var post = Discourse.Post.create(result);
|
var post = Discourse.Post.create(result);
|
||||||
|
|
|
@ -12,6 +12,12 @@
|
||||||
</div>
|
</div>
|
||||||
<button title="{{i18n post.revisions.controls.next}}" {{bind-attr class=":btn :standard displayGoToNext::invisible" disabled=loading}} {{action "loadNextVersion"}}><i class="fa fa-forward"></i></button>
|
<button title="{{i18n post.revisions.controls.next}}" {{bind-attr class=":btn :standard displayGoToNext::invisible" disabled=loading}} {{action "loadNextVersion"}}><i class="fa fa-forward"></i></button>
|
||||||
<button title="{{i18n post.revisions.controls.last}}" {{bind-attr class=":btn :standard displayGoToLast::invisible" disabled=loading}} {{action "loadLastVersion"}}><i class="fa fa-fast-forward"></i></button>
|
<button title="{{i18n post.revisions.controls.last}}" {{bind-attr class=":btn :standard displayGoToLast::invisible" disabled=loading}} {{action "loadLastVersion"}}><i class="fa fa-fast-forward"></i></button>
|
||||||
|
{{#if displayHide}}
|
||||||
|
<button title="{{i18n post.revisions.controls.hide}}" {{bind-attr class=":btn :standard" disabled=loading}} {{action "hideVersion"}}><i class="fa fa-trash-o"></i></button>
|
||||||
|
{{/if}}
|
||||||
|
{{#if displayShow}}
|
||||||
|
<button title="{{i18n post.revisions.controls.show}}" {{bind-attr class=":btn :standard" disabled=loading}} {{action "showVersion"}}><i class="fa fa-undo"></i></button>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div id="display-modes">
|
<div id="display-modes">
|
||||||
<button {{bind-attr class=":btn displayingInline:btn-primary:standard"}} title="{{i18n post.revisions.displays.inline.title}}" {{action "displayInline"}}>{{{i18n post.revisions.displays.inline.button}}}</button>
|
<button {{bind-attr class=":btn displayingInline:btn-primary:standard"}} title="{{i18n post.revisions.displays.inline.title}}" {{action "displayInline"}}>{{{i18n post.revisions.displays.inline.button}}}</button>
|
||||||
|
|
|
@ -216,6 +216,20 @@ class PostsController < ApplicationController
|
||||||
render_json_dump(post_revision_serializer)
|
render_json_dump(post_revision_serializer)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hide_revision
|
||||||
|
post_revision = find_post_revision_from_params
|
||||||
|
guardian.ensure_can_hide_post_revision! post_revision
|
||||||
|
post_revision.hide!
|
||||||
|
render nothing: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_revision
|
||||||
|
post_revision = find_post_revision_from_params
|
||||||
|
guardian.ensure_can_show_post_revision! post_revision
|
||||||
|
post_revision.show!
|
||||||
|
render nothing: true
|
||||||
|
end
|
||||||
|
|
||||||
def bookmark
|
def bookmark
|
||||||
post = find_post_from_params
|
post = find_post_from_params
|
||||||
if current_user
|
if current_user
|
||||||
|
|
|
@ -29,8 +29,8 @@ class PostRevision < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def wiki_changes
|
def wiki_changes
|
||||||
prev = lookup("wiki", 0)
|
prev = previous("wiki")
|
||||||
cur = lookup("wiki", 1)
|
cur = current("wiki")
|
||||||
return if prev == cur
|
return if prev == cur
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -40,8 +40,8 @@ class PostRevision < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_type_changes
|
def post_type_changes
|
||||||
prev = lookup("post_type", 0)
|
prev = previous("post_type")
|
||||||
cur = lookup("post_type", 1)
|
cur = current("post_type")
|
||||||
return if prev == cur
|
return if prev == cur
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -75,48 +75,96 @@ class PostRevision < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def previous(field)
|
def previous(field)
|
||||||
lookup_with_fallback(field, 0)
|
val = lookup(field)
|
||||||
|
if val.nil?
|
||||||
|
val = lookup_in_previous_revisions(field)
|
||||||
|
end
|
||||||
|
|
||||||
|
if val.nil?
|
||||||
|
val = lookup_in_post(field)
|
||||||
|
end
|
||||||
|
|
||||||
|
val
|
||||||
end
|
end
|
||||||
|
|
||||||
def current(field)
|
def current(field)
|
||||||
lookup_with_fallback(field, 1)
|
val = lookup_in_next_revision(field)
|
||||||
|
if val.nil?
|
||||||
|
val = lookup_in_post(field)
|
||||||
|
end
|
||||||
|
|
||||||
|
if val.nil?
|
||||||
|
val = lookup(field)
|
||||||
|
end
|
||||||
|
|
||||||
|
if val.nil?
|
||||||
|
val = lookup_in_previous_revisions(field)
|
||||||
|
end
|
||||||
|
|
||||||
|
return val
|
||||||
end
|
end
|
||||||
|
|
||||||
def previous_revisions
|
def previous_revisions
|
||||||
@previous_revs ||= PostRevision.where("post_id = ? AND number < ?", post_id, number)
|
@previous_revs ||= PostRevision.where("post_id = ? AND number < ? AND hidden = ?", post_id, number, false)
|
||||||
.order("number desc")
|
.order("number desc")
|
||||||
.to_a
|
.to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def next_revision
|
||||||
|
@next_revision ||= PostRevision.where("post_id = ? AND number > ? AND hidden = ?", post_id, number, false)
|
||||||
|
.order("number asc")
|
||||||
|
.to_a.first
|
||||||
|
end
|
||||||
|
|
||||||
def has_topic_data?
|
def has_topic_data?
|
||||||
post && post.post_number == 1
|
post && post.post_number == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def lookup_with_fallback(field, index)
|
def lookup_in_previous_revisions(field)
|
||||||
|
previous_revisions.each do |v|
|
||||||
unless val = lookup(field, index)
|
val = v.lookup(field)
|
||||||
previous_revisions.each do |v|
|
return val unless val.nil?
|
||||||
break if val = v.lookup(field, 1)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
unless val
|
nil
|
||||||
if ["cooked", "raw"].include?(field)
|
end
|
||||||
val = post.send(field)
|
|
||||||
else
|
def lookup_in_next_revision(field)
|
||||||
val = post.topic.send(field)
|
if next_revision
|
||||||
end
|
return next_revision.lookup(field)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def lookup_in_post(field)
|
||||||
|
if !post
|
||||||
|
return
|
||||||
|
elsif ["cooked", "raw"].include?(field)
|
||||||
|
val = post.send(field)
|
||||||
|
elsif ["title", "category_id"].include?(field)
|
||||||
|
val = post.topic.send(field)
|
||||||
end
|
end
|
||||||
|
|
||||||
val
|
val
|
||||||
end
|
end
|
||||||
|
|
||||||
def lookup(field, index)
|
def lookup(field)
|
||||||
if mod = modifications[field]
|
return nil if hidden
|
||||||
mod[index]
|
mod = modifications[field]
|
||||||
|
unless mod.nil?
|
||||||
|
mod[0]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hide!
|
||||||
|
self.hidden = true
|
||||||
|
self.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def show!
|
||||||
|
self.hidden = false
|
||||||
|
self.save!
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
|
|
|
@ -12,7 +12,8 @@ class PostRevisionSerializer < ApplicationSerializer
|
||||||
:category_changes,
|
:category_changes,
|
||||||
:user_changes,
|
:user_changes,
|
||||||
:wiki_changes,
|
:wiki_changes,
|
||||||
:post_type_changes
|
:post_type_changes,
|
||||||
|
:hidden
|
||||||
|
|
||||||
def include_title_changes?
|
def include_title_changes?
|
||||||
object.has_topic_data?
|
object.has_topic_data?
|
||||||
|
@ -22,6 +23,10 @@ class PostRevisionSerializer < ApplicationSerializer
|
||||||
object.has_topic_data?
|
object.has_topic_data?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hidden
|
||||||
|
object.hidden
|
||||||
|
end
|
||||||
|
|
||||||
def version
|
def version
|
||||||
object.number
|
object.number
|
||||||
end
|
end
|
||||||
|
@ -43,7 +48,7 @@ class PostRevisionSerializer < ApplicationSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit_reason
|
def edit_reason
|
||||||
object.lookup("edit_reason", 1)
|
object.current("edit_reason")
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_changes
|
def user_changes
|
||||||
|
|
|
@ -1221,6 +1221,8 @@ en:
|
||||||
previous: "Previous revision"
|
previous: "Previous revision"
|
||||||
next: "Next revision"
|
next: "Next revision"
|
||||||
last: "Last revision"
|
last: "Last revision"
|
||||||
|
hide: "Hide revision"
|
||||||
|
show: "Show revision"
|
||||||
comparing_previous_to_current_out_of_total: "<strong>{{previous}}</strong> vs <strong>{{current}}</strong> / {{total}}"
|
comparing_previous_to_current_out_of_total: "<strong>{{previous}}</strong> vs <strong>{{current}}</strong> / {{total}}"
|
||||||
displays:
|
displays:
|
||||||
inline:
|
inline:
|
||||||
|
|
|
@ -277,6 +277,8 @@ Discourse::Application.routes.draw do
|
||||||
put "unhide"
|
put "unhide"
|
||||||
get "replies"
|
get "replies"
|
||||||
get "revisions/:revision" => "posts#revisions"
|
get "revisions/:revision" => "posts#revisions"
|
||||||
|
put "revisions/:revision/hide" => "posts#hide_revision"
|
||||||
|
put "revisions/:revision/show" => "posts#show_revision"
|
||||||
put "recover"
|
put "recover"
|
||||||
collection do
|
collection do
|
||||||
delete "destroy_many"
|
delete "destroy_many"
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddHiddenToPostRevision < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :post_revisions, :hidden, :boolean, null: false, default: false
|
||||||
|
end
|
||||||
|
end
|
|
@ -157,6 +157,14 @@ module PostGuardian
|
||||||
can_see_post?(post)
|
can_see_post?(post)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can_hide_post_revision?(post_revision)
|
||||||
|
is_staff?
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_show_post_revision?(post_revision)
|
||||||
|
is_staff?
|
||||||
|
end
|
||||||
|
|
||||||
def can_vote?(post, opts={})
|
def can_vote?(post, opts={})
|
||||||
post_can_act?(post,:vote, opts)
|
post_can_act?(post,:vote, opts)
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,18 +12,19 @@ describe PostRevision do
|
||||||
PostRevision.create!(post_id: post_id, user_id: 1, number: @number, modifications: modifications)
|
PostRevision.create!(post_id: post_id, user_id: 1, number: @number, modifications: modifications)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can grab history from current object" do
|
it "ignores deprecated current values in history" do
|
||||||
p = PostRevision.new(modifications: {"foo" => ["bar", "bar1"]})
|
p = PostRevision.new(modifications: {"foo" => ["bar", "bar1"]})
|
||||||
p.previous("foo").should == "bar"
|
p.previous("foo").should == "bar"
|
||||||
p.current("foo").should == "bar1"
|
p.current("foo").should == "bar"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can fallback to previous revisions if needed" do
|
it "can fallback to previous revisions if needed" do
|
||||||
create_rev("foo" => ["A", "B"])
|
r1 = create_rev("foo" => ["A", "B"])
|
||||||
r2 = create_rev("bar" => ["C", "D"])
|
r2 = create_rev("foo" => ["C", "D"])
|
||||||
|
|
||||||
r2.current("foo").should == "B"
|
r1.current("foo").should == "C"
|
||||||
r2.previous("foo").should == "B"
|
r2.current("foo").should == "C"
|
||||||
|
r2.previous("foo").should == "C"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can fallback to post if needed" do
|
it "can fallback to post if needed" do
|
||||||
|
@ -36,6 +37,16 @@ describe PostRevision do
|
||||||
r.previous("cooked").should == post.cooked
|
r.previous("cooked").should == post.cooked
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "can fallback to post for current rev only if needed" do
|
||||||
|
post = Fabricate(:post)
|
||||||
|
r = create_rev({"raw" => ["A"], "cooked" => ["AA"]}, post.id)
|
||||||
|
|
||||||
|
r.current("raw").should == post.raw
|
||||||
|
r.previous("raw").should == "A"
|
||||||
|
r.current("cooked").should == post.cooked
|
||||||
|
r.previous("cooked").should == "AA"
|
||||||
|
end
|
||||||
|
|
||||||
it "can fallback to topic if needed" do
|
it "can fallback to topic if needed" do
|
||||||
post = Fabricate(:post)
|
post = Fabricate(:post)
|
||||||
r = create_rev({"foo" => ["A", "B"]}, post.id)
|
r = create_rev({"foo" => ["A", "B"]}, post.id)
|
||||||
|
@ -45,37 +56,64 @@ describe PostRevision do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can find title changes" do
|
it "can find title changes" do
|
||||||
r = create_rev({"title" => ["hello", "frog"]})
|
r1 = create_rev({"title" => ["hello"]})
|
||||||
r.title_changes[:inline].should =~ /frog.*hello/
|
r2 = create_rev({"title" => ["frog"]})
|
||||||
r.title_changes[:side_by_side].should =~ /hello.*frog/
|
r1.title_changes[:inline].should =~ /frog.*hello/
|
||||||
|
r1.title_changes[:side_by_side].should =~ /hello.*frog/
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can find category changes" do
|
it "can find category changes" do
|
||||||
cat1 = Fabricate(:category, name: "cat1")
|
cat1 = Fabricate(:category, name: "cat1")
|
||||||
cat2 = Fabricate(:category, name: "cat2")
|
cat2 = Fabricate(:category, name: "cat2")
|
||||||
|
|
||||||
r = create_rev({"category_id" => [cat1.id, cat2.id]})
|
r1 = create_rev({"category_id" => [cat1.id, cat2.id]})
|
||||||
|
r2 = create_rev({"category_id" => [cat2.id, cat1.id]})
|
||||||
|
|
||||||
changes = r.category_changes
|
changes = r1.category_changes
|
||||||
changes[:previous_category_id].should == cat1.id
|
changes[:previous_category_id].should == cat1.id
|
||||||
changes[:current_category_id].should == cat2.id
|
changes[:current_category_id].should == cat2.id
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can find wiki changes" do
|
it "can find wiki changes" do
|
||||||
r = create_rev("wiki" => [false, true])
|
r1 = create_rev("wiki" => [false])
|
||||||
|
r2 = create_rev("wiki" => [true])
|
||||||
|
|
||||||
changes = r.wiki_changes
|
changes = r1.wiki_changes
|
||||||
changes[:previous_wiki].should == false
|
changes[:previous_wiki].should == false
|
||||||
changes[:current_wiki].should == true
|
changes[:current_wiki].should == true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can find post_type changes" do
|
it "can find post_type changes" do
|
||||||
r = create_rev("post_type" => [1, 2])
|
r1 = create_rev("post_type" => [1])
|
||||||
|
r2 = create_rev("post_type" => [2])
|
||||||
|
|
||||||
changes = r.post_type_changes
|
changes = r1.post_type_changes
|
||||||
changes[:previous_post_type].should == 1
|
changes[:previous_post_type].should == 1
|
||||||
changes[:current_post_type].should == 2
|
changes[:current_post_type].should == 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "hides revisions that were hidden" do
|
||||||
|
r1 = create_rev({"raw" => ["one"]})
|
||||||
|
r2 = create_rev({"raw" => ["two"]})
|
||||||
|
r3 = create_rev({"raw" => ["three"]})
|
||||||
|
|
||||||
|
r2.hide!
|
||||||
|
|
||||||
|
r1.current("raw").should == "three"
|
||||||
|
r2.previous("raw").should == "one"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows revisions that were shown" do
|
||||||
|
r1 = create_rev({"raw" => ["one"]})
|
||||||
|
r2 = create_rev({"raw" => ["two"]})
|
||||||
|
r3 = create_rev({"raw" => ["three"]})
|
||||||
|
|
||||||
|
r2.hide!
|
||||||
|
r2.show!
|
||||||
|
|
||||||
|
r2.previous("raw").should == "two"
|
||||||
|
r1.current("raw").should == "two"
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue