Commit c916579e2ebfddba26d985c3dff642dac103561b

Authored by Leandro Nunes dos Santos
2 parents 68fd5fb8 2e7d2cf5

Merge branch 'ActionItem2562' into ActionItem2520

app/controllers/public/comment_controller.rb
... ... @@ -160,6 +160,15 @@ class CommentController < ApplicationController
160 160 end
161 161 end
162 162 end
  163 +
  164 + #FIXME make this test
  165 + def check_actions
  166 + comment = profile.comments_received.find(params[:id])
  167 + ids = @plugins.dispatch(:check_comment_actions, comment).collect do |action|
  168 + action.kind_of?(Proc) ? self.instance_eval(&action) : action
  169 + end.flatten.compact
  170 + render :json => {:ids => ids}
  171 + end
163 172  
164 173 protected
165 174  
... ...
app/helpers/application_helper.rb
... ... @@ -30,6 +30,8 @@ module ApplicationHelper
30 30  
31 31 include AccountHelper
32 32  
  33 + include CommentHelper
  34 +
33 35 include BlogHelper
34 36  
35 37 include ContentViewerHelper
... ...
app/helpers/comment_helper.rb
... ... @@ -21,5 +21,48 @@ module CommentHelper
21 21 end
22 22 title
23 23 end
  24 +
  25 + def comment_actions(comment)
  26 + url = url_for(:profile => profile.identifier, :controller => :comment, :action => :check_actions, :id => comment.id)
  27 + links = links_for_comment_actions(comment)
  28 + content_tag(:li, link_to(content_tag(:span, _('Contents menu')), '#', :onclick => "toggleSubmenu(this,'',#{links.to_json}); return false", :class => 'menu-submenu-trigger comment-trigger', :url => url), :class=> 'vcard') unless links.empty?
  29 + end
  30 +
  31 + private
  32 +
  33 + def links_for_comment_actions(comment)
  34 + actions = [link_for_report_abuse(comment), link_for_spam(comment), link_for_edit(comment), link_for_remove(comment)]
  35 + @plugins.dispatch(:comment_actions, comment).collect do |action|
  36 + actions << (action.kind_of?(Proc) ? self.instance_eval(&action) : action)
  37 + end
  38 + actions.flatten.compact
  39 + end
  40 +
  41 + def link_for_report_abuse(comment)
  42 + if comment.author
  43 + report_abuse_link = report_abuse(comment.author, :comment_link, comment)
  44 + {:link => report_abuse_link} if report_abuse_link
  45 + end
  46 + end
  47 +
  48 + def link_for_spam(comment)
  49 + if comment.spam?
  50 + {:link => link_to_function(_('Mark as NOT SPAM'), 'remove_comment(this, %s); return false;' % url_for(:profile => profile.identifier, :mark_comment_as_ham => comment.id).to_json, :class => 'comment-footer comment-footer-link comment-footer-hide')}
  51 + elsif (logged_in? && (user == profile || user.has_permission?(:moderate_comments, profile)))
  52 + {:link => link_to_function(_('Mark as SPAM'), 'remove_comment(this, %s, %s); return false;' % [url_for(:profile => profile.identifier, :controller => 'comment', :action => :mark_as_spam, :id => comment.id).to_json, _('Are you sure you want to mark this comment as SPAM?').to_json], :class => 'comment-footer comment-footer-link comment-footer-hide')}
  53 + end
  54 + end
  55 +
  56 + def link_for_edit(comment)
  57 + if comment.author && comment.author == user
  58 + {:link => expirable_comment_link(comment, :edit, _('Edit'), url_for(:profile => profile.identifier, :controller => :comment, :action => :edit, :id => comment.id),:class => 'colorbox')}
  59 + end
  60 + end
  61 +
  62 + def link_for_remove(comment)
  63 + if logged_in? && (user == profile || user == comment.author || user.has_permission?(:moderate_comments, profile))
  64 + {:link => link_to_function(_('Remove'), 'remove_comment(this, %s, %s); return false ;' % [url_for(:profile => profile.identifier, :controller => 'comment', :action => :destroy, :id => comment.id).to_json, _('Are you sure you want to remove this comment and all its replies?').to_json], :class => 'comment-footer comment-footer-link comment-footer-hide remove-children')}
  65 + end
  66 + end
