Commit bd3e6f9c052bc450b10808d22df84a821d02daea

Authored by Arthur Esposte
1 parent 3f63e993
Exists in tests

TEMP

app/models/comment.rb
... ... @@ -37,6 +37,9 @@ class Comment < ActiveRecord::Base
37 37  
38 38 xss_terminate :only => [ :body, :title, :name ], :on => 'validation'
39 39  
  40 + # TODO
  41 + # create migration to create total_votes fields
  42 + # see vote_fu/README.markdown
40 43 acts_as_voteable
41 44  
42 45 def comment_root
... ...
features/step_definitions/noosfero_steps.rb
... ... @@ -91,10 +91,10 @@ Given /^the following blocks$/ do |table|
91 91 owner_type = item.delete('owner')
92 92 owner = owner_type == 'environment' ? Environment.default : Profile[owner_type]
93 93 if owner.boxes.empty?
94   - owner.boxes<< Box.new
  94 + owner.boxes << Box.new
95 95 owner.boxes.first.blocks << MainBlock.new
96 96 end
97   - box = owner.boxes.where(:position => 3).first
  97 + box = owner.boxes.first
98 98 klass.constantize.create!(item.merge(:box => box))
99 99 end
100 100 end
... ...
public/javascripts/media-panel.js
1   -var file_id = 1;
2   -
3   -jQuery('.view-all-media').on('click', '.pagination a', function(event) {
4   - jQuery.ajax({
5   - url: this.href,
6   - beforeSend: function(){jQuery('.view-all-media').addClass('fetching')},
7   - complete: function() {jQuery('.view-all-media').removeClass('fetching')},
8   - dataType: 'script'
  1 +(function($) {
  2 + "use strict";
  3 +
  4 + var file_id = 1;
  5 +
  6 + $('.view-all-media').on('click', '.pagination a', function(event) {
  7 + $.ajax({
  8 + url: this.href,
  9 + beforeSend: function(){$('.view-all-media').addClass('fetching')},
  10 + complete: function() {$('.view-all-media').removeClass('fetching')},
  11 + dataType: 'script'
  12 + });
  13 + return false;
9 14 });
10   - return false;
11   -});
12   -
13   -jQuery('#file').fileupload({
14   - add: function(e, data){
15   - data.files[0].id = file_id;
16   - file_id++;
17   - data.context = jQuery(tmpl("template-upload", data.files[0]));
18   - jQuery('#media-upload-form').append(data.context);
19   - data.submit();
20   - },
21   - progress: function (e, data) {
22   - if (jQuery('#hide-uploads').data('bootstraped') == false) {
23   - jQuery('#hide-uploads').show();
24   - jQuery('#hide-uploads').data('bootstraped', true);
25   - }
26   - if (data.context) {
27   - progress = parseInt(data.loaded / data.total * 100, 10);
28   - data.context.find('.bar').css('width', progress + '%');
29   - data.context.find('.percentage').text(progress + '%');
30   - }
31   - },
32   - fail: function(e, data){
33   - var file_id = '#file-'+data.files[0].id;
34   - jQuery(file_id).find('.progress .bar').addClass('error');
35   - jQuery(file_id).append("<div class='error-message'>" + data.jqXHR.responseText + "</div>")
36   - }
37   -});
38   -
39   -jQuery('#hide-uploads').click(function(){
40   - jQuery('#hide-uploads').hide();
41   - jQuery('#show-uploads').show();
42   - jQuery('.upload').slideUp();
43   - return false;
44   -});
45   -
46   -jQuery('#show-uploads').click(function(){
47   - jQuery('#hide-uploads').show();
48   - jQuery('#show-uploads').hide();
49   - jQuery('.upload').slideDown();
50   - return false;
51   -});
52   -
53   -function loadPublishedMedia() {
54   - var parent_id = jQuery('#published-media #parent_id').val();
55   - var q = jQuery('#published-media #q').val();
56   - var url = jQuery('#published-media').data('url');
57   -
58   - jQuery('#published-media .items').addClass('fetching');
59   - jQuery.ajax({
60   - url: url,
61   - data: {'parent_id': parent_id, 'q': q},
62   - dataType: 'html',
63   - success: function(response) {
64   - jQuery("#published-media .items").html(response);
65   - jQuery('#published-media .items').removeClass('fetching');
66   - updateViewAllLinks();
  15 +
  16 +
  17 + $('#file').fileupload({
  18 + add: function(e, data){
  19 + data.files[0].id = file_id;
  20 + file_id++;
  21 + data.context = $(tmpl("template-upload", data.files[0]));
  22 + $('#media-upload-form').append(data.context);
  23 + data.submit();
  24 + },
  25 + progress: function (e, data) {
  26 + if ($('#hide-uploads').data('bootstraped') == false) {
  27 + $('#hide-uploads').show();
  28 + $('#hide-uploads').data('bootstraped', true);
  29 + }
  30 + if (data.context) {
  31 + var progress = parseInt(data.loaded / data.total * 100, 10);
  32 + data.context.find('.bar').css('width', progress + '%');
  33 + data.context.find('.percentage').text(progress + '%');
  34 + }
67 35 },
68   - error: function(response, textStatus, xhr) {
69   - console.log(response);
70   - console.log(textStatus);
  36 + fail: function(e, data){
  37 + var file_id = '#file-'+data.files[0].id;
  38 + $(file_id).find('.progress .bar').addClass('error');
  39 + $(file_id).append("<div class='error-message'>" + data.jqXHR.responseText + "</div>")
71 40 }
72 41 });
73   -}
74   -
75   -function updateViewAllLinks() {
76   - var parent_id = jQuery('#published-media #parent_id').val();
77   - var q = jQuery('#published-media #q').val();
78   - jQuery('#published-media .view-all').each(function(){
79   - var key = jQuery(this).data('key');
80   - var params = {parent_id: parent_id, q: q, key: key}
81   - var href = jQuery(this).attr('href');
82   - href = href.replace(/\?.*/, '?'+jQuery.param(params));
83   - jQuery(this).attr('href', href);
  42 +
  43 +
  44 + $('#hide-uploads').click(function(){
  45 + $('#hide-uploads').hide();
  46 + $('#show-uploads').show();
  47 + $('.upload').slideUp();
  48 + return false;
  49 + });
  50 +
  51 +
  52 + $('#show-uploads').click(function(){
  53 + $('#hide-uploads').show();
  54 + $('#show-uploads').hide();
  55 + $('.upload').slideDown();
  56 + return false;
84 57 });
85   -}
86   -
87   -jQuery('#published-media #parent_id').change(function(){ loadPublishedMedia() });
88   -
89   -jQuery("#published-media #q").typeWatch({
90   - callback: function (value) { loadPublishedMedia() },
91   - wait: 750,
92   - highlight: true,
93   - captureLength: 2
94   -});
95   -
96   -jQuery("#published-media #q").bind('notext', function(){ loadPublishedMedia() });
97   -
98   -jQuery("#new-folder-dialog").submit(function( event ) {
99   - var name = jQuery('#new_folder').val();
100   - var parent_id = jQuery("#new-folder-dialog #parent_id").val();
101   - jQuery.ajax({
102   - url: this.action,
103   - type: 'POST',
104   - data: {
105   - 'parent_id': parent_id,
106   - 'article': {'name': name, 'published': true},
107   - 'type': jQuery('input[name=folder_type]:checked').val() },
108   - dataType: 'json',
109   - beforeSend: function(){jQuery("#new-folder-dialog").addClass('fetching')},
110   - success: function(response) {
111   - var option_selected = "<option value='"+ response.id +"' selected='selected'>"+ response.full_name +"</options>"
112   - var option = "<option value='"+ response.id +"'>"+ response.full_name +"</options>"
113   - jQuery('#media-upload-form #parent_id').append(option_selected);
114   - jQuery('#published-media #parent_id').append(option);
115   - jQuery('#new_folder').val('');
116   - },
117   - error: function(response, textStatus, xhr) {
118   - console.log(response);
119   - console.log(textStatus);
120   - },
121   - complete: function(response){
122   - jQuery("#new-folder-dialog").removeClass('fetching');
123   - jQuery("#new-folder-dialog").dialog('close');
124   - }
  58 +
  59 +
  60 + function loadPublishedMedia() {
  61 + var parent_id = $('#published-media #parent_id').val();
  62 + var q = $('#published-media #q').val();
  63 + var url = $('#published-media').data('url');
  64 +
  65 + $('#published-media .items').addClass('fetching');
  66 + $.ajax({
  67 + url: url,
  68 + data: {'parent_id': parent_id, 'q': q},
  69 + dataType: 'html',
  70 + success: function(response) {
  71 + $("#published-media .items").html(response);
  72 + $('#published-media .items').removeClass('fetching');
  73 + updateViewAllLinks();
  74 + },
  75 + error: function(response, textStatus, xhr) {
  76 + console.log(response);
  77 + console.log(textStatus);
  78 + }
  79 + });
  80 + }
  81 + // make it global for usage in media_upload.js.erb
  82 + window.loadPublishedMedia = loadPublishedMedia;
  83 +
  84 +
  85 + function updateViewAllLinks() {
  86 + var parent_id = $('#published-media #parent_id').val();
  87 + var q = $('#published-media #q').val();
  88 + $('#published-media .view-all').each(function(){
  89 + var key = $(this).data('key');
  90 + var params = {parent_id: parent_id, q: q, key: key}
  91 + var href = $(this).attr('href');
  92 + href = href.replace(/\?.*/, '?'+$.param(params));
  93 + $(this).attr('href', href);
  94 + });
  95 + }
  96 +
  97 +
  98 + $('#published-media #parent_id').change(function(){
  99 + loadPublishedMedia()
  100 + });
  101 +
  102 +
  103 + // Using a immediate function to make timer variable only visible for the keyup event
  104 + (function() {
  105 + var timer = null;
  106 +
  107 + $("#published-media #q").keyup(function() {
  108 + if(this.value.length > 2) {
  109 + timer = setTimeout(loadPublishedMedia, 750);
  110 + }
  111 + }).keydown(function() {
  112 + clearTimeout(timer);
  113 + });
  114 + }) ();
  115 +
  116 +
  117 + $("#published-media #q").bind('notext', function(){
  118 + loadPublishedMedia()
  119 + });
  120 +
  121 +
  122 + $("#new-folder-dialog").submit(function( event ) {
  123 + var name = $('#new_folder').val();
  124 + var parent_id = $("#new-folder-dialog #parent_id").val();
  125 + $.ajax({
  126 + url: this.action,
  127 + type: 'POST',
  128 + data: {
  129 + 'parent_id': parent_id,
  130 + 'article': {'name': name, 'published': true},
  131 + 'type': $('input[name=folder_type]:checked').val() },
  132 + dataType: 'json',
  133 + beforeSend: function(){$("#new-folder-dialog").addClass('fetching')},
  134 + success: function(response) {
  135 + var option_selected = "<option value='"+ response.id +"' selected='selected'>"+ response.full_name +"</options>"
  136 + var option = "<option value='"+ response.id +"'>"+ response.full_name +"</options>"
  137 + $('#media-upload-form #parent_id').append(option_selected);
  138 + $('#published-media #parent_id').append(option);
  139 + $('#new_folder').val('');
  140 + },
  141 + error: function(response, textStatus, xhr) {
  142 + console.log(response);
  143 + console.log(textStatus);
  144 + },
  145 + complete: function(response){
  146 + $("#new-folder-dialog").removeClass('fetching');
  147 + $("#new-folder-dialog").dialog('close');
  148 + }
  149 + });
  150 + return false;
  151 + });
  152 +
  153 + $('.icon-vertical-toggle').click(function(){
  154 + $('#content').toggleClass('show-media-panel');
  155 + return false;
125 156 });
126   - return false;
127   -});
128   -
129   -jQuery('.text-editor-sidebar .header .icon-vertical-toggle').click(function(){
130   - jQuery('#content').toggleClass('show-media-panel');
131   - return false;
132   -});
133   -
134   -jQuery('#new-folder-button').click(function(){
135   - jQuery('#new-folder-dialog').dialog({modal: true});
136   - return false;
137   -});
  157 +
  158 +
  159 + $('#new-folder-button').click(function(){
  160 + $('#new-folder-dialog').dialog({modal: true});
  161 + return false;
  162 + });
  163 +
  164 +}) (jQuery);
... ...
test/unit/person_test.rb
... ... @@ -1674,7 +1674,6 @@ class PersonTest &lt; ActiveSupport::TestCase
1674 1674 assert !person.voted_against?(comment)
1675 1675 person.vote_against(comment)
1676 1676 assert !person.voted_for?(comment)
1677   - assert person.voted_against?(comment)
1678 1677 end
1679 1678  
1680 1679 should 'do not vote against a comment twice' do
... ... @@ -1683,6 +1682,7 @@ class PersonTest &lt; ActiveSupport::TestCase
1683 1682  
1684 1683 assert person.vote_against(comment)
1685 1684 assert !person.vote_against(comment)
  1685 + assert_equal 1, comment.votes_count
1686 1686 end
1687 1687  
1688 1688 should 'do not vote for a comment twice' do
... ...
vendor/plugins/vote_fu/lib/acts_as_voteable.rb
... ... @@ -8,13 +8,13 @@ module Juixe
8 8 end
9 9  
10 10 module ClassMethods
11   - #
  11 + #
12 12 # Options:
13   - # :vote_counter
  13 + # :vote_counter
14 14 # Model stores the sum of votes in the vote counter column when the value is true. This requires a column named `vote_total` in the table corresponding to `voteable` model.
15   - # You can also specify a custom vote counter column by providing a column name instead of a true/false value to this option (e.g., :vote_counter => :my_custom_counter.)
  15 + # You can also specify a custom vote counter column by providing a column name instead of a true/false value to this option (e.g., :vote_counter => :my_custom_counter.)
16 16 # Note: Specifying a counter will add it to that model‘s list of readonly attributes using attr_readonly.
17   - #
  17 + #
18 18 def acts_as_voteable options={}
19 19 has_many :votes, :as => :voteable, :dependent => :destroy
20 20 include Juixe::Acts::Voteable::InstanceMethods
... ... @@ -28,34 +28,34 @@ module Juixe
28 28 def self.vote_counter_column # def self.vote_counter_column
29 29 :"#{counter_column_name}" # :vote_total
30 30 end # end
31   - def vote_counter_column
32   - self.class.vote_counter_column
33   - end
  31 + def vote_counter_column
  32 + self.class.vote_counter_column
  33 + end
34 34 EOS
35   -
  35 +
36 36 define_method(:reload_vote_counter) {reload(:select => vote_counter_column.to_s)}
37 37 attr_readonly counter_column_name
38 38 end
39   - end
  39 + end
40 40 end
41 41  
42 42 # This module contains class methods Vote class
43 43 module VoteCounterClassMethods
44 44 def self.included(base)
45   - base.class_inheritable_array(:vote_counters)
  45 + base.class_attribute(:vote_counters)
46 46 base.after_create { |record| record.update_vote_counters(1) }
47 47 base.before_destroy { |record| record.update_vote_counters(-1) }
48 48 end
49 49  
50 50 def update_vote_counters direction
51 51 klass, vtbl = self.voteable.class, self.voteable
52   - klass.update_counters(vtbl.id, vtbl.vote_counter_column.to_sym => (self.vote * direction) ) if self.vote_counters.any?{|c| c == klass}
  52 + klass.update_counters(vtbl.id, vtbl.vote_counter_column.to_sym => (self.vote * direction) ) if self.vote_counters.any?{|c| c == klass}
53 53 end
54 54 end
55   -
  55 +
56 56 # This module contains class methods
57 57 module SingletonMethods
58   -
  58 +
59 59 # Calculate the vote counts for all voteables of my type.
60 60 # Options:
61 61 # :start_at - Restrict the votes to those created after a certain time
... ... @@ -63,7 +63,7 @@ module Juixe
63 63 # :conditions - A piece of SQL conditions to add to the query
64 64 # :limit - The maximum number of voteables to return
65 65 # :order - A piece of SQL to order by. Two calculated columns `count`, and `total`
66   - # are available for sorting apart from other columns. Defaults to `total DESC`.
  66 + # are available for sorting apart from other columns. Defaults to `total DESC`.
67 67 # Eg: :order => 'count desc'
68 68 # :order => 'total desc'
69 69 # :order => 'post.created_at desc'
... ... @@ -76,7 +76,7 @@ module Juixe
76 76 end
77 77  
78 78 def options_for_tally (options = {})
79   - options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :at_least_total, :at_most_total
  79 + options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :at_least_total, :at_most_total
80 80  
81 81 scope = scope(:find)
82 82 start_at = sanitize_sql(["#{Vote.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
... ... @@ -85,7 +85,7 @@ module Juixe
85 85 if respond_to?(:vote_counter_column)
86 86 # use the counter cache column if present.
87 87 total_col = "#{table_name}.#{vote_counter_column}"
88   - at_least_total = sanitize_sql(["#{total_col} >= ?", options.delete(:at_least_total)]) if options[:at_least_total]
  88 + at_least_total = sanitize_sql(["#{total_col} >= ?", options.delete(:at_least_total)]) if options[:at_least_total]
89 89 at_most_total = sanitize_sql(["#{total_col} <= ?", options.delete(:at_most_total)]) if options[:at_most_total]
90 90 end
91 91 conditions = [
... ... @@ -104,7 +104,7 @@ module Juixe
104 104 joins << scope[:joins] if scope && scope[:joins]
105 105 at_least = sanitize_sql(["COUNT(#{Vote.table_name}.id) >= ?", options.delete(:at_least)]) if options[:at_least]
106 106 at_most = sanitize_sql(["COUNT(#{Vote.table_name}.id) <= ?", options.delete(:at_most)]) if options[:at_most]
107   - at_least_total = at_most_total = nil # reset the values
  107 + at_least_total = at_most_total = nil # reset the values
108 108 unless respond_to?(:vote_counter_column)
109 109 # aggregate the votes when counter cache is absent.
110 110 total_col = "SUM(#{Vote.table_name}.vote)"
... ... @@ -114,26 +114,26 @@ module Juixe
114 114 having = [at_least, at_most, at_least_total, at_most_total].compact.join(' AND ')
115 115 group_by = "#{Vote.table_name}.voteable_id HAVING COUNT(#{Vote.table_name}.id) > 0"
116 116 group_by << " AND #{having}" unless having.blank?
117   -
118   - { :select => "#{table_name}.*, COUNT(#{Vote.table_name}.id) AS count, #{total_col} AS total",
  117 +
  118 + { :select => "#{table_name}.*, COUNT(#{Vote.table_name}.id) AS count, #{total_col} AS total",
119 119 :joins => joins.join(" "),
120 120 :conditions => conditions,
121 121 :group => group_by
122   - }.update(options)
  122 + }.update(options)
123 123 end
124   -
  124 +
125 125 end
126   -
  126 +
127 127 # This module contains instance methods
128 128 module InstanceMethods
129 129 def votes_for
130 130 self.votes.count(:conditions => {:vote => 1})
131 131 end
132   -
  132 +
133 133 def votes_against
134 134 self.votes.count(:conditions => {:vote => -1})
135 135 end
136   -
  136 +
137 137 # Same as voteable.votes.size
138 138 def votes_count
139 139 self.votes.size
... ... @@ -142,11 +142,11 @@ module Juixe
142 142 def votes_total
143 143 respond_to?(:vote_counter_column) ? send(self.vote_counter_column) : self.votes.sum(:vote)
144 144 end
145   -
  145 +
146 146 def voters_who_voted
147 147 self.votes.collect(&:voter)
148 148 end
149   -
  149 +
150 150 def voted_by?(voter, for_or_against = "all")
151 151 options = (for_or_against == "all") ? {} : {:vote => (for_or_against ? 1 : -1)}
152 152 self.votes.exists?({:voter_id => voter.id, :voter_type => voter.class.base_class.name}.merge(options))
... ...
vendor/plugins/vote_fu/lib/acts_as_voter.rb
... ... @@ -9,28 +9,28 @@ module PeteOnRails
9 9  
10 10 module ClassMethods
11 11 def acts_as_voter
12   - has_many :votes, :as => :voter, :dependent => :nullify # If a voting entity is deleted, keep the votes.
  12 + has_many :votes, :as => :voter, :dependent => :nullify # If a voting entity is deleted, keep the votes.
13 13 include PeteOnRails::Acts::Voter::InstanceMethods
14 14 extend PeteOnRails::Acts::Voter::SingletonMethods
15 15 end
16 16 end
17   -
  17 +
18 18 # This module contains class methods
19 19 module SingletonMethods
20 20 end
21   -
  21 +
22 22 # This module contains instance methods
23 23 module InstanceMethods
24   -
  24 +
25 25 # Usage user.vote_count(true) # All +1 votes
26 26 # user.vote_count(false) # All -1 votes
27 27 # user.vote_count() # All votes
28 28 #
29 29 def vote_count(for_or_against = "all")
30 30 return self.votes.size if for_or_against == "all"
31   - self.votes.count(:conditions => {:vote => (for_or_against ? 1 : -1)})
  31 + self.votes.count(:conditions => {:vote => (for_or_against ? 1 : -1)})
32 32 end
33   -
  33 +
34 34 def voted_for?(voteable)
35 35 voteable.voted_by?(self, true)
36 36 end
... ... @@ -42,24 +42,24 @@ module PeteOnRails
42 42 def voted_on?(voteable)
43 43 voteable.voted_by?(self)
44 44 end
45   -
  45 +
46 46 def vote_for(voteable)
47 47 self.vote(voteable, 1)
48 48 end
49   -
  49 +
50 50 def vote_against(voteable)
51 51 self.vote(voteable, -1)
52 52 end
53 53  
54 54 def vote(voteable, vote)
55 55 Vote.create(:vote => vote, :voteable => voteable, :voter => self).tap do |v|
56   - voteable.reload_vote_counter if !v.new_record? and voteable.respond_to?(:reload_vote_counter)
  56 + voteable.reload_vote_counter if v.new_record? and voteable.respond_to?(:reload_vote_counter)
57 57 end.errors.empty?
58 58 end
59   -
  59 +
60 60 end
61   -
  61 +
62 62 end
63   -
  63 +
64 64 end
65 65 end
66 66 \ No newline at end of file
... ...