diff --git a/app/assets/javascripts/answerbox/comment/smile.coffee b/app/assets/javascripts/answerbox/comment/smile.coffee new file mode 100644 index 00000000..d713de7a --- /dev/null +++ b/app/assets/javascripts/answerbox/comment/smile.coffee @@ -0,0 +1,41 @@ +$(document).on "click", "button[name=ab-smile-comment]", -> + btn = $(this) + cid = btn[0].dataset.cId + action = btn[0].dataset.action + count = Number $("span#ab-comment-smile-count-#{cid}").html() + btn[0].dataset.loadingText = " #{count}" + btn.button "loading" + + target_url = switch action + when 'smile' + count++ + '/ajax/create_comment_smile' + when 'unsmile' + count-- + '/ajax/destroy_comment_smile' + + success = false + + $.ajax + url: target_url + type: 'POST' + data: + id: cid + success: (data, status, jqxhr) -> + success = data.success + if success + $("span#ab-comment-smile-count-#{cid}").html(count) + showNotification data.message, data.success + error: (jqxhr, status, error) -> + console.log jqxhr, status, error + showNotification "An error occurred, a developer should check the console for details", false + complete: (jqxhr, status) -> + btn.button "reset" + if success + switch action + when 'smile' + btn[0].dataset.action = 'unsmile' + btn.html " #{count}" + when 'unsmile' + btn[0].dataset.action = 'smile' + btn.html " #{count}" diff --git a/app/assets/javascripts/answerbox/smile.coffee b/app/assets/javascripts/answerbox/smile.coffee index 0595cdc1..ee029193 100644 --- a/app/assets/javascripts/answerbox/smile.coffee +++ b/app/assets/javascripts/answerbox/smile.coffee @@ -38,4 +38,4 @@ $(document).on "click", "button[name=ab-smile]", -> btn.html " #{count}" when 'unsmile' btn[0].dataset.action = 'smile' - btn.html " #{count}" \ No newline at end of file + btn.html " #{count}" diff --git a/app/assets/stylesheets/base.css.scss b/app/assets/stylesheets/base.css.scss index c580bf84..a4086ec2 100644 --- a/app/assets/stylesheets/base.css.scss +++ b/app/assets/stylesheets/base.css.scss @@ -6,6 +6,7 @@ body { background-color: #fafafa; } +@import "scss/generic"; @import "scss/answerbox"; @import "scss/comments"; @import "scss/entry"; diff --git a/app/assets/stylesheets/scss/answerbox.scss b/app/assets/stylesheets/scss/answerbox.scss index a8553b8b..362efe92 100644 --- a/app/assets/stylesheets/scss/answerbox.scss +++ b/app/assets/stylesheets/scss/answerbox.scss @@ -41,3 +41,19 @@ font-size: 12px; line-height: 1.3em; } + +.answerbox [name="ab-smile"], .answerbox [name="ab-smile-comment"] { + padding: 6px 11px; + padding-left: 21px; + position: relative; + overflow: hidden; + border: none; + + i.fa.fa-smile-o, i.fa.fa-frown-o, i.fa.fa-meh-o { + position: absolute; + font-size: 3em; + left: -13px; + top: -10px; + display: block; + } +} diff --git a/app/assets/stylesheets/scss/generic.scss b/app/assets/stylesheets/scss/generic.scss new file mode 100644 index 00000000..f4ecb99c --- /dev/null +++ b/app/assets/stylesheets/scss/generic.scss @@ -0,0 +1,26 @@ +.user-list { + margin: 0; + padding: 0; +} + +.user-list-entry-smiles { + * { + display: inline-block; + vertical-align: middle; + } + + img { + height: 64px + } + + span { + margin-left: 5px; + } + + a:hover { + text-decoration: none; + span { + text-decoration: underline; + } + } +} diff --git a/app/controllers/ajax/smile_controller.rb b/app/controllers/ajax/smile_controller.rb index f6f8fa82..d834d483 100644 --- a/app/controllers/ajax/smile_controller.rb +++ b/app/controllers/ajax/smile_controller.rb @@ -36,4 +36,42 @@ class Ajax::SmileController < ApplicationController @message = "Successfully unsmiled answer." @success = true end + + def create_comment + params.require :id + + comment = Comment.find(params[:id]) + + begin + current_user.smile_comment comment + rescue + @status = :fail + @message = "You have already smiled that comment." + @success = false + return + end + + @status = :okay + @message = "Successfully smiled comment." + @success = true + end + + def destroy_comment + params.require :id + + comment = Comment.find(params[:id]) + + begin + current_user.unsmile_comment comment + rescue + @status = :fail + @message = "You have not smiled that comment." + @success = false + return + end + + @status = :okay + @message = "Successfully unsmiled comment." + @success = true + end end diff --git a/app/models/comment.rb b/app/models/comment.rb index abc04dbe..94059387 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -3,6 +3,7 @@ class Comment < ActiveRecord::Base belongs_to :answer validates :user_id, presence: true validates :answer_id, presence: true + has_many :smiles, class_name: "CommentSmile", foreign_key: :comment_id, dependent: :destroy validates :content, length: { maximum: 160 } diff --git a/app/models/comment_smile.rb b/app/models/comment_smile.rb new file mode 100644 index 00000000..8c116723 --- /dev/null +++ b/app/models/comment_smile.rb @@ -0,0 +1,22 @@ +class CommentSmile < ActiveRecord::Base + belongs_to :user + belongs_to :comment + validates :user_id, presence: true, uniqueness: { scope: :comment_id, message: "already smiled comment" } + validates :comment_id, presence: true + + after_create do + Notification.notify comment.user, self unless comment.user == user + user.increment! :comment_smiled_count + comment.increment! :smile_count + end + + before_destroy do + Notification.denotify comment.user, self unless comment.user == user + user.decrement! :comment_smiled_count + comment.decrement! :smile_count + end + + def notification_type(*_args) + Notifications::CommentSmiled + end +end diff --git a/app/models/notifications/comment_smiled.rb b/app/models/notifications/comment_smiled.rb new file mode 100644 index 00000000..7b013c46 --- /dev/null +++ b/app/models/notifications/comment_smiled.rb @@ -0,0 +1,2 @@ +class Notifications::CommentSmiled < Notification +end diff --git a/app/models/user.rb b/app/models/user.rb index c991e2a2..b2bdd111 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -20,6 +20,7 @@ class User < ActiveRecord::Base has_many :friends, through: :active_relationships, source: :target has_many :followers, through: :passive_relationships, source: :source has_many :smiles, dependent: :destroy + has_many :comment_smiles, dependent: :destroy has_many :services, dependent: :destroy has_many :notifications, foreign_key: :recipient_id, dependent: :destroy has_many :reports, dependent: :destroy @@ -140,10 +141,26 @@ class User < ActiveRecord::Base Smile.find_by(user: self, answer: answer).destroy end + # smiles a comment + # @param comment [Comment] the comment to smile + def smile_comment(comment) + CommentSmile.create!(user: self, comment: comment) + end + + # unsmile an comment + # @param comment [Comment] the comment to unsmile + def unsmile_comment(comment) + CommentSmile.find_by(user: self, comment: comment).destroy + end + def smiled?(answer) answer.smiles.pluck(:user_id).include? self.id end + def smiled_comment?(comment) + comment.smiles.pluck(:user_id).include? self.id + end + def display_website website.match(/https?:\/\/([A-Za-z.\-0-9]+)\/?(?:.*)/i)[1] rescue NoMethodError diff --git a/app/views/ajax/smile/create_comment.json.jbuilder b/app/views/ajax/smile/create_comment.json.jbuilder new file mode 100644 index 00000000..f98b3c38 --- /dev/null +++ b/app/views/ajax/smile/create_comment.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'ajax/shared/status' diff --git a/app/views/ajax/smile/destroy_comment.json.jbuilder b/app/views/ajax/smile/destroy_comment.json.jbuilder new file mode 100644 index 00000000..f98b3c38 --- /dev/null +++ b/app/views/ajax/smile/destroy_comment.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'ajax/shared/status' diff --git a/app/views/layouts/_notifications.html.haml b/app/views/layouts/_notifications.html.haml index 6eb6db5f..2bed92ba 100644 --- a/app/views/layouts/_notifications.html.haml +++ b/app/views/layouts/_notifications.html.haml @@ -50,6 +50,18 @@ your answer = time_ago_in_words notification.target.created_at ago + - when "CommentSmile" + .pull-left + %img.img-rounded.notification--dropdown-img{src: gravatar_url(notification.target.user)} + .media-body + %h6.media-heading.notification--dropdown-user + = user_screen_name notification.target.user + .notification--dropdown-text + smiled at + %a{href: show_user_answer_path(username: notification.target.user.screen_name, id: notification.target.comment.answer.id), title: "#{notification.target.comment.content[0..40]}...", data: { toggle: :tooltip, placement: :top }} + your comment + = time_ago_in_words notification.target.created_at + ago - when "Comment" .pull-left %img.img-rounded.notification--dropdown-img{src: gravatar_url(notification.target.user)} @@ -68,4 +80,4 @@ answer = time_ago_in_words notification.target.created_at ago - %li= link_to "Show all notifications#{" and mark them as read" if notifications.pluck(:new).any?}", notifications_path \ No newline at end of file + %li= link_to "Show all notifications#{" and mark them as read" if notifications.pluck(:new).any?}", notifications_path diff --git a/app/views/notifications/_notification.html.haml b/app/views/notifications/_notification.html.haml index eed4e08e..4f569859 100644 --- a/app/views/notifications/_notification.html.haml +++ b/app/views/notifications/_notification.html.haml @@ -44,6 +44,21 @@ ago .notification--icon %i.fa.fa-smile-o + - when "CommentSmile" + .pull-left + %img.img-rounded.notification--img{src: gravatar_url(notification.target.user)} + .media-body + %h6.media-heading.notification--user + = user_screen_name notification.target.user + %p.notification--text + smiled at + %a{href: show_user_answer_path(username: notification.target.user.screen_name, id: notification.target.comment.answer.id), title: "#{notification.target.comment.content[0..40]}...", data: { toggle: :tooltip, placement: :top }} + your comment + %span{title: notification.target.created_at, data: { toggle: :tooltip, placement: :bottom }} + = time_ago_in_words notification.target.created_at + ago + .notification--icon + %i.fa.fa-smile-o - when "Comment" .pull-left %img.img-rounded.notification--img{src: gravatar_url(notification.target.user)} diff --git a/app/views/shared/_answerbox_buttons.html.haml b/app/views/shared/_answerbox_buttons.html.haml index da2262fe..f37b6738 100644 --- a/app/views/shared/_answerbox_buttons.html.haml +++ b/app/views/shared/_answerbox_buttons.html.haml @@ -1,8 +1,9 @@ %span.hidden-xs.text-muted - unless user_signed_in? - if a.smile_count > 0 - %span{id: "ab-smile-count-#{a.id}"}= a.smile_count - users smiled this + %button.btn.btn-info.btn-sm{name: 'ab-smile', disabled: true} + %i.fa.fa-smile-o + %span{id: "ab-smile-count-#{a.id}"}= a.smile_count - if user_signed_in? - if current_user.smiled? a %button.btn.btn-info.btn-sm{type: :button, name: 'ab-smile', data: { a_id: a.id, action: :unsmile }} diff --git a/app/views/shared/_comment_smiles.html.haml b/app/views/shared/_comment_smiles.html.haml new file mode 100644 index 00000000..5aaa36b2 --- /dev/null +++ b/app/views/shared/_comment_smiles.html.haml @@ -0,0 +1,18 @@ +.modal.fade{"id" => "modal-view-comment#{comment.id}-smiles","aria-hidden" => "true", "aria-labelledby" => "modal-view-comment#{comment.id}-smiles-label", :role => "dialog", :tabindex => "-1"} + .modal-dialog + .modal-content + .modal-header + %button.close{"data-dismiss" => "modal", :type => "button"} + %span{"aria-hidden" => "true"} × + %span.sr-only Close + %h4#modal-ask-followers-label.modal-title People who smiled this comment + .modal-body + - if comment.smiles.all.count == 0 + No one smiled this, yet. + - else + %ul.user-list.user-list-smiles + - comment.smiles.all.each do |smile| + %li.user-list-entry.user-list-entry-smiles + %a{href: show_user_profile_path(smile.user.screen_name)} + %img.img-rounded{src: gravatar_url(smile.user), alt: user_screen_name(smile.user, false, false)} + %span= user_screen_name(smile.user, false, false) diff --git a/app/views/shared/_comments.html.haml b/app/views/shared/_comments.html.haml index 20c52a0c..07c7971e 100644 --- a/app/views/shared/_comments.html.haml +++ b/app/views/shared/_comments.html.haml @@ -4,17 +4,36 @@ %ul.comments - a.comments.order(:created_at).each do |comment| %li{data: { comment_id: comment.id }} + %div{class: "ab-comment-smile-list", style: "height: 0; width: 0"}= render "shared/comment_smiles", comment: comment .media.comments--media .pull-left %img.img-rounded.answerbox--img{src: gravatar_url(comment.user)} .media-body.comments--body %h6.media-heading.answerbox--question-user= user_screen_name comment.user - - if user_signed_in? - .pull-right + .pull-right + %span.hidden-xs.text-muted + - unless user_signed_in? + - if comment.smile_count > 0 + %button.btn.btn-info.btn-sm{name: 'ab-smile-comment', disabled: true} + %i.fa.fa-smile-o + %span{id: "ab-comment-smile-count-#{comment.id}"}= comment.smile_count + - if user_signed_in? + - if current_user.smiled_comment? comment + %button.btn.btn-info.btn-sm{type: :button, name: 'ab-smile-comment', data: { c_id: comment.id, action: :unsmile }} + %i.fa.fa-frown-o + %span{id: "ab-comment-smile-count-#{comment.id}"}= comment.smile_count + - else + %button.btn.btn-info.btn-sm{type: :button, name: 'ab-smile-comment', data: { c_id: comment.id, action: :smile }} + %i.fa.fa-smile-o + %span{id: "ab-comment-smile-count-#{comment.id}"}= comment.smile_count .btn-group %button.btn.btn-link.btn-sm.dropdown-toggle{data: { toggle: :dropdown }, aria: { expanded: :false }} %span.caret %ul.dropdown-menu.dropdown-menu-right{role: :menu} + %li + %a{href: '#', type: :button, data: { target: "#modal-view-comment#{comment.id}-smiles", toggle: :modal}} + %i.fa.fa-smile-o + View comment smiles - if privileged?(comment.user) or privileged?(a.user) %li.text-danger %a{href: '#', data: { action: 'ab-comment-destroy', c_id: comment.id }} diff --git a/config/routes.rb b/config/routes.rb index 712f511f..760a680a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -76,6 +76,8 @@ Rails.application.routes.draw do match '/destroy_friend', to: 'friend#destroy', via: :post, as: :destroy_friend match '/create_smile', to: 'smile#create', via: :post, as: :create_smile match '/destroy_smile', to: 'smile#destroy', via: :post, as: :destroy_smile + match '/create_comment_smile', to: 'smile#create_comment', via: :post, as: :create_comment_smile + match '/destroy_comment_smile', to: 'smile#destroy_comment', via: :post, as: :destroy_comment_smile match '/create_comment', to: 'comment#create', via: :post, as: :create_comment match '/destroy_comment', to: 'comment#destroy', via: :post, as: :destroy_comment match '/report', to: 'report#create', via: :post, as: :report diff --git a/db/migrate/20150504004931_create_comment_smiles.rb b/db/migrate/20150504004931_create_comment_smiles.rb new file mode 100644 index 00000000..e14cde62 --- /dev/null +++ b/db/migrate/20150504004931_create_comment_smiles.rb @@ -0,0 +1,17 @@ +class CreateCommentSmiles < ActiveRecord::Migration + def change + create_table :comment_smiles do |t| + t.integer :user_id + t.integer :comment_id + + t.timestamps null: false + end + + add_index :comment_smiles, :user_id + add_index :comment_smiles, :comment_id + add_index :comment_smiles, [:user_id, :comment_id], unique: true + + add_column :users, :comment_smiled_count, :integer, default: 0, null: false + add_column :comments, :smile_count, :integer, default: 0, null: false + end +end diff --git a/spec/models/comment_smile_spec.rb b/spec/models/comment_smile_spec.rb new file mode 100644 index 00000000..43e4d9f6 --- /dev/null +++ b/spec/models/comment_smile_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe CommentSmile, :type => :model do + pending "add some examples to (or delete) #{__FILE__}" +end