From bc3de84ebf2d7f2f85e0b80c49ba37c7cef752c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 9 May 2014 17:49:39 +0200 Subject: [PATCH] FEATURE: remove bookmark button in activity feed --- .../discourse/models/user_action.js | 26 ++++++++++--------- .../discourse/models/user_stream.js | 24 +++++++++++++++++ .../discourse/routes/user_activity_route.js | 2 +- .../routes/user_activity_stream_route.js | 19 ++++++++++++++ .../templates/user/stream.js.handlebars | 10 ++++--- .../views/user/activity_filter_view.js | 25 +++++++----------- app/assets/stylesheets/desktop/user.scss | 4 +++ app/assets/stylesheets/mobile/user.scss | 4 +++ app/controllers/posts_controller.rb | 8 ++++++ app/controllers/user_actions_controller.rb | 6 +---- app/models/user_action.rb | 1 + app/serializers/user_action_serializer.rb | 8 ++++-- config/locales/client.en.yml | 1 + config/routes.rb | 1 + spec/controllers/posts_controller_spec.rb | 11 +++++++- 15 files changed, 110 insertions(+), 40 deletions(-) diff --git a/app/assets/javascripts/discourse/models/user_action.js b/app/assets/javascripts/discourse/models/user_action.js index 3b584f059b4..7a781f43aca 100644 --- a/app/assets/javascripts/discourse/models/user_action.js +++ b/app/assets/javascripts/discourse/models/user_action.js @@ -102,9 +102,8 @@ Discourse.UserAction = Discourse.Model.extend({ presentName: Em.computed.any('name', 'username'), targetDisplayName: Em.computed.any('target_name', 'target_username'), actingDisplayName: Em.computed.any('acting_name', 'acting_username'), - - targetUserUrl: Discourse.computed.url('target_username', '/users/%@'), + usernameLower: function() { return this.get('username').toLowerCase(); }.property('username'), @@ -122,6 +121,7 @@ Discourse.UserAction = Discourse.Model.extend({ replyType: Em.computed.equal('action_type', UserActionTypes.replies), postType: Em.computed.equal('action_type', UserActionTypes.posts), topicType: Em.computed.equal('action_type', UserActionTypes.topics), + bookmarkType: Em.computed.equal('action_type', UserActionTypes.bookmarks), messageSentType: Em.computed.equal('action_type', UserActionTypes.messages_sent), messageReceivedType: Em.computed.equal('action_type', UserActionTypes.messages_received), mentionType: Em.computed.equal('action_type', UserActionTypes.mentions), @@ -168,7 +168,11 @@ Discourse.UserAction = Discourse.Model.extend({ }); } return rval; - }.property("childGroups"), + }.property("childGroups", + "childGroups.likes.items", "childGroups.likes.items.@each", + "childGroups.stars.items", "childGroups.stars.items.@each", + "childGroups.edits.items", "childGroups.edits.items.@each", + "childGroups.bookmarks.items", "childGroups.bookmarks.items.@each"), switchToActing: function() { this.setProperties({ @@ -193,7 +197,6 @@ Discourse.UserAction.reopenClass({ var current; if (Discourse.UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) { current = Discourse.UserAction.create(item); - current.setProperties({action_type: null, description: null}); item.switchToActing(); current.addChild(item); } else { @@ -217,11 +220,13 @@ Discourse.UserAction.reopenClass({ TYPES: UserActionTypes, TYPES_INVERTED: InvertedActionTypes, - TO_COLLAPSE: [UserActionTypes.likes_given, - UserActionTypes.likes_received, - UserActionTypes.starred, - UserActionTypes.edits, - UserActionTypes.bookmarks], + TO_COLLAPSE: [ + UserActionTypes.likes_given, + UserActionTypes.likes_received, + UserActionTypes.starred, + UserActionTypes.edits, + UserActionTypes.bookmarks + ], TO_SHOW: [ UserActionTypes.likes_given, @@ -234,6 +239,3 @@ Discourse.UserAction.reopenClass({ ] }); - - - diff --git a/app/assets/javascripts/discourse/models/user_stream.js b/app/assets/javascripts/discourse/models/user_stream.js index 6a72c7ef88b..0ea56a6d0e4 100644 --- a/app/assets/javascripts/discourse/models/user_stream.js +++ b/app/assets/javascripts/discourse/models/user_stream.js @@ -36,6 +36,30 @@ Discourse.UserStream = Discourse.Model.extend({ return this.findItems(); }, + remove: function(userAction) { + // 1) remove the user action from the child groups + this.get("content").forEach(function (ua) { + ["likes", "stars", "edits", "bookmarks"].forEach(function (group) { + var items = ua.get("childGroups." + group + ".items"); + if (items) { + items.removeObject(userAction); + } + }); + }); + + // 2) remove the parents that have no children + var content = this.get("content").filter(function (ua) { + return ["likes", "stars", "edits", "bookmarks"].any(function (group) { + return ua.get("childGroups." + group + ".items.length") > 0; + }); + }); + + this.setProperties({ + content: content, + itemsLoaded: content.length + }); + }, + findItems: function() { var userStream = this; if(this.get('loading')) { return Ember.RSVP.reject(); } diff --git a/app/assets/javascripts/discourse/routes/user_activity_route.js b/app/assets/javascripts/discourse/routes/user_activity_route.js index e5c9644110c..d1c86b547ac 100644 --- a/app/assets/javascripts/discourse/routes/user_activity_route.js +++ b/app/assets/javascripts/discourse/routes/user_activity_route.js @@ -36,4 +36,4 @@ Discourse.UserActivityRoute = Discourse.Route.extend({ }); -Discourse.UserPrivateMessagesRoute = Discourse.UserActivityRoute.extend({}); \ No newline at end of file +Discourse.UserPrivateMessagesRoute = Discourse.UserActivityRoute.extend({}); diff --git a/app/assets/javascripts/discourse/routes/user_activity_stream_route.js b/app/assets/javascripts/discourse/routes/user_activity_stream_route.js index 56a333b0201..b26e9e705cb 100644 --- a/app/assets/javascripts/discourse/routes/user_activity_stream_route.js +++ b/app/assets/javascripts/discourse/routes/user_activity_stream_route.js @@ -24,6 +24,25 @@ Discourse.UserActivityStreamRoute = Discourse.Route.extend({ this.controllerFor('user_activity').set('userActionType', this.get('userActionType')); this.controllerFor('user').set('indexStream', !this.get('userActionType')); + }, + + actions: { + + removeBookmark: function(userAction) { + var self = this; + Discourse.ajax("/posts/by_number/" + userAction.topic_id + "/" + userAction.post_number + "/bookmarks/remove", { type: "PUT" }) + .then(function() { + // remove the user action from the stream + self.modelFor("user").get("stream").remove(userAction); + // update the counts + self.modelFor("user").get("stats").forEach(function (stat) { + if (stat.get("action_type") === userAction.action_type) { + stat.decrementProperty("count"); + } + }) + }); + }, + } }); diff --git a/app/assets/javascripts/discourse/templates/user/stream.js.handlebars b/app/assets/javascripts/discourse/templates/user/stream.js.handlebars index 4f3972ef299..2a80eaea293 100644 --- a/app/assets/javascripts/discourse/templates/user/stream.js.handlebars +++ b/app/assets/javascripts/discourse/templates/user/stream.js.handlebars @@ -8,13 +8,17 @@ {{unbound descriptionHtml}} -

- {{{unbound excerpt}}} -

+

{{{unbound excerpt}}}

{{#groupedEach children}}
{{#groupedEach items}} + {{#if bookmarkType}} + + {{/if}}
{{avatar this imageSize="tiny" extraClasses="actor" ignoreTitle="true"}}
{{#if edit_reason}} — {{unbound edit_reason}}{{/if}} {{/groupedEach}} diff --git a/app/assets/javascripts/discourse/views/user/activity_filter_view.js b/app/assets/javascripts/discourse/views/user/activity_filter_view.js index 439f75f5ace..985e2527967 100644 --- a/app/assets/javascripts/discourse/views/user/activity_filter_view.js +++ b/app/assets/javascripts/discourse/views/user/activity_filter_view.js @@ -10,7 +10,7 @@ Discourse.ActivityFilterView = Ember.Component.extend({ tagName: 'li', classNameBindings: ['active', 'noGlyph'], - shouldRerender: Discourse.View.renderIfChanged('count'), + shouldRerender: Discourse.View.renderIfChanged('content.count', 'count'), noGlyph: Em.computed.empty('icon'), active: function() { @@ -23,11 +23,10 @@ Discourse.ActivityFilterView = Ember.Component.extend({ }.property('userActionType', 'indexStream'), activityCount: function() { - return this.get('content.count') || this.get('count'); + return this.get('content.count') || this.get('count') || 0; }.property('content.count', 'count'), typeKey: function() { - var actionType = this.get('content.action_type'); if (actionType === Discourse.UserAction.TYPES.messages_received) { return ""; } @@ -52,23 +51,17 @@ Discourse.ActivityFilterView = Ember.Component.extend({ if (icon) { buffer.push(" "); } - buffer.push(this.get('description') + " (" + this.get('activityCount') + ")"); buffer.push(""); }, - icon: function(){ - switch(parseInt(this.get('content.action_type'),10)) { - case Discourse.UserAction.TYPES.likes_received: - return "heart"; - case Discourse.UserAction.TYPES.bookmarks: - return "bookmark"; - case Discourse.UserAction.TYPES.edits: - return "pencil"; - case Discourse.UserAction.TYPES.replies: - return "reply"; - case Discourse.UserAction.TYPES.starred: - return "star"; + icon: function() { + switch(parseInt(this.get('content.action_type'), 10)) { + case Discourse.UserAction.TYPES.likes_received: return "heart"; + case Discourse.UserAction.TYPES.bookmarks: return "bookmark"; + case Discourse.UserAction.TYPES.edits: return "pencil"; + case Discourse.UserAction.TYPES.replies: return "reply"; + case Discourse.UserAction.TYPES.starred: return "star"; } }.property("content.action_type") diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index a52fe7c1805..17ea377dd7f 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -344,5 +344,9 @@ background-color: scale-color($highlight, $lightness: 25%); padding: 3px 5px 5px 5px; } + .remove-bookmark { + float: right; + margin-top: -4px; + } } } diff --git a/app/assets/stylesheets/mobile/user.scss b/app/assets/stylesheets/mobile/user.scss index 1fff6649a47..9acae27ef6d 100644 --- a/app/assets/stylesheets/mobile/user.scss +++ b/app/assets/stylesheets/mobile/user.scss @@ -249,5 +249,9 @@ background-color: scale-color($highlight, $lightness: 50%); padding: 3px 5px 5px 5px; } + .remove-bookmark { + float: right !important; + margin-top: -8px; + } } } diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 83943160975..68abb60c4e0 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -122,6 +122,14 @@ class PostsController < ApplicationController display_post(post) end + def remove_bookmark_by_number + if current_user + post = find_post_from_params_by_number + PostAction.remove_act(current_user, post, PostActionType.types[:bookmark]) + end + render nothing: true + end + def reply_history post = find_post_from_params render_serialized(post.reply_history, PostSerializer) diff --git a/app/controllers/user_actions_controller.rb b/app/controllers/user_actions_controller.rb index 9b617546071..2c61e211b71 100644 --- a/app/controllers/user_actions_controller.rb +++ b/app/controllers/user_actions_controller.rb @@ -1,4 +1,5 @@ class UserActionsController < ApplicationController + def index params.require(:username) params.permit(:filter, :offset) @@ -24,9 +25,4 @@ class UserActionsController < ApplicationController render json: UserAction.stream_item(params[:id], guardian) end - def private_messages - # todo - end - - end diff --git a/app/models/user_action.rb b/app/models/user_action.rb index 0c19f8c93ee..b52df902ceb 100644 --- a/app/models/user_action.rb +++ b/app/models/user_action.rb @@ -103,6 +103,7 @@ SQL builder = SqlBuilder.new(" SELECT + a.id, t.title, a.action_type, a.created_at, t.id topic_id, a.user_id AS target_user_id, au.name AS target_name, au.username AS target_username, coalesce(p.post_number, 1) post_number, diff --git a/app/serializers/user_action_serializer.rb b/app/serializers/user_action_serializer.rb index 4e7629e4a82..7fed4913633 100644 --- a/app/serializers/user_action_serializer.rb +++ b/app/serializers/user_action_serializer.rb @@ -68,8 +68,12 @@ class UserActionSerializer < ApplicationSerializer object.post_type == Post.types[:moderator_action] end - def edit_reason - object.edit_reason if object.action_type == UserAction::EDIT + def include_reply_to_post_number? + object.action_type == UserAction::REPLY + end + + def include_edit_reason? + object.action_type == UserAction::EDIT end private diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 1baf4b00a44..77a1a8112e2 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -146,6 +146,7 @@ en: created: "you've bookmarked this post" not_bookmarked: "you've read this post; click to bookmark it" last_read: "this is the last post you've read; click to bookmark it" + remove: "Remove Bookmark" new_topics_inserted: "{{count}} new topics." show_new_topics: "Click to show." diff --git a/config/routes.rb b/config/routes.rb index 9e92d084720..a66953b547e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -214,6 +214,7 @@ Discourse::Application.routes.draw do post "uploads" => "uploads#create" get "posts/by_number/:topic_id/:post_number" => "posts#by_number" + put "posts/by_number/:topic_id/:post_number/bookmarks/remove" => "posts#remove_bookmark_by_number" get "posts/:id/reply-history" => "posts#reply_history" resources :groups do diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index 4b5474e5856..ec3941454b3 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -288,9 +288,13 @@ describe PostsController do let(:post) { Fabricate(:post, user: log_in) } it "raises an error if the user doesn't have permission to see the post" do - Guardian.any_instance.expects(:can_see?).with(post).returns(false) + Guardian.any_instance.expects(:can_see?).with(post).returns(false).twice + xhr :put, :bookmark, post_id: post.id, bookmarked: 'true' response.should be_forbidden + + xhr :put, :remove_bookmark_by_number, topic_id: post.topic_id, post_number: post.post_number + response.should be_forbidden end it 'creates a bookmark' do @@ -303,6 +307,11 @@ describe PostsController do xhr :put, :bookmark, post_id: post.id end + it 'removes a bookmark using the topic_id and the post_number' do + PostAction.expects(:remove_act).with(post.user, post, PostActionType.types[:bookmark]) + xhr :put, :remove_bookmark_by_number, topic_id: post.topic_id, post_number: post.post_number + end + end end