24 67  
25 68 end
... ...
app/views/comment/_comment.rhtml
... ... @@ -31,6 +31,21 @@
31 31 <% comment_balloon do %>
32 32  
33 33 <div class="comment-details">
  34 + <div class="comment-header">
  35 + <ul>
  36 + <div class="comment-actions">
  37 + <%= comment_actions(comment) %>
  38 + </div>
  39 + </ul>
  40 + <% unless comment.spam? %>
  41 + <%= link_to_function '',
  42 + "var f = add_comment_reply_form(this, %s); f.find('comment_title, textarea').val(''); return false" % comment.id,
  43 + :class => 'comment-footer comment-footer-link comment-footer-hide comment-actions-reply button',
  44 + :id => 'comment-reply-to-' + comment.id.to_s
  45 + %>
  46 + <% end %>
  47 + </div>
  48 +
34 49 <div class="comment-created-at">
35 50 <%= show_time(comment.created_at) %>
36 51 </div>
... ... @@ -41,7 +56,7 @@
41 56 </div>
42 57 </div>
43 58  
44   - <div class="comment_reply post_comment_box closed">
  59 + <div class="comment_reply post_comment_box closed" id="comment_reply_to_<%= comment.id %>">
45 60 <% if @comment && @comment.errors.any? && @comment.reply_of_id.to_i == comment.id %>
46 61 <%= error_messages_for :comment %>
47 62 <script type="text/javascript">
... ... @@ -51,36 +66,6 @@
51 66 });
52 67 </script>
53 68 <% end %>
54   - <%= report_abuse(comment.author, :comment_link, comment) if comment.author %>
55   -
56   - <% if comment.spam? %>
57   - &nbsp;
58   - <%= link_to_function(_('Mark as NOT SPAM'), 'remove_comment(this, %s); return false;' % url_for(:profile => profile.identifier, :mark_comment_as_ham => comment.id).to_json, :class => 'comment-footer comment-footer-link comment-footer-hide') %>
59   - <% else %>
60   - <% if (logged_in? && (user == profile || user.has_permission?(:moderate_comments, profile))) %>
61   - &nbsp;
62   - <%= link_to_function(_('Mark as SPAM'), 'remove_comment(this, %s, %s); return false;' % [url_for(:profile => profile.identifier, :controller => 'comment', :action => :mark_as_spam, :id => comment.id).to_json, _('Are you sure you want to mark this comment as SPAM?').to_json], :class => 'comment-footer comment-footer-link comment-footer-hide') %>
63   - <% end %>
64   - <% end %>
65   -
66   - <% if comment.author && comment.author == user %>
67   - &nbsp;
68   - <%= expirable_comment_link comment, :edit, _('Edit'), url_for(:profile => profile.identifier, :controller => :comment, :action => :edit, :id => comment.id),:class => 'colorbox' %>
69   - <% end %>
70   -
71   - <% if logged_in? && (user == profile || user == comment.author || user.has_permission?(:moderate_comments, profile)) %>
72   - &nbsp;
73   - <%= link_to_function(_('Remove'), 'remove_comment(this, %s, %s); return false ;' % [url_for(:profile => profile.identifier, :controller => 'comment', :action => :destroy, :id => comment.id).to_json, _('Are you sure you want to remove this comment and all its replies?').to_json], :class => 'comment-footer comment-footer-link comment-footer-hide remove-children') %>
74   - <% end %>
75   -
76   - <% unless comment.spam? %>
77   - &nbsp;
78   - <%= link_to_function _('Reply'),
79   - "var f = add_comment_reply_form(this, %s); f.find('comment_title, textarea').val(''); return false" % comment.id,
80   - :class => 'comment-footer comment-footer-link comment-footer-hide',
81   - :id => 'comment-reply-to-' + comment.id.to_s
82   - %>
83   - <% end %>
84 69 </div>
85 70  
86 71 <% end %>
... ...
config/plugins/comment_actions 0 → 120000
... ... @@ -0,0 +1 @@
  1 +/home/81665687568/projetos/noosfero_development/plugins/comment_actions
