Commit 825c88ca01d079a8f08773c2f8a6d53246d7e410
1 parent
36553bc6
Exists in
master
and in
28 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; | ... | ... |