Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Kris Aubuchon 2014-04-09 21:26:00 -04:00
commit 1725f6e5bc
12 changed files with 107 additions and 33 deletions

View File

@ -9,24 +9,54 @@
Discourse.TopicStatusComponent = Ember.Component.extend({ Discourse.TopicStatusComponent = Ember.Component.extend({
classNames: ['topic-statuses'], classNames: ['topic-statuses'],
hasDisplayableStatus: Em.computed.or('topic.closed', 'topic.pinned', 'topic.invisible', 'topic.archetypeObject.notDefault'), 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'), 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) { render: function(buffer) {
if (!this.get('hasDisplayableStatus')) { return; } if (!this.get('hasDisplayableStatus')) { return; }
var self = this, var self = this,
renderIconIf = function(conditionProp, name, key) { renderIconIf = function(conditionProp, name, key, actionable) {
if (!self.get(conditionProp)) { return; } if (!self.get(conditionProp)) { return; }
var title = I18n.t("topic_statuses." + key + ".help"); var title = I18n.t("topic_statuses." + key + ".help");
buffer.push("<span title='" + title + "' class='topic-status'><i class='fa fa-" + name + "'></i></span>");
var startTag = actionable ? "a href='#'" : "span";
var endTag = actionable ? "a" : "span";
buffer.push("<" + startTag +
" title='" + title +"' class='topic-status'><i class='fa fa-" + name + "'></i></" + endTag + ">");
}; };
// Allow a plugin to add a custom icon to a topic // Allow a plugin to add a custom icon to a topic
this.trigger('addCustomIcon', buffer); this.trigger('addCustomIcon', buffer);
var togglePin = function(){
};
renderIconIf('topic.closed', 'lock', 'locked'); 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'); renderIconIf('topic.invisible', 'eye-slash', 'invisible');
} }
}); });

View File

@ -271,12 +271,35 @@ Discourse.Topic = Discourse.Model.extend({
// Clear the pin optimistically from the object // Clear the pin optimistically from the object
topic.set('pinned', false); topic.set('pinned', false);
topic.set('unpinned', true);
Discourse.ajax("/t/" + this.get('id') + "/clear-pin", { Discourse.ajax("/t/" + this.get('id') + "/clear-pin", {
type: 'PUT' type: 'PUT'
}).then(null, function() { }).then(null, function() {
// On error, put the pin back // On error, put the pin back
topic.set('pinned', true); 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);
}); });
}, },

View File

@ -8,6 +8,6 @@
@import "common/components/*"; @import "common/components/*";
@import "common/admin/*"; @import "common/admin/*";
@import "common/input_tip"; @import "common/input_tip";
@import "common/base/*";
/* This file doesn't actually exist, it is injected by DiscourseSassImporter. */ /* This file doesn't actually exist, it is injected by DiscourseSassImporter. */
@import "plugins"; @import "plugins";

View File

@ -0,0 +1,3 @@
.fa-thumb-tack.unpinned {
@include fa-icon-rotate(315deg, 1);
}

View File

