Commit 772c27b56f93207382bc7dd577708a5ac9181de1

Authored by Victor Costa
1 parent db23d209

Add a plugin to vote on articles and comments

plugins/vote/controllers/vote_plugin_admin_controller.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +class VotePluginAdminController < AdminController
  2 +
  3 + def index
  4 + settings = params[:settings]
  5 + settings ||= {}
  6 + settings.each do |k, v|
  7 + settings[k] = settings[k].map{|v| v.to_i }.reject{|v| v==0} if k.start_with?('enable_vote')
  8 + end
  9 +
  10 + @settings = Noosfero::Plugin::Settings.new(environment, VotePlugin, settings)
  11 + if request.post?
  12 + @settings.save!
  13 + session[:notice] = 'Settings succefully saved.'
  14 + redirect_to :action => 'index'
  15 + end
  16 + end
  17 +
  18 +end
... ...
plugins/vote/controllers/vote_plugin_profile_controller.rb 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +class VotePluginProfileController < ProfileController
  2 +
  3 + before_filter :login_required, :only => [:vote]
  4 +
  5 + def vote
  6 + model = params[:model].to_sym
  7 + vote = params[:vote].to_i
  8 + settings = Noosfero::Plugin::Settings.new(environment, VotePlugin)
  9 + model_settings = settings.get_setting("enable_vote_#{model}")
  10 +
  11 + unless model_settings && model_settings.include?(vote)
  12 + render_access_denied
  13 + return
  14 + end
  15 +
  16 + object = target(model)
  17 + vote_target(object, vote)
  18 +
  19 + render :update do |page|
  20 + model_settings.each do |v|
  21 + page.replace "vote_#{model}_#{params[:id]}_#{v}", instance_eval(&controller.vote_partial(object, v==1, false))
  22 + end
  23 + end
  24 + end
  25 +
  26 + include VotePluginHelper
  27 +
  28 + def reload_vote
  29 + model = params[:model].to_sym
  30 + vote = params[:vote].to_i
  31 + object = target(model)
  32 +
  33 + render :update do |page|
  34 + page.replace "vote_#{model}_#{params[:id]}_#{vote}", instance_eval(&controller.vote_partial(object, vote==1, true))
  35 + end
  36 + end
  37 +
  38 + protected
  39 +
  40 + def target(model)
  41 + case model
  42 + when :article
  43 + profile.articles.find(params[:id])
  44 + when :comment
  45 + profile.comments_received.find(params[:id])
  46 + end
  47 + end
  48 +
  49 + def vote_target(object, vote)
  50 + old_vote = user.votes.for_voteable(object).first
  51 + user.votes.for_voteable(object).each { |v| v.destroy }
  52 + if old_vote.nil? || old_vote.vote != vote
  53 + user.vote(object, vote)
  54 + end
  55 + end
  56 +
  57 +end
... ...
plugins/vote/lib/vote_plugin.rb 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +class VotePlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + "Vote Plugin"
  5 + end
  6 +
  7 + def self.plugin_description
  8 + _("Provide buttons to like/dislike a articles and comments.")
  9 + end
  10 +
  11 + def stylesheet?
  12 + true
  13 + end
  14 +
  15 + def js_files
  16 + 'vote_actions.js'
  17 + end
  18 +
  19 + def self.enable_vote_article_default_setting
  20 + [-1, 1]
  21 + end
  22 +
  23 + def self.enable_vote_comment_default_setting
  24 + [-1, 1]
  25 + end
  26 +
  27 + def self.voters_limit_default_setting
  28 + 6
  29 + end
  30 +
  31 + include VotePluginHelper
  32 +
  33 + def comment_actions(comment)
  34 + like = vote_partial(comment)
  35 + dislike = vote_partial(comment, false)
  36 + lambda do
  37 + [{:link => instance_eval(&dislike), :action_bar => true}, {:link => instance_eval(&like), :action_bar => true}]
  38 + end
  39 + end
  40 +
  41 + def article_header_extra_contents(article)
  42 + like = vote_partial(article)
  43 + dislike = vote_partial(article, false)
  44 + lambda do
  45 + content_tag('div', instance_eval(&dislike) + instance_eval(&like), :class => 'vote-actions')
  46 + end
  47 + end
  48 +
  49 +end