0 2 \ No newline at end of file
... ...
lib/noosfero/plugin.rb
... ... @@ -339,6 +339,30 @@ class Noosfero::Plugin
339 339 def comment_marked_as_ham(comment)
340 340 end
341 341  
  342 + # Adds extra actions for comments
  343 + # returns = list of hashes or lambda block that creates a list of hashes
  344 + # example:
  345 + #
  346 + # def comment_actions(comment)
  347 + # [{:link => link_to_function(...)}]
  348 + # end
  349 + #
  350 + def comment_actions(comment)
  351 + nil
  352 + end
  353 +
  354 + # This method is called when the user click on comment actions menu.
  355 + # returns = list or lambda block that return ids of enabled menu items for comments
  356 + # example:
  357 + #
  358 + # def check_comment_actions(comment)
  359 + # ['#action1', '#action2']
  360 + # end
  361 + #
  362 + def check_comment_actions(comment)
  363 + []
  364 + end
  365 +
342 366 # -> Adds fields to the signup form
343 367 # returns = lambda block that creates html code
344 368 def signup_extra_contents
... ...
public/javascripts/add-and-join.js
... ... @@ -100,4 +100,24 @@ jQuery(function($) {
100 100 clicked.parent().find(".send-an-email").fadeOut();
101 101 })
102 102 })
  103 +
  104 + $(".comment-trigger").live('click', function(){
  105 + clicked = $(this);
  106 + url = clicked.attr("url");
  107 + $.get(url, function(data){
  108 + ids = [];
  109 + if(data && data.ids) {
  110 + for(var i=0; i<data.ids.length; i++) {
  111 + clicked.parent().find(data.ids[i]).fadeIn();
  112 + ids.push(data.ids[i]);
  113 + }
  114 + }
  115 + clicked.parent().find('.comment-action-extra').each(function() {
  116 + if($.inArray('#'+$(this).attr('id'), ids))
  117 + $(this).fadeOut();
  118 + });
  119 + })
  120 + return false;
  121 + })
  122 +
