Commit 260f0d799b8520b7de6f9c6c802266e3b1bc052a

Authored by Leandro Nunes dos Santos
1 parent 3e4b7e40

Adding macro support for TinyMCE articles and a plugin called CommentGroupMacro …

…that uses the macro support
app/controllers/public/content_viewer_controller.rb
@@ -98,10 +98,13 @@ class ContentViewerController < ApplicationController @@ -98,10 +98,13 @@ class ContentViewerController < ApplicationController
98 session[:notice] = _("Notification of new comments to '%s' was successfully canceled") % params[:email] 98 session[:notice] = _("Notification of new comments to '%s' was successfully canceled") % params[:email]
99 end 99 end
100 end 100 end
  101 +
  102 + @comments = @plugins.dispatch_first(:load_comments, @page)
  103 + @comments ||= @page.comments.without_spam.as_thread
  104 + @comments = [@comments] unless @comments.is_a?(Array)
  105 +
  106 + @comments_count = Comment.count_thread(@comments)
101 107
102 - comments = @page.comments.without_spam  
103 - @comments = comments.as_thread  
104 - @comments_count = comments.count  
105 if params[:slideshow] 108 if params[:slideshow]
106 render :action => 'slideshow', :layout => 'slideshow' 109 render :action => 'slideshow', :layout => 'slideshow'
107 end 110 end
app/helpers/application_helper.rb
@@ -988,10 +988,7 @@ module ApplicationHelper @@ -988,10 +988,7 @@ module ApplicationHelper
988 options.merge!(:page => params[:npage]) 988 options.merge!(:page => params[:npage])
989 content = article.to_html(options) 989 content = article.to_html(options)
990 content = content.kind_of?(Proc) ? self.instance_eval(&content) : content 990 content = content.kind_of?(Proc) ? self.instance_eval(&content) : content
991 - @plugins && @plugins.each do |plugin|  
992 - content = plugin.parse_content(content)  
993 - end  
994 - content 991 + filter_html(content, article)
995 end 992 end
996 993
997 def colorpicker_field(object_name, method, options = {}) 994 def colorpicker_field(object_name, method, options = {})
@@ -1425,4 +1422,36 @@ module ApplicationHelper @@ -1425,4 +1422,36 @@ module ApplicationHelper
1425 @no_design_blocks = true 1422 @no_design_blocks = true
1426 end 1423 end
1427 1424
  1425 + def filter_html(html, source)
  1426 + if @plugins
  1427 + html = convert_macro(html, source)
  1428 + end
  1429 + html
  1430 + end
  1431 +
  1432 + def convert_macro(html, source)
  1433 + doc = Hpricot(html)
  1434 + while element = doc.search('.macro').first
  1435 + macro_name = element['data-macro']
  1436 + method_name = "macro_#{macro_name}"
  1437 + attrs = collect_macro_attributes(element)
  1438 + plugin_instance = Environment.macros[environment.id][method_name]
  1439 + if plugin_instance
  1440 + result = plugin_instance.send(method_name, attrs, element.inner_html, source)
  1441 + element.inner_html = result.kind_of?(Proc) ? self.instance_eval(&result) : result
  1442 + element['class'] = "parsed-macro #{macro_name}"
  1443 + else
  1444 + element.inner_html = _("Unsupported macro %s!") % macro_name
  1445 + element['class'] = "failed-macro #{macro_name}"
  1446 + end
  1447 + attrs.each {|key, value| element.remove_attribute("data-macro-#{key}")}
  1448 + end
  1449 + doc.html
  1450 + end
  1451 +
  1452 + def collect_macro_attributes(element)
  1453 + element.attributes.to_hash.select {|key, value| key[0..10] == 'data-macro-'}.
  1454 + inject({}){|result, a| result.merge({a[0][11..-1] => a[1]})}.with_indifferent_access
  1455 + end
  1456 +
1428 end 1457 end
app/helpers/boxes_helper.rb
@@ -99,8 +99,8 @@ module BoxesHelper @@ -99,8 +99,8 @@ module BoxesHelper
99 unless block.visible? 99 unless block.visible?
100 options[:title] = _("This block is invisible. Your visitors will not see it.") 100 options[:title] = _("This block is invisible. Your visitors will not see it.")
101 end 101 end
102 - @controller.send(:content_editor?) || @plugins.each do |plugin|  
103 - result = plugin.parse_content(result) 102 + if @controller.send(:content_editor?)
  103 + result = filter_html(result, block)
