diff --git a/Dockerfile b/Dockerfile index 4b13d676d..4e4b49920 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,7 @@ RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \ default-jre-headless \ unison \ sudo graphviz --no-install-recommends \ + poppler-utils \ libfile-mimeinfo-perl && \ apt-get install -y --no-install-recommends -t $(cat /tmp/lsb_release)-backports libreoffice && \ npm install -g yarn && \ diff --git a/Dockerfile.production b/Dockerfile.production index 533c1e64f..678b10b67 100644 --- a/Dockerfile.production +++ b/Dockerfile.production @@ -18,6 +18,7 @@ RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \ netcat \ default-jre-headless \ sudo graphviz --no-install-recommends \ + poppler-utils \ libfile-mimeinfo-perl && \ apt-get install -y --no-install-recommends -t stretch-backports libreoffice && \ npm install -g yarn && \ diff --git a/app/assets/javascripts/sitewide/drag_n_drop.js.erb b/app/assets/javascripts/sitewide/drag_n_drop.js.erb index 949ccdbec..54c7e77d2 100644 --- a/app/assets/javascripts/sitewide/drag_n_drop.js.erb +++ b/app/assets/javascripts/sitewide/drag_n_drop.js.erb @@ -270,7 +270,7 @@ function _uploadedAssetPreview(asset, i) { var html = '
'; - html +='
'; + html +='
'; html +=''; html +='
'; html +='
' + truncateLongString(asset.name, <%= Constants::FILENAME_TRUNCATION_LENGTH %>); diff --git a/app/assets/stylesheets/steps.scss b/app/assets/stylesheets/steps.scss index 96e40789e..17806d96b 100644 --- a/app/assets/stylesheets/steps.scss +++ b/app/assets/stylesheets/steps.scss @@ -143,25 +143,6 @@ background-color: rgba(95, 95, 95, .1); } - .attachment-thumbnail { - display: inline-block; - height: 160px; - margin: 16px 10px 5px; - overflow: hidden; - text-align: center; - - img { - border-radius: 5px; - max-height: 100%; - max-width: 100%; - } - - i.fas { - font-size: 100px; - line-height: 160px; - } - } - .no-shadow { box-shadow: none; } diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss index 1d9157b2c..c9e5a14c0 100644 --- a/app/assets/stylesheets/themes/scinote.scss +++ b/app/assets/stylesheets/themes/scinote.scss @@ -1442,6 +1442,32 @@ table.dataTable { } } +.attachment-thumbnail { + display: inline-block; + height: 160px; + margin: 16px 10px 5px; + overflow: hidden; + text-align: center; + width: 100%; + + img { + border-radius: 5px; + max-height: 100%; + max-width: 100%; + } + + .fas { + font-size: 100px; + line-height: 160px; + } + + &:not(.new) { + background-image: url("/images/medium/processing.gif"); + background-position: center; + background-repeat: no-repeat; + } +} + // Image preview modal .modal-file-preview { background: transparent; @@ -1484,6 +1510,9 @@ table.dataTable { .file-preview-container { align-items: center; + background-image: url("/images/medium/processing.gif"); + background-position: center; + background-repeat: no-repeat; color: $gray-dark; display: -moz-flex; display: -webkit-flex; diff --git a/app/models/asset.rb b/app/models/asset.rb index de5202ea8..7c1faca30 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -1,9 +1,12 @@ +# frozen_string_literal: true + class Asset < ApplicationRecord include ActiveStorage::Downloading include SearchableModel include DatabaseHelper include Encryptor include WopiUtil + include ActiveStorageFileUtil require 'tempfile' # Lock duration set to 30 minutes @@ -191,27 +194,25 @@ class Asset < ApplicationRecord def previewable? return false unless file.attached? - previewable_document? || previewable_image? + previewable_document?(blob) || previewable_image? end def medium_preview return file.variant(resize: Constants::MEDIUM_PIC_FORMAT) if previewable_image? - '/images/medium/processing.gif' - # file.preview(resize: Constants::MEDIUM_PIC_FORMAT) + file.preview(resize: Constants::MEDIUM_PIC_FORMAT) end def large_preview return file.variant(resize: Constants::LARGE_PIC_FORMAT) if previewable_image? - '/images/large/processing.gif' - # file.preview(resize: Constants::LARGE_PIC_FORMAT) + file.preview(resize: Constants::LARGE_PIC_FORMAT) end def file_name return '' unless file.attached? - file.blob&.filename&.to_s + file.blob&.filename&.sanitized end def file_size @@ -221,11 +222,9 @@ class Asset < ApplicationRecord end def content_type - file&.blob&.content_type - end + return '' unless file.attached? - def file_size - file&.blob&.byte_size + file&.blob&.content_type end def duplicate @@ -455,25 +454,6 @@ class Asset < ApplicationRecord Rails.root.join('tmp') end - def previewable_document? - previewable = Constants::PREVIEWABLE_FILE_TYPES.include?(file.content_type) - - filename = file.filename.to_s - content_type = file.content_type - - extensions = %w(.xlsx .docx .pptx .xls .doc .ppt) - # Mimetype sometimes recognizes Office files as zip files - # In this case we also check the extension of the given file - # Otherwise the conversion should fail if the file is being something else - previewable ||= (content_type == 'application/zip' && extensions.include?(File.extname(filename))) - - # Mimetype also sometimes recognizes '.xls' and '.ppt' files as - # application/x-ole-storage (https://github.com/minad/mimemagic/issues/50) - previewable ||= (content_type == 'application/x-ole-storage' && %w(.xls .ppt).include?(File.extname(filename))) - - previewable - end - def previewable_image? file.blob&.content_type =~ %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}} end diff --git a/app/models/tiny_mce_asset.rb b/app/models/tiny_mce_asset.rb index 6f56d307a..4178192b7 100644 --- a/app/models/tiny_mce_asset.rb +++ b/app/models/tiny_mce_asset.rb @@ -73,7 +73,7 @@ class TinyMceAsset < ApplicationRecord def file_name return '' unless image.attached? - image.blob&.filename&.to_s + image.blob&.filename&.sanitized end def file_size @@ -83,11 +83,9 @@ class TinyMceAsset < ApplicationRecord end def content_type - image&.blob&.content_type - end + return '' unless image.attached? - def file_size - image&.blob&.byte_size + image&.blob&.content_type end def preview @@ -140,7 +138,7 @@ class TinyMceAsset < ApplicationRecord if exists? order(:id).each do |tiny_mce_asset| asset_guid = get_guid(tiny_mce_asset.id) - asset_file_name = "rte-#{asset_guid.to_s + File.extname(tiny_mce_asset.image.filename.to_s)}" + asset_file_name = "rte-#{asset_guid.to_s + tiny_mce_asset.image.blob.filename.extension}" ostream.put_next_entry("#{dir}/#{asset_file_name}") ostream.print(tiny_mce_asset.image.download) input_file.close @@ -172,7 +170,7 @@ class TinyMceAsset < ApplicationRecord tiny_img_clone.transaction do tiny_img_clone.save! - tiny_img_clone.image.attach(io: image.download, filename: image.filename.to_s) + tiny_img_clone.image.attach(io: image.download, filename: image.filename.sanitized) end return false unless tiny_img_clone.persisted? diff --git a/app/services/active_storage/previewer/libreoffice_previewer.rb b/app/services/active_storage/previewer/libreoffice_previewer.rb new file mode 100644 index 000000000..23c18b5ce --- /dev/null +++ b/app/services/active_storage/previewer/libreoffice_previewer.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module ActiveStorage + class Previewer + class LibreofficePreviewer < Previewer + class << self + include ActiveStorageFileUtil + + def accept?(blob) + previewable_document?(blob) + end + end + + def preview + download_blob_to_tempfile do |input| + work_dir = File.dirname(input.path) + basename = File.basename(input.path, '.*') + preview_file = File.join(work_dir, "#{basename}.png") + + Rails.logger.info "Starting preparing document preview for file #{blob.filename.sanitized}..." + + begin + success = system( + "#{libreoffice_path} --headless --invisible --convert-to png --outdir #{work_dir} #{input.path}" + ) + + unless success && File.file?(preview_file) + raise StandardError, "There was an error generating document preview, blob id: #{blob.id}" + end + + yield io: File.open(preview_file), filename: "#{blob.filename.base}.png", content_type: 'image/png' + + Rails.logger.info "Finished preparing document preview for file #{blob.filename.sanitized}." + ensure + File.delete(preview_file) if File.file?(preview_file) + end + end + end + + private + + def tempdir + Rails.root.join('tmp') + end + + def libreoffice_path + ENV['LIBREOFFICE_PATH'] || 'libreoffice' + end + end + end +end diff --git a/lib/active_storage/service/custom_s3_service.rb b/app/services/active_storage/service/custom_s3_service.rb similarity index 100% rename from lib/active_storage/service/custom_s3_service.rb rename to app/services/active_storage/service/custom_s3_service.rb diff --git a/app/utilities/active_storage_file_util.rb b/app/utilities/active_storage_file_util.rb new file mode 100644 index 000000000..1bdf89a75 --- /dev/null +++ b/app/utilities/active_storage_file_util.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveStorageFileUtil + # Method expects instance of ActiveStorage::Blob as argument + def previewable_document?(blob) + previewable = Constants::PREVIEWABLE_FILE_TYPES.include?(blob.content_type) + + file_extension = blob.filename.extension + content_type = blob.content_type + + extensions = %w(.xlsx .docx .pptx .xls .doc .ppt) + # Mimetype sometimes recognizes Office files as zip files + # In this case we also check the extension of the given file + # Otherwise the conversion should fail if the file is being something else + previewable ||= (content_type == 'application/zip' && extensions.include?(file_extension)) + + # Mimetype also sometimes recognizes '.xls' and '.ppt' files as + # application/x-ole-storage (https://github.com/minad/mimemagic/issues/50) + previewable ||= (content_type == 'application/x-ole-storage' && %w(.xls .ppt).include?(file_extension)) + + previewable + end +end diff --git a/app/views/shared/_asset_link.html.erb b/app/views/shared/_asset_link.html.erb index fb762f4c5..e63960561 100644 --- a/app/views/shared/_asset_link.html.erb +++ b/app/views/shared/_asset_link.html.erb @@ -1,15 +1,9 @@ <%= link_to download_asset_path(asset), class: 'file-preview-link', id: "modal_link#{asset.id}", - data: { no_turbolink: true, id: true, status: 'asset-present', 'preview-url': asset_file_preview_path(asset) } do %> - <% if display_image_tag && asset.previewable? %> - <%= image_tag asset.medium_preview %> -

- <%= truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %> -

- <% else %> - - <%= truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %> - - <% end %> + data: { no_turbolink: true, + id: true, + status: 'asset-present', + 'preview-url': asset_file_preview_path(asset) } do %> + <%= render partial: 'shared/asset_placeholder.html.erb', locals: { edit_page: false, asset: asset } %> <% end %> diff --git a/app/views/steps/attachments/_placeholder.html.erb b/app/views/shared/_asset_placeholder.html.erb similarity index 86% rename from app/views/steps/attachments/_placeholder.html.erb rename to app/views/shared/_asset_placeholder.html.erb index 56c7acf81..e8426337d 100644 --- a/app/views/steps/attachments/_placeholder.html.erb +++ b/app/views/shared/_asset_placeholder.html.erb @@ -10,7 +10,7 @@ <%= truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %>
- <%= t('protocols.steps.attachments.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %>
+ <%= t('assets.placeholder.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %>
<%= number_to_human_size(asset.file_size) %>
diff --git a/app/views/steps/_empty_step.html.erb b/app/views/steps/_empty_step.html.erb index d68b3edeb..4a890c65a 100644 --- a/app/views/steps/_empty_step.html.erb +++ b/app/views/steps/_empty_step.html.erb @@ -69,7 +69,7 @@
<%= f.nested_fields_for :assets do |ff| %> - <%= render partial: 'steps/attachments/placeholder.html.erb', + <%= render partial: 'shared/asset_placeholder.html.erb', locals: { edit_page: true, asset: ff.object, ff: ff } %> <% end %>
diff --git a/app/views/steps/attachments/_item.html.erb b/app/views/steps/attachments/_item.html.erb index 679f4dafa..1b5faeb86 100644 --- a/app/views/steps/attachments/_item.html.erb +++ b/app/views/steps/attachments/_item.html.erb @@ -11,7 +11,6 @@ 'order-new': assets_count - i, } do %> - <%= render partial: 'steps/attachments/placeholder.html.erb', - locals: { edit_page: false, asset: asset } %> + <%= render partial: 'shared/asset_placeholder.html.erb', locals: { edit_page: false, asset: asset } %> <% end %>
diff --git a/config/application.rb b/config/application.rb index fc530cda8..bf21c82a1 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,7 +9,7 @@ Bundler.require(*Rails.groups) module Scinote class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 5.1 + config.load_defaults 5.2 # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb new file mode 100644 index 000000000..8833691a2 --- /dev/null +++ b/config/initializers/active_storage.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +Rails.application.config.active_storage.previewers = [ActiveStorage::Previewer::PopplerPDFPreviewer, + ActiveStorage::Previewer::LibreofficePreviewer] diff --git a/config/locales/en.yml b/config/locales/en.yml index 14407e7b5..2116ac1e7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1763,7 +1763,6 @@ en: sort_old: "Oldest first ↑" sort_atoz: "Name ↓" sort_ztoa: "Name ↑" - modified_label: "Modified:" new: add_step_title: "Add new step" tab_checklists: "Checklists" @@ -1900,6 +1899,8 @@ en: add_image: 'Add' file_name: 'File name' file_name_placeholder: 'Image' + placeholder: + modified_label: "Modified:" wopi_supported_text_formats_title: 'Only .docx, .docm, .odt file formats are supported for editing in Word Online.' wopi_supported_table_formats_title: 'Only .xlsx, .xlsm, .xlsb, .ods file formats are supported for editing in Excel Online.' wopi_supported_presentation_formats_title: 'Only .pptx, ppsx, .odp file formats are supported for editing in Powerpoint Online.'