103 123 });
... ...
public/javascripts/application.js
... ... @@ -314,11 +314,17 @@ function toggleSubmenu(trigger, title, link_list) {
314 314 content.append('<h4>' + title + '</h4>');
315 315 jQuery.each(link_list, function(index, link_hash) {
316 316 for (label in link_hash) {
317   - options = "";
318   - jQuery.each(link_hash[label], function(option, value){
319   - options += option +'="'+ value + '" ';
320   - })
321   - list.append('<li><a '+ options +'>' + label + '</a></li>');
  317 + if(link_hash[label]!=null) {
  318 + if(label=='link' && jQuery.type(link_hash[label])=="string") {
  319 + list.append('<li>' + link_hash[label] + '</li>');
  320 + } else {
  321 + options = "";
  322 + jQuery.each(link_hash[label], function(option, value){
  323 + options += option +'="'+ value + '" ';
  324 + })
  325 + list.append('<li><a '+ options +'>' + label + '</a></li>');
  326 + }
  327 + }
322 328 }
323 329 });
324 330 content.append(list);
... ... @@ -342,9 +348,9 @@ function hideAllSubmenus() {
342 348 // Hide visible ballons when clicked outside them
343 349 jQuery(document).ready(function() {
344 350 jQuery('body').live('click', function() { hideAllSubmenus(); });
345   - jQuery('.menu-submenu-trigger').click(function(e) { e.stopPropagation(); });
346   - jQuery('.simplemenu-trigger').click(function(e) { e.stopPropagation(); });
347   - jQuery('#chat-online-users').click(function(e) { e.stopPropagation(); });
  351 + jQuery('.menu-submenu-trigger').live('click', function(e) { e.stopPropagation(); });
  352 + jQuery('.simplemenu-trigger').live('click', function(e) { e.stopPropagation(); });
  353 + jQuery('#chat-online-users').live('click', function(e) { e.stopPropagation(); });
348 354 });
349 355  
350 356 function input_javascript_ordering_stuff() {
... ... @@ -710,8 +716,8 @@ jQuery(function($) {
710 716 });
711 717  
712 718 function add_comment_reply_form(button, comment_id) {
713   - var container = jQuery(button).parents('.comment_reply');
714   -
  719 + //var container = jQuery(button).parents('.comment_reply');
  720 + var container = jQuery('#comment_reply_to_'+comment_id);
715 721 var f = container.find('.comment_form');
716 722 if (f.length == 0) {
717 723 comments_div = jQuery(button).parents('.comments');
... ...
public/stylesheets/application.css
... ... @@ -1802,13 +1802,32 @@ a.button.disabled, input.disabled {
1802 1802 #content .profile-list-block ul {
1803 1803 width: 200px;
1804 1804 }
1805   -#content .common-profile-list-block li {
1806   - margin: 0px;
  1805 +#content .comment-header .comment-actions-reply {
  1806 + float: right;
  1807 + background-image: url(/designs/icons/tango/Tango/16x16/actions/go-jump.png);
  1808 + height: 12px;
  1809 +}
  1810 +#content .comment-header ul {
  1811 + float: right;
  1812 + margin: 1px 0px 14px 0px;
  1813 +}
  1814 +#content .comment-actions .menu-submenu-header, #content .comment-actions .menu-submenu-footer, #content .comment-actions .menu-submenu h4 {
  1815 + display: none;
  1816 +}
  1817 +#content .comment-actions .menu-submenu ul {
  1818 + border: 1px solid #888a85;
  1819 + background-color: #efefef;
  1820 + padding-right: 2px;
  1821 + height: auto;
  1822 + display: block;
  1823 +}
  1824 +#content .common-profile-list-block li, #content .comment-actions li {
  1825 + margin: 0px !important;
1807 1826 padding: 0px;
1808 1827 list-style: none;
1809 1828 position: relative;
1810 1829 }
1811   -.common-profile-list-block .vcard a {
  1830 +.common-profile-list-block .vcard a, .comment-actions .vcard a {
1812 1831 display: block;
1813 1832 height: 112px;
1814 1833 max-height: 112px;
... ... @@ -1819,7 +1838,7 @@ a.button.disabled, input.disabled {
1819 1838 text-align: center;
1820 1839 overflow: hidden;
1821 1840 font-size: 11px;
1822   - text-decoration: none;
  1841 + text-decoration: none !important;
1823 1842 color: #000;
1824 1843 position: relative;
1825 1844 cursor: pointer; /* work arround bug for MSIE */
... ... @@ -4575,11 +4594,17 @@ h1#agenda-title {
4575 4594 }
4576 4595 /* Profile balloon */
4577 4596  
4578   -.common-profile-list-block .vcard {
  4597 +.common-profile-list-block .vcard, .comment-actions .vcard {
4579 4598 position: relative !important;
4580 4599 float: left;
4581 4600 }
4582   -.common-profile-list-block .vcard .menu-submenu-trigger, .menu-submenu-trigger {
  4601 +#content .comment-actions .vcard {
  4602 + padding-right: 20px;
  4603 +}
  4604 +#content .comment-actions .vcard .menu-submenu-trigger {
  4605 + display: block;
  4606 +}
  4607 +.common-profile-list-block .vcard .menu-submenu-trigger, .menu-submenu-trigger, .comment-actions .vcard .menu-submenu-trigger {
4583 4608 display: none;
4584 4609 width: 16px;
4585 4610 height: 16px;
... ... @@ -4593,7 +4618,7 @@ h1#agenda-title {
4593 4618 -moz-border-radius: 5px;
4594 4619 -webkit-border-radius: 5px;
4595 4620 }
4596   -.common-profile-list-block .vcard .menu-submenu-trigger:hover, .menu-submenu-trigger:hover {
  4621 +.common-profile-list-block .vcard .menu-submenu-trigger:hover, .menu-submenu-trigger:hover, .comment-actions .vcard .menu-submenu-trigger:hover {
4597 4622 background: #fff url(/images/top-arrow.png) center center no-repeat;
4598 4623 border: 1px solid #ccc;
4599 4624 }
... ... @@ -4612,6 +4637,10 @@ h1#agenda-title {
4612 4637 padding: 0;
4613 4638 text-align: left;
4614 4639 }
  4640 +.comment-details .menu-submenu {
  4641 + bottom: 0px;
  4642 + right: -134px;
  4643 +}
4615 4644 .box-2 .menu-submenu, .box-3 .menu-submenu {
4616 4645 bottom: 78px;
4617 4646 right: -44px;
... ... @@ -4646,7 +4675,7 @@ h1#agenda-title {
4646 4675 .msie7 #search-results .menu-submenu-trigger {
4647 4676 width: 20px !important;
4648 4677 }
4649   -.common-profile-list-block .vcard .menu-submenu a {
  4678 +.common-profile-list-block .vcard .menu-submenu a, .comment-actions .vcard .menu-submenu a {
4650 4679 float: none;
4651 4680 display: block;
4652 4681 height: auto;
... ...
test/functional/comment_controller_test.rb
... ... @@ -493,7 +493,7 @@ class CommentControllerTest &lt; ActionController::TestCase
493 493 assert_equal 'Comment edited', Comment.find(comment.id).body
494 494 end
495 495  
496   - should 'not crash on update comment if comment does not exist' do
  496 + should 'not crash on update comment if comment does not exist' do
497 497 login_as profile.identifier
498 498 page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
499 499  
... ... @@ -501,4 +501,18 @@ class CommentControllerTest &lt; ActionController::TestCase
501 501 assert_response 404
502 502 end
503 503  
  504 + should 'returns ids of menu items that has to be displayed' do
  505 + class TestActionPlugin < Noosfero::Plugin
  506 + def check_comment_actions(c)
  507 + ['action1', 'action2']
  508 + end
  509 + end
  510 + Noosfero::Plugin::Manager.any_instance.stubs(:enabled_plugins).returns([TestActionPlugin.new])
  511 + login_as profile.identifier
  512 + page = profile.articles.create!(:name => 'myarticle', :body => 'the body of the text')
  513 + comment = fast_create(Comment, :body => 'Original comment', :source_id => page.id, :source_type => 'Article')
  514 + xhr :post, :check_actions, :profile => profile.identifier, :id => comment.id
  515 + assert_match /\{\"ids\":\[\"action1\",\"action2\"\]\}/, @response.body
  516 + end
  517 +
504 518 end
... ...
test/functional/content_viewer_controller_test.rb
... ... @@ -1047,7 +1047,7 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1047 1047 comment.save!
1048 1048 login_as 'testuser'
1049 1049 get :view_page, :profile => 'testuser', :page => [ 'test' ]
1050   - assert_tag :tag => 'a', :attributes => { :class => /comment-footer-link/ }, :content => 'Reply'
  1050 + assert_tag :tag => 'a', :attributes => { :class => /comment-actions-reply/ }
1051 1051 end
1052 1052  
1053 1053 should 'display reply to comment button if not authenticated' do
... ... @@ -1057,7 +1057,7 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1057 1057 comment = article.comments.build(:author => profile, :title => 'a comment', :body => 'lalala')
1058 1058 comment.save!
1059 1059 get :view_page, :profile => 'testuser', :page => [ 'test' ]
1060   - assert_tag :tag => 'a', :attributes => { :class => /comment-footer-link/ }, :content => 'Reply'
  1060 + assert_tag :tag => 'a', :attributes => { :class => /comment-actions-reply/ }
1061 1061 end
1062 1062  
1063 1063 should 'display replies if comment has replies' do
... ...
test/unit/comment_helper_test.rb 0 → 100644
... ... @@ -0,0 +1,104 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class CommentHelperTest < ActiveSupport::TestCase
  4 +
  5 + include CommentHelper
  6 + include ActionView::Helpers::TagHelper
  7 + include NoosferoTestHelper
  8 +
  9 + def setup
  10 + @user = create_user('usertest').person
  11 + @profile = @user
  12 + self.stubs(:logged_in?).returns(true)
  13 + self.stubs(:report_abuse).returns('<a href="#">link</a>')
  14 + self.stubs(:expirable_comment_link).returns('<a href="#">link</a>')
  15 + @plugins.stubs(:dispatch).returns([])
  16 + end
  17 +
  18 + attr_reader :user, :profile
  19 +
  20 + should 'show menu if it has links for actions' do
  21 + comment = Comment.new
  22 + menu = comment_actions(comment)
  23 + assert menu
  24 + end
  25 +
  26 + should 'do not show menu if it has no actions' do
  27 + comment = Comment.new
  28 + self.stubs(:links_for_comment_actions).returns([])
  29 + menu = comment_actions(comment)
  30 + assert !menu
  31 + end
  32 +
  33 + should 'do not show menu if it has nil actions only' do
  34 + comment = Comment.new
  35 + self.stubs(:link_for_report_abuse).returns(nil)
  36 + self.stubs(:link_for_spam).returns(nil)
  37 + self.stubs(:link_for_edit).returns(nil)
  38 + self.stubs(:link_for_remove).returns(nil)
  39 + menu = comment_actions(comment)
  40 + assert !menu
  41 + end
  42 +
  43 + should 'include actions of plugins in menu' do
  44 + comment = Comment.new
  45 + plugin_action = {:link => 'plugin_action'}
  46 + @plugins.stubs(:dispatch).returns([plugin_action])
  47 + links = links_for_comment_actions(comment)
  48 + assert_includes links, plugin_action
  49 + end
  50 +
  51 + should 'include lambda actions of plugins in menu' do
  52 + comment = Comment.new
  53 + plugin_action = lambda{[{:link => 'plugin_action'}, {:link => 'plugin_action2'}]}
  54 + @plugins.stubs(:dispatch).returns([plugin_action])
  55 + links = links_for_comment_actions(comment)
  56 + assert_includes links, {:link => 'plugin_action'}
  57 + assert_includes links, {:link => 'plugin_action2'}
  58 + end
  59 +
  60 + should 'return link for report abuse action when comment has a author' do
  61 + comment = Comment.new
  62 + comment.author = user
  63 + link = link_for_report_abuse(comment)
  64 + assert link
  65 + end
  66 +
  67 + should 'do not return link for report abuse action when comment has no author' do
  68 + comment = Comment.new
  69 + link = link_for_report_abuse(comment)
  70 + assert !link
  71 + end
  72 +
  73 + should 'return link for mark comment as spam' do
  74 + comment = Comment.new
  75 + link = link_for_spam(comment)
  76 + assert_match /Mark as SPAM/, link[:link]
  77 + end
  78 +
  79 + should 'return link for mark comment as not spam' do
  80 + comment = Comment.new
  81 + comment.spam = true
  82 + link = link_for_spam(comment)
  83 + assert_match /Mark as NOT SPAM/, link[:link]
  84 + end
  85 +
  86 + should 'do not return link for edit comment' do
  87 + comment = Comment.new
  88 + link = link_for_edit(comment)
  89 + assert !link
  90 + end
  91 +
  92 + should 'return link for edit comment' do
  93 + comment = Comment.new
  94 + comment.author = user
  95 + link = link_for_edit(comment)
  96 + assert link
  97 + end
  98 +
  99 + def link_to_function(content, url, options = {})
  100 + link_to(content, url, options)
  101 + end
  102 +
  103 +end
  104 +
... ...