Merge branch 'develop' into features/native-tables-revamp

This commit is contained in:
Martin Artnik 2025-02-13 14:33:19 +01:00
commit ace4139a84
35 changed files with 286 additions and 109 deletions

View file

@ -1 +1 @@
1.39.0 1.39.1.1

View file

@ -81,16 +81,19 @@
dropdownSelector.init(myModuleUserSelector, { dropdownSelector.init(myModuleUserSelector, {
closeOnSelect: true, closeOnSelect: true,
labelHTML: true, labelHTML: true,
tagClass: 'my-module-user-tags',
tagLabel: (data) => { tagLabel: (data) => {
return `<img class="img-responsive block-inline" src="${data.params.avatar_url}" alt="${data.label}"/> return `<div class="flex items-center gap-2">
<span class="user-full-name block-inline">${data.label}</span>`; <img class="img-responsive block-inline" src="${data.params.avatar_url}" alt="${data.label}"/>
<span class="user-full-name block-inline">${data.label}</span>
</div>`;
}, },
optionLabel: (data) => { optionLabel: (data) => {
if (data.params.avatar_url) { if (data.params.avatar_url) {
return `<span class="global-avatar-container" style="margin-top: 10px"> return `<div class="flex items-center gap-2">
<span class="global-avatar-container">
<img src="${data.params.avatar_url}" alt="${data.label}"/></span> <img src="${data.params.avatar_url}" alt="${data.label}"/></span>
<span style="margin-left: 10px">${data.label}</span>`; <span style="margin-left: 10px">${data.label}</span>
</div>`;
} }
return data.label; return data.label;

View file

@ -747,7 +747,7 @@ var MyModuleRepositories = (function() {
function openUpdateRecordsModal(downstream) { function openUpdateRecordsModal(downstream) {
var updateUrl = FULL_VIEW_MODAL.data('update-url-modal'); var updateUrl = FULL_VIEW_MODAL.data('update-url-modal');
$.get(updateUrl, { selected_rows: SELECTED_ROWS, downstream: downstream }, function(data) { $.post(updateUrl, { selected_rows: SELECTED_ROWS, downstream: downstream }, function(data) {
var assignList; var assignList;
var assignListScrollbar; var assignListScrollbar;
var unassignList; var unassignList;
@ -766,7 +766,7 @@ var MyModuleRepositories = (function() {
function openAssignRecordsModal(downstream) { function openAssignRecordsModal(downstream) {
var assignUrl = FULL_VIEW_MODAL.data('assign-url-modal'); var assignUrl = FULL_VIEW_MODAL.data('assign-url-modal');
$.get(assignUrl, { selected_rows: Object.keys(SELECTED_ROWS), downstream: downstream }, function(data) { $.post(assignUrl, { selected_rows: Object.keys(SELECTED_ROWS), downstream: downstream }, function(data) {
UPDATE_REPOSITORY_MODAL.find('.modal-content').html(data.html); UPDATE_REPOSITORY_MODAL.find('.modal-content').html(data.html);
UPDATE_REPOSITORY_MODAL.data('update-url', data.update_url); UPDATE_REPOSITORY_MODAL.data('update-url', data.update_url);
UPDATE_REPOSITORY_MODAL.modal('show'); UPDATE_REPOSITORY_MODAL.modal('show');

View file

@ -1,24 +0,0 @@
const GLOBAL_CONSTANTS = {
NAME_TRUNCATION_LENGTH: <%= Constants::NAME_TRUNCATION_LENGTH %>,
NAME_MAX_LENGTH: <%= Constants::NAME_MAX_LENGTH %>,
NAME_MIN_LENGTH: <%= Constants::NAME_MIN_LENGTH %>,
TEXT_MAX_LENGTH: <%= Constants::TEXT_MAX_LENGTH %>,
TABLE_CARD_MIN_WIDTH: 340, <%# pixels %>
TABLE_CARD_GAP: 16, <%# pixels %>
FILENAME_TRUNCATION_LENGTH: <%= Constants::FILENAME_TRUNCATION_LENGTH %>,
FILE_MAX_SIZE_MB: parseInt($('meta[name="max-file-size"]').attr('content'), 10),
IS_SAFARI: /^((?!chrome|android).)*safari/i.test(navigator.userAgent),
REPOSITORY_LIST_ITEMS_PER_COLUMN: <%= Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN %>,
REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN: <%= Constants::REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN %>,
REPOSITORY_STOCK_UNIT_ITEMS_PER_COLUMN: <%= Constants::REPOSITORY_STOCK_UNIT_ITEMS_PER_COLUMN %>,
HAS_UNSAVED_DATA_CLASS_NAME: 'has-unsaved-data',
DEFAULT_ELEMENTS_PER_PAGE: <%= Constants::DEFAULT_ELEMENTS_PER_PAGE %>,
FILENAME_MAX_LENGTH: <%= Constants::FILENAME_MAX_LENGTH %>,
FAST_STATUS_POLLING_INTERVAL: <%= Constants::FAST_STATUS_POLLING_INTERVAL %>,
SLOW_STATUS_POLLING_INTERVAL: <%= Constants::SLOW_STATUS_POLLING_INTERVAL %>,
ASSET_POLLING_INTERVAL: <%= Constants::ASSET_POLLING_INTERVAL %>,
ASSET_SYNC_URL: '<%= Constants::ASSET_SYNC_URL %>',
GLOBAL_SEARCH_PREVIEW_LIMIT: <%= Constants::GLOBAL_SEARCH_PREVIEW_LIMIT %>,
SEARCH_LIMIT: <%= Constants::SEARCH_LIMIT %>,
SCINOTE_EDIT_RESTRICTED_EXTENSIONS: <%= Constants::SCINOTE_EDIT_RESTRICTED_EXTENSIONS %>
};

View file

@ -182,7 +182,7 @@ var zebraPrint = (function() {
repository_id: int repository_id: int
} }
*/ */
print: function(modalUrl, progressModal, printModal, printData) { print: function(modalUrl, progressModal, printModal, printData, finishedCallback = null) {
var modal = $(progressModal); var modal = $(progressModal);
$.ajax({ $.ajax({
method: 'GET', method: 'GET',
@ -224,7 +224,7 @@ var zebraPrint = (function() {
} }
}).fail(() => { }).fail(() => {
HelperModule.flashAlertMsg(I18n.t('repository_row.modal_print_label.general_error'), 'danger'); HelperModule.flashAlertMsg(I18n.t('repository_row.modal_print_label.general_error'), 'danger');
}); }).always(() => { if (finishedCallback) finishedCallback(); });
} }
}; };
}()); }());

View file

@ -36,3 +36,9 @@ table.dataTable.table--resizable-columns {
} }
} }
} }
div.dataTables_wrapper div.dataTables_processing {
// This overrides default library behaviour, where the 'Processing' element
// can go off-screen on large tables due to top: 50% positioning.
top: 150px !important;
}

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
class GlobalConstantsController < ApplicationController
before_action :load_global_constants
skip_before_action :authenticate_user!, only: :index
skip_before_action :verify_authenticity_token, only: :index
def index; end
private
def load_global_constants
@global_constants = {
NAME_TRUNCATION_LENGTH: Constants::NAME_TRUNCATION_LENGTH,
NAME_MAX_LENGTH: Constants::NAME_MAX_LENGTH,
NAME_MIN_LENGTH: Constants::NAME_MIN_LENGTH,
TEXT_MAX_LENGTH: Constants::TEXT_MAX_LENGTH,
TABLE_CARD_MIN_WIDTH: 340,
TABLE_CARD_GAP: 16,
FILENAME_TRUNCATION_LENGTH: Constants::FILENAME_TRUNCATION_LENGTH,
FILE_MAX_SIZE_MB: Rails.configuration.x.file_max_size_mb,
REPOSITORY_LIST_ITEMS_PER_COLUMN: Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN,
REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN: Constants::REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN,
REPOSITORY_STOCK_UNIT_ITEMS_PER_COLUMN: Constants::REPOSITORY_STOCK_UNIT_ITEMS_PER_COLUMN,
HAS_UNSAVED_DATA_CLASS_NAME: 'has-unsaved-data',
DEFAULT_ELEMENTS_PER_PAGE: Constants::DEFAULT_ELEMENTS_PER_PAGE,
FILENAME_MAX_LENGTH: Constants::FILENAME_MAX_LENGTH,
FAST_STATUS_POLLING_INTERVAL: Constants::FAST_STATUS_POLLING_INTERVAL,
SLOW_STATUS_POLLING_INTERVAL: Constants::SLOW_STATUS_POLLING_INTERVAL,
ASSET_POLLING_INTERVAL: Constants::ASSET_POLLING_INTERVAL,
ASSET_SYNC_URL: Constants::ASSET_SYNC_URL,
GLOBAL_SEARCH_PREVIEW_LIMIT: Constants::GLOBAL_SEARCH_PREVIEW_LIMIT,
SEARCH_LIMIT: Constants::SEARCH_LIMIT,
SCINOTE_EDIT_RESTRICTED_EXTENSIONS: Constants::SCINOTE_EDIT_RESTRICTED_EXTENSIONS,
SCINOTE_EDIT_LATEST_JSON_URL: Constants::SCINOTE_EDIT_LATEST_JSON_URL
}
end
end

View file

@ -9,15 +9,16 @@ class RepositoriesController < ApplicationController
include RepositoriesDatatableHelper include RepositoriesDatatableHelper
include MyModulesHelper include MyModulesHelper
before_action :switch_team_with_param, only: %i(index)
before_action :load_repository, except: %i(index create create_modal sidebar archive restore actions_toolbar before_action :load_repository, except: %i(index create create_modal sidebar archive restore actions_toolbar
export_modal export_repositories list) export_repositories list)
before_action :load_repositories, only: %i(index list) before_action :load_repositories, only: %i(index list)
before_action :load_repositories_for_archiving, only: :archive before_action :load_repositories_for_archiving, only: :archive
before_action :load_repositories_for_restoring, only: :restore before_action :load_repositories_for_restoring, only: :restore
before_action :check_view_all_permissions, only: %i(index sidebar list) before_action :check_view_all_permissions, only: %i(index sidebar list)
before_action :check_view_permissions, except: %i(index create_modal create update destroy parse_sheet before_action :check_view_permissions, except: %i(index create_modal create update destroy parse_sheet
import_records sidebar archive restore actions_toolbar import_records sidebar archive restore actions_toolbar
export_modal export_repositories list) export_repositories list)
before_action :check_manage_permissions, only: %i(rename_modal update) before_action :check_manage_permissions, only: %i(rename_modal update)
before_action :check_delete_permissions, only: %i(destroy destroy_modal) before_action :check_delete_permissions, only: %i(destroy destroy_modal)
before_action :check_archive_permissions, only: %i(archive restore) before_action :check_archive_permissions, only: %i(archive restore)
@ -83,7 +84,6 @@ class RepositoriesController < ApplicationController
def show def show
respond_to do |format| respond_to do |format|
format.html do format.html do
current_team_switch(@repository.team) unless @repository.shared_with?(current_team)
@display_edit_button = can_create_repository_rows?(@repository) @display_edit_button = can_create_repository_rows?(@repository)
@display_delete_button = can_delete_repository_rows?(@repository) @display_delete_button = can_delete_repository_rows?(@repository)
@display_duplicate_button = can_create_repository_rows?(@repository) @display_duplicate_button = can_create_repository_rows?(@repository)
@ -470,7 +470,7 @@ class RepositoriesController < ApplicationController
def load_repository def load_repository
repository_id = params[:id] || params[:repository_id] repository_id = params[:id] || params[:repository_id]
@repository = Repository.viewable_by_user(current_user).find_by(id: repository_id) @repository = Repository.viewable_by_user(current_user, current_user.teams).find_by(id: repository_id)
render_404 unless @repository render_404 unless @repository
end end
@ -515,6 +515,7 @@ class RepositoriesController < ApplicationController
end end
def check_view_permissions def check_view_permissions
current_team_switch(@repository.team) unless @repository.shared_with?(current_team)
render_403 unless can_read_repository?(@repository) render_403 unless can_read_repository?(@repository)
end end

View file

@ -3,6 +3,7 @@
class ResultsController < ApplicationController class ResultsController < ApplicationController
include Breadcrumbs include Breadcrumbs
include TeamsHelper include TeamsHelper
before_action :load_my_module before_action :load_my_module
before_action :load_vars, only: %i(destroy elements assets upload_attachment archive restore destroy before_action :load_vars, only: %i(destroy elements assets upload_attachment archive restore destroy
update_view_state update_asset_view_mode update duplicate) update_view_state update_asset_view_mode update duplicate)

View file

@ -114,7 +114,7 @@ class SearchController < ApplicationController
return return
when 'repository_rows' when 'repository_rows'
@model = RepositoryRow @model = RepositoryRow
search_by_name(RepositoryRow) search_by_name
render json: @records, render json: @records,
each_serializer: GlobalSearch::RepositoryRowSerializer, each_serializer: GlobalSearch::RepositoryRowSerializer,

View file

@ -11,7 +11,7 @@
<div v-if="!loading && actions.length === 0" class="sn-action-toolbar__message"> <div v-if="!loading && actions.length === 0" class="sn-action-toolbar__message">
{{ i18n.t('action_toolbar.no_actions') }} {{ i18n.t('action_toolbar.no_actions') }}
</div> </div>
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0"> <div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0" :class="{ 'disable-click': disabledActions[action.name] }">
<div v-if="action.type === 'group' && Array.isArray(action.actions) && action.actions.length > 1" class="export-actions-dropdown sci-dropdown dropup"> <div v-if="action.type === 'group' && Array.isArray(action.actions) && action.actions.length > 1" class="export-actions-dropdown sci-dropdown dropup">
<button class="btn btn-primary dropdown-toggle single-object-action rounded" type="button" id="exportDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" data-e2e="e2e-DD-actionToolbar-export"> <button class="btn btn-primary dropdown-toggle single-object-action rounded" type="button" id="exportDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" data-e2e="e2e-DD-actionToolbar-export">
<i class="sn-icon sn-icon-export"></i> <i class="sn-icon sn-icon-export"></i>
@ -75,6 +75,7 @@
<script> <script>
import { debounce } from '../shared/debounce.js'; import { debounce } from '../shared/debounce.js';
import axios from '../../packs/custom_axios.js';
export default { export default {
name: 'ActionToolbar', name: 'ActionToolbar',
@ -95,7 +96,8 @@ export default {
bottomOffset: 0, bottomOffset: 0,
leftOffset: 0, leftOffset: 0,
buttonOverflow: false, buttonOverflow: false,
submitting: false submitting: false,
disabledActions: {}
}; };
}, },
created() { created() {
@ -105,8 +107,8 @@ export default {
this.debouncedFetchActions = debounce((params) => { this.debouncedFetchActions = debounce((params) => {
this.params = params; this.params = params;
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => { axios.post(this.actionsUrl, this.params).then((response) => {
this.actions = data.actions; this.actions = response.data.actions;
this.loading = false; this.loading = false;
this.setButtonOverflow(); this.setButtonOverflow();
if (this.actionsLoadedCallback) this.$nextTick(this.actionsLoadedCallback); if (this.actionsLoadedCallback) this.$nextTick(this.actionsLoadedCallback);
@ -168,6 +170,12 @@ export default {
this.actionsLoadedCallback = func; this.actionsLoadedCallback = func;
}, },
doAction(action, event) { doAction(action, event) {
this.disabledActions[action.name] = true;
setTimeout(() => {
delete this.disabledActions[action.name];
}, 1000); // enable action after one second, to prevent multi-clicks
switch (action.type) { switch (action.type) {
case 'legacy': case 'legacy':
// do nothing, this is handled by legacy code based on the button class // do nothing, this is handled by legacy code based on the button class

View file

@ -367,6 +367,7 @@ export default {
deleteSteps() { deleteSteps() {
$.post(this.urls.delete_steps_url, () => { $.post(this.urls.delete_steps_url, () => {
this.steps = []; this.steps = [];
this.refreshProtocolDropdownOptions();
}).fail(() => { }).fail(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger'); HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
}); });

View file

@ -238,6 +238,9 @@ export default {
label_template_id: this.selectedTemplate.id, label_template_id: this.selectedTemplate.id,
row_ids: this.row_ids, row_ids: this.row_ids,
repository_id: this.repository_id repository_id: this.repository_id
},
() => {
this.submitting = false;
} }
); );
} else { } else {

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="p-4 w-full rounded bg-sn-light-grey min-h-[68px]" :class="{ 'disable-click': submitting }" data-e2e="e2e-CO-actionToolbar"> <div class="p-4 w-full rounded bg-sn-light-grey min-h-[68px]" data-e2e="e2e-CO-actionToolbar">
<div class="flex gap-4 items-center h-full"> <div class="flex gap-4 items-center h-full">
<div v-if="loading && !actions.length" class="sn-action-toolbar__action"> <div v-if="loading && !actions.length" class="sn-action-toolbar__action">
<a class="rounded flex items-center py-1.5 px-2.5 bg-transparent text-transparent no-underline"></a> <a class="rounded flex items-center py-1.5 px-2.5 bg-transparent text-transparent no-underline"></a>
@ -7,7 +7,7 @@
<div v-if="!loading && actions.length === 0" class="text-sn-grey-grey"> <div v-if="!loading && actions.length === 0" class="text-sn-grey-grey">
{{ i18n.t('action_toolbar.no_actions') }} {{ i18n.t('action_toolbar.no_actions') }}
</div> </div>
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0"> <div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0" :class="{ 'disable-click': disabledActions[action.name] }">
<a :class="`rounded flex gap-2 items-center py-1.5 px-1.5 xl:px-2.5 hover:text-sn-white hover:bg-sn-blue <a :class="`rounded flex gap-2 items-center py-1.5 px-1.5 xl:px-2.5 hover:text-sn-white hover:bg-sn-blue
bg-sn-white color-sn-blue hover:no-underline focus:no-underline ${action.button_class}`" bg-sn-white color-sn-blue hover:no-underline focus:no-underline ${action.button_class}`"
:href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'" :href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'"
@ -32,7 +32,7 @@ export default {
name: 'ActionToolbar', name: 'ActionToolbar',
props: { props: {
actionsUrl: { type: String, required: true }, actionsUrl: { type: String, required: true },
actionsMethod: { type: String, default: 'get' }, actionsMethod: { type: String, default: 'post' },
params: { type: Object }, params: { type: Object },
}, },
data() { data() {
@ -42,7 +42,7 @@ export default {
reloadCallback: null, reloadCallback: null,
loaded: false, loaded: false,
loading: true, loading: true,
submitting: false disabledActions: {}
}; };
}, },
watch: { watch: {
@ -70,10 +70,15 @@ export default {
}); });
}, },
doAction(action, event) { doAction(action, event) {
this.disabledActions[action.name] = true;
setTimeout(() => {
delete this.disabledActions[action.name];
}, 1000); // enable action after one second, to prevent multi-clicks
switch (action.type) { switch (action.type) {
case 'emit': case 'emit':
event.preventDefault(); event.preventDefault();
this.submitting = true;
this.$emit('toolbar:action', action); this.$emit('toolbar:action', action);
break; break;
case 'modal': case 'modal':

View file

@ -50,7 +50,7 @@ export default {
methods: { methods: {
loadActions() { loadActions() {
if (this.actionsMenu.length > 0) return; if (this.actionsMenu.length > 0) return;
axios.get(this.params.data.urls.actions) axios.post(this.params.data.urls.actions)
.then((response) => { .then((response) => {
this.actionsMenu = response.data.actions; this.actionsMenu = response.data.actions;
}); });

View file

@ -114,7 +114,7 @@ export default {
}, },
methods: { methods: {
fetchData() { fetchData() {
$.get('https://extras.scinote.net/scinote-edit/latest.json', (result) => { $.get(GLOBAL_CONSTANTS.SCINOTE_EDIT_LATEST_JSON_URL, (result) => {
this.responseData = result; this.responseData = result;
}); });
} }

View file

@ -3,6 +3,8 @@
require 'caxlsx' require 'caxlsx'
class FormResponsesZipExportJob < ZipExportJob class FormResponsesZipExportJob < ZipExportJob
include StringUtility
private private
# Override # Override
@ -11,7 +13,7 @@ class FormResponsesZipExportJob < ZipExportJob
exported_data = to_xlsx(form) exported_data = to_xlsx(form)
File.binwrite("#{dir}/#{form.name}.xlsx", exported_data) File.binwrite("#{dir}/#{to_filesystem_name(form.name)}.xlsx", exported_data)
end end
def failed_notification_title def failed_notification_title

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class RepositoryItemDateReminderJob < ApplicationJob class RepositoryItemDateReminderJob < ApplicationJob
BUFFER_DAYS = 2
queue_as :default queue_as :default
def perform def perform
@ -11,20 +13,24 @@ class RepositoryItemDateReminderJob < ApplicationJob
private private
def process_repository_values(model, comparison_value) def repository_values_due(model, comparison_value)
model model
.joins(repository_cell: [:repository_row, { repository_column: :repository }]) .joins(repository_cell: [:repository_row, { repository_column: :repository }])
.where( .where(
notification_sent: false, notification_sent: false,
repositories: { type: 'Repository', archived: false }, repositories: { type: 'Repository', archived: false },
repository_rows: { archived: false } repository_rows: { archived: false }
).where('repository_date_time_values.updated_at >= ?', 2.days.ago) ).where( # date(time) values that are within the reminder range including buffer
.where( # date(time) values that are within the reminder range "(data > (:comparison_value::timestamp - (INTERVAL ':buffer_days DAY'))) AND data <= " \
"data <= " \ "(:comparison_value::timestamp + CAST(((repository_columns.metadata->>'reminder_unit')::int * " \
"(?::timestamp + CAST(((repository_columns.metadata->>'reminder_unit')::int * " \
"(repository_columns.metadata->>'reminder_value')::int) || ' seconds' AS Interval))", "(repository_columns.metadata->>'reminder_value')::int) || ' seconds' AS Interval))",
comparison_value buffer_days: BUFFER_DAYS,
).find_each do |value| comparison_value: comparison_value
)
end
def process_repository_values(model, comparison_value)
repository_values_due(model, comparison_value).find_each do |value|
repository_row = RepositoryRow.find(value.repository_cell.repository_row_id) repository_row = RepositoryRow.find(value.repository_cell.repository_row_id)
repository_column = RepositoryColumn.find(value.repository_cell.repository_column_id) repository_column = RepositoryColumn.find(value.repository_cell.repository_column_id)

View file

@ -299,13 +299,13 @@ class Asset < ApplicationRecord
# Extract only the licenced user flag parameter # Extract only the licenced user flag parameter
is_licenced_user = ENV['WOPI_BUSINESS_USERS'] == 'true' && action_url.include?('IsLicensedUser=BUSINESS_USER') is_licenced_user = ENV['WOPI_BUSINESS_USERS'] == 'true' && action_url.include?('IsLicensedUser=BUSINESS_USER')
action_url = action_url.split('?').first + "?IsLicencedUser=#{is_licenced_user ? 1 : 0}&" action_url = action_url.split(/<.*>/).first + "IsLicencedUser=#{is_licenced_user ? 1 : 0}"
rest_url = Rails.application.routes.url_helpers.wopi_rest_endpoint_url( rest_url = Rails.application.routes.url_helpers.wopi_rest_endpoint_url(
host: ENV['WOPI_ENDPOINT_URL'], host: ENV['WOPI_ENDPOINT_URL'],
id: id id: id
) )
action_url += "WOPISrc=#{rest_url}" action_url += "&WOPISrc=#{rest_url}"
if with_tokens if with_tokens
token = user.get_wopi_token token = user.get_wopi_token
action_url + "&access_token=#{token.token}"\ action_url + "&access_token=#{token.token}"\

View file

@ -137,11 +137,7 @@ class RepositoryRow < ApplicationRecord
teams = options[:teams] || current_team || user.teams.select(:id) teams = options[:teams] || current_team || user.teams.select(:id)
searchable_row_fields = [RepositoryRow::PREFIXED_ID_SQL, 'repository_rows.name', 'users.full_name'] searchable_row_fields = [RepositoryRow::PREFIXED_ID_SQL, 'repository_rows.name', 'users.full_name']
readable_rows = readable_rows = distinct.joins(:repository, :created_by).viewable_by_user(user, teams)
distinct
.joins(:repository, :created_by)
.where(repositories: { id: Repository.with_granted_permissions(user, RepositoryPermissions::READ).select(:id),
team_id: teams })
readable_rows = readable_rows.active unless include_archived readable_rows = readable_rows.active unless include_archived
repository_rows = readable_rows.where_attributes_like_boolean(searchable_row_fields, query, options) repository_rows = readable_rows.where_attributes_like_boolean(searchable_row_fields, query, options)

View file

@ -273,8 +273,8 @@ class TeamImporter
updated = true updated = true
end end
end end
text.scan(/\[@[\w+-@?! ]+~\w+\]/).each do |user_match| text.scan(SmartAnnotations::TagToText::USER_REGEX).each do |user_match|
orig_id_encoded = user_match.match(/\[@[\w+-@?! ]+~(\w+)\]/)[1] orig_id_encoded = user_match[1]
orig_id = orig_id_encoded.base62_decode orig_id = orig_id_encoded.base62_decode
next unless @user_mappings[orig_id] next unless @user_mappings[orig_id]

View file

@ -1,6 +1,7 @@
<html> <html>
<head> <head>
<%= csp_meta_tag %> <%= csp_meta_tag %>
<%= csrf_meta_tags %>
<%= javascript_include_tag 'i18n_bundle' %> <%= javascript_include_tag 'i18n_bundle' %>
<%= javascript_include_tag 'jquery_bundle' %> <%= javascript_include_tag 'jquery_bundle' %>
<%= stylesheet_link_tag 'sn_icon_font' %> <%= stylesheet_link_tag 'sn_icon_font' %>

View file

@ -0,0 +1 @@
const GLOBAL_CONSTANTS = <%= @global_constants.to_json.html_safe %>;

View file

@ -5,7 +5,6 @@
<meta data-hook="head-js"> <meta data-hook="head-js">
<title><%=t "head.title", title: (yield :head_title) %></title> <title><%=t "head.title", title: (yield :head_title) %></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="max-file-size" content="<%= Rails.configuration.x.file_max_size_mb %>">
<meta name="tiny-mce-assets-url" content="<%= tiny_mce_assets_path %>"> <meta name="tiny-mce-assets-url" content="<%= tiny_mce_assets_path %>">
<% if user_signed_in? %> <% if user_signed_in? %>
<meta name="expiration-url" content="<%= users_expire_in_path %>"> <meta name="expiration-url" content="<%= users_expire_in_path %>">
@ -18,6 +17,8 @@
<% if ::NewRelic::Agent.instance.started? %> <% if ::NewRelic::Agent.instance.started? %>
<%= ::NewRelic::Agent.browser_timing_header(controller.request.content_security_policy_nonce) %> <%= ::NewRelic::Agent.browser_timing_header(controller.request.content_security_policy_nonce) %>
<% end %> <% end %>
<script src="<%= global_constants_path(format: :js) %>"></script>
<%= javascript_include_tag 'jquery_bundle' %> <%= javascript_include_tag 'jquery_bundle' %>
<%= javascript_include_tag 'application_pack' %> <%= javascript_include_tag 'application_pack' %>
@ -79,7 +80,6 @@
data-datetime-picker-format-vue="<%= datetime_picker_format_date_only_vue %>" data-datetime-picker-format-vue="<%= datetime_picker_format_date_only_vue %>"
<% end %> <% end %>
> >
<span style="display: none;" data-hook="body-js"></span> <span style="display: none;" data-hook="body-js"></span>
<span style="display: none;" data-hook="application-body-html"></span> <span style="display: none;" data-hook="application-body-html"></span>
@ -140,6 +140,5 @@
<%= javascript_include_tag 'prism' %> <%= javascript_include_tag 'prism' %>
<%= javascript_include_tag "vue_components_repository_item_sidebar" %> <%= javascript_include_tag "vue_components_repository_item_sidebar" %>
</body> </body>
</html> </html>

View file

@ -5,6 +5,8 @@
<meta data-hook="head-js"> <meta data-hook="head-js">
<title><%=t "head.title", title: (yield :head_title) %></title> <title><%=t "head.title", title: (yield :head_title) %></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<script src="<%= global_constants_path(format: :js) %>"></script>
<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %> <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag 'bootstrap_pack', media: 'all' %> <%= stylesheet_link_tag 'bootstrap_pack', media: 'all' %>
<%= stylesheet_link_tag 'application', media: 'all' %> <%= stylesheet_link_tag 'application', media: 'all' %>

View file

@ -10,6 +10,8 @@
<style media="all"> <style media="all">
html, body { height: 100%; min-height: 100%; } html, body { height: 100%; min-height: 100%; }
</style> </style>
<script src="<%= global_constants_path(format: :js) %>"></script>
<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %> <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag 'bootstrap_pack', media: 'all' %> <%= stylesheet_link_tag 'bootstrap_pack', media: 'all' %>
<%= stylesheet_link_tag 'application_pack_styles', media: 'all' %> <%= stylesheet_link_tag 'application_pack_styles', media: 'all' %>

View file

@ -458,6 +458,8 @@ class Constants
JSE LNK MSC MSI MSP MST PAF PIF PS1 REG RGS SCR SCT SHB SHS U3P VB VBE VBS VBSCRIPT WS WSF WSH JSE LNK MSC MSI MSP MST PAF PIF PS1 REG RGS SCR SCT SHB SHS U3P VB VBE VBS VBSCRIPT WS WSF WSH
).freeze ).freeze
SCINOTE_EDIT_LATEST_JSON_URL = ENV['SCINOTE_EDIT_LATEST_JSON_URL'].freeze
# quick search # quick search
QUICK_SEARCH_LIMIT = 5 QUICK_SEARCH_LIMIT = 5
QUICK_SEARCH_SEARCHABLE_OBJECTS = %w(project experiment my_module protocol repository_row QUICK_SEARCH_SEARCHABLE_OBJECTS = %w(project experiment my_module protocol repository_row

View file

@ -4196,8 +4196,8 @@ en:
body: "This file type appears to be executable and can potentially harm your computer. It cannot be opened with SciNote Edit. To open the file please download it manually." body: "This file type appears to be executable and can potentially harm your computer. It cannot be opened with SciNote Edit. To open the file please download it manually."
action: "I understand" action: "I understand"
update_version_modal: update_version_modal:
title: "Update required" title: "Incompatible SciNote Edit version"
body_text_html: "The current version of the SciNote Edit application is no longer supported. To ensure a seamless and secure user experience, we recommend updating to the latest version." body_text_html: "Your current version of the SciNote Edit application is no longer supported. To ensure a seamless and secure experience, download the appropriate version by clicking the Download button below — it will automatically provide the correct version for you."
edit_launching_application_modal: edit_launching_application_modal:
title: "Launching application" title: "Launching application"
description: "%{file_name} will now open in %{application}. Saved changes in %{application} will automatically be synced in SciNote." description: "%{file_name} will now open in %{application}. Saved changes in %{application} will automatically be synced in SciNote."

View file

@ -7,6 +7,8 @@ Rails.application.routes.draw do
get 'api/health', to: 'api/api#health', as: 'api_health' get 'api/health', to: 'api/api#health', as: 'api_health'
get 'api/status', to: 'api/api#status', as: 'api_status' get 'api/status', to: 'api/api#status', as: 'api_status'
get '/global_constants', to: 'global_constants#index', as: 'global_constants'
post 'access_tokens/revoke', to: 'doorkeeper/access_tokens#revoke' post 'access_tokens/revoke', to: 'doorkeeper/access_tokens#revoke'
# Addons # Addons
@ -64,7 +66,7 @@ Rails.application.routes.draw do
get :template_tags get :template_tags
get :zpl_preview get :zpl_preview
post :sync_fluics_templates post :sync_fluics_templates
get :actions_toolbar post :actions_toolbar
end end
end end
@ -208,7 +210,7 @@ Rails.application.routes.draw do
defaults: { format: 'json' } defaults: { format: 'json' }
get 'create_modal', to: 'repositories#create_modal', get 'create_modal', to: 'repositories#create_modal',
defaults: { format: 'json' } defaults: { format: 'json' }
get 'actions_toolbar' post 'actions_toolbar'
get :list get :list
get :rows_list get :rows_list
end end
@ -285,7 +287,7 @@ Rails.application.routes.draw do
end end
collection do collection do
get :project_contents get :project_contents
get 'actions_toolbar' post 'actions_toolbar'
end end
end end
get 'reports/datatable', to: 'reports#datatable' get 'reports/datatable', to: 'reports#datatable'
@ -393,7 +395,7 @@ Rails.application.routes.draw do
get 'users_filter' get 'users_filter'
post 'archive_group' post 'archive_group'
post 'restore_group' post 'restore_group'
get 'actions_toolbar' post 'actions_toolbar'
get :user_roles get :user_roles
end end
end end
@ -418,7 +420,7 @@ Rails.application.routes.draw do
get 'inventory_assigning_experiment_filter' get 'inventory_assigning_experiment_filter'
get 'clone_modal', action: :clone_modal get 'clone_modal', action: :clone_modal
get 'move_modal', action: :move_modal get 'move_modal', action: :move_modal
get 'actions_toolbar' post 'actions_toolbar'
get 'move_modal' # return modal with move options get 'move_modal' # return modal with move options
post 'move' # move experiment post 'move' # move experiment
end end
@ -464,7 +466,7 @@ Rails.application.routes.draw do
post 'save_table_state', on: :collection, defaults: { format: 'json' } post 'save_table_state', on: :collection, defaults: { format: 'json' }
collection do collection do
get 'actions_toolbar' post 'actions_toolbar'
get 'inventory_assigning_my_module_filter' get 'inventory_assigning_my_module_filter'
end end
@ -503,8 +505,8 @@ Rails.application.routes.draw do
get :full_view_table get :full_view_table
post :index_dt, defaults: { format: 'json' } post :index_dt, defaults: { format: 'json' }
post :export_repository post :export_repository
get :assign_repository_records_modal, as: :assign_modal post :assign_repository_records_modal, as: :assign_modal
get :update_repository_records_modal, as: :update_modal post :update_repository_records_modal, as: :update_modal
get :consume_modal get :consume_modal
post :update_consumption post :update_consumption
end end
@ -709,7 +711,7 @@ Rails.application.routes.draw do
post 'protocolsio_import_save', to: 'protocols#protocolsio_import_save' post 'protocolsio_import_save', to: 'protocols#protocolsio_import_save'
get 'export', to: 'protocols#export' get 'export', to: 'protocols#export'
get 'protocolsio', to: 'protocols#protocolsio_index' get 'protocolsio', to: 'protocols#protocolsio_index'
get 'actions_toolbar', to: 'protocols#actions_toolbar' post 'actions_toolbar', to: 'protocols#actions_toolbar'
get 'user_roles', to: 'protocols#user_roles' get 'user_roles', to: 'protocols#user_roles'
end end
end end
@ -775,7 +777,7 @@ Rails.application.routes.draw do
end end
collection do collection do
get :actions_toolbar post :actions_toolbar
end end
resources :repository_row_connections, only: %i(create destroy) do resources :repository_row_connections, only: %i(create destroy) do
@ -845,7 +847,7 @@ Rails.application.routes.draw do
resources :storage_locations, only: %i(index create destroy update show) do resources :storage_locations, only: %i(index create destroy update show) do
collection do collection do
get :actions_toolbar post :actions_toolbar
get :tree get :tree
end end
member do member do
@ -875,7 +877,7 @@ Rails.application.routes.draw do
end end
collection do collection do
get :actions_toolbar post :actions_toolbar
post :archive post :archive
post :restore post :restore
get :user_roles get :user_roles

View file

@ -19,6 +19,10 @@ describe ReportsController, type: :controller do
describe 'POST create' do describe 'POST create' do
context 'in JSON format' do context 'in JSON format' do
before do
allow(Reports::PdfJob).to receive(:perform_later)
end
let(:action) { post :create, params: params, format: :json } let(:action) { post :create, params: params, format: :json }
let(:params) do let(:params) do
{ project_id: project.id, { project_id: project.id,
@ -42,11 +46,14 @@ describe ReportsController, type: :controller do
.to(change { Activity.count }) .to(change { Activity.count })
end end
end end
# Temporary disabled due to webpack problems end
end if false
describe 'PUT update' do describe 'PUT update' do
context 'in JSON format' do context 'in JSON format' do
before do
allow(Reports::PdfJob).to receive(:perform_later)
end
let(:action) { put :update, params: params, format: :json } let(:action) { put :update, params: params, format: :json }
let(:params) do let(:params) do
{ project_id: project.id, { project_id: project.id,
@ -69,8 +76,7 @@ describe ReportsController, type: :controller do
.to(change { Activity.count }) .to(change { Activity.count })
end end
end end
# Temporary disabled due to webpack problems end
end if false
describe 'DELETE destroy' do describe 'DELETE destroy' do
let(:action) { delete :destroy, params: params } let(:action) { delete :destroy, params: params }

View file

@ -45,11 +45,11 @@ describe RepositoryRowsController, type: :controller do
end end
it 'successful response' do it 'successful response' do
allow_any_instance_of(ActionView::Helpers::AssetUrlHelper).to receive(:asset_path)
get :show, format: :json, params: { repository_id: repository.id, id: repository_row.id } get :show, format: :json, params: { repository_id: repository.id, id: repository_row.id }
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
end end
# Temporary disabled due to webpack problems end
end if false
context '#index' do context '#index' do
before do before do

View file

@ -9,6 +9,18 @@ describe TeamsController, type: :controller do
let(:team) { create :team, created_by: user } let(:team) { create :team, created_by: user }
describe 'POST export_projects' do describe 'POST export_projects' do
before do
view_response = '<div class="content-pane" id="report-new"><div class="report-container"><div id="report-content"></div></div></div>'
proxy_manager = Warden::Manager.new({})
proxy = Warden::Proxy.new({}, proxy_manager)
renderer_double = double('Renderer', render: view_response.dup)
allow(Warden::Manager).to receive(:new).and_return(proxy_manager)
allow(Warden::Proxy).to receive(:new).with({}, proxy_manager).and_return(proxy)
allow(ApplicationController.renderer).to receive(:new).with({ warden: proxy }).and_return(renderer_double)
allow(ApplicationController).to receive(:render)
end
let!(:first_project) { create :project, team: team, created_by: user } let!(:first_project) { create :project, team: team, created_by: user }
let!(:second_project) { create :project, team: team, created_by: user } let!(:second_project) { create :project, team: team, created_by: user }
let(:params) do let(:params) do
@ -31,6 +43,5 @@ describe TeamsController, type: :controller do
expect { action } expect { action }
.to(change { Activity.count }) .to(change { Activity.count })
end end
# Temporary disabled due to webpack problems end
end if false
end end

View file

@ -0,0 +1,87 @@
# frozen_string_literal: true
require 'rails_helper'
describe RepositoryItemDateReminderJob, type: :job do
around do |example|
Timecop.freeze(Time.utc(2025, 1, 15, 0, 0, 0)) do
example.run
end
end
let(:user) { create :user }
let(:team) { create :team, created_by: user }
let!(:owner_role) { UserRole.find_by(name: I18n.t('user_roles.predefined.owner')) }
let!(:team_assignment) { create_user_assignment(team, owner_role, user) }
let(:repository) { create :repository, team: team, created_by: user }
let!(:date_column_with_reminder) do
create :repository_column, repository: repository,
created_by: user,
name: 'Custom items',
data_type: 'RepositoryDateValue',
metadata: {"reminder_unit"=>"86400", "reminder_value"=>"5", "reminder_message"=>""}
end
let!(:repository_date_value_due) do
row = create :repository_row, name: "row 1",
repository: repository,
created_by: user,
last_modified_by: user
create(
:repository_date_value,
data: Date.parse('20-1-2025'),
repository_cell_attributes: { repository_row: row, repository_column: date_column_with_reminder }
)
end
let!(:repository_date_value_due_outside_buffer) do
row = create :repository_row, name: "row 1",
repository: repository,
created_by: user,
last_modified_by: user
create(
:repository_date_value,
data: Date.parse('15-1-2025') - (RepositoryItemDateReminderJob::BUFFER_DAYS + 1).days,
repository_cell_attributes: { repository_row: row, repository_column: date_column_with_reminder }
)
end
let!(:repository_date_value_due_inside_buffer) do
row = create :repository_row, name: "row 1",
repository: repository,
created_by: user,
last_modified_by: user
create(
:repository_date_value,
data: Date.parse('15-1-2025') - (RepositoryItemDateReminderJob::BUFFER_DAYS - 1).days,
repository_cell_attributes: { repository_row: row, repository_column: date_column_with_reminder }
)
end
let!(:repository_date_value_not_due) do
row = create :repository_row, name: "row 1",
repository: repository,
created_by: user,
last_modified_by: user
create(
:repository_date_value,
data: Date.parse('10-10-2025'),
repository_cell_attributes: { repository_row: row, repository_column: date_column_with_reminder }
)
end
describe '#repository_values_due' do
it "returns repository values that are due with a #{RepositoryItemDateReminderJob::BUFFER_DAYS} day buffer" do
values = described_class.new.send(:repository_values_due, RepositoryDateValue, Date.current)
expect(values).to include(repository_date_value_due)
expect(values).to_not include(repository_date_value_not_due)
expect(values).to include(repository_date_value_due_inside_buffer)
expect(values).to_not include(repository_date_value_due_outside_buffer)
end
end
end

View file

@ -228,11 +228,8 @@
<glyph unicode="&#xe9c7;" glyph-name="text-fields" data-tags="text-fields" d="M336.406 144.414v554.668h-213.333v52.523h479.188v-52.523h-213.332v-554.668h-52.523zM720.405 144.414v341.333h-128v52.524h308.523v-52.524h-128v-341.333h-52.523z" /> <glyph unicode="&#xe9c7;" glyph-name="text-fields" data-tags="text-fields" d="M336.406 144.414v554.668h-213.333v52.523h479.188v-52.523h-213.332v-554.668h-52.523zM720.405 144.414v341.333h-128v52.524h308.523v-52.524h-128v-341.333h-52.523z" />
<glyph unicode="&#xe9c8;" glyph-name="merge-cells" data-tags="merge-cells" d="M149.333 85.333v213.333h42.667v-170.667h170.667v-42.667h-213.333zM661.333 85.333v42.667h170.667v170.667h42.667v-213.333h-213.333zM306.133 315.157l-30.443 29.376 82.133 82.133h-251.157v42.667h251.157l-82.133 82.133 30.443 29.376 132.843-132.843-132.843-132.843zM717.867 315.157l-132.843 132.843 132.843 132.843 30.443-29.376-82.133-82.133h251.157v-42.667h-251.157l82.133-82.133-30.443-29.376zM149.333 597.333v213.333h213.333v-42.667h-170.667v-170.667h-42.667zM832 597.333v170.667h-170.667v42.667h213.333v-213.333h-42.667z" /> <glyph unicode="&#xe9c8;" glyph-name="merge-cells" data-tags="merge-cells" d="M149.333 85.333v213.333h42.667v-170.667h170.667v-42.667h-213.333zM661.333 85.333v42.667h170.667v170.667h42.667v-213.333h-213.333zM306.133 315.157l-30.443 29.376 82.133 82.133h-251.157v42.667h251.157l-82.133 82.133 30.443 29.376 132.843-132.843-132.843-132.843zM717.867 315.157l-132.843 132.843 132.843 132.843 30.443-29.376-82.133-82.133h251.157v-42.667h-251.157l82.133-82.133-30.443-29.376zM149.333 597.333v213.333h213.333v-42.667h-170.667v-170.667h-42.667zM832 597.333v170.667h-170.667v42.667h213.333v-213.333h-42.667z" />
<glyph unicode="&#xe9c9;" glyph-name="text-Italic" data-tags="text-Italic" d="M246.976 181.333v47.595h155.072l148.514 438.144h-155.074v47.595h354.464v-47.595h-150.153l-148.514-438.144h150.157v-47.595h-354.466z" /> <glyph unicode="&#xe9c9;" glyph-name="text-Italic" data-tags="text-Italic" d="M246.976 181.333v47.595h155.072l148.514 438.144h-155.074v47.595h354.464v-47.595h-150.153l-148.514-438.144h150.157v-47.595h-354.466z" />
<<<<<<< HEAD
=======
<glyph unicode="&#xe9ca;" glyph-name="forms" data-tags="forms" d="M320 256h256v42.667h-256v-42.667zM320 426.667h384v42.667h-384v-42.667zM320 597.333h384v42.667h-384v-42.667zM239.595 106.667c-19.641 0-36.039 6.579-49.195 19.733s-19.733 29.555-19.733 49.195v544.811c0 19.641 6.578 36.039 19.733 49.195s29.554 19.733 49.195 19.733h544.811c19.639 0 36.041-6.578 49.195-19.733s19.733-29.554 19.733-49.195v-544.811c0-19.639-6.579-36.041-19.733-49.195s-29.555-19.733-49.195-19.733h-544.811zM239.595 149.333h544.811c6.571 0 12.591 2.735 18.057 8.205 5.47 5.466 8.205 11.486 8.205 18.057v544.811c0 6.571-2.735 12.59-8.205 18.059-5.466 5.469-11.486 8.203-18.057 8.203h-544.811c-6.571 0-12.59-2.734-18.059-8.203s-8.203-11.488-8.203-18.059v-544.811c0-6.571 2.734-12.591 8.203-18.057 5.469-5.47 11.488-8.205 18.059-8.205z" /> <glyph unicode="&#xe9ca;" glyph-name="forms" data-tags="forms" d="M320 256h256v42.667h-256v-42.667zM320 426.667h384v42.667h-384v-42.667zM320 597.333h384v42.667h-384v-42.667zM239.595 106.667c-19.641 0-36.039 6.579-49.195 19.733s-19.733 29.555-19.733 49.195v544.811c0 19.641 6.578 36.039 19.733 49.195s29.554 19.733 49.195 19.733h544.811c19.639 0 36.041-6.578 49.195-19.733s19.733-29.554 19.733-49.195v-544.811c0-19.639-6.579-36.041-19.733-49.195s-29.555-19.733-49.195-19.733h-544.811zM239.595 149.333h544.811c6.571 0 12.591 2.735 18.057 8.205 5.47 5.466 8.205 11.486 8.205 18.057v544.811c0 6.571-2.735 12.59-8.205 18.059-5.466 5.469-11.486 8.203-18.057 8.203h-544.811c-6.571 0-12.59-2.734-18.059-8.203s-8.203-11.488-8.203-18.059v-544.811c0-6.571 2.734-12.591 8.203-18.057 5.469-5.47 11.488-8.205 18.059-8.205z" />
<glyph unicode="&#xe9cb;" glyph-name="value" data-tags="value" d="M512.141 64c-53.103 0-103.024 10.078-149.773 30.229-46.741 20.151-87.403 47.501-121.984 82.048s-61.955 75.17-82.123 121.877c-20.174 46.699-30.261 96.602-30.261 149.705s10.077 103.024 30.229 149.773c20.153 46.741 47.502 87.403 82.048 121.984s75.171 61.955 121.877 82.123c46.699 20.174 96.602 30.261 149.705 30.261s103.023-10.077 149.773-30.229c46.741-20.153 87.403-47.502 121.984-82.048s61.956-75.171 82.125-121.877c20.173-46.699 30.259-96.602 30.259-149.705s-10.078-103.023-30.229-149.773c-20.151-46.741-47.501-87.403-82.048-121.984s-75.17-61.956-121.877-82.125c-46.699-20.173-96.602-30.259-149.705-30.259zM512 106.667c95.287 0 176 33.067 242.133 99.2s99.2 146.846 99.2 242.133c0 95.289-33.067 176-99.2 242.133s-146.846 99.2-242.133 99.2c-95.289 0-176-33.067-242.133-99.2s-99.2-146.845-99.2-242.133c0-95.287 33.067-176 99.2-242.133s146.845-99.2 242.133-99.2zM512 256h42.667v384h-128v-42.667h85.333v-341.333z" /> <glyph unicode="&#xe9cb;" glyph-name="value" data-tags="value" d="M512.141 64c-53.103 0-103.024 10.078-149.773 30.229-46.741 20.151-87.403 47.501-121.984 82.048s-61.955 75.17-82.123 121.877c-20.174 46.699-30.261 96.602-30.261 149.705s10.077 103.024 30.229 149.773c20.153 46.741 47.502 87.403 82.048 121.984s75.171 61.955 121.877 82.123c46.699 20.174 96.602 30.261 149.705 30.261s103.023-10.077 149.773-30.229c46.741-20.153 87.403-47.502 121.984-82.048s61.956-75.171 82.125-121.877c20.173-46.699 30.259-96.602 30.259-149.705s-10.078-103.023-30.229-149.773c-20.151-46.741-47.501-87.403-82.048-121.984s-75.17-61.956-121.877-82.125c-46.699-20.173-96.602-30.259-149.705-30.259zM512 106.667c95.287 0 176 33.067 242.133 99.2s99.2 146.846 99.2 242.133c0 95.289-33.067 176-99.2 242.133s-146.846 99.2-242.133 99.2c-95.289 0-176-33.067-242.133-99.2s-99.2-146.845-99.2-242.133c0-95.287 33.067-176 99.2-242.133s146.845-99.2 242.133-99.2zM512 256h42.667v384h-128v-42.667h85.333v-341.333z" />
<glyph unicode="&#xe9cc;" glyph-name="choice-multiple" data-tags="choice-multiple" d="M170.667 746.667c0 23.564 19.103 42.667 42.667 42.667h597.333c23.565 0 42.667-19.103 42.667-42.667v-597.333c0-23.565-19.102-42.667-42.667-42.667h-597.333c-23.564 0-42.667 19.102-42.667 42.667v597.333zM810.667 746.667h-597.333v-597.333h597.333v597.333z" /> <glyph unicode="&#xe9cc;" glyph-name="choice-multiple" data-tags="choice-multiple" d="M170.667 746.667c0 23.564 19.103 42.667 42.667 42.667h597.333c23.565 0 42.667-19.103 42.667-42.667v-597.333c0-23.565-19.102-42.667-42.667-42.667h-597.333c-23.564 0-42.667 19.102-42.667 42.667v597.333zM810.667 746.667h-597.333v-597.333h597.333v597.333z" />
<glyph unicode="&#xe9cd;" glyph-name="choice-single" data-tags="choice-single" d="M853.333 448c0-188.514-152.819-341.333-341.333-341.333s-341.333 152.819-341.333 341.333c0 188.513 152.82 341.333 341.333 341.333s341.333-152.82 341.333-341.333zM512 149.333c164.949 0 298.667 133.717 298.667 298.667s-133.717 298.667-298.667 298.667c-164.949 0-298.667-133.718-298.667-298.667s133.718-298.667 298.667-298.667z" /> <glyph unicode="&#xe9cd;" glyph-name="choice-single" data-tags="choice-single" d="M853.333 448c0-188.514-152.819-341.333-341.333-341.333s-341.333 152.819-341.333 341.333c0 188.513 152.82 341.333 341.333 341.333s341.333-152.82 341.333-341.333zM512 149.333c164.949 0 298.667 133.717 298.667 298.667s-133.717 298.667-298.667 298.667c-164.949 0-298.667-133.718-298.667-298.667s133.718-298.667 298.667-298.667z" />
>>>>>>> develop
</font></defs></svg> </font></defs></svg>

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 149 KiB

View file

@ -5681,11 +5681,32 @@ tiny-emitter@^2.1.0:
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
version "1.3.1"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
tiny-warning@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tinycolor2@^1.4.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==
tinymce@^6.8.5: tinymce@^6.8.5:
version "6.8.5" version "6.8.5"
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-6.8.5.tgz#aa9a711c4e0b59d506dd281bade857d35a7b3c59" resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-6.8.5.tgz#aa9a711c4e0b59d506dd281bade857d35a7b3c59"
integrity sha512-qAL/FxL7cwZHj4BfaF818zeJJizK9jU5IQzTcSLL4Rj5MaJdiVblEj7aDr80VCV1w9h4Lak9hlnALhq/kVtN1g== integrity sha512-qAL/FxL7cwZHj4BfaF818zeJJizK9jU5IQzTcSLL4Rj5MaJdiVblEj7aDr80VCV1w9h4Lak9hlnALhq/kVtN1g==
tippy.js@^6.3.7:
version "6.3.7"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==
dependencies:
"@popperjs/core" "^2.9.0"
to-fast-properties@^2.0.0: to-fast-properties@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"