... ...
plugins/vote/lib/vote_plugin_helper.rb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +module VotePluginHelper
  2 +
  3 + def vote_partial(target, like = true, load_voters=false)
  4 + vote = like ? 1 : -1
  5 + like_action = like ? 'like' : 'dislike'
  6 + type = target.kind_of?(Article) ? 'article' : target.kind_of?(Comment) ? 'comment' : nil
  7 +
  8 + lambda do
  9 + settings = Noosfero::Plugin::Settings.new(environment, VotePlugin)
  10 +
  11 + if settings.get_setting("enable_vote_#{type}").include?(vote)
  12 +
  13 + voters = !load_voters ? nil : target.votes.where(:vote => vote).includes(:voter).limit(settings.get_setting('voters_limit')).map(&:voter)
  14 + active = user ? (like ? user.voted_for?(target) : user.voted_against?(target)) : false
  15 + count = like ? target.votes_for : target.votes_against
  16 +
  17 + render(:partial => 'vote/vote.rhtml', :locals => {:target => target, :active => active, :action => like_action, :count => count, :voters => voters, :vote => vote, :model => type })
  18 + else
  19 + ""
  20 + end
  21 + end
  22 + end
  23 +
  24 +end
... ...
plugins/vote/public/style.css 0 → 100644
... ... @@ -0,0 +1,68 @@
  1 +.vote-actions {
  2 + position: absolute;
  3 + top: 40px;
  4 + right: 0px;
  5 +}
  6 +
  7 +.action {
  8 + float: right;
  9 + font-size: 8pt;
  10 +}
  11 +
  12 +#article .action a {
  13 + text-decoration: none;
  14 +}
  15 +
  16 +#article .action .like-action-active:hover {
  17 + color: #156E16;
  18 +}
  19 +
  20 +#article .action .like-action-active {
  21 + color: #2A8C32;
  22 +}
  23 +
  24 +#article .action .action-icon {
  25 + top: -1px;
  26 + left: -2px;
  27 + position: relative;
  28 + margin-left: 2px;
  29 +}
  30 +
  31 +.action .dislike:before {
  32 + content: "\25bc";
  33 +}
  34 +
  35 +.action .like:before {
  36 + content: "\25b2";
  37 +}
  38 +
  39 +.dislike-action .like-action-counter {
  40 + border-left: 1px solid rgba(75,83,94,.3);
  41 + padding-left: 4px;
  42 +}
  43 +
  44 +.action .vote-detail {
  45 + position: absolute;
  46 + width: 120px;
  47 + border: 1px solid #888a85;
  48 + background-color: #efefef;
  49 + padding-right: 2px;
  50 + height: auto;
  51 + display: block;
  52 + z-index: 12;
  53 +}
  54 +
  55 +#article .action .vote-detail ul {
  56 + float: left;
  57 + padding: 5px;
  58 + margin: 0px;
  59 +}
  60 +
  61 +#article .action .vote-detail li {
  62 + margin: 0 0 2px 0;
  63 + list-style-type: none;
  64 +}
  65 +
  66 +#article .action .vote-detail img {
  67 + vertical-align: bottom;
  68 +}
... ...
plugins/vote/public/vote_actions.js 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +var openEvent = null;
  2 +jQuery( document ).ready(function( $ ) {
  3 + $(".vote-action").live('mouseenter', function() {
  4 + var div = $(this);
  5 + if(openEvent==null)
  6 + openEvent = setInterval(function() { openVotersDialog(div); }, 500);
  7 + });
  8 + $(".vote-action").live('mouseleave', function() {
  9 + clearTimeout(openEvent);
  10 + openEvent = null;
  11 + });
  12 +});
  13 +
  14 +function openVotersDialog(div) {
  15 + var $ = jQuery;
  16 + clearTimeout(openEvent);
  17 + var url = $(div).data('reload_url');
  18 + hideAllVoteDetail();
  19 + $.post(url);
  20 +}
  21 +
  22 +jQuery('body').live('click', function() { hideAllVoteDetail(); });
  23 +
  24 +function hideAllVoteDetail() {
  25 + jQuery('.vote-detail').fadeOut('slow');
  26 +}
