mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 09:12:45 +08:00
FEATURE: display unpinned state, allow unpinning by clicking on pin
This commit is contained in:
parent
3f6764ce22
commit
b9d4edd91a
|
@ -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("<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
|
||||
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');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
3
app/assets/stylesheets/common/base/_topic-list.scss
Normal file
3
app/assets/stylesheets/common/base/_topic-list.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.fa-thumb-tack.unpinned {
|
||||
@include fa-icon-rotate(315deg, 1);
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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+/}
|
||||
|
|
|
@ -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
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user