104 end 104 end
105 box_decorator.block_target(block.box, block) + 105 box_decorator.block_target(block.box, block) +
106 content_tag('div', 106 content_tag('div',
app/helpers/macros_helper.rb 0 → 100644
@@ -0,0 +1,96 @@ @@ -0,0 +1,96 @@
  1 +module MacrosHelper
  2 +
  3 + def macros_in_menu
  4 + Environment.macros[@environment.id].reject{ |macro_name, plugin_instance| macro_configuration(macro_name)[:icon_path] }
  5 + end
  6 +
  7 + def macros_with_buttons
  8 + Environment.macros[@environment.id].reject{ |macro_name, plugin_instance| !macro_configuration(macro_name)[:icon_path] }
  9 + end
  10 +
  11 + def macro_configuration(macro_name)
  12 + plugin_instance = Environment.macros[@environment.id][macro_name]
  13 + plugin_instance.send("config_#{macro_name}")
  14 + end
  15 +
  16 + def macro_title(macro_name)
  17 + macro_configuration(macro_name)[:title] || macro_name.to_s.humanize
  18 + end
  19 +
  20 + def generate_macro_config_dialog(macro_name)
  21 + if macro_configuration(macro_name)[:skip_dialog]
  22 + "function(){#{macro_generator(macro_name)}}"
  23 + else
  24 + "function(){
  25 + jQuery('<div>'+#{macro_configuration_dialog(macro_name).to_json}+'</div>').dialog({
  26 + title: #{macro_title(macro_name).to_json},
  27 + modal: true,
  28 + buttons: [
  29 + {text: #{_('Ok').to_json}, click: function(){
  30 + tinyMCE.activeEditor.execCommand('mceInsertContent', false,
  31 + (function(dialog){ #{macro_generator(macro_name)} })(this));
  32 + jQuery(this).dialog('close');
  33 + }},
  34 + {text: #{_('Cancel').to_json}, click: function(){jQuery(this).dialog('close');}}
  35 + ]
  36 + });
  37 + }"
  38 + end
  39 + end
  40 +
  41 + def include_macro_js_files
  42 + plugins_javascripts = []
  43 + Environment.macros[environment.id].map do |macro_name, plugin_instance|
  44 + if macro_configuration(macro_name)[:js_files]
  45 + macro_configuration(macro_name)[:js_files].map { |js| plugins_javascripts << plugin_instance.class.public_path(js) }
  46 + end
  47 + end
  48 + javascript_include_tag(plugins_javascripts, :cache => 'cache/plugins-' + Digest::MD5.hexdigest(plugins_javascripts.to_s)) unless plugins_javascripts.empty?
  49 + end
  50 +
  51 + def macro_css_files
  52 + plugins_css = []
  53 + Environment.macros[environment.id].map do |macro_name, plugin_instance|
  54 + if macro_configuration(macro_name)[:css_files]
  55 + macro_configuration(macro_name)[:css_files].map { |css| plugins_css << plugin_instance.class.public_path(css) }
  56 + end
  57 + end
  58 + plugins_css.join(',')
  59 + end
  60 +
  61 + protected
  62 +
  63 + def macro_generator(macro_name)
  64 + if macro_configuration(macro_name)[:generator]
  65 + macro_configuration(macro_name)[:generator]
  66 + else
  67 + macro_default_generator(macro_name)
  68 + end
  69 + end
  70 +
  71 + def macro_default_generator(macro_name)
  72 + code = "var params = {};"
  73 + configuration = macro_configuration(macro_name)
  74 + configuration[:params].map do |field|
  75 + code += "params.#{field[:name]} = jQuery('*[name=#{field[:name]}]', dialog).val();"
  76 + end
  77 + code + "
  78 + var html = jQuery('<div class=\"macro mceNonEditable\" data-macro=\"#{macro_name[6..-1]}\">'+#{macro_title(macro_name).to_json}+'</div>')[0];
  79 + for(key in params) html.setAttribute('data-macro-'+key,params[key]);
  80 + return html.outerHTML;
  81 + "
  82 + end
  83 +
  84 + def macro_configuration_dialog(macro_name)
  85 + macro_configuration(macro_name)[:params].map do |field|
  86 + label_name = field[:label] || field[:name].to_s.humanize
  87 + case field[:type]
  88 + when 'text'
  89 + labelled_form_field(label_name, text_field_tag(field[:name], field[:default]))
  90 + when 'select'
  91 + labelled_form_field(label_name, select_tag(field[:name], options_for_select(field[:values], field[:default])))
  92 + end
  93 + end.join("\n")
  94 + end
  95 +
  96 +end
app/models/comment.rb
@@ -157,6 +157,14 @@ class Comment &lt; ActiveRecord::Base @@ -157,6 +157,14 @@ class Comment &lt; ActiveRecord::Base
157 @replies = comments_list 157 @replies = comments_list
158 end 158 end
159 159
  160 + def self.count_thread(comments)
  161 + count = comments.length
  162 + comments.each do |c|
  163 + count+=count_thread(c.replies)
  164 + end
  165 + count
  166 + end
  167 +
160 def self.as_thread 168 def self.as_thread
161 result = {} 169 result = {}
162 root = [] 170 root = []
app/models/environment.rb
@@ -16,6 +16,10 @@ class Environment &lt; ActiveRecord::Base @@ -16,6 +16,10 @@ class Environment &lt; ActiveRecord::Base
16 filename 16 filename
17 end 17 end
18 18
  19 + class << self
  20 + attr_accessor :macros
  21 + end
  22 +
19 PERMISSIONS['Environment'] = { 23 PERMISSIONS['Environment'] = {
20 'view_environment_admin_panel' => N_('View environment admin panel'), 24 'view_environment_admin_panel' => N_('View environment admin panel'),
21 'edit_environment_features' => N_('Edit environment features'), 25 'edit_environment_features' => N_('Edit environment features'),
app/views/comment/_comment_form.rhtml
@@ -89,6 +89,8 @@ function check_captcha(button, confirm_action) { @@ -89,6 +89,8 @@ function check_captcha(button, confirm_action) {
89 <%= required labelled_form_field(_('Enter your comment'), f.text_area(:body, :rows => 5)) %> 89 <%= required labelled_form_field(_('Enter your comment'), f.text_area(:body, :rows => 5)) %>
90 <%= f.hidden_field(:reply_of_id) %> 90 <%= f.hidden_field(:reply_of_id) %>
91 91
  92 + <%= @plugins.dispatch(:comment_form_extra_contents, local_assigns).collect { |content| instance_eval(&content) }.join("") %>
  93 +
92 <% button_bar do %> 94 <% button_bar do %>
93 <%= submit_button('add', _('Post comment'), :onclick => "if(check_captcha(this)) { save_comment(this) } else { check_captcha(this, save_comment)};return false;") %> 95 <%= submit_button('add', _('Post comment'), :onclick => "if(check_captcha(this)) { save_comment(this) } else { check_captcha(this, save_comment)};return false;") %>
94 <%= button_to_function :cancel, _('Cancel'), "jQuery.colorbox.close();f=jQuery(this).parents('.post_comment_box'); f.removeClass('opened'); f.addClass('closed'); return false" %> 96 <%= button_to_function :cancel, _('Cancel'), "jQuery.colorbox.close();f=jQuery(this).parents('.post_comment_box'); f.removeClass('opened'); f.addClass('closed'); return false" %>
app/views/content_viewer/view_page.rhtml
@@ -91,7 +91,7 @@ @@ -91,7 +91,7 @@
91 91
92 <% if @page.accept_comments? || @comments_count > 0 %> 92 <% if @page.accept_comments? || @comments_count > 0 %>
93 <h3 <%= 'class="no-comments-yet"' if @comments_count == 0 %>> 93 <h3 <%= 'class="no-comments-yet"' if @comments_count == 0 %>>
94 - <%= number_of_comments(@page) %> 94 + <%= display_number_of_comments(@comments_count) %>
95 </h3> 95 </h3>
96 <% end %> 96 <% end %>
97 97
app/views/shared/tiny_mce.rhtml
  1 +<% extend MacrosHelper %>
1 <%= javascript_include_tag 'tinymce/jscripts/tiny_mce/tiny_mce.js' %> 2 <%= javascript_include_tag 'tinymce/jscripts/tiny_mce/tiny_mce.js' %>
  3 +<%= include_macro_js_files %>
2 <script type="text/javascript"> 4 <script type="text/javascript">
3 - var myplugins = "searchreplace,print,table,contextmenu"; 5 + var myplugins = "searchreplace,print,table,contextmenu,-macrosPlugin";
4 var first_line, second_line; 6 var first_line, second_line;
5 var mode = '<%= mode ||= false %>' 7 var mode = '<%= mode ||= false %>'
6 <% if mode %> 8 <% if mode %>
@@ -8,36 +10,82 @@ @@ -8,36 +10,82 @@
8 second_line = "" 10 second_line = ""
9 <% else %> 11 <% else %>
10 first_line = "print,separator,copy,paste,separator,undo,redo,separator,search,replace,separator,forecolor,fontsizeselect,formatselect" 12 first_line = "print,separator,copy,paste,separator,undo,redo,separator,search,replace,separator,forecolor,fontsizeselect,formatselect"
11 - second_line = "bold,italic,underline,strikethrough,separator,bullist,numlist,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,link,unlink,image,table,separator,cleanup,code" 13 + second_line = "bold,italic,underline,strikethrough,separator,bullist,numlist,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,link,unlink,image,table,separator,cleanup,code,macros"
  14 + <% Environment.macros[environment.id].each do |macro_name, plugin_instance| %>
  15 + <% if macro_configuration(macro_name)[:icon_path] %>
  16 + second_line += ',<%=macro_name %>'
  17 + <% end %>
  18 + <% end %>
12 <% end %> 19 <% end %>
13 20
14 if (tinymce.isIE) { 21 if (tinymce.isIE) {
15 // the paste plugin is only useful in Internet Explorer 22 // the paste plugin is only useful in Internet Explorer
16 myplugins = "paste," + myplugins; 23 myplugins = "paste," + myplugins;
17 } 24 }
  25 +
  26 +tinymce.create('tinymce.plugins.MacrosPlugin', {
  27 + createControl: function(n, cm) {
  28 + switch (n) {
  29 + case 'macros':
  30 + <% unless macros_in_menu.empty? %>
  31 + var c = cm.createMenuButton('macros', {
  32 + title : 'Macros',
  33 + image : '/designs/icons/tango/Tango/16x16/emblems/emblem-system.png',
  34 + icons : false
  35 + });
  36 +
  37 + <% macros_in_menu.each do |macro_name, plugin_instance| %>
  38 + c.onRenderMenu.add(function(c, m) {
  39 + m.add({
  40 + title: <%= macro_title(macro_name).to_json %>,
  41 + onclick: <%= generate_macro_config_dialog(macro_name) %>
  42 + });
  43 + });
  44 + <% end %>
  45 +
  46 + // Return the new menu button instance
  47 + return c;
  48 + <% end %>
  49 + }
  50 + return null;
  51 + }
  52 +});
  53 +
  54 +// Register plugin with a short name
  55 +tinymce.PluginManager.add('macrosPlugin', tinymce.plugins.MacrosPlugin);
  56 +
18 tinyMCE.init({ 57 tinyMCE.init({
19 - mode : "textareas",  
20 - editor_selector : "mceEditor",  
21 - theme : "advanced",  
22 - relative_urls : false,  
23 - remove_script_host : true,  
24 - document_base_url : <%= environment.top_url.to_json %>,  
25 - plugins: myplugins,  
26 - theme_advanced_toolbar_location : "top",  
27 - theme_advanced_layout_manager: 'SimpleLayout',  
28 - theme_advanced_buttons1 : first_line,  
29 - theme_advanced_buttons2 : second_line,  
30 - theme_advanced_buttons3 : "",  
31 - theme_advanced_blockformats :"p,address,pre,h2,h3,h4,h5,h6",  
32 - paste_auto_cleanup_on_paste : true,  
33 - paste_insert_word_content_callback : "convertWord",  
34 - paste_use_dialog: false,  
35 - apply_source_formatting : true,  
36 - extended_valid_elements : "applet[style|archive|codebase|code|height|width],comment,iframe[src|style|allowtransparency|frameborder|width|height|scrolling],embed[title|src|type|height|width]",  
37 - content_css: '/stylesheets/tinymce.css',  
38 - language: <%= tinymce_language.inspect %>,  
39 - entity_encoding: 'raw'  
40 - }); 58 + mode : "textareas",
  59 + editor_selector : "mceEditor",
  60 + theme : "advanced",
  61 + relative_urls : false,
  62 + remove_script_host : true,
  63 + document_base_url : <%= environment.top_url.to_json %>,
  64 + plugins: myplugins,
  65 + theme_advanced_toolbar_location : "top",
  66 + theme_advanced_layout_manager: 'SimpleLayout',
  67 + theme_advanced_buttons1 : first_line,
  68 + theme_advanced_buttons2 : second_line,
  69 + theme_advanced_buttons3 : "",
  70 + theme_advanced_blockformats :"p,address,pre,h2,h3,h4,h5,h6",
  71 + paste_auto_cleanup_on_paste : true,
  72 + paste_insert_word_content_callback : "convertWord",
  73 + paste_use_dialog: false,
  74 + apply_source_formatting : true,
  75 + extended_valid_elements : "applet[style|archive|codebase|code|height|width],comment,iframe[src|style|allowtransparency|frameborder|width|height|scrolling],embed[title|src|type|height|width]",
  76 + content_css: '/stylesheets/tinymce.css,<%= macro_css_files %>',
  77 + language: <%= tinymce_language.inspect %>,
  78 + entity_encoding: 'raw',
  79 + setup : function(ed) {
  80 + <% macros_with_buttons.each do |macro_name, plugin_instance| %>
  81 + ed.addButton('<%= macro_name %>', {
  82 + title: <%= macro_title(macro_name).to_json %>,
  83 + onclick: <%= generate_macro_config_dialog(macro_name) %>,
  84 + image : '<%= macro_configuration(macro_name)[:icon_path]%>'
  85 + });
  86 + <% end %>
  87 + }
  88 +});
41 89
42 function convertWord(type, content) { 90 function convertWord(type, content) {
43 switch (type) { 91 switch (type) {
lib/noosfero/plugin.rb
@@ -5,6 +5,13 @@ class Noosfero::Plugin @@ -5,6 +5,13 @@ class Noosfero::Plugin
5 5
6 attr_accessor :context 6 attr_accessor :context
7 7
  8 + def initialize(context=nil)
  9 + self.context = context
  10 + macro_methods.each do |method|
  11 + Environment.macros[context.environment.id][method] = self unless context.nil?
  12 + end
  13 + end
  14 +
8 class << self 15 class << self
9 16
10 def klass(dir) 17 def klass(dir)
@@ -199,8 +206,8 @@ class Noosfero::Plugin @@ -199,8 +206,8 @@ class Noosfero::Plugin
199 206
200 # -> Parse and possibly make changes of content (article, block, etc) during HTML rendering 207 # -> Parse and possibly make changes of content (article, block, etc) during HTML rendering
201 # returns = content as string after parser and changes 208 # returns = content as string after parser and changes
202 - def parse_content(raw_content)  
203 - raw_content 209 + def parse_content(args)
  210 + args
204 end 211 end
205 212
206 # -> Adds links to the admin panel 213 # -> Adds links to the admin panel
@@ -236,6 +243,18 @@ class Noosfero::Plugin @@ -236,6 +243,18 @@ class Noosfero::Plugin
236 def filter_comment(comment) 243 def filter_comment(comment)
237 end 244 end
238 245
  246 + # Define custom logic to load the article comments.
  247 + #
  248 + # Example:
  249 + #
  250 + # def load_comments(article)
  251 + # article.comments.find(:all, :conditions => ['spam IS NULL OR spam = ?', false])
  252 + # end
  253 + #
  254 + def load_comments(article)
  255 + nil
  256 + end
  257 +
239 # This method is called by the CommentHandler background job before sending 258 # This method is called by the CommentHandler background job before sending
240 # the notification email. If the comment is marked as spam (i.e. by calling 259 # the notification email. If the comment is marked as spam (i.e. by calling
241 # <tt>comment.spam!</tt>), then the notification email will *not* be sent. 260 # <tt>comment.spam!</tt>), then the notification email will *not* be sent.
@@ -288,7 +307,7 @@ class Noosfero::Plugin @@ -288,7 +307,7 @@ class Noosfero::Plugin
288 def profile_info_extra_contents 307 def profile_info_extra_contents
289 nil 308 nil
290 end 309 end
291 - 310 +
292 # -> Removes the invite friend button from the friends controller 311 # -> Removes the invite friend button from the friends controller
293 # returns = boolean 312 # returns = boolean
294 def remove_invite_friends_button 313 def remove_invite_friends_button
@@ -350,6 +369,18 @@ class Noosfero::Plugin @@ -350,6 +369,18 @@ class Noosfero::Plugin
350 def login_extra_contents 369 def login_extra_contents
351 nil 370 nil
352 end 371 end
  372 +
  373 + # -> Adds adicional content to comment form
  374 + # returns = lambda block that creates html code
  375 + def comment_form_extra_contents(args)
  376 + nil
  377 + end
  378 +
  379 + # -> Register macro methods in environment
  380 + # returns = ['method1', 'method2', ...]
  381 + def macro_methods
  382 + []
  383 + end
353 384
354 def method_missing(method, *args, &block) 385 def method_missing(method, *args, &block)
355 # This is a generic hotspot for all controllers on Noosfero. 386 # This is a generic hotspot for all controllers on Noosfero.
@@ -376,6 +407,11 @@ class Noosfero::Plugin @@ -376,6 +407,11 @@ class Noosfero::Plugin
376 # returns = string with reason of expiration 407 # returns = string with reason of expiration
377 elsif method.to_s =~ /^content_expire_(#{content_actions.join('|')})$/ 408 elsif method.to_s =~ /^content_expire_(#{content_actions.join('|')})$/
378 nil 409 nil
  410 + # -> Answers to a specific macro
  411 + # expects: params, inner_html, content
  412 + # returns = html_code
  413 + elsif method.to_s =~ /^macro_(.+)$/
  414 + nil
379 else 415 else
380 super 416 super
381 end 417 end
lib/noosfero/plugin/manager.rb
@@ -6,6 +6,7 @@ class Noosfero::Plugin::Manager @@ -6,6 +6,7 @@ class Noosfero::Plugin::Manager
6 def initialize(environment, context) 6 def initialize(environment, context)
7 @environment = environment 7 @environment = environment
8 @context = context 8 @context = context
  9 + Environment.macros = {environment.id => {}}
9 end 10 end
10 11
11 delegate :each, :to => :enabled_plugins 12 delegate :each, :to => :enabled_plugins
@@ -27,13 +28,20 @@ class Noosfero::Plugin::Manager @@ -27,13 +28,20 @@ class Noosfero::Plugin::Manager
27 map { |plugin| plugin.send(event, *args) }.compact 28 map { |plugin| plugin.send(event, *args) }.compact
28 end 29 end
29 30
  31 + def dispatch_first(event, *args)
  32 + value = nil
  33 + map do |plugin|
  34 + value = plugin.send(event, *args)
  35 + break if value
  36 + end
  37 + value
  38 + end
  39 +
30 alias :dispatch_scopes :dispatch_without_flatten 40 alias :dispatch_scopes :dispatch_without_flatten
31 41
32 def enabled_plugins 42 def enabled_plugins
33 @enabled_plugins ||= (Noosfero::Plugin.all & environment.enabled_plugins).map do |plugin| 43 @enabled_plugins ||= (Noosfero::Plugin.all & environment.enabled_plugins).map do |plugin|
34 - p = plugin.constantize.new  
35 - p.context = context  
36 - p 44 + plugin.constantize.new(context)
37 end 45 end
38 end 46 end
39 47
plugins/comment_group_macro/controllers/profile/comment_group_macro_plugin_profile_controller.rb 0 → 100644
@@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
  1 +class CommentGroupMacroPluginProfileController < ProfileController
  2 + append_view_path File.join(File.dirname(__FILE__) + '/../views')
  3 +
  4 + def view_comments
  5 + article_id = params[:article_id]
  6 + group_id = params[:group_id]
  7 +
  8 + article = profile.articles.find(article_id)
  9 + comments = article.group_comments.without_spam.in_group(group_id)
  10 + render :update do |page|
  11 + page.replace_html "comments_list_group_#{group_id}", :partial => 'comment/comment.rhtml', :collection => comments.as_thread
  12 + page.replace_html "comment-count-#{group_id}", comments.count
  13 + end
  14 + end
  15 +
  16 +end
plugins/comment_group_macro/db/migrate/20121220201629_add_group_id_to_comment.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class AddGroupIdToComment < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :comments, :group_id, :integer
  4 + end
  5 +
  6 + def self.down
  7 + remove_column :comments, :group_id
  8 + end
  9 +end
plugins/comment_group_macro/lib/comment_group_macro_plugin.rb 0 → 100644
@@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
  1 +require_dependency 'comment_group_macro_plugin/ext/article'
  2 +require_dependency 'comment_group_macro_plugin/ext/comment'
  3 +
  4 +#FIXME See a better way to generalize this parameter.
  5 +ActionView::Base.sanitized_allowed_attributes += ['data-macro', 'data-macro-group_id']
  6 +
  7 +class CommentGroupMacroPlugin < Noosfero::Plugin
  8 +
  9 + def self.plugin_name
  10 + "Comment Group"
  11 + end
  12 +
  13 + def self.plugin_description
  14 + _("A plugin that display comment groups.")
  15 + end
  16 +
  17 + def load_comments(article)
  18 + article.comments.without_spam.without_group.as_thread
  19 + end
  20 +
  21 + #FIXME make this test
  22 + def macro_display_comments(params, inner_html, source)
  23 + group_id = params[:group_id].to_i
  24 + article = source
  25 + count = article.group_comments.without_spam.in_group(group_id).count
  26 +
  27 + lambda {render :partial => 'plugins/comment_group_macro/views/comment_group.rhtml', :locals => {:group_id => group_id, :article_id => article.id, :inner_html => inner_html, :count => count }}
  28 + end
  29 +
  30 + def macro_methods
  31 + 'macro_display_comments'
  32 + end
  33 +
  34 + def config_macro_display_comments
  35 + { :params => [], :skip_dialog => true, :generator => 'makeCommentable();', :js_files => 'comment_group.js', :icon_path => '/designs/icons/tango/Tango/16x16/emblems/emblem-system.png', :css_files => 'comment_group.css' }
  36 + end
  37 +
  38 + def comment_form_extra_contents(args)
  39 + lambda {
  40 + hidden_field_tag('comment[group_id]', args[:group_id]) if args.has_key?(:group_id)
  41 + }
  42 + end
  43 +
  44 +end
plugins/comment_group_macro/lib/comment_group_macro_plugin/ext/article.rb 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +require_dependency 'article'
  2 +
  3 +class Article
  4 +
  5 + #FIXME make this test
  6 + has_many :group_comments, :class_name => 'Comment', :foreign_key => 'source_id', :dependent => :destroy, :order => 'created_at asc', :conditions => [ 'group_id IS NOT NULL']
  7 +
  8 + #FIXME make this test
  9 + validate :not_empty_group_comments_removed
  10 +
  11 + #FIXME make this test
  12 + def not_empty_group_comments_removed
  13 + if body
  14 + groups_with_comments = group_comments.collect {|comment| comment.group_id}.uniq
  15 + groups = Hpricot(body.to_s).search('.macro').collect{|element| element['data-macro-group_id'].to_i}
  16 + errors.add_to_base(N_('Not empty group comment cannot be removed')) unless (groups_with_comments-groups).empty?
  17 + end
  18 + end
  19 +
  20 +end
  21 +
plugins/comment_group_macro/lib/comment_group_macro_plugin/ext/comment.rb 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +require_dependency 'comment'
  2 +
  3 +class Comment
  4 +
  5 + named_scope :without_group, :conditions => {:group_id => nil }
  6 +
  7 + named_scope :in_group, lambda { |group_id| {
  8 + :conditions => ['group_id = ?', group_id]
  9 + }
  10 + }
  11 +
  12 +end
plugins/comment_group_macro/public/comment_group.css 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +div.article_comments {
  2 + background-color:#dddddd;
  3 + border-style: solid;
  4 + border-color:#bbbbbb;
  5 + border-width:2px;
  6 +}
plugins/comment_group_macro/public/comment_group.js 0 → 100644
@@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
  1 +function getNextGroupId() {
  2 + max = -1;
  3 + groups = jQuery('#article_body_ifr').contents().find('.article_comments');
  4 + groups.each(function(key, value) {
  5 + value = jQuery(value).attr('data-macro-group_id');
  6 + if(value>max) max = parseInt(value);
  7 + });
  8 + return max+1;
  9 +}
  10 +
  11 +function makeCommentable() {
  12 + tinyMCE.activeEditor.focus();
  13 + start = jQuery(tinyMCE.activeEditor.selection.getStart()).closest('p');
  14 + end = jQuery(tinyMCE.activeEditor.selection.getEnd()).closest('p');
  15 +
  16 + //text = start.parent().children();
  17 + text = jQuery('#article_body_ifr').contents().find('*');
  18 + selection = text.slice(text.index(start), text.index(end)+1);
  19 +
  20 + hasTag = false;
  21 + selection.each(function(key, value) {
  22 + commentTag = jQuery(value).closest('.article_comments');
  23 + if(commentTag.length) {
  24 + commentTag.children().unwrap('<div class=\"article_comments\"/>');
  25 + hasTag = true;
  26 + }
  27 + });
  28 +
  29 + if(!hasTag) {
  30 + tags = start.siblings().add(start);
  31 + tags = tags.slice(tags.index(start), tags.index(end)>=0?tags.index(end)+1:tags.index(start)+1);
  32 + tags.wrapAll('<div class=\"macro article_comments\" data-macro=\"display_comments\" data-macro-group_id=\"'+getNextGroupId()+'\"/>');
  33 +
  34 + contents = jQuery('#article_body_ifr').contents();
  35 + lastP = contents.find('p.article_comments_last_paragraph');
  36 + if(lastP.text().trim().length > 0) {
  37 + lastP.removeClass('article_comments_last_paragraph');
  38 + } else {
  39 + lastP.remove();
  40 + }
  41 + lastDiv = contents.find('div.article_comments').last();
  42 + if(lastDiv.next().length==0) {
  43 + lastDiv.after("<p class='article_comments_last_paragraph'>&nbsp;</p>");
  44 + }
  45 + }
  46 +}
  47 +
plugins/comment_group_macro/public/images/comments.gif 0 → 100644

1.66 KB

plugins/comment_group_macro/test/functional/comment_group_macro_plugin_profile_controller_test.rb 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../../controllers/profile/comment_group_macro_plugin_profile_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class CommentGroupMacroPluginProfileController; def rescue_action(e) raise e end; end
  6 +
  7 +class CommentGroupMacroPluginProfileControllerTest < ActionController::TestCase
  8 +
  9 + def setup
  10 + @controller = CommentGroupMacroPluginProfileController.new
  11 + @request = ActionController::TestRequest.new
  12 + @response = ActionController::TestResponse.new
  13 +
  14 + @profile = create_user('testuser').person
  15 + @article = profile.articles.build(:name => 'test')
  16 + @article.save!
  17 + end
  18 + attr_reader :article
  19 + attr_reader :profile
  20 +
  21 + should 'be able to show group comments' do
  22 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'a comment', :body => 'lalala', :group_id => 0)
  23 + xhr :get, :view_comments, :profile => @profile.identifier, :article_id => article.id, :group_id => 0
  24 + assert_template 'comment/_comment.rhtml'
  25 + assert_match /comments_list_group_0/, @response.body
  26 + assert_match /\"comment-count-0\", \"1\"/, @response.body
  27 + end
  28 +
  29 + should 'do not show global comments' do
  30 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'global comment', :body => 'global', :group_id => nil)
  31 + comment = fast_create(Comment, :source_id => article, :author_id => profile, :title => 'a comment', :body => 'lalala', :group_id => 0)
  32 + xhr :get, :view_comments, :profile => @profile.identifier, :article_id => article.id, :group_id => 0
  33 + assert_template 'comment/_comment.rhtml'
  34 + assert_match /comments_list_group_0/, @response.body
  35 + assert_match /\"comment-count-0\", \"1\"/, @response.body
  36 + end
  37 +
  38 +end
plugins/comment_group_macro/test/unit/comment_group_macro_plugin_test.rb 0 → 100644
@@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +
  3 +class CommentGroupMacroPluginTest < ActiveSupport::TestCase
  4 +
  5 + include Noosfero::Plugin::HotSpot
  6 +
  7 + def setup
  8 + @environment = Environment.default
  9 + end
  10 +
  11 + attr_reader :environment
  12 +
  13 + should 'register comment_group_macro in environment' do
  14 + Environment.macros = {}
  15 + Environment.macros[environment.id] = {}
  16 + macros = Environment.macros[environment.id]
  17 + context = mock()
  18 + context.stubs(:environment).returns(environment)
  19 + plugin = CommentGroupMacroPlugin.new(context)
  20 + assert_equal ['macro_display_comments'], macros.keys
  21 + end
  22 +
  23 + should 'load_comments returns all the comments wihout group of an article passed as parameter' do
  24 + article = fast_create(Article)
  25 + c1 = fast_create(Comment, :source_id => article.id, :group_id => 1)
  26 + c2 = fast_create(Comment, :source_id => article.id)
  27 + c3 = fast_create(Comment, :source_id => article.id)
  28 +
  29 + plugin = CommentGroupMacroPlugin.new
  30 + assert_equal [], [c2, c3] - plugin.load_comments(article)
  31 + assert_equal [], plugin.load_comments(article) - [c2, c3]
  32 + end
  33 +
  34 + should 'load_comments not returns spam comments' do
  35 + article = fast_create(Article)
  36 + c1 = fast_create(Comment, :source_id => article.id, :group_id => 1)
  37 + c2 = fast_create(Comment, :source_id => article.id)
  38 + c3 = fast_create(Comment, :source_id => article.id, :spam => true)
  39 +
  40 + plugin = CommentGroupMacroPlugin.new
  41 + assert_equal [], [c2] - plugin.load_comments(article)
  42 + assert_equal [], plugin.load_comments(article) - [c2]
  43 + end
  44 +
  45 + should 'load_comments returns only root comments of article' do
  46 + article = fast_create(Article)
  47 + c1 = fast_create(Comment, :source_id => article.id, :group_id => 1)
  48 + c2 = fast_create(Comment, :source_id => article.id)
  49 + c3 = fast_create(Comment, :source_id => article.id, :reply_of_id => c2.id)
  50 +
  51 + plugin = CommentGroupMacroPlugin.new
  52 + assert_equal [], [c2] - plugin.load_comments(article)
  53 + assert_equal [], plugin.load_comments(article) - [c2]
  54 + end
  55 +
  56 + should 'params of macro display comments configuration be an empty array' do
  57 + plugin = CommentGroupMacroPlugin.new
  58 + assert_equal [], plugin.config_macro_display_comments[:params]
  59 + end
  60 +
  61 + should 'skip_dialog of macro display comments configuration be true' do
  62 + plugin = CommentGroupMacroPlugin.new
  63 + assert plugin.config_macro_display_comments[:skip_dialog]
  64 + end
  65 +
  66 + should 'generator of macro display comments configuration be the makeCommentable function' do
  67 + plugin = CommentGroupMacroPlugin.new
  68 + assert_equal 'makeCommentable();', plugin.config_macro_display_comments[:generator]
  69 + end
  70 +
  71 + should 'js_files of macro display comments configuration return comment_group.js' do
  72 + plugin = CommentGroupMacroPlugin.new
  73 + assert_equal 'comment_group.js', plugin.config_macro_display_comments[:js_files]
  74 + end
  75 +
  76 + should 'css_files of macro display comments configuration return comment_group.css' do
  77 + plugin = CommentGroupMacroPlugin.new
  78 + assert_equal 'comment_group.css', plugin.config_macro_display_comments[:css_files]
  79 + end
  80 +
  81 +end
plugins/comment_group_macro/views/_comment_group.rhtml 0 → 100644
@@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
  1 +<div class="comments">
  2 + <div class="comment_group_<%= group_id %>" style="float: left">
  3 + <div style="float: left">
  4 + <%= link_to_remote(image_tag("/plugins/comment_group_macro/images/comments.gif"),
  5 + :url => { :controller => 'comment_group_macro_plugin_profile', :action => 'view_comments', :group_id => group_id, :article_id => article_id},
  6 + :loaded => visual_effect(:highlight, "comments_list_group_#{group_id}"),
  7 + :method => :post,
  8 + :condition => "!groupVisible(#{group_id})",
  9 + :complete => "jQuery('div.comment-group-loading-#{group_id}').removeClass('comment-button-loading')")%>
  10 + </div>
  11 + <!-- FIXME: css file -->
  12 + <div id="comments_group_count_<%= group_id %>" style="float: right; vertical-align: middle; padding-left: 3px; padding-right: 5px; color: #5AC1FC"><span id="comment-count-<%= group_id %>" class='comment-count'><%= count %></span></div>
  13 +
  14 + </div>
  15 +
  16 + <div>
  17 + <%= inner_html %>
  18 + </div>
  19 +
  20 + <div class="comment-group-loading-<%= group_id %>"/>
  21 +
  22 + <div class="comments_list_toggle_group_<%= group_id %>" style="display:none">
  23 + <div class="article-comments-list">
  24 + <div id="comments_list_group_<%= group_id %>"></div>
  25 + </div>
  26 +
  27 + <div id="page-comment-form-<%= group_id %>" class='post_comment_box closed'><%= render :partial => 'comment/comment_form', :locals => {:comment => Comment.new, :display_link => true, :cancel_triggers_hide => true, :group_id => group_id}%></div>
  28 +
  29 + </div>
  30 +
  31 + <script type="text/javascript">
  32 + function groupVisible(group) {
  33 + return jQuery('div.comments_list_toggle_group_'+group).is(':visible');
  34 + }
  35 +
  36 + (function($) {
  37 + var button = $('div.comment_group_<%= group_id %> a');
  38 + button.click(function() {
  39 + var div = $('div.comments_list_toggle_group_<%= group_id %>')
  40 + if(!div.is(':visible'))
  41 + $('div.comment-group-loading-<%= group_id %>').addClass('comment-button-loading');
  42 + div.toggle('fast');
  43 + });
  44 + })(jQuery)
  45 + </script>
  46 +</div>
  47 +
plugins/send_email/lib/send_email_plugin.rb
@@ -12,12 +12,14 @@ class SendEmailPlugin &lt; Noosfero::Plugin @@ -12,12 +12,14 @@ class SendEmailPlugin &lt; Noosfero::Plugin
12 true 12 true
13 end 13 end
14 14
15 - def parse_content(raw_content) 15 + def parse_content(args)
  16 + raw_content = args[:html]
16 if context.profile 17 if context.profile
17 raw_content.gsub(/\{sendemail\}/, "/profile/#{context.profile.identifier}/plugins/send_email/deliver") 18 raw_content.gsub(/\{sendemail\}/, "/profile/#{context.profile.identifier}/plugins/send_email/deliver")
18 else 19 else
19 raw_content.gsub(/\{sendemail\}/, '/plugin/send_email/deliver') 20 raw_content.gsub(/\{sendemail\}/, '/plugin/send_email/deliver')
20 end 21 end
  22 + args.clone.merge({:html => raw_content})
21 end 23 end
22 24
23 end 25 end
test/functional/content_viewer_controller_test.rb
@@ -1229,4 +1229,118 @@ class ContentViewerControllerTest &lt; ActionController::TestCase @@ -1229,4 +1229,118 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1229 assert_equal 1, assigns(:comments_count) 1229 assert_equal 1, assigns(:comments_count)
1230 end 1230 end
1231 1231
  1232 + should 'add extra content on comment form from plugins' do
  1233 + class Plugin1 < Noosfero::Plugin
  1234 + def comment_form_extra_contents(args)
  1235 + lambda {
  1236 + hidden_field_tag('comment[some_field_id]', 1)
  1237 + }
  1238 + end
  1239 + end
  1240 + class Plugin2 < Noosfero::Plugin
  1241 + def comment_form_extra_contents(args)
  1242 + lambda {
  1243 + hidden_field_tag('comment[another_field_id]', 1)
  1244 + }
  1245 + end
  1246 + end
  1247 +
  1248 + Environment.default.enable_plugin(Plugin1.name)
  1249 + Environment.default.enable_plugin(Plugin2.name)
  1250 +
  1251 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1252 +
  1253 + get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ]
  1254 +
  1255 + assert_tag :tag => 'input', :attributes => {:name => 'comment[some_field_id]', :type => 'hidden'}
  1256 + assert_tag :tag => 'input', :attributes => {:name => 'comment[another_field_id]', :type => 'hidden'}
  1257 + end
  1258 +
  1259 + should 'collect comments as plugin definition' do
  1260 + class Plugin1 < Noosfero::Plugin
  1261 + def load_comments(page)
  1262 + [page.comments.find(4)]
  1263 + end
  1264 + end
  1265 + Environment.default.enable_plugin(Plugin1.name)
  1266 + Comment.delete_all
  1267 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1268 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 1' ).save!
  1269 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 2' ).save!
  1270 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 3' ).save!
  1271 + c = page.comments.build(:author => profile, :title => 'hi', :body => 'hello 4' )
  1272 + c.save!
  1273 +
  1274 +
  1275 + get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ]
  1276 +
  1277 + assert_equal [c], assigns(:comments)
  1278 + end
  1279 +
  1280 + should 'not be a problem if loaded comments of plugins not be an array' do
  1281 + class Plugin1 < Noosfero::Plugin
  1282 + def load_comments(page)
  1283 + page.comments.find(4)
  1284 + end
  1285 + end
  1286 + Environment.default.enable_plugin(Plugin1.name)
  1287 + Comment.delete_all
  1288 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1289 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 1' ).save!
  1290 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 2' ).save!
  1291 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 3' ).save!
  1292 + c = page.comments.build(:author => profile, :title => 'hi', :body => 'hello 4' )
  1293 + c.save!
  1294 +
  1295 +
  1296 + get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ]
  1297 +
  1298 + assert_equal [c], assigns(:comments)
  1299 + end
  1300 +
  1301 +
  1302 + should 'take in consideration only the first plugin comments definition' do
  1303 + class Plugin1 < Noosfero::Plugin
  1304 + def load_comments(page)
  1305 + page.comments.first
  1306 + end
  1307 + end
  1308 + class Plugin2 < Noosfero::Plugin
  1309 + def load_comments(page)
  1310 + page.comments.last
  1311 + end
  1312 + end
  1313 + Environment.default.enable_plugin(Plugin1.name)
  1314 + Environment.default.enable_plugin(Plugin2.name)
  1315 +
  1316 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1317 + c1 = page.comments.build(:author => profile, :title => 'hi', :body => 'hello 1' )
  1318 + c1.save!
  1319 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 2' ).save!
  1320 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 3' ).save!
  1321 + c2 = page.comments.build(:author => profile, :title => 'hi', :body => 'hello 4' )
  1322 + c2.save!
  1323 +
  1324 + get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ]
  1325 +
  1326 + assert_equal [c1], assigns(:comments)
  1327 + end
  1328 +
  1329 + should 'empty array of comments collected by plugin make the comments variable be an empty array' do
  1330 + class Plugin1 < Noosfero::Plugin
  1331 + def load_comments(page)
  1332 + []
  1333 + end
  1334 + end
  1335 + Environment.default.enable_plugin(Plugin1.name)
  1336 +
  1337 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  1338 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 1' ).save!
  1339 + page.comments.build(:author => profile, :title => 'hi', :body => 'hello 2' ).save!
  1340 +
  1341 +
  1342 + get :view_page, :profile => profile.identifier, :page => [ 'myarticle' ]
  1343 +
  1344 + assert_equal [], assigns(:comments)
  1345 + end
1232 end 1346 end
test/unit/application_helper_test.rb
@@ -3,6 +3,7 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -3,6 +3,7 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
3 class ApplicationHelperTest < ActiveSupport::TestCase 3 class ApplicationHelperTest < ActiveSupport::TestCase
4 4
5 include ApplicationHelper 5 include ApplicationHelper
  6 + include Noosfero::Plugin::HotSpot
6 7
7 def setup 8 def setup
8 self.stubs(:session).returns({}) 9 self.stubs(:session).returns({})
@@ -657,6 +658,45 @@ class ApplicationHelperTest &lt; ActiveSupport::TestCase @@ -657,6 +658,45 @@ class ApplicationHelperTest &lt; ActiveSupport::TestCase
657 assert_not_nil add_zoom_to_images 658 assert_not_nil add_zoom_to_images
658 end 659 end
659 660
  661 + should 'parse macros' do
  662 + class Plugin1 < Noosfero::Plugin
  663 + def macro_test1(params, inner_html, source)
  664 + 'Test1'
  665 + end
  666 + def macro_test2(params, inner_html, source)
  667 + 'Test2'
  668 + end
  669 +
  670 + def macro_methods
  671 + ['macro_test1', 'macro_test2']
  672 + end
  673 + end
  674 +
  675 + @environment = Environment.default
  676 + Environment.macros = {@environment.id => {}}
  677 + context = mock()
  678 + context.stubs(:environment).returns(@environment)
  679 + Plugin1.new(context)
  680 + html = '
  681 + <div class="macro nonEdit" data-macro="test1" data-macro-param="123"></div>
  682 + <div class="macro nonEdit" data-macro="test2"></div>
  683 + <div class="macro nonEdit" data-macro="unexistent" data-macro-param="987"></div>
  684 + '
  685 + parsed_html = convert_macro(html, mock())
  686 + parsed_divs = Hpricot(parsed_html).search('div')
  687 + expected_divs = Hpricot('
  688 + <div data-macro="test1" class="parsed-macro test1">Test1</div>
  689 + <div data-macro="test2" class="parsed-macro test2">Test2</div>
  690 + <div data-macro="unexistent" class="failed-macro unexistent">Unsupported macro unexistent!</div>
  691 + ').search('div')
  692 +
  693 + # comparing div attributes between parsed and expected html
  694 + parsed_divs.each_with_index do |div, i|
  695 + assert_equal expected_divs[i].attributes.to_hash, div.attributes.to_hash
  696 + assert_equal expected_divs[i].inner_text, div.inner_text
  697 + end
  698 + end
  699 +
660 protected 700 protected
661 include NoosferoTestHelper 701 include NoosferoTestHelper
662 702
test/unit/comment_test.rb
@@ -571,6 +571,21 @@ class CommentTest &lt; ActiveSupport::TestCase @@ -571,6 +571,21 @@ class CommentTest &lt; ActiveSupport::TestCase
571 SpammerLogger.clean_log 571 SpammerLogger.clean_log
572 end 572 end
573 573
  574 + should 'count a thread of comments' do
  575 + Comment.delete_all
  576 + comments = []
  577 + comments.push(create_comment)
  578 + c1 = create_comment
  579 + comments.push(c1)
  580 + create_comment(:reply_of_id => c1.id)
  581 + create_comment(:reply_of_id => c1.id)
  582 + c2 = create_comment
  583 + comments.push(c2)
  584 + create_comment(:reply_of_id => c2.id)
  585 +
  586 + assert_equal 6, Comment.count_thread(comments)
  587 + end
  588 +
574 private 589 private
575 590
576 def create_comment(args = {}) 591 def create_comment(args = {})
test/unit/macros_helper_test.rb 0 → 100644
@@ -0,0 +1,124 @@ @@ -0,0 +1,124 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class MacrosHelperTest < ActiveSupport::TestCase
  4 + include MacrosHelper
  5 + include ApplicationHelper
  6 + include ActionView::Helpers::FormOptionsHelper
  7 + include ActionView::Helpers::FormTagHelper
  8 + include ActionView::Helpers::TagHelper
  9 +
  10 + CONFIG = {
  11 + :params => [
  12 + { :name => :identifier, :type => 'text'},
  13 + { :name => :size, :type => 'select',
  14 + :values => [
  15 + [_('Big'), :big],
  16 + [_('Icon'), :icon],
  17 + [_('Minor'), :minor],
  18 + [_('Portrait'), :portrait],
  19 + [_('Thumb'), :thumb]
  20 + ],
  21 + :default => :portrait
  22 + }
  23 + ],
  24 + :title => _('Profile Image Link')
  25 + }
  26 +
  27 + should 'generate html for macro configuration' do
  28 + @environment = Environment.default
  29 + Environment.macros = {}
  30 + macros = Environment.macros[@environment.id] = {}
  31 + plugin_instance = mock
  32 + plugin_instance.stubs('config_macro_example').returns(CONFIG)
  33 + macros['macro_example'] = plugin_instance
  34 + html = macro_configuration_dialog('macro_example')
  35 + assert_tag_in_string html, :tag => 'label', :content => _('Identifier')
  36 + assert_tag_in_string html, :tag => 'input', :attributes => {:name => 'identifier'}
  37 + assert_tag_in_string html, :tag => 'label', :content => 'size'.humanize
  38 + assert_tag_in_string html, :tag => 'select', :attributes => {:name => 'size'}, :descendant => {:tag => 'option', :attributes => {:value => 'big'}, :content => _('Big')}
  39 + assert_tag_in_string html, :tag => 'select', :attributes => {:name => 'size'}, :descendant => {:tag => 'option', :attributes => {:value => 'icon'}, :content => _('Icon')}
  40 + assert_tag_in_string html, :tag => 'select', :attributes => {:name => 'size'}, :descendant => {:tag => 'option', :attributes => {:value => 'minor'}, :content => _('Minor')}
  41 + assert_tag_in_string html, :tag => 'select', :attributes => {:name => 'size'}, :descendant => {:tag => 'option', :attributes => {:value => 'portrait', :selected => true}, :content => _('Portrait')}
  42 + assert_tag_in_string html, :tag => 'select', :attributes => {:name => 'size'}, :descendant => {:tag => 'option', :attributes => {:value => 'thumb'}, :content => _('Thumb')}
  43 + end
  44 +
  45 + should 'get macro title' do
  46 + @environment = Environment.default
  47 + Environment.macros = {}
  48 + macros = Environment.macros[@environment.id] = {}
  49 + plugin_instance = mock
  50 + plugin_instance.stubs('config_macro_example').returns(CONFIG)
  51 + macros['macro_example'] = plugin_instance
  52 + title = macro_title('macro_example')
  53 + assert _('Profile Image Link'), title
  54 + end
  55 +
  56 + should 'get only macros in menu' do
  57 + @environment = Environment.default
  58 + Environment.macros = {}
  59 + macros = Environment.macros[@environment.id] = {}
  60 + plugin_instance = mock
  61 + plugin_instance.stubs('config_macro_example').returns({})
  62 + macros['macro_example'] = plugin_instance
  63 + plugin_instance_other = mock
  64 + plugin_instance_other.stubs('config_macro_example_other').returns({:icon_path => 'icon.png'})
  65 + macros['macro_example_other'] = plugin_instance_other
  66 + assert_equal [plugin_instance], macros_in_menu.values
  67 + end
  68 +
  69 + should 'get only macros with buttons' do
  70 + @environment = Environment.default
  71 + Environment.macros = {}
  72 + macros = Environment.macros[@environment.id] = {}
  73 + plugin_instance = mock
  74 + plugin_instance.stubs('config_macro_example').returns({})
  75 + macros['macro_example'] = plugin_instance
  76 + plugin_instance_other = mock
  77 + plugin_instance_other.stubs('config_macro_example_other').returns({:icon_path => 'icon.png'})
  78 + macros['macro_example_other'] = plugin_instance_other
  79 + assert_equal [plugin_instance_other], macros_with_buttons.values
  80 + end
  81 +
  82 + should 'skip macro config dialog and call generator directly' do
  83 + @environment = Environment.default
  84 + Environment.macros = {}
  85 + macros = Environment.macros[@environment.id] = {}
  86 + plugin_instance = mock
  87 + plugin_instance.stubs('config_macro_example').returns({:skip_dialog => true, :generator => '', :params => [] })
  88 + macros['macro_example'] = plugin_instance
  89 + assert_equal 'function(){}', generate_macro_config_dialog('macro_example')
  90 + end
  91 +
  92 + should 'include js files' do
  93 + @environment = Environment.default
  94 + Environment.macros = {}
  95 + macros = Environment.macros[@environment.id] = {}
  96 + plugin_instance = mock
  97 + plugin_instance.stubs('config_macro_example').returns({:js_files => 'macro.js' })
  98 + plugin_instance.class.stubs(:public_path).with('macro.js').returns('macro.js')
  99 + macros['macro_example'] = plugin_instance
  100 + assert_equal '<script src="/javascripts/macro.js" type="text/javascript"></script>', include_macro_js_files
  101 + end
  102 +
  103 + should 'get macro css files' do
  104 + @environment = Environment.default
  105 + Environment.macros = {}
  106 + macros = Environment.macros[@environment.id] = {}
  107 + plugin_instance = mock
  108 + plugin_instance.stubs('config_macro_example').returns({:css_files => 'macro.css' })
  109 + plugin_instance.class.stubs(:public_path).with('macro.css').returns('macro.css')
  110 + macros['macro_example'] = plugin_instance
  111 + assert_equal 'macro.css', macro_css_files
  112 + end
  113 +
  114 + should 'get macro specific generator' do
  115 + @environment = Environment.default
  116 + Environment.macros = {}
  117 + macros = Environment.macros[@environment.id] = {}
  118 + plugin_instance = mock
  119 + plugin_instance.stubs('config_macro_example').returns({:generator => 'macro_generator' })
  120 + macros['macro_example'] = plugin_instance
  121 + assert_equal 'macro_generator', macro_generator('macro_example')
  122 + end
  123 +
  124 +end
test/unit/plugin_manager_test.rb
@@ -2,22 +2,12 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -2,22 +2,12 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
2 2
3 class PluginManagerTest < ActiveSupport::TestCase 3 class PluginManagerTest < ActiveSupport::TestCase
4 4
  5 + include Noosfero::Plugin::HotSpot
  6 +
5 def setup 7 def setup
6 @environment = Environment.default 8 @environment = Environment.default
7 - @controller = mock()  
8 - @controller.stubs(:profile).returns()  
9 - @controller.stubs(:request).returns()  
10 - @controller.stubs(:response).returns()  
11 - @controller.stubs(:params).returns()  
12 - @manager = Noosfero::Plugin::Manager.new(@environment, @controller)  
13 end 9 end
14 attr_reader :environment 10 attr_reader :environment
15 - attr_reader :manager  
16 -  
17 - should 'give access to environment and context' do  
18 - assert_same @environment, @manager.environment  
19 - assert_same @controller, @manager.context  
20 - end  
21 11
22 should 'return the intersection between environment\'s enabled plugins and system available plugins' do 12 should 'return the intersection between environment\'s enabled plugins and system available plugins' do
23 class Plugin1 < Noosfero::Plugin; end; 13 class Plugin1 < Noosfero::Plugin; end;
@@ -26,8 +16,8 @@ class PluginManagerTest &lt; ActiveSupport::TestCase @@ -26,8 +16,8 @@ class PluginManagerTest &lt; ActiveSupport::TestCase
26 class Plugin4 < Noosfero::Plugin; end; 16 class Plugin4 < Noosfero::Plugin; end;
27 environment.stubs(:enabled_plugins).returns([Plugin1.to_s, Plugin2.to_s, Plugin4.to_s]) 17 environment.stubs(:enabled_plugins).returns([Plugin1.to_s, Plugin2.to_s, Plugin4.to_s])
28 Noosfero::Plugin.stubs(:all).returns([Plugin1.to_s, Plugin3.to_s, Plugin4.to_s]) 18 Noosfero::Plugin.stubs(:all).returns([Plugin1.to_s, Plugin3.to_s, Plugin4.to_s])
29 - plugins = manager.enabled_plugins.map { |instance| instance.class.to_s }  
30 - assert_equal [Plugin1.to_s, Plugin4.to_s], plugins 19 + results = plugins.enabled_plugins.map { |instance| instance.class.to_s }
  20 + assert_equal [Plugin1.to_s, Plugin4.to_s], results
31 end 21 end
32 22
33 should 'map events to registered plugins' do 23 should 'map events to registered plugins' do
@@ -55,8 +45,62 @@ class PluginManagerTest &lt; ActiveSupport::TestCase @@ -55,8 +45,62 @@ class PluginManagerTest &lt; ActiveSupport::TestCase
55 p1 = Plugin1.new 45 p1 = Plugin1.new
56 p2 = Plugin2.new 46 p2 = Plugin2.new
57 47
58 - assert_equal [p1.random_event, p2.random_event], manager.dispatch(:random_event) 48 + assert_equal [p1.random_event, p2.random_event], plugins.dispatch(:random_event)
  49 + end
  50 +
  51 + should 'dispatch_first method returns the first plugin response if there is many plugins to responde the event' do
  52 +
  53 + class Plugin1 < Noosfero::Plugin
  54 + def random_event
  55 + 'Plugin 1 action.'
  56 + end
  57 + end
  58 +
  59 + class Plugin2 < Noosfero::Plugin
  60 + def random_event
  61 + 'Plugin 2 action.'
  62 + end
  63 + end
  64 +
  65 + class Plugin3 < Noosfero::Plugin
  66 + def random_event
  67 + 'Plugin 3 action.'
  68 + end
  69 + end
  70 +
  71 + environment.stubs(:enabled_plugins).returns([Plugin1.to_s, Plugin2.to_s, Plugin3.to_s])
  72 + p1 = Plugin1.new
  73 +
  74 + assert_equal p1.random_event, plugins.dispatch_first(:random_event)
59 end 75 end
60 76
  77 + should 'dispatch_first method returns the first plugin response if there is many plugins to responde the event and the first one respond nil' do
  78 +
  79 + class Plugin1 < Noosfero::Plugin
  80 + def random_event
  81 + nil
  82 + end
  83 + end
  84 +
  85 + class Plugin2 < Noosfero::Plugin
  86 + def random_event
  87 + 'Plugin 2 action.'
  88 + end
  89 + end
  90 +
  91 + class Plugin3 < Noosfero::Plugin
  92 + def random_event
  93 + 'Plugin 3 action.'
  94 + end
  95 + end
  96 +
  97 + environment.stubs(:enabled_plugins).returns([Plugin1.to_s, Plugin2.to_s, Plugin3.to_s])
  98 +
  99 + p2 = Plugin2.new
  100 +
  101 + assert_equal p2.random_event, plugins.dispatch_first(:random_event)
  102 + end
  103 +
  104 +
61 end 105 end
62 106
test/unit/plugin_test.rb
@@ -7,6 +7,8 @@ class PluginTest &lt; ActiveSupport::TestCase @@ -7,6 +7,8 @@ class PluginTest &lt; ActiveSupport::TestCase
7 end 7 end
8 attr_reader :environment 8 attr_reader :environment
9 9
  10 + include Noosfero::Plugin::HotSpot
  11 +
10 should 'keep the list of all loaded subclasses' do 12 should 'keep the list of all loaded subclasses' do
11 class Plugin1 < Noosfero::Plugin 13 class Plugin1 < Noosfero::Plugin
12 end 14 end
@@ -26,4 +28,59 @@ class PluginTest &lt; ActiveSupport::TestCase @@ -26,4 +28,59 @@ class PluginTest &lt; ActiveSupport::TestCase
26 assert_equal({:controller => 'plugin_test/plugin1_admin', :action => 'index'}, Plugin1.admin_url) 28 assert_equal({:controller => 'plugin_test/plugin1_admin', :action => 'index'}, Plugin1.admin_url)
27 end 29 end
28 30
  31 + should 'register its macros in the environment when instantiated' do
  32 + class Plugin1 < Noosfero::Plugin
  33 + def macro_example1(params, inner_html, source)
  34 + end
  35 +
  36 + def example2(params, inner_html, source)
  37 + end
  38 +
  39 + def not_macro
  40 + end
  41 +
  42 + def macro_methods
  43 + ['macro_example1', 'example2']
  44 + end
  45 + end
  46 +
  47 + Environment.macros = {}
  48 + Environment.macros[environment.id] = {}
  49 + macros = Environment.macros[environment.id]
  50 + context = mock()
  51 + context.stubs(:environment).returns(environment)
  52 +
  53 + plugin_instance = Plugin1.new(context)
  54 +
  55 + assert_equal plugin_instance, macros['macro_example1']
  56 + assert_equal plugin_instance, macros['example2']
  57 + assert_nil macros['not_macro']
  58 + end
  59 +
  60 + should 'load_comments return nil by default' do
  61 +
  62 + class Plugin1 < Noosfero::Plugin; end;
  63 +
  64 + environment.stubs(:enabled_plugins).returns([Plugin1.to_s])
  65 +
  66 + profile = fast_create(Profile, :name => 'test profile', :identifier => 'test_profile')
  67 + a = fast_create(Article, :name => 'my article', :profile_id => profile.id)
  68 + assert_nil plugins.dispatch_first(:load_comments, a)
  69 + end
  70 +
  71 + should 'load_comments return the value defined by plugin' do
  72 +
  73 + class Plugin1 < Noosfero::Plugin
  74 + def load_comments(page)
  75 + 'some value'
  76 + end
  77 + end
  78 +
  79 + environment.stubs(:enabled_plugins).returns([Plugin1.to_s])
  80 +
  81 + profile = fast_create(Profile, :name => 'test profile', :identifier => 'test_profile')
  82 + a = fast_create(Article, :name => 'my article', :profile_id => profile.id)
  83 + assert_equal 'some value', plugins.dispatch_first(:load_comments, a)
  84 + end
  85 +
29 end 86 end