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 " "
+ when 'unsmile'
+ btn[0].dataset.action = 'smile'
+ btn.html " "
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