Commit 45c48c56b08996e2eac67a828b1e0d84084c189b
1 parent
09892926
Exists in
master
and in
23 other branches
[tolerance-time] Hotspot to expire or remove article and comment buttons
Showing
15 changed files
with
184 additions
and
57 deletions
Show diff stats
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
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 < 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 < 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 | ... | ... |