@ -20,6 +20,7 @@ class TopicsController < ApplicationController
:move_posts, :move_posts,
:merge_topic, :merge_topic,
:clear_pin, :clear_pin,
:re_pin,
:autoclose, :autoclose,
:bulk, :bulk,
:reset_new, :reset_new,
@ -281,6 +282,13 @@ class TopicsController < ApplicationController
render nothing: true render nothing: true
end 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 def timings
PostTiming.process_timings( PostTiming.process_timings(
current_user, current_user,

View File

@ -618,6 +618,11 @@ class Topic < ActiveRecord::Base
TopicUser.change(user.id, id, cleared_pinned_at: Time.now) TopicUser.change(user.id, id, cleared_pinned_at: Time.now)
end 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) def update_pinned(status, global=false)
update_column(:pinned_at, status ? Time.now : nil) update_column(:pinned_at, status ? Time.now : nil)
update_column(:pinned_globally, global) update_column(:pinned_globally, global)

View File

@ -14,6 +14,7 @@ class ListableTopicSerializer < BasicTopicSerializer
:unread, :unread,
:new_posts, :new_posts,
:pinned, :pinned,
:unpinned,
:excerpt, :excerpt,
:visible, :visible,
:closed, :closed,
@ -65,7 +66,11 @@ class ListableTopicSerializer < BasicTopicSerializer
end end
def pinned 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 end
protected protected

View File

@ -31,6 +31,7 @@ class TopicViewSerializer < ApplicationSerializer
:draft_sequence, :draft_sequence,
:starred, :starred,
:posted, :posted,
:unpinned,
:pinned, # Is topic pinned and viewer hasn't cleared the pin? :pinned, # Is topic pinned and viewer hasn't cleared the pin?
:pinned_at, # Ignores clear pin :pinned_at, # Ignores clear pin
:details, :details,
@ -41,7 +42,7 @@ class TopicViewSerializer < ApplicationSerializer
:expandable_first_post :expandable_first_post
# Define a delegator for each attribute of the topic we want # Define a delegator for each attribute of the topic we want
attributes *topic_attributes attributes(*topic_attributes)
topic_attributes.each do |ta| topic_attributes.each do |ta|
class_eval %{def #{ta} class_eval %{def #{ta}
object.topic.#{ta} object.topic.#{ta}
@ -145,7 +146,11 @@ class TopicViewSerializer < ApplicationSerializer
alias_method :include_posted?, :has_topic_user? alias_method :include_posted?, :has_topic_user?
def pinned 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 end
def pinned_at def pinned_at

View File

@ -1145,6 +1145,8 @@ en:
topic_statuses: topic_statuses:
locked: locked:
help: "this topic is closed; it no longer accepts new replies" help: "this topic is closed; it no longer accepts new replies"
unpinned:
help: "this topic is unpinned; it will displayed in default ordering"
pinned: pinned:
help: "this topic is pinned; it will display at the top of its category" help: "this topic is pinned; it will display at the top of its category"
archived: archived:

View File

@ -315,6 +315,7 @@ Discourse::Application.routes.draw do
put "t/:slug/:topic_id/status" => "topics#status", constraints: {topic_id: /\d+/} 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/status" => "topics#status", constraints: {topic_id: /\d+/}
put "t/:topic_id/clear-pin" => "topics#clear_pin", 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/mute" => "topics#mute", constraints: {topic_id: /\d+/}
put "t/:topic_id/unmute" => "topics#unmute", 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+/} put "t/:topic_id/autoclose" => "topics#autoclose", constraints: {topic_id: /\d+/}

View File

@ -2,23 +2,16 @@
# taking into account anonymous users and users who have dismissed it # taking into account anonymous users and users who have dismissed it
class PinnedCheck class PinnedCheck
def initialize(topic, topic_user=nil) def self.unpinned?(topic,topic_user=nil)
@topic, @topic_user = topic, topic_user topic.pinned_at &&
topic_user &&
topic_user.cleared_pinned_at &&
topic_user.cleared_pinned_at > topic.pinned_at
end end
def pinned? def self.pinned?(topic, topic_user=nil)
!!topic.pinned_at &&
# If the topic isn't pinned the answer is false !unpinned?(topic,topic_user)
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
end end
end end

View File

@ -1,9 +1,8 @@
require 'spec_helper'
require 'pinned_check' require 'pinned_check'
describe PinnedCheck do describe PinnedCheck do
#let(:topic) { Fabricate.build(:topic) }
let(:pinned_at) { 12.hours.ago } let(:pinned_at) { 12.hours.ago }
let(:unpinned_topic) { Fabricate.build(:topic) } let(:unpinned_topic) { Fabricate.build(:topic) }
let(:pinned_topic) { Fabricate.build(:topic, pinned_at: pinned_at) } 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 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 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 end
it "returns true if the topic is pinned" do 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
end end
@ -28,7 +27,7 @@ describe PinnedCheck do
let(:topic_user) { TopicUser.new(topic: unpinned_topic, user: user) } let(:topic_user) { TopicUser.new(topic: unpinned_topic, user: user) }
it "returns false" do 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
end end
@ -37,17 +36,17 @@ describe PinnedCheck do
let(:topic_user) { TopicUser.new(topic: pinned_topic, user: user) } let(:topic_user) { TopicUser.new(topic: pinned_topic, user: user) }
it "is pinned if the topic_user's cleared_pinned_at is blank" do 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 end
it "is not pinned if the topic_user's cleared_pinned_at is later than when it was pinned_at" do 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) 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 end
it "is pinned if the topic_user's cleared_pinned_at is earlier than when it was pinned_at" do 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) 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
end end