Commit 45c48c56b08996e2eac67a828b1e0d84084c189b

Authored by Rodrigo Souto
1 parent 09892926

[tolerance-time] Hotspot to expire or remove article and comment buttons

app/helpers/application_helper.rb
... ... @@ -1329,4 +1329,17 @@ module ApplicationHelper
1329 1329 _("Are you sure that you want to remove the item \"#{article.name}\"?")
1330 1330 end
1331 1331 end
  1332 +
  1333 + def expirable_link_to(expired, content, url, options = {})
  1334 + if expired
  1335 + options[:class] = (options[:class] || '') + ' disabled'
  1336 + content_tag('a', ' '+content_tag('span', content), options)
  1337 + else
  1338 + link_to content, url, options
  1339 + end
  1340 + end
  1341 +
  1342 + def remove_content_button(action)
  1343 + @plugins.dispatch("content_remove_#{action.to_s}", @page).include?(true)
  1344 + end
1332 1345 end
... ...
app/helpers/cms_helper.rb
... ... @@ -42,13 +42,25 @@ module CmsHelper
42 42  
43 43 def display_spread_button(profile, article)
44 44 if profile.person?
45   - button_without_text :spread, _('Spread this'), :action => 'publish', :id => article.id
  45 + expirable_button article, :spread, _('Spread this'), :action => 'publish', :id => article.id
46 46 elsif profile.community? && environment.portal_community
47   - button_without_text :spread, _('Spread this'), :action => 'publish_on_portal_community', :id => article.id
  47 + expirable_button article, :spread, _('Spread this'), :action => 'publish_on_portal_community', :id => article.id
48 48 end
49 49 end
50 50  
51 51 def display_delete_button(article)
52   - button_without_text :delete, _('Delete'), { :action => 'destroy', :id => article.id }, :method => :post, :confirm => delete_article_message(article)
  52 + expirable_button article, :delete, _('Delete'), { :action => 'destroy', :id => article.id }, :method => :post, :confirm => delete_article_message(article)
  53 + end
  54 +
  55 + def expirable_button(content, action, title, url, options = {})
  56 + reason = @plugins.dispatch("content_expire_#{action.to_s}", content).first
  57 + if reason.present?
  58 + options[:class] = (options[:class] || '') + ' disabled'
  59 + options[:disabled] = 'disabled'
  60 + options.delete(:confirm)
  61 + options.delete(:method)
  62 + title = reason
  63 + end
  64 + button_without_text action.to_sym, title, url, options
53 65 end
54 66 end
... ...
app/helpers/content_viewer_helper.rb
... ... @@ -68,4 +68,20 @@ module ContentViewerHelper
68 68 end
69 69 end
70 70  
  71 + def expirable_content_reference(content, action, text, url, options = {})
  72 + reason = @plugins.dispatch("content_expire_#{action.to_s}", content).first
  73 + options[:title] = reason
  74 + expirable_link_to reason.present?, text, url, options
  75 + end
  76 +
  77 + def expirable_button(content, action, text, url, options = {})
  78 + options[:class] = "button with-text icon-#{action.to_s}"
  79 + expirable_content_reference content, action, text, url, options
  80 + end
  81 +
  82 + def expirable_comment_link(content, action, text, url, options = {})
  83 + options[:class] = "comment-footer comment-footer-link comment-footer-hide"
  84 + expirable_content_reference content, action, text, url, options
  85 + end
  86 +
71 87 end
... ...
app/models/comment.rb
... ... @@ -25,6 +25,8 @@ class Comment < ActiveRecord::Base
25 25  
26 26 xss_terminate :only => [ :body, :title, :name ], :on => 'validation'
27 27  
  28 + delegate :environment, :to => :source
  29 +
