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 | 1 | class ApplicationController < ActionController::Base |
2 | 2 | |
3 | + # Preload FilePresenters to allow `FilePresenter.for()` to work | |
4 | + FilePresenter::Generic | |
5 | + FilePresenter::Image | |
6 | + | |
3 | 7 | before_filter :setup_multitenancy |
4 | 8 | before_filter :detect_stuff_by_domain |
5 | 9 | before_filter :init_noosfero_plugins | ... | ... |
app/controllers/public/content_viewer_controller.rb
... | ... | @@ -55,7 +55,9 @@ class ContentViewerController < ApplicationController |
55 | 55 | # At this point the page will be showed |
56 | 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 | 61 | headers['Content-Type'] = @page.mime_type |
60 | 62 | data = @page.data |
61 | 63 | ... | ... |
app/helpers/folder_helper.rb
... | ... | @@ -21,6 +21,7 @@ module FolderHelper |
21 | 21 | end |
22 | 22 | |
23 | 23 | def display_article_in_listing(article, recursive = false, level = 0) |
24 | + article = FilePresenter.for article | |
24 | 25 | article_link = if article.image? |
25 | 26 | link_to(' ' * (level * 4) + image_tag(icon_for_article(article)) + short_filename(article.name), article.url.merge(:view => true)) |
26 | 27 | else |
... | ... | @@ -40,12 +41,12 @@ module FolderHelper |
40 | 41 | end |
41 | 42 | |
42 | 43 | def icon_for_article(article) |
43 | - icon = article.class.icon_name(article) | |
44 | + icon = article.icon_name || article.class.icon_name(article) | |
44 | 45 | if (icon =~ /\//) |
45 | 46 | icon |
46 | 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 | 50 | klasses += ' icon-upload-file' |
50 | 51 | end |
51 | 52 | klasses | ... | ... |
app/models/article.rb
... | ... | @@ -133,8 +133,12 @@ class Article < ActiveRecord::Base |
133 | 133 | end |
134 | 134 | end |
135 | 135 | |
136 | + def css_class_list | |
137 | + [self.class.name.underscore.dasherize] | |
138 | + end | |
139 | + | |
136 | 140 | def css_class_name |
137 | - self.class.name.underscore.dasherize | |
141 | + [css_class_list].flatten.compact.join(' ') | |
138 | 142 | end |
139 | 143 | |
140 | 144 | def pending_categorizations |
... | ... | @@ -314,7 +318,7 @@ class Article < ActiveRecord::Base |
314 | 318 | end |
315 | 319 | |
316 | 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 | 322 | end |
319 | 323 | |
320 | 324 | def allow_children? | ... | ... |
app/models/uploaded_file.rb
... | ... | @@ -61,11 +61,13 @@ class UploadedFile < Article |
61 | 61 | postgresql_attachment_fu |
62 | 62 | |
63 | 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 | 71 | end |
70 | 72 | |
71 | 73 | def mime_type |
... | ... | @@ -91,40 +93,26 @@ class UploadedFile < Article |
91 | 93 | end |
92 | 94 | |
93 | 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 | 102 | article = self |
95 | 103 | if image? |
96 | 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 | 109 | end |
124 | 110 | else |
125 | 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 | 116 | end |
129 | 117 | end |
130 | 118 | end | ... | ... |
... | ... | @@ -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 @@ |
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 @@ |
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 @@ |
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 @@ |
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 | 80 | .icon-application-postscript { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-postscript.png) } |
81 | 81 | .icon-application-pdf { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-pdf.png) } |
82 | 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 | 84 | .icon-application-vnd-oasis-opendocument-text { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.text.png) } |
85 | 85 | .icon-application-vnd-oasis-opendocument-spreadsheet { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.spreadsheet.png) } |
86 | 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 | 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 | 825 | /* ==> search-results.css <== */ |
832 | 826 | |
833 | 827 | |
... | ... | @@ -1019,6 +1013,46 @@ hr.pre-posts, hr.sep-posts { |
1019 | 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 | 1057 | /**************************** Comments *******************************/ |
1024 | 1058 | ... | ... |
public/stylesheets/application.css
... | ... | @@ -1534,14 +1534,15 @@ a.button.disabled, input.disabled { |
1534 | 1534 | .map { |
1535 | 1535 | clear: both; |
1536 | 1536 | } |
1537 | + | |
1537 | 1538 | /*********************************************************** |
1538 | 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 | 1547 | #content .communities-block .vcard .profile_link { |
1547 | 1548 | text-align: center; |
... | ... | @@ -3313,26 +3314,7 @@ div#article-parent { |
3313 | 3314 | .article-body-uploaded-file { |
3314 | 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 | 3318 | /* ==> public/stylesheets/controller_events.css <== */ |
3337 | 3319 | #agenda { |
3338 | 3320 | position: relative; | ... | ... |