Commit 825c88ca01d079a8f08773c2f8a6d53246d7e410

Authored by Aurélio A. Heckert
1 parent 36553bc6

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
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 &lt; ApplicationController @@ -55,7 +55,9 @@ class ContentViewerController &lt; 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('&nbsp;' * (level * 4) + image_tag(icon_for_article(article)) + short_filename(article.name), article.url.merge(:view => true)) 26 link_to('&nbsp;' * (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 &lt; ActiveRecord::Base @@ -133,8 +133,12 @@ class Article &lt; 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 &lt; ActiveRecord::Base @@ -314,7 +318,7 @@ class Article &lt; 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 &lt; Article @@ -61,11 +61,13 @@ class UploadedFile &lt; 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 &lt; Article @@ -91,40 +93,26 @@ class UploadedFile &lt; 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(_('&laquo; Previous'), images[current_index - 1].view_url, :class => 'left')  
104 - else  
105 - content_tag('span', _('&laquo; Previous'), :class => 'left')  
106 - end  
107 -  
108 - link_to_next = if current_index < total_of_images - 1  
109 - link_to(_('Next &raquo;'), images[current_index + 1].view_url, :class => 'right')  
110 - else  
111 - content_tag('span', _('Next &raquo;'), :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
app/views/file_presenter/_generic.html.erb 0 → 100644
@@ -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 +
app/views/file_presenter/_image.html.erb 0 → 100644
@@ -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(_('&laquo; Previous'), images[current_index - 1].view_url, :class => 'previous')
  8 + else
  9 + content_tag('span', _('&laquo; Previous'), :class => 'previous')
  10 + end
  11 +
  12 + link_to_next = if current_index < total_of_images - 1
  13 + link_to(_('Next &raquo;'), images[current_index + 1].view_url, :class => 'next')
  14 + else
  15 + content_tag('span', _('Next &raquo;'), :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 +
lib/file_presenter.rb 0 → 100644
@@ -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
lib/file_presenter/generic.rb 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +# Made to encapsulate any UploadedFile
  2 +class FilePresenter::Generic < FilePresenter
  3 + def initialize(f)
  4 + @file = f
  5 + end
  6 +
  7 + # if returns low priority, because it is generic.
  8 + def self.accepts?(f)
  9 + 1 if f.is_a? UploadedFile
  10 + end
  11 +end
lib/file_presenter/image.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +class FilePresenter::Image < FilePresenter
  2 + def initialize(f)
  3 + @file = f
  4 + end
  5 +
  6 + def self.accepts?(f)
  7 + f.image? ? 10 : nil
  8 + end
  9 +
  10 + def icon_name
  11 + article.public_filename :icon
  12 + end
  13 +end
plugins/html5_video/lib/file_presenter/video.rb 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +class FilePresenter::Video < FilePresenter
  2 + def initialize(f)
  3 + @file = f
  4 + end
  5 +
  6 + def self.accepts?(f)
  7 + return nil if f.content_type.nil?
  8 + ( f.content_type[0..4] == 'video' ) ? 10 : nil
  9 + end
  10 +end
plugins/html5_video/lib/html5_video_plugin.rb 0 → 100644
@@ -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;