Commit 825c88ca01d079a8f08773c2f8a6d53246d7e410
1 parent
36553bc6
Exists in
master
and in
29 other branches
Create FilePresenter infra
The idea is never to give a Upload File to a view. That must always work with a File Presenter providing overwrite methods and specific views/partials. ActionItem2373
Showing
16 changed files
with
275 additions
and
69 deletions
Show diff stats
app/controllers/application_controller.rb
1 | class ApplicationController < ActionController::Base | 1 | class ApplicationController < ActionController::Base |
2 | 2 | ||
3 | + # Preload FilePresenters to allow `FilePresenter.for()` to work | ||
4 | + FilePresenter::Generic | ||
5 | + FilePresenter::Image | ||
6 | + | ||
3 | before_filter :setup_multitenancy | 7 | before_filter :setup_multitenancy |
4 | before_filter :detect_stuff_by_domain | 8 | before_filter :detect_stuff_by_domain |
5 | before_filter :init_noosfero_plugins | 9 | before_filter :init_noosfero_plugins |
app/controllers/public/content_viewer_controller.rb
@@ -55,7 +55,9 @@ class ContentViewerController < ApplicationController | @@ -55,7 +55,9 @@ class ContentViewerController < ApplicationController | ||
55 | # At this point the page will be showed | 55 | # At this point the page will be showed |
56 | @page.hit | 56 | @page.hit |
57 | 57 | ||
58 | - unless @page.mime_type == 'text/html' || (@page.image? && params[:view]) | 58 | + @page = FilePresenter.for @page |
59 | + | ||
60 | + unless @page.mime_type == 'text/html' || params[:view] | ||
59 | headers['Content-Type'] = @page.mime_type | 61 | headers['Content-Type'] = @page.mime_type |
60 | data = @page.data | 62 | data = @page.data |
61 | 63 |
app/helpers/folder_helper.rb
@@ -21,6 +21,7 @@ module FolderHelper | @@ -21,6 +21,7 @@ module FolderHelper | ||
21 | end | 21 | end |
22 | 22 | ||
23 | def display_article_in_listing(article, recursive = false, level = 0) | 23 | def display_article_in_listing(article, recursive = false, level = 0) |
24 | + article = FilePresenter.for article | ||
24 | article_link = if article.image? | 25 | article_link = if article.image? |
25 | link_to(' ' * (level * 4) + image_tag(icon_for_article(article)) + short_filename(article.name), article.url.merge(:view => true)) | 26 | link_to(' ' * (level * 4) + image_tag(icon_for_article(article)) + short_filename(article.name), article.url.merge(:view => true)) |
26 | else | 27 | else |
@@ -40,12 +41,12 @@ module FolderHelper | @@ -40,12 +41,12 @@ module FolderHelper | ||
40 | end | 41 | end |
41 | 42 | ||
42 | def icon_for_article(article) | 43 | def icon_for_article(article) |
43 | - icon = article.class.icon_name(article) | 44 | + icon = article.icon_name || article.class.icon_name(article) |
44 | if (icon =~ /\//) | 45 | if (icon =~ /\//) |
45 | icon | 46 | icon |
46 | else | 47 | else |
47 | - klasses = 'icon icon-' + icon | ||
48 | - if article.kind_of?(UploadedFile) | 48 | + klasses = 'icon ' + [icon].flatten.map{|name| 'icon-'+name}.join(' ') |
49 | + if article.kind_of?(UploadedFile) || article.kind_of?(FilePresenter) | ||
49 | klasses += ' icon-upload-file' | 50 | klasses += ' icon-upload-file' |
50 | end | 51 | end |
51 | klasses | 52 | klasses |
app/models/article.rb
@@ -133,8 +133,12 @@ class Article < ActiveRecord::Base | @@ -133,8 +133,12 @@ class Article < ActiveRecord::Base | ||
133 | end | 133 | end |
134 | end | 134 | end |
135 | 135 | ||
136 | + def css_class_list | ||
137 | + [self.class.name.underscore.dasherize] | ||
138 | + end | ||
139 | + | ||
136 | def css_class_name | 140 | def css_class_name |
137 | - self.class.name.underscore.dasherize | 141 | + [css_class_list].flatten.compact.join(' ') |
138 | end | 142 | end |
139 | 143 | ||
140 | def pending_categorizations | 144 | def pending_categorizations |
@@ -314,7 +318,7 @@ class Article < ActiveRecord::Base | @@ -314,7 +318,7 @@ class Article < ActiveRecord::Base | ||
314 | end | 318 | end |
315 | 319 | ||
316 | def view_url | 320 | def view_url |
317 | - @view_url ||= image? ? url.merge(:view => true) : url | 321 | + @view_url ||= is_a?(UploadedFile) ? url.merge(:view => true) : url |
318 | end | 322 | end |
319 | 323 | ||
320 | def allow_children? | 324 | def allow_children? |
app/models/uploaded_file.rb
@@ -61,11 +61,13 @@ class UploadedFile < Article | @@ -61,11 +61,13 @@ class UploadedFile < Article | ||
61 | postgresql_attachment_fu | 61 | postgresql_attachment_fu |
62 | 62 | ||
63 | def self.icon_name(article = nil) | 63 | def self.icon_name(article = nil) |
64 | - if article | ||
65 | - article.image? ? article.public_filename(:icon) : (article.mime_type ? article.mime_type.gsub(/[\/+.]/, '-') : 'upload-file') | ||
66 | - else | ||
67 | - 'upload-file' | ||
68 | - end | 64 | + warn = ('='*80) + "\n" + |
65 | + 'The method `UploadedFile.icon_name(obj)` is deprecated. ' + | ||
66 | + 'You must to encapsulate UploadedFile with `FilePresenter.for()`.' + | ||
67 | + "\n" + ('='*80) | ||
68 | + Rails.logger.warn warn if Rails.logger | ||
69 | + puts warn | ||
70 | + 'upload-file' | ||
69 | end | 71 | end |
70 | 72 | ||
71 | def mime_type | 73 | def mime_type |
@@ -91,40 +93,26 @@ class UploadedFile < Article | @@ -91,40 +93,26 @@ class UploadedFile < Article | ||
91 | end | 93 | end |
92 | 94 | ||
93 | def to_html(options = {}) | 95 | def to_html(options = {}) |
96 | + warn = ('='*80) + "\n" + | ||
97 | + 'The method `UploadedFile::to_html()` is deprecated. ' + | ||
98 | + 'You must to encapsulate UploadedFile with `FilePresenter.for()`.' + | ||
99 | + "\n" + ('='*80) | ||
100 | + Rails.logger.warn warn if Rails.logger | ||
101 | + puts warn | ||
94 | article = self | 102 | article = self |
95 | if image? | 103 | if image? |
96 | lambda do | 104 | lambda do |
97 | - if article.gallery? && options[:gallery_view] | ||
98 | - images = article.parent.images | ||
99 | - current_index = images.index(article) | ||
100 | - total_of_images = images.count | ||
101 | - | ||
102 | - link_to_previous = if current_index >= 1 | ||
103 | - link_to(_('« Previous'), images[current_index - 1].view_url, :class => 'left') | ||
104 | - else | ||
105 | - content_tag('span', _('« Previous'), :class => 'left') | ||
106 | - end | ||
107 | - | ||
108 | - link_to_next = if current_index < total_of_images - 1 | ||
109 | - link_to(_('Next »'), images[current_index + 1].view_url, :class => 'right') | ||
110 | - else | ||
111 | - content_tag('span', _('Next »'), :class => 'right') | ||
112 | - end | ||
113 | - | ||
114 | - content_tag( | ||
115 | - 'div', | ||
116 | - link_to_previous + (content_tag('span', _('image %d of %d'), :class => 'total-of-images') % [current_index + 1, total_of_images]).html_safe + link_to_next, | ||
117 | - :class => 'gallery-navigation' | ||
118 | - ) | ||
119 | - end.to_s + | ||
120 | - image_tag(article.public_filename(:display), :class => article.css_class_name, :style => 'max-width: 100%') + | ||
121 | - content_tag('p', article.abstract, :class => 'uploaded-file-description') | ||
122 | - | 105 | + image_tag(article.public_filename(:display), |
106 | + :class => article.css_class_name, | ||
107 | + :style => 'max-width: 100%') + | ||
108 | + content_tag('div', article.abstract, :class => 'uploaded-file-description') | ||
123 | end | 109 | end |
124 | else | 110 | else |
125 | lambda do | 111 | lambda do |
126 | - content_tag('ul', content_tag('li', link_to(article.name, article.url, :class => article.css_class_name))) + | ||
127 | - content_tag('p', article.abstract, :class => 'uploaded-file-description') | 112 | + content_tag('div', |
113 | + link_to(article.name, article.url), | ||
114 | + :class => article.css_class_name) + | ||
115 | + content_tag('div', article.abstract, :class => 'uploaded-file-description') | ||
128 | end | 116 | end |
129 | end | 117 | end |
130 | end | 118 | end |
@@ -0,0 +1,9 @@ | @@ -0,0 +1,9 @@ | ||
1 | +<span class="download-link"> | ||
2 | + <span>Download</span> | ||
3 | + <strong><%= link_to generic.filename, generic.public_filename %></strong> | ||
4 | +</span> | ||
5 | + | ||
6 | +<div class="uploaded-file-description <%= 'empty' if generic.abstract.blank? %>"> | ||
7 | + <%= generic.abstract %> | ||
8 | +</div> | ||
9 | + |
@@ -0,0 +1,37 @@ | @@ -0,0 +1,37 @@ | ||
1 | +<% if image.gallery? && options[:gallery_view] %> | ||
2 | +<% | ||
3 | + images = image.parent.images | ||
4 | + current_index = images.index(image.encapsulated_file) | ||
5 | + total_of_images = images.count | ||
6 | + link_to_previous = if current_index >= 1 | ||
7 | + link_to(_('« Previous'), images[current_index - 1].view_url, :class => 'previous') | ||
8 | + else | ||
9 | + content_tag('span', _('« Previous'), :class => 'previous') | ||
10 | + end | ||
11 | + | ||
12 | + link_to_next = if current_index < total_of_images - 1 | ||
13 | + link_to(_('Next »'), images[current_index + 1].view_url, :class => 'next') | ||
14 | + else | ||
15 | + content_tag('span', _('Next »'), :class => 'next') | ||
16 | + end | ||
17 | +%> | ||
18 | + | ||
19 | +<div class="gallery-navigation"> | ||
20 | + <%= link_to_previous %> | ||
21 | + <span class="total-of-images"> | ||
22 | + <%= _('image %d of %d') % [current_index + 1, total_of_images] %> | ||
23 | + </span> | ||
24 | + <%= link_to_next %> | ||
25 | +</div> | ||
26 | + | ||
27 | +<% end %> | ||
28 | + | ||
29 | +<%# image_tag(article.public_filename(:display), :class => article.css_class_name, :style => 'max-width: 100%') %> | ||
30 | + | ||
31 | +<img src="<%=image.public_filename(:display)%>" class="<%=image.css_class_name%>"> | ||
32 | + | ||
33 | +<div class="uploaded-file-description <%= 'empty' if image.abstract.blank? %>"> | ||
34 | + <%= image.class %> | ||
35 | + <%= image.abstract %> | ||
36 | +</div> | ||
37 | + |
@@ -0,0 +1,90 @@ | @@ -0,0 +1,90 @@ | ||
1 | +# All file presenters must extends `FilePresenter` not only to ensure the | ||
2 | +# same interface, but also to make `FilePresenter.for(file)` to work. | ||
3 | +class FilePresenter | ||
4 | + | ||
5 | + # Will return a encapsulated `UploadedFile` or the same object if no | ||
6 | + # one accepts it. That behave allow to give any model to this class, | ||
7 | + # like a Article and have no trouble with that. | ||
8 | + def self.for(f) | ||
9 | + return f if f.is_a? FilePresenter | ||
10 | + klass = FilePresenter.subclasses.sort_by {|class_name| | ||
11 | + class_name.constantize.accepts?(f) || 0 | ||
12 | + }.last.constantize | ||
13 | + klass.accepts?(f) ? klass.new(f) : f | ||
14 | + end | ||
15 | + | ||
16 | + def initialize(f) | ||
17 | + @file = f | ||
18 | + end | ||
19 | + | ||
20 | + # Allows to use the original `UploadedFile` reference. | ||
21 | + def encapsulated_file | ||
22 | + @file | ||
23 | + end | ||
24 | + | ||
25 | + # This method must be overridden in subclasses. | ||
26 | + # | ||
27 | + # If the class accepts the file, return a number that represents the | ||
28 | + # priority the class should be given to handle that file. Higher numbers | ||
29 | + # mean higher priority. | ||
30 | + # | ||
31 | + # If the class does not accept the file, return false. | ||
32 | + def self.accepts?(f) | ||
33 | + nil | ||
34 | + end | ||
35 | + | ||
36 | + # Define the css classes to style the page fragment with the file related | ||
37 | + # content. If you want other classes to identify this area to your | ||
38 | + # customized presenter, so do this: | ||
39 | + # def css_class_list | ||
40 | + # [super, 'myclass'].flatten | ||
41 | + # end | ||
42 | + def css_class_list | ||
43 | + [ @file.css_class_list, | ||
44 | + 'file-' + self.class.to_s.split(/:+/).map(&:underscore)[1..-1].join('-'), | ||
45 | + 'content-type_' + self.content_type.split('/')[0], | ||
46 | + 'content-type_' + self.content_type.gsub(/[^a-z0-9]/i,'-') | ||
47 | + ].flatten | ||
48 | + end | ||
49 | + | ||
50 | + # Enable file presenter to customize the css classes on view_page.rhtml | ||
51 | + # You may not overwrite this method on your customized presenter. | ||
52 | + def css_class_name | ||
53 | + [css_class_list].flatten.compact.join(' ') | ||
54 | + end | ||
55 | + | ||
56 | + # The generic icon class-name or the specific file path. | ||
57 | + # You may replace this method on your custom FilePresenter. | ||
58 | + # See the current used icons class-names in public/designs/icons/tango/style.css | ||
59 | + def icon_name | ||
60 | + if mime_type | ||
61 | + [ mime_type.split('/')[0], mime_type.gsub(/[^a-z0-9]/i, '-') ] | ||
62 | + else | ||
63 | + 'upload-file' | ||
64 | + end | ||
65 | + end | ||
66 | + | ||
67 | + # Automatic render `file_presenter/<custom>.html.erb` to display your | ||
68 | + # custom presenter html content. | ||
69 | + # You may not overwrite this method on your customized presenter. | ||
70 | + # A variable with the same presenter name will be created to refer | ||
71 | + # to the file object. | ||
72 | + # Example: | ||
73 | + # The `FilePresenter::Image` render `file_presenter/image.html.erb` | ||
74 | + # inside the `file_presenter/image.html.erb` you can access the | ||
75 | + # required `FilePresenter::Image` instance in the `image` variable. | ||
76 | + def to_html(options = {}) | ||
77 | + file = self | ||
78 | + lambda do | ||
79 | + render :partial => file.class.to_s.underscore, | ||
80 | + :locals => { :options => options }, | ||
81 | + :object => file | ||
82 | + end | ||
83 | + end | ||
84 | + | ||
85 | + # That makes the presenter to works like any other `UploadedFile` instance. | ||
86 | + def method_missing(m, *args) | ||
87 | + @file.send(m, *args) | ||
88 | + end | ||
89 | + | ||
90 | +end |
@@ -0,0 +1,13 @@ | @@ -0,0 +1,13 @@ | ||
1 | +class Html5VideoPlugin < Noosfero::Plugin | ||
2 | + | ||
3 | + FilePresenter::Video | ||
4 | + | ||
5 | + def self.plugin_name | ||
6 | + "HTML5 Video" | ||
7 | + end | ||
8 | + | ||
9 | + def self.plugin_description | ||
10 | + _("A plugin to enable the video suport, with auto conversion for the web.") | ||
11 | + end | ||
12 | + | ||
13 | +end |
plugins/html5_video/views/file_presenter/_video.html.erb
0 → 100644
@@ -0,0 +1,8 @@ | @@ -0,0 +1,8 @@ | ||
1 | +<video class="video-js vjs-default-skin" controls poster="video.jpg" preload="auto" data-setup="{}"> | ||
2 | + <source type="video/ogg" src="<%= video.public_filename %>"> | ||
3 | +</video> | ||
4 | + | ||
5 | +<div class="uploaded-file-description <%= 'empty' if video.abstract.blank? %>"> | ||
6 | + <%= video.abstract %> | ||
7 | +</div> | ||
8 | + |
public/designs/icons/tango/style.css
@@ -80,7 +80,7 @@ | @@ -80,7 +80,7 @@ | ||
80 | .icon-application-postscript { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-postscript.png) } | 80 | .icon-application-postscript { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-postscript.png) } |
81 | .icon-application-pdf { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-pdf.png) } | 81 | .icon-application-pdf { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-pdf.png) } |
82 | .icon-application-ogg { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-ogg.png) } | 82 | .icon-application-ogg { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-ogg.png) } |
83 | -.icon-video-mpeg { background-image: url(Tango/16x16/mimetypes/video-x-generic.png) } | 83 | +.icon-video, .icon-video-mpeg { background-image: url(Tango/16x16/mimetypes/video-x-generic.png) } |
84 | .icon-application-vnd-oasis-opendocument-text { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.text.png) } | 84 | .icon-application-vnd-oasis-opendocument-text { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.text.png) } |
85 | .icon-application-vnd-oasis-opendocument-spreadsheet { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.spreadsheet.png) } | 85 | .icon-application-vnd-oasis-opendocument-spreadsheet { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.spreadsheet.png) } |
86 | .icon-application-vnd-oasis-opendocument-presentation { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.presentation.png) } | 86 | .icon-application-vnd-oasis-opendocument-presentation { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.presentation.png) } |
public/designs/themes/base/style.css
@@ -822,12 +822,6 @@ div#notice { | @@ -822,12 +822,6 @@ div#notice { | ||
822 | margin-bottom: 0px; | 822 | margin-bottom: 0px; |
823 | } | 823 | } |
824 | 824 | ||
825 | -X.sep { | ||
826 | - background: url(imgs/blog-sep.png) 50% 50% repeat-x; | ||
827 | - padding: 10px 20px; | ||
828 | - margin: 10px -19px; | ||
829 | -} | ||
830 | - | ||
831 | /* ==> search-results.css <== */ | 825 | /* ==> search-results.css <== */ |
832 | 826 | ||
833 | 827 | ||
@@ -1019,6 +1013,46 @@ hr.pre-posts, hr.sep-posts { | @@ -1019,6 +1013,46 @@ hr.pre-posts, hr.sep-posts { | ||
1019 | background: #FFF; | 1013 | background: #FFF; |
1020 | } | 1014 | } |
1021 | 1015 | ||
1016 | +/************* uploaded file *****************/ | ||
1017 | + | ||
1018 | +#article .gallery-navigation { | ||
1019 | + padding: 10px 0; | ||
1020 | +} | ||
1021 | + | ||
1022 | +#article .gallery-navigation .previous { | ||
1023 | + margin-right: 10px; | ||
1024 | +} | ||
1025 | + | ||
1026 | +#article .gallery-navigation .next { | ||
1027 | + margin-left: 10px; | ||
1028 | +} | ||
1029 | + | ||
1030 | +#article .gallery-navigation .total-of-images { | ||
1031 | + font-weight: bold; | ||
1032 | +} | ||
1033 | + | ||
1034 | +#article .uploaded-file-description { | ||
1035 | + background: #f6f6f6; | ||
1036 | + border-top: 1px solid #ccc; | ||
1037 | + border-bottom: 1px solid #ccc; | ||
1038 | + padding: 1em; | ||
1039 | +} | ||
1040 | +#article .uploaded-file-description.empty { | ||
1041 | + display: none; | ||
1042 | +} | ||
1043 | + | ||
1044 | +#article.file-generic .download-link { | ||
1045 | + display: block; | ||
1046 | + margin-bottom: 10px; | ||
1047 | +} | ||
1048 | +#article.file-generic .download-link span { | ||
1049 | + font-size: 150%; | ||
1050 | + padding-right: 5px; | ||
1051 | +} | ||
1052 | +#article.file-generic .download-link a { | ||
1053 | + font-size: 180%; | ||
1054 | + text-decoration: none; | ||
1055 | +} | ||
1022 | 1056 | ||
1023 | /**************************** Comments *******************************/ | 1057 | /**************************** Comments *******************************/ |
1024 | 1058 |
public/stylesheets/application.css
@@ -1534,14 +1534,15 @@ a.button.disabled, input.disabled { | @@ -1534,14 +1534,15 @@ a.button.disabled, input.disabled { | ||
1534 | .map { | 1534 | .map { |
1535 | clear: both; | 1535 | clear: both; |
1536 | } | 1536 | } |
1537 | + | ||
1537 | /*********************************************************** | 1538 | /*********************************************************** |
1538 | * style for blocks | 1539 | * style for blocks |
1539 | ***********************************************************/ | 1540 | ***********************************************************/ |
1540 | 1541 | ||
1541 | - #content .communities-block .profile-image { | ||
1542 | - float: none; | ||
1543 | - padding-left: 0px; | ||
1544 | - max-width: none; | 1542 | +#content .communities-block .profile-image { |
1543 | + float: none; | ||
1544 | + padding-left: 0px; | ||
1545 | + max-width: none; | ||
1545 | } | 1546 | } |
1546 | #content .communities-block .vcard .profile_link { | 1547 | #content .communities-block .vcard .profile_link { |
1547 | text-align: center; | 1548 | text-align: center; |
@@ -3313,26 +3314,7 @@ div#article-parent { | @@ -3313,26 +3314,7 @@ div#article-parent { | ||
3313 | .article-body-uploaded-file { | 3314 | .article-body-uploaded-file { |
3314 | text-align: center; | 3315 | text-align: center; |
3315 | } | 3316 | } |
3316 | -/************* uploaded file *****************/ | ||
3317 | 3317 | ||
3318 | -#article .gallery-navigation { | ||
3319 | - padding: 10px 0; | ||
3320 | -} | ||
3321 | -#article .gallery-navigation .left { | ||
3322 | - margin-right: 10px; | ||
3323 | -} | ||
3324 | -#article .gallery-navigation .right { | ||
3325 | - margin-left: 10px; | ||
3326 | -} | ||
3327 | -#article .gallery-navigation .total-of-images { | ||
3328 | - font-weight: bold; | ||
3329 | -} | ||
3330 | -#article .uploaded-file-description { | ||
3331 | - background: #f6f6f6; | ||
3332 | - border-top: 1px solid #ccc; | ||
3333 | - border-bottom: 1px solid #ccc; | ||
3334 | - padding: 1em; | ||
3335 | -} | ||
3336 | /* ==> public/stylesheets/controller_events.css <== */ | 3318 | /* ==> public/stylesheets/controller_events.css <== */ |
3337 | #agenda { | 3319 | #agenda { |
3338 | position: relative; | 3320 | position: relative; |