... ...
plugins/vote/test/functional/vote_plugin_admin_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../../controllers/vote_plugin_admin_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class VotePluginAdminController; def rescue_action(e) raise e end; end
  6 +
  7 +class VotePluginAdminControllerTest < ActionController::TestCase
  8 +
  9 + def setup
  10 + @environment = Environment.default
  11 + @profile = create_user('profile').person
  12 + login_as(@profile.identifier)
  13 + end
  14 +
  15 + attr_reader :environment
  16 +
  17 + should 'save vote_plugin settings' do
  18 + post :index, :settings => {"enable_vote_article" => [1], "enable_vote_comment" => [-1]}
  19 + @settings = Noosfero::Plugin::Settings.new(environment.reload, VotePlugin)
  20 + assert_equal [1], @settings.settings[:enable_vote_article]
  21 + assert_equal [-1], @settings.settings[:enable_vote_comment]
  22 + assert_redirected_to :action => 'index'
  23 + end
  24 +
  25 + should 'redirect to index after save' do
  26 + post :index, :settings => {"enable_vote_article" => [1]}
  27 + assert_redirected_to :action => 'index'
  28 + end
  29 +
  30 +end
... ...
plugins/vote/test/functional/vote_plugin_profile_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,85 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../../controllers/vote_plugin_profile_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class VotePluginProfileController; def rescue_action(e) raise e end; end
  6 +
  7 +class VotePluginProfileControllerTest < ActionController::TestCase
  8 +
  9 + def setup
  10 + @profile = create_user('profile').person
  11 + @article = TinyMceArticle.create!(:profile => @profile, :name => 'An article')
  12 + @comment = Comment.new(:source => @article, :author => @profile, :body => 'test')
  13 + @comment.save!
  14 + login_as(@profile.identifier)
  15 + @environment = Environment.default
  16 + @environment.enable_plugin(VotePlugin)
  17 + self.stubs(:user).returns(@profile)
  18 + end
  19 +
  20 + attr_reader :profile, :comment, :environment, :article
  21 +
  22 + should 'do not vote if user is not logged in' do
  23 + logout
  24 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1
  25 + assert_response 401
  26 + end
  27 +
  28 + should 'not vote if value is not allowed' do
  29 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 4
  30 + assert !profile.voted_on?(comment)
  31 + end
  32 +
  33 + should 'not vote in a disallowed model' do
  34 + xhr :post, :vote, :profile => profile.identifier, :id => environment.id, :model => 'environment', :vote => 1
  35 + assert profile.votes.empty?
  36 + end
  37 +
  38 + should 'like comment' do
  39 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1
  40 + assert profile.voted_for?(comment)
  41 + end
  42 +
  43 + should 'unlike comment' do
  44 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1
  45 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1
  46 + assert !profile.voted_for?(comment)
  47 + end
  48 +
  49 + should 'dislike comment' do
  50 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => -1
  51 + assert profile.voted_against?(comment)
  52 + end
  53 +
  54 + should 'undislike comment' do
  55 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => -1
  56 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => -1
  57 + assert !profile.voted_against?(comment)
  58 + end
  59 +
  60 + should 'dislike a liked comment' do
  61 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1
  62 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => -1
  63 + assert profile.voted_against?(comment)
  64 + end
  65 +
  66 + should 'like a disliked comment' do
  67 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => -1
  68 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1
  69 + assert profile.voted_for?(comment)
  70 + end
  71 +
  72 + should 'like article' do
  73 + xhr :post, :vote, :profile => profile.identifier, :id => article.id, :model => 'article', :vote => 1
  74 + assert profile.voted_for?(article)
  75 + end
  76 +
  77 + should 'update views with new vote state' do
  78 + xhr :post, :vote, :profile => profile.identifier, :id => article.id, :model => 'article', :vote => 1
  79 + assert_select_rjs :replace do
  80 + assert_select "#vote_article_#{article.id}_1"
  81 + assert_select "#vote_article_#{article.id}_-1"
  82 + end
  83 + end
  84 +
  85 +end
