{{#if showUserReplyTab}}
-
- {{#if loadingParent}}
+
+ {{#if loadingReplyHistory}}
{{i18n loading}}
{{else}}
{{i18n post.in_reply_to}}
@@ -31,7 +27,7 @@
-
+
{{#unless controller.multiSelect}}
{{#if hasHistory}}
diff --git a/app/assets/javascripts/discourse/templates/reply_history.js.handlebars b/app/assets/javascripts/discourse/templates/reply_history.js.handlebars
new file mode 100644
index 00000000000..3efc57755ca
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/reply_history.js.handlebars
@@ -0,0 +1 @@
+the world ain't all sunshine and rainbows
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/views/parent_view.js b/app/assets/javascripts/discourse/views/parent_view.js
deleted file mode 100644
index 5cdc6f8cccc..00000000000
--- a/app/assets/javascripts/discourse/views/parent_view.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/*global Markdown:true*/
-
-/**
- A control to support embedding a post as a parent of the current post (in reply to)
-
- @class ParentView
- @extends Discourse.EmbeddedPostView
- @namespace Discourse
- @module Discourse
-**/
-Discourse.ParentView = Discourse.EmbeddedPostView.extend({
- previousPost: true,
-
- // Nice animation for when the replies appear
- didInsertElement: function() {
- this._super();
-
- var $parentPost = this.get('postView').$('section.parent-post');
-
- // Animate unless we're on a touch device
- if (Discourse.get('touch')) {
- $parentPost.show();
- } else {
- $parentPost.slideDown();
- }
- }
-});
-
-
diff --git a/app/assets/javascripts/discourse/views/post_menu_view.js b/app/assets/javascripts/discourse/views/post_menu_view.js
index cf7731acde7..2d54c238adf 100644
--- a/app/assets/javascripts/discourse/views/post_menu_view.js
+++ b/app/assets/javascripts/discourse/views/post_menu_view.js
@@ -19,7 +19,8 @@ Discourse.PostMenuView = Discourse.View.extend({
'post.bookmarkClass',
'post.bookmarkTooltip',
'post.shareUrl',
- 'post.topic.deleted_at'),
+ 'post.topic.deleted_at',
+ 'post.replies.length'),
render: function(buffer) {
var post = this.get('post');
@@ -55,12 +56,16 @@ Discourse.PostMenuView = Discourse.View.extend({
buffer.push("" + reply_count + "");
buffer.push(I18n.t("post.has_replies", { count: reply_count }));
- var icon = this.get('postView.repliesShown') ? 'icon-chevron-up' : 'icon-chevron-down';
+ var icon = (this.get('post.replies.length') > 0) ? 'icon-chevron-up' : 'icon-chevron-down';
return buffer.push("");
},
clickReplies: function() {
- this.get('postView').showReplies();
+ if (this.get('post.replies.length') > 0) {
+ this.set('post.replies', []);
+ } else {
+ this.get('post').loadReplies();
+ }
},
// Delete button
diff --git a/app/assets/javascripts/discourse/views/post_view.js b/app/assets/javascripts/discourse/views/post_view.js
index f0dc293dc52..7329cdd4c69 100644
--- a/app/assets/javascripts/discourse/views/post_view.js
+++ b/app/assets/javascripts/discourse/views/post_view.js
@@ -12,8 +12,7 @@ Discourse.PostView = Discourse.GroupedView.extend({
classNameBindings: ['postTypeClass',
'selected',
'post.hidden:hidden',
- 'post.deleted',
- 'parentPost:replies-above'],
+ 'post.deleted'],
postBinding: 'content',
postTypeClass: function() {
@@ -44,56 +43,7 @@ Discourse.PostView = Discourse.GroupedView.extend({
return this.get('selected') ? I18n.t('topic.multi_select.selected', { count: this.get('controller.selectedPostsCount') }) : I18n.t('topic.multi_select.select');
}.property('selected', 'controller.selectedPostsCount'),
- repliesHidden: Em.computed.not('repliesShown'),
-
- // Click on the replies button
- showReplies: function() {
- var postView = this;
- if (this.get('repliesShown')) {
- this.set('repliesShown', false);
- } else {
- this.get('post').loadReplies().then(function() {
- postView.set('repliesShown', true);
- });
- }
- return false;
- },
-
- // Toggle visibility of parent post
- toggleParent: function(e) {
- var postView = this;
- var post = this.get('post');
- var $parent = this.$('.parent-post');
- var inReplyTo = post.get('reply_to_post_number');
-
- if (post.get('post_number') - 1 === inReplyTo) {
- // true means ... avoid scroll if possible
- Discourse.TopicView.jumpToPost(post.get('topic_id'), inReplyTo, true);
- return;
- }
-
- if (this.get('parentPost')) {
- $('nav', $parent).removeClass('toggled');
- // Don't animate on touch
- if (Discourse.get('touch')) {
- $parent.hide();
- this.set('parentPost', null);
- } else {
- $parent.slideUp(function() { postView.set('parentPost', null); });
- }
- } else {
- this.set('loadingParent', true);
- $('nav', $parent).addClass('toggled');
-
- Discourse.Post.loadByPostNumber(post.get('topic_id'), inReplyTo).then(function(result) {
- postView.set('loadingParent', false);
- // Give the post a reference back to the topic
- result.topic = postView.get('post.topic');
- postView.set('parentPost', result);
- });
- }
- return false;
- },
+ repliesShown: Em.computed.gt('post.replies.length', 0),
updateQuoteElements: function($aside, desc) {
var navLink = "";
@@ -143,7 +93,7 @@ Discourse.PostView = Discourse.GroupedView.extend({
if ($aside.data('topic')) {
topic_id = $aside.data('topic');
}
- Discourse.ajax("/posts/by_number/" + topic_id + "/" + ($aside.data('post'))).then(function (result) {
+ Discourse.ajax("/posts/by_number/" + topic_id + "/" + $aside.data('post')).then(function (result) {
var parsed = $(result.cooked);
parsed.replaceText(originalText, "" + originalText + "");
$blockQuote.showHtml(parsed);
diff --git a/app/assets/javascripts/discourse/views/replies_view.js b/app/assets/javascripts/discourse/views/replies_view.js
index 20179ae8d18..d4aa7b645a2 100644
--- a/app/assets/javascripts/discourse/views/replies_view.js
+++ b/app/assets/javascripts/discourse/views/replies_view.js
@@ -7,24 +7,10 @@
@module Discourse
**/
Discourse.RepliesView = Ember.CollectionView.extend({
- templateName: 'replies',
tagName: 'section',
- classNames: ['replies-list', 'embedded-posts', 'bottom'],
+ classNameBindings: [':embedded-posts', ':bottom', 'hidden'],
itemViewClass: Discourse.EmbeddedPostView,
-
- repliesShownChanged: function() {
- var $this = this.$();
- if (this.get('parentView.repliesShown')) {
- Em.run.schedule('afterRender', function() {
- $this.slideDown();
- });
- } else {
- Em.run.schedule('afterRender', function() {
- $this.slideUp();
- });
- }
- }.observes('parentView.repliesShown')
-
+ hidden: Em.computed.equal('content.length', 0)
});
diff --git a/app/assets/javascripts/discourse/views/reply_history.js b/app/assets/javascripts/discourse/views/reply_history.js
new file mode 100644
index 00000000000..ce230d31917
--- /dev/null
+++ b/app/assets/javascripts/discourse/views/reply_history.js
@@ -0,0 +1,17 @@
+/**
+ Lists previous posts in the history of a post.
+
+ @class ReplyHistory
+ @extends Discourse.EmbeddedPostView
+ @namespace Discourse
+ @module Discourse
+**/
+Discourse.ReplyHistory = Em.CollectionView.extend({
+ tagName: 'section',
+ classNameBindings: [':embedded-posts', ':top', ':span14', ':offset2', 'hidden'],
+ itemViewClass: Discourse.EmbeddedPostView,
+ hidden: Em.computed.equal('content.length', 0),
+ previousPost: true
+});
+
+
diff --git a/app/assets/stylesheets/application/topic-post.css.scss b/app/assets/stylesheets/application/topic-post.css.scss
index cd96640a694..a08c402764d 100644
--- a/app/assets/stylesheets/application/topic-post.css.scss
+++ b/app/assets/stylesheets/application/topic-post.css.scss
@@ -423,7 +423,6 @@
.embedded-posts.bottom {
@include border-radius-bottom(4px);
border-bottom: 1px solid #b9b9b9;
- display: none;
overflow: hidden;
.arrow {
float: right;
@@ -432,7 +431,7 @@
}
.embedded-posts.top {
@include border-radius-top(4px);
- border-top: 1px solid #b9b9b9;
+ border-top: 1px solid #ddd;
overflow: hidden;
}
@@ -444,8 +443,8 @@
}
div.reply {
- border-left: 1px solid #b9b9b9;
- border-right: 1px solid #b9b9b9;
+ border-left: 1px solid #ddd;
+ border-right: 1px solid #ddd;
padding: 10px;
margin: 0;
background-clip: padding-box;
@@ -466,7 +465,7 @@
}
.topic-body {
z-index: 10;
- border: 1px solid #b9b9b9;
+ border: 1px solid #ddd;
padding: 0 8px;
color: black;
background-color: $white;
@@ -593,9 +592,6 @@
color: $dark_gray;
}
}
- &.replies-above .boxed .topic-body .contents {
- @include border-radius-top(0);
- }
}
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 64e1e7c25ca..6a990875e9e 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -5,7 +5,7 @@ require_dependency 'distributed_memoizer'
class PostsController < ApplicationController
# Need to be logged in for all actions here
- before_filter :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :versions]
+ before_filter :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :versions, :reply_history]
skip_before_filter :store_incoming_links, only: [:short_link]
skip_before_filter :check_xhr, only: [:markdown,:short_link]
@@ -113,6 +113,13 @@ class PostsController < ApplicationController
render_post_json(@post)
end
+ def reply_history
+ @post = Post.where(id: params[:id]).first
+ guardian.ensure_can_see!(@post)
+
+ render_serialized(@post.reply_history, PostSerializer)
+ end
+
def show
@post = find_post_from_params
@post.revert_to(params[:version].to_i) if params[:version].present?
diff --git a/app/models/post.rb b/app/models/post.rb
index 9d141f479ef..7c76e80c533 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -335,6 +335,21 @@ class Post < ActiveRecord::Base
private_posts.with_topic_subtype(topic_subtype).where('posts.created_at > ?', since_days_ago.days.ago).group('date(posts.created_at)').order('date(posts.created_at)').count
end
+
+ def reply_history
+ post_ids = Post.exec_sql("WITH RECURSIVE breadcrumb(id, reply_to_post_number) AS (
+ SELECT p.id, p.reply_to_post_number FROM posts AS p
+ WHERE p.id = :post_id
+ UNION
+ SELECT p.id, p.reply_to_post_number FROM posts AS p, breadcrumb
+ WHERE breadcrumb.reply_to_post_number = p.post_number
+ AND p.topic_id = :topic_id
+ ) SELECT id from breadcrumb ORDER by id", post_id: id, topic_id: topic_id).to_a
+
+ post_ids.map! {|r| r['id'].to_i }.reject! {|post_id| post_id == id}
+ Post.where(id: post_ids).includes(:user, :topic).order(:id).to_a
+ end
+
private
diff --git a/config/routes.rb b/config/routes.rb
index 4647f13f680..71961ebbb97 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -140,6 +140,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'
resources :posts do
get 'versions'
put 'bookmark'
diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb
index 94d1f0505f4..578dafd89dd 100644
--- a/spec/controllers/posts_controller_spec.rb
+++ b/spec/controllers/posts_controller_spec.rb
@@ -13,7 +13,6 @@ describe PostsController do
end
describe 'show' do
-
let(:user) { log_in }
let(:post) { Fabricate(:post, user: user) }
@@ -52,9 +51,26 @@ describe PostsController do
end
end
-
end
+ describe 'reply_history' do
+ let(:user) { log_in }
+ let(:post) { Fabricate(:post, user: user) }
+
+ it 'ensures the user can see the post' do
+ Guardian.any_instance.expects(:can_see?).with(post).returns(false)
+ xhr :get, :reply_history, id: post.id
+ response.should be_forbidden
+ end
+
+ it 'suceeds' do
+ Post.any_instance.expects(:reply_history)
+ xhr :get, :reply_history, id: post.id
+ response.should be_success
+ end
+ end
+
+
describe 'versions' do
shared_examples 'posts_controller versions examples' do
diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb
index e3999848ac9..6f5b2c69dfb 100644
--- a/spec/models/post_spec.rb
+++ b/spec/models/post_spec.rb
@@ -711,7 +711,6 @@ describe Post do
context 'sort_order' do
-
context 'regular topic' do
let!(:p1) { Fabricate(:post, post_args) }
@@ -722,6 +721,20 @@ describe Post do
Post.regular_order.should == [p1, p2, p3]
end
end
+ end
+
+ context "reply_history" do
+
+ let!(:p1) { Fabricate(:post, post_args) }
+ let!(:p2) { Fabricate(:post, post_args.merge(reply_to_post_number: p1.post_number)) }
+ let!(:p3) { Fabricate(:post, post_args) }
+ let!(:p4) { Fabricate(:post, post_args.merge(reply_to_post_number: p2.post_number)) }
+
+ it "returns the posts in reply to this post" do
+ p4.reply_history.should == [p1, p2]
+ p3.reply_history.should be_blank
+ p2.reply_history.should == [p1]
+ end
end
diff --git a/test/javascripts/models/post_stream_test.js b/test/javascripts/models/post_stream_test.js
index f99beaa2187..0a6355962a2 100644
--- a/test/javascripts/models/post_stream_test.js
+++ b/test/javascripts/models/post_stream_test.js
@@ -261,6 +261,22 @@ asyncTestDiscourse("loadIntoIdentityMap with post ids", function() {
});
});
+asyncTestDiscourse("loading a post's history", function() {
+ var postStream = buildStream(1234);
+ expect(3);
+
+ var post = Discourse.Post.create({id: 4321});
+
+ var secondPost = Discourse.Post.create({id: 2222});
+
+ this.stub(Discourse, "ajax").returns(Ember.RSVP.resolve([secondPost]));
+ postStream.findReplyHistory(post).then(function() {
+ ok(Discourse.ajax.calledOnce, "it made the ajax request");
+ present(postStream.findLoadedPost(2222), "it stores the returned post in the identity map");
+ present(post.get('replyHistory'), "it sets the replyHistory attribute for the post");
+ start();
+ });
+});
test("staging and undoing a new post", function() {
var postStream = buildStream(10101, [1]);
diff --git a/test/javascripts/models/post_test.js b/test/javascripts/models/post_test.js
index c49f874d884..cac49f5bbac 100644
--- a/test/javascripts/models/post_test.js
+++ b/test/javascripts/models/post_test.js
@@ -12,6 +12,7 @@ test('defaults', function() {
var post = Discourse.Post.create({id: 1});
blank(post.get('deleted_at'), "it has no deleted_at by default");
blank(post.get('deleted_by'), "there is no deleted_by by default");
+ equal(post.get('replyHistory.length'), 0, "there is no reply history by default");
});
test('new_user', function() {