diff --git a/plugins/vote/controllers/vote_plugin_admin_controller.rb b/plugins/vote/controllers/vote_plugin_admin_controller.rb new file mode 100644 index 0000000..3a8cd88 --- /dev/null +++ b/plugins/vote/controllers/vote_plugin_admin_controller.rb @@ -0,0 +1,18 @@ +class VotePluginAdminController < AdminController + + def index + settings = params[:settings] + settings ||= {} + settings.each do |k, v| + settings[k] = settings[k].map{|v| v.to_i }.reject{|v| v==0} if k.start_with?('enable_vote') + end + + @settings = Noosfero::Plugin::Settings.new(environment, VotePlugin, settings) + if request.post? + @settings.save! + session[:notice] = 'Settings succefully saved.' + redirect_to :action => 'index' + end + end + +end diff --git a/plugins/vote/controllers/vote_plugin_profile_controller.rb b/plugins/vote/controllers/vote_plugin_profile_controller.rb new file mode 100644 index 0000000..aa1e3a8 --- /dev/null +++ b/plugins/vote/controllers/vote_plugin_profile_controller.rb @@ -0,0 +1,57 @@ +class VotePluginProfileController < ProfileController + + before_filter :login_required, :only => [:vote] + + def vote + model = params[:model].to_sym + vote = params[:vote].to_i + settings = Noosfero::Plugin::Settings.new(environment, VotePlugin) + model_settings = settings.get_setting("enable_vote_#{model}") + + unless model_settings && model_settings.include?(vote) + render_access_denied + return + end + + object = target(model) + vote_target(object, vote) + + render :update do |page| + model_settings.each do |v| + page.replace "vote_#{model}_#{params[:id]}_#{v}", instance_eval(&controller.vote_partial(object, v==1, false)) + end + end + end + + include VotePluginHelper + + def reload_vote + model = params[:model].to_sym + vote = params[:vote].to_i + object = target(model) + + render :update do |page| + page.replace "vote_#{model}_#{params[:id]}_#{vote}", instance_eval(&controller.vote_partial(object, vote==1, true)) + end + end + + protected + + def target(model) + case model + when :article + profile.articles.find(params[:id]) + when :comment + profile.comments_received.find(params[:id]) + end + end + + def vote_target(object, vote) + old_vote = user.votes.for_voteable(object).first + user.votes.for_voteable(object).each { |v| v.destroy } + if old_vote.nil? || old_vote.vote != vote + user.vote(object, vote) + end + end + +end diff --git a/plugins/vote/lib/vote_plugin.rb b/plugins/vote/lib/vote_plugin.rb new file mode 100644 index 0000000..139ca70 --- /dev/null +++ b/plugins/vote/lib/vote_plugin.rb @@ -0,0 +1,49 @@ +class VotePlugin < Noosfero::Plugin + + def self.plugin_name + "Vote Plugin" + end + + def self.plugin_description + _("Provide buttons to like/dislike a articles and comments.") + end + + def stylesheet? + true + end + + def js_files + 'vote_actions.js' + end + + def self.enable_vote_article_default_setting + [-1, 1] + end + + def self.enable_vote_comment_default_setting + [-1, 1] + end + + def self.voters_limit_default_setting + 6 + end + + include VotePluginHelper + + def comment_actions(comment) + like = vote_partial(comment) + dislike = vote_partial(comment, false) + lambda do + [{:link => instance_eval(&dislike), :action_bar => true}, {:link => instance_eval(&like), :action_bar => true}] + end + end + + def article_header_extra_contents(article) + like = vote_partial(article) + dislike = vote_partial(article, false) + lambda do + content_tag('div', instance_eval(&dislike) + instance_eval(&like), :class => 'vote-actions') + end + end + +end diff --git a/plugins/vote/lib/vote_plugin_helper.rb b/plugins/vote/lib/vote_plugin_helper.rb new file mode 100644 index 0000000..dfa4ee4 --- /dev/null +++ b/plugins/vote/lib/vote_plugin_helper.rb @@ -0,0 +1,24 @@ +module VotePluginHelper + + def vote_partial(target, like = true, load_voters=false) + vote = like ? 1 : -1 + like_action = like ? 'like' : 'dislike' + type = target.kind_of?(Article) ? 'article' : target.kind_of?(Comment) ? 'comment' : nil + + lambda do + settings = Noosfero::Plugin::Settings.new(environment, VotePlugin) + + if settings.get_setting("enable_vote_#{type}").include?(vote) + + voters = !load_voters ? nil : target.votes.where(:vote => vote).includes(:voter).limit(settings.get_setting('voters_limit')).map(&:voter) + active = user ? (like ? user.voted_for?(target) : user.voted_against?(target)) : false + count = like ? target.votes_for : target.votes_against + + render(:partial => 'vote/vote.rhtml', :locals => {:target => target, :active => active, :action => like_action, :count => count, :voters => voters, :vote => vote, :model => type }) + else + "" + end + end + end + +end diff --git a/plugins/vote/public/style.css b/plugins/vote/public/style.css new file mode 100644 index 0000000..4d6041a --- /dev/null +++ b/plugins/vote/public/style.css @@ -0,0 +1,68 @@ +.vote-actions { + position: absolute; + top: 40px; + right: 0px; +} + +.action { + float: right; + font-size: 8pt; +} + +#article .action a { + text-decoration: none; +} + +#article .action .like-action-active:hover { + color: #156E16; +} + +#article .action .like-action-active { + color: #2A8C32; +} + +#article .action .action-icon { + top: -1px; + left: -2px; + position: relative; + margin-left: 2px; +} + +.action .dislike:before { + content: "\25bc"; +} + +.action .like:before { + content: "\25b2"; +} + +.dislike-action .like-action-counter { + border-left: 1px solid rgba(75,83,94,.3); + padding-left: 4px; +} + +.action .vote-detail { + position: absolute; + width: 120px; + border: 1px solid #888a85; + background-color: #efefef; + padding-right: 2px; + height: auto; + display: block; + z-index: 12; +} + +#article .action .vote-detail ul { + float: left; + padding: 5px; + margin: 0px; +} + +#article .action .vote-detail li { + margin: 0 0 2px 0; + list-style-type: none; +} + +#article .action .vote-detail img { + vertical-align: bottom; +} diff --git a/plugins/vote/public/vote_actions.js b/plugins/vote/public/vote_actions.js new file mode 100644 index 0000000..86268af --- /dev/null +++ b/plugins/vote/public/vote_actions.js @@ -0,0 +1,26 @@ +var openEvent = null; +jQuery( document ).ready(function( $ ) { + $(".vote-action").live('mouseenter', function() { + var div = $(this); + if(openEvent==null) + openEvent = setInterval(function() { openVotersDialog(div); }, 500); + }); + $(".vote-action").live('mouseleave', function() { + clearTimeout(openEvent); + openEvent = null; + }); +}); + +function openVotersDialog(div) { + var $ = jQuery; + clearTimeout(openEvent); + var url = $(div).data('reload_url'); + hideAllVoteDetail(); + $.post(url); +} + +jQuery('body').live('click', function() { hideAllVoteDetail(); }); + +function hideAllVoteDetail() { + jQuery('.vote-detail').fadeOut('slow'); +} diff --git a/plugins/vote/test/functional/vote_plugin_admin_controller_test.rb b/plugins/vote/test/functional/vote_plugin_admin_controller_test.rb new file mode 100644 index 0000000..3412a21 --- /dev/null +++ b/plugins/vote/test/functional/vote_plugin_admin_controller_test.rb @@ -0,0 +1,30 @@ +require File.dirname(__FILE__) + '/../../../../test/test_helper' +require File.dirname(__FILE__) + '/../../controllers/vote_plugin_admin_controller' + +# Re-raise errors caught by the controller. +class VotePluginAdminController; def rescue_action(e) raise e end; end + +class VotePluginAdminControllerTest < ActionController::TestCase + + def setup + @environment = Environment.default + @profile = create_user('profile').person + login_as(@profile.identifier) + end + + attr_reader :environment + + should 'save vote_plugin settings' do + post :index, :settings => {"enable_vote_article" => [1], "enable_vote_comment" => [-1]} + @settings = Noosfero::Plugin::Settings.new(environment.reload, VotePlugin) + assert_equal [1], @settings.settings[:enable_vote_article] + assert_equal [-1], @settings.settings[:enable_vote_comment] + assert_redirected_to :action => 'index' + end + + should 'redirect to index after save' do + post :index, :settings => {"enable_vote_article" => [1]} + assert_redirected_to :action => 'index' + end + +end diff --git a/plugins/vote/test/functional/vote_plugin_profile_controller_test.rb b/plugins/vote/test/functional/vote_plugin_profile_controller_test.rb new file mode 100644 index 0000000..73db1e3 --- /dev/null +++ b/plugins/vote/test/functional/vote_plugin_profile_controller_test.rb @@ -0,0 +1,85 @@ +require File.dirname(__FILE__) + '/../../../../test/test_helper' +require File.dirname(__FILE__) + '/../../controllers/vote_plugin_profile_controller' + +# Re-raise errors caught by the controller. +class VotePluginProfileController; def rescue_action(e) raise e end; end + +class VotePluginProfileControllerTest < ActionController::TestCase + + def setup + @profile = create_user('profile').person + @article = TinyMceArticle.create!(:profile => @profile, :name => 'An article') + @comment = Comment.new(:source => @article, :author => @profile, :body => 'test') + @comment.save! + login_as(@profile.identifier) + @environment = Environment.default + @environment.enable_plugin(VotePlugin) + self.stubs(:user).returns(@profile) + end + + attr_reader :profile, :comment, :environment, :article + + should 'do not vote if user is not logged in' do + logout + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1 + assert_response 401 + end + + should 'not vote if value is not allowed' do + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 4 + assert !profile.voted_on?(comment) + end + + should 'not vote in a disallowed model' do + xhr :post, :vote, :profile => profile.identifier, :id => environment.id, :model => 'environment', :vote => 1 + assert profile.votes.empty? + end + + should 'like comment' do + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1 + assert profile.voted_for?(comment) + end + + should 'unlike comment' do + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1 + assert !profile.voted_for?(comment) + end + + should 'dislike comment' do + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => -1 + assert profile.voted_against?(comment) + end + + should 'undislike comment' do + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => -1 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => -1 + assert !profile.voted_against?(comment) + end + + should 'dislike a liked comment' do + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => -1 + assert profile.voted_against?(comment) + end + + should 'like a disliked comment' do + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => -1 + xhr :post, :vote, :profile => profile.identifier, :id => comment.id, :model => 'comment', :vote => 1 + assert profile.voted_for?(comment) + end + + should 'like article' do + xhr :post, :vote, :profile => profile.identifier, :id => article.id, :model => 'article', :vote => 1 + assert profile.voted_for?(article) + end + + should 'update views with new vote state' do + xhr :post, :vote, :profile => profile.identifier, :id => article.id, :model => 'article', :vote => 1 + assert_select_rjs :replace do + assert_select "#vote_article_#{article.id}_1" + assert_select "#vote_article_#{article.id}_-1" + end + end + +end diff --git a/plugins/vote/test/test_helper.rb b/plugins/vote/test/test_helper.rb new file mode 100644 index 0000000..cca1fd3 --- /dev/null +++ b/plugins/vote/test/test_helper.rb @@ -0,0 +1 @@ +require File.dirname(__FILE__) + '/../../../test/test_helper' diff --git a/plugins/vote/test/unit/vote_plugin_test.rb b/plugins/vote/test/unit/vote_plugin_test.rb new file mode 100644 index 0000000..951f87a --- /dev/null +++ b/plugins/vote/test/unit/vote_plugin_test.rb @@ -0,0 +1,30 @@ +require File.dirname(__FILE__) + '/../../../../test/test_helper' + +class VotePluginTest < ActiveSupport::TestCase + + def setup + @plugin = VotePlugin.new + @person = create_user('user').person + @article = TinyMceArticle.create!(:profile => @person, :name => 'An article') + @comment = Comment.create!(:source => @article, :author => @person, :body => 'test') + end + + attr_reader :plugin, :comment, :article + + should 'have a stylesheet' do + assert plugin.stylesheet? + end + + should 'have a javascript' do + assert plugin.js_files + end + + should 'return proc to display partials to vote for comments' do + assert plugin.comment_actions(comment).kind_of?(Proc) + end + + should 'return proc to display partials to vote for articles' do + assert plugin.article_header_extra_contents(article).kind_of?(Proc) + end + +end diff --git a/plugins/vote/views/vote/_vote.rhtml b/plugins/vote/views/vote/_vote.rhtml new file mode 100644 index 0000000..57fce2d --- /dev/null +++ b/plugins/vote/views/vote/_vote.rhtml @@ -0,0 +1,22 @@ +<% +url = url_for(:controller => 'vote_plugin_profile', :profile => profile.identifier, :action => :vote, :id => target.id, :model => model, :vote => vote) +reload_url = url_for(:controller => 'vote_plugin_profile', :profile => profile.identifier, :action => :reload_vote, :id => target.id, :model => model, :vote => vote) +%> + + class="vote-action action <%= action %>-action"> + + <%= 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'}"} %> + + <% if !voters.blank? %> + + + + <% end %> + diff --git a/plugins/vote/views/vote_plugin_admin/index.rhtml b/plugins/vote/views/vote_plugin_admin/index.rhtml new file mode 100644 index 0000000..2cbecd6 --- /dev/null +++ b/plugins/vote/views/vote_plugin_admin/index.rhtml @@ -0,0 +1,21 @@ +

<%= _('Vote settings')%>

+ +<% form_for(:settings) do |f| %> + + <% ['article', 'comment'].each do |model| %> +
<%= _('Enable on %s:' % model) %>
+ <%= f.check_box("enable_vote_#{model}", {:multiple => true}, 1) + _('Like') %> + <%= f.check_box("enable_vote_#{model}", {:multiple => true}, -1) + _('Dislike') %> + <% end %> +

+ + + <%= labelled_form_field _('Limit of voters to display:'), f.text_field(:voters_limit, :size => 3) %> + + + <% button_bar do %> + <%= submit_button(:save, _('Save'), :cancel => {:controller => 'plugins', :action => 'index'}) %> + <% end %> + +<% end %> + -- libgit2 0.21.2