diff --git a/app/assets/javascripts/discourse/components/topic_status_component.js b/app/assets/javascripts/discourse/components/topic_status_component.js
index 2bc42f500..4724b4b04 100644
--- a/app/assets/javascripts/discourse/components/topic_status_component.js
+++ b/app/assets/javascripts/discourse/components/topic_status_component.js
@@ -9,24 +9,54 @@
Discourse.TopicStatusComponent = Ember.Component.extend({
classNames: ['topic-statuses'],
- hasDisplayableStatus: Em.computed.or('topic.closed', 'topic.pinned', 'topic.invisible', 'topic.archetypeObject.notDefault'),
- shouldRerender: Discourse.View.renderIfChanged('topic.closed', 'topic.pinned', 'topic.visible'),
+ hasDisplayableStatus: Em.computed.or('topic.closed', 'topic.pinned', 'topic.unpinned', 'topic.invisible', 'topic.archetypeObject.notDefault'),
+ shouldRerender: Discourse.View.renderIfChanged('topic.closed', 'topic.pinned', 'topic.visible', 'topic.unpinned'),
+
+ didInsertElement: function(){
+ var topic = this.get('topic');
+
+ // could be passed in a controller
+ if(topic.constructor.toString() !== 'Discourse.Topic') {
+ topic = topic.get('model');
+ }
+
+ this.$('a').click(function(){
+ // only pin unpin for now
+ if (topic.get('pinned')) {
+ topic.clearPin();
+ } else {
+ topic.rePin();
+ }
+
+ return false;
+ });
+ },
render: function(buffer) {
if (!this.get('hasDisplayableStatus')) { return; }
var self = this,
- renderIconIf = function(conditionProp, name, key) {
+ renderIconIf = function(conditionProp, name, key, actionable) {
if (!self.get(conditionProp)) { return; }
var title = I18n.t("topic_statuses." + key + ".help");
- buffer.push("");
+
+ var startTag = actionable ? "a href='#'" : "span";
+ var endTag = actionable ? "a" : "span";
+
+ buffer.push("<" + startTag +
+ " title='" + title +"' class='topic-status'>" + endTag + ">");
};
// Allow a plugin to add a custom icon to a topic
this.trigger('addCustomIcon', buffer);
+ var togglePin = function(){
+
+ };
+
renderIconIf('topic.closed', 'lock', 'locked');
- renderIconIf('topic.pinned', 'thumb-tack', 'pinned');
+ renderIconIf('topic.pinned', 'thumb-tack', 'pinned', togglePin);
+ renderIconIf('topic.unpinned', 'thumb-tack unpinned', 'unpinned', togglePin);
renderIconIf('topic.invisible', 'eye-slash', 'invisible');
}
});
diff --git a/app/assets/javascripts/discourse/models/topic.js b/app/assets/javascripts/discourse/models/topic.js
index a68b5155a..802641623 100644
--- a/app/assets/javascripts/discourse/models/topic.js
+++ b/app/assets/javascripts/discourse/models/topic.js
@@ -271,12 +271,35 @@ Discourse.Topic = Discourse.Model.extend({
// Clear the pin optimistically from the object
topic.set('pinned', false);
+ topic.set('unpinned', true);
Discourse.ajax("/t/" + this.get('id') + "/clear-pin", {
type: 'PUT'
}).then(null, function() {
// On error, put the pin back
topic.set('pinned', true);
+ topic.set('unpinned', false);
+ });
+ },
+
+ /**
+ Re-pins a topic with a cleared pin
+
+ @method rePin
+ **/
+ rePin: function() {
+ var topic = this;
+
+ // Clear the pin optimistically from the object
+ topic.set('pinned', true);
+ topic.set('unpinned', false);
+
+ Discourse.ajax("/t/" + this.get('id') + "/re-pin", {
+ type: 'PUT'
+ }).then(null, function() {
+ // On error, put the pin back
+ topic.set('pinned', true);
+ topic.set('unpinned', false);
});
},
diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss
index 69064e175..51b3f565c 100644
--- a/app/assets/stylesheets/common.scss
+++ b/app/assets/stylesheets/common.scss
@@ -8,6 +8,6 @@
@import "common/components/*";
@import "common/admin/*";
@import "common/input_tip";
-
+@import "common/base/*";
/* This file doesn't actually exist, it is injected by DiscourseSassImporter. */
@import "plugins";
diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss
new file mode 100644
index 000000000..f2f01d733
--- /dev/null
+++ b/app/assets/stylesheets/common/base/_topic-list.scss
@@ -0,0 +1,3 @@
+.fa-thumb-tack.unpinned {
+ @include fa-icon-rotate(315deg, 1);
+}
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
index 0f1c58859..bb0dc41e6 100644
--- a/app/controllers/topics_controller.rb
+++ b/app/controllers/topics_controller.rb
@@ -20,6 +20,7 @@ class TopicsController < ApplicationController
:move_posts,
:merge_topic,
:clear_pin,
+ :re_pin,
:autoclose,
:bulk,
:reset_new,
@@ -281,6 +282,13 @@ class TopicsController < ApplicationController
render nothing: true
end
+ def re_pin
+ topic = Topic.where(id: params[:topic_id].to_i).first
+ guardian.ensure_can_see!(topic)
+ topic.re_pin_for(current_user)
+ render nothing: true
+ end
+
def timings
PostTiming.process_timings(
current_user,
diff --git a/app/models/topic.rb b/app/models/topic.rb
index bf7a6b9dc..b6dc18dea 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -618,6 +618,11 @@ class Topic < ActiveRecord::Base
TopicUser.change(user.id, id, cleared_pinned_at: Time.now)
end
+ def re_pin_for(user)
+ return unless user.present?
+ TopicUser.change(user.id, id, cleared_pinned_at: nil)
+ end
+
def update_pinned(status, global=false)
update_column(:pinned_at, status ? Time.now : nil)
update_column(:pinned_globally, global)
diff --git a/app/serializers/listable_topic_serializer.rb b/app/serializers/listable_topic_serializer.rb
index b82e2a124..713ca6327 100644
--- a/app/serializers/listable_topic_serializer.rb
+++ b/app/serializers/listable_topic_serializer.rb
@@ -14,6 +14,7 @@ class ListableTopicSerializer < BasicTopicSerializer
:unread,
:new_posts,
:pinned,
+ :unpinned,
:excerpt,
:visible,
:closed,
@@ -65,7 +66,11 @@ class ListableTopicSerializer < BasicTopicSerializer
end
def pinned
- PinnedCheck.new(object, object.user_data).pinned?
+ PinnedCheck.pinned?(object, object.user_data)
+ end
+
+ def unpinned
+ PinnedCheck.unpinned?(object, object.user_data)
end
protected
diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb
index 503ff305a..dd162dcaf 100644
--- a/app/serializers/topic_view_serializer.rb
+++ b/app/serializers/topic_view_serializer.rb
@@ -31,6 +31,7 @@ class TopicViewSerializer < ApplicationSerializer
:draft_sequence,
:starred,
:posted,
+ :unpinned,
:pinned, # Is topic pinned and viewer hasn't cleared the pin?
:pinned_at, # Ignores clear pin
:details,
@@ -41,7 +42,7 @@ class TopicViewSerializer < ApplicationSerializer
:expandable_first_post
# Define a delegator for each attribute of the topic we want
- attributes *topic_attributes
+ attributes(*topic_attributes)
topic_attributes.each do |ta|
class_eval %{def #{ta}
object.topic.#{ta}
@@ -145,7 +146,11 @@ class TopicViewSerializer < ApplicationSerializer
alias_method :include_posted?, :has_topic_user?
def pinned
- PinnedCheck.new(object.topic, object.topic_user).pinned?
+ PinnedCheck.pinned?(object.topic, object.topic_user)
+ end
+
+ def unpinned
+ PinnedCheck.unpinned?(object.topic, object.topic_user)
end
def pinned_at
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index f14463be6..a7622ad8f 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1145,6 +1145,8 @@ en:
topic_statuses:
locked:
help: "this topic is closed; it no longer accepts new replies"
+ unpinned:
+ help: "this topic is unpinned; it will displayed in default ordering"
pinned:
help: "this topic is pinned; it will display at the top of its category"
archived:
diff --git a/config/routes.rb b/config/routes.rb
index 243b70288..9a1df2aa7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -315,6 +315,7 @@ Discourse::Application.routes.draw do
put "t/:slug/:topic_id/status" => "topics#status", constraints: {topic_id: /\d+/}
put "t/:topic_id/status" => "topics#status", constraints: {topic_id: /\d+/}
put "t/:topic_id/clear-pin" => "topics#clear_pin", constraints: {topic_id: /\d+/}
+ put "t/:topic_id/re-pin" => "topics#re_pin", constraints: {topic_id: /\d+/}
put "t/:topic_id/mute" => "topics#mute", constraints: {topic_id: /\d+/}
put "t/:topic_id/unmute" => "topics#unmute", constraints: {topic_id: /\d+/}
put "t/:topic_id/autoclose" => "topics#autoclose", constraints: {topic_id: /\d+/}
diff --git a/lib/pinned_check.rb b/lib/pinned_check.rb
index d1890c58b..b1a25a4d5 100644
--- a/lib/pinned_check.rb
+++ b/lib/pinned_check.rb
@@ -2,23 +2,16 @@
# taking into account anonymous users and users who have dismissed it
class PinnedCheck
- def initialize(topic, topic_user=nil)
- @topic, @topic_user = topic, topic_user
+ def self.unpinned?(topic,topic_user=nil)
+ topic.pinned_at &&
+ topic_user &&
+ topic_user.cleared_pinned_at &&
+ topic_user.cleared_pinned_at > topic.pinned_at
end
- def pinned?
-
- # If the topic isn't pinned the answer is false
- return false if @topic.pinned_at.blank?
-
- # If the user is anonymous or hasn't entered the topic, the value is always true
- return true if @topic_user.blank?
-
- # If the user hasn't cleared the pin, it's true
- return true if @topic_user.cleared_pinned_at.blank?
-
- # The final check is to see whether the cleared the pin before or after it was last pinned
- @topic_user.cleared_pinned_at < @topic.pinned_at
+ def self.pinned?(topic, topic_user=nil)
+ !!topic.pinned_at &&
+ !unpinned?(topic,topic_user)
end
-end
\ No newline at end of file
+end
diff --git a/spec/components/pinned_check_spec.rb b/spec/components/pinned_check_spec.rb
index 2cab45ae9..f858c936c 100644
--- a/spec/components/pinned_check_spec.rb
+++ b/spec/components/pinned_check_spec.rb
@@ -1,9 +1,8 @@
+require 'spec_helper'
require 'pinned_check'
describe PinnedCheck do
- #let(:topic) { Fabricate.build(:topic) }
-
let(:pinned_at) { 12.hours.ago }
let(:unpinned_topic) { Fabricate.build(:topic) }
let(:pinned_topic) { Fabricate.build(:topic, pinned_at: pinned_at) }
@@ -11,11 +10,11 @@ describe PinnedCheck do
context "without a topic_user record (either anonymous or never been in the topic)" do
it "returns false if the topic is not pinned" do
- PinnedCheck.new(unpinned_topic).should_not be_pinned
+ PinnedCheck.pinned?(unpinned_topic).should be_false
end
it "returns true if the topic is pinned" do
- PinnedCheck.new(unpinned_topic).should_not be_pinned
+ PinnedCheck.pinned?(unpinned_topic).should be_false
end
end
@@ -28,7 +27,7 @@ describe PinnedCheck do
let(:topic_user) { TopicUser.new(topic: unpinned_topic, user: user) }
it "returns false" do
- PinnedCheck.new(unpinned_topic, topic_user).should_not be_pinned
+ PinnedCheck.pinned?(unpinned_topic, topic_user).should be_false
end
end
@@ -37,17 +36,17 @@ describe PinnedCheck do
let(:topic_user) { TopicUser.new(topic: pinned_topic, user: user) }
it "is pinned if the topic_user's cleared_pinned_at is blank" do
- PinnedCheck.new(pinned_topic, topic_user).should be_pinned
+ PinnedCheck.pinned?(pinned_topic, topic_user).should be_true
end
it "is not pinned if the topic_user's cleared_pinned_at is later than when it was pinned_at" do
topic_user.cleared_pinned_at = (pinned_at + 1.hour)
- PinnedCheck.new(pinned_topic, topic_user).should_not be_pinned
+ PinnedCheck.pinned?(pinned_topic, topic_user).should be_false
end
it "is pinned if the topic_user's cleared_pinned_at is earlier than when it was pinned_at" do
topic_user.cleared_pinned_at = (pinned_at - 3.hours)
- PinnedCheck.new(pinned_topic, topic_user).should be_pinned
+ PinnedCheck.pinned?(pinned_topic, topic_user).should be_true
end
end