mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-11-10 17:36:33 +08:00
Merge branch 'master' into features/marvinjs-integration
This commit is contained in:
commit
259b75a6ba
98 changed files with 875 additions and 15314 deletions
4
Makefile
4
Makefile
|
@ -82,10 +82,10 @@ integration-tests:
|
|||
@$(MAKE) rails cmd="bundle exec cucumber"
|
||||
|
||||
tests-ci:
|
||||
@docker-compose run --rm web bash -c "bundle install && npm install"
|
||||
@docker-compose run --rm web bash -c "bundle install && yarn install"
|
||||
@docker-compose up -d webpack
|
||||
@docker-compose ps
|
||||
@docker-compose run -e ENABLE_EMAIL_CONFIRMATIONS=false -e MAILER_PORT=$MAILER_PORT -e SMTP_DOMAIN=$SMTP_DOMAIN -e SMTP_USERNAME=$SMTP_USERNAME -e SMTP_PASSWORD=$SMTP_PASSWORD -e SMTP_ADDRESS=$SMTP_ADDRESS -e PAPERCLIP_HASH_SECRET=PAPERCLIP_HASH_SECRET -e MAIL_SERVER_URL=localhost -e PAPERCLIP_STORAGE=filesystem -e ENABLE_RECAPTCHA=false -e ENABLE_USER_CONFIRMATION=false -e ENABLE_USER_REGISTRATION=true -e CORE_API_RATE_LIMIT=1000000 --rm web bash -c "rake db:create db:migrate && rake db:migrate RAILS_ENV=test && npm install && bundle exec rspec && bundle exec cucumber"
|
||||
@docker-compose run -e ENABLE_EMAIL_CONFIRMATIONS=false -e MAILER_PORT=$MAILER_PORT -e SMTP_DOMAIN=$SMTP_DOMAIN -e SMTP_USERNAME=$SMTP_USERNAME -e SMTP_PASSWORD=$SMTP_PASSWORD -e SMTP_ADDRESS=$SMTP_ADDRESS -e PAPERCLIP_HASH_SECRET=PAPERCLIP_HASH_SECRET -e MAIL_SERVER_URL=localhost -e PAPERCLIP_STORAGE=filesystem -e ENABLE_RECAPTCHA=false -e ENABLE_USER_CONFIRMATION=false -e ENABLE_USER_REGISTRATION=true -e CORE_API_RATE_LIMIT=1000000 --rm web bash -c "rake db:create db:migrate && rake db:migrate RAILS_ENV=test && yarn install && bundle exec rspec && bundle exec cucumber"
|
||||
|
||||
console:
|
||||
@$(MAKE) rails cmd="rails console"
|
||||
|
|
11
app/assets/images/office/office.svg
Normal file
11
app/assets/images/office/office.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 53.59 64.356" width="53.589577" height="64.356148">
|
||||
<g transform="translate(-216.07358,-549.28882)">
|
||||
<g transform="matrix(1.8232952,0,0,1.8232952,-597.71681,-124.12247)">
|
||||
<g transform="translate(0,-91.137241)">
|
||||
<g fill="#eb3c00" transform="matrix(0.74069815,0,0,0.74069815,98.5698,-8.2505871)">
|
||||
<path d="m469.87,671.03,0-28.52,25.229-9.3238,13.711,4.3877,0,38.392-13.711,4.133-25.229-9.0691,25.229,3.0361,0-33.201-16.454,3.8392,0,22.487z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 627 B |
|
@ -1,6 +1,10 @@
|
|||
/* global animateSpinner FilePreviewModal */
|
||||
|
||||
function setupAssetsLoading() {
|
||||
var DELAY = 2500;
|
||||
var REPETITIONS = 60;
|
||||
var cntr = 0;
|
||||
var intervalId;
|
||||
|
||||
function refreshAssets() {
|
||||
var elements = $("[data-status='asset-loading']");
|
||||
|
@ -15,50 +19,55 @@ function setupAssetsLoading() {
|
|||
// Perform an AJAX call to present URL
|
||||
// to check if file already exists
|
||||
$.ajax({
|
||||
url: $el.data("present-url"),
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
var wopiBtns;
|
||||
$el.attr("data-status", "asset-loaded");
|
||||
url: $el.data('present-url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data.processing === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.processing === false) {
|
||||
$el.html(data.placeholder_html);
|
||||
$el.attr('data-status', 'asset-loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
$el.attr('data-status', 'asset-loaded');
|
||||
$el.find('img').hide();
|
||||
$el.next().hide();
|
||||
$el.html("");
|
||||
$el.html('');
|
||||
|
||||
if (data.type === 'image') {
|
||||
$el.html(
|
||||
"<a class='file-preview-link' id='modal_link" +
|
||||
data['asset-id'] + "' data-status='asset-present' " +
|
||||
"href='" + data['download-url'] + "' data-preview-url='" +
|
||||
data['preview-url'] + "'>" +
|
||||
"<img src='" + data['image-tag-url'] + "'><p>" +
|
||||
data.filename + '</p></a>'
|
||||
"<a class='file-preview-link' id='modal_link"
|
||||
+ data['asset-id'] + "' data-status='asset-present' "
|
||||
+ "href='" + data['download-url'] + "' data-preview-url='" + data['preview-url'] + "'>"
|
||||
+ "<img src='" + data['image-tag-url'] + "'><p>" + data.filename + '</p></a>'
|
||||
);
|
||||
} else {
|
||||
$el.html(
|
||||
"<a class='file-preview-link' id='modal_link" +
|
||||
data['asset-id'] + "' data-status='asset-present' " +
|
||||
"href='" + data['download-url'] + "' data-preview-url='" +
|
||||
data['preview-url'] + "'><p>" +
|
||||
data.filename + '</p></a>'
|
||||
"<a class='file-preview-link' id='modal_link"
|
||||
+ data['asset-id'] + "' data-status='asset-present' "
|
||||
+ "href='" + data['download-url'] + "' data-preview-url='"
|
||||
+ data['preview-url'] + "'><p>" + data.filename + '</p></a>'
|
||||
);
|
||||
}
|
||||
animateSpinner(null, false);
|
||||
FilePreviewModal.init();
|
||||
},
|
||||
error: function(data) {
|
||||
if (data.status == 403) {
|
||||
if (data.status === 403) {
|
||||
$el.find('img').hide();
|
||||
$el.next().hide();
|
||||
// Image/file exists, but user doesn't have
|
||||
// rights to download it
|
||||
if (type === "image") {
|
||||
if (data.type === 'image') {
|
||||
$el.html(
|
||||
"<img src='" + data['image-tag-url'] + "'><p>" +
|
||||
data.filename + "</p>"
|
||||
"<img src='" + data['image-tag-url'] + "'><p>" + data.filename + '</p>'
|
||||
);
|
||||
} else {
|
||||
$el.html("<p>" + data.filename + "</p>");
|
||||
$el.html('<p>' + data.filename + '</p>');
|
||||
}
|
||||
} else {
|
||||
// Do nothing, file is not yet present
|
||||
|
@ -76,14 +85,15 @@ function setupAssetsLoading() {
|
|||
|
||||
$.each(elements, function(_, el) {
|
||||
var $el = $(el);
|
||||
$el.attr("data-status", "asset-failed");
|
||||
$el.html($el.data("filename"));
|
||||
$el.attr('data-status', 'asset-failed');
|
||||
if ($el.data('filename')) {
|
||||
$el.html($el.data('filename'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var cntr = 0;
|
||||
var intervalId = window.setInterval(function() {
|
||||
cntr++;
|
||||
intervalId = window.setInterval(function() {
|
||||
cntr += 1;
|
||||
if (cntr >= REPETITIONS || !refreshAssets()) {
|
||||
finalizeAssets();
|
||||
window.clearInterval(intervalId);
|
||||
|
|
|
@ -20,9 +20,8 @@ function initCreateWopiFileModal() {
|
|||
$('#new-office-file-modal form')
|
||||
.on('ajax:success', function(ev, data) {
|
||||
window.open(data.edit_url, '_blank');
|
||||
$('#new-office-file-modal').modal('hide');
|
||||
|
||||
// location.reload();
|
||||
window.focus();
|
||||
location.reload(); // Reload current page, to display the new element
|
||||
})
|
||||
.on('ajax:error', function(ev, response) {
|
||||
var element;
|
||||
|
|
|
@ -206,6 +206,24 @@ var Comments = (function() {
|
|||
$form.submit();
|
||||
});
|
||||
|
||||
$form.find('textarea').on('focus', function(){
|
||||
$(this).addClass('border');
|
||||
if (this.value.length > 0) {
|
||||
$submitBtn.addClass('show');
|
||||
}
|
||||
}).on('blur',function(){
|
||||
if (this.value.length == 0) {
|
||||
$(this).removeClass('border');
|
||||
$submitBtn.removeClass('show');
|
||||
}
|
||||
}).on('keyup',function(){
|
||||
if (this.value.length > 0) {
|
||||
$submitBtn.addClass('show');
|
||||
} else {
|
||||
$submitBtn.removeClass('show');
|
||||
}
|
||||
})
|
||||
|
||||
$('.help-block', $form).addClass('hide');
|
||||
|
||||
$form.off().on('ajax:send', function () {
|
||||
|
@ -220,6 +238,7 @@ var Comments = (function() {
|
|||
$('.form-group', $form).removeClass('has-error');
|
||||
$('.help-block', $form).html('').addClass('hide');
|
||||
$submitBtn.removeClass('has-error');
|
||||
$submitBtn.removeClass('show');
|
||||
|
||||
var currnetCount = $('#counter-' + stepId).html()
|
||||
$('#counter-' + stepId).html(parseInt(currnetCount) + 1)
|
||||
|
|
|
@ -17,7 +17,6 @@ function init() {
|
|||
initLoadFromRepository();
|
||||
initRefreshStatusBar();
|
||||
initImport();
|
||||
setupAssetsLoading();
|
||||
}
|
||||
|
||||
function initEditMyModuleDescription() {
|
||||
|
@ -170,6 +169,10 @@ function initLinkUpdate() {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('[data-role="protocol-status-bar"] .preview-protocol').click(function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function initLoadFromRepository() {
|
||||
|
|
|
@ -293,17 +293,16 @@ function importProtocolFromFile(
|
|||
stepGuid,
|
||||
element.getAttribute('fileref'));
|
||||
|
||||
if (description.includes('[~tiny_mce_id')) {
|
||||
// old format load
|
||||
imageTag = '<img style="max-width:300px; max-height:300px;" src="data:' + element.children[1].innerHTML + ';base64,' + assetBytes + '" />';
|
||||
description = description.replace(match, imageTag);
|
||||
} else {
|
||||
// new format load
|
||||
description = $('<div>' + description + '</div>').find('img[data-mce-token="' + element.getAttribute('tokenId') + '"]')
|
||||
.attr('src', 'data:' + element.children[1].innerHTML + ';base64,' + assetBytes).prop('outerHTML');
|
||||
}
|
||||
});
|
||||
// new format load
|
||||
description = $('<div>' + description + '</div>');
|
||||
description.find('img[data-mce-token="' + element.getAttribute('tokenId') + '"]')
|
||||
.attr('src', 'data:' + element.children[1].innerHTML + ';base64,' + assetBytes);
|
||||
description = description.prop('outerHTML');
|
||||
|
||||
// old format load
|
||||
imageTag = '<img style="max-width:300px; max-height:300px;" src="data:' + element.children[1].innerHTML + ';base64,' + assetBytes + '" />';
|
||||
description = description.replace(match, imageTag);
|
||||
});
|
||||
// I know is crazy but is the only way I found to pass valid HTML
|
||||
return $('<div></div>').html(description).html();
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@ function initializeNewElement(newEl) {
|
|||
switch (parent.data("type")) {
|
||||
case "experiment":
|
||||
url = dh.data("add-experiment-contents-url"); break;
|
||||
case 'my_module' || 'protocol':
|
||||
case 'my_module':
|
||||
url = dh.data("add-module-contents-url"); break;
|
||||
case "step":
|
||||
url = dh.data("add-step-contents-url"); break;
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "initInlineEditing" }]*/
|
||||
/* global SmartAnnotation */
|
||||
function initInlineEditing(title) {
|
||||
var editBlocks = $('.' + title + '-editable-field');
|
||||
|
||||
function prepareText(text) {
|
||||
return text.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
||||
}
|
||||
|
||||
$.each(editBlocks, function(i, element) {
|
||||
var editBlock = element;
|
||||
var $editBlock = $(editBlock);
|
||||
|
@ -12,19 +17,16 @@ function initInlineEditing(title) {
|
|||
$editBlock.addClass('inline-edit-active');
|
||||
if ($inputString.length === 0) {
|
||||
$inputString = $editBlock.find('textarea');
|
||||
$inputString.off('keydown').on('keydown', function() {
|
||||
var el = this;
|
||||
setTimeout(() => {
|
||||
el.style.cssText = 'height:0px; padding:0';
|
||||
el.style.cssText = 'height:' + (el.scrollHeight + 10) + 'px';
|
||||
}, 0);
|
||||
});
|
||||
$inputString.keydown();
|
||||
}
|
||||
inputString = $inputString[0]
|
||||
|
||||
function cancelAllEditFields() {
|
||||
$('.inline-edit-active').find('.cancel-button').click();
|
||||
inputString = $inputString[0];
|
||||
|
||||
if (editBlock.dataset.smartAnnotation === 'true') {
|
||||
SmartAnnotation.init($inputString);
|
||||
}
|
||||
|
||||
function saveAllEditFields() {
|
||||
$('.inline-edit-active').find('.save-button').click();
|
||||
}
|
||||
|
||||
function updateField() {
|
||||
|
@ -33,6 +35,8 @@ function initInlineEditing(title) {
|
|||
if (inputString.value === editBlock.dataset.originalName) {
|
||||
inputString.disabled = true;
|
||||
editBlock.dataset.editMode = 0;
|
||||
$inputString.addClass('hidden');
|
||||
$editBlock.find('.view-mode').removeClass('hidden');
|
||||
return false;
|
||||
}
|
||||
params[editBlock.dataset.paramsGroup] = {};
|
||||
|
@ -42,9 +46,21 @@ function initInlineEditing(title) {
|
|||
type: 'PUT',
|
||||
dataType: 'json',
|
||||
data: params,
|
||||
success: function() {
|
||||
success: function(result) {
|
||||
var viewData;
|
||||
if (editBlock.dataset.responseField) {
|
||||
// If we want to modify preview element on backend
|
||||
// we can use this data field and we will take string from response
|
||||
viewData = result[editBlock.dataset.responseField];
|
||||
} else {
|
||||
// By default we just copy value from input string
|
||||
viewData = inputString.value;
|
||||
}
|
||||
editBlock.dataset.originalName = inputString.value;
|
||||
editBlock.dataset.error = false;
|
||||
$inputString.addClass('hidden');
|
||||
$editBlock.find('.view-mode').html(prepareText(viewData)).removeClass('hidden');
|
||||
|
||||
inputString.disabled = true;
|
||||
editBlock.dataset.editMode = 0;
|
||||
},
|
||||
|
@ -63,21 +79,28 @@ function initInlineEditing(title) {
|
|||
return true;
|
||||
}
|
||||
|
||||
$editBlock.click(e => {
|
||||
cancelAllEditFields();
|
||||
$editBlock.click((e) => {
|
||||
// 'A' mean that, if we click on <a></a> element we will not go in edit mode
|
||||
if (e.target.tagName === 'A') return true;
|
||||
if (inputString.disabled) {
|
||||
saveAllEditFields();
|
||||
editBlock.dataset.editMode = 1;
|
||||
inputString.disabled = false;
|
||||
$inputString.removeClass('hidden');
|
||||
$editBlock.find('.view-mode').addClass('hidden');
|
||||
$inputString.focus();
|
||||
}
|
||||
e.stopPropagation();
|
||||
return true;
|
||||
});
|
||||
|
||||
$(window).click(() => {
|
||||
$(window).click((e) => {
|
||||
if ($(e.target).closest('.atwho-view').length > 0) return false;
|
||||
if (inputString.disabled === false) {
|
||||
updateField();
|
||||
}
|
||||
editBlock.dataset.editMode = 0;
|
||||
return true;
|
||||
});
|
||||
|
||||
$($editBlock.find('.save-button')).click(e => {
|
||||
|
@ -90,6 +113,8 @@ function initInlineEditing(title) {
|
|||
editBlock.dataset.editMode = 0;
|
||||
editBlock.dataset.error = false;
|
||||
inputString.value = editBlock.dataset.originalName;
|
||||
$inputString.addClass('hidden');
|
||||
$editBlock.find('.view-mode').removeClass('hidden');
|
||||
$inputString.keydown();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
|
|
@ -190,7 +190,7 @@
|
|||
$('.attachment-placeholder.new').remove();
|
||||
_dragNdropAssetsOff();
|
||||
for(var i = 0; i < droppedFiles.length; i++) {
|
||||
$('.attacments.edit')
|
||||
$('.attachments.edit')
|
||||
.append(_uploadedAssetPreview(droppedFiles[i], i))
|
||||
.promise()
|
||||
.done(function() {
|
||||
|
|
|
@ -308,7 +308,7 @@ var FilePreviewModal = (function() {
|
|||
};
|
||||
|
||||
var mousePosition = {
|
||||
top: e.clientY - $(imageEditorWindow).offset().top,
|
||||
top: e.clientY - (imageEditorWindow.offsetTop - scrollContainerInitial.top),
|
||||
left: e.clientX - $(imageEditorWindow).offset().left
|
||||
};
|
||||
|
||||
|
@ -452,7 +452,7 @@ var FilePreviewModal = (function() {
|
|||
link.attr('data-status', 'asset-present');
|
||||
if (data.type === 'image') {
|
||||
if (data.processing) {
|
||||
animateSpinner('.file-preview-container', true);
|
||||
modal.find('.file-preview-container').append(data['processing-img']);
|
||||
} else {
|
||||
animateSpinner('.file-preview-container', false);
|
||||
modal.find('.file-preview-container')
|
||||
|
|
57
app/assets/javascripts/sitewide/tiny_mce.js
vendored
57
app/assets/javascripts/sitewide/tiny_mce.js
vendored
|
@ -1,4 +1,8 @@
|
|||
|
||||
/* global _ hljs tinyMCE SmartAnnotation MarvinJsEditor FilePreviewModal */
|
||||
|
||||
/* global _ I18n */
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var TinyMCE = (function() {
|
||||
|
@ -16,26 +20,6 @@ var TinyMCE = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
function moveToolbar(editor, editorToolbar, editorToolbaroffset) {
|
||||
var scrollPosition = $(window).scrollTop();
|
||||
var containerOffset;
|
||||
var containerHeight;
|
||||
var toolbarPosition;
|
||||
var toolbarPositionLimit;
|
||||
if (editor.getContainer() === null) return;
|
||||
containerOffset = $(editor.getContainer()).offset().top;
|
||||
containerHeight = $(editor.getContainer()).height();
|
||||
toolbarPosition = scrollPosition - containerOffset + editorToolbaroffset;
|
||||
toolbarPositionLimit = containerHeight - editorToolbaroffset;
|
||||
if (toolbarPosition > 0 && toolbarPosition < toolbarPositionLimit) {
|
||||
editorToolbar.css('top', toolbarPosition + 'px');
|
||||
} else {
|
||||
editorToolbar.css(
|
||||
'top',
|
||||
toolbarPosition < 0 ? '0px' : toolbarPositionLimit + 'px'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function initImageToolBar(editor) {
|
||||
var editorForm = $(editor.getContainer()).closest('form');
|
||||
|
@ -207,14 +191,17 @@ var TinyMCE = (function() {
|
|||
var editorContainer = $(editor.getContainer());
|
||||
var menuBar = editorForm.find('.mce-menubar.mce-toolbar.mce-first .mce-flow-layout');
|
||||
var editorToolbar = editorForm.find('.mce-top-part');
|
||||
|
||||
var editorToolbaroffset = mceConfig.toolbar_offset || 120;
|
||||
var editorIframe = $('#' + editor.id).prev().find('.mce-edit-area iframe');
|
||||
|
||||
var editorToolbaroffset;
|
||||
|
||||
|
||||
$('.tinymce-placeholder').css('height', $(editor.editorContainer).height() + 'px');
|
||||
setTimeout(() => {
|
||||
$(editor.editorContainer).addClass('show');
|
||||
$('.tinymce-placeholder').remove();
|
||||
moveToolbar(editor, editorToolbar, editorToolbaroffset);
|
||||
}, 400);
|
||||
|
||||
// Init saved status label
|
||||
|
@ -222,10 +209,16 @@ var TinyMCE = (function() {
|
|||
editorForm.find('.tinymce-status-badge').removeClass('hidden');
|
||||
}
|
||||
|
||||
// Init Floating toolbar
|
||||
$(window).on('scroll', function() {
|
||||
moveToolbar(editor, editorToolbar, editorToolbaroffset);
|
||||
});
|
||||
if ($('.navbar-secondary').length) {
|
||||
editorToolbaroffset = $('.navbar-secondary').position().top + $('.navbar-secondary').height();
|
||||
} else if ($('#main-nav').length) {
|
||||
editorToolbaroffset = $('#main-nav').height();
|
||||
} else {
|
||||
editorToolbaroffset = 0;
|
||||
}
|
||||
|
||||
editorToolbar.css('position', 'sticky');
|
||||
editorToolbar.css('top', editorToolbaroffset + 'px');
|
||||
|
||||
// Init image toolbar
|
||||
initImageToolBar(editor);
|
||||
|
@ -288,6 +281,7 @@ var TinyMCE = (function() {
|
|||
editor.selection.collapse(false);
|
||||
|
||||
SmartAnnotation.init($(editor.contentDocument.activeElement));
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
initHighlightjsIframe($(this.iframeElement).contents());
|
||||
},
|
||||
setup: function(editor) {
|
||||
|
@ -352,3 +346,16 @@ var TinyMCE = (function() {
|
|||
highlight: initHighlightjs
|
||||
});
|
||||
}());
|
||||
|
||||
$(document).on('turbolinks:before-visit', function(e) {
|
||||
_.each(tinyMCE.editors, function(editor) {
|
||||
if (editor.isNotDirty === false) {
|
||||
if (confirm(I18n.t('tiny_mce.leaving_warning'))) {
|
||||
return false;
|
||||
}
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,4 +23,5 @@
|
|||
@import "select2.min";
|
||||
@import "extend/perfect-scrollbar";
|
||||
@import "my_modules/protocols/*";
|
||||
@import "protocols/*";
|
||||
@import "hooks/*";
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
|
||||
img {
|
||||
margin-right: 5px;
|
||||
width: 23px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
.protocol-info,
|
||||
.module-header {
|
||||
.well {
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
font-size: 14px;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.badge-icon {
|
||||
background: transparent;
|
||||
color: $color-silver;
|
||||
padding-left: 0;
|
||||
padding-right: 5px;
|
||||
|
||||
+ .well-sm {
|
||||
margin-bottom: 5px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.module-header {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
@ -46,25 +69,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.well {
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.badge-icon {
|
||||
background: transparent;
|
||||
color: $color-silver;
|
||||
padding-left: 0;
|
||||
padding-right: 5px;
|
||||
|
||||
+ .well-sm {
|
||||
margin-bottom: 10px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.module-description {
|
||||
float: left;
|
||||
width: 100%;
|
||||
|
@ -76,7 +80,7 @@
|
|||
.title {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
padding: 10px 0 5px;
|
||||
padding: 20px 0 5px;
|
||||
}
|
||||
|
||||
.my-module-description-content {
|
||||
|
@ -97,14 +101,15 @@
|
|||
display: inline-block;
|
||||
line-height: 32px;
|
||||
padding: 0 5px 0 0;
|
||||
width: 38px;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.tags-title {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
padding-right: 3px;
|
||||
width: 35px;
|
||||
width: 37px;
|
||||
}
|
||||
|
||||
.select-container {
|
||||
|
@ -112,7 +117,7 @@
|
|||
flex-basis: 100px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
max-width: calc(100% - 73px);
|
||||
max-width: calc(100% - 65px);
|
||||
position: relative;
|
||||
|
||||
select {
|
||||
|
|
|
@ -54,7 +54,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:hover input {
|
||||
&:hover input,
|
||||
&:hover .view-mode {
|
||||
border: 1px solid $color-silver;
|
||||
|
||||
&:disabled {
|
||||
|
@ -62,6 +63,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.view-mode {
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
line-height: 26px;
|
||||
padding: 8px 5px;
|
||||
padding-right: 36px;
|
||||
width: calc(100% - 36px);
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid $color-silver;
|
||||
border-radius: $border-radius-small;
|
||||
|
|
17
app/assets/stylesheets/protocols/preview_modal.scss
Normal file
17
app/assets/stylesheets/protocols/preview_modal.scss
Normal file
|
@ -0,0 +1,17 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
|
||||
@import "constants";
|
||||
@import "mixins";
|
||||
|
||||
#protocol-preview-modal .modal-dialog {
|
||||
.modal-body {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
|
||||
.ql-editor {
|
||||
min-height: initial;
|
||||
}
|
||||
}
|
||||
}
|
12
app/assets/stylesheets/protocols/protocol.scss
Normal file
12
app/assets/stylesheets/protocols/protocol.scss
Normal file
|
@ -0,0 +1,12 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
|
||||
@import "constants";
|
||||
@import "mixins";
|
||||
|
||||
.protocol-info {
|
||||
|
||||
.protocol-description {
|
||||
padding: 0 48px;
|
||||
}
|
||||
}
|
|
@ -93,7 +93,7 @@
|
|||
overflow: hidden;
|
||||
|
||||
strong {
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
@ -103,17 +103,18 @@
|
|||
}
|
||||
|
||||
.author-block {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
padding-right: 10px;
|
||||
margin-right: 20px;
|
||||
white-space: nowrap;
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attacments {
|
||||
.attachments {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
|
||||
|
@ -320,6 +321,17 @@
|
|||
float: left;
|
||||
width: 100%;
|
||||
|
||||
.view-mode {
|
||||
border: 1px solid transparent;
|
||||
border-radius: $border-radius-small;
|
||||
display: inline-block;
|
||||
line-height: 16px;
|
||||
min-height: 20px;
|
||||
overflow: hidden;
|
||||
padding: 2px 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
border: 1px solid $color-silver;
|
||||
border-radius: $border-radius-small;
|
||||
|
@ -327,7 +339,6 @@
|
|||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
padding: 2px 5px;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
|
@ -337,6 +348,7 @@
|
|||
&:disabled {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
@ -412,7 +424,8 @@
|
|||
width: 220px;
|
||||
}
|
||||
|
||||
textarea:disabled {
|
||||
textarea:disabled,
|
||||
.view-mode {
|
||||
border: 1px solid $color-gainsboro;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -444,6 +457,13 @@
|
|||
.error-block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
|
||||
.edit-buttons {
|
||||
display: inline !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -478,7 +498,7 @@
|
|||
outline: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&.border {
|
||||
border: 1px solid $color-silver;
|
||||
}
|
||||
}
|
||||
|
@ -492,8 +512,8 @@
|
|||
right: -36px;
|
||||
transition: $md-transaction;
|
||||
|
||||
&.has-error {
|
||||
top: -64px;
|
||||
&.show {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -502,9 +522,5 @@
|
|||
textarea {
|
||||
border: 1px solid $color-silver;
|
||||
}
|
||||
|
||||
.new-comment-button {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -915,12 +915,43 @@ ul.content-activities {
|
|||
border-radius: 0;
|
||||
}
|
||||
|
||||
.panel-protocol-status {
|
||||
display: inline-block;
|
||||
margin-bottom: 5px;
|
||||
.protocol-status-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
& > .panel-body {
|
||||
padding: 5px 5px 5px 15px;
|
||||
.protocol-button {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.protocol-status-bar {
|
||||
display: flex;
|
||||
height: 33px;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.panel-protocol-status {
|
||||
border-color: $color-silver;
|
||||
box-shadow: none;
|
||||
display: inline-block;
|
||||
height: 33px;
|
||||
|
||||
& > .panel-body {
|
||||
padding: 0 0 0 15px;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.link-button,
|
||||
.link-toggle {
|
||||
height: 33px;
|
||||
position: relative;
|
||||
right: -1px;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -940,18 +971,6 @@ ul.content-activities {
|
|||
}
|
||||
}
|
||||
|
||||
#protocol-preview-modal .modal-dialog {
|
||||
.modal-body {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
|
||||
.ql-editor {
|
||||
min-height: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Import protocol/s modal */
|
||||
#import-protocol-modal .modal-dialog {
|
||||
width: 70%;
|
||||
|
|
|
@ -10,6 +10,10 @@ class ApplicationController < ActionController::Base
|
|||
around_action :set_time_zone, if: :current_user
|
||||
layout 'main'
|
||||
|
||||
rescue_from ActionController::InvalidAuthenticityToken do
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
def respond_422(message = t('client_api.permission_error'))
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
|
|
|
@ -5,6 +5,7 @@ class AssetsController < ApplicationController
|
|||
include ActionView::Helpers::TextHelper
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ActionView::Context
|
||||
include ApplicationHelper
|
||||
include InputSanitizeHelper
|
||||
include FileIconsHelper
|
||||
|
||||
|
@ -31,9 +32,8 @@ class AssetsController < ApplicationController
|
|||
'asset-id' => @asset.id,
|
||||
'image-tag-url' => @asset.url(:medium),
|
||||
'preview-url' => asset_file_preview_path(@asset),
|
||||
'filename' => truncate(@asset.file_file_name,
|
||||
length:
|
||||
Constants::FILENAME_TRUNCATION_LENGTH),
|
||||
'filename' => truncate(escape_input(@asset.file_file_name),
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH),
|
||||
'download-url' => download_asset_path(@asset),
|
||||
'type' => asset_data_type(@asset)
|
||||
}, status: 200
|
||||
|
@ -42,12 +42,30 @@ class AssetsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def step_file_present
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if @asset.file.processing?
|
||||
render json: { processing: true }
|
||||
else
|
||||
render json: {
|
||||
placeholder_html: render_to_string(
|
||||
partial: 'steps/attachments/placeholder.html.erb',
|
||||
locals: { asset: @asset, edit_page: false }
|
||||
),
|
||||
processing: false
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def file_preview
|
||||
response_json = {
|
||||
'id' => @asset.id,
|
||||
'type' => (@asset.is_image? ? 'image' : 'file'),
|
||||
|
||||
'filename' => truncate(@asset.file_file_name,
|
||||
'filename' => truncate(escape_input(@asset.file_file_name),
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH),
|
||||
'download-url' => download_asset_path(@asset, timestamp: Time.now.to_i)
|
||||
}
|
||||
|
@ -69,7 +87,7 @@ class AssetsController < ApplicationController
|
|||
'mime-type' => @asset.file.content_type,
|
||||
'processing' => @asset.file.processing?,
|
||||
'large-preview-url' => @asset.url(:large),
|
||||
'processing-url' => image_tag('medium/processing.gif')
|
||||
'processing-img' => image_tag('medium/processing.gif')
|
||||
)
|
||||
else
|
||||
response_json.merge!(
|
||||
|
@ -81,10 +99,10 @@ class AssetsController < ApplicationController
|
|||
)
|
||||
end
|
||||
|
||||
if wopi_file?(@asset)
|
||||
if wopi_enabled? && wopi_file?(@asset)
|
||||
edit_supported, title = wopi_file_edit_button_status
|
||||
response_json['wopi-controls'] = render_to_string(
|
||||
partial: 'shared/file_wopi_controlls.html.erb',
|
||||
partial: 'assets/wopi/file_wopi_controls.html.erb',
|
||||
locals: {
|
||||
asset: @asset,
|
||||
can_edit: can_edit,
|
||||
|
@ -168,15 +186,29 @@ class AssetsController < ApplicationController
|
|||
# Post process file here
|
||||
@asset.post_process_file(@asset.team)
|
||||
|
||||
render_html = if @asset.step
|
||||
asset_position = @asset.step.asset_position(@asset)
|
||||
render_to_string(
|
||||
partial: 'steps/attachments/item.html.erb',
|
||||
locals: {
|
||||
asset: @asset,
|
||||
i: asset_position[:pos],
|
||||
assets_count: asset_position[:count],
|
||||
step: @asset.step
|
||||
},
|
||||
formats: :html
|
||||
)
|
||||
else
|
||||
render_to_string(
|
||||
partial: 'shared/asset_link',
|
||||
locals: { asset: @asset, display_image_tag: true },
|
||||
formats: :html
|
||||
)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'shared/asset_link',
|
||||
locals: { asset: @asset, display_image_tag: true },
|
||||
formats: :html
|
||||
)
|
||||
}
|
||||
render json: { html: render_html }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -237,12 +269,9 @@ class AssetsController < ApplicationController
|
|||
@asset = Asset.find_by_id(params[:id])
|
||||
return render_404 unless @asset
|
||||
|
||||
step_assoc = @asset.step
|
||||
result_assoc = @asset.result
|
||||
repository_cell_assoc = @asset.repository_cell
|
||||
@assoc = step_assoc unless step_assoc.nil?
|
||||
@assoc = result_assoc unless result_assoc.nil?
|
||||
@assoc = repository_cell_assoc unless repository_cell_assoc.nil?
|
||||
@assoc ||= @asset.step
|
||||
@assoc ||= @asset.result
|
||||
@assoc ||= @asset.repository_cell
|
||||
|
||||
if @assoc.class == Step
|
||||
@protocol = @asset.step.protocol
|
||||
|
@ -293,6 +322,7 @@ class AssetsController < ApplicationController
|
|||
def asset_data_type(asset)
|
||||
return 'wopi' if wopi_file?(asset)
|
||||
return 'image' if asset.is_image?
|
||||
|
||||
'file'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class AtWhoController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
|
||||
before_action :load_vars
|
||||
before_action :check_users_permissions
|
||||
|
||||
|
@ -55,7 +57,7 @@ class AtWhoController < ApplicationController
|
|||
format.json do
|
||||
render json: {
|
||||
repositories: repositories.map do |r|
|
||||
[r.id, r.name.truncate(Constants::ATWHO_REP_NAME_LIMIT)]
|
||||
[r.id, escape_input(r.name.truncate(Constants::ATWHO_REP_NAME_LIMIT))]
|
||||
end.to_h,
|
||||
status: :ok
|
||||
}
|
||||
|
@ -122,9 +124,8 @@ class AtWhoController < ApplicationController
|
|||
res.each do |obj|
|
||||
tmp = {}
|
||||
tmp['id'] = obj[0].base62_encode
|
||||
tmp['full_name'] =
|
||||
obj[1].truncate(Constants::NAME_TRUNCATION_LENGTH_DROPDOWN)
|
||||
tmp['email'] = obj[2]
|
||||
tmp['full_name'] = escape_input(obj[1].truncate(Constants::NAME_TRUNCATION_LENGTH_DROPDOWN))
|
||||
tmp['email'] = escape_input(obj[2])
|
||||
tmp['img_url'] = avatar_path(obj[0], :icon_small)
|
||||
data << tmp
|
||||
end
|
||||
|
|
|
@ -70,13 +70,6 @@ module ReportActions
|
|||
def generate_module_contents_json(my_module)
|
||||
res = []
|
||||
|
||||
res << generate_new_el(false)
|
||||
el = generate_el(
|
||||
'reports/elements/my_module_protocol_element.html.erb',
|
||||
protocol: my_module.protocol
|
||||
)
|
||||
res << el
|
||||
|
||||
ReportExtends::MODULE_CONTENTS.each do |contents|
|
||||
elements = []
|
||||
contents.values.each do |element|
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GlobalActivitiesController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
|
||||
def index
|
||||
# Preload filter format
|
||||
# {
|
||||
|
@ -109,7 +111,7 @@ class GlobalActivitiesController < ApplicationController
|
|||
.pluck(:id, :name)
|
||||
next if matched.length.zero?
|
||||
|
||||
results[subject] = matched.map { |pr| { id: pr[0], name: pr[1] } }
|
||||
results[subject] = matched.map { |pr| { id: pr[0], name: escape_input(pr[1]) } }
|
||||
end
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
|
|
|
@ -458,6 +458,7 @@ class MyModulesController < ApplicationController
|
|||
repository: @repository.id,
|
||||
record_names: dowmstream_records[my_module.id].join(', '))
|
||||
end
|
||||
records_names.map! { |n| escape_input(n) }
|
||||
flash = I18n.t('repositories.assigned_records_flash',
|
||||
records: records_names.join(', '))
|
||||
flash = I18n.t('repositories.assigned_records_downstream_flash',
|
||||
|
@ -515,7 +516,7 @@ class MyModulesController < ApplicationController
|
|||
record_names: records.map(&:name).join(', '))
|
||||
|
||||
flash = I18n.t('repositories.unassigned_records_flash',
|
||||
records: records.map(&:name).join(', '))
|
||||
records: records.map { |r| escape_input(r.name) }.join(', '))
|
||||
respond_to do |format|
|
||||
format.json { render json: { flash: flash }, status: :ok }
|
||||
end
|
||||
|
|
|
@ -104,7 +104,7 @@ class ProjectsController < ApplicationController
|
|||
up.save
|
||||
log_activity(:create_project)
|
||||
|
||||
message = t('projects.create.success_flash', name: @project.name)
|
||||
message = t('projects.create.success_flash', name: escape_input(@project.name))
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
render json: { message: message }, status: :ok
|
||||
|
@ -136,7 +136,7 @@ class ProjectsController < ApplicationController
|
|||
|
||||
def update
|
||||
return_error = false
|
||||
flash_error = t('projects.update.error_flash', name: @project.name)
|
||||
flash_error = t('projects.update.error_flash', name: escape_input(@project.name))
|
||||
|
||||
# Check archive permissions if archiving/restoring
|
||||
if project_params.include? :archived
|
||||
|
@ -147,7 +147,7 @@ class ProjectsController < ApplicationController
|
|||
return_error = true
|
||||
is_archive = project_params[:archived] == 'true' ? 'archive' : 'restore'
|
||||
flash_error =
|
||||
t("projects.#{is_archive}.error_flash", name: @project.name)
|
||||
t("projects.#{is_archive}.error_flash", name: escape_input(@project.name))
|
||||
end
|
||||
elsif !can_manage_project?(@project)
|
||||
render_403 && return
|
||||
|
@ -177,11 +177,11 @@ class ProjectsController < ApplicationController
|
|||
log_activity(:archive_project) if project_params[:archived] == 'true'
|
||||
log_activity(:restore_project) if project_params[:archived] == 'false'
|
||||
|
||||
flash_success = t('projects.update.success_flash', name: @project.name)
|
||||
flash_success = t('projects.update.success_flash', name: escape_input(@project.name))
|
||||
if project_params[:archived] == 'true'
|
||||
flash_success = t('projects.archive.success_flash', name: @project.name)
|
||||
flash_success = t('projects.archive.success_flash', name: escape_input(@project.name))
|
||||
elsif project_params[:archived] == 'false'
|
||||
flash_success = t('projects.restore.success_flash', name: @project.name)
|
||||
flash_success = t('projects.restore.success_flash', name: escape_input(@project.name))
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
|
|
@ -626,7 +626,7 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
format.json do
|
||||
render json: {
|
||||
name: p_name, new_name: protocol.name, status: :ok
|
||||
name: escape_input(p_name), new_name: escape_input(protocol.name), status: :ok
|
||||
},
|
||||
status: :ok
|
||||
end
|
||||
|
|
|
@ -96,7 +96,7 @@ class RepositoryColumnsController < ApplicationController
|
|||
id: @repository_column.id,
|
||||
name: escape_input(@repository_column.name),
|
||||
message: t('libraries.repository_columns.update.success_flash',
|
||||
name: @repository_column.name)
|
||||
name: escape_input(@repository_column.name))
|
||||
}, status: :ok
|
||||
else
|
||||
render json: {
|
||||
|
@ -137,7 +137,7 @@ class RepositoryColumnsController < ApplicationController
|
|||
if @repository_column.destroy
|
||||
render json: {
|
||||
message: t('libraries.repository_columns.destroy.success_flash',
|
||||
name: column_name),
|
||||
name: escape_input(column_name)),
|
||||
id: column_id,
|
||||
status: :ok
|
||||
}
|
||||
|
|
|
@ -429,6 +429,7 @@ class RepositoryRowsController < ApplicationController
|
|||
.where(repository_column: cell.repository_column)
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.pluck(:id, :data)
|
||||
.map { |li| [li[0], escape_input(li[1])] }
|
||||
end
|
||||
|
||||
def fetch_columns_list_items
|
||||
|
@ -442,6 +443,7 @@ class RepositoryRowsController < ApplicationController
|
|||
list_items: column.repository_list_items
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.pluck(:id, :data)
|
||||
.map { |li| [li[0], escape_input(li[1])] }
|
||||
}
|
||||
end
|
||||
collection
|
||||
|
|
|
@ -101,7 +101,8 @@ class StepCommentsController < ApplicationController
|
|||
# Generate activity
|
||||
log_activity(:edit_step_comment)
|
||||
|
||||
message = custom_auto_link(@comment.message, team: current_team)
|
||||
message = custom_auto_link(@comment.message, simple_format: false,
|
||||
tags: %w(img), team: current_team)
|
||||
render json: { comment: message }, status: :ok
|
||||
else
|
||||
render json: { errors: @comment.errors.to_hash(true) },
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
class Users::SessionsController < Devise::SessionsController
|
||||
# before_filter :configure_sign_in_params, only: [:create]
|
||||
after_action :after_sign_in, only: :create
|
||||
|
||||
rescue_from ActionController::InvalidAuthenticityToken do
|
||||
redirect_to new_user_session_path
|
||||
end
|
||||
|
||||
# GET /resource/sign_in
|
||||
def new
|
||||
# If user was redirected here from OAuth's authorize/new page (Doorkeeper
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ZipExportsController < ApplicationController
|
||||
before_action :load_var, only: :download
|
||||
before_action :load_var_export_all, only: :download_export_all_zip
|
||||
before_action :check_edit_permissions, only: :download
|
||||
before_action :load_var, only: %i(download download_export_all_zip)
|
||||
before_action :check_download_permissions, except: :file_expired
|
||||
|
||||
def download
|
||||
if @zip_export.stored_on_s3?
|
||||
|
@ -22,16 +23,11 @@ class ZipExportsController < ApplicationController
|
|||
private
|
||||
|
||||
def load_var
|
||||
@zip_export = ZipExport.find_by_id(params[:id])
|
||||
redirect_to(file_expired_url, status: 301) and return unless @zip_export
|
||||
@zip_export = current_user.zip_exports.find_by_id(params[:id])
|
||||
redirect_to(file_expired_url, status: 301) and return unless @zip_export&.zip_file&.exists?
|
||||
end
|
||||
|
||||
def load_var_export_all
|
||||
@zip_export = TeamZipExport.find_by_id(params[:id])
|
||||
redirect_to(file_expired_url, status: 301) and return unless @zip_export
|
||||
end
|
||||
|
||||
def check_edit_permissions
|
||||
def check_download_permissions
|
||||
render_403 unless @zip_export.user == current_user
|
||||
end
|
||||
end
|
||||
|
|
|
@ -221,4 +221,8 @@ module ApplicationHelper
|
|||
user.avatar(style) == '/images/icon_small/missing.png' ||
|
||||
user.avatar(style) == '/images/thumb/missing.png'
|
||||
end
|
||||
|
||||
def wopi_enabled?
|
||||
ENV['WOPI_ENABLED'] == 'true'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ module ProtocolStatusHelper
|
|||
def protocol_status_href(protocol)
|
||||
parent = protocol.parent
|
||||
res = ''
|
||||
res << '<a href="#" data-toggle="popover" data-html="true" '
|
||||
res << '<a href="#" data-toggle="popover" data-html="true" class="preview-protocol"'
|
||||
res << 'data-trigger="focus" data-placement="bottom" title="'
|
||||
res << protocol_status_popover_title(parent) +
|
||||
'" data-content="' + protocol_status_popover_content(parent) +
|
||||
|
@ -53,7 +53,7 @@ module ProtocolStatusHelper
|
|||
else
|
||||
res = "<p>"
|
||||
if protocol.description.present?
|
||||
res << protocol.description
|
||||
res << protocol.tinymce_render(:description)
|
||||
else
|
||||
res << "<em>" + I18n.t("my_modules.protocols.protocol_status_bar.no_description") + "</em>"
|
||||
end
|
||||
|
@ -69,6 +69,6 @@ module ProtocolStatusHelper
|
|||
end
|
||||
res << "</p>"
|
||||
end
|
||||
sanitize_input(res)
|
||||
escape_input(res)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,6 @@ module ReportsHelper
|
|||
|
||||
def render_report_element(element, provided_locals = nil)
|
||||
# Determine partial
|
||||
|
||||
file_name = element.type_of
|
||||
if element.type_of.in? ReportExtends::MY_MODULE_CHILDREN_ELEMENTS
|
||||
file_name = "my_module_#{element.type_of.singularize}"
|
||||
|
|
|
@ -49,7 +49,7 @@ class Asset < ApplicationRecord
|
|||
# This could cause some problems if you create empty asset and want to
|
||||
# assign it to result
|
||||
validate :step_or_result_or_repository_asset_value
|
||||
validate :name_should_not_be_empty_without_extension,
|
||||
validate :wopi_filename_valid,
|
||||
on: :wopi_file_creation
|
||||
|
||||
belongs_to :created_by,
|
||||
|
@ -320,7 +320,7 @@ class Asset < ApplicationRecord
|
|||
end
|
||||
|
||||
def url(style = :original, timeout: Constants::URL_SHORT_EXPIRE_TIME)
|
||||
if file.is_stored_on_s3?
|
||||
if file.is_stored_on_s3? && !file.processing?
|
||||
presigned_url(style, timeout: timeout)
|
||||
else
|
||||
file.url(style)
|
||||
|
@ -521,13 +521,24 @@ class Asset < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def name_should_not_be_empty_without_extension
|
||||
def wopi_filename_valid
|
||||
# Check that filename without extension is not blank
|
||||
unless file.original_filename[0..-6].present?
|
||||
errors.add(
|
||||
:file,
|
||||
I18n.t('general.text.not_blank')
|
||||
)
|
||||
end
|
||||
# Check maximum filename length
|
||||
if file.original_filename.length > Constants::FILENAME_MAX_LENGTH
|
||||
errors.add(
|
||||
:file,
|
||||
I18n.t(
|
||||
'general.file.file_name_too_long',
|
||||
limit: Constants::FILENAME_MAX_LENGTH
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def cache
|
||||
|
|
|
@ -15,7 +15,7 @@ module TinyMceImages
|
|||
description = self[field]
|
||||
|
||||
# Check tinymce for old format
|
||||
description = TinyMceAsset.update_old_tinymce(description)
|
||||
description = TinyMceAsset.update_old_tinymce(description, self)
|
||||
|
||||
tiny_mce_assets.each do |tm_asset|
|
||||
tmp_f = Tempfile.open(tm_asset.image_file_name, Rails.root.join('tmp'))
|
||||
|
@ -27,6 +27,8 @@ module TinyMceImages
|
|||
tm_asset_to_update = html_description.css(
|
||||
"img[data-mce-token=\"#{Base62.encode(tm_asset.id)}\"]"
|
||||
)[0]
|
||||
next unless tm_asset_to_update
|
||||
|
||||
tm_asset_to_update.attributes['src'].value = new_tm_asset_src
|
||||
description = html_description.css('body').inner_html.to_s
|
||||
ensure
|
||||
|
@ -38,7 +40,7 @@ module TinyMceImages
|
|||
end
|
||||
|
||||
def tinymce_render(field)
|
||||
TinyMceAsset.generate_url(self[field])
|
||||
TinyMceAsset.generate_url(self[field], self)
|
||||
end
|
||||
|
||||
# Takes array of old/new TinyMCE asset ID pairs
|
||||
|
@ -48,7 +50,7 @@ module TinyMceImages
|
|||
description = read_attribute(object_field)
|
||||
|
||||
# Check tinymce for old format
|
||||
description = TinyMceAsset.update_old_tinymce(description)
|
||||
description = TinyMceAsset.update_old_tinymce(description, self)
|
||||
|
||||
parsed_description = Nokogiri::HTML(description)
|
||||
images.each do |image|
|
||||
|
@ -57,7 +59,7 @@ module TinyMceImages
|
|||
image = parsed_description.at_css("img[data-mce-token=\"#{Base62.encode(old_id)}\"]")
|
||||
image['data-mce-token'] = Base62.encode(new_id)
|
||||
end
|
||||
update(object_field => parsed_description.to_html)
|
||||
update(object_field => parsed_description.css('body').inner_html.to_s)
|
||||
end
|
||||
|
||||
def clone_tinymce_assets(target, team)
|
||||
|
|
|
@ -100,6 +100,7 @@ class Report < ApplicationRecord
|
|||
exp.my_modules.each do |my_module|
|
||||
module_children = []
|
||||
|
||||
module_children += gen_element_content(my_module, nil, 'my_module_protocol', true)
|
||||
my_module.protocol.steps.each do |step|
|
||||
step_children =
|
||||
gen_element_content(step, step.assets, 'step_asset')
|
||||
|
|
|
@ -132,6 +132,12 @@ class Step < ApplicationRecord
|
|||
st
|
||||
end
|
||||
|
||||
def asset_position(asset)
|
||||
assets.order(:file_updated_at).each_with_index do |step_asset, i|
|
||||
return { count: assets.count, pos: i } if asset.id == step_asset.id
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def cascade_after_destroy
|
||||
|
|
|
@ -30,7 +30,7 @@ class SystemNotification < ApplicationRecord
|
|||
user,
|
||||
query = nil
|
||||
)
|
||||
notifications = order(last_time_changed_at: :DESC)
|
||||
notifications = order(created_at: :DESC)
|
||||
notifications = notifications.search_notifications(query) if query.present?
|
||||
notifications.joins(:user_system_notifications)
|
||||
.where('user_system_notifications.user_id = ?', user.id)
|
||||
|
@ -39,6 +39,7 @@ class SystemNotification < ApplicationRecord
|
|||
:title,
|
||||
:description,
|
||||
:last_time_changed_at,
|
||||
:created_at,
|
||||
'user_system_notifications.seen_at',
|
||||
'user_system_notifications.read_at'
|
||||
)
|
||||
|
@ -50,6 +51,6 @@ class SystemNotification < ApplicationRecord
|
|||
SystemNotification
|
||||
.order(last_time_changed_at: :desc)
|
||||
.first&.last_time_changed_at&.to_i ||
|
||||
User.order(created_at: :desc).first&.created_at&.to_i
|
||||
User.order(created_at: :asc).first&.created_at&.to_i
|
||||
end
|
||||
end
|
||||
|
|
|
@ -322,7 +322,7 @@ class Team < ApplicationRecord
|
|||
query = query.where(id: users_team)
|
||||
end
|
||||
query = query.where(id: team_by_subject(filters[:subjects])) if filters[:subjects]
|
||||
query.select(:id, :name)
|
||||
query.select(:id, :name).map { |i| { id: i[:id], name: ApplicationController.helpers.escape_input(i[:name]) } }
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -57,9 +57,9 @@ class TinyMceAsset < ApplicationRecord
|
|||
Rails.logger.error e.message
|
||||
end
|
||||
|
||||
def self.generate_url(description)
|
||||
def self.generate_url(description, obj = nil)
|
||||
# Check tinymce for old format
|
||||
description = update_old_tinymce(description)
|
||||
description = update_old_tinymce(description, obj)
|
||||
|
||||
description = Nokogiri::HTML(description)
|
||||
tm_assets = description.css('img')
|
||||
|
@ -119,12 +119,23 @@ class TinyMceAsset < ApplicationRecord
|
|||
asset.destroy if asset && !asset.saved
|
||||
end
|
||||
|
||||
def self.update_old_tinymce(description)
|
||||
def self.update_old_tinymce(description, obj = nil)
|
||||
return description unless description
|
||||
|
||||
description.scan(/\[~tiny_mce_id:(\w+)\]/).flatten.each do |token|
|
||||
old_format = /\[~tiny_mce_id:#{token}\]/
|
||||
new_format = "<img src=\"\" class=\"img-responsive\" data-mce-token=\"#{Base62.encode(token.to_i)}\"/>"
|
||||
|
||||
asset = find_by_id(token)
|
||||
unless asset
|
||||
# remove tag if asset deleted
|
||||
description.sub!(old_format, '')
|
||||
next
|
||||
end
|
||||
|
||||
# If object (step or result text) don't have direct assciation to tinyMCE image, we need copy it.
|
||||
asset.clone_tinymce_asset(obj) if obj && obj != asset.object
|
||||
|
||||
description.sub!(old_format, new_format)
|
||||
end
|
||||
description
|
||||
|
@ -146,6 +157,42 @@ class TinyMceAsset < ApplicationRecord
|
|||
ostream
|
||||
end
|
||||
|
||||
def clone_tinymce_asset(obj)
|
||||
begin
|
||||
# It will trigger only for Step or ResultText
|
||||
team_id = if obj.class.name == 'Step'
|
||||
obj.protocol.team_id
|
||||
else
|
||||
obj.result.my_module.protocol.team_id
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
team_id = nil
|
||||
end
|
||||
|
||||
return false unless team_id
|
||||
|
||||
tiny_img_clone = TinyMceAsset.new(
|
||||
image: image,
|
||||
estimated_size: estimated_size,
|
||||
object: obj,
|
||||
team_id: team_id
|
||||
)
|
||||
tiny_img_clone.save!
|
||||
|
||||
obj.tiny_mce_assets << tiny_img_clone
|
||||
# Prepare array of image to update
|
||||
cloned_img_ids = [[id, tiny_img_clone.id]]
|
||||
|
||||
obj_field = Extends::RICH_TEXT_FIELD_MAPPINGS[obj.class.name]
|
||||
|
||||
# Update description with new format
|
||||
obj.update(obj_field => TinyMceAsset.update_old_tinymce(obj[obj_field]))
|
||||
|
||||
# reassign images
|
||||
obj.reassign_tiny_mce_image_references(cloned_img_ids)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self_destruct
|
||||
|
|
|
@ -5,6 +5,7 @@ class User < ApplicationRecord
|
|||
include User::TeamRoles
|
||||
include User::ProjectRoles
|
||||
include TeamBySubjectModel
|
||||
include InputSanitizeHelper
|
||||
|
||||
acts_as_token_authenticatable
|
||||
devise :invitable, :confirmable, :database_authenticatable, :registerable,
|
||||
|
@ -553,7 +554,7 @@ class User < ApplicationRecord
|
|||
User.where(id: UserTeam.where(team_id: query_teams).select(:user_id))
|
||||
.search(false, search_query)
|
||||
.select(:full_name, :id)
|
||||
.map { |i| { name: i[:full_name], id: i[:id] } }
|
||||
.map { |i| { name: escape_input(i[:full_name]), id: i[:id] } }
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -4,8 +4,7 @@ class UserSystemNotification < ApplicationRecord
|
|||
belongs_to :user
|
||||
belongs_to :system_notification
|
||||
|
||||
after_create :send_email,
|
||||
if: proc { |sn| sn.user.system_message_email_notification }
|
||||
validates :system_notification, uniqueness: { scope: :user }
|
||||
|
||||
scope :unseen, -> { where(seen_at: nil) }
|
||||
|
||||
|
@ -15,23 +14,22 @@ class UserSystemNotification < ApplicationRecord
|
|||
|
||||
def self.mark_as_read(notification_id)
|
||||
notification = find_by_system_notification_id(notification_id)
|
||||
if notification && notification.read_at.nil?
|
||||
notification.update(read_at: Time.now)
|
||||
end
|
||||
notification.update(read_at: Time.now) if notification && notification.read_at.nil?
|
||||
end
|
||||
|
||||
def self.show_on_login(update_read_time = false)
|
||||
# for notification check leave update_read_time empty
|
||||
notification = joins(:system_notification)
|
||||
.where('system_notifications.show_on_login = true')
|
||||
.order('system_notifications.last_time_changed_at DESC')
|
||||
.order('system_notifications.created_at DESC')
|
||||
.select(
|
||||
:modal_title,
|
||||
:modal_body,
|
||||
'user_system_notifications.id',
|
||||
:read_at,
|
||||
:user_id,
|
||||
:system_notification_id
|
||||
:system_notification_id,
|
||||
:created_at
|
||||
)
|
||||
.first
|
||||
if notification && notification.read_at.nil?
|
||||
|
@ -44,10 +42,4 @@ class UserSystemNotification < ApplicationRecord
|
|||
notification
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_email
|
||||
AppMailer.delay.system_notification(user, system_notification)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,6 +40,7 @@ class UserTeam < ApplicationRecord
|
|||
end
|
||||
|
||||
def destroy(new_owner)
|
||||
return super() unless new_owner
|
||||
# If any project of the team has the sole owner and that
|
||||
# owner is the user to be removed from the team, then we must
|
||||
# create a new owner of the project (the provided user).
|
||||
|
|
|
@ -24,6 +24,8 @@ class ZipExport < ApplicationRecord
|
|||
validates_attachment :zip_file,
|
||||
content_type: { content_type: 'application/zip' }
|
||||
|
||||
after_create :self_destruct
|
||||
|
||||
# When using S3 file upload, we can limit file accessibility with url signing
|
||||
def presigned_url(style = :original,
|
||||
download: false,
|
||||
|
@ -48,6 +50,11 @@ class ZipExport < ApplicationRecord
|
|||
zip_file.options[:storage].to_sym == :s3
|
||||
end
|
||||
|
||||
def self.delete_expired_export(id)
|
||||
export = find_by_id(id)
|
||||
export.destroy if export
|
||||
end
|
||||
|
||||
def generate_exportable_zip(user, data, type, options = {})
|
||||
I18n.backend.date_format =
|
||||
user.settings[:date_format] || Constants::DEFAULT_DATE_FORMAT
|
||||
|
@ -71,6 +78,11 @@ class ZipExport < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def self_destruct
|
||||
ZipExport.delay(run_at: Constants::EXPORTABLE_ZIP_EXPIRATION_DAYS.days.from_now)
|
||||
.delete_expired_export(id)
|
||||
end
|
||||
|
||||
def method_missing(m, *args, &block)
|
||||
puts 'Method is missing! To use this zip_export you have to ' \
|
||||
'define a method: generate_( type )_zip.'
|
||||
|
|
|
@ -11,7 +11,7 @@ module Api
|
|||
end
|
||||
|
||||
def table_contents
|
||||
object.table&.contents
|
||||
object.table&.contents&.force_encoding(Encoding::UTF_8)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Notifications
|
||||
class HandleSystemNotificationInCommunicationChannelService
|
||||
extend Service
|
||||
|
||||
attr_reader :errors
|
||||
|
||||
def initialize(system_notification)
|
||||
@system_notification = system_notification
|
||||
@errors = {}
|
||||
end
|
||||
|
||||
def call
|
||||
@system_notification.user_system_notifications.find_each do |usn|
|
||||
user = usn.user
|
||||
AppMailer.delay.system_notification(user, @system_notification) if user.system_message_email_notification
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def succeed?
|
||||
@errors.none?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Notifications
|
||||
class PushToCommunicationChannelService
|
||||
extend Service
|
||||
|
||||
WHITELISTED_ITEM_TYPES = %w(SystemNotification).freeze
|
||||
|
||||
attr_reader :errors
|
||||
|
||||
def initialize(item_id:, item_type:)
|
||||
@item_type = item_type
|
||||
@item = item_type.constantize.find item_id
|
||||
@errors = {}
|
||||
end
|
||||
|
||||
def call
|
||||
return self unless valid?
|
||||
|
||||
"Notifications::Handle#{@item_type}InCommunicationChannelService".constantize.call(@item)
|
||||
self
|
||||
end
|
||||
|
||||
def succeed?
|
||||
@errors.none?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid?
|
||||
raise 'Dont know how to handle this type of items' unless WHITELISTED_ITEM_TYPES.include?(@item_type)
|
||||
|
||||
if @item.nil?
|
||||
@errors[:invalid_arguments] = 'Can\'t find item' if @item.nil?
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -81,12 +81,27 @@ module Notifications
|
|||
.where(source_id: attrs[:source_id]).first_or_initialize(attrs)
|
||||
|
||||
if n.new_record?
|
||||
n.users = User.all
|
||||
n.save!
|
||||
save_notification n
|
||||
elsif n.last_time_changed_at < attrs[:last_time_changed_at]
|
||||
n.update_attributes!(attrs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def save_notification(notification)
|
||||
ActiveRecord::Base.transaction do
|
||||
notification.save!
|
||||
|
||||
User.find_in_batches do |user_ids|
|
||||
user_system_notifications = user_ids.pluck(:id).collect do |item|
|
||||
Hash[:user_id, item, :system_notification_id, notification.id]
|
||||
end
|
||||
UserSystemNotification.import user_system_notifications, validate: false
|
||||
end
|
||||
end
|
||||
|
||||
Notifications::PushToCommunicationChannelService.delay.call(item_id: notification.id,
|
||||
item_type: notification.class.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ module RepositoryActions
|
|||
def duplicate_row(id)
|
||||
row = RepositoryRow.find_by_id(id)
|
||||
new_row = RepositoryRow.new(
|
||||
row.attributes.merge(new_row_attributes(row.name))
|
||||
row.attributes.merge(new_row_attributes(row.name, @user.id))
|
||||
)
|
||||
|
||||
if new_row.save
|
||||
|
@ -46,10 +46,11 @@ module RepositoryActions
|
|||
end
|
||||
end
|
||||
|
||||
def new_row_attributes(name)
|
||||
def new_row_attributes(name, user_id)
|
||||
timestamp = DateTime.now
|
||||
{ id: nil,
|
||||
name: "#{name} (1)",
|
||||
created_by_id: user_id,
|
||||
created_at: timestamp,
|
||||
updated_at: timestamp }
|
||||
end
|
||||
|
|
|
@ -158,10 +158,14 @@ module ProtocolsImporter
|
|||
)
|
||||
tiny_mce_img.image_content_type = tiny_mce_img_json['fileType']
|
||||
tiny_mce_img.save!
|
||||
if description.gsub!("data-mce-token=\"#{tiny_mce_img_json['tokenId']}\"",
|
||||
"data-mce-token=\"#{Base62.encode(tiny_mce_img.id)}\"")
|
||||
description.gsub!(' ]]-->', '')
|
||||
|
||||
description.gsub!("data-mce-token=\"#{tiny_mce_img_json['tokenId']}\"",
|
||||
"data-mce-token=\"#{Base62.encode(tiny_mce_img.id)}\"")
|
||||
.gsub!(' ]]-->', '')
|
||||
else
|
||||
description.gsub!("data-mce-token=\"#{Base62.encode(tiny_mce_img_json['tokenId'].to_i)}\"",
|
||||
"data-mce-token=\"#{Base62.encode(tiny_mce_img.id)}\"").gsub!(' ]]-->', '')
|
||||
end
|
||||
end
|
||||
description
|
||||
end
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<%= link_to create_wopi_file_path,
|
||||
class: 'btn btn-default create-wopi-file-btn',
|
||||
target: '_blank',
|
||||
<% if wopi_enabled? %>
|
||||
<%= link_to create_wopi_file_path,
|
||||
class: 'btn btn-default create-wopi-file-btn',
|
||||
target: '_blank',
|
||||
title: 'Create_new_file',
|
||||
data: { 'id': element_id, 'type': element_type, } do %>
|
||||
|
||||
<span class="btn btn-default new-asset-upload-button">
|
||||
<span class="fas fa-file-medical new-asset-upload-icon"></span>
|
||||
<span class="btn btn-default new-asset-upload-button">
|
||||
<%= image_tag 'office/office.svg' %>
|
||||
<%=t 'assets.create_wopi_file.button_text' %>
|
||||
</span>
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<% provide(:head_title, t("experiments.canvas.head_title", project: h(@project.name)).html_safe) %>
|
||||
<%= render partial: "shared/sidebar", locals: { current_experiment: @experiment, page: 'canvas' } %>
|
||||
<%= render partial: "shared/secondary_navigation" , locals: {
|
||||
<%= render partial: "shared/secondary_navigation" , locals: {
|
||||
editable: {
|
||||
name: 'title',
|
||||
active: true,
|
||||
active: can_manage_experiment?(@experiment),
|
||||
width: 'calc(100% - 500px)',
|
||||
params_group: 'experiment',
|
||||
field_to_udpate: 'name',
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
<% if my_module.completed? %>
|
||||
<%= t("my_modules.states.completed") %>
|
||||
<span class="due-date-label">
|
||||
<%= l(my_module.completed_on, format: :full_date) %>
|
||||
<%= l(my_module.completed_on.utc, format: :full_date) %>
|
||||
<span class="fas fa-check"></span>
|
||||
</span>
|
||||
<% elsif my_module.is_one_day_prior? %>
|
||||
<%= t("my_modules.states.due_soon") %>
|
||||
<span class="due-date-label">
|
||||
<%=l my_module.due_date, format: :full_date %>
|
||||
<%=l my_module.due_date.utc, format: :full_date %>
|
||||
<span class="fas fa-exclamation-triangle"></span>
|
||||
</span>
|
||||
<% elsif my_module.is_overdue? %>
|
||||
<%= t("my_modules.states.overdue") %>
|
||||
<span class="due-date-label">
|
||||
<%=l my_module.due_date, format: :full_date %>
|
||||
<%=l my_module.due_date.utc, format: :full_date %>
|
||||
<span class="fas fa-exclamation-triangle"></span>
|
||||
</span>
|
||||
<% elsif my_module.due_date %>
|
||||
<%= t("experiments.canvas.full_zoom.due_date") %>
|
||||
<span class="due-date-label">
|
||||
<%=l my_module.due_date, format: :full_date %>
|
||||
<%=l my_module.due_date.utc, format: :full_date %>
|
||||
</span>
|
||||
<% else %>
|
||||
<%= t("experiments.canvas.full_zoom.due_date") %>
|
||||
|
|
|
@ -63,8 +63,8 @@
|
|||
<div class="badge-icon">
|
||||
<span class="fas fa-tags"></span>
|
||||
</div>
|
||||
<span class="hidden-xs hidden-sm tags-title"><%=t "my_modules.module_header.tags" %></span>
|
||||
<%= render partial: "my_modules/tags", locals: { my_module: @my_module, editable: can_manage_module?(@my_module) } %>
|
||||
<span class="hidden-xs hidden-sm tags-title"><%=t "my_modules.module_header.tags" %></span>
|
||||
<%= render partial: "my_modules/tags", locals: { my_module: @my_module, editable: can_manage_module?(@my_module) } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -77,7 +77,7 @@
|
|||
<% if can_manage_module?(@my_module) %>
|
||||
<%= render partial: "description_form" %>
|
||||
<% elsif @my_module.description.present? %>
|
||||
<%= custom_auto_link(@my_module.description,
|
||||
<%= custom_auto_link(@my_module.tinymce_render(:description),
|
||||
simple_format: false,
|
||||
tags: %w(img),
|
||||
team: current_team) %>
|
||||
|
|
|
@ -4,20 +4,20 @@
|
|||
<strong>
|
||||
<% if my_module.completed? %>
|
||||
<span class="alert-green">
|
||||
<%= l(my_module.due_date, format: :full) %>
|
||||
<%= l(my_module.due_date.utc, format: :full) %>
|
||||
</span>
|
||||
<% elsif my_module.is_one_day_prior? %>
|
||||
<span class="alert-yellow">
|
||||
<%= l(my_module.due_date, format: :full) %>
|
||||
<%= l(my_module.due_date.utc, format: :full) %>
|
||||
(<%= t('my_modules.states.due_soon') %>)
|
||||
</span>
|
||||
<% elsif my_module.is_overdue? %>
|
||||
<span class="alert-red">
|
||||
<%= l(my_module.due_date, format: :full) %>
|
||||
<%= l(my_module.due_date.utc, format: :full) %>
|
||||
(<%= t('my_modules.states.overdue') %>)
|
||||
</span>
|
||||
<% else %>
|
||||
<%= l(my_module.due_date, format: :full) %>
|
||||
<%= l(my_module.due_date.utc, format: :full) %>
|
||||
<% end %>
|
||||
</strong>
|
||||
<% end %>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<%= render partial: "shared/secondary_navigation", locals: {
|
||||
editable: {
|
||||
name: 'title',
|
||||
active: true,
|
||||
active: can_manage_module?(@my_module),
|
||||
width: 'calc(100% - 580px)',
|
||||
params_group: 'my_module',
|
||||
field_to_udpate: 'name',
|
||||
|
@ -22,8 +22,8 @@
|
|||
<%= t('Protocol') %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div data-role="protocol-status-bar" style="display: inline;">
|
||||
<div class="protocol-status-container">
|
||||
<div class="protocol-status-bar" data-role="protocol-status-bar">
|
||||
<%= render partial: "my_modules/protocols/protocol_status_bar.html.erb" %>
|
||||
</div>
|
||||
<%= render partial: "my_modules/protocols/protocol_buttons.html.erb" %>
|
||||
|
@ -40,7 +40,7 @@
|
|||
}
|
||||
%>
|
||||
<% elsif @my_module.protocol.description.present? %>
|
||||
<%= custom_auto_link(@my_module.protocol.description,
|
||||
<%= custom_auto_link(@my_module.protocol.tinymce_render(:description),
|
||||
simple_format: false,
|
||||
tags: %w(img),
|
||||
team: current_team) %>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="btn-group" role="group" aria-label="" style="margin-left: 15px;">
|
||||
<div class="btn-group protocol-button" role="group" aria-label="">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" href="#">
|
||||
<span class="fas fa-download"></span>
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<div class="btn-group">
|
||||
<% if @protocol.unlinked? %>
|
||||
<a type="button" class="btn btn-info" tabindex="0" role="button" data-trigger="focus" data-container="body" data-html="true" data-toggle="popover" data-placement="bottom" title="<%= t("my_modules.protocols.protocol_status_bar.btns_unlinked.title") %>" data-content="<%= t("my_modules.protocols.protocol_status_bar.btns_unlinked.text") %>">
|
||||
<a type="button" class="btn btn-info link-button" tabindex="0" role="button" data-trigger="focus" data-container="body" data-html="true" data-toggle="popover" data-placement="bottom" title="<%= t("my_modules.protocols.protocol_status_bar.btns_unlinked.title") %>" data-content="<%= t("my_modules.protocols.protocol_status_bar.btns_unlinked.text") %>">
|
||||
<span class="fas fa-book"></span>
|
||||
|
||||
<span class="fas fa-times-sign"></span>
|
||||
</a>
|
||||
<a type="button" class="btn btn-info dropdown-toggle" disabled="disabled">
|
||||
<a type="button" class="btn btn-info dropdown-toggle link-toggle" disabled="disabled">
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<% else %>
|
||||
<% if @protocol.linked_no_diff? %>
|
||||
<a type="button" class="btn btn-info" tabindex="0" role="button" data-trigger="focus" data-container="body" data-html="true" data-toggle="popover" data-placement="bottom" title="<%= t("my_modules.protocols.protocol_status_bar.btns_linked_no_diff.title") %>" data-content="<%= t("my_modules.protocols.protocol_status_bar.btns_linked_no_diff.text") %>">
|
||||
<a type="button" class="btn btn-info link-button" tabindex="0" role="button" data-trigger="focus" data-container="body" data-html="true" data-toggle="popover" data-placement="bottom" title="<%= t("my_modules.protocols.protocol_status_bar.btns_linked_no_diff.title") %>" data-content="<%= t("my_modules.protocols.protocol_status_bar.btns_linked_no_diff.text") %>">
|
||||
<span class="fas fa-book"></span>
|
||||
|
||||
<span class="fas fa-check-circle"></span>
|
||||
</a>
|
||||
<a type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a type="button" class="btn btn-info dropdown-toggle link-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
|
@ -31,12 +31,12 @@
|
|||
<% end %>
|
||||
</ul>
|
||||
<% elsif @protocol.newer_than_parent? %>
|
||||
<a type="button" class="btn btn-warning" tabindex="0" role="button" data-trigger="focus" data-container="body" data-html="true" data-toggle="popover" data-placement="bottom" title="<%= t("my_modules.protocols.protocol_status_bar.btns_newer_than_parent.title") %>" data-content="<%= t("my_modules.protocols.protocol_status_bar.btns_newer_than_parent.text", self_ts: l(@protocol.updated_at, format: :full), parent_ts: l(@protocol.parent.updated_at, format: :full)).html_safe %>">
|
||||
<a type="button" class="btn btn-warning link-button" tabindex="0" role="button" data-trigger="focus" data-container="body" data-html="true" data-toggle="popover" data-placement="bottom" title="<%= t("my_modules.protocols.protocol_status_bar.btns_newer_than_parent.title") %>" data-content="<%= t("my_modules.protocols.protocol_status_bar.btns_newer_than_parent.text", self_ts: l(@protocol.updated_at, format: :full), parent_ts: l(@protocol.parent.updated_at, format: :full)).html_safe %>">
|
||||
<span class="fas fa-book"></span>
|
||||
|
||||
<span class="fas fa-arrow-circle-up"></span>
|
||||
</a>
|
||||
<a type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a type="button" class="btn btn-warning dropdown-toggle link-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
|
@ -72,12 +72,12 @@
|
|||
<% end %>
|
||||
</ul>
|
||||
<% elsif @protocol.parent_newer? %>
|
||||
<a type="button" class="btn btn-warning" tabindex="0" role="button" data-trigger="focus" data-container="body" data-html="true" data-toggle="popover" data-placement="bottom" title="<%= t("my_modules.protocols.protocol_status_bar.btns_parent_newer.title") %>" data-content="<%= t("my_modules.protocols.protocol_status_bar.btns_parent_newer.text", self_ts: l(@protocol.updated_at, format: :full), parent_ts: l(@protocol.parent.updated_at, format: :full)).html_safe %>">
|
||||
<a type="button" class="btn btn-warning link-button" tabindex="0" role="button" data-trigger="focus" data-container="body" data-html="true" data-toggle="popover" data-placement="bottom" title="<%= t("my_modules.protocols.protocol_status_bar.btns_parent_newer.title") %>" data-content="<%= t("my_modules.protocols.protocol_status_bar.btns_parent_newer.text", self_ts: l(@protocol.updated_at, format: :full), parent_ts: l(@protocol.parent.updated_at, format: :full)).html_safe %>">
|
||||
<span class="fas fa-book"></span>
|
||||
|
||||
<span class="fas fa-arrow-circle-up"></span>
|
||||
</a>
|
||||
<a type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a type="button" class="btn btn-warning dropdown-toggle link-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
|
@ -103,12 +103,12 @@
|
|||
<% end %>
|
||||
</ul>
|
||||
<% elsif @protocol.parent_and_self_newer? %>
|
||||
<a type="button" class="btn btn-warning" tabindex="0" role="button" data-trigger="focus" data-container="body" data-html="true" data-toggle="popover" data-placement="bottom" title="<%= t("my_modules.protocols.protocol_status_bar.btns_parent_and_self_newer.title") %>" data-content="<%= t("my_modules.protocols.protocol_status_bar.btns_parent_and_self_newer.text", self_ts: l(@protocol.updated_at, format: :full), parent_ts: l(@protocol.parent.updated_at, format: :full)).html_safe %>">
|
||||
<a type="button" class="btn btn-warning link-button" tabindex="0" role="button" data-trigger="focus" data-container="body" data-html="true" data-toggle="popover" data-placement="bottom" title="<%= t("my_modules.protocols.protocol_status_bar.btns_parent_and_self_newer.title") %>" data-content="<%= t("my_modules.protocols.protocol_status_bar.btns_parent_and_self_newer.text", self_ts: l(@protocol.updated_at, format: :full), parent_ts: l(@protocol.parent.updated_at, format: :full)).html_safe %>">
|
||||
<span class="fas fa-book"></span>
|
||||
|
||||
<span class="fas fa-exclamation-circle"></span>
|
||||
</a>
|
||||
<a type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a type="button" class="btn btn-warning dropdown-toggle link-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
|
|
|
@ -59,17 +59,7 @@
|
|||
<%= javascript_include_tag "handsontable.full.min" %>
|
||||
|
||||
<!-- Libraries for formulas -->
|
||||
<%= javascript_include_tag "lodash" %>
|
||||
<%= javascript_include_tag "numeral" %>
|
||||
<%= javascript_include_tag "numeric" %>
|
||||
<%= javascript_include_tag "md5" %>
|
||||
<%= javascript_include_tag "jstat" %>
|
||||
<%= javascript_include_tag "formula" %>
|
||||
<%= javascript_include_tag "parser" %>
|
||||
<%= javascript_include_tag "ruleJS" %>
|
||||
<%= javascript_include_tag "handsontable.formula" %>
|
||||
<%= javascript_include_tag "big.min" %>
|
||||
<%= stylesheet_link_tag "handsontable.formula" %>
|
||||
<%= render partial: "shared/formulas_libraries.html.erb" %>
|
||||
|
||||
<%= javascript_include_tag("canvas-to-blob.min") %>
|
||||
<%= javascript_include_tag "assets/wopi/create_wopi_file" %>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<% provide(:head_title, t("projects.show.head_title", project: h(@project.name)).html_safe) %>
|
||||
<%= render partial: "shared/sidebar", locals: { current_project: @project, page: 'project' } %>
|
||||
<%= render partial: "shared/secondary_navigation", locals: {
|
||||
<%= render partial: "shared/secondary_navigation", locals: {
|
||||
editable: {
|
||||
name: 'title',
|
||||
active: true,
|
||||
active: can_manage_project?(@project),
|
||||
width: 'calc(100% - 500px)',
|
||||
params_group: 'project',
|
||||
field_to_udpate: 'name',
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="row protocol-info">
|
||||
<div class="col-xs-6 col-sm-4 col-md-4">
|
||||
<div class="badge-icon">
|
||||
<span class="fas fa-calendar-alt"></span>
|
||||
|
@ -31,7 +31,7 @@
|
|||
|
||||
<div class="col-xs-12 col-sm-4 col-md-4">
|
||||
<div class="badge-icon">
|
||||
<span class="fas fa-user fa-lg"></span>
|
||||
<span class="fas fa-user"></span>
|
||||
</div>
|
||||
<div class="well well-sm">
|
||||
<span class="hidden-xs hidden-sm hidden-md"><%=t "protocols.header.added_by" %>:</span>
|
||||
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-6 col-md-6">
|
||||
<div class="col-xs-12 col-sm-8 col-md-8">
|
||||
<div class="badge-icon">
|
||||
<% if can_manage_protocol_in_repository?(@protocol) %>
|
||||
<a data-action="edit-keywords" data-remote="true" href="<%= edit_keywords_modal_protocol_path(@protocol, format: :json) %>" style="color: inherit;">
|
||||
|
@ -61,7 +61,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-6 col-md-6">
|
||||
<div class="col-xs-12 col-sm-4 col-md-4">
|
||||
<div class="badge-icon">
|
||||
<% if can_manage_protocol_in_repository?(@protocol) %>
|
||||
<a data-action="edit-authors" data-remote="true" href="<%= edit_authors_modal_protocol_path(@protocol, format: :json) %>" style="color: inherit;">
|
||||
|
@ -83,7 +83,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12 protocol-description">
|
||||
<%= render partial: "protocols/header/description_label.html.erb", locals: {edit_mode: true} %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -31,17 +31,7 @@
|
|||
<%= javascript_include_tag "handsontable.full.min" %>
|
||||
|
||||
<!-- Libraries for formulas -->
|
||||
<%= javascript_include_tag "lodash" %>
|
||||
<%= javascript_include_tag "numeral" %>
|
||||
<%= javascript_include_tag "numeric" %>
|
||||
<%= javascript_include_tag "md5" %>
|
||||
<%= javascript_include_tag "jstat" %>
|
||||
<%= javascript_include_tag "formula" %>
|
||||
<%= javascript_include_tag "parser" %>
|
||||
<%= javascript_include_tag "ruleJS" %>
|
||||
<%= javascript_include_tag "handsontable.formula" %>
|
||||
<%= javascript_include_tag "big.min" %>
|
||||
<%= stylesheet_link_tag "handsontable.formula" %>
|
||||
<%= render partial: "shared/formulas_libraries.html.erb" %>
|
||||
|
||||
<%= javascript_include_tag "assets/wopi/create_wopi_file" %>
|
||||
<%= javascript_include_tag "protocols/steps" %>
|
||||
|
|
|
@ -10,3 +10,5 @@
|
|||
</div>
|
||||
|
||||
<%= javascript_include_tag "protocols/edit" %>
|
||||
<!-- Create new office file modal -->
|
||||
<%= render partial: 'assets/wopi/create_wopi_file_modal.html.erb' %>
|
|
@ -150,17 +150,7 @@
|
|||
<%= javascript_include_tag "handsontable.full.min" %>
|
||||
|
||||
<!-- Libraries for formulas -->
|
||||
<%= javascript_include_tag "lodash" %>
|
||||
<%= javascript_include_tag "numeral" %>
|
||||
<%= javascript_include_tag "numeric" %>
|
||||
<%= javascript_include_tag "md5" %>
|
||||
<%= javascript_include_tag "jstat" %>
|
||||
<%= javascript_include_tag "formula" %>
|
||||
<%= javascript_include_tag "parser" %>
|
||||
<%= javascript_include_tag "ruleJS" %>
|
||||
<%= javascript_include_tag "handsontable.formula" %>
|
||||
<%= javascript_include_tag "big.min" %>
|
||||
<%= stylesheet_link_tag "handsontable.formula" %>
|
||||
<%= render partial: "shared/formulas_libraries.html.erb" %>
|
||||
|
||||
<%= stylesheet_link_tag 'datatables' %>
|
||||
<%= javascript_include_tag "assets/wopi/create_wopi_file" %>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="row">
|
||||
<div class="row protocol-info">
|
||||
<div class="col-xs-6 col-sm-4 col-md-4">
|
||||
<div class="badge-icon">
|
||||
<span class="fas fa-calendar-alt"></span>
|
||||
|
@ -21,7 +21,7 @@
|
|||
|
||||
<div class="col-xs-12 col-sm-4 col-md-4">
|
||||
<div class="badge-icon">
|
||||
<span class="fas fa-user fa-lg"></span>
|
||||
<span class="fas fa-user"></span>
|
||||
</div>
|
||||
<div class="well well-sm">
|
||||
<span class="hidden-xs hidden-sm hidden-md"><%=t "protocols.header.added_by" %>:</span>
|
||||
|
@ -29,7 +29,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-6 col-md-6">
|
||||
<div class="col-xs-12 col-sm-8 col-md-8">
|
||||
<div class="badge-icon">
|
||||
<span class="fas fa-font"></span>
|
||||
</div>
|
||||
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-6 col-md-6">
|
||||
<div class="col-xs-12 col-sm-4 col-md-4">
|
||||
<div class="badge-icon">
|
||||
<span class="fas fa-graduation-cap"></span>
|
||||
</div>
|
||||
|
@ -49,12 +49,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||||
<div class="badge-icon">
|
||||
<span class="fas fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-12 col-md-12 protocol-description">
|
||||
<div class="well well-sm">
|
||||
<span class="hidden-xs hidden-sm hidden-md"><%=t "protocols.header.description" %>:</span>
|
||||
<%= render partial: "protocols/header/description_label.html.erb", locals: {edit_mode: false} %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -68,88 +64,22 @@
|
|||
</div>
|
||||
<div id="steps">
|
||||
<% protocol.steps.order(:position).each do |step| %>
|
||||
<div class ="step <%= step.completed? ? "completed" : "not-completed" %>">
|
||||
<div class="badge-num">
|
||||
<span class="badge size-digit-<%= (step.position_plus_one).to_s.length %>"><%= step.position_plus_one %></span>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong><%= step.name %></strong> |
|
||||
<span><%= t("protocols.steps.published_on", timestamp: l(step.created_at, format: :full), user: h(step.user.full_name)).html_safe %></span>
|
||||
</div>
|
||||
<div class="panel-collapse collapse in" id="step-panel-<%= step.id %>" role="tabpanel" aria-expanded="true">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<% if sanitize_input(step.description).blank? %>
|
||||
<em><%= t("protocols.steps.no_description") %></em>
|
||||
<% else %>
|
||||
<div class="ql-editor">
|
||||
<%= sanitize_input(step.tinymce_render(:description), ['img']) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<% unless step.tables.blank? then %>
|
||||
<hr>
|
||||
<div class="col-xs-12">
|
||||
<% step.tables.each do |table| %>
|
||||
<strong><%= table.name %></strong>
|
||||
<div data-role="hot-table" class="hot-table">
|
||||
<%= hidden_field(table, :contents, value: table.contents_utf_8, class: "hot-contents") %>
|
||||
<div data-role="step-hot-table" class="step-result-hot-table"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% assets = ordered_assets(step) %>
|
||||
<% unless assets.blank? then %>
|
||||
<hr>
|
||||
<div class="col-xs-12">
|
||||
<strong><%= t("protocols.steps.files") %></strong>
|
||||
<ul>
|
||||
<% assets.each do |asset| %>
|
||||
<li>
|
||||
<%= render partial: "shared/asset_link", locals: { asset: asset, display_image_tag: true }, formats: :html %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% unless step.checklists.blank? then %>
|
||||
<div class="col-xs-12">
|
||||
<% step.checklists.asc.each do |checklist| %>
|
||||
<strong><%= checklist.name %></strong>
|
||||
<% if checklist.checklist_items.empty? %>
|
||||
</br>
|
||||
<%= t("protocols.steps.empty_checklist") %>
|
||||
</br>
|
||||
<% else %>
|
||||
<% ordered_checklist_items(checklist).each do |checklist_item| %>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<% if protocol.in_module? %>
|
||||
<input type="checkbox" value="" <%= "checked" if checklist_item.checked? %> disabled="disabled"/>
|
||||
<% else %>
|
||||
<input type="checkbox" value="" disabled="disabled" />
|
||||
<% end %>
|
||||
<%= checklist_item.text %>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render partial: "steps/step.html.erb", locals: { step: step, preview: true } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= javascript_include_tag "handsontable.full.min" %>
|
||||
<%= javascript_include_tag "lodash" %>
|
||||
<%= javascript_include_tag "numeral" %>
|
||||
<%= javascript_include_tag "numeric" %>
|
||||
<%= javascript_include_tag "md5" %>
|
||||
<%= javascript_include_tag "jstat" %>
|
||||
<%= javascript_include_tag "formula" %>
|
||||
<%= javascript_include_tag "parser" %>
|
||||
<%= javascript_include_tag "ruleJS" %>
|
||||
<%= javascript_include_tag "handsontable.formula" %>
|
||||
<%= javascript_include_tag "big.min" %>
|
||||
<%= stylesheet_link_tag "handsontable.formula" %>
|
||||
|
||||
<%= javascript_include_tag "protocols/steps" %>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
<div class="report-element report-module-protocol-element" data-ts="<%= protocol.created_at %>" data-type="protocol" data-id='{ "protocol_id": <%= protocol.id %> }' data-scroll-id="<%= protocol.id %>">
|
||||
<% protocol ||= my_module.protocol %>
|
||||
<div class="report-element report-module-protocol-element" data-ts="<%= protocol.created_at %>" data-type="my_module_protocol" data-id='{ "my_module_id": <%= my_module.id %> }' data-scroll-id="<%= protocol.id %>">
|
||||
<div class="report-element-header">
|
||||
<div class="row">
|
||||
<div class="pull-left user-time">
|
||||
|
@ -18,4 +18,5 @@
|
|||
<em><%= t('my_modules.protocols.protocol_status_bar.no_description') %></em>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -83,17 +83,7 @@
|
|||
<%= javascript_include_tag "handsontable.full.min" %>
|
||||
|
||||
<!-- Libraries for formulas -->
|
||||
<%= javascript_include_tag "lodash" %>
|
||||
<%= javascript_include_tag "numeral" %>
|
||||
<%= javascript_include_tag "numeric" %>
|
||||
<%= javascript_include_tag "md5" %>
|
||||
<%= javascript_include_tag "jstat" %>
|
||||
<%= javascript_include_tag "formula" %>
|
||||
<%= javascript_include_tag "parser" %>
|
||||
<%= javascript_include_tag "ruleJS" %>
|
||||
<%= javascript_include_tag "handsontable.formula" %>
|
||||
<%= javascript_include_tag "big.min" %>
|
||||
<%= stylesheet_link_tag "handsontable.formula" %>
|
||||
<%= render partial: "shared/formulas_libraries.html.erb" %>
|
||||
|
||||
<%= javascript_include_tag("reports/new") %>
|
||||
<%= javascript_include_tag 'reports/save_pdf_to_inventory' %>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<% my_module_undefined = !defined? my_module or my_module.blank? %>
|
||||
|
||||
<div>
|
||||
<em>
|
||||
<%= t("projects.reports.elements.modals.module_contents_inner.instructions") %>
|
||||
|
@ -12,6 +11,10 @@
|
|||
<%= form.check_box :module_all, label: t("projects.reports.elements.modals.module_contents_inner.check_all") %>
|
||||
<ul>
|
||||
|
||||
<li>
|
||||
<%= form.check_box :module_protocol, label: t("projects.reports.elements.modals.module_contents_inner.protocol") %>
|
||||
</li>
|
||||
|
||||
<% if my_module_undefined or my_module.protocol.steps.exists? %>
|
||||
<li>
|
||||
<%= form.check_box :module_steps, label: t("projects.reports.elements.modals.module_contents_inner.steps") %>
|
||||
|
|
|
@ -125,17 +125,7 @@
|
|||
<%= javascript_include_tag "handsontable.full.min" %>
|
||||
|
||||
<!-- Libraries for formulas -->
|
||||
<%= javascript_include_tag "lodash" %>
|
||||
<%= javascript_include_tag "numeral" %>
|
||||
<%= javascript_include_tag "numeric" %>
|
||||
<%= javascript_include_tag "md5" %>
|
||||
<%= javascript_include_tag "jstat" %>
|
||||
<%= javascript_include_tag "formula" %>
|
||||
<%= javascript_include_tag "parser" %>
|
||||
<%= javascript_include_tag "ruleJS" %>
|
||||
<%= javascript_include_tag "handsontable.formula" %>
|
||||
<%= javascript_include_tag "big.min" %>
|
||||
<%= stylesheet_link_tag "handsontable.formula" %>
|
||||
<%= render partial: "shared/formulas_libraries.html.erb" %>
|
||||
|
||||
<%= stylesheet_link_tag 'datatables' %>
|
||||
<%= javascript_include_tag "protocols/index" %>
|
||||
|
|
11
app/views/shared/_formulas_libraries.html.erb
Normal file
11
app/views/shared/_formulas_libraries.html.erb
Normal file
|
@ -0,0 +1,11 @@
|
|||
<%= javascript_include_tag "lodash" %>
|
||||
<%= javascript_include_tag "numeral" %>
|
||||
<%= javascript_include_tag "numeric" %>
|
||||
<%= javascript_include_tag "md5" %>
|
||||
<%= javascript_include_tag "jstat" %>
|
||||
<%= javascript_include_tag "formula" %>
|
||||
<%= javascript_include_tag "parser" %>
|
||||
<%= javascript_include_tag "ruleJS" %>
|
||||
<%= javascript_include_tag "handsontable.formula" %>
|
||||
<%= javascript_include_tag "big.min" %>
|
||||
<%= stylesheet_link_tag "handsontable.formula" %>
|
|
@ -6,7 +6,8 @@
|
|||
data-original-name="<%= initial_value %>"
|
||||
error="false"
|
||||
>
|
||||
<input type="text" value="<%= initial_value %>" disabled/>
|
||||
<div class="view-mode"><%= initial_value %></div>
|
||||
<input class="hidden" type="text" value="<%= initial_value %>" disabled/>
|
||||
<div class="button-container">
|
||||
<span class="save-button"><i class="fas fa-check"></i></span>
|
||||
<span class="cancel-button"><i class="fas fa-times"></i></span>
|
||||
|
|
|
@ -152,7 +152,7 @@
|
|||
<% editable = false if local_assigns[:editable].nil? %>
|
||||
<h4 class="nav-name <%= (editable && editable[:active]) ? 'editable' : '' %>">
|
||||
<% if editable && editable[:active] %>
|
||||
<%= render partial: "shared/inline_editing", locals: {
|
||||
<%= render partial: "shared/inline_editing", locals: {
|
||||
initial_value: truncate(title_element.name, length: Constants::MAX_EDGE_LENGTH),
|
||||
width: editable[:width] || '100%',
|
||||
name: editable[:name],
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
</span>
|
||||
</div>
|
||||
<div id="new-step-assets-group" class="form-group">
|
||||
<div class="col-xs-12 attacments edit">
|
||||
<div class="col-xs-12 attachments edit">
|
||||
<%= f.nested_fields_for :assets do |ff| %>
|
||||
<%= render partial: 'steps/attachments/placeholder.html.erb',
|
||||
locals: { edit_page: true, asset: ff.object, ff: ff } %>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<% preview = (defined?(preview) ? preview : false) %>
|
||||
<div class ="step <%= step.completed? ? "completed" : "not-completed" %>">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="panel-options pull-right">
|
||||
<% if can_complete_or_checkbox_step?(@protocol) %>
|
||||
<% if can_complete_or_checkbox_step?(@protocol) && !(preview) %>
|
||||
<% if step.completed? %>
|
||||
<div data-action="uncomplete-step"
|
||||
class="complete-step-btn"
|
||||
|
@ -24,8 +25,8 @@
|
|||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if can_manage_protocol_in_module?(@protocol) ||
|
||||
can_manage_protocol_in_repository?(@protocol) %>
|
||||
<% if (can_manage_protocol_in_module?(@protocol) ||
|
||||
can_manage_protocol_in_repository?(@protocol)) && !(preview) %>
|
||||
<a data-action="move-step"
|
||||
class="btn btn-link"
|
||||
href="<%= move_up_step_path(step, format: :json) %>"
|
||||
|
@ -110,7 +111,7 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render partial: 'steps/attachments/list.html.erb', locals: { step: step } %>
|
||||
<%= render partial: 'steps/attachments/list.html.erb', locals: { step: step, preview: preview } %>
|
||||
|
||||
<% unless step.checklists.blank? then %>
|
||||
<div class="col-xs-12">
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
<% if asset.file.processing? && asset.is_image? %>
|
||||
<% asset_status = 'asset-loading' %>
|
||||
<% present_url = step_file_present_asset_path(asset.id) %>
|
||||
<% else %>
|
||||
<% asset_status = 'asset-present' %>
|
||||
<% present_url = '' %>
|
||||
<% end %>
|
||||
|
||||
<div class="pseudo-attachment-container" style="order: <%= assets_count - i %>">
|
||||
<%= link_to download_asset_path(asset),
|
||||
class: 'file-preview-link',
|
||||
id: "modal_link#{asset.id}",
|
||||
data: { no_turbolink: true, id: true, status: 'asset-present',
|
||||
data: { no_turbolink: true,
|
||||
id: true,
|
||||
status: asset_status,
|
||||
'present-url': present_url,
|
||||
'preview-url': asset_file_preview_path(asset),
|
||||
'order-atoz': az_ordered_assets_index(step, asset.id),
|
||||
'order-ztoa': assets_count - az_ordered_assets_index(step, asset.id),
|
||||
|
|
|
@ -16,8 +16,11 @@
|
|||
<%= render partial: '/assets/marvinjs/create_marvin_sketch_button.html.erb',
|
||||
locals: { element_id: step.id, element_type: 'Step', sketch_container: ".attacments#att-#{step.id}" } %>
|
||||
<% end %>
|
||||
<%= render partial: '/assets/wopi/create_wopi_file_button.html.erb',
|
||||
locals: { element_id: step.id, element_type: 'Step' } %>
|
||||
|
||||
<% if !(preview) %>
|
||||
<%= 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('protocols.steps.attachments.sort_new').html_safe %></span>
|
||||
|
@ -36,7 +39,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 attacments" id="att-<%= step.id %>">
|
||||
<div class="col-xs-12 attachments" id="att-<%= step.id %>">
|
||||
<% assets.each_with_index do |asset, i| %>
|
||||
<% if asset.class.name == 'Asset' %>
|
||||
<%= render partial: 'steps/attachments/item.html.erb',
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
data-params-group="comment"
|
||||
data-path-to-update="<%= step_step_comment_path(comment.step, comment, format: :json) %>"
|
||||
data-original-name="<%= comment.message %>"
|
||||
data-response-field="comment"
|
||||
data-smart-annotation="true"
|
||||
error="false"
|
||||
>
|
||||
<div class="avatar-placehodler">
|
||||
|
@ -34,7 +36,13 @@
|
|||
<% end %>
|
||||
</div>
|
||||
<div class="comment-message">
|
||||
<%= text_area_tag 'message', comment.message, disabled: true %>
|
||||
<div class="view-mode"><%= custom_auto_link(comment.message,
|
||||
simple_format: false,
|
||||
tags: %w(img br),
|
||||
team: current_team).gsub(/\n/, '<br/>').html_safe %></div>
|
||||
<% if user_comment %>
|
||||
<%= text_area_tag 'message', comment.message, disabled: true, class: 'smart-text-area hidden' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="error-block"></div>
|
||||
</div>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
</div>
|
||||
<div class="body-block">
|
||||
<div class="datetime">
|
||||
<span><%= l(notification.last_time_changed_at, format: :full) %></span>
|
||||
<span><%= l(notification.created_at, format: :full) %></span>
|
||||
</div>
|
||||
<h5 class="title">
|
||||
<%= notification.title %>
|
||||
|
|
|
@ -23,6 +23,9 @@ class Constants
|
|||
COLOR_MAX_LENGTH = 7
|
||||
# Max characters for text in dropdown list element
|
||||
DROPDOWN_TEXT_MAX_LENGTH = 15
|
||||
# Max characters limit for (on most operating systems, it's ~255 characters,
|
||||
# but this is with a bit more safety margin)
|
||||
FILENAME_MAX_LENGTH = 100
|
||||
# Max characters for filenames, after which they get truncated
|
||||
FILENAME_TRUNCATION_LENGTH = 50
|
||||
# Max characters for names of exported files and folders, after which they get
|
||||
|
|
|
@ -38,7 +38,8 @@ class Extends
|
|||
project_samples: 14, # TODO
|
||||
experiment: 15,
|
||||
# Higher number because of addons
|
||||
my_module_repository: 17 }
|
||||
my_module_repository: 17,
|
||||
my_module_protocol: 18 }
|
||||
|
||||
# Data type name should match corresponding model's name
|
||||
REPOSITORY_DATA_TYPES = { RepositoryTextValue: 0,
|
||||
|
|
|
@ -59,6 +59,10 @@ module ReportExtends
|
|||
|
||||
# Module contents element
|
||||
MODULE_CONTENTS = [
|
||||
ModuleElement.new([:protocol],
|
||||
:protocol,
|
||||
false,
|
||||
[:my_module]),
|
||||
ModuleElement.new(%i(completed_steps uncompleted_steps),
|
||||
:steps,
|
||||
true,
|
||||
|
@ -114,6 +118,7 @@ module ReportExtends
|
|||
result_comments)
|
||||
# sets local :my_module to the listed my_module child elements
|
||||
MY_MODULE_ELEMENTS = %w(my_module
|
||||
my_module_protocol
|
||||
my_module_activity
|
||||
my_module_repository)
|
||||
|
||||
|
@ -153,6 +158,7 @@ module ReportExtends
|
|||
ElementReference.new(
|
||||
proc do |report_element|
|
||||
report_element.my_module? ||
|
||||
report_element.my_module_protocol? ||
|
||||
report_element.my_module_activity? ||
|
||||
report_element.my_module_samples?
|
||||
end,
|
||||
|
@ -198,6 +204,7 @@ module ReportExtends
|
|||
ElementReference.new(
|
||||
proc do |report_element|
|
||||
report_element.my_module? ||
|
||||
report_element.my_module_protocol? ||
|
||||
report_element.my_module_activity? ||
|
||||
report_element.my_module_samples?
|
||||
end,
|
||||
|
|
|
@ -410,6 +410,7 @@ en:
|
|||
module_contents_inner:
|
||||
instructions: "Select the information from your task that you would like to include to your report."
|
||||
check_all: "All tasks content"
|
||||
protocol: "Protocol"
|
||||
steps: "Steps"
|
||||
completed_steps: "Completed"
|
||||
uncompleted_steps: "Uncompleted"
|
||||
|
@ -599,7 +600,7 @@ en:
|
|||
start_date: "Start date:"
|
||||
due_date: "Due date:"
|
||||
tags: "Tags:"
|
||||
no_tags: "click here to add Task Tags (optional)"
|
||||
no_tags: "Add new Task Tags (optional)"
|
||||
manage_tags: "Manage tags"
|
||||
create_new_tag: "create new"
|
||||
no_description: "No task description"
|
||||
|
@ -1954,6 +1955,7 @@ en:
|
|||
error_message: 'You must choose a file'
|
||||
server_not_respond: "Didn't get a response from the server"
|
||||
saved_label: "Saved"
|
||||
leaving_warning: "You have made some changes, are you sure you want to leave this page?"
|
||||
general:
|
||||
save: "Save"
|
||||
update: "Update"
|
||||
|
@ -1981,6 +1983,7 @@ en:
|
|||
blank: "You didn't select any file"
|
||||
uploading: "If you leave this page, the file(s) that is/are currently uploading will not be saved! Are you sure you want to continue?"
|
||||
upload_failure: "Upload connection error. Try again or contact the administrator."
|
||||
file_name_too_long: 'is too long (maximum is %{limit} characters, with extension)'
|
||||
text:
|
||||
not_blank: "can't be blank"
|
||||
length_too_long_general: "is too long"
|
||||
|
|
|
@ -340,10 +340,10 @@ Rails.application.routes.draw do
|
|||
# as well as 'module info' page for single module (HTML)
|
||||
resources :my_modules, path: '/modules', only: [:show, :update] do
|
||||
resources :my_module_tags, path: '/tags', only: [:index, :create, :destroy] do
|
||||
collection do
|
||||
collection do
|
||||
get :search_tags
|
||||
end
|
||||
member do
|
||||
member do
|
||||
post :destroy_by_tag_id
|
||||
end
|
||||
end
|
||||
|
@ -581,6 +581,7 @@ Rails.application.routes.draw do
|
|||
# We cannot use 'resources :assets' because assets is a reserved route
|
||||
# in Rails (assets pipeline) and causes funky behavior
|
||||
get 'files/:id/present', to: 'assets#file_present', as: 'file_present_asset'
|
||||
get 'files/:id/present_in_step', to: 'assets#step_file_present', as: 'step_file_present_asset'
|
||||
get 'files/:id/preview',
|
||||
to: 'assets#file_preview',
|
||||
as: 'asset_file_preview'
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddUniqueIndexToSystemNotifications < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
# remove not unique index and add new with uniq
|
||||
remove_index :system_notifications, :source_id
|
||||
add_index :system_notifications, :source_id, unique: true
|
||||
|
||||
add_index :user_system_notifications, %i(user_id system_notification_id), unique: true,
|
||||
name: 'index_user_system_notifications_on_user_and_notification_id'
|
||||
end
|
||||
end
|
|
@ -10,8 +10,10 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
|
||||
ActiveRecord::Schema.define(version: 20190427115413) do
|
||||
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
enable_extension "pg_trgm"
|
||||
|
@ -695,7 +697,7 @@ ActiveRecord::Schema.define(version: 20190427115413) do
|
|||
t.datetime "updated_at", null: false
|
||||
t.index ["last_time_changed_at"], name: "index_system_notifications_on_last_time_changed_at"
|
||||
t.index ["source_created_at"], name: "index_system_notifications_on_source_created_at"
|
||||
t.index ["source_id"], name: "index_system_notifications_on_source_id"
|
||||
t.index ["source_id"], name: "index_system_notifications_on_source_id", unique: true
|
||||
end
|
||||
|
||||
create_table "tables", force: :cascade do |t|
|
||||
|
@ -834,6 +836,7 @@ ActiveRecord::Schema.define(version: 20190427115413) do
|
|||
t.index ["read_at"], name: "index_user_system_notifications_on_read_at"
|
||||
t.index ["seen_at"], name: "index_user_system_notifications_on_seen_at"
|
||||
t.index ["system_notification_id"], name: "index_user_system_notifications_on_system_notification_id"
|
||||
t.index ["user_id", "system_notification_id"], name: "index_user_system_notifications_on_user_and_notification_id", unique: true
|
||||
t.index ["user_id"], name: "index_user_system_notifications_on_user_id"
|
||||
end
|
||||
|
||||
|
|
|
@ -5,19 +5,29 @@ namespace :tinymce_assets do
|
|||
desc 'Migrate old TinyMCE images to new polymorphic format' \
|
||||
'IT SHOULD BE RUN ONE TIME ONLY'
|
||||
task migrate_tinymce_assets: :environment do
|
||||
old_images = TinyMceAsset.where('step_id IS NOT NULL OR result_text_id IS NOT NULL').where(object: nil)
|
||||
old_images.each do |old_image|
|
||||
old_format = /\[~tiny_mce_id:#{old_image.id}\]/
|
||||
new_format = "<img src='' class='img-responsive' data-mce-token='#{Base62.encode(old_image.id)}'/>"
|
||||
if old_image.step_id
|
||||
object = old_image.step
|
||||
object.description.sub!(old_format, new_format)
|
||||
else
|
||||
object = old_image.result_text
|
||||
object.text.sub!(old_format, new_format)
|
||||
ActiveRecord::Base.no_touching do
|
||||
old_images = TinyMceAsset.where('step_id IS NOT NULL OR result_text_id IS NOT NULL')
|
||||
.where(object: nil)
|
||||
.preload(:step, :result_text)
|
||||
old_images.find_each do |old_image|
|
||||
ActiveRecord::Base.transaction do
|
||||
old_format = /\[~tiny_mce_id:#{old_image.id}\]/
|
||||
new_format = "<img src='' class='img-responsive' data-mce-token='#{Base62.encode(old_image.id)}'/>"
|
||||
if old_image.step_id
|
||||
object = old_image.step
|
||||
object.description.sub!(old_format, new_format)
|
||||
else
|
||||
object = old_image.result_text
|
||||
object.text.sub!(old_format, new_format)
|
||||
end
|
||||
object.save!
|
||||
old_image.update!(object_id: object.id, object_type: object.class.to_s, step_id: nil, result_text_id: nil)
|
||||
rescue StandardError => ex
|
||||
Rails.logger.error "Failed to update TinyMceAsset id: #{old_image.id}"
|
||||
Rails.logger.error ex.message
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
object.save
|
||||
old_image.update(object: object, step_id: nil, result_text_id: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
14824
package-lock.json
generated
14824
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,7 @@ FactoryBot.define do
|
|||
modal_title { Faker::Name.first_name }
|
||||
modal_body { Faker::Lorem.paragraphs(4).map { |pr| "<p>#{pr}</p>" }.join }
|
||||
source_created_at { Faker::Time.between(3.days.ago, Date.today) }
|
||||
source_id { Faker::Number.between(1, 1000) }
|
||||
source_id { SystemNotification.order(source_id: :desc).first&.source_id.to_i + 1 }
|
||||
last_time_changed_at { Time.now }
|
||||
trait :show_on_login do
|
||||
show_on_login { true }
|
||||
|
|
|
@ -56,11 +56,12 @@ describe SystemNotification do
|
|||
end
|
||||
|
||||
context 'when there is no system notifications in db' do
|
||||
it 'returns last user created_at' do
|
||||
it 'returns first users created_at' do
|
||||
create :user
|
||||
create :user, created_at: Time.now + 5.seconds
|
||||
|
||||
expect(described_class.last_sync_timestamp)
|
||||
.to be == User.last.created_at.to_i
|
||||
.to be == User.first.created_at.to_i
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -15,36 +15,6 @@ describe UserSystemNotification do
|
|||
it { is_expected.to belong_to(:system_notification) }
|
||||
end
|
||||
|
||||
describe '.create' do
|
||||
before do
|
||||
Delayed::Worker.delay_jobs = false
|
||||
end
|
||||
|
||||
after do
|
||||
Delayed::Worker.delay_jobs = true
|
||||
end
|
||||
|
||||
context 'when user has enabled notifications' do
|
||||
it 'calls send an email on creation' do
|
||||
allow(user_system_notification.user)
|
||||
.to receive(:system_message_email_notification).and_return(true)
|
||||
|
||||
expect(user_system_notification).to receive(:send_email)
|
||||
user_system_notification.save
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has disabled notifications' do
|
||||
it 'doesn\'t call send an email on createion' do
|
||||
allow(user_system_notification.user)
|
||||
.to receive(:system_message_email_notification).and_return(false)
|
||||
|
||||
expect(user_system_notification).not_to receive(:send_email)
|
||||
user_system_notification.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Methods' do
|
||||
let(:notifcation_one) { create :system_notification }
|
||||
let(:notifcation_two) { create :system_notification }
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Notifications::HandleSystemNotificationInCommunicationChannelService do
|
||||
let(:system_notification) { create :system_notification }
|
||||
let!(:user_system_notification) do
|
||||
create :user_system_notification, user: user, system_notification: system_notification
|
||||
end
|
||||
let(:user) { create :user }
|
||||
let(:service_call) do
|
||||
Notifications::HandleSystemNotificationInCommunicationChannelService.call(system_notification)
|
||||
end
|
||||
|
||||
before do
|
||||
Delayed::Worker.delay_jobs = false
|
||||
end
|
||||
|
||||
after do
|
||||
Delayed::Worker.delay_jobs = true
|
||||
end
|
||||
|
||||
context 'when user has enabled notifications' do
|
||||
it 'calls AppMailer' do
|
||||
allow_any_instance_of(User).to receive(:system_message_email_notification).and_return(true)
|
||||
|
||||
expect(AppMailer).to receive(:system_notification).and_return(double('Mailer', deliver: true))
|
||||
|
||||
service_call
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has disabled notifications' do
|
||||
it 'does not call AppMailer' do
|
||||
allow_any_instance_of(User).to receive(:system_message_email_notification).and_return(false)
|
||||
|
||||
expect(AppMailer).not_to receive(:system_notification)
|
||||
|
||||
service_call
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Notifications::PushToCommunicationChannelService do
|
||||
let(:system_notification) { create :system_notification }
|
||||
let(:service_call) do
|
||||
Notifications::PushToCommunicationChannelService.call(item_id: system_notification.id,
|
||||
item_type: system_notification.class.name)
|
||||
end
|
||||
|
||||
context 'when call with valid items' do
|
||||
it 'call service to to handle sending out' do
|
||||
expect(Notifications::HandleSystemNotificationInCommunicationChannelService)
|
||||
.to receive(:call).with(system_notification)
|
||||
|
||||
service_call
|
||||
end
|
||||
end
|
||||
|
||||
context 'when call with not valid items' do
|
||||
it 'returns error with key invalid_arguments when system notification not exists' do
|
||||
allow(SystemNotification).to receive(:find).and_return(nil)
|
||||
|
||||
expect(service_call.errors).to have_key(:invalid_arguments)
|
||||
end
|
||||
|
||||
it 'raise error when have not listed object' do
|
||||
u = create :user
|
||||
|
||||
expect do
|
||||
Notifications::PushToCommunicationChannelService.call(item_id: u.id, item_type: 'User')
|
||||
end.to(raise_error('Dont know how to handle this type of items'))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,6 +6,8 @@ describe Notifications::SyncSystemNotificationsService do
|
|||
url = 'http://system-notifications-service.test/api/system_notifications'
|
||||
let!(:user) { create :user }
|
||||
let(:service_call) do
|
||||
allow_any_instance_of(Notifications::PushToCommunicationChannelService).to receive(:call).and_return(nil)
|
||||
|
||||
Notifications::SyncSystemNotificationsService.call
|
||||
end
|
||||
|
||||
|
@ -80,6 +82,14 @@ describe Notifications::SyncSystemNotificationsService do
|
|||
|
||||
expect { service_call }.to change { UserSystemNotification.count }.by(20)
|
||||
end
|
||||
|
||||
it 'calls service to notify users about notification' do
|
||||
Delayed::Worker.delay_jobs = false
|
||||
expect(Notifications::PushToCommunicationChannelService).to receive(:call).exactly(10)
|
||||
|
||||
service_call
|
||||
Delayed::Worker.delay_jobs = true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when request is unsuccessful' do
|
||||
|
@ -101,5 +111,13 @@ describe Notifications::SyncSystemNotificationsService do
|
|||
|
||||
expect(service_call.errors).to have_key(:socketerror)
|
||||
end
|
||||
|
||||
it 'does not call service to notify users about notification' do
|
||||
Delayed::Worker.delay_jobs = false
|
||||
expect(Notifications::PushToCommunicationChannelService).to_not receive(:call)
|
||||
|
||||
service_call
|
||||
Delayed::Worker.delay_jobs = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,11 +59,14 @@ describe RepositoryZipExport, type: :background_job do
|
|||
end
|
||||
|
||||
it 'generates a new zip export object' do
|
||||
ZipExport.skip_callback(:create, :after, :self_destruct)
|
||||
RepositoryZipExport.generate_zip(params, repository, user)
|
||||
expect(ZipExport.count).to eq 1
|
||||
ZipExport.set_callback(:create, :after, :self_destruct)
|
||||
end
|
||||
|
||||
it 'generates a zip with csv file with exported rows' do
|
||||
ZipExport.skip_callback(:create, :after, :self_destruct)
|
||||
RepositoryZipExport.generate_zip(params, repository, user)
|
||||
csv_zip_file = ZipExport.first.zip_file
|
||||
parsed_csv_content = Zip::File.open(csv_zip_file.path) do |zip_file|
|
||||
|
@ -80,6 +83,7 @@ describe RepositoryZipExport, type: :background_job do
|
|||
expect(row_hash.fetch('Name')).to eq "row #{index}"
|
||||
index += 1
|
||||
end
|
||||
ZipExport.set_callback(:create, :after, :self_destruct)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue