Commit 45c48c56b08996e2eac67a828b1e0d84084c189b
1 parent
09892926
Exists in
staging
and in
42 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 | ... | ... |