diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb index bd2dc4d13..f4c32ae68 100644 --- a/app/controllers/admins_controller.rb +++ b/app/controllers/admins_controller.rb @@ -31,8 +31,6 @@ class AdminsController < ApplicationController def stats @popular_tags = ActsAsTaggableOn::Tagging.joins(:tag).limit(15).count(:group => :tag, :order => 'count(taggings.id) DESC') - @most_liked_posts = Post.where(:type => ['StatusMessage', 'ActivityStreams::Photo'], - :public => true).order('likes_count DESC').limit(15).all @new_posts = Post.where(:type => ['StatusMessage','ActivityStreams::Photo'], :public => true).order('created_at DESC').limit(15).all end diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb index a436667a9..83be0a7cc 100644 --- a/app/controllers/likes_controller.rb +++ b/app/controllers/likes_controller.rb @@ -9,17 +9,16 @@ class LikesController < ApplicationController respond_to :html, :mobile, :json def create - target = current_user.find_visible_post_by_id params[:post_id] positive = (params[:positive] == 'true') ? true : false if target - @like = current_user.build_like(:positive => positive, :post => target) + @like = current_user.build_like(:positive => positive, :target => target) if @like.save Rails.logger.info("event=create type=like user=#{current_user.diaspora_handle} status=success like=#{@like.id} positive=#{positive}") Postzord::Dispatch.new(current_user, @like).post respond_to do |format| - format.js { render :status => 201 } + format.js { render 'likes/update', :status => 201 } format.html { render :nothing => true, :status => 201 } format.mobile { redirect_to post_path(@like.post_id) } end @@ -32,8 +31,12 @@ class LikesController < ApplicationController end def destroy - if @like = Like.where(:id => params[:id], :author_id => current_user.person.id, :post_id => params[:post_id]).first + if @like = Like.where(:id => params[:id], :author_id => current_user.person.id).first current_user.retract(@like) + respond_to do |format| + format.all{} + format.js{ render 'likes/update' } + end else respond_to do |format| format.mobile {redirect_to :back} @@ -43,11 +46,21 @@ class LikesController < ApplicationController end def index - if target = current_user.find_visible_post_by_id(params[:post_id]) + if target @likes = target.likes.includes(:author => :profile) render :layout => false else render :nothing => true, :status => 404 end end + + def target + @target ||= if params[:post_id] + current_user.find_visible_post_by_id(params[:post_id]) + else + comment = Comment.find(params[:comment_id]) + comment = nil unless current_user.find_visible_post_by_id(comment.post_id) + comment + end + end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 1b29378c9..79545b5cc 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -4,7 +4,7 @@ class SessionsController < Devise::SessionsController - after_filter :enqueue_update, :only => :create + #after_filter :enqueue_update, :only => :create protected def enqueue_update diff --git a/app/helpers/likes_helper.rb b/app/helpers/likes_helper.rb index 1de4f44b9..20614e1c2 100644 --- a/app/helpers/likes_helper.rb +++ b/app/helpers/likes_helper.rb @@ -8,11 +8,25 @@ module LikesHelper links.join(", ").html_safe end - def like_action(post, current_user=current_user) - if current_user.liked?(post) - link_to t('shared.stream_element.unlike'), post_like_path(post, current_user.like_for(post)), :method => :delete, :class => 'unlike', :remote => true + def like_action(target, current_user=current_user) + + target = target.model if target.instance_of?(PostsFake::Fake) + + if target.instance_of?(Comment) + if current_user.liked?(target) + link_to t('shared.stream_element.unlike'), comment_like_path(target, current_user.like_for(target)), :method => :delete, :class => 'unlike', :remote => true + else + link_to t('shared.stream_element.like'), comment_likes_path(target, :positive => 'true'), :method => :post, :class => 'like', :remote => true + end + else - link_to t('shared.stream_element.like'), post_likes_path(post, :positive => 'true'), :method => :post, :class => 'like', :remote => true + + if current_user.liked?(target) + link_to t('shared.stream_element.unlike'), post_like_path(target, current_user.like_for(target)), :method => :delete, :class => 'unlike', :remote => true + else + link_to t('shared.stream_element.like'), post_likes_path(target, :positive => 'true'), :method => :post, :class => 'like', :remote => true + end + end end end diff --git a/app/models/comment.rb b/app/models/comment.rb index fd416b370..65d4a3d6f 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -14,6 +14,7 @@ class Comment < ActiveRecord::Base include Diaspora::Socketable include Diaspora::Taggable + include Diaspora::Likeable acts_as_taggable_on :tags extract_tags_from :text diff --git a/app/models/like.rb b/app/models/like.rb index 9d549e909..16e3475f6 100644 --- a/app/models/like.rb +++ b/app/models/like.rb @@ -8,18 +8,27 @@ class Like < ActiveRecord::Base include Diaspora::Webhooks include Diaspora::Guid - include Diaspora::Relayable + xml_attr :target_type + include Diaspora::Relayable include Diaspora::Socketable xml_attr :positive xml_attr :diaspora_handle - belongs_to :post, :counter_cache => true + belongs_to :target, :polymorphic => true belongs_to :author, :class_name => 'Person' - validates_uniqueness_of :post_id, :scope => :author_id - validates_presence_of :author, :post + validates_uniqueness_of :target_id, :scope => [:target_type, :author_id] + validates_presence_of :author, :target + + after_create do + self.target.update_likes_counter + end + + after_destroy do + self.target.update_likes_counter + end def diaspora_handle self.author.diaspora_handle @@ -30,18 +39,20 @@ class Like < ActiveRecord::Base end def parent_class - Post + self.target_type.constantize end def parent - self.post + self.target end def parent= parent - self.post = parent + self.target = parent end def notification_type(user, person) - Notifications::Liked if self.post.author == user.person && user.person != person + #TODO(dan) need to have a notification for likes on comments, until then, return nil + return nil if self.target_type == "Comment" + Notifications::Liked if self.target.author == user.person && user.person != person end end diff --git a/app/models/notification.rb b/app/models/notification.rb index 167cad1a6..ec1dbe138 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -19,7 +19,7 @@ class Notification < ActiveRecord::Base if target.respond_to? :notification_type if note_type = target.notification_type(recipient, actor) if(target.is_a? Comment) || (target.is_a? Like) - n = note_type.concatenate_or_create(recipient, target.post, actor, note_type) + n = note_type.concatenate_or_create(recipient, target.parent, actor, note_type) else n = note_type.make_notification(recipient, target, actor, note_type) end diff --git a/app/models/post.rb b/app/models/post.rb index 97de06edc..45dec7028 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -9,13 +9,13 @@ class Post < ActiveRecord::Base include Diaspora::Webhooks include Diaspora::Guid + include Diaspora::Likeable + xml_attr :diaspora_handle xml_attr :public xml_attr :created_at has_many :comments, :dependent => :destroy - has_many :likes, :conditions => {:positive => true}, :dependent => :delete_all - has_many :dislikes, :conditions => {:positive => false}, :class_name => 'Like', :dependent => :delete_all has_many :aspect_visibilities has_many :aspects, :through => :aspect_visibilities diff --git a/app/models/status_message.rb b/app/models/status_message.rb index 145b724ae..3701d3066 100644 --- a/app/models/status_message.rb +++ b/app/models/status_message.rb @@ -24,7 +24,6 @@ class StatusMessage < Post serialize :youtube_titles, Hash - before_create :build_tags after_create :create_mentions def text(opts = {}) diff --git a/app/models/user.rb b/app/models/user.rb index d93087837..f8c2ecf47 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -183,26 +183,26 @@ class User < ActiveRecord::Base # Check whether the user has liked a post. Extremely inefficient if the post's likes are not loaded. # @param [Post] post - def liked?(post) - if post.likes.loaded? - if self.like_for(post) + def liked?(target) + if target.likes.loaded? + if self.like_for(target) return true else return false end else - Like.exists?(:author_id => self.person.id, :post_id => post.id) + Like.exists?(:author_id => self.person.id, :target_type => target.class.base_class.to_s, :target_id => target.id) end end # Get the user's like of a post, if there is one. Extremely inefficient if the post's likes are not loaded. # @param [Post] post # @return [Like] - def like_for(post) - if post.likes.loaded? - return post.likes.detect{ |like| like.author_id == self.person.id } + def like_for(target) + if target.likes.loaded? + return target.likes.detect{ |like| like.author_id == self.person.id } else - return Like.where(:author_id => self.person.id, :post_id => post.id).first + return Like.where(:author_id => self.person.id, :target_type => target.class.base_class.to_s, :target_id => target.id).first end end @@ -215,13 +215,11 @@ class User < ActiveRecord::Base end ######### Posts and Such ############### - def retract(post) - if post.respond_to?(:relayable?) && post.relayable? - aspects = post.parent.aspects - retraction = RelayableRetraction.build(self, post) + def retract(target) + if target.respond_to?(:relayable?) && target.relayable? + retraction = RelayableRetraction.build(self, target) else - aspects = post.aspects - retraction = Retraction.for(post) + retraction = Retraction.for(target) end mailman = Postzord::Dispatch.new(self, retraction) diff --git a/app/views/comments/_comment.html.haml b/app/views/comments/_comment.html.haml index 5f2203a6b..516266519 100644 --- a/app/views/comments/_comment.html.haml +++ b/app/views/comments/_comment.html.haml @@ -2,7 +2,7 @@ -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. -%li.comment.posted{:data=>{:guid => comment.id}, :class => ("hidden" if(defined? hidden))} +%li.comment.posted{:id => comment.guid, :class => ("hidden" if(defined? hidden))} - if current_user && (current_user.owns?(comment) || current_user.owns?(post)) .right.controls = link_to image_tag('deletelabel.png'), post_comment_path(comment.post_id, comment), :confirm => t('are_you_sure'), :method => :delete, :remote => true, :class => "delete comment_delete", :title => t('delete') @@ -16,7 +16,18 @@ %span{:class => direction_for(comment.text)} = markdownify(Diaspora::Taggable.format_tags(comment.text), :youtube_maps => comment.youtube_titles) - %br + .comment_info %time.timeago{:datetime => comment.created_at} = comment.created_at ? timeago(comment.created_at) : timeago(Time.now) + · + + .likes + = render "likes/likes_container", :target_id => comment.id, :likes_count => comment.likes_count + + - if comment.likes_count > 0 + · + + - unless (defined?(@commenting_disabled) && @commenting_disabled) + %span.like_action + = like_action(comment, current_user) diff --git a/app/views/comments/_comments.haml b/app/views/comments/_comments.haml index 304550afc..85254bb51 100644 --- a/app/views/comments/_comments.haml +++ b/app/views/comments/_comments.haml @@ -5,7 +5,7 @@ - unless comments_expanded %ul.show_comments{:class => ("hidden" if post.comments.size <= 3)} %li - %b= comment_toggle( post) + = comment_toggle( post) %ul.comments{:id => post.id, :class => ('loaded' if post.comments.size <= 3)} -if post.comments.size > 3 && !comments_expanded @@ -14,5 +14,5 @@ = render :partial => 'comments/comment', :collection => post.comments, :locals => {:post => post} - unless @commenting_disabled - .new_comment_form_wrapper{:class => ( 'hidden' if post.comments.size == 0)} + .new_comment_form_wrapper{:class => ('hidden' if post.comments.size == 0)} = new_comment_form(post.id, current_user) diff --git a/app/views/comments/create.js.erb b/app/views/comments/create.js.erb index bc4bba110..9b7c7bde5 100644 --- a/app/views/comments/create.js.erb +++ b/app/views/comments/create.js.erb @@ -1,5 +1,5 @@ -WebSocketReceiver.processComment(<%= @comment.post_id %>, - <%= @comment.id %>, +WebSocketReceiver.processComment("<%= @comment.post.guid %>", + "<%= @comment.guid%>", "<%= escape_javascript(render(:partial => 'comments/comment', :locals => { :comment => @comment, :person => current_user.person}))%>", false); diff --git a/app/views/likes/_likes_container.haml b/app/views/likes/_likes_container.haml index 1e1b03fe0..0a8ea4de0 100644 --- a/app/views/likes/_likes_container.haml +++ b/app/views/likes/_likes_container.haml @@ -2,11 +2,13 @@ -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. -- if likes_count > 0 - .likes_container - .likes - = image_tag('icons/heart.svg') - = link_to t('likes.likes.people_like_this', :count => likes_count), post_likes_path(post_id), :class => "expand_likes" - %span.hidden.likes_list - /= render 'likes/likes', :likes => likes +.likes_container + - if likes_count > 0 + = image_tag('icons/heart.svg') + - if defined?(likes_index_link) && likes_index_link + = link_to t('likes.likes.people_like_this', :count => likes_count), post_likes_path(target_id), :class => "expand_likes" + - else + = t('likes.likes.people_like_this', :count => likes_count) + %span.hidden.likes_list + /= render 'likes/likes', :likes => likes diff --git a/app/views/likes/create.js.erb b/app/views/likes/create.js.erb deleted file mode 100644 index ff6f0ef63..000000000 --- a/app/views/likes/create.js.erb +++ /dev/null @@ -1,4 +0,0 @@ -$(".like_action", ".stream_element[data-guid=<%=@like.post_id%>]").html("<%= escape_javascript(like_action(@like.post))%>"); - -WebSocketReceiver.processLike("<%=@like.post_id%>", "<%= escape_javascript(render("likes/likes_container", :post_id => @like.post_id, :likes_count => @like.post.likes.count)) %>"); - diff --git a/app/views/likes/destroy.js.erb b/app/views/likes/destroy.js.erb deleted file mode 100644 index 0f9663348..000000000 --- a/app/views/likes/destroy.js.erb +++ /dev/null @@ -1,3 +0,0 @@ -$(".like_action", ".stream_element[data-guid=<%=@like.post_id%>]").html("<%= escape_javascript(like_action(@like.post))%>"); -WebSocketReceiver.processLike("<%=@like.post_id%>", "<%= escape_javascript(render("likes/likes_container", :post_id => @like.post_id, :likes_count => @like.post.likes.count)) %>"); - diff --git a/app/views/likes/update.js.erb b/app/views/likes/update.js.erb new file mode 100644 index 000000000..c93faafa3 --- /dev/null +++ b/app/views/likes/update.js.erb @@ -0,0 +1,2 @@ +$(".like_action", "#<%=@like.target.guid%>").first().html("<%= escape_javascript(like_action(@like.target))%>"); +WebSocketReceiver.processLike("<%=@like.target.guid%>", "<%= escape_javascript(render("likes/likes_container", :target_id => @like.target_id, :likes_count => @like.target.likes_count)) %>"); diff --git a/app/views/notifier/liked.html.haml b/app/views/notifier/liked.html.haml index ae853a4db..6cf23d193 100644 --- a/app/views/notifier/liked.html.haml +++ b/app/views/notifier/liked.html.haml @@ -4,12 +4,11 @@ = t('.liked', :name => "#{@sender.name} (#{@sender.diaspora_handle})") %p - = @like.post.formatted_message(:plain_text => true) + = @like.target.formatted_message(:plain_text => true) %p %br - = link_to t('.sign_in'), post_url(@like.post) - + = link_to t('.sign_in'), post_url(@like.target) %br = t('notifier.love') %br diff --git a/app/views/notifier/liked.text.haml b/app/views/notifier/liked.text.haml index 4b2a4e642..9cc0740a6 100644 --- a/app/views/notifier/liked.text.haml +++ b/app/views/notifier/liked.text.haml @@ -2,7 +2,7 @@ != t('notifier.liked.liked', :name => "#{@sender.name} (#{@sender.diaspora_handle})") -!= @like.post.formatted_message(:plain_text => true) +!= @like.target.formatted_message(:plain_text => true) != t('notifier.love') != t('notifier.diaspora') diff --git a/app/views/post_visibilities/update.js.erb b/app/views/post_visibilities/update.js.erb index 010c13a6b..b7c4a701a 100644 --- a/app/views/post_visibilities/update.js.erb +++ b/app/views/post_visibilities/update.js.erb @@ -1,3 +1,3 @@ -var target = $(".stream_element[data-guid=<%= escape_javascript(@post.id.to_s) %>]") +var target = $("#<%= @post.guid %>") target.find(".sm_body").toggleClass("hidden"); target.find(".undo_text").toggleClass("hidden"); diff --git a/app/views/posts/destroy.js.erb b/app/views/posts/destroy.js.erb index e873e96c7..5a0c3dd83 100644 --- a/app/views/posts/destroy.js.erb +++ b/app/views/posts/destroy.js.erb @@ -1,2 +1,2 @@ -var target = $(".stream_element[data-guid=<%= escape_javascript(@post.id.to_s) %>]") +var target = $("#<%= @post.guid %>") target.hide('blind', { direction: 'vertical' }, 300, function(){ target.remove() }); diff --git a/app/views/shared/_stream_element.html.haml b/app/views/shared/_stream_element.html.haml index 113f47c28..0c1d4769a 100644 --- a/app/views/shared/_stream_element.html.haml +++ b/app/views/shared/_stream_element.html.haml @@ -2,7 +2,7 @@ -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. -.stream_element{:data=>{:guid=>post.id}} +.stream_element{:id => post.guid} - if current_user && post.author.owner_id == current_user.id .right.controls = link_to image_tag('deletelabel.png'), post_path(post), :confirm => t('are_you_sure'), :method => :delete, :remote => true, :class => "delete stream_element_delete", :title => t('delete') @@ -55,8 +55,7 @@ · = link_to t('comments.new_comment.comment'), '#', :class => 'focus_comment_textarea' - .likes - - if post.likes_count > 0 - = render "likes/likes_container", :post_id => post.id, :likes_count => post.likes_count, :current_user => current_user + .likes.on_post + = render "likes/likes_container", :target_id => post.id, :likes_count => post.likes_count, :current_user => current_user, :likes_index_link => true = render "comments/comments", :post => post, :current_user => current_user, :commenting_disabled => (defined?(@commenting_disabled) && @commenting_disabled) diff --git a/app/views/status_messages/create.js.erb b/app/views/status_messages/create.js.erb index cfd9a5ef0..02965ff00 100644 --- a/app/views/status_messages/create.js.erb +++ b/app/views/status_messages/create.js.erb @@ -8,4 +8,4 @@ :all_aspects => current_user.aspects } ), - :post_id => @status_message.id}.to_json.html_safe%> + :post_id => @status_message.guid}.to_json.html_safe%> diff --git a/config/routes.rb b/config/routes.rb index 1a7926762..0f844e329 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,6 +19,12 @@ Diaspora::Application.routes.draw do resources :comments, :only => [:create, :destroy, :index] end + # roll up likes into a nested resource above + resources :comments, :only => [:create, :destroy] do + resources :likes, :only => [:create, :destroy, :index] + end + + get 'bookmarklet' => 'status_messages#bookmarklet' get 'p/:id' => 'publics#post', :as => 'public_post' diff --git a/db/migrate/20110707234802_likes_on_comments.rb b/db/migrate/20110707234802_likes_on_comments.rb new file mode 100644 index 000000000..7b3e80e75 --- /dev/null +++ b/db/migrate/20110707234802_likes_on_comments.rb @@ -0,0 +1,37 @@ +class LikesOnComments < ActiveRecord::Migration + class Likes < ActiveRecord::Base; end + def self.up + remove_foreign_key :likes, :posts + + add_column :likes, :target_type, :string, :limit => 60, :null => false + rename_column :likes, :post_id, :target_id + + add_column :comments, :likes_count, :integer, :default => 0, :null => false + + execute < 1') + keeper_likes.each do |like| + l = Like.arel_table + Like.where(:target_id => like.target_id, :author_id => like.author_id, :target_type => like.target_type).where(l[:id].not_eq(like.id)).delete_all + end + add_index :likes, [:target_id, :author_id, :target_type], :unique => true + end + + def self.down + remove_column :comments, :likes_count + + remove_column :likes, :target_type + rename_column :likes, :target_id, :post_id + add_index :likes, :post_id + remove_index :likes, :target_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 536e3167e..da2eb61fc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110707221112) do +ActiveRecord::Schema.define(:version => 20110707234802) do create_table "aspect_memberships", :force => true do |t| t.integer "aspect_id", :null => false @@ -46,15 +46,16 @@ ActiveRecord::Schema.define(:version => 20110707221112) do add_index "aspects", ["user_id"], :name => "index_aspects_on_user_id" create_table "comments", :force => true do |t| - t.text "text", :null => false - t.integer "post_id", :null => false - t.integer "author_id", :null => false - t.string "guid", :null => false + t.text "text", :null => false + t.integer "post_id", :null => false + t.integer "author_id", :null => false + t.string "guid", :null => false t.text "author_signature" t.text "parent_author_signature" t.text "youtube_titles" t.datetime "created_at" t.datetime "updated_at" + t.integer "likes_count", :default => 0, :null => false end add_index "comments", ["author_id"], :name => "index_comments_on_person_id" @@ -109,19 +110,21 @@ ActiveRecord::Schema.define(:version => 20110707221112) do add_index "invitations", ["sender_id"], :name => "index_invitations_on_sender_id" create_table "likes", :force => true do |t| - t.boolean "positive", :default => true - t.integer "post_id" + t.boolean "positive", :default => true + t.integer "target_id" t.integer "author_id" t.string "guid" t.text "author_signature" t.text "parent_author_signature" t.datetime "created_at" t.datetime "updated_at" + t.string "target_type", :limit => 60, :null => false end add_index "likes", ["author_id"], :name => "likes_author_id_fk" add_index "likes", ["guid"], :name => "index_likes_on_guid", :unique => true - add_index "likes", ["post_id"], :name => "index_likes_on_post_id" + add_index "likes", ["target_id", "author_id", "target_type"], :name => "index_likes_on_target_id_and_author_id_and_target_type", :unique => true + add_index "likes", ["target_id"], :name => "index_likes_on_post_id" create_table "mentions", :force => true do |t| t.integer "post_id", :null => false @@ -418,7 +421,6 @@ ActiveRecord::Schema.define(:version => 20110707221112) do add_foreign_key "invitations", "users", :name => "invitations_sender_id_fk", :column => "sender_id", :dependent => :delete add_foreign_key "likes", "people", :name => "likes_author_id_fk", :column => "author_id", :dependent => :delete - add_foreign_key "likes", "posts", :name => "likes_post_id_fk", :dependent => :delete add_foreign_key "messages", "conversations", :name => "messages_conversation_id_fk", :dependent => :delete add_foreign_key "messages", "people", :name => "messages_author_id_fk", :column => "author_id", :dependent => :delete diff --git a/lib/diaspora/likeable.rb b/lib/diaspora/likeable.rb new file mode 100644 index 000000000..6d0fc532d --- /dev/null +++ b/lib/diaspora/likeable.rb @@ -0,0 +1,20 @@ +# Copyright (c) 2010, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. + +module Diaspora + module Likeable + def self.included(model) + model.instance_eval do + has_many :likes, :conditions => {:positive => true}, :dependent => :delete_all, :as => :target + has_many :dislikes, :conditions => {:positive => false}, :class_name => 'Like', :dependent => :delete_all, :as => :target + end + end + + # @return [Integer] + def update_likes_counter + self.likes_count = self.likes.count + self.save + end + end +end diff --git a/lib/diaspora/relayable.rb b/lib/diaspora/relayable.rb index acdd8526d..e45bfb623 100644 --- a/lib/diaspora/relayable.rb +++ b/lib/diaspora/relayable.rb @@ -55,7 +55,7 @@ module Diaspora Postzord::Dispatch.new(user, object).post end - object.socket_to_user(user, :aspect_ids => object.parent.aspect_ids) if object.respond_to? :socket_to_user + object.socket_to_user(user) if object.respond_to? :socket_to_user if object.after_receive(user, person) object end @@ -69,16 +69,18 @@ module Diaspora #sign relayable as model creator self.author_signature = self.sign_with_key(author.owner.encryption_key) - if !self.post_id.blank? && self.author.owns?(self.parent) + if !self.parent.blank? && self.author.owns?(self.parent) #sign relayable as parent object owner self.parent_author_signature = sign_with_key(author.owner.encryption_key) end end + # @return [Boolean] def verify_parent_author_signature verify_signature(self.parent_author_signature, self.parent.author) end + # @return [Boolean] def signature_valid? verify_signature(self.author_signature, self.author) end diff --git a/lib/diaspora/taggable.rb b/lib/diaspora/taggable.rb index 8302cbe81..0c706a8eb 100644 --- a/lib/diaspora/taggable.rb +++ b/lib/diaspora/taggable.rb @@ -11,6 +11,8 @@ module Diaspora cattr_accessor :field_with_tags end model.instance_eval do + before_create :build_tags + def extract_tags_from sym self.field_with_tags = sym end diff --git a/lib/diaspora/user/querying.rb b/lib/diaspora/user/querying.rb index 4a6fa6680..3e7cf3478 100644 --- a/lib/diaspora/user/querying.rb +++ b/lib/diaspora/user/querying.rb @@ -7,7 +7,7 @@ module Diaspora module Querying def find_visible_post_by_id( id, opts={} ) - post = Post.where(:id => id).joins(:contacts).where(:contacts => {:user_id => self.id}).where(opts).first + post = Post.where(:id => id).joins(:contacts).where(:contacts => {:user_id => self.id}).where(opts).select("posts.*").first post ||= Post.where(:id => id, :author_id => self.person.id).where(opts).first post ||= Post.where(:id => id, :public => true).where(opts).first end diff --git a/public/javascripts/content-updater.js b/public/javascripts/content-updater.js index 0f1e115c7..a3c0c7dd0 100644 --- a/public/javascripts/content-updater.js +++ b/public/javascripts/content-updater.js @@ -4,14 +4,11 @@ */ var ContentUpdater = { - elementWithGuid: function(selector, guid) { - return $(selector + "[data-guid='" + guid + "']"); - }, addPostToStream: function(html) { var streamElement = $(html); - var postId = streamElement.attr("data-guid"); + var postGUID = $(streamElement).attr('id'); - if($(".stream_element[data-guid='" + postId + "']").length === 0) { + if($("#"+postGUID).length === 0) { if($("#no_posts").length) { $("#no_posts").detach(); } @@ -20,19 +17,19 @@ var ContentUpdater = { streamElement.find("label").inFieldLabels(); }); - Diaspora.widgets.publish("stream/postAdded", [postId]); + Diaspora.widgets.publish("stream/postAdded", [postGUID]); Diaspora.widgets.timeago.updateTimeAgo(); Diaspora.widgets.directionDetector.updateBinds(); } }, - addLikesToPost: function(postId, html) { - var post = ContentUpdater.elementWithGuid("div", postId); + addLikesToPost: function(postGUID, html) { + var post = $("#" + postGUID); $(".likes_container", post) .fadeOut("fast") .html(html) .fadeIn("fast"); } - -}; \ No newline at end of file + +}; diff --git a/public/javascripts/view.js b/public/javascripts/view.js index 4ae588bb6..90b7d500e 100644 --- a/public/javascripts/view.js +++ b/public/javascripts/view.js @@ -79,7 +79,7 @@ var View = { /* notification routing */ $("#notification").delegate('.hard_object_link', 'click', function(evt){ - var post = $("*[data-guid='"+ $(this).attr('data-ref') +"']"), + var post = $("#"+ $(this).attr('data-ref')), lastComment = post.find('.comment.posted').last(); if(post.length > 0){ diff --git a/public/javascripts/web-socket-receiver.js b/public/javascripts/web-socket-receiver.js index ddd36e354..1ae0db45a 100644 --- a/public/javascripts/web-socket-receiver.js +++ b/public/javascripts/web-socket-receiver.js @@ -33,7 +33,12 @@ var WebSocketReceiver = { WebSocketReceiver.processPerson(obj); } else { - WSR.debug("got a " + obj['class'] + " for aspects " + obj.aspect_ids); + debug_string = "got a " + obj['class']; + if(obj.aspect_ids !== undefined){ + debug_string += " for aspects " + obj.aspect_ids; + } + + WSR.debug(debug_string); if (obj['class']=="retractions") { WebSocketReceiver.processRetraction(obj.post_id); @@ -77,7 +82,7 @@ var WebSocketReceiver = { }, processRetraction: function(post_id){ - $("*[data-guid='" + post_id + "']").fadeOut(400, function() { + $("#" + post_id).fadeOut(400, function() { $(this).remove(); }); if($("#main_stream")[0].childElementCount === 0) { @@ -85,11 +90,10 @@ var WebSocketReceiver = { } }, - processComment: function(postId, commentId, html, opts) { + processComment: function(postGUID, commentGUID, html, opts) { - if( $(".comment[data-guid='"+commentId+"']").length === 0 ) { - - var post = $("*[data-guid='"+postId+"']'"), + if( $("#"+commentGUID).length === 0 ) { + var post = $("#"+postGUID), prevComments = $('.comment.posted', post); if(prevComments.length > 0) { @@ -123,9 +127,8 @@ var WebSocketReceiver = { Diaspora.widgets.directionDetector.updateBinds(); }, - processLike: function(postId, html) { - var post = $("*[data-guid='"+postId+"']"); - $('.likes', post).html(html); + processLike: function(targetGUID, html) { + $('.likes', "#" + targetGUID).first().html(html); }, processPost: function(className, postId, html, aspectIds) { diff --git a/public/stylesheets/sass/application.sass b/public/stylesheets/sass/application.sass index 22cf2154b..a5ed7b3c6 100644 --- a/public/stylesheets/sass/application.sass +++ b/public/stylesheets/sass/application.sass @@ -302,10 +302,10 @@ ul.as-selections :height 370px :width 500px - time - :font - :weight normal - :size smaller + .comment + .comment_info + :font + :size smaller .from a @@ -667,9 +667,23 @@ form.new_comment :display inline-block -.comments - .timeago - :color #999 +.comment + .likes, + .likes_container + :display inline + :padding 0 + :margin 0 + :font + :size 10px + a + :font + :weight normal + + img + :margin 0 + :height 9px + :width 9px + :top 1px .stream.show ul.comments @@ -816,6 +830,7 @@ label :cursor pointer #publisher + :z-index 0 :color #999 :position relative @@ -2210,6 +2225,11 @@ ul.show_comments :border :top 1px dotted #aaa +ul.show_comments, +.likes_container + a + :color #999 + .likes_container :margin :bottom -4px @@ -2217,18 +2237,11 @@ ul.show_comments ul.show_comments, .likes_container - * - :font - :weight bold - :color #999 - img :position relative - :top 3px + :top 2px :height 12px :width 12px - :margin - :left 0.5em .mark_all_read :position relative diff --git a/spec/controllers/likes_controller_spec.rb b/spec/controllers/likes_controller_spec.rb index e30f2954d..71a2e097d 100644 --- a/spec/controllers/likes_controller_spec.rb +++ b/spec/controllers/likes_controller_spec.rb @@ -15,108 +15,122 @@ describe LikesController do sign_in :user, @user1 end - describe '#create' do - let(:like_hash) { - {:positive => 1, - :post_id => "#{@post.id}"} - } - let(:dislike_hash) { - {:positive => 0, - :post_id => "#{@post.id}"} - } + [Comment, Post].each do |class_const| + context class_const.to_s do + let(:id_field){ + "#{class_const.to_s.underscore}_id" + } - context "on my own post" do - before do - @post = @user1.post :status_message, :text => "AWESOME", :to => @aspect1.id + describe '#create' do + let(:like_hash) { + {:positive => 1, + id_field => "#{@target.id}"} + } + let(:dislike_hash) { + {:positive => 0, + id_field => "#{@target.id}"} + } + + context "on my own post" do + before do + @target = @user1.post :status_message, :text => "AWESOME", :to => @aspect1.id + + @target = @user1.comment "hey", :post => @target if class_const == Comment + end + + it 'responds to format js' do + post :create, like_hash.merge(:format => 'js') + response.code.should == '201' + end + end + + context "on a post from a contact" do + before do + @target = @user2.post :status_message, :text => "AWESOME", :to => @aspect2.id + @target = @user2.comment "hey", :post => @target if class_const == Comment + end + + it 'likes' do + post :create, like_hash + response.code.should == '201' + end + + it 'dislikes' do + post :create, dislike_hash + response.code.should == '201' + end + + it "doesn't post multiple times" do + @user1.like(1, :target => @target) + post :create, dislike_hash + response.code.should == '422' + end + end + + context "on a post from a stranger" do + before do + @target = eve.post :status_message, :text => "AWESOME", :to => eve.aspects.first.id + @target = eve.comment "hey", :post => @target if class_const == Comment + end + + it "doesn't post" do + @user1.should_not_receive(:like) + post :create, like_hash + response.code.should == '422' + end + end end - it 'responds to format js' do - post :create, like_hash.merge(:format => 'js') - response.code.should == '201' - end - end + describe '#index' do + before do + @message = alice.post(:status_message, :text => "hey", :to => @aspect1.id) + @message = alice.comment( "hey", :post => @message) if class_const == Comment + end + it 'returns a 404 for a post not visible to the user' do + sign_in eve + get :index, id_field => @message.id + end - context "on a post from a contact" do - before do - @post = @user2.post :status_message, :text => "AWESOME", :to => @aspect2.id + it 'returns an array of likes for a post' do + like = bob.build_like(:positive => true, :target => @message) + like.save! + + get :index, id_field => @message.id + assigns[:likes].map(&:id).should == @message.likes.map(&:id) + end + + it 'returns an empty array for a post with no likes' do + get :index, id_field => @message.id + assigns[:likes].should == [] + end end - it 'likes' do - post :create, like_hash - response.code.should == '201' + describe '#destroy' do + before do + @message = bob.post(:status_message, :text => "hey", :to => @aspect1.id) + @message = bob.comment( "hey", :post => @message) if class_const == Comment + @like = alice.build_like(:positive => true, :target => @message) + @like.save + end + + it 'lets a user destroy their like' do + expect { + delete :destroy, :format => "js", id_field => @like.target_id, :id => @like.id + }.should change(Like, :count).by(-1) + response.status.should == 200 + end + + it 'does not let a user destroy other likes' do + like2 = eve.build_like(:positive => true, :target => @message) + like2.save + + expect { + delete :destroy, :format => "js", id_field => like2.target_id, :id => like2.id + }.should_not change(Like, :count) + + response.status.should == 403 + end end - - it 'dislikes' do - post :create, dislike_hash - response.code.should == '201' - end - - it "doesn't post multiple times" do - @user1.like(1, :post => @post) - post :create, dislike_hash - response.code.should == '422' - end - end - - context "on a post from a stranger" do - before do - @post = eve.post :status_message, :text => "AWESOME", :to => eve.aspects.first.id - end - - it "doesn't post" do - @user1.should_not_receive(:like) - post :create, like_hash - response.code.should == '422' - end - end - end - - describe '#index' do - before do - @message = alice.post(:status_message, :text => "hey", :to => @aspect1.id) - end - it 'returns a 404 for a post not visible to the user' do - sign_in eve - get :index, :post_id => @message.id - end - - it 'returns an array of likes for a post' do - like = bob.build_like(:positive => true, :post => @message) - like.save! - - get :index, :post_id => @message.id - assigns[:likes].map(&:id).should == @message.likes.map(&:id) - end - - it 'returns an empty array for a post with no likes' do - get :index, :post_id => @message.id - assigns[:likes].should == [] - end - end - - describe '#destroy' do - before do - @message = bob.post(:status_message, :text => "hey", :to => @aspect1.id) - @like = alice.build_like(:positive => true, :post => @message) - @like.save - end - - it 'lets a user destroy their like' do - expect { - delete :destroy, :format => "js", :post_id => @like.post_id, :id => @like.id - }.should change(Like, :count).by(-1) - response.status.should == 200 - end - - it 'does not let a user destroy other likes' do - like2 = eve.build_like(:positive => true, :post => @message) - like2.save - - expect { - delete :destroy, :format => "js", :post_id => like2.post_id, :id => like2.id - }.should_not change(Like, :count) - - response.status.should == 403 end end end diff --git a/spec/factories.rb b/spec/factories.rb index f25b92fbe..7d9ebe9ca 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -37,7 +37,7 @@ end Factory.define :like do |x| x.association :author, :factory => :person - x.association :post, :factory => :status_message + x.association :target, :factory => :status_message end Factory.define :user do |u| diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb index 3e4d599a2..59a9e827f 100644 --- a/spec/helpers/notifications_helper_spec.rb +++ b/spec/helpers/notifications_helper_spec.rb @@ -8,10 +8,10 @@ describe NotificationsHelper do @person = Factory(:person) @post = Factory(:status_message, :author => @user.person) @person2 = Factory(:person) - @notification = Notification.notify(@user, Factory(:like, :author => @person, :post => @post), @person) - @notification = Notification.notify(@user, Factory(:like, :author => @person2, :post => @post), @person2) - + @notification = Notification.notify(@user, Factory(:like, :author => @person, :target => @post), @person) + @notification = Notification.notify(@user, Factory(:like, :author => @person2, :target => @post), @person2) end + describe '#notification_people_link' do context 'formatting' do include ActionView::Helpers::SanitizeHelper diff --git a/spec/mailers/notifier_spec.rb b/spec/mailers/notifier_spec.rb index 69bf65f2c..1eeb068ab 100644 --- a/spec/mailers/notifier_spec.rb +++ b/spec/mailers/notifier_spec.rb @@ -102,7 +102,7 @@ describe Notifier do describe ".liked" do before do @sm = Factory(:status_message, :author => alice.person) - @like = @sm.likes.create(:author => bob.person) + @like = @sm.likes.create!(:author => bob.person) @mail = Notifier.liked(alice.id, @like.author.id, @like.id) end diff --git a/spec/models/like_spec.rb b/spec/models/like_spec.rb index ade64c299..018b2fc79 100644 --- a/spec/models/like_spec.rb +++ b/spec/models/like_spec.rb @@ -21,26 +21,26 @@ describe Like do describe 'User#like' do it "should be able to like on one's own status" do - alice.like(1, :post => @status) + alice.like(1, :target => @status) @status.reload.likes.first.positive.should == true end it "should be able to like on a contact's status" do - bob.like(0, :post => @status) + bob.like(0, :target => @status) @status.reload.dislikes.first.positive.should == false end it "does not allow multiple likes" do lambda { - alice.like(1, :post => @status) - alice.like(0, :post => @status) + alice.like(1, :target => @status) + alice.like(0, :target => @status) }.should raise_error end end describe '#notification_type' do before do - @like = @alice.like(1, :post => @status) + @like = @alice.like(1, :target => @status) end it 'should be notifications liked if you are the post owner' do @@ -59,9 +59,16 @@ describe Like do describe 'counter cache' do it 'increments the counter cache on its post' do lambda { - @alice.like(1, :post => @status) + @alice.like(1, :target => @status) }.should change{ @status.reload.likes_count }.by(1) end + + it 'increments the counter cache on its comment' do + comment = Factory(:comment, :post => @status) + lambda { + @alice.like(1, :target => comment) + }.should change{ comment.reload.likes_count }.by(1) + end end describe 'xml' do @@ -70,7 +77,7 @@ describe Like do @liker_aspect = @liker.aspects.create(:name => "dummies") connect_users(alice, @alices_aspect, @liker, @liker_aspect) @post = alice.post :status_message, :text => "huhu", :to => @alices_aspect.id - @like = @liker.like 0, :post => @post + @like = @liker.like 0, :target => @post @xml = @like.to_xml.to_s end it 'serializes the sender handle' do @@ -87,7 +94,7 @@ describe Like do @marshalled_like.author.should == @liker.person end it 'marshals the post' do - @marshalled_like.post.should == @post + @marshalled_like.target.should == @post end end end @@ -98,11 +105,11 @@ describe Like do @remote_parent = Factory.create(:status_message, :author => @remote_raphael) @local_parent = @local_luke.post :status_message, :text => "foobar", :to => @local_luke.aspects.first - @object_by_parent_author = @local_luke.like(1, :post => @local_parent) - @object_by_recipient = @local_leia.build_like(:positive => 1, :post => @local_parent) + @object_by_parent_author = @local_luke.like(1, :target => @local_parent) + @object_by_recipient = @local_leia.build_like(:positive => 1, :target => @local_parent) @dup_object_by_parent_author = @object_by_parent_author.dup - @object_on_remote_parent = @local_luke.like(0, :post => @remote_parent) + @object_on_remote_parent = @local_luke.like(0, :target => @remote_parent) end it_should_behave_like 'it is relayable' end diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 4a025d6ff..609392db1 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -96,8 +96,8 @@ describe Notification do it 'concatinates the like notifications' do p = Factory(:status_message, :author => @user.person) person2 = Factory(:person) - notification = Notification.notify(@user, Factory(:like, :author => @person, :post => p), @person) - notification2 = Notification.notify(@user, Factory(:like, :author => person2, :post => p), person2) + notification = Notification.notify(@user, Factory(:like, :author => @person, :target => p), @person) + notification2 = Notification.notify(@user, Factory(:like, :author => person2, :target => p), person2) notification.id.should == notification2.id end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c206fe1a7..f95c68d01 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -569,8 +569,8 @@ describe User do before do @message = alice.post(:status_message, :text => "cool", :to => alice.aspects.first) @message2 = bob.post(:status_message, :text => "uncool", :to => bob.aspects.first) - @like = alice.like(true, :post => @message) - @like2 = bob.like(true, :post => @message) + @like = alice.like(true, :target => @message) + @like2 = bob.like(true, :target => @message) end describe '#like_for' do