mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 06:35:56 +08:00
Implement custom document file previewer [SCI-3677]
This commit is contained in:
parent
e1185ff551
commit
64b7a5646a
|
@ -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 && \
|
||||
|
|
|
@ -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 && \
|
||||
|
|
|
@ -270,7 +270,7 @@
|
|||
|
||||
function _uploadedAssetPreview(asset, i) {
|
||||
var html = '<div class="attachment-placeholder pull-left new">';
|
||||
html +='<div class="attachment-thumbnail no-shadow %>">';
|
||||
html +='<div class="attachment-thumbnail no-shadow new %>">';
|
||||
html +='<i class="fas fa-image"></i>';
|
||||
html +='</div>';
|
||||
html +='<div class="attachment-label">' + truncateLongString(asset.name, <%= Constants::FILENAME_TRUNCATION_LENGTH %>);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
23
app/utilities/active_storage_file_util.rb
Normal file
23
app/utilities/active_storage_file_util.rb
Normal file
|
@ -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
|
|
@ -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 %>
|
||||
<p>
|
||||
<%= truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %>
|
||||
</p>
|
||||
<% else %>
|
||||
<span>
|
||||
<%= truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %>
|
||||
</span>
|
||||
<% 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 %>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<%= truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %>
|
||||
</div>
|
||||
<div class="spencer-bonnet-modif">
|
||||
<%= t('protocols.steps.attachments.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %> <br>
|
||||
<%= t('assets.placeholder.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %> <br>
|
||||
<%= number_to_human_size(asset.file_size) %>
|
||||
</div>
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
<div id="new-step-assets-group" class="form-group">
|
||||
<div class="col-xs-12 attachments edit">
|
||||
<%= 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 %>
|
||||
</div>
|
||||
|
|
|
@ -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 %>
|
||||
</div>
|
||||
|
|
|
@ -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
|
||||
|
|
4
config/initializers/active_storage.rb
Normal file
4
config/initializers/active_storage.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Rails.application.config.active_storage.previewers = [ActiveStorage::Previewer::PopplerPDFPreviewer,
|
||||
ActiveStorage::Previewer::LibreofficePreviewer]
|
|
@ -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.'
|
||||
|
|
Loading…
Reference in a new issue