... ...
plugins/vote/test/test_helper.rb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +require File.dirname(__FILE__) + '/../../../test/test_helper'
... ...
plugins/vote/test/unit/vote_plugin_test.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +
  3 +class VotePluginTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @plugin = VotePlugin.new
  7 + @person = create_user('user').person
  8 + @article = TinyMceArticle.create!(:profile => @person, :name => 'An article')
  9 + @comment = Comment.create!(:source => @article, :author => @person, :body => 'test')
  10 + end
  11 +
  12 + attr_reader :plugin, :comment, :article
  13 +
  14 + should 'have a stylesheet' do
  15 + assert plugin.stylesheet?
  16 + end
  17 +
  18 + should 'have a javascript' do
  19 + assert plugin.js_files
  20 + end
  21 +
  22 + should 'return proc to display partials to vote for comments' do
  23 + assert plugin.comment_actions(comment).kind_of?(Proc)
  24 + end
  25 +
  26 + should 'return proc to display partials to vote for articles' do
  27 + assert plugin.article_header_extra_contents(article).kind_of?(Proc)
  28 + end
  29 +
  30 +end
... ...
plugins/vote/views/vote/_vote.rhtml 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +<%
  2 +url = url_for(:controller => 'vote_plugin_profile', :profile => profile.identifier, :action => :vote, :id => target.id, :model => model, :vote => vote)
  3 +reload_url = url_for(:controller => 'vote_plugin_profile', :profile => profile.identifier, :action => :reload_vote, :id => target.id, :model => model, :vote => vote)
  4 +%>
  5 +
  6 +<span id="vote_<%= model %>_<%= target.id %>_<%= vote %>" data-reload_url=<%= reload_url %> class="vote-action action <%= action %>-action">
  7 +
  8 + <%= link_to_remote content_tag(:span, count, :class=>'like-action-counter') + content_tag(:span, '', :class=>"action-icon #{action}"), :url => url, :html => {:class => "#{active ? 'like-action-active':''} #{user ? '':'disabled'}"} %>
  9 +
  10 + <% if !voters.blank? %>
  11 + <span class="vote-detail">
  12 + <ul>
  13 + <% voters.each do |voter| %>
  14 + <li>
  15 + <%= link_to image_tag(profile_icon(voter, :icon)) + content_tag('span', voter.short_name),
  16 + voter.url, :title => voter.short_name %>
  17 + </li>
  18 + <% end %>
  19 + </ul>
  20 + </span>
  21 + <% end %>
  22 +</span>
... ...
plugins/vote/views/vote_plugin_admin/index.rhtml 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +<h1><%= _('Vote settings')%></h1>
  2 +
  3 +<% form_for(:settings) do |f| %>
  4 +
  5 + <% ['article', 'comment'].each do |model| %>
  6 + <h5><%= _('Enable on %s:' % model) %></h5>
  7 + <%= f.check_box("enable_vote_#{model}", {:multiple => true}, 1) + _('Like') %>
  8 + <%= f.check_box("enable_vote_#{model}", {:multiple => true}, -1) + _('Dislike') %>
  9 + <% end %>
  10 + <br/><br/>
  11 +
  12 + <strong>
  13 + <%= labelled_form_field _('Limit of voters to display:'), f.text_field(:voters_limit, :size => 3) %>
  14 + </strong>
  15 +
  16 + <% button_bar do %>
  17 + <%= submit_button(:save, _('Save'), :cancel => {:controller => 'plugins', :action => 'index'}) %>
  18 + <% end %>
  19 +
  20 +<% end %>
  21 +
... ...