mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-07 21:55:20 +08:00
Merge branch 'master' of https://github.com/biosistemika/scinote-web into zd_SCI_1248
This commit is contained in:
commit
9bec7dd96e
37 changed files with 898 additions and 173 deletions
1
Gemfile
1
Gemfile
|
@ -14,6 +14,7 @@ gem 'bootstrap_form'
|
|||
gem 'yomu'
|
||||
gem 'font-awesome-rails', '~> 4.6'
|
||||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
gem 'sanitize', '~> 4.4'
|
||||
|
||||
# JS datetime library, requirement of datetime picker
|
||||
gem 'momentjs-rails', '>= 2.9.0'
|
||||
|
|
|
@ -103,6 +103,7 @@ GEM
|
|||
colorize (0.8.1)
|
||||
commit_param_routing (0.0.1)
|
||||
concurrent-ruby (1.0.0)
|
||||
crass (1.0.2)
|
||||
debug_inspector (0.0.2)
|
||||
deface (1.0.2)
|
||||
colorize (>= 0.5.8)
|
||||
|
@ -192,6 +193,8 @@ GEM
|
|||
nokogiri (1.6.8)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
pkg-config (~> 1.1.7)
|
||||
nokogumbo (1.4.10)
|
||||
nokogiri
|
||||
oj (2.17.4)
|
||||
orm_adapter (0.5.0)
|
||||
paperclip (4.3.2)
|
||||
|
@ -266,6 +269,10 @@ GEM
|
|||
ruby-graphviz (1.2.2)
|
||||
ruby-progressbar (1.8.1)
|
||||
rubyzip (1.1.7)
|
||||
sanitize (4.4.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.4.4)
|
||||
nokogumbo (~> 1.4.1)
|
||||
sass (3.4.23)
|
||||
sass-rails (5.0.4)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
|
@ -387,6 +394,7 @@ DEPENDENCIES
|
|||
rubocop
|
||||
ruby-graphviz (~> 1.2)
|
||||
rubyzip
|
||||
sanitize (~> 4.4)
|
||||
sass-rails (~> 5.0)
|
||||
scss_lint
|
||||
sdoc (~> 0.4.0)
|
||||
|
|
|
@ -544,14 +544,12 @@ $("[data-action='new-step']").on("ajax:success", function(e, data) {
|
|||
toggleButtons(false);
|
||||
initializeCheckboxSorting();
|
||||
|
||||
|
||||
$("#step_name").focus();
|
||||
$("#new-step-main-tab a").on("shown.bs.tab", function() {
|
||||
$("#step_name").focus();
|
||||
});
|
||||
|
||||
TinyMCE.refresh();
|
||||
|
||||
});
|
||||
|
||||
// Needed because server-side validation failure clears locations of
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
Results.toggleResultEditButtons(false);
|
||||
$('#result_name').focus();
|
||||
},
|
||||
error: function() {
|
||||
error: function(xhr, status, e) {
|
||||
$(this).renderFormErrors('result', xhr.responseJSON, true, e);
|
||||
animateSpinner(null, false);
|
||||
initNewResultAsset();
|
||||
}
|
||||
|
@ -74,15 +75,8 @@
|
|||
initPreviewModal();
|
||||
Comments.initialize();
|
||||
initNewResultAsset();
|
||||
}).on('ajax:error', function(e, data) {
|
||||
// This check is here only because of remotipart bug, which returns
|
||||
// HTML instead of JSON, go figure
|
||||
var errors = '';
|
||||
if (data.errors)
|
||||
errors = data.errors;
|
||||
else
|
||||
errors = data.responseJSON.errors;
|
||||
$form.renderFormErrors('result', errors, true, e);
|
||||
}).on('ajax:error', function(xhr, status, e) {
|
||||
$form.renderFormErrors('result', xhr.responseJSON, true, e);
|
||||
animateSpinner(null, false);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -154,7 +154,10 @@ function dataTableInit() {
|
|||
fnInitComplete: function(oSettings, json) {
|
||||
// Reload correct column order and visibility (if you refresh page)
|
||||
for (var i = 0; i < table.columns()[0].length; i++) {
|
||||
var visibility = myData.columns[i].visible;
|
||||
var visibility = false;
|
||||
if (myData.columns[i]) {
|
||||
visibility = myData.columns[i].visible;
|
||||
}
|
||||
if (typeof (visibility) === 'string') {
|
||||
visibility = (visibility === 'true');
|
||||
}
|
||||
|
@ -976,7 +979,6 @@ function changeToEditMode() {
|
|||
// Add number of columns
|
||||
$('#samples').data('num-columns',
|
||||
$('#samples').data('num-columns') + 1);
|
||||
|
||||
// Add column to table (=table header)
|
||||
originalHeader.append(
|
||||
'<th class="custom-field" id="' + data.id + '" ' +
|
||||
|
@ -1181,7 +1183,7 @@ function changeToEditMode() {
|
|||
text.html(generateColumnNameTooltip(newName));
|
||||
$(table.columns().header()).filter('#' + id)
|
||||
.html(generateColumnNameTooltip(newName));
|
||||
|
||||
originalHeader.find('#' + id).html(newName);
|
||||
cancelEditMode();
|
||||
initHeaderTooltip();
|
||||
},
|
||||
|
|
|
@ -2,18 +2,85 @@
|
|||
* Define AJAX methods for handling errors on forms.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Render errors specified in array of strings format (or string if
|
||||
* just one error) for a single form element.
|
||||
*
|
||||
* Show error message/s and mark error input (if errMsgs is defined)
|
||||
* and, if present, mark and show the tab where the error occured and
|
||||
* focus/scroll to the error input, if it is the first one to be
|
||||
* specified or if errMsgs is undefined.
|
||||
*
|
||||
* @param {string} errAttributes Span element (error) attributes
|
||||
* @param {boolean} clearErr Set clearErr to true if this is the only
|
||||
* error that can happen/show.
|
||||
*/
|
||||
var renderFormError = function(ev, input, errMsgs, clearErr, errAttributes) {
|
||||
clearErr = _.isUndefined(clearErr) ? false : clearErr;
|
||||
errAttributes = _.isUndefined(errAttributes) ? '' : ' ' + errAttributes;
|
||||
var $form = $(input).closest('form');
|
||||
|
||||
if (!_.isUndefined(errMsgs)) {
|
||||
if (clearErr) {
|
||||
$form.clearFormErrors();
|
||||
}
|
||||
|
||||
// Mark error form group
|
||||
var $formGroup = $(input).closest('.form-group');
|
||||
if (!$formGroup.hasClass('has-error')) {
|
||||
$formGroup.addClass('has-error');
|
||||
}
|
||||
|
||||
// Add error message/s
|
||||
var errorText = ($.makeArray(errMsgs).map(function(m) {
|
||||
return m.strToErrorFormat();
|
||||
})).join('<br />');
|
||||
var $errSpan = "<span class='help-block'" +
|
||||
errAttributes + '>' + errorText + '</span>';
|
||||
$formGroup.append($errSpan);
|
||||
}
|
||||
|
||||
var $parent;
|
||||
var $tab = $(input).closest('.tab-pane');
|
||||
if ($tab.length) {
|
||||
// Mark error tab
|
||||
tabsPropagateErrorClass($form);
|
||||
$parent = $tab;
|
||||
} else {
|
||||
$parent = $form;
|
||||
}
|
||||
|
||||
// Focus and scroll to the error if it is the first (most upper) one
|
||||
if ($parent.find('.form-group.has-error').length === 1 ||
|
||||
_.isUndefined(errMsgs)) {
|
||||
goToFormElement(input);
|
||||
}
|
||||
|
||||
if (!_.isUndefined(ev)) {
|
||||
// Don't submit form
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Render errors specified in JSON format for many form elements.
|
||||
*/
|
||||
$.fn.renderFormErrors = function (modelName, errors, clear, ev) {
|
||||
clear = (typeof clear !== 'undefined') ? clear : true;
|
||||
$.fn.renderFormErrors = function(modelName, errors, clear, ev) {
|
||||
clear = ((typeof clear) === 'undefined') ? true : clear;
|
||||
if (clear || _.isUndefined(clear)) {
|
||||
this.clearFormErrors();
|
||||
}
|
||||
|
||||
var form = $(this);
|
||||
$.each(errors, function (field, messages) {
|
||||
$input = $(_.filter(form.find('input, select, textarea'), function (el) {
|
||||
$.each(errors, function(field, messages) {
|
||||
// Special exception for file uploads in steps and results
|
||||
if (field === 'assets.file') {
|
||||
field = 'assets_attributes';
|
||||
} else if (field === 'asset.file') {
|
||||
field = 'asset_attribute';
|
||||
}
|
||||
var types = 'input, file, select, textarea';
|
||||
var $input = $(_.filter(form.find(types), function(el) {
|
||||
var name = $(el).attr('name');
|
||||
if (name) {
|
||||
return name.match(new RegExp(modelName + '\\[' + field + '\\(?'));
|
||||
|
@ -25,80 +92,22 @@ $.fn.renderFormErrors = function (modelName, errors, clear, ev) {
|
|||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Render errors specified in array of strings format (or string if
|
||||
* just one error) for a single form element.
|
||||
*
|
||||
* Show error message/s and mark error input (if errMsgs is defined)
|
||||
* and, if present, mark and show the tab where the error occured and
|
||||
* focus/scroll to the error input, if it is the first one to be
|
||||
* specified or if errMsgs is undefined.
|
||||
*
|
||||
* @param {string} errAttributes Span element (error) attributes
|
||||
* @param {boolean} clearErr Set clearErr to true if this is the only
|
||||
* error that can happen/show.
|
||||
*/
|
||||
var renderFormError = function (ev, input, errMsgs, clearErr, errAttributes) {
|
||||
clearErr = _.isUndefined(clearErr) ? false : clearErr;
|
||||
errAttributes = _.isUndefined(errAttributes) ? "" : " " + errAttributes;
|
||||
$form = $(input).closest("form");
|
||||
|
||||
if (!_.isUndefined(errMsgs)) {
|
||||
if (clearErr) {
|
||||
$form.clearFormErrors();
|
||||
}
|
||||
|
||||
// Mark error form group
|
||||
$formGroup = $(input).closest(".form-group");
|
||||
if (!$formGroup.hasClass("has-error")) {
|
||||
$formGroup.addClass("has-error");
|
||||
}
|
||||
|
||||
// Add error message/s
|
||||
error_text = ($.makeArray(errMsgs).map(function (m) {
|
||||
return m.strToErrorFormat();
|
||||
})).join("<br />");
|
||||
$errSpan = "<span class='help-block'" + errAttributes + ">" + error_text + "</span>";
|
||||
$formGroup.append($errSpan);
|
||||
}
|
||||
|
||||
$tab = $(input).closest(".tab-pane");
|
||||
if ($tab.length) {
|
||||
// Mark error tab
|
||||
tabsPropagateErrorClass($form);
|
||||
$parent = $tab;
|
||||
} else {
|
||||
$parent = $form;
|
||||
}
|
||||
|
||||
// Focus and scroll to the error if it is the first (most upper) one
|
||||
if ($parent.find(".form-group.has-error").length === 1 || _.isUndefined(errMsgs)) {
|
||||
goToFormElement(input);
|
||||
}
|
||||
|
||||
if(!_.isUndefined(ev)) {
|
||||
// Don't submit form
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If any of form tabs (if exist) has errors, mark it and
|
||||
* and show the first erroneous tab.
|
||||
*/
|
||||
function tabsPropagateErrorClass($form) {
|
||||
var $contents = $form.find("div.tab-pane");
|
||||
_.each($contents, function (tab) {
|
||||
var $contents = $form.find('div.tab-pane');
|
||||
_.each($contents, function(tab) {
|
||||
var $tab = $(tab);
|
||||
var $errorFields = $tab.find(".has-error");
|
||||
var $errorFields = $tab.find('.has-error');
|
||||
if ($errorFields.length) {
|
||||
var id = $tab.attr("id");
|
||||
var id = $tab.attr('id');
|
||||
var navLink = $form.find("a[href='#" + id + "'][data-toggle='tab']");
|
||||
if (navLink.parent().length) {
|
||||
navLink.parent().addClass("has-error");
|
||||
navLink.parent().addClass('has-error');
|
||||
}
|
||||
}
|
||||
});
|
||||
$form.find(".nav-tabs .has-error:first > a", $form).tab("show");
|
||||
$form.find('.nav-tabs .has-error:first > a', $form).tab('show');
|
||||
}
|
||||
|
|
|
@ -200,7 +200,7 @@ path, ._jsPlumb_endpoint {
|
|||
cursor: pointer;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 294px;
|
||||
width: 300px;
|
||||
z-index: 5;
|
||||
|
||||
.panel-body .due-date-link {
|
||||
|
|
|
@ -282,7 +282,9 @@ a {
|
|||
|
||||
.badge-indicator,
|
||||
.btn .badge-indicator {
|
||||
font-size: 9px;
|
||||
margin-left: -8px;
|
||||
padding: 3px 5px;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ module ReportActions
|
|||
|
||||
def generate_experiment_contents_json(experiment, selected_modules)
|
||||
res = []
|
||||
experiment.my_modules.each do |my_module|
|
||||
experiment.my_modules.order(:workflow_order).each do |my_module|
|
||||
next unless selected_modules.include?(my_module.id)
|
||||
|
||||
res << generate_new_el(false)
|
||||
|
@ -72,9 +72,9 @@ module ReportActions
|
|||
ReportExtends::MODULE_CONTENTS.each do |contents|
|
||||
protocol = contents.element == :step ? my_module.protocol.present? : true
|
||||
next unless in_params?("module_#{contents.element}".to_sym) && protocol
|
||||
res << generate_new_el(false)
|
||||
if contents.children
|
||||
contents.collection(my_module).each do |report_el|
|
||||
res << generate_new_el(false)
|
||||
el = generate_el(
|
||||
"reports/elements/my_module_#{contents
|
||||
.element
|
||||
|
@ -82,9 +82,9 @@ module ReportActions
|
|||
.singularize}_element.html.erb",
|
||||
contents.parse_locals([report_el])
|
||||
)
|
||||
if contents.element == :step
|
||||
if contents.locals.first == :step
|
||||
el[:children] = generate_step_contents_json(report_el)
|
||||
elsif contents.element == :result
|
||||
elsif contents.locals.first == :result
|
||||
el[:children] = generate_result_contents_json(report_el)
|
||||
end
|
||||
res << el
|
||||
|
@ -92,6 +92,7 @@ module ReportActions
|
|||
else
|
||||
file_name = contents.file_name
|
||||
file_name = contents.element if contents.element == :samples
|
||||
res << generate_new_el(false)
|
||||
res << generate_el(
|
||||
"reports/elements/my_module_#{file_name}_element.html.erb",
|
||||
contents.parse_locals([my_module, :asc])
|
||||
|
@ -105,7 +106,7 @@ module ReportActions
|
|||
def generate_step_contents_json(step)
|
||||
res = []
|
||||
if in_params? :step_checklists
|
||||
step.checklists.each do |checklist|
|
||||
step.checklists.asc.each do |checklist|
|
||||
res << generate_new_el(false)
|
||||
res << generate_el(
|
||||
'reports/elements/step_checklist_element.html.erb',
|
||||
|
|
|
@ -185,6 +185,8 @@ class ExperimentsController < ApplicationController
|
|||
cloned_experiment = @experiment.deep_clone_to_project(current_user,
|
||||
project)
|
||||
success = cloned_experiment.valid?
|
||||
# Create workflow image
|
||||
cloned_experiment.delay.generate_workflow_img if success
|
||||
else
|
||||
success = false
|
||||
end
|
||||
|
|
|
@ -72,11 +72,7 @@ class ResultAssetsController < ApplicationController
|
|||
}, status: :ok
|
||||
end
|
||||
else
|
||||
# This response is sent as 200 OK due to IE security error when
|
||||
# accessing iframe content.
|
||||
format.json do
|
||||
render json: { status: 'error', errors: @result.errors }
|
||||
end
|
||||
format.json { render json: @result.errors, status: :bad_request }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -97,11 +97,6 @@ class StepsController < ApplicationController
|
|||
status: :ok
|
||||
end
|
||||
else
|
||||
# On error, delete the newly added files from S3, as they were
|
||||
# uploaded on client-side (in case of client-side hacking of
|
||||
# asset's signature response)
|
||||
Asset.destroy_all(new_assets)
|
||||
|
||||
format.json {
|
||||
render json: {
|
||||
html: render_to_string(partial: 'new.html.erb')
|
||||
|
|
|
@ -53,6 +53,7 @@ class TeamsController < ApplicationController
|
|||
)
|
||||
|
||||
if @temp_file.save
|
||||
@temp_file.destroy_obsolete
|
||||
# format.html
|
||||
format.json {
|
||||
render :json => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module Users
|
||||
class InvitationsController < Devise::InvitationsController
|
||||
include InputSanitizeHelper
|
||||
include UsersGenerator
|
||||
|
||||
prepend_before_action :check_captcha, only: [:update]
|
||||
|
@ -175,8 +176,8 @@ module Users
|
|||
message = "#{I18n.t('search.index.team')} #{team.name}"
|
||||
notification = Notification.create(
|
||||
type_of: :assignment,
|
||||
title: ActionController::Base.helpers.sanitize(title),
|
||||
message: ActionController::Base.helpers.sanitize(message)
|
||||
title: sanitize_input(title),
|
||||
message: sanitize_input(message)
|
||||
)
|
||||
|
||||
if target_user.assignments_notification
|
||||
|
|
|
@ -92,10 +92,8 @@ module ApplicationHelper
|
|||
def generate_annotation_notification(target_user, title, message)
|
||||
notification = Notification.create(
|
||||
type_of: :assignment,
|
||||
title:
|
||||
ActionController::Base.helpers.sanitize(title),
|
||||
message:
|
||||
ActionController::Base.helpers.sanitize(message)
|
||||
title: sanitize_input(title),
|
||||
message: sanitize_input(message)
|
||||
)
|
||||
if target_user.assignments_notification
|
||||
UserNotification.create(notification: notification, user: target_user)
|
||||
|
@ -122,11 +120,13 @@ module ApplicationHelper
|
|||
project = Project.find_by_id(match[3].base62_decode)
|
||||
next unless project
|
||||
if project.archived?
|
||||
"<span class='sa-type'>#{sanitize(match[2])}</span> " \
|
||||
"<span class='sa-type'>" \
|
||||
"#{sanitize_input(match[2])}</span> " \
|
||||
"#{link_to project.name,
|
||||
projects_archive_path} #{I18n.t('atwho.res.archived')}"
|
||||
else
|
||||
"<span class='sa-type'>#{sanitize(match[2])}</span> " \
|
||||
"<span class='sa-type'>" \
|
||||
"#{sanitize_input(match[2])}</span> " \
|
||||
"#{link_to project.name,
|
||||
project_path(project)}"
|
||||
end
|
||||
|
@ -134,12 +134,14 @@ module ApplicationHelper
|
|||
experiment = Experiment.find_by_id(match[3].base62_decode)
|
||||
next unless experiment
|
||||
if experiment.archived?
|
||||
"<span class='sa-type'>#{sanitize(match[2])}</span> " \
|
||||
"<span class='sa-type'>" \
|
||||
"#{sanitize_input(match[2])}</span> " \
|
||||
"#{link_to experiment.name,
|
||||
experiment_archive_project_path(experiment.project)} " \
|
||||
"#{I18n.t('atwho.res.archived')}"
|
||||
else
|
||||
"<span class='sa-type'>#{sanitize(match[2])}</span> " \
|
||||
"<span class='sa-type'>"\
|
||||
"#{sanitize_input(match[2])}</span> " \
|
||||
"#{link_to experiment.name,
|
||||
canvas_experiment_path(experiment)}"
|
||||
end
|
||||
|
@ -147,12 +149,14 @@ module ApplicationHelper
|
|||
my_module = MyModule.find_by_id(match[3].base62_decode)
|
||||
next unless my_module
|
||||
if my_module.archived?
|
||||
"<span class='sa-type'>#{sanitize(match[2])}</span> " \
|
||||
"<span class='sa-type'>" \
|
||||
"#{sanitize_input(match[2])}</span> " \
|
||||
"#{link_to my_module.name,
|
||||
module_archive_experiment_path(my_module.experiment)} " \
|
||||
"#{I18n.t('atwho.res.archived')}"
|
||||
else
|
||||
"<span class='sa-type'>#{sanitize(match[2])}</span> " \
|
||||
"<span class='sa-type'>" \
|
||||
"#{sanitize_input(match[2])}</span> " \
|
||||
"#{link_to my_module.name,
|
||||
protocols_my_module_path(my_module)}"
|
||||
end
|
||||
|
@ -213,19 +217,20 @@ module ApplicationHelper
|
|||
user_description += %(<p></p></div></div></div>)
|
||||
end
|
||||
|
||||
raw(image_tag(user_avatar_absolute_url(user, :icon_small),
|
||||
class: 'atwho-user-img-popover')) +
|
||||
raw('<a onClick="$(this).popover(\'show\')" ' \
|
||||
'class="atwho-user-popover" data-container="body" ' \
|
||||
'data-html="true" tabindex="0" data-trigger="focus" ' \
|
||||
'data-placement="top" data-toggle="popover" data-content="') +
|
||||
raw(user_description) + raw('" >') + user.full_name + raw('</a>')
|
||||
raw("<img src='#{user_avatar_absolute_url(user, :icon_small)}'" \
|
||||
"alt='avatar' class='atwho-user-img-popover'>") +
|
||||
raw('<a onClick="$(this).popover(\'show\')" ' \
|
||||
'class="atwho-user-popover" data-container="body" ' \
|
||||
'data-html="true" tabindex="0" data-trigger="focus" ' \
|
||||
'data-placement="top" data-toggle="popover" data-content="') +
|
||||
raw(user_description) + raw('" >') + user.full_name + raw('</a>')
|
||||
end
|
||||
|
||||
def user_avatar_absolute_url(user, style)
|
||||
unless user.avatar(style) == '/images/icon_small/missing.png'
|
||||
return user.avatar(style)
|
||||
end
|
||||
URI.join(root_url, "/images/#{style}/missing.png").to_s
|
||||
URI.join(Rails.application.routes.url_helpers.root_url,
|
||||
"/images/#{style}/missing.png").to_s
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
require 'sanitize'
|
||||
|
||||
module InputSanitizeHelper
|
||||
def sanitize_input(
|
||||
text,
|
||||
tags = [],
|
||||
attributes = []
|
||||
)
|
||||
ActionController::Base.helpers.sanitize(
|
||||
text,
|
||||
tags: Constants::WHITELISTED_TAGS + tags,
|
||||
attributes: Constants::WHITELISTED_ATTRIBUTES + attributes
|
||||
)
|
||||
# Rails default ActionController::Base.helpers.sanitize method call
|
||||
# the ActiveRecord connecton method on the caller object which in
|
||||
# our cases throws an error when called from not ActiveRecord objects
|
||||
# such as SamplesDatatables
|
||||
def sanitize_input(html, tags = [], attributes = [])
|
||||
Sanitize.fragment(
|
||||
html,
|
||||
elements: Constants::WHITELISTED_TAGS + tags,
|
||||
attributes: { all: Constants::WHITELISTED_ATTRIBUTES + attributes },
|
||||
css: Constants::WHITELISTED_CSS_ATTRIBUTES
|
||||
).html_safe
|
||||
end
|
||||
|
||||
def escape_input(text)
|
||||
|
@ -16,15 +19,13 @@ module InputSanitizeHelper
|
|||
end
|
||||
|
||||
def custom_auto_link(text, options = {})
|
||||
simple_format = options.fetch(:simple_format) { true }
|
||||
team = options.fetch(:team) { nil },
|
||||
simple_f = options.fetch(:simple_format) { true }
|
||||
team = options.fetch(:team) { nil }
|
||||
wrapper_tag = options.fetch(:wrapper_tag) { {} }
|
||||
tags = options.fetch(:tags) { [] }
|
||||
text = if simple_format
|
||||
simple_format(sanitize_input(text), {}, wrapper_tag)
|
||||
else
|
||||
sanitize_input(text, tags)
|
||||
end
|
||||
format_opt = wrapper_tag.merge(sanitize: false)
|
||||
text = sanitize_input(text, tags)
|
||||
text = simple_format(sanitize_input(text), {}, format_opt) if simple_f
|
||||
auto_link(
|
||||
smart_annotation_parser(text, team),
|
||||
link: :urls,
|
||||
|
|
|
@ -40,10 +40,8 @@ module NotificationsHelper
|
|||
|
||||
notification = Notification.create(
|
||||
type_of: :assignment,
|
||||
title:
|
||||
ActionController::Base.helpers.sanitize(title),
|
||||
message:
|
||||
ActionController::Base.helpers.sanitize(message)
|
||||
title: sanitize_input(title),
|
||||
message: sanitize_input(message)
|
||||
)
|
||||
|
||||
if target_user.assignments_notification
|
||||
|
|
|
@ -11,10 +11,11 @@ module SearchHelper
|
|||
if search_team != current_team
|
||||
link_to text,
|
||||
path,
|
||||
data: { confirm: t('users.settings.changed_team_in_search',
|
||||
data: { no_turbolink: true,
|
||||
confirm: t('users.settings.changed_team_in_search',
|
||||
team: search_team.name) }
|
||||
else
|
||||
link_to text, path
|
||||
link_to text, path, data: { no_turbolink: true }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -23,7 +23,9 @@ module TinyMceHelper
|
|||
match = el.match(regex)
|
||||
img = TinyMceAsset.find_by_id(match[1])
|
||||
next unless img
|
||||
image_tag img.url, data: { token: Base62.encode(img.id) }
|
||||
image_tag img.url,
|
||||
class: 'img-responsive',
|
||||
data: { token: Base62.encode(img.id) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class Activity < ActiveRecord::Base
|
||||
include InputSanitizeHelper
|
||||
|
||||
after_create :generate_notification
|
||||
|
||||
enum type_of: [
|
||||
|
@ -104,15 +106,12 @@ class Activity < ActiveRecord::Base
|
|||
|
||||
notification = Notification.create(
|
||||
type_of: notification_type,
|
||||
title:
|
||||
ActionController::Base.helpers.sanitize(message, tags: %w(strong a)),
|
||||
message:
|
||||
ActionController::Base
|
||||
.helpers.sanitize(
|
||||
"#{I18n.t('search.index.project')}
|
||||
#{project_m} #{experiment_m} #{task_m}",
|
||||
tags: %w(strong a)
|
||||
),
|
||||
title: sanitize_input(message, %w(strong a)),
|
||||
message: sanitize_input(
|
||||
"#{I18n.t('search.index.project')}
|
||||
#{project_m} #{experiment_m} #{task_m}",
|
||||
%w(strong a)
|
||||
),
|
||||
generator_user_id: user.id
|
||||
)
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ class Checklist < ActiveRecord::Base
|
|||
reject_if: :all_blank,
|
||||
allow_destroy: true
|
||||
|
||||
scope :asc, -> { order('checklists.created_at ASC') }
|
||||
|
||||
def self.search(user,
|
||||
include_archived,
|
||||
query = nil,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class Experiment < ActiveRecord::Base
|
||||
include ArchivableModel, SearchableModel
|
||||
include ArchivableModel
|
||||
include SearchableModel
|
||||
|
||||
belongs_to :project, inverse_of: :experiments
|
||||
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
|
||||
|
@ -354,7 +355,7 @@ class Experiment < ActiveRecord::Base
|
|||
i += 1 while experiment_names.include?(format(format, i, name))
|
||||
|
||||
clone = Experiment.new(
|
||||
name: format(format, i, name),
|
||||
name: format(format, i, name).truncate(Constants::NAME_MAX_LENGTH),
|
||||
description: description,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
|
@ -371,10 +372,6 @@ class Experiment < ActiveRecord::Base
|
|||
m.deep_clone_to_experiment(current_user, clone)
|
||||
end
|
||||
clone.save
|
||||
|
||||
# Create workflow image
|
||||
clone.delay.generate_workflow_img
|
||||
|
||||
clone
|
||||
end
|
||||
|
||||
|
|
|
@ -189,4 +189,12 @@ class Project < ActiveRecord::Base
|
|||
.distinct
|
||||
end
|
||||
end
|
||||
|
||||
def notifications_count(user)
|
||||
res = 0
|
||||
assigned_modules(user).find_each do |t|
|
||||
res += 1 if t.is_overdue? || t.is_one_day_prior?
|
||||
end
|
||||
res
|
||||
end
|
||||
end
|
||||
|
|
|
@ -268,7 +268,7 @@ class Protocol < ActiveRecord::Base
|
|||
step2.save
|
||||
|
||||
# Copy checklists
|
||||
step.checklists.each do |checklist|
|
||||
step.checklists.asc.each do |checklist|
|
||||
checklist2 = Checklist.new(
|
||||
name: checklist.name,
|
||||
step: step2
|
||||
|
@ -372,7 +372,7 @@ class Protocol < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def completed_steps
|
||||
steps.select(&:completed)
|
||||
steps.where(completed: true)
|
||||
end
|
||||
|
||||
def space_taken
|
||||
|
|
|
@ -3,4 +3,11 @@ class TempFile < ActiveRecord::Base
|
|||
|
||||
has_attached_file :file
|
||||
do_not_validate_attachment_file_type :file
|
||||
|
||||
def destroy_obsolete
|
||||
destroy! if self
|
||||
end
|
||||
|
||||
handle_asynchronously :destroy_obsolete,
|
||||
run_at: proc { 7.days.from_now }
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ class TinyMceAsset < ActiveRecord::Base
|
|||
# When using S3 file upload, we can limit file accessibility with url signing
|
||||
def presigned_url(style = :large,
|
||||
download: false,
|
||||
timeout: Constants::URL_SHORT_EXPIRE_TIME)
|
||||
timeout: Constants::URL_LONG_EXPIRE_TIME)
|
||||
if stored_on_s3?
|
||||
if download
|
||||
download_arg = 'attachment; filename=' + URI.escape(image_file_name)
|
||||
|
@ -46,7 +46,7 @@ class TinyMceAsset < ActiveRecord::Base
|
|||
image.options[:storage].to_sym == :s3
|
||||
end
|
||||
|
||||
def url(style = :large, timeout: Constants::URL_SHORT_EXPIRE_TIME)
|
||||
def url(style = :large, timeout: Constants::URL_LONG_EXPIRE_TIME)
|
||||
if image.is_stored_on_s3?
|
||||
presigned_url(style, timeout: timeout)
|
||||
else
|
||||
|
|
|
@ -44,14 +44,14 @@
|
|||
<ul class="nav nav-tabs nav-tabs-less" role="tablist">
|
||||
<% if can_view_module_info(my_module) %>
|
||||
<li role="presentation">
|
||||
<a class="btn btn-link" href="<%= my_module_url(id: my_module.id, format: :json) %>" aria-controls="<%= my_module.id %>_info" role="tab" data-remote="true">
|
||||
<a class="btn btn-link task-card-view-info" href="<%= my_module_url(id: my_module.id, format: :json) %>" aria-controls="<%= my_module.id %>_info" role="tab" data-remote="true">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if can_view_module_users(my_module) %>
|
||||
<li role="presentation">
|
||||
<a class="btn btn-link" href="<%= my_module_user_my_modules_url(my_module_id: my_module.id, format: :json) %>" aria-controls="<%= my_module.id %>_users" role="tab" data-remote="true">
|
||||
<a class="btn btn-link task-card-view-users" href="<%= my_module_user_my_modules_url(my_module_id: my_module.id, format: :json) %>" aria-controls="<%= my_module.id %>_users" role="tab" data-remote="true">
|
||||
<span class="glyphicon glyphicon-user" aria-hidden="true"></span>
|
||||
<span class="badge badge-indicator users-badge-indicator <%= 'hidden' unless my_module.users.count.positive? %>"
|
||||
data-linked-id="<%= my_module.id %>">
|
||||
|
@ -62,14 +62,14 @@
|
|||
<% end %>
|
||||
<% if can_view_module_activities(my_module) %>
|
||||
<li role="presentation">
|
||||
<a class="btn btn-link" href="<%= activities_tab_my_module_url(id: my_module.id, format: :json) %>" aria-controls="<%= my_module.id %>_activities" role="tab" data-remote="true">
|
||||
<a class="btn btn-link task-card-view-activities" href="<%= activities_tab_my_module_url(id: my_module.id, format: :json) %>" aria-controls="<%= my_module.id %>_activities" role="tab" data-remote="true">
|
||||
<span class="glyphicon glyphicon-equalizer" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if can_view_module_comments(my_module) %>
|
||||
<li role="presentation">
|
||||
<a class="btn btn-link" href="<%= my_module_my_module_comments_url(my_module_id: my_module.id, format: :json) %>" aria-controls="<%= my_module.id %>_comments" role="tab" data-remote="true">
|
||||
<a class="btn btn-link task-card-view-comments" href="<%= my_module_my_module_comments_url(my_module_id: my_module.id, format: :json) %>" aria-controls="<%= my_module.id %>_comments" role="tab" data-remote="true">
|
||||
<span class="glyphicon glyphicon-comment" aria-hidden="true"></span>
|
||||
<span class="badge badge-indicator comments-badge-indicator <%= 'hidden' unless my_module.task_comments.count.positive? %>"
|
||||
data-linked-id="<%= my_module.id %>">
|
||||
|
@ -80,7 +80,7 @@
|
|||
<% end %>
|
||||
<% if can_view_module_samples(my_module) %>
|
||||
<li role="presentation">
|
||||
<a class="btn btn-link" href="<%= my_module_sample_my_modules_url(my_module_id: my_module.id, format: :json) %>" aria-controls="<%= my_module.id %>_samples" role="tab" data-remote="true">
|
||||
<a class="btn btn-link task-card-view-samples" href="<%= my_module_sample_my_modules_url(my_module_id: my_module.id, format: :json) %>" aria-controls="<%= my_module.id %>_samples" role="tab" data-remote="true">
|
||||
<span class="glyphicon glyphicon-tint" aria-hidden="true"></span>
|
||||
<% if my_module.samples.count.positive? %>
|
||||
<span class="badge badge-indicator"><%= my_module.samples.count %></span>
|
||||
|
|
|
@ -75,6 +75,9 @@ data-project-users-tab-url="<%= url_for project_user_projects_path(project_id: p
|
|||
<li role="presentation">
|
||||
<a class="btn btn-link" href="<%= url_for notifications_project_path(id: project.id, format: :json) %>" aria-controls="notifications-<%= project.id %>" role="tab" data-remote="true">
|
||||
<span class="glyphicon glyphicon-bell"></span>
|
||||
<% if project.notifications_count(current_user).positive? %>
|
||||
<span class="badge badge-indicator"><%= project.notifications_count(current_user) %></span>
|
||||
<% end %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
|
|
|
@ -160,7 +160,7 @@
|
|||
|
||||
<% unless step.checklists.blank? then %>
|
||||
<div class="col-xs-12">
|
||||
<% step.checklists.each do |checklist| %>
|
||||
<% step.checklists.asc.each do |checklist| %>
|
||||
<strong><%= checklist.name %></strong>
|
||||
<% if checklist.checklist_items.empty? %>
|
||||
</br>
|
||||
|
|
|
@ -7,6 +7,44 @@
|
|||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
<span>
|
||||
<%= t "samples.modal_info.sample_type" %>
|
||||
<% if @sample.sample_type.present? %>
|
||||
<%= @sample.sample_type.name %>
|
||||
<% else %>
|
||||
<em><%= t "samples.modal_info.no_type" %></em>
|
||||
<% end %>
|
||||
</span>
|
||||
<br>
|
||||
<span>
|
||||
<%= t "samples.modal_info.sample_group" %>
|
||||
<span class="glyphicon glyphicon-asterisk" style="<%= "color: #{@sample.sample_group.color}" if @sample.sample_group.present? %>"></span>
|
||||
<% if @sample.sample_group.present? %>
|
||||
<%= @sample.sample_group.name %>
|
||||
<% else %>
|
||||
<em><%= t "samples.modal_info.no_group" %></em>
|
||||
<% end %>
|
||||
</span>
|
||||
<br>
|
||||
<span>
|
||||
<%= t "samples.modal_info.added_on" %>
|
||||
<%= l @sample.created_at, format: :full %>
|
||||
</span>
|
||||
<br>
|
||||
<span>
|
||||
<%= t "samples.modal_info.added_by" %>
|
||||
<%= @sample.user.full_name %>
|
||||
</span>
|
||||
<% @sample.sample_custom_fields.each do |sample_custom_field| %>
|
||||
<br>
|
||||
<span>
|
||||
<%= t "samples.modal_info.custom_field", cf: sample_custom_field.custom_field.name %>
|
||||
<%= sample_custom_field.value %>
|
||||
</span>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
<% if @sample.my_modules.count > 0 %>
|
||||
<div>
|
||||
<%= t("samples.modal_info.title", nr: @sample.my_modules.count) %>
|
||||
|
|
|
@ -134,7 +134,7 @@
|
|||
|
||||
<% unless step.checklists.blank? then %>
|
||||
<div class="col-xs-12">
|
||||
<% step.checklists.each do |checklist| %>
|
||||
<% step.checklists.asc.each do |checklist| %>
|
||||
<strong><%= custom_auto_link(checklist.name) %></strong>
|
||||
<% if checklist.checklist_items.empty? %>
|
||||
</br>
|
||||
|
|
|
@ -13,6 +13,10 @@ Rails.application.configure do
|
|||
config.consider_all_requests_local = true
|
||||
config.action_controller.perform_caching = false
|
||||
|
||||
Rails.application.routes.default_url_options = {
|
||||
host: Rails.application.secrets.mail_server_url
|
||||
}
|
||||
|
||||
# Don't care if the mailer can't send.
|
||||
config.action_mailer.default_url_options = {
|
||||
host: Rails.application.secrets.mail_server_url
|
||||
|
|
|
@ -14,6 +14,10 @@ Rails.application.configure do
|
|||
config.consider_all_requests_local = false
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
Rails.application.routes.default_url_options = {
|
||||
host: Rails.application.secrets.mail_server_url
|
||||
}
|
||||
|
||||
# Don't care if the mailer can't send.
|
||||
config.action_mailer.default_url_options = {
|
||||
host: Rails.application.secrets.mail_server_url
|
||||
|
|
|
@ -36,6 +36,10 @@ Rails.application.configure do
|
|||
# ActionMailer::Base.deliveries array.
|
||||
config.action_mailer.delivery_method = :test
|
||||
|
||||
Rails.application.routes.default_url_options = {
|
||||
host: Rails.application.secrets.mail_server_url
|
||||
}
|
||||
|
||||
# Don't care if the mailer can't send.
|
||||
config.action_mailer.default_url_options = { host: Rails.application.secrets.mail_server_url }
|
||||
config.action_mailer.default_options = { from: Rails.application.secrets.mail_from }
|
||||
|
|
|
@ -222,8 +222,643 @@ class Constants
|
|||
|
||||
WHITELISTED_ATTRIBUTES = %w(
|
||||
href src width height alt cite datetime title class name xml:lang abbr style
|
||||
target data-*
|
||||
).freeze
|
||||
|
||||
WHITELISTED_CSS_ATTRIBUTES = {
|
||||
allow_comments: false,
|
||||
allow_hacks: false,
|
||||
at_rules_with_properties: %w[
|
||||
bottom-center bottom-left bottom-left-corner bottom-right
|
||||
bottom-right-corner font-face left-bottom left-middle left-top page
|
||||
right-bottom right-middle right-top top-center top-left
|
||||
top-left-corner top-right top-right-corner
|
||||
],
|
||||
at_rules_with_styles: %w[
|
||||
-moz-keyframes -o-keyframes -webkit-keyframes document
|
||||
keyframes media supports
|
||||
],
|
||||
protocols: ['http', 'https', :relative],
|
||||
properties: %w[
|
||||
-moz-appearance
|
||||
-moz-background-inline-policy
|
||||
-moz-box-sizing
|
||||
-moz-column-count
|
||||
-moz-column-fill
|
||||
-moz-column-gap
|
||||
-moz-column-rule
|
||||
-moz-column-rule-color
|
||||
-moz-column-rule-style
|
||||
-moz-column-rule-width
|
||||
-moz-column-width
|
||||
-moz-font-feature-settings
|
||||
-moz-font-language-override
|
||||
-moz-hyphens
|
||||
-moz-text-align-last
|
||||
-moz-text-decoration-color
|
||||
-moz-text-decoration-line
|
||||
-moz-text-decoration-style
|
||||
-moz-text-size-adjust
|
||||
-ms-background-position-x
|
||||
-ms-background-position-y
|
||||
-ms-block-progression
|
||||
-ms-content-zoom-chaining
|
||||
-ms-content-zoom-limit
|
||||
-ms-content-zoom-limit-max
|
||||
-ms-content-zoom-limit-min
|
||||
-ms-content-zoom-snap
|
||||
-ms-content-zoom-snap-points
|
||||
-ms-content-zoom-snap-type
|
||||
-ms-content-zooming
|
||||
-ms-filter
|
||||
-ms-flex
|
||||
-ms-flex-align
|
||||
-ms-flex-direction
|
||||
-ms-flex-order
|
||||
-ms-flex-pack
|
||||
-ms-flex-wrap
|
||||
-ms-flow-from
|
||||
-ms-flow-into
|
||||
-ms-grid-column
|
||||
-ms-grid-column-align
|
||||
-ms-grid-column-span
|
||||
-ms-grid-columns
|
||||
-ms-grid-row
|
||||
-ms-grid-row-align
|
||||
-ms-grid-row-span
|
||||
-ms-grid-rows
|
||||
-ms-high-contrast-adjust
|
||||
-ms-hyphenate-limit-chars
|
||||
-ms-hyphenate-limit-lines
|
||||
-ms-hyphenate-limit-zone
|
||||
-ms-hyphens
|
||||
-ms-ime-mode
|
||||
-ms-interpolation-mode
|
||||
-ms-layout-flow
|
||||
-ms-layout-grid
|
||||
-ms-layout-grid-char
|
||||
-ms-layout-grid-line
|
||||
-ms-layout-grid-mode
|
||||
-ms-layout-grid-type
|
||||
-ms-overflow-style
|
||||
-ms-overflow-x
|
||||
-ms-overflow-y
|
||||
-ms-progress-appearance
|
||||
-ms-scroll-chaining
|
||||
-ms-scroll-limit
|
||||
-ms-scroll-limit-x-max
|
||||
-ms-scroll-limit-x-min
|
||||
-ms-scroll-limit-y-max
|
||||
-ms-scroll-limit-y-min
|
||||
-ms-scroll-rails
|
||||
-ms-scroll-snap-points-x
|
||||
-ms-scroll-snap-points-y
|
||||
-ms-scroll-snap-type
|
||||
-ms-scroll-snap-x
|
||||
-ms-scroll-snap-y
|
||||
-ms-scroll-translation
|
||||
-ms-scrollbar-arrow-color
|
||||
-ms-scrollbar-base-color
|
||||
-ms-scrollbar-darkshadow-color
|
||||
-ms-scrollbar-face-color
|
||||
-ms-scrollbar-highlight-color
|
||||
-ms-scrollbar-shadow-color
|
||||
-ms-scrollbar-track-color
|
||||
-ms-text-align-last
|
||||
-ms-text-autospace
|
||||
-ms-text-justify
|
||||
-ms-text-kashida-space
|
||||
-ms-text-overflow
|
||||
-ms-text-size-adjust
|
||||
-ms-text-underline-position
|
||||
-ms-touch-action
|
||||
-ms-user-select
|
||||
-ms-word-break
|
||||
-ms-word-wrap
|
||||
-ms-wrap-flow
|
||||
-ms-wrap-margin
|
||||
-ms-wrap-through
|
||||
-ms-writing-mode
|
||||
-ms-zoom
|
||||
-webkit-align-content
|
||||
-webkit-align-items
|
||||
-webkit-align-self
|
||||
-webkit-animation
|
||||
-webkit-animation-delay
|
||||
-webkit-animation-direction
|
||||
-webkit-animation-duration
|
||||
-webkit-animation-fill-mode
|
||||
-webkit-animation-iteration-count
|
||||
-webkit-animation-name
|
||||
-webkit-animation-play-state
|
||||
-webkit-animation-timing-function
|
||||
-webkit-appearance
|
||||
-webkit-backface-visibility
|
||||
-webkit-background-blend-mode
|
||||
-webkit-background-clip
|
||||
-webkit-background-composite
|
||||
-webkit-background-origin
|
||||
-webkit-background-size
|
||||
-webkit-blend-mode
|
||||
-webkit-border-after
|
||||
-webkit-border-after-color
|
||||
-webkit-border-after-style
|
||||
-webkit-border-after-width
|
||||
-webkit-border-before
|
||||
-webkit-border-before-color
|
||||
-webkit-border-before-style
|
||||
-webkit-border-before-width
|
||||
-webkit-border-bottom-left-radius
|
||||
-webkit-border-bottom-right-radius
|
||||
-webkit-border-end
|
||||
-webkit-border-end-color
|
||||
-webkit-border-end-style
|
||||
-webkit-border-end-width
|
||||
-webkit-border-fit
|
||||
-webkit-border-image
|
||||
-webkit-border-radius
|
||||
-webkit-border-start
|
||||
-webkit-border-start-color
|
||||
-webkit-border-start-style
|
||||
-webkit-border-start-width
|
||||
-webkit-border-top-left-radius
|
||||
-webkit-border-top-right-radius
|
||||
-webkit-box-align
|
||||
-webkit-box-decoration-break
|
||||
-webkit-box-flex
|
||||
-webkit-box-flex-group
|
||||
-webkit-box-lines
|
||||
-webkit-box-ordinal-group
|
||||
-webkit-box-orient
|
||||
-webkit-box-pack
|
||||
-webkit-box-reflect
|
||||
-webkit-box-shadow
|
||||
-webkit-box-sizing
|
||||
-webkit-clip-path
|
||||
-webkit-column-axis
|
||||
-webkit-column-break-after
|
||||
-webkit-column-break-before
|
||||
-webkit-column-break-inside
|
||||
-webkit-column-count
|
||||
-webkit-column-gap
|
||||
-webkit-column-progression
|
||||
-webkit-column-rule
|
||||
-webkit-column-rule-color
|
||||
-webkit-column-rule-style
|
||||
-webkit-column-rule-width
|
||||
-webkit-column-span
|
||||
-webkit-column-width
|
||||
-webkit-columns
|
||||
-webkit-filter
|
||||
-webkit-flex
|
||||
-webkit-flex-basis
|
||||
-webkit-flex-direction
|
||||
-webkit-flex-flow
|
||||
-webkit-flex-grow
|
||||
-webkit-flex-shrink
|
||||
-webkit-flex-wrap
|
||||
-webkit-flow-from
|
||||
-webkit-flow-into
|
||||
-webkit-font-size-delta
|
||||
-webkit-font-smoothing
|
||||
-webkit-grid-area
|
||||
-webkit-grid-auto-columns
|
||||
-webkit-grid-auto-flow
|
||||
-webkit-grid-auto-rows
|
||||
-webkit-grid-column
|
||||
-webkit-grid-column-end
|
||||
-webkit-grid-column-start
|
||||
-webkit-grid-definition-columns
|
||||
-webkit-grid-definition-rows
|
||||
-webkit-grid-row
|
||||
-webkit-grid-row-end
|
||||
-webkit-grid-row-start
|
||||
-webkit-justify-content
|
||||
-webkit-line-clamp
|
||||
-webkit-logical-height
|
||||
-webkit-logical-width
|
||||
-webkit-margin-after
|
||||
-webkit-margin-after-collapse
|
||||
-webkit-margin-before
|
||||
-webkit-margin-before-collapse
|
||||
-webkit-margin-bottom-collapse
|
||||
-webkit-margin-collapse
|
||||
-webkit-margin-end
|
||||
-webkit-margin-start
|
||||
-webkit-margin-top-collapse
|
||||
-webkit-marquee
|
||||
-webkit-marquee-direction
|
||||
-webkit-marquee-increment
|
||||
-webkit-marquee-repetition
|
||||
-webkit-marquee-speed
|
||||
-webkit-marquee-style
|
||||
-webkit-mask
|
||||
-webkit-mask-box-image
|
||||
-webkit-mask-box-image-outset
|
||||
-webkit-mask-box-image-repeat
|
||||
-webkit-mask-box-image-slice
|
||||
-webkit-mask-box-image-source
|
||||
-webkit-mask-box-image-width
|
||||
-webkit-mask-clip
|
||||
-webkit-mask-composite
|
||||
-webkit-mask-image
|
||||
-webkit-mask-origin
|
||||
-webkit-mask-position
|
||||
-webkit-mask-position-x
|
||||
-webkit-mask-position-y
|
||||
-webkit-mask-repeat
|
||||
-webkit-mask-repeat-x
|
||||
-webkit-mask-repeat-y
|
||||
-webkit-mask-size
|
||||
-webkit-mask-source-type
|
||||
-webkit-max-logical-height
|
||||
-webkit-max-logical-width
|
||||
-webkit-min-logical-height
|
||||
-webkit-min-logical-width
|
||||
-webkit-opacity
|
||||
-webkit-order
|
||||
-webkit-padding-after
|
||||
-webkit-padding-before
|
||||
-webkit-padding-end
|
||||
-webkit-padding-start
|
||||
-webkit-perspective
|
||||
-webkit-perspective-origin
|
||||
-webkit-perspective-origin-x
|
||||
-webkit-perspective-origin-y
|
||||
-webkit-region-break-after
|
||||
-webkit-region-break-before
|
||||
-webkit-region-break-inside
|
||||
-webkit-region-fragment
|
||||
-webkit-shape-inside
|
||||
-webkit-shape-margin
|
||||
-webkit-shape-outside
|
||||
-webkit-shape-padding
|
||||
-webkit-svg-shadow
|
||||
-webkit-tap-highlight-color
|
||||
-webkit-text-decoration
|
||||
-webkit-text-decoration-color
|
||||
-webkit-text-decoration-line
|
||||
-webkit-text-decoration-style
|
||||
-webkit-text-size-adjust
|
||||
-webkit-touch-callout
|
||||
-webkit-transform
|
||||
-webkit-transform-origin
|
||||
-webkit-transform-origin-x
|
||||
-webkit-transform-origin-y
|
||||
-webkit-transform-origin-z
|
||||
-webkit-transform-style
|
||||
-webkit-transition
|
||||
-webkit-transition-delay
|
||||
-webkit-transition-duration
|
||||
-webkit-transition-property
|
||||
-webkit-transition-timing-function
|
||||
-webkit-user-drag
|
||||
-webkit-wrap-flow
|
||||
-webkit-wrap-through
|
||||
align-content
|
||||
align-items
|
||||
align-self
|
||||
alignment-adjust
|
||||
alignment-baseline
|
||||
all
|
||||
anchor-point
|
||||
animation
|
||||
animation-delay
|
||||
animation-direction
|
||||
animation-duration
|
||||
animation-fill-mode
|
||||
animation-iteration-count
|
||||
animation-name
|
||||
animation-play-state
|
||||
animation-timing-function
|
||||
azimuth
|
||||
backface-visibility
|
||||
background
|
||||
background-attachment
|
||||
background-clip
|
||||
background-color
|
||||
background-image
|
||||
background-origin
|
||||
background-position
|
||||
background-repeat
|
||||
background-size
|
||||
baseline-shift
|
||||
binding
|
||||
bleed
|
||||
bookmark-label
|
||||
bookmark-level
|
||||
bookmark-state
|
||||
border
|
||||
border-bottom
|
||||
border-bottom-color
|
||||
border-bottom-left-radius
|
||||
border-bottom-right-radius
|
||||
border-bottom-style
|
||||
border-bottom-width
|
||||
border-collapse
|
||||
border-color
|
||||
border-image
|
||||
border-image-outset
|
||||
border-image-repeat
|
||||
border-image-slice
|
||||
border-image-source
|
||||
border-image-width
|
||||
border-left
|
||||
border-left-color
|
||||
border-left-style
|
||||
border-left-width
|
||||
border-radius
|
||||
border-right
|
||||
border-right-color
|
||||
border-right-style
|
||||
border-right-width
|
||||
border-spacing
|
||||
border-style
|
||||
border-top
|
||||
border-top-color
|
||||
border-top-left-radius
|
||||
border-top-right-radius
|
||||
border-top-style
|
||||
border-top-width
|
||||
border-width
|
||||
bottom
|
||||
box-decoration-break
|
||||
box-shadow
|
||||
box-sizing
|
||||
box-snap
|
||||
box-suppress
|
||||
break-after
|
||||
break-before
|
||||
break-inside
|
||||
caption-side
|
||||
chains
|
||||
clear
|
||||
clip
|
||||
clip-path
|
||||
clip-rule
|
||||
color
|
||||
color-interpolation-filters
|
||||
column-count
|
||||
column-fill
|
||||
column-gap
|
||||
column-rule
|
||||
column-rule-color
|
||||
column-rule-style
|
||||
column-rule-width
|
||||
column-span
|
||||
column-width
|
||||
columns
|
||||
contain
|
||||
content
|
||||
counter-increment
|
||||
counter-reset
|
||||
counter-set
|
||||
crop
|
||||
cue
|
||||
cue-after
|
||||
cue-before
|
||||
cursor
|
||||
direction
|
||||
display
|
||||
display-inside
|
||||
display-list
|
||||
display-outside
|
||||
dominant-baseline
|
||||
elevation
|
||||
empty-cells
|
||||
filter
|
||||
flex
|
||||
flex-basis
|
||||
flex-direction
|
||||
flex-flow
|
||||
flex-grow
|
||||
flex-shrink
|
||||
flex-wrap
|
||||
float
|
||||
float-offset
|
||||
flood-color
|
||||
flood-opacity
|
||||
flow-from
|
||||
flow-into
|
||||
font
|
||||
font-family
|
||||
font-feature-settings
|
||||
font-kerning
|
||||
font-language-override
|
||||
font-size
|
||||
font-size-adjust
|
||||
font-stretch
|
||||
font-style
|
||||
font-synthesis
|
||||
font-variant
|
||||
font-variant-alternates
|
||||
font-variant-caps
|
||||
font-variant-east-asian
|
||||
font-variant-ligatures
|
||||
font-variant-numeric
|
||||
font-variant-position
|
||||
font-weight
|
||||
grid
|
||||
grid-area
|
||||
grid-auto-columns
|
||||
grid-auto-flow
|
||||
grid-auto-rows
|
||||
grid-column
|
||||
grid-column-end
|
||||
grid-column-start
|
||||
grid-row
|
||||
grid-row-end
|
||||
grid-row-start
|
||||
grid-template
|
||||
grid-template-areas
|
||||
grid-template-columns
|
||||
grid-template-rows
|
||||
hanging-punctuation
|
||||
height
|
||||
hyphens
|
||||
icon
|
||||
image-orientation
|
||||
image-rendering
|
||||
image-resolution
|
||||
ime-mode
|
||||
initial-letters
|
||||
inline-box-align
|
||||
justify-content
|
||||
justify-items
|
||||
justify-self
|
||||
left
|
||||
letter-spacing
|
||||
lighting-color
|
||||
line-box-contain
|
||||
line-break
|
||||
line-grid
|
||||
line-height
|
||||
line-snap
|
||||
line-stacking
|
||||
line-stacking-ruby
|
||||
line-stacking-shift
|
||||
line-stacking-strategy
|
||||
list-style
|
||||
list-style-image
|
||||
list-style-position
|
||||
list-style-type
|
||||
margin
|
||||
margin-bottom
|
||||
margin-left
|
||||
margin-right
|
||||
margin-top
|
||||
marker-offset
|
||||
marker-side
|
||||
marks
|
||||
mask
|
||||
mask-box
|
||||
mask-box-outset
|
||||
mask-box-repeat
|
||||
mask-box-slice
|
||||
mask-box-source
|
||||
mask-box-width
|
||||
mask-clip
|
||||
mask-image
|
||||
mask-origin
|
||||
mask-position
|
||||
mask-repeat
|
||||
mask-size
|
||||
mask-source-type
|
||||
mask-type
|
||||
max-height
|
||||
max-lines
|
||||
max-width
|
||||
min-height
|
||||
min-width
|
||||
move-to
|
||||
nav-down
|
||||
nav-index
|
||||
nav-left
|
||||
nav-right
|
||||
nav-up
|
||||
object-fit
|
||||
object-position
|
||||
opacity
|
||||
order
|
||||
orphans
|
||||
outline
|
||||
outline-color
|
||||
outline-offset
|
||||
outline-style
|
||||
outline-width
|
||||
overflow
|
||||
overflow-wrap
|
||||
overflow-x
|
||||
overflow-y
|
||||
padding
|
||||
padding-bottom
|
||||
padding-left
|
||||
padding-right
|
||||
padding-top
|
||||
page
|
||||
page-break-after
|
||||
page-break-before
|
||||
page-break-inside
|
||||
page-policy
|
||||
pause
|
||||
pause-after
|
||||
pause-before
|
||||
perspective
|
||||
perspective-origin
|
||||
pitch
|
||||
pitch-range
|
||||
play-during
|
||||
position
|
||||
presentation-level
|
||||
quotes
|
||||
region-fragment
|
||||
resize
|
||||
rest
|
||||
rest-after
|
||||
rest-before
|
||||
richness
|
||||
right
|
||||
rotation
|
||||
rotation-point
|
||||
ruby-align
|
||||
ruby-merge
|
||||
ruby-position
|
||||
shape-image-threshold
|
||||
shape-margin
|
||||
shape-outside
|
||||
size
|
||||
speak
|
||||
speak-as
|
||||
speak-header
|
||||
speak-numeral
|
||||
speak-punctuation
|
||||
speech-rate
|
||||
stress
|
||||
string-set
|
||||
tab-size
|
||||
table-layout
|
||||
text-align
|
||||
text-align-last
|
||||
text-combine-horizontal
|
||||
text-combine-upright
|
||||
text-decoration
|
||||
text-decoration-color
|
||||
text-decoration-line
|
||||
text-decoration-skip
|
||||
text-decoration-style
|
||||
text-emphasis
|
||||
text-emphasis-color
|
||||
text-emphasis-position
|
||||
text-emphasis-style
|
||||
text-height
|
||||
text-indent
|
||||
text-justify
|
||||
text-orientation
|
||||
text-overflow
|
||||
text-rendering
|
||||
text-shadow
|
||||
text-size-adjust
|
||||
text-space-collapse
|
||||
text-transform
|
||||
text-underline-position
|
||||
text-wrap
|
||||
top
|
||||
touch-action
|
||||
transform
|
||||
transform-origin
|
||||
transform-style
|
||||
transition
|
||||
transition-delay
|
||||
transition-duration
|
||||
transition-property
|
||||
transition-timing-function
|
||||
unicode-bidi
|
||||
unicode-range
|
||||
vertical-align
|
||||
visibility
|
||||
voice-balance
|
||||
voice-duration
|
||||
voice-family
|
||||
voice-pitch
|
||||
voice-range
|
||||
voice-rate
|
||||
voice-stress
|
||||
voice-volume
|
||||
volume
|
||||
white-space
|
||||
widows
|
||||
width
|
||||
will-change
|
||||
word-break
|
||||
word-spacing
|
||||
word-wrap
|
||||
wrap-flow
|
||||
wrap-through
|
||||
writing-mode
|
||||
z-index
|
||||
]
|
||||
}.freeze
|
||||
|
||||
EXPORTABLE_ZIP_EXPIRATION_DAYS = 7
|
||||
|
||||
# Very basic regex to check for validity of emails
|
||||
|
|
|
@ -52,7 +52,7 @@ module ReportExtends
|
|||
true,
|
||||
[:step],
|
||||
proc do |my_module|
|
||||
my_module.protocol.completed_steps
|
||||
my_module.protocol.completed_steps.order(:position)
|
||||
end),
|
||||
ModuleElement.new(:result_assets,
|
||||
true,
|
||||
|
|
|
@ -850,9 +850,16 @@ en:
|
|||
add_new_sample_group: "Add sample group"
|
||||
add_new_column: "Add column"
|
||||
modal_info:
|
||||
added_on: "Added on"
|
||||
added_by: "Added by"
|
||||
custom_field: "%{cf}: "
|
||||
head_title: "Information for sample '%{sample}'"
|
||||
title: "This sample is assigned to %{nr} tasks."
|
||||
no_tasks: "This sample in not assigned to any task."
|
||||
no_group: "No sample group"
|
||||
no_type: "No sample type"
|
||||
sample_group: "Sample group:"
|
||||
sample_type: "Sample type:"
|
||||
modal_import:
|
||||
title: "Import samples"
|
||||
notice: "You may upload .csv file (comma separated) or tab separated file (.txt or .tdv) or Excel file (.xls, .xlsx). First row should include header names, followed by rows with sample data."
|
||||
|
@ -1013,8 +1020,8 @@ en:
|
|||
add_comment_to_step: "<i>%{user}</i> commented on Step %{step} <strong>%{step_name}</strong>."
|
||||
complete_step: "<i>%{user}</i> completed Step %{step} <strong>%{step_name}</strong> (%{completed}/%{all} completed)."
|
||||
uncomplete_step: "<i>%{user}</i> uncompleted Step %{step} <strong>%{step_name}</strong> (%{completed}/%{all} completed)."
|
||||
check_step_checklist_item: "<i>%{user}</i> completed task <strong>%{checkbox}</strong> (%{completed}/%{all} completed) in Step %{step} <strong>%{step_name}</strong>."
|
||||
uncheck_step_checklist_item: "<i>%{user}</i> uncompleted task <strong>%{checkbox}</strong> (%{completed}/%{all} completed) in Step %{step} <strong>%{step_name}</strong>."
|
||||
check_step_checklist_item: "<i>%{user}</i> completed checklist item <strong>%{checkbox}</strong> (%{completed}/%{all} completed) in Step %{step} <strong>%{step_name}</strong>."
|
||||
uncheck_step_checklist_item: "<i>%{user}</i> uncompleted checklist item <strong>%{checkbox}</strong> (%{completed}/%{all} completed) in Step %{step} <strong>%{step_name}</strong>."
|
||||
edit_step: "<i>%{user}</i> edited Step %{step} <strong>%{step_name}</strong>."
|
||||
add_asset_result: "<i>%{user}</i> added file result <strong>%{result}</strong>."
|
||||
add_text_result: "<i>%{user}</i> added text result <strong>%{result}</strong>."
|
||||
|
|
Loading…
Add table
Reference in a new issue