Merge branch 'features/office-file-previews' of https://github.com/biosistemika/scinote-web into features/office-file-previews

This commit is contained in:
aignatov-bio 2020-10-15 15:25:54 +02:00
commit 016fc8a632
25 changed files with 457 additions and 414 deletions

View file

@ -279,3 +279,10 @@ var HelperModule = (function(){
function notTurbolinksPreview() {
return !document.documentElement.hasAttribute("data-turbolinks-preview");
}
const windowScrollEvents = {};
$(window).scroll(function() {
$.each(windowScrollEvents, function(key, scroll_function){
scroll_function();
})
})

View file

@ -330,18 +330,20 @@
}
function initPreviewToggle(){
$('.attachment-placeholder').on('click', '.dataPreviewAction', function (e) {
var view_mode = $(this).data('preview-type');
var asset_id = $(this).data('asset-id');
$('.attachments').on('click', '.change-preview-type', function (e) {
var viewMode = $(this).data('preview-type');
var assetId = $(this).data('asset-id');
e.preventDefault();
e.stopPropagation();
$.ajax({
url: `/files/${asset_id}/toggle_view_mode`,
url: `/files/${assetId}/toggle_view_mode`,
type: "PATCH",
dataType: "json",
data: { asset: { view_mode: view_mode }},
data: { asset: { view_mode: viewMode }},
success: function (data) {
console.log(data);
$(`.step-asset[data-asset-id=${assetId}]`).replaceWith(data.html);
FilePreviewModal.init();
$(`.step-asset[data-asset-id=${assetId}]`).closest('.attachments').trigger('reorder');
},
error: function (data) {
console.log(data);
@ -359,6 +361,7 @@
initDeleteStep();
TinyMCE.highlight();
initPreviewToggle();
reorderAttachmentsInit();
}
/*
@ -643,20 +646,50 @@
}
// Reorder attachments
global.reorderAttachments = function reorderAtt(elem, stepId, sortType) {
var label_value = $("#dd-att-step-" + stepId + "> .dropdown-menu > li > a[data-order=" + sortType + "]").html();
$("#dd-att-step-" + stepId + "-label").html(label_value);
$('#att-' + stepId + ' a.file-preview-link').each(function(){
var elm = $(this)
elm.parent().css('order', elm.attr('data-order-' + sortType));
function reorderAttachmentsInit() {
$('.attachments-order').on('click', '.change-order', function(e){
var orderDropdown = $(this).closest('.attachments-order');
var assetsContainer = $(`.attachments[data-step-id=${orderDropdown.data('step-id')}]`)
var order = $(this).data('order');
e.preventDefault();
orderDropdown.find('.selected-order').html($(this).text());
assetsContainer.data('order', order);
assetsContainer.trigger('reorder');
$.post(orderDropdown.data('state-save-path'), {
assets: { order: order }
});
})
$.post(
$(elem).closest('.dropdown-menu').data('stateSavePath'),
{ assets: { order: sortType } },
null,
'json',
);
$('.attachments').on('reorder', function() {
var assets = $(`.attachments[data-step-id=${$(this).data('step-id')}] .step-asset`);
var order = $(this).data('order');
var sortedAssets = assets.sort(function(a, b) {
if (a.dataset.assetOrder == b.dataset.assetOrder) {
if (order == 'new') {
return b.dataset.assetUpdatedAt - a.dataset.assetUpdatedAt;
} if (order == 'old') {
return a.dataset.assetUpdatedAt - b.dataset.assetUpdatedAt;
} if (order == 'atoz') {
return a.dataset.assetFileName.toLowerCase() > b.dataset.assetFileName.toLowerCase() ? 1 : -1;
} if (order == 'ztoa') {
return b.dataset.assetFileName.toLowerCase() > a.dataset.assetFileName.toLowerCase() ? 1 : -1;
}
}
return a.dataset.assetOrder > b.dataset.assetOrder ? 1 : -1
})
$.each(sortedAssets, function(i, element){
element.style.order = i
})
})
$('.attachments').trigger('reorder');
}
function initInlineAttachment() {
$(document).on('scroll', 'body', function(){
console.log(1)
})
}
// On init
@ -677,3 +710,61 @@
global.initHandsOnTable = initHandsOnTable;
})(window);
var StepInlineAttachments = (function() {
function elementVisible(element) {
var elementRect = element.getBoundingClientRect();
var elementHeight = $(element).height()
return elementRect.top + (elementHeight / 2) >= 0 &&
elementRect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + (elementHeight / 2)
}
function showElement(element) {
setTimeout(() => {
var iframeUrl = $(element).find('.iframe-placeholder').data('iframe-url');
if (elementVisible(element) && iframeUrl) {
$(element).find('.iframe-placeholder')
.replaceWith(`<iframe class="active-iframe-preview" src="${iframeUrl}"></iframe>`);
$(element).addClass('active').attr('data-created-at', new Date().getTime());
}
},500)
}
function hideElement(element) {
var iframeUrl = $(element).find('.active-iframe-preview').attr('src');
if (!elementVisible(element) && iframeUrl) {
$(element).find('iframe')
.replaceWith(`<div class="iframe-placeholder" data-iframe-url="${iframeUrl}"></div>`);
$(element).removeClass('active').attr('data-created-at', null)
return true
}
return false
}
function checkForAttachmentsState() {
$.each($('.inline-attachment-container'), function(i, element){
showElement(element)
})
if ($('.active-iframe-preview').length > 5){
let sortedIframes = $('.inline-attachment-container.active').sort(function(a, b) {
return +a.dataset.createdAt - +b.dataset.createdAt;
})
$.each(sortedIframes, function(i, element){
if (hideElement(element)) return false
})
}
}
return {
init: () => {
windowScrollEvents['StepInlineAttachments'] = StepInlineAttachments.scrollEvent;
},
scrollEvent: () => {
checkForAttachmentsState()
}
}
})();
StepInlineAttachments.init();

View file

@ -288,14 +288,14 @@
}
function uploadedAssetPreview(asset, i) {
var html = `<div class="attachment-placeholder pull-left new">
<div class="attachment-thumbnail no-shadow new %>">
var html = `<div class="attachment-container pull-left new">
<div class="attachment-preview no-shadow new %>">
<i class="fas fa-image"></i>
</div>
<div class="attachment-label">
${truncateLongString(asset.name, GLOBAL_CONSTANTS.FILENAME_TRUNCATION_LENGTH)}
</div>
<div class="spencer-bonnet-modif"></div>
<div class="attachment-metadata"></div>
<div class="remove-icon pull-right">
<a data-item-id="${i}" href="#">
<span class="fas fa-trash"></span>

View file

@ -18,6 +18,7 @@ var FilePreviewModal = (function() {
$('.file-preview-link').off('click');
$('.file-preview-link').click(function(e) {
if ($(e.target.offsetParent).hasClass('change-preview-type')) return;
e.preventDefault();
name = $(this).find('.attachment-label').text();
url = $(this).data('preview-url');

View file

@ -5,10 +5,17 @@
// scss-lint:disable NestingDepth
// scss-lint:disable SelectorFormat
// scss-lint:disable ImportantRule
// scss-lint:disable Unknown
@import "constants";
@import "mixins";
:root {
--attachment-card-thumbnail-height: 280px;
--attachment-card-thumbnail-width: 220px;
}
#new_step,
.panel-step-attachment {
ul {
@ -128,71 +135,51 @@
.attachments {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
.pseudo-attachment-container {
display: flex;
grid-auto-rows: var(--attachment-card-thumbnail-height);
grid-column-gap: 1em;
grid-row-gap: 1em;
grid-template-columns: repeat(auto-fill, minmax(var(--attachment-card-thumbnail-width), 1fr));
justify-content: center;
overflow: hidden;
.file-preview-link {
position: relative;
margin: 1em 0;
}
&.new {
order: 0 !important;
.file-preview-link {
transition: .5s;
}
.attachment-placeholder {
border: 1px solid $brand-primary;
&::before {
background: $brand-primary;
border-radius: 0 5px;
bottom: 16px;
color: $color-white;
content: "NEW";
left: 8px;
line-height: 20px;
position: absolute;
width: 50px;
}
}
}
}
}
.attachment-placeholder {
.attachment-container {
@include md-card-style;
color: $color-silver-chalice;
height: 280px;
margin: 4px 8px 16px;
text-align: center;
width: 220px;
a {
color: inherit;
}
&.new {
background-color: rgba(95, 95, 95, .1);
}
.attachment-thumbnail {
display: inline-block;
height: 160px;
margin: 16px 0 5px;
height: var(--attachment-card-thumbnail-height);
justify-self: center;
overflow: hidden;
padding: 0 10px;
padding: 1em;
position: relative;
width: var(--attachment-card-thumbnail-width);
.file-preview-link {
display: block;
height: 100%;
width: 100%;
}
.show-inline {
background: $color-white;
border-radius: $border-radius-default;
line-height: 1em;
padding: .5em;
position: absolute;
right: 0;
text-align: center;
top: 0;
width: 2em;
z-index: 10;
}
.attachment-preview {
border-radius: $border-radius-default;
height: 170px;
position: relative;
text-align: center;
width: 100%;
img {
border-radius: 5px;
max-height: 100%;
max-width: 100%;
}
@ -201,15 +188,15 @@
&::before,
&::after {
border-radius: 1em 0 0 1em;
bottom: 1em;
content: "";
border-radius: 16px 0 0 16px;
display: block;
height: 32px;
right: 0;
line-height: 32px;
height: 2em;
line-height: 2em;
position: absolute;
bottom: 10px;
width: 36px;
right: -1em;
width: 2.25em;
}
&::before {
@ -218,8 +205,9 @@
&::after {
background-image: url("/images/icon_small/marvinjs-white.svg");
right: 4px;
width: 32px;
height: 2.25em;
right: -.75em;
width: 2em;
}
}
@ -233,70 +221,59 @@
box-shadow: none;
}
.attachment-label {
.attachment-label,
.attachment-metadata {
background: $color-white;
color: $brand-primary;
font-family: Lato;
font-size: 16px;
height: 40px;
line-height: 18px;
margin: 0 auto;
overflow: hidden;
padding-top: 1em;
position: relative;
text-align: center;
top: 20px;
text-overflow: ellipsis;
transition: $md-transaction;
width: 190px;
white-space: nowrap;
}
.attachment-label {
@include font-main;
margin-top: 1.5em;
z-index: 2;
}
.spencer-bonnet-modif {
align-items: center;
.attachment-metadata {
@include font-small;
color: $color-silver-chalice;
display: flex;
font-family: Lato;
font-size: 12px;
height: 40px;
justify-content: center;
line-height: 15px;
margin: 0 auto 5px;
position: relative;
text-align: center;
top: -20px;
transition: $md-transaction;
width: 149px;
margin-top: -4em;
}
.remove-icon {
bottom: 15px;
bottom: .5em;
cursor: pointer;
display: none;
position: relative;
right: 10px;
position: absolute;
right: .5em;
}
&:hover {
box-shadow: $md-shadow;
.file-preview-link {
text-decoration: none;
}
.remove-icon {
display: inline-block;
}
.attachment-label {
top: 0;
}
.attachment-label,
.attachment-metadata {
margin-top: 0;
.spencer-bonnet-modif {
top: 0;
}
}
&.new {
background-color: rgba(95, 95, 95, .1);
.attachment-label {
background-color: transparent;
}
border: 1px solid $brand-primary;
.dnd-error {
bottom: 15px;
@ -305,10 +282,18 @@
position: relative;
}
&:hover {
.attachment-label {
top: 20px;
}
&::before {
background: $brand-primary;
border-radius: 0 $border-radius-default;
bottom: 0;
color: $color-white;
content: "NEW";
left: 0;
line-height: 20px;
position: absolute;
text-align: center;
width: 50px;
z-index: 2;
}
}
}
@ -354,3 +339,40 @@
.expand-all-steps {
margin: 0 0 15px 15px;
}
.inline-attachment-container {
@include md-card-style;
grid-column: 1/-1;
grid-row: span 2;
.active-iframe-preview {
border: 0;
height: calc(100% - 4em);
width: 100%;
}
.header {
align-items: center;
display: flex;
height: 4em;
padding: 0 1em;
.show-as-thumbnail {
cursor: pointer;
margin-left: auto;
}
.file-name {
@include font-main;
color: $brand-primary;
}
.file-metadata {
@include font-small;
color: $color-silver-chalice;
display: grid;
grid-column-gap: 1em;
grid-template-columns: max-content max-content;
}
}
}

View file

@ -1458,31 +1458,6 @@ ul.content-activities {
}
}
.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;
}
&.processing {
background-image: url("/images/medium/processing.gif");
background-position: center;
background-repeat: no-repeat;
}
}
// Image preview modal
.modal-file-preview {

View file

@ -16,7 +16,7 @@ class AssetsController < ApplicationController
before_action :load_vars, except: :create_wopi_file
before_action :check_read_permission, except: :edit
before_action :check_edit_permission, only: :edit
before_action :check_edit_permission, only: %i(edit toggle_view_mode)
def file_preview
file_type = @asset.file.metadata[:asset_type] || (@asset.previewable? ? 'previewable' : false)
@ -98,22 +98,16 @@ class AssetsController < ApplicationController
end
def toggle_view_mode
# view_mode: card / inline
# @asset.update!(view_mode: toggle_view_mode_params[:view_mode])
html = if @asset.inline_card && wopi_enabled? && wopi_file?(@asset)
url = @asset.get_action_url(current_user, 'embedview')
"<iframe src=\"#{url}\" title=\"DocumentPreview\"></iframe>"
else
render_to_string(partial: 'shared/asset_placeholder.html.erb', locals: { asset: @asset, edit_page: false })
end
@asset.view_mode = toggle_view_mode_params[:view_mode]
if @asset.save(touch: false)
html = render_to_string(partial: 'assets/asset.html.erb', locals: { asset: @asset })
respond_to do |format|
format.json do
render json: { html: html }, status: :ok
end
end
end
end
def file_url
return render_404 unless @asset.file.attached?
@ -168,34 +162,10 @@ class AssetsController < ApplicationController
@asset.post_process_file(@asset.team)
@asset.step&.protocol&.update(updated_at: Time.now)
render_html = if @asset.step
assets = @asset.step.assets
order_atoz = az_ordered_assets_index(@asset.step, @asset.id)
order_ztoa = assets.length - az_ordered_assets_index(@asset.step, @asset.id)
asset_position = @asset.step.asset_position(@asset)
render_html = if @asset.step || @asset.result
render_to_string(
partial: 'steps/attachments/item.html.erb',
locals: {
asset: @asset,
i: asset_position[:pos],
assets_count: asset_position[:count],
step: @asset.step,
order_atoz: order_atoz,
order_ztoa: order_ztoa
},
formats: :html
)
elsif @asset.result
render_to_string(
partial: 'steps/attachments/item.html.erb',
locals: {
asset: @asset,
i: 0,
assets_count: 0,
step: nil,
order_atoz: 0,
order_ztoa: 0
},
partial: 'assets/asset.html.erb',
locals: { asset: @asset },
formats: :html
)
else
@ -230,7 +200,7 @@ class AssetsController < ApplicationController
unless asset.valid?(:wopi_file_creation)
render json: {
message: asset.errors
}, status: 400 and return
}, status: :bad_request and return
end
# Create file depending on the type
@ -270,7 +240,7 @@ class AssetsController < ApplicationController
private
def load_vars
@asset = Asset.find_by_id(params[:id])
@asset = Asset.find_by(id: params[:id])
return render_404 unless @asset
@assoc ||= @asset.step

View file

@ -17,15 +17,7 @@ class MarvinJsAssetsController < ApplicationController
if result[:asset] && marvin_params[:object_type] == 'Step'
render json: {
html: render_to_string(
partial: 'steps/attachments/item.html.erb',
locals: { asset: result[:asset],
i: 0,
assets_count: 0,
step: result[:object],
order_atoz: 0,
order_ztoa: 0 }
)
html: render_to_string(partial: 'assets/asset.html.erb', locals: { asset: result[:asset] })
}
elsif result[:asset] && marvin_params[:object_type] == 'Result'
@my_module = result[:object].my_module
@ -63,7 +55,7 @@ class MarvinJsAssetsController < ApplicationController
private
def load_vars
@asset = current_team.assets.find_by_id(params[:id])
@asset = current_team.assets.find_by(id: params[:id])
return render_404 unless @asset
@assoc ||= @asset.step
@ -77,8 +69,8 @@ class MarvinJsAssetsController < ApplicationController
end
def load_create_vars
@assoc = Step.find_by_id(marvin_params[:object_id]) if marvin_params[:object_type] == 'Step'
@assoc = MyModule.find_by_id(params[:object_id]) if marvin_params[:object_type] == 'Result'
@assoc = Step.find_by(id: marvin_params[:object_id]) if marvin_params[:object_type] == 'Step'
@assoc = MyModule.find_by(id: params[:object_id]) if marvin_params[:object_type] == 'Result'
if @assoc.class == Step
@protocol = @assoc.protocol

View file

@ -1,4 +1,6 @@
class WopiController < ActionController::Base
# frozen_string_literal: true
class WopiController < ApplicationController
include WopiUtil
skip_before_action :verify_authenticity_token
@ -334,7 +336,7 @@ class WopiController < ActionController::Base
url = request.original_url.upcase.encode('utf-8')
if convert_to_unix_timestamp(timestamp) + 20.minutes >= Time.now
if current_wopi_discovery.verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
if wopi_verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
logger.warn 'WOPI: proof verification: successful'
else
logger.warn 'WOPI: proof verification: not verified'

View file

@ -317,7 +317,7 @@ class Asset < ApplicationRecord
file_ext = file_name.split('.').last
action = get_action(file_ext, action)
if !action.nil?
action_url = action.urlsrc
action_url = action[:urlsrc]
if ENV['WOPI_BUSINESS_USERS'] && ENV['WOPI_BUSINESS_USERS'] == 'true'
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/,
'IsLicensedUser=1&')
@ -351,7 +351,7 @@ class Asset < ApplicationRecord
def favicon_url(action)
file_ext = file_name.split('.').last
action = get_action(file_ext, action)
action.wopi_app.icon if action.try(:wopi_app)
action[:icon] if action[:icon]
end
# locked?, lock_asset and refresh_lock rely on the asset
@ -402,14 +402,6 @@ class Asset < ApplicationRecord
return convert_variant_to_base64(medium_preview) if style == :medium
end
def small_card
false
end
def inline_card
!small_card
end
private
def tempdir

View file

@ -4,9 +4,4 @@ class WopiAction < ApplicationRecord
belongs_to :wopi_app, foreign_key: 'wopi_app_id', class_name: 'WopiApp'
validates :action, :extension, :urlsrc, :wopi_app, presence: true
def self.find_action(extension, activity)
WopiAction.distinct
.where('extension = ? and action = ?', extension, activity).first
end
end

View file

@ -13,43 +13,4 @@ class WopiDiscovery < ApplicationRecord
:proof_key_old_mod,
:proof_key_old_exp,
presence: true
# Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted
# with this discovery public key (two key possible old/new)
def verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
token_length = [token.length].pack('>N').bytes
timestamp_bytes = [timestamp.to_i].pack('>Q').bytes.reverse
timestamp_length = [timestamp_bytes.length].pack('>N').bytes
url_length = [url.length].pack('>N').bytes
expected_proof = token_length + token.bytes +
url_length + url.upcase.bytes +
timestamp_length + timestamp_bytes
key = generate_key(proof_key_mod, proof_key_exp)
old_key = generate_key(proof_key_old_mod, proof_key_old_exp)
# Try all possible combiniations
try_verification(expected_proof, signed_proof, key) ||
try_verification(expected_proof, signed_proof_old, key) ||
try_verification(expected_proof, signed_proof, old_key)
end
# Generates a public key from given modulus and exponent
def generate_key(modulus, exponent)
mod = Base64.decode64(modulus).unpack('H*').first.to_i(16)
exp = Base64.decode64(exponent).unpack('H*').first.to_i(16)
seq = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(mod),
OpenSSL::ASN1::Integer.new(exp)])
OpenSSL::PKey::RSA.new(seq.to_der)
end
# Verify if decrypting signed_proof with public_key equals to expected_proof
def try_verification(expected_proof, signed_proof_b64, public_key)
signed_proof = Base64.decode64(signed_proof_b64)
public_key.verify(OpenSSL::Digest::SHA256.new, signed_proof,
expected_proof.pack('c*'))
end
end

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module WopiUtil
require 'open-uri'
@ -5,72 +7,101 @@ module WopiUtil
UNIX_EPOCH_IN_CLR_TICKS = 621355968000000000
CLR_TICKS_PER_SECOND = 10000000
DISCOVERY_TTL = 1.days
DISCOVERY_TTL = 1.day
DISCOVERY_TTL.freeze
# For more explanation see this:
# http://stackoverflow.com/questions/11888053/
# convert-net-datetime-ticks-property-to-date-in-objective-c
def convert_to_unix_timestamp(timestamp)
Time.at((timestamp - UNIX_EPOCH_IN_CLR_TICKS) / CLR_TICKS_PER_SECOND)
Time.zone.at((timestamp - UNIX_EPOCH_IN_CLR_TICKS) / CLR_TICKS_PER_SECOND)
end
def get_action(extension, activity)
current_wopi_discovery
WopiAction.find_action(extension, activity)
def get_action(extension, action)
discovery = current_wopi_discovery
discovery[:actions].find { |i| i[:extension] == extension && i[:action] == action }
end
def current_wopi_discovery
discovery = WopiDiscovery.first
return discovery if discovery && discovery.expires >= Time.now.to_i
initialize_discovery(discovery)
initialize_discovery
end
# Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted
# with this discovery public key (two key possible old/new)
def wopi_verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
discovery = current_wopi_discovery
token_length = [token.length].pack('>N').bytes
timestamp_bytes = [timestamp.to_i].pack('>Q').bytes.reverse
timestamp_length = [timestamp_bytes.length].pack('>N').bytes
url_length = [url.length].pack('>N').bytes
expected_proof = token_length + token.bytes +
url_length + url.upcase.bytes +
timestamp_length + timestamp_bytes
key = generate_key(discovery[:proof_key_mod], discovery[:proof_key_exp])
old_key = generate_key(discovery[:proof_key_old_mod], discovery[:proof_key_old_exp])
# Try all possible combiniations
try_verification(expected_proof, signed_proof, key) ||
try_verification(expected_proof, signed_proof_old, key) ||
try_verification(expected_proof, signed_proof, old_key)
end
private
# Currently only saves Excel, Word and PowerPoint view and edit actions
def initialize_discovery(discovery)
Rails.logger.warn 'Initializing discovery'
discovery.destroy if discovery
def initialize_discovery
Rails.cache.fetch(:wopi_discovery, expires_in: DISCOVERY_TTL) do
@doc = Nokogiri::XML(Kernel.open(ENV['WOPI_DISCOVERY_URL']))
discovery = WopiDiscovery.new
discovery.expires = Time.now.to_i + DISCOVERY_TTL
discovery_json = {}
key = @doc.xpath('//proof-key')
discovery.proof_key_mod = key.xpath('@modulus').first.value
discovery.proof_key_exp = key.xpath('@exponent').first.value
discovery.proof_key_old_mod = key.xpath('@oldmodulus').first.value
discovery.proof_key_old_exp = key.xpath('@oldexponent').first.value
discovery.save!
discovery_json[:proof_key_mod] = key.xpath('@modulus').first.value
discovery_json[:proof_key_exp] = key.xpath('@exponent').first.value
discovery_json[:proof_key_old_mod] = key.xpath('@oldmodulus').first.value
discovery_json[:proof_key_old_exp] = key.xpath('@oldexponent').first.value
discovery_json[:actions] = []
@doc.xpath('//app').each do |app|
app_name = app.xpath('@name').first.value
next unless %w(Excel Word PowerPoint WopiTest).include?(app_name)
wopi_app = WopiApp.new
wopi_app.name = app.xpath('@name').first.value
wopi_app.icon = app.xpath('@favIconUrl').first.value
wopi_app.wopi_discovery_id = discovery.id
wopi_app.save!
app.xpath('action').each do |action|
name = action.xpath('@name').first.value
next unless %w(view edit editnew embedview wopitest).include?(name)
icon = app.xpath('@favIconUrl').first.value
wopi_action = WopiAction.new
wopi_action.action = name
wopi_action.extension = action.xpath('@ext').first.value
wopi_action.urlsrc = action.xpath('@urlsrc').first.value
wopi_action.wopi_app_id = wopi_app.id
wopi_action.save!
app.xpath('action').each do |action|
action_name = action.xpath('@name').first.value
next unless %w(view edit editnew embedview wopitest).include?(action_name)
action_json = {}
action_json[:icon] = icon
action_json[:action] = action_name
action_json[:extension] = action.xpath('@ext').first.value
action_json[:urlsrc] = action.xpath('@urlsrc').first.value
discovery_json[:actions].push(action_json)
end
end
discovery
rescue => e
discovery_json
end
rescue StandardError => e
Rails.logger.warn 'WOPI: initialization failed: ' + e.message
e.backtrace.each { |line| Rails.logger.error line }
discovery = WopiDiscovery.first
discovery.destroy if discovery
end
# Generates a public key from given modulus and exponent
def generate_key(modulus, exponent)
mod = Base64.decode64(modulus).unpack1('H*').to_i(16)
exp = Base64.decode64(exponent).unpack1('H*').to_i(16)
seq = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(mod),
OpenSSL::ASN1::Integer.new(exp)])
OpenSSL::PKey::RSA.new(seq.to_der)
end
# Verify if decrypting signed_proof with public_key equals to expected_proof
def try_verification(expected_proof, signed_proof_b64, public_key)
signed_proof = Base64.decode64(signed_proof_b64)
public_key.verify(OpenSSL::Digest::SHA256.new, signed_proof,
expected_proof.pack('c*'))
end
def create_wopi_file_activity(current_user, started_editing)

View file

@ -0,0 +1,5 @@
<% if wopi_enabled? && wopi_file?(asset) && asset.show_inline? %>
<%= render partial: 'assets/asset_inline.html.erb', locals: { asset: asset } %>
<% else %>
<%= render partial: 'assets/asset_thumbnail.html.erb', locals: { edit_page: false, asset: asset } %>
<% end %>

View file

@ -0,0 +1,25 @@
<div class="inline-attachment-container step-asset"
data-asset-id="<%= asset.id %>"
data-asset-file-name="<%= asset.file_name %>"
data-asset-updated-at="<%= asset.updated_at.to_i %>"
data-asset-order="0"
>
<div class="header">
<div class="file-info">
<div class="file-name">
<%= asset.file_name %>
</div>
<div class="file-metadata">
<span><%= t('assets.placeholder.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %></span>
<span><%= number_to_human_size(asset.file_size) %></span>
</div>
</div>
<div class="show-as-thumbnail change-preview-type"
data-preview-type="show_as_thumbnail"
data-asset-id="<%= asset.id %>" >
<i class="fas fa-compress"></i>
</div>
</div>
<div class="iframe-placeholder" data-iframe-url="<%= asset.get_action_url(current_user, 'embedview') %>"></div>
</div>

View file

@ -0,0 +1,40 @@
<div class="attachment-container step-asset"
data-asset-id="<%= asset.id %>"
data-asset-file-name="<%= asset.file_name %>"
data-asset-updated-at="<%= asset.updated_at.to_i %>"
data-asset-order="1"
>
<%= link_to rails_blob_path(asset.file, disposition: 'attachment'),
class: "file-preview-link",
id: "modal_link#{asset.id}",
data: { no_turbolink: true, id: true, 'preview-url': asset_file_preview_path(asset)} do %>
<% if wopi_enabled? && wopi_file?(asset) %>
<div class="show-inline change-preview-type"
data-preview-type="show_inline"
data-asset-id="<%= asset.id %>">
<i class="fas fa-expand"></i>
</div>
<% end %>
<div class="attachment-preview <%= asset.file.attached? ? asset.file.metadata['asset_type'] : '' %>">
<% if asset.previewable? %>
<%= image_tag asset.medium_preview,
onerror: 'ActiveStoragePreviews.reCheckPreview(event)',
onload: 'ActiveStoragePreviews.showPreview(event)',
style: 'opacity: 0' %>
<% else %>
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
<% end %>
</div>
<div class="attachment-label">
<% if asset.file.attached? && asset&.file&.metadata['asset_type'] %>
<%= asset.file.metadata['name'] %>
<% else %>
<%= asset.file_name %>
<% end %>
</div>
<div class="attachment-metadata">
<%= 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>
<% end %>
</div>

View file

@ -1,9 +1,9 @@
<div class="attachment-placeholder pull-left new">
<div class="attachment-thumbnail no-shadow">
<div class="attachment-container pull-left new">
<div class="attachment-preview no-shadow">
<i class="fas fa-image"></i>
</div>
<div class="attachment-label"><%= truncate(file_name || file_url, length: Constants::FILENAME_TRUNCATION_LENGTH) %>
</div>
<div class="spencer-bonnet-modif">
<div class="attachment-metadata">
</div>
</div>

View file

@ -3,6 +3,5 @@
<% elsif result.is_table %>
<%= render partial: "results/result_table.html.erb", locals: {result: result} %>
<% elsif result.is_asset %>
<%= render partial: 'steps/attachments/item.html.erb',
locals: { asset: result.asset, i: 0, assets_count: 0, step: nil, order_atoz: 0, order_ztoa: 0 } %>
<%= render partial: 'assets/asset.html.erb', locals: { asset: result.asset } %>
<% end %>

View file

@ -1,34 +0,0 @@
<div class="attachment-placeholder pull-left">
<a href="#" class="dataPreviewAction" data-asset-id="<%= asset.id %>" data-preview-type="inline">InLine</a>
<div class="attachment-thumbnail <%= asset.previewable? ? '' : 'no-shadow' %> <%= asset.file.attached? ? asset.file.metadata['asset_type'] : '' %>">
<% if asset.previewable? %>
<%= image_tag asset.medium_preview,
onerror: 'ActiveStoragePreviews.reCheckPreview(event)',
onload: 'ActiveStoragePreviews.showPreview(event)',
style: 'opacity: 0' %>
<% else %>
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
<% end %>
</div>
<div class="attachment-label">
<% if asset.file.attached? && asset&.file&.metadata['asset_type'] %>
<%= truncate(asset.file.metadata['name'], length: Constants::FILENAME_TRUNCATION_LENGTH) %>
<% else %>
<%= truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %>
<% end %>
</div>
<div class="spencer-bonnet-modif">
<%= 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>
<% if edit_page %>
<div class="remove-icon pull-right">
<% unless ff.object.file.attached? && ff.object.locked? %>
<%= ff.remove_nested_fields_link do %>
<span class="fas fa-trash"></span>
<% end %>
<% end %>
</div>
<% end %>
</div>

View file

@ -72,8 +72,8 @@
<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: 'shared/asset_placeholder.html.erb',
locals: { edit_page: true, asset: ff.object, ff: ff } %>
<%= render partial: 'steps/attachments/edit_item.html.erb',
locals: { asset: ff.object, ff: ff } %>
<% end %>
</div>
</div>

View file

@ -0,0 +1,30 @@
<div class="attachment-container step-asset" data-asset-id="<%= asset.id %>">
<div class="attachment-preview <%= asset.file.attached? ? asset.file.metadata['asset_type'] : '' %>">
<% if asset.previewable? %>
<%= image_tag asset.medium_preview,
onerror: 'ActiveStoragePreviews.reCheckPreview(event)',
onload: 'ActiveStoragePreviews.showPreview(event)',
style: 'opacity: 0' %>
<% else %>
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
<% end %>
</div>
<div class="attachment-label">
<% if asset.file.attached? && asset&.file&.metadata['asset_type'] %>
<%= truncate(asset.file.metadata['name'], length: Constants::FILENAME_TRUNCATION_LENGTH) %>
<% else %>
<%= truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %>
<% end %>
</div>
<div class="attachment-metadata">
<%= 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>
<% unless asset.locked? %>
<div class="remove-icon pull-right">
<%= ff.remove_nested_fields_link do %>
<span class="fas fa-trash"></span>
<% end %>
</div>
<% end %>
</div>

View file

@ -1,17 +0,0 @@
<div class="pseudo-attachment-container" style="order: <%= i %>">
<%= link_to rails_blob_path(asset.file, disposition: 'attachment'),
class: 'file-preview-link',
id: "modal_link#{asset.id}",
data: { no_turbolink: true,
id: true,
'preview-url': asset_file_preview_path(asset),
'order-atoz': order_atoz,
'order-ztoa': order_ztoa,
'order-new': i,
'order-old': assets_count - i,
} do %>
<%= render partial: 'shared/asset_placeholder.html.erb', locals: { edit_page: false, asset: asset } %>
<% end %>
</div>

View file

@ -1,4 +1,5 @@
<% assets = ordered_assets(step) %>
<% assets = step.assets %>
<% current_order = step.current_view_state(current_user).state.dig('assets', 'sort') %>
<div class="col-xs-12">
<hr>
</div>
@ -15,23 +16,21 @@
<% if !(preview) && (can_manage_protocol_in_module?(@protocol) ||
can_manage_protocol_in_repository?(@protocol)) %>
<%= render partial: '/assets/marvinjs/create_marvin_sketch_button.html.erb',
locals: { element_id: step.id, element_type: 'Step', sketch_container: ".attachments#att-#{step.id}" } %>
locals: { element_id: step.id, element_type: 'Step', sketch_container: ".attachments[data-step-id=#{step.id}]" } %>
<%= render partial: '/assets/wopi/create_wopi_file_button.html.erb',
locals: { element_id: step.id, element_type: 'Step' } %>
<% end %>
<div class="dropdown attachments-order" id="dd-att-step-<%= step.id %>">
<button class="btn btn-default dropdown-toggle" type="button" id="sortMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span id="dd-att-step-<%= step.id %>-label">
<%= t("general.sort.#{step.current_view_state(current_user).state.dig('assets', 'sort')}_html") %>
<div class="dropdown attachments-order" data-step-id="<%= step.id %>" data-state-save-path="<%= update_view_state_step_path(step.id) %>">
<button class="btn btn-light dropdown-toggle" type="button" id="sortMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span class="selected-order">
<%= t("general.sort.#{current_order}_html") %>
</span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="sortMenu" data-state-save-path="<%= update_view_state_step_path(step.id) %>">
<ul class="dropdown-menu" aria-labelledby="sortMenu" >
<% ['new', 'old', 'atoz', 'ztoa'].each do |sort| %>
<li>
<a data-order="<%= sort %>" onClick="reorderAttachments(this, '<%= step.id %>', '<%= sort %>')">
<%= t("general.sort.#{sort}_html") %>
</a>
<a data-order="<%= sort %>" class="change-order"><%= t("general.sort.#{sort}_html") %></a>
</li>
<% end %>
</ul>
@ -40,12 +39,9 @@
</div>
</div>
<div class="col-xs-12 attachments" id="att-<%= step.id %>">
<div class="col-xs-12 attachments" data-step-id="<%= step.id %>" data-order="<%= current_order %>">
<% assets.each_with_index do |asset, i| %>
<% order_atoz = az_ordered_assets_index(step, asset.id) %>
<% order_ztoa = assets.length - az_ordered_assets_index(step, asset.id) %>
<%= render partial: 'steps/attachments/item.html.erb',
locals: { asset: asset, i: i, assets_count: assets.length, step: step, order_atoz: order_atoz, order_ztoa: order_ztoa } %>
<%= render partial: 'assets/asset.html.erb', locals: { asset: asset } %>
<% end %>
</div>
<hr>

View file

@ -67,44 +67,4 @@ describe AssetsController, type: :controller do
.to(change { Activity.count })
end
end
describe 'GET asset_card' do
let(:action) { get :toggle_view_mode, params: params, format: :json }
let!(:params) do
{ id: asset.id }
end
it do
action
expect(response).to have_http_status 200
end
context 'when small card' do
it do
action
expect(response).to have_http_status 200
# wtf, not working here?, render_to_string is returning nil
# expect(JSON.parse(response.body)['html']).to be_eql 'hellow world'
end
end
context 'when iframe' do
before do
allow_any_instance_of(Asset).to receive(:small_card).and_return(false)
allow_any_instance_of(Asset).to receive(:get_action_url).and_return('fakeurl.com')
allow(controller).to receive(:wopi_enabled?).and_return(true)
allow(controller).to receive(:wopi_file?).and_return(true)
end
it do
action
expect(response).to have_http_status 200
expect(JSON.parse(response.body)['html'])
.to be_eql '<iframe src="fakeurl.com" title="DocumentPreview"></iframe>'
end
end
end
end