Feature #43484 » 0004-Show-the-same-file-type-icon-whether-the-content-typ.patch
| app/helpers/icons_helper.rb | ||
|---|---|---|
| 23 | 23 |
DEFAULT_ICON_SIZE = "18" |
| 24 | 24 |
DEFAULT_SPRITE = "icons" |
| 25 | 25 | |
| 26 |
# Content types covered by each icon. The same kind of file can be denoted by |
|
| 27 |
# several types, depending on whether the type was detected from the file |
|
| 28 |
# contents or taken from Redmine::MimeType, so list them all rather than |
|
| 29 |
# deriving the icon name from the type. An entry with no subtype (eg. audio) |
|
| 30 |
# covers every type of that top level type that is not listed elsewhere. |
|
| 31 |
ICON_MIME_TYPES = {
|
|
| 32 |
'application-gzip' => %w(application/gzip), |
|
| 33 |
'application-javascript' => %w(application/javascript text/javascript), |
|
| 34 |
'application-pdf' => %w(application/pdf), |
|
| 35 |
'application-zip' => %w(application/zip), |
|
| 36 |
'file-music' => %w(audio), |
|
| 37 |
'movie' => %w(video), |
|
| 38 |
'photo' => %w(image), |
|
| 39 |
'text-css' => %w(text/css), |
|
| 40 |
'text-html' => %w(text/html), |
|
| 41 |
'text-plain' => %w(text application/sql application/x-csh application/x-sh), |
|
| 42 |
'text-x-c' => %w(text/x-c text/x-c++hdr text/x-c++src), |
|
| 43 |
'text-x-csharp' => %w(text/x-csharp), |
|
| 44 |
'text-x-java' => %w(text/x-java text/x-java-source), |
|
| 45 |
'text-x-php' => %w(text/x-php), |
|
| 46 |
'text-x-ruby' => %w(text/x-ruby), |
|
| 47 |
'text-xml' => %w(text/xml application/xml), |
|
| 48 |
# Microsoft Office Open XML documents |
|
| 49 |
# Do not add legacy Office formats (.doc, .xls, .ppt) here because |
|
| 50 |
# Redmine does not provide attachment preview for these formats. |
|
| 51 |
'file-type-docx' => %w(application/vnd.openxmlformats-officedocument.wordprocessingml.document), |
|
| 52 |
'file-type-ppt' => %w(application/vnd.openxmlformats-officedocument.presentationml.presentation), |
|
| 53 |
'file-type-xls' => %w(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet) |
|
| 54 |
}.freeze |
|
| 55 | ||
| 56 |
MIME_TYPE_ICONS = ICON_MIME_TYPES.each_with_object({}) do |(icon, types), map|
|
|
| 57 |
types.each {|type| map[type] = icon}
|
|
| 58 |
end.freeze |
|
| 59 | ||
| 26 | 60 |
def sprite_source(icon_name, sprite: DEFAULT_SPRITE, plugin: nil) |
| 27 | 61 |
if plugin |
| 28 | 62 |
"plugin_assets/#{plugin}/#{sprite}.svg"
|
| ... | ... | |
| 102 | 136 |
sprite_icon(icon_name, **) |
| 103 | 137 |
end |
| 104 | 138 | |
| 105 |
def file_type_icon(mime_type, ...)
|
|
| 106 |
icon_name = icon_for_mime_type(mime_type) |
|
| 107 |
sprite_icon(icon_name, ...)
|
|
| 139 |
def file_type_icon(mime_type, label = nil, filename: nil, **)
|
|
| 140 |
icon_name = icon_for_mime_type(mime_type, filename)
|
|
| 141 |
sprite_icon(icon_name, label, **)
|
|
| 108 | 142 |
end |
| 109 | 143 | |
| 110 | 144 |
private |
| ... | ... | |
| 125 | 159 |
) |
| 126 | 160 |
end |
| 127 | 161 | |
| 128 |
def icon_for_mime_type(mime) |
|
| 129 |
if %w(text/x-c text/x-csharp text/x-java text/x-php |
|
| 130 |
text/x-ruby text/xml text/css text/html text/css text/html |
|
| 131 |
application/pdf application/zip application/gzip application/javascript).include?(mime) |
|
| 132 |
icon_name = mime.tr('/', '-')
|
|
| 133 |
else |
|
| 134 |
top_level_type, subtype = mime.to_s.split('/')
|
|
| 135 |
icon_name = |
|
| 136 |
case top_level_type |
|
| 137 |
when 'audio' then 'file-music' |
|
| 138 |
when 'image' then 'photo' |
|
| 139 |
when 'text' |
|
| 140 |
%w(markdown plain x-textile).include?(subtype) ? 'text-plain' : nil |
|
| 141 |
when 'video' then 'movie' |
|
| 142 |
else |
|
| 143 |
# MIME type mapping |
|
| 144 |
{
|
|
| 145 |
# Microsoft Office Open XML documents |
|
| 146 |
# Do not add legacy Office formats (.doc, .xls, .ppt) here because |
|
| 147 |
# Redmine does not provide attachment preview for these formats. |
|
| 148 |
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'file-type-ppt', |
|
| 149 |
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'file-type-xls', |
|
| 150 |
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'file-type-docx' |
|
| 151 |
}[mime.to_s] |
|
| 152 |
end |
|
| 162 |
def icon_for_mime_type(mime, filename = nil) |
|
| 163 |
# Content type detection falls back to application/octet-stream when it |
|
| 164 |
# cannot tell the type from the file contents. In that case only, guess |
|
| 165 |
# the type from the filename, as AttachmentsController#detect_content_type |
|
| 166 |
# does. |
|
| 167 |
if filename.present? && (mime.blank? || mime == 'application/octet-stream') |
|
| 168 |
mime = Redmine::MimeType.of(filename) |
|
| 153 | 169 |
end |
| 154 |
icon_name || 'file' |
|
| 170 | ||
| 171 |
MIME_TYPE_ICONS[mime] || |
|
| 172 |
MIME_TYPE_ICONS[mime.to_s.split('/').first] ||
|
|
| 173 |
'file' |
|
| 155 | 174 |
end |
| 156 | 175 |
end |
| app/views/attachments/_links.html.erb | ||
|---|---|---|
| 15 | 15 |
<% for attachment in attachments %> |
| 16 | 16 |
<tr> |
| 17 | 17 |
<td> |
| 18 |
<%= link_to_attachment attachment, class: 'icon icon-attachment ', icon: icon_for_mime_type(attachment.content_type) -%> |
|
| 18 |
<%= link_to_attachment attachment, class: 'icon icon-attachment ', icon: icon_for_mime_type(attachment.content_type, attachment.filename) -%>
|
|
| 19 | 19 |
<span class="size">(<%= number_to_human_size attachment.filesize %>)</span> |
| 20 | 20 |
<%= link_to_attachment attachment, class: 'icon-only icon-download ', title: l(:button_download), download: true, icon: 'download' -%> |
| 21 | 21 |
</td> |
| app/views/attachments/edit_all.html.erb | ||
|---|---|---|
| 11 | 11 |
<% @attachments.each do |attachment| %> |
| 12 | 12 |
<tr> |
| 13 | 13 |
<td colspan="2"> |
| 14 |
<span class="icon icon-attachment"><%= file_type_icon(attachment.content_type_was, attachment.filename_was) %></span> |
|
| 14 |
<span class="icon icon-attachment"><%= file_type_icon(attachment.content_type_was, attachment.filename_was, filename: attachment.filename_was) %></span>
|
|
| 15 | 15 |
<span class="size">(<%= number_to_human_size attachment.filesize %>)</span> |
| 16 | 16 |
<span class="author"><%= attachment.author %>, <%= format_time(attachment.created_on) %></span> |
| 17 | 17 |
</td> |
| app/views/issues/_edit.html.erb | ||
|---|---|---|
| 55 | 55 |
<div id="existing-attachments" style="<%= @issue.deleted_attachment_ids.blank? ? 'display:none;' : '' %>"> |
| 56 | 56 |
<% @issue.attachments.each do |attachment| %> |
| 57 | 57 |
<span class="existing-attachment"> |
| 58 |
<%= file_type_icon(attachment.content_type, size: 12) %> |
|
| 58 |
<%= file_type_icon(attachment.content_type, filename: attachment.filename, size: 12) %>
|
|
| 59 | 59 |
<%= text_field_tag '', attachment.filename, :class => "icon icon-attachment filename", :disabled => true %> |
| 60 | 60 |
<label> |
| 61 | 61 |
<%= check_box_tag 'issue[deleted_attachment_ids][]', |
| app/views/wiki/edit.html.erb | ||
|---|---|---|
| 41 | 41 |
<div id="existing-attachments" style="<%= @page.deleted_attachment_ids.blank? ? 'display:none;' : '' %>"> |
| 42 | 42 |
<% @page.attachments.each do |attachment| %> |
| 43 | 43 |
<span class="existing-attachment"> |
| 44 |
<%= file_type_icon(attachment.content_type, size: 12) %> |
|
| 44 |
<%= file_type_icon(attachment.content_type, filename: attachment.filename, size: 12) %>
|
|
| 45 | 45 |
<%= text_field_tag '', attachment.filename, :class => "icon icon-attachment filename", :disabled => true %> |
| 46 | 46 |
<label class='inline'> |
| 47 | 47 |
<%= check_box_tag 'wiki_page[deleted_attachment_ids][]', |
| test/helpers/icons_helper_test.rb | ||
|---|---|---|
| 242 | 242 |
assert_equal 'movie', icon_for_mime_type('video/mp4')
|
| 243 | 243 |
end |
| 244 | 244 | |
| 245 |
def test_icon_for_mime_type_should_return_the_same_icon_for_equivalent_mime_types |
|
| 246 |
# Types detected from the file contents and their Redmine::MimeType counterparts |
|
| 247 |
{
|
|
| 248 |
'text/javascript' => 'application/javascript', |
|
| 249 |
'application/xml' => 'text/xml', |
|
| 250 |
'text/x-c++src' => 'text/x-c', |
|
| 251 |
'text/x-java-source' => 'text/x-java', |
|
| 252 |
'text/x-diff' => 'text/plain', |
|
| 253 |
'application/sql' => 'text/plain' |
|
| 254 |
}.each do |detected, counterpart| |
|
| 255 |
assert_equal icon_for_mime_type(counterpart), icon_for_mime_type(detected), |
|
| 256 |
"#{detected} should get the same icon as #{counterpart}"
|
|
| 257 |
end |
|
| 258 |
end |
|
| 259 | ||
| 260 |
def test_icon_for_mime_type_should_fall_back_to_filename_only_when_type_is_undetermined |
|
| 261 |
assert_equal 'text-plain', icon_for_mime_type('application/octet-stream', 'readme.textile')
|
|
| 262 |
assert_equal 'application-pdf', icon_for_mime_type(nil, 'document.pdf') |
|
| 263 |
# A type detected from the file contents takes precedence over the filename |
|
| 264 |
assert_equal 'text-html', icon_for_mime_type('text/html', 'not-really-an-image.png')
|
|
| 265 |
end |
|
| 266 | ||
| 245 | 267 |
def test_icon_for_mime_type_should_return_generic_file_icon_for_unknown_mime_types |
| 246 | 268 |
assert_equal 'file', icon_for_mime_type('unknown-type')
|
| 247 | 269 |
end |