28 30 def action_tracker_target
29 31 self.article.profile
30 32 end
... ...
app/views/cms/view.rhtml
... ... @@ -49,13 +49,13 @@
49 49 <%= article.class.short_description %>
50 50 </td>
51 51 <td class="article-controls">
52   - <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => article.id %>
  52 + <%= expirable_button article, :edit, _('Edit'), {:action => 'edit', :id => article.id} if !remove_content_button(:edit) %>
53 53 <%= button_without_text :eyes, _('Public view'), article.view_url %>
54   - <%= display_spread_button(profile, article) unless article.folder? %>
55   - <% if !environment.enabled?('cant_change_homepage') %>
56   - <%= button_without_text :home, _('Use as homepage'), { :action => 'set_home_page', :id => article.id }, :method => :post %>
  54 + <%= display_spread_button(profile, article) unless article.folder? || remove_content_button(:spread)%>
  55 + <% if !environment.enabled?('cant_change_homepage') && !remove_content_button(:home) %>
  56 + <%= expirable_button article, :home, _('Use as homepage'), { :action => 'set_home_page', :id => article.id }, :method => :post %>
57 57 <% end %>
58   - <%= display_delete_button(article) %>
  58 + <%= display_delete_button(article) if !remove_content_button(:delete) %>
59 59 </td>
60 60 </tr>
61 61 <% end %>
... ...
app/views/content_viewer/_article_toolbar.rhtml
1 1 <div<%= user && " class='logged-in'" %>>
2 2 <div id="article-actions">
3 3  
4   - <% if @page.allow_edit?(user) %>
5   - <%= link_to content_tag( 'span', label_for_edit_article(@page) ),
6   - profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }),
7   - :class => 'button with-text icon-edit' %>
  4 +
  5 + <% if @page.allow_edit?(user) && !remove_content_button(:edit) %>
  6 + <% content = content_tag('span', label_for_edit_article(@page)) %>
  7 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }) %>
  8 + <%= expirable_button @page, :edit, content, url %>
8 9 <% end %>
9 10  
10   - <% if @page != profile.home_page && !@page.has_posts? && @page.allow_delete?(user) %>
11   - <%= link_to content_tag( 'span', _('Delete') ),
12   - profile.admin_url.merge({ :controller => 'cms', :action => 'destroy', :id => @page}),
13   - :method => :post,
14   - :class => 'button with-text icon-delete',
15   - :confirm => delete_article_message(@page) %>
  11 + <% if @page != profile.home_page && !@page.has_posts? && @page.allow_delete?(user) && !remove_content_button(:delete)%>
  12 + <% content = content_tag( 'span', _('Delete') ) %>
  13 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'destroy', :id => @page}) %>
  14 + <% options = {:method => :post, :confirm => delete_article_message(@page)} %>
  15 + <%= expirable_button @page, :delete, content, url, options %>
16 16 <% end %>
17 17  
18   - <% if !@page.folder? && @page.allow_spread?(user) %>
  18 + <% if !@page.folder? && @page.allow_spread?(user) && !remove_content_button(:spread) %>
  19 + <% content = content_tag( 'span', _('Spread this') ) %>
  20 + <% url = nil %>
19 21 <% if profile.kind_of?(Person) %>
20   - <%= link_to content_tag( 'span', _('Spread this') ),
21   - profile.admin_url.merge({ :controller => 'cms', :action => 'publish', :id => @page }),
22   - :class => 'button with-text icon-spread' %>
  22 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'publish', :id => @page }) %>
23 23 <% elsif profile.kind_of?(Community) && environment.portal_community %>
24   - <%= link_to content_tag( 'span', _('Spread this') ),
25   - profile.admin_url.merge({ :controller => 'cms', :action => 'publish_on_portal_community', :id => @page }),
26   - :class => 'button with-text icon-spread' %>
  24 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'publish_on_portal_community', :id => @page }) %>
27 25 <% end %>
  26 + <%= expirable_button @page, :spread, content, url if url %>
28 27 <% end %>
29 28  
30 29 <% if !@page.gallery? && @page.allow_create?(user) %>
31   - <%= link_to _('Add translation'),
32   - profile.admin_url.merge(:controller => 'cms', :action => 'new',
33   - :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)),
34   - :type => @page.type, :article => { :translation_of_id => @page.native_translation.id }),
35   - :class => 'button with-text icon-locale' if @page.translatable? && !@page.native_translation.language.blank? %>
  30 + <% if @page.translatable? && !@page.native_translation.language.blank? && !remove_content_button(:locale) %>
  31 + <% content = _('Add translation') %>
  32 + <% parent_id = (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)) %>
  33 + <% url = profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => parent_id, :type => @page.type, :article => { :translation_of_id => @page.native_translation.id })%>
  34 + <%= expirable_button @page, :locale, content, url %>
  35 + <% end %>
  36 +
36 37 <%= lightbox_remote_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) %>
37 38 <% end %>
38 39  
... ... @@ -40,8 +41,11 @@
40 41 <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) %>
41 42 <% end %>
42 43  
43   - <% if !@page.allow_create?(user) && profile.community? && (@page.blog? || @page.parent && @page.parent.blog?) %>
44   - <%= link_to content_tag( 'span', _('Suggest an article') ), profile.admin_url.merge({ :controller => 'cms', :action => 'suggest_an_article'}), :id => 'suggest-article-link', :class => 'button with-text icon-new' %>
  44 + <% if !@page.allow_create?(user) && profile.community? && (@page.blog? || @page.parent && @page.parent.blog?) && !remove_content_button(:suggest) %>
  45 + <% content = content_tag( 'span', _('Suggest an article') ) %>
  46 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'suggest_an_article'}) %>
  47 + <% options = {:id => 'suggest-article-link'} %>
  48 + <%= expirable_button @page, :suggest, content, url, options %>
45 49 <% end %>
46 50  
47 51 <%= report_abuse(profile, :link, @page) %>
... ...
app/views/content_viewer/_comment.rhtml
... ... @@ -57,6 +57,10 @@
57 57 </script>
58 58 <% end %>
59 59 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %>
  60 + <% if comment.author && comment.author == user %>
  61 + <%= expirable_comment_link comment, :edit, _('Edit'), {:action => 'edit_comment', :id => comment.id, :profile => profile.identifier} %>
  62 + <%= content_tag('span', ' | ', :class => 'comment-footer comment-footer-hide') %>
  63 + <% end %>
60 64 <%= link_to_function _('Reply'),
61 65 "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id,
62 66 :class => 'comment-footer comment-footer-link comment-footer-hide',
... ...
app/views/profile/_comment.rhtml
... ... @@ -46,6 +46,10 @@
46 46 </script>
47 47 <% end %>
48 48 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %>
  49 + <% if comment.author && comment.author == user %>
  50 + <%= expirable_comment_link comment, :edit, _('Edit'), {:action => 'edit_comment', :id => comment.id, :profile => profile.identifier} %>
  51 + <%= content_tag('span', ' | ', :class => 'comment-footer comment-footer-hide') %>
  52 + <% end %>
49 53 <%= link_to_function _('Reply'),
50 54 "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id,
51 55 :class => 'comment-footer comment-footer-link comment-footer-hide',
... ...
lib/noosfero/plugin.rb
... ... @@ -196,28 +196,6 @@ class Noosfero::Plugin
196 196 nil
197 197 end
198 198  
199   - # This is a generic hotspot for all controllers on Noosfero.
200   - # If any plugin wants to define filters to run on any controller, the name of
201   - # the hotspot must be in the following form: <underscored_controller_name>_filters.
202   - # Example: for ProfileController the hotspot is profile_controller_filters
203   - #
204   - # -> Adds a filter to a controller
205   - # returns = { :type => type,
206   - # :method_name => method_name,
207   - # :options => {:opt1 => opt1, :opt2 => opt2},
208   - # :block => Proc or lambda block}
209   - # type = 'before_filter' or 'after_filter'
210   - # method_name = The name of the filter
211   - # option = Filter options, like :only or :except
212   - # block = Block that the filter will call
213   - def method_missing(method, *args, &block)
214   - if method.to_s =~ /^(.+)_controller_filters$/
215   - []
216   - else
217   - super
218   - end
219   - end
220   -
221 199 # This method will be called just before a comment is saved to the database.
222 200 #
223 201 # It can modify the comment in several ways. In special, a plugin can call
... ... @@ -256,4 +234,40 @@ class Noosfero::Plugin
256 234 nil
257 235 end
258 236  
  237 + def method_missing(method, *args, &block)
  238 + # This is a generic hotspot for all controllers on Noosfero.
  239 + # If any plugin wants to define filters to run on any controller, the name of
  240 + # the hotspot must be in the following form: <underscored_controller_name>_filters.
  241 + # Example: for ProfileController the hotspot is profile_controller_filters
  242 + #
  243 + # -> Adds a filter to a controller
  244 + # returns = { :type => type,
  245 + # :method_name => method_name,
  246 + # :options => {:opt1 => opt1, :opt2 => opt2},
  247 + # :block => Proc or lambda block}
  248 + # type = 'before_filter' or 'after_filter'
  249 + # method_name = The name of the filter
  250 + # option = Filter options, like :only or :except
  251 + # block = Block that the filter will call
  252 + if method.to_s =~ /^(.+)_controller_filters$/
  253 + []
  254 + # -> Removes the action button from the content
  255 + # returns = boolean
  256 + elsif method.to_s =~ /^content_remove_(#{content_actions.join('|')})$/
  257 + nil
  258 + # -> Expire the action button from the content
  259 + # returns = string with reason of expiration
  260 + elsif method.to_s =~ /^content_expire_(#{content_actions.join('|')})$/
  261 + nil
  262 + else
  263 + super
  264 + end
  265 + end
  266 +
  267 + private
  268 +
  269 + def content_actions
  270 + %w[edit delete spread locale suggest home]
  271 + end
  272 +
259 273 end
... ...
public/designs/icons/tango/ie6.css
1 1 .msie6 .icon-edit { background-image: url(ie6/Tango/16x16/apps/text-editor.gif) }
2 2 .msie6 .icon-home { background-image: url(ie6/Tango/16x16/actions/go-home.gif) }
3   -.msie6 .icon-new { background-image: url(ie6/Tango/16x16/actions/filenew.gif) }
  3 +.msie6 .icon-new,
  4 +.msie6 .icon-suggest { background-image: url(ie6/Tango/16x16/actions/filenew.gif) }
4 5 .msie6 .icon-close { background-image: url(ie6/Tango/16x16/actions/gtk-cancel.gif) }
5 6 .msie6 .icon-newfolder { background-image: url(ie6/Tango/16x16/actions/folder-new.gif) }
6 7 .msie6 .icon-save { background-image: url(ie6/Tango/16x16/actions/filesave.gif) }
... ...
public/designs/icons/tango/style.css
... ... @@ -3,7 +3,8 @@
3 3 /******************SMALL ICONS********************/
4 4 .icon-edit { background-image: url(Tango/16x16/apps/text-editor.png) }
5 5 .icon-home { background-image: url(Tango/16x16/actions/go-home.png) }
6   -.icon-new { background-image: url(Tango/16x16/actions/filenew.png) }
  6 +.icon-new,
  7 +.icon-suggest { background-image: url(Tango/16x16/actions/filenew.png) }
7 8 .icon-close { background-image: url(Tango/16x16/actions/gtk-cancel.png) }
8 9 .icon-newfolder { background-image: url(Tango/16x16/actions/folder-new.png) }
9 10 .icon-folder { background-image: url(Tango/16x16/places/folder.png) }
... ...
public/designs/themes/base/style.css
... ... @@ -908,6 +908,16 @@ hr.pre-posts, hr.sep-posts {
908 908 #article-actions a.button:hover {
909 909 color: #555753;
910 910 }
  911 +#content a.disabled,
  912 +#content a.disabled:hover {
  913 + color: #888;
  914 + text-decoration: none;
  915 +}
  916 +#content a.button.disabled,
  917 +#content a.button.disabled:hover {
  918 + background-color: #CCC;
  919 + border-color: #CCC;
  920 +}
911 921  
912 922 #addThis {
913 923 text-align: right;
... ...
public/stylesheets/application.css
... ... @@ -1487,7 +1487,13 @@ a.button:hover, body.noosfero a.button:hover, input.button:hover, a.button.with-
1487 1487 body.noosfero a.button.with-text.icon-none, body.noosfero input.button.with-text.icon-none {
1488 1488 padding-left: 2px;
1489 1489 }
1490   -a.button.disabled, input.disabled {
  1490 +a.disabled{
  1491 + filter: url(filters.svg#grayscale); /* Firefox 3.5+ */
  1492 + filter: gray; /* IE6-9 */
  1493 + -webkit-filter: grayscale(1); /* Google Chrome & Safari 6+ */
  1494 + cursor: default;
  1495 +}
  1496 +input.disabled {
1491 1497 opacity: 0.5;
1492 1498 filter-opacity: 50%;
1493 1499 }
... ...
test/functional/content_viewer_controller_test.rb
... ... @@ -1414,4 +1414,36 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1414 1414  
1415 1415 end
1416 1416  
  1417 + should 'not display article actions button if any plugins says so' do
  1418 + class Plugin1 < Noosfero::Plugin
  1419 + def content_viewer_remove_edit(content); true; end
  1420 + end
  1421 + class Plugin2 < Noosfero::Plugin
  1422 + def content_viewer_remove_edit(content); false; end
  1423 + end
  1424 +
  1425 + environment.enable_plugin(Plugin1.name)
  1426 + environment.enable_plugin(Plugin2.name)
  1427 +
  1428 + login_as('testinguser')
  1429 + xhr :get, :view_page, :profile => 'testinguser', :page => [], :toolbar => true
  1430 + assert_no_tag :tag => 'div', :attributes => { :id => 'article-actions' }, :descendant => { :tag => 'a', :attributes => { :href => "/myprofile/testinguser/cms/edit/#{profile.home_page.id}" } }
  1431 + end
  1432 +
  1433 + should 'expire article actions button if any plugins says so' do
  1434 + class Plugin1 < Noosfero::Plugin
  1435 + def content_viewer_expire_edit(content); 'This button is expired.'; end
  1436 + end
  1437 + class Plugin2 < Noosfero::Plugin
  1438 + def content_viewer_expire_edit(content); nil; end
  1439 + end
  1440 +
  1441 + environment.enable_plugin(Plugin1.name)
  1442 + environment.enable_plugin(Plugin2.name)
  1443 +
  1444 + login_as('testinguser')
  1445 + xhr :get, :view_page, :profile => 'testinguser', :page => [], :toolbar => true
  1446 + assert_tag :tag => 'div', :attributes => { :id => 'article-actions' }, :descendant => { :tag => 'a', :attributes => { :href => "/myprofile/testinguser/cms/edit/#{profile.home_page.id}", :title => 'This button is expired.', :class => 'button with-text icon-edit disabled', :onclick => 'return false' } }
  1447 + end
  1448 +
1417 1449 end
... ...
test/unit/comment_test.rb
... ... @@ -378,4 +378,12 @@ class CommentTest &lt; ActiveSupport::TestCase
378 378 assert_not_nil article.activity
379 379 end
380 380  
  381 + should 'delegate environment to article' do
  382 + profile = fast_create(Profile, :environment_id => Environment.default)
  383 + article = fast_create(Article, :profile_id => profile.id)
  384 + comment = fast_create(Comment, :source_id => article.id, :source_type => 'Article')
  385 +
  386 + assert_equal Environment.default, comment.environment
  387 + end
  388 +
381 389 end
... ...