diff --git a/app/assets/images/sn-loader.svg b/app/assets/images/sn-loader.svg
new file mode 100644
index 000000000..8d7159575
--- /dev/null
+++ b/app/assets/images/sn-loader.svg
@@ -0,0 +1,11 @@
+
diff --git a/app/assets/javascripts/my_modules/repositories.js b/app/assets/javascripts/my_modules/repositories.js
index 7caac5a1d..c2ff89b62 100644
--- a/app/assets/javascripts/my_modules/repositories.js
+++ b/app/assets/javascripts/my_modules/repositories.js
@@ -182,8 +182,14 @@ var MyModuleRepositories = (function() {
targets: 0,
className: 'item-name',
render: function(data, type, row) {
- var recordName = "" + data + '';
+ let recordName;
+
+ if (row.recordInfoUrl) {
+ recordName = `${data}`;
+ } else {
+ recordName = `
diff --git a/app/assets/javascripts/repositories/renderers/columns/date_time_helper.js b/app/assets/javascripts/repositories/renderers/columns/date_time_helper.js
index c346b4e02..4be902d54 100644
--- a/app/assets/javascripts/repositories/renderers/columns/date_time_helper.js
+++ b/app/assets/javascripts/repositories/renderers/columns/date_time_helper.js
@@ -14,6 +14,31 @@ var DateTimeHelper = (function() {
return ('0' + value).slice(-2);
}
+ function setDateTimePickerOpeningDirection(event) {
+ const element = $(event.target);
+ const dateTimePickerWidget = $('.bootstrap-datetimepicker-widget');
+
+ const windowHeight = element.closest('table').offset().top;
+ const inputTop = element.offset().top;
+ const pickerHeight = $('.bootstrap-datetimepicker-widget').outerHeight();
+
+ if (inputTop - windowHeight > pickerHeight) {
+ dateTimePickerWidget.addClass('top')
+ .removeClass('bottom')
+ .css({
+ top: 'auto',
+ bottom: '36px',
+ });
+ } else {
+ dateTimePickerWidget.addClass('bottom')
+ .removeClass('top')
+ .css({
+ top: '36px',
+ bottom: 'auto',
+ });
+ }
+ }
+
function recalcTimestamp(date, timeStr) {
if (!isValidTimeStr(timeStr)) {
date.setHours(0);
@@ -221,7 +246,11 @@ var DateTimeHelper = (function() {
hourFormat: 24
}).mask($cell.find('input[data-mask-type="time"]'));
- $cell.find('.calendar-input').datetimepicker({ ignoreReadonly: true, locale: 'en', format: formatJS });
+ $cell.find('.calendar-input')
+ .datetimepicker({ ignoreReadonly: true, locale: 'en', format: formatJS })
+ .on('dp.show', (e) => {
+ setDateTimePickerOpeningDirection(e);
+ });
initChangeEvents($cell);
}
@@ -273,9 +302,13 @@ var DateTimeHelper = (function() {
$cal1.on('dp.change', function(e) {
$cal2.data('DateTimePicker').minDate(e.date);
+ }).on('dp.show', (e) => {
+ setDateTimePickerOpeningDirection(e);
});
$cal2.on('dp.change', function(e) {
$cal1.data('DateTimePicker').maxDate(e.date);
+ }).on('dp.show', (e) => {
+ setDateTimePickerOpeningDirection(e);
});
initChangeEvents($cell);
diff --git a/app/assets/javascripts/repositories/repository_datatable.js b/app/assets/javascripts/repositories/repository_datatable.js
index 9c5ce079e..b2329d9a5 100644
--- a/app/assets/javascripts/repositories/repository_datatable.js
+++ b/app/assets/javascripts/repositories/repository_datatable.js
@@ -284,10 +284,6 @@ var RepositoryDatatable = (function(global) {
});
}
- function updateSelectedRowsForAssignments() {
- window.AssignItemsToTaskModalComponent.setShowCallback(() => rowsSelected);
- }
-
function checkAvailableColumns() {
$.ajax({
url: $(TABLE_ID).data('available-columns'),
@@ -817,6 +813,11 @@ var RepositoryDatatable = (function(global) {
initRepositoryViewSwitcher();
DataTableHelpers.initLengthAppearance($(TABLE_ID).closest('.dataTables_wrapper'));
+ $('.dataTables_wrapper').on('click', '.pagination', () => {
+ const dataTablesScrollBody = document.querySelector('.dataTables_scrollBody');
+ dataTablesScrollBody.scrollTo(0, 0);
+ });
+
$('.dataTables_filter').addClass('hidden');
addRepositorySearch();
@@ -882,7 +883,6 @@ var RepositoryDatatable = (function(global) {
})
initRowSelection();
- updateSelectedRowsForAssignments();
return TABLE;
}
@@ -1018,7 +1018,7 @@ var RepositoryDatatable = (function(global) {
e.preventDefault();
e.stopPropagation();
- window.AssignItemsToTaskModalComponentContainer.showModal();
+ window.AssignItemsToTaskModalComponentContainer.showModal(rowsSelected);
})
.on('click', '#deleteRepositoryRecords', function(e) {
e.preventDefault();
diff --git a/app/assets/javascripts/repository_columns/columns_initializers/date_column.js b/app/assets/javascripts/repository_columns/columns_initializers/date_column.js
index 0fc2d61ff..656861054 100644
--- a/app/assets/javascripts/repository_columns/columns_initializers/date_column.js
+++ b/app/assets/javascripts/repository_columns/columns_initializers/date_column.js
@@ -17,7 +17,8 @@ var RepositoryDateColumnType = (function() {
$modal.on('change', `${columnContainer} #date-reminder, ${columnContainer} #date-range`, function() {
let reminderCheckbox = $(columnContainer).find('#date-reminder');
let rangeCheckbox = $(columnContainer).find('#date-range');
- rangeCheckbox.attr('disabled', reminderCheckbox.is(':checked'));
+ const isExistingRecord = $('#new-repo-column-submit').css('display') === 'none';
+ rangeCheckbox.attr('disabled', isExistingRecord || reminderCheckbox.is(':checked'));
reminderCheckbox.attr('disabled', rangeCheckbox.is(':checked'));
$(columnContainer).find('.reminder-group').toggleClass('hidden', !reminderCheckbox.is(':checked'));
});
diff --git a/app/assets/javascripts/repository_columns/columns_initializers/date_time_column.js b/app/assets/javascripts/repository_columns/columns_initializers/date_time_column.js
index c041126b0..69991c52a 100644
--- a/app/assets/javascripts/repository_columns/columns_initializers/date_time_column.js
+++ b/app/assets/javascripts/repository_columns/columns_initializers/date_time_column.js
@@ -17,7 +17,8 @@ var RepositoryDateTimeColumnType = (function() {
$modal.on('change', `${columnContainer} #datetime-reminder, ${columnContainer} #datetime-range`, function() {
let reminderCheckbox = $(columnContainer).find('#datetime-reminder');
let rangeCheckbox = $(columnContainer).find('#datetime-range');
- rangeCheckbox.attr('disabled', reminderCheckbox.is(':checked'));
+ const isExistingRecord = $('#new-repo-column-submit').css('display') === 'none';
+ rangeCheckbox.attr('disabled', isExistingRecord || reminderCheckbox.is(':checked'));
reminderCheckbox.attr('disabled', rangeCheckbox.is(':checked'));
$(columnContainer).find('.reminder-group').toggleClass('hidden', !reminderCheckbox.is(':checked'));
});
diff --git a/app/assets/javascripts/repository_columns/index.js b/app/assets/javascripts/repository_columns/index.js
index e4db50832..b95f6f630 100644
--- a/app/assets/javascripts/repository_columns/index.js
+++ b/app/assets/javascripts/repository_columns/index.js
@@ -1,6 +1,6 @@
/* global I18n HelperModule truncateLongString animateSpinner RepositoryListColumnType RepositoryStockColumnType */
/* global RepositoryDatatable RepositoryStatusColumnType RepositoryChecklistColumnType dropdownSelector RepositoryDateTimeColumnType */
-/* global RepositoryDateColumnType RepositoryDatatable */
+/* global RepositoryDateColumnType RepositoryDatatable _ */
/* eslint-disable no-restricted-globals */
@@ -297,6 +297,8 @@ var RepositoryColumns = (function() {
} else {
thederName = el.innerText;
}
+ thederName = _.escape(thederName);
+
if (['row-name', 'archived-by', 'archived-on'].includes(el.id)) {
visClass = '';
visText = '';
diff --git a/app/assets/javascripts/shareable_links/my_module_results_show.js b/app/assets/javascripts/shareable_links/my_module_results_show.js
index a17af4da5..52394fa67 100644
--- a/app/assets/javascripts/shareable_links/my_module_results_show.js
+++ b/app/assets/javascripts/shareable_links/my_module_results_show.js
@@ -31,6 +31,15 @@
});
}
+ function initResultComments() {
+ $(document).on('click', '.shareable-link-open-comments-sidebar', function(e) {
+ e.preventDefault();
+ $('.comments-sidebar').removeClass('open');
+
+ $($(this).data('objectTarget')).addClass('open');
+ });
+ }
+
function initResultsExpandCollapse() {
$(document).on('click', '#results-collapse-btn', function() {
$('.result .panel-collapse').collapse('hide');
@@ -44,6 +53,7 @@
function initMyModuleResultsShow() {
initAttachments();
initResultsExpandCollapse();
+ initResultComments();
$('.hot-table-container').each(function() {
initializeHandsonTable($(this));
diff --git a/app/assets/javascripts/sitewide/marvinjs_editor.js b/app/assets/javascripts/sitewide/marvinjs_editor.js
index 9694fb1d9..c3330445f 100644
--- a/app/assets/javascripts/sitewide/marvinjs_editor.js
+++ b/app/assets/javascripts/sitewide/marvinjs_editor.js
@@ -200,6 +200,8 @@ var MarvinJsEditorApi = (function() {
$('#modal_link' + json.id + ' .attachment-label').text(json.file_name);
}
$(marvinJsModal).modal('hide');
+
+ config.editor.focus();
config.button.dataset.inProgress = false;
if (MarvinJsEditor.saveCallback) MarvinJsEditor.saveCallback();
diff --git a/app/assets/javascripts/sitewide/repository_row_card.js b/app/assets/javascripts/sitewide/repository_row_card.js
new file mode 100644
index 000000000..45b049241
--- /dev/null
+++ b/app/assets/javascripts/sitewide/repository_row_card.js
@@ -0,0 +1,72 @@
+/* global PrintModalComponent RepositoryDatatable HelperModule MyModuleRepositories */
+
+(function() {
+ 'use strict';
+
+ $(document).on('click', '.record-info-link', function(e) {
+ const myModuleId = $('.my-modules-protocols-index').data('task-id');
+ const repositoryRowURL = $(this).attr('href');
+
+ e.stopPropagation();
+ e.preventDefault();
+
+ window.repositoryItemSidebarComponent.toggleShowHideSidebar(repositoryRowURL, myModuleId);
+ });
+
+ $(document).on('click', '.print-label-button', function(e) {
+ var selectedRows = $(this).data('rows');
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (typeof PrintModalComponent !== 'undefined') {
+ PrintModalComponent.showModal = true;
+ if (selectedRows && selectedRows.length) {
+ $('#modal-info-repository-row').modal('hide');
+ PrintModalComponent.row_ids = selectedRows;
+ } else {
+ PrintModalComponent.row_ids = [...RepositoryDatatable.selectedRows()];
+ }
+ }
+ });
+
+ $(document).on('click', '.assign-inventory-button', function(e) {
+ e.preventDefault();
+ const assignUrl = $(this).attr('data-assign-url');
+ const repositoryRowId = $(this).attr('data-repository-row-id');
+
+ $.ajax({
+ url: assignUrl,
+ type: 'POST',
+ data: { repository_row_id: repositoryRowId },
+ dataType: 'json',
+ success: function(data) {
+ HelperModule.flashAlertMsg(data.flash, 'success');
+ $('#modal-info-repository-row').modal('hide');
+ if (typeof MyModuleRepositories !== 'undefined') {
+ MyModuleRepositories.reloadRepositoriesList(repositoryRowId);
+ }
+ window.repositoryItemSidebarComponent.reload();
+ },
+ error: function(error) {
+ HelperModule.flashAlertMsg(error.responseJSON.flash, 'danger');
+ }
+ });
+ });
+
+ $(document).on('click', '.export-consumption-button', function(e) {
+ const selectedRows = $(this).data('rows') || RepositoryDatatable.selectedRows();
+
+ e.preventDefault();
+
+ window.initExportStockConsumptionModal();
+
+ if (window.exportStockConsumptionModalComponent) {
+ window.exportStockConsumptionModalComponent.fetchRepositoryData(
+ selectedRows,
+ { repository_id: $(this).data('objectId') },
+ );
+ $('#modal-info-repository-row').modal('hide');
+ }
+ });
+}());
diff --git a/app/assets/javascripts/sitewide/repository_row_info_modal.js b/app/assets/javascripts/sitewide/repository_row_info_modal.js
deleted file mode 100644
index 162bb1098..000000000
--- a/app/assets/javascripts/sitewide/repository_row_info_modal.js
+++ /dev/null
@@ -1,125 +0,0 @@
-/* global bwipjs PrintModalComponent RepositoryDatatable HelperModule MyModuleRepositories */
-
-(function() {
- 'use strict';
-
- $(document).on('click', '.record-info-link', function(e) {
- var that = $(this);
- let params = {};
- if ($('.my-modules-protocols-index, #results').length) {
- params.my_module_id = $('.my-modules-protocols-index, #results').data('task-id');
- }
-
- $.ajax({
- method: 'GET',
- url: that.attr('href'),
- data: params,
- dataType: 'json'
- }).done(function(xhr, settings, data) {
- if ($('#modal-info-repository-row').length) {
- $('#modal-info-repository-row').find('.modal-body #repository_row-info-table').DataTable().destroy();
- $('#modal-info-repository-row').remove();
- $('.modal-backdrop').remove();
- }
- $('body').append($.parseHTML(data.responseJSON.html));
- $('[data-toggle="tooltip"]').tooltip();
- $('#modal-info-repository-row').modal('show', {
- backdrop: true,
- keyboard: false
- }).on('hidden.bs.modal', function() {
- $(this).find('.modal-body #repository_row-info-table').DataTable().destroy();
- $(this).remove();
- });
-
- let barCodeCanvas = bwipjs.toCanvas('bar-code-canvas', {
- bcid: 'qrcode',
- text: $('#modal-info-repository-row #bar-code-canvas').data('id').toString(),
- scale: 3
- });
- $('#modal-info-repository-row #bar-code-image').attr('src', barCodeCanvas.toDataURL('image/png'));
-
- $('#repository_row-info-table').DataTable({
- dom: 'RBltpi',
- stateSave: false,
- buttons: [],
- processing: true,
- colReorder: {
- fixedColumnsLeft: 1000000 // Disable reordering
- },
- columnDefs: [{
- targets: 0,
- searchable: false,
- orderable: false
- }],
- fnDrawCallback: function(settings, json) {
- animateSpinner(this, false);
- },
- preDrawCallback: function(settings) {
- animateSpinner(this);
- }
- });
- });
- e.preventDefault();
- return false;
- });
-
- $(document).on('click', '.export-consumption-button', function() {
- let selectedRows = [];
-
- if ($(this).attr('id') === 'exportStockConsumptionButton') {
- selectedRows = RepositoryDatatable.selectedRows();
- } else {
- selectedRows = $('#modal-info-repository-row .print-label-button').data('rows');
- }
-
- window.initExportStockConsumptionModal();
-
- if (window.exportStockConsumptionModalComponent) {
- window.exportStockConsumptionModalComponent.fetchRepositoryData(
- selectedRows,
- { repository_id: $(this).data('objectId') },
- );
- $('#modal-info-repository-row').modal('hide');
- }
- });
-
- $(document).on('click', '.print-label-button', function(e) {
- var selectedRows = $(this).data('rows');
-
- e.preventDefault();
- e.stopPropagation();
-
- if (typeof PrintModalComponent !== 'undefined') {
- PrintModalComponent.showModal = true;
- if (selectedRows && selectedRows.length) {
- $('#modal-info-repository-row').modal('hide');
- PrintModalComponent.row_ids = selectedRows;
- } else {
- PrintModalComponent.row_ids = [...RepositoryDatatable.selectedRows()];
- }
- }
- });
-
- $(document).on('click', '.assign-inventory-button', function(e) {
- e.preventDefault();
- let assignUrl = $(this).data('assignUrl');
- let repositoryRowId = $(this).data('repositoryRowId');
-
- $.ajax({
- url: assignUrl,
- type: 'POST',
- data: { repository_row_id: repositoryRowId },
- dataType: 'json',
- success: function(data) {
- HelperModule.flashAlertMsg(data.flash, 'success');
- $('#modal-info-repository-row').modal('hide');
- if (typeof MyModuleRepositories !== 'undefined') {
- MyModuleRepositories.reloadRepositoriesList(repositoryRowId);
- }
- },
- error: function(error) {
- HelperModule.flashAlertMsg(error.responseJSON.flash, 'danger');
- }
- });
- });
-}());
diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css
index ce964b0f3..703ee0481 100644
--- a/app/assets/stylesheets/application.tailwind.css
+++ b/app/assets/stylesheets/application.tailwind.css
@@ -2,6 +2,7 @@
@import "tailwind/buttons";
@import "tailwind/modals";
@import "tailwind/flyouts";
+@import "tailwind/loader.css";
@tailwind base;
@tailwind components;
diff --git a/app/assets/stylesheets/my_modules/protocol.scss b/app/assets/stylesheets/my_modules/protocol.scss
index b6b7d3817..11d83d2a2 100644
--- a/app/assets/stylesheets/my_modules/protocol.scss
+++ b/app/assets/stylesheets/my_modules/protocol.scss
@@ -103,14 +103,3 @@
color: var(--sn-grey);
}
}
-
-@media screen and (max-width: 1395px) {
- .task-section-header {
- height: 7.44rem;
- }
-
- .protocol-buttons-group {
- flex-wrap: wrap;
- margin: 1rem;
- }
-}
diff --git a/app/assets/stylesheets/shared/assets.scss b/app/assets/stylesheets/shared/assets.scss
index 3e58a4b5b..947746b12 100644
--- a/app/assets/stylesheets/shared/assets.scss
+++ b/app/assets/stylesheets/shared/assets.scss
@@ -288,9 +288,6 @@
.dropdown-menu {
@include font-button;
- min-width: 200px;
- padding: .5em 0;
- z-index: 102;
.divider-label {
@include font-small;
diff --git a/app/assets/stylesheets/shared/content/checklist.scss b/app/assets/stylesheets/shared/content/checklist.scss
index 65eddae49..5317f14ec 100644
--- a/app/assets/stylesheets/shared/content/checklist.scss
+++ b/app/assets/stylesheets/shared/content/checklist.scss
@@ -2,16 +2,6 @@
// scss-lint:disable NestingDepth
.step-checklist-items {
- .sci-inline-edit {
- margin-top: 5px;
-
- .sci-inline-edit__content {
- margin-bottom: 7px;
- margin-left: 0;
- margin-right: .5em;
- }
- }
-
.step-checklist-item {
padding-left: 5px;
}
@@ -59,14 +49,6 @@
.step-checklist-item-ghost {
border: 1px solid $brand-primary;
}
-
- .sci-checkbox-container {
- margin: 11px 0;
-
- &.disabled {
- pointer-events: none;
- }
- }
}
.step-checklist-container {
diff --git a/app/assets/stylesheets/tailwind/buttons.css b/app/assets/stylesheets/tailwind/buttons.css
index 14d1c2001..6a3b96db4 100644
--- a/app/assets/stylesheets/tailwind/buttons.css
+++ b/app/assets/stylesheets/tailwind/buttons.css
@@ -137,4 +137,18 @@
.btn.btn-danger.disabled {
@apply bg-sn-delete-red-disabled;
}
+
+ .btn-text-link {
+ @apply text-sn-blue text-sm cursor-pointer
+ }
+
+ .btn-text-link:visited,
+ .btn-text-link:hover {
+ @apply text-sn-blue no-underline
+ }
+
+ .btn-text-link.disabled,
+ .btn-text-link:disabled {
+ @apply text-sn-sleepy-grey
+ }
}
diff --git a/app/assets/stylesheets/tailwind/loader.css b/app/assets/stylesheets/tailwind/loader.css
new file mode 100644
index 000000000..91655d80c
--- /dev/null
+++ b/app/assets/stylesheets/tailwind/loader.css
@@ -0,0 +1,6 @@
+@layer components {
+ .sci-loader {
+ @apply flex m-auto h-[30px] w-[30px] animate-spin;
+ background: image-url("sn-loader.svg") center center no-repeat;
+ }
+}
diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb
index 6341043e4..162f8224e 100644
--- a/app/controllers/comments_controller.rb
+++ b/app/controllers/comments_controller.rb
@@ -112,6 +112,6 @@ class CommentsController < ApplicationController
end
def check_manage_permissions
- comment_editable?(@comment)
+ render_403 unless comment_editable?(@comment)
end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index fc09cfca8..e487264cc 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -350,7 +350,7 @@ class ProjectsController < ApplicationController
def notifications
@modules = @project.assigned_modules(current_user).order(due_date: :desc)
render json: {
- html: render_to_string(partial: 'notifications')
+ html: render_to_string(partial: 'notifications', formats: :html)
}
end
diff --git a/app/controllers/repository_rows_controller.rb b/app/controllers/repository_rows_controller.rb
index 9d94811d7..00c8a55c5 100644
--- a/app/controllers/repository_rows_controller.rb
+++ b/app/controllers/repository_rows_controller.rb
@@ -10,7 +10,7 @@ class RepositoryRowsController < ApplicationController
before_action :load_repository_row_print, only: %i(print rows_to_print print_zpl validate_label_template_columns)
before_action :load_repository_or_snapshot, only: %i(print rows_to_print print_zpl validate_label_template_columns)
before_action :load_repository_row, only: %i(update assigned_task_list active_reminder_repository_cells)
- before_action :check_read_permissions, except: %i(show create update delete_records
+ before_action :check_read_permissions, except: %i(create update delete_records
copy_records reminder_repository_cells
delete_records archive_records restore_records
actions_toolbar)
@@ -42,6 +42,28 @@ class RepositoryRowsController < ApplicationController
render json: { custom_error: I18n.t('repositories.show.repository_filter.errors.value_not_found') }
end
+ def show
+ @repository_row = @repository.repository_rows.find_by(id: params[:id])
+ return render_404 unless @repository_row
+
+ @my_module = if params[:my_module_id].present?
+ MyModule.repository_row_assignable_by_user(current_user).find_by(id: params[:my_module_id])
+ end
+ return render_403 if @my_module && !can_read_my_module?(@my_module)
+
+ if @my_module
+ @my_module_assign_error = if !can_assign_my_module_repository_rows?(@my_module)
+ I18n.t('repository_row.modal_info.assign_to_task_error.no_access')
+ elsif @repository_row.my_modules.where(id: @my_module.id).any?
+ I18n.t('repository_row.modal_info.assign_to_task_error.already_assigned')
+ end
+ end
+
+ @assigned_modules = @repository_row.my_modules.joins(experiment: :project)
+ @viewable_modules = @assigned_modules.viewable_by_user(current_user, current_user.teams)
+ @reminders_present = @repository_row.repository_cells.with_active_reminder(@current_user).any?
+ end
+
def create
service = RepositoryRows::CreateRepositoryRowService
.call(repository: @repository, user: current_user, params: update_params)
@@ -83,7 +105,7 @@ class RepositoryRowsController < ApplicationController
@private_modules = @assigned_modules - @viewable_modules
render json: {
- html: render_to_string(partial: 'repositories/repository_row_info_modal')
+ html: render_to_string(partial: 'repositories/repository_row_info_modal', formats: :html)
}
end
diff --git a/app/controllers/repository_stock_values_controller.rb b/app/controllers/repository_stock_values_controller.rb
index 68e3881ad..ead16911e 100644
--- a/app/controllers/repository_stock_values_controller.rb
+++ b/app/controllers/repository_stock_values_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class RepositoryStockValuesController < ApplicationController
- include RepositoryDatatableHelper # for use of display_cell_value method on stock update
+ include RepositoryDatatableHelper # for use of serialize_repository_cell_value method on stock update
before_action :load_vars
before_action :check_manage_permissions
@@ -51,7 +51,7 @@ class RepositoryStockValuesController < ApplicationController
stock_managable: true,
stock_status: @repository_stock_value.status,
manageStockUrl: edit_repository_stock_repository_repository_row_url(@repository, @repository_row)
- }.merge(display_cell_value(@repository_stock_value.repository_cell, current_team, @repository))
+ }.merge(serialize_repository_cell_value(@repository_stock_value.repository_cell, current_team, @repository))
end
private
diff --git a/app/controllers/team_repositories_controller.rb b/app/controllers/team_repositories_controller.rb
index 3840fbeea..266ddaefe 100644
--- a/app/controllers/team_repositories_controller.rb
+++ b/app/controllers/team_repositories_controller.rb
@@ -6,7 +6,7 @@ class TeamRepositoriesController < ApplicationController
# DELETE :team_id/repositories/:repository_id/team_repositories/:id
def destroy
- team_shared_object = @repository.team_shared_objects.find(destory_params[:id])
+ team_shared_object = @repository.team_shared_objects.find(destroy_params[:id])
ActiveRecord::Base.transaction do
log_activity(:unshare_inventory, team_shared_object)
team_shared_object.destroy!
@@ -50,7 +50,7 @@ class TeamRepositoriesController < ApplicationController
params.permit(:team_id, :repository_id, :target_team_id, :permission_level)
end
- def destory_params
+ def destroy_params
params.permit(:team_id, :id)
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 0c6f5ed76..8242d6be2 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -144,7 +144,7 @@ module ApplicationHelper
popover_for_user_name(user, team, false, false, base64_encoded_imgs)
end
- new_text
+ sanitize_input(new_text)
end
# Generate smart annotation link for one user object
diff --git a/app/helpers/input_sanitize_helper.rb b/app/helpers/input_sanitize_helper.rb
index 860d76479..e30a23666 100644
--- a/app/helpers/input_sanitize_helper.rb
+++ b/app/helpers/input_sanitize_helper.rb
@@ -4,8 +4,16 @@ require 'sanitize'
require 'cgi'
module InputSanitizeHelper
- def sanitize_input(html, _tags = [], _attributes = [], sanitizer_config: Constants::INPUT_SANITIZE_CONFIG)
- Sanitize.fragment(html, sanitizer_config).html_safe
+ def sanitize_input(html, _tags = [], _attributes = [], sanitizer_config: nil)
+ config =
+ if Rails.application.config.x.custom_sanitizer_config.present?
+ Rails.application.config.x.custom_sanitizer_config
+ elsif sanitizer_config.present?
+ sanitizer_config
+ else
+ Constants::INPUT_SANITIZE_CONFIG
+ end
+ Sanitize.fragment(html, config).html_safe
end
def escape_input(text)
diff --git a/app/helpers/my_modules_helper.rb b/app/helpers/my_modules_helper.rb
index b2014275c..6b706987a 100644
--- a/app/helpers/my_modules_helper.rb
+++ b/app/helpers/my_modules_helper.rb
@@ -112,4 +112,53 @@ module MyModulesHelper
''
end
end
+
+ def serialize_assigned_my_module_value(my_module)
+ [
+ serialize_assigned_my_module_team_data(my_module.team),
+ serialize_assigned_my_module_project_data(my_module.project),
+ serialize_assigned_my_module_experiment_data(my_module.experiment),
+ serialize_assigned_my_module_data(my_module)
+ ]
+ end
+
+ private
+
+ def serialize_assigned_my_module_team_data(team)
+ {
+ type: team.class.name.underscore,
+ value: team.name,
+ url: projects_path(team: team.id),
+ archived: false
+ }
+ end
+
+ def serialize_assigned_my_module_project_data(project)
+ archived = project.archived?
+ {
+ type: project.class.name.underscore,
+ value: project.name,
+ url: project_path(project, view_mode: archived ? 'archived' : 'active'),
+ archived: archived
+ }
+ end
+
+ def serialize_assigned_my_module_experiment_data(experiment)
+ archived = experiment.archived_branch?
+ {
+ type: experiment.class.name.underscore,
+ value: experiment.name,
+ url: archived ? module_archive_experiment_path(experiment) : my_modules_experiment_path(experiment),
+ archived: archived
+ }
+ end
+
+ def serialize_assigned_my_module_data(my_module)
+ {
+ type: my_module.class.name.underscore,
+ value: my_module.name,
+ url: protocols_my_module_path(my_module, view_mode: my_module.archived_branch? ? 'archived' : 'active'),
+ archived: my_module.archived_branch?
+ }
+ end
end
diff --git a/app/helpers/repository_datatable_helper.rb b/app/helpers/repository_datatable_helper.rb
index 3c92261bd..c933f791f 100644
--- a/app/helpers/repository_datatable_helper.rb
+++ b/app/helpers/repository_datatable_helper.rb
@@ -43,7 +43,7 @@ module RepositoryDatatableHelper
custom_cells.each do |cell|
row[columns_mappings[cell.repository_column.id]] =
- display_cell_value(cell, team, repository, reminders_enabled: reminders_enabled)
+ serialize_repository_cell_value(cell, team, repository, reminders_enabled: reminders_enabled)
end
if has_stock_management
@@ -64,7 +64,12 @@ module RepositoryDatatableHelper
stock_cell = record.repository_cells.find { |cell| cell.value_type == 'RepositoryStockValue' }
# always add stock cell, even if empty
- row['stock'] = stock_cell.present? ? display_cell_value(record.repository_stock_cell, team, repository) : {}
+ row['stock'] =
+ if stock_cell.present?
+ serialize_repository_cell_value(record.repository_stock_cell, team, repository)
+ else
+ {}
+ end
row['stock'][:stock_managable] = stock_managable
row['stock']['displayWarnings'] = display_stock_warnings?(repository)
row['stock'][:stock_status] = stock_cell&.value&.status
@@ -114,7 +119,6 @@ module RepositoryDatatableHelper
DT_RowId: record.id,
DT_RowAttr: { 'data-state': row_style(record) },
'0': escape_input(record.name),
- recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(record.repository, record),
rowRemindersUrl:
Rails.application.routes.url_helpers
.active_reminder_repository_cells_repository_repository_row_url(
@@ -123,6 +127,11 @@ module RepositoryDatatableHelper
)
}
+ unless record.repository.is_a?(RepositorySnapshot)
+ row['recordInfoUrl'] = Rails.application.routes.url_helpers.repository_repository_row_path(record.repository,
+ record)
+ end
+
if reminders_enabled
row['hasActiveReminders'] = record.has_active_stock_reminders || record.has_active_datetime_reminders
end
@@ -132,7 +141,12 @@ module RepositoryDatatableHelper
consumption_managable = stock_consumption_managable?(record, repository, my_module)
- row['stock'] = stock_present ? display_cell_value(record.repository_stock_cell, record.repository.team, repository) : {}
+ row['stock'] =
+ if stock_present
+ serialize_repository_cell_value(record.repository_stock_cell, record.repository.team, repository)
+ else
+ {}
+ end
row['stock']['displayWarnings'] = display_stock_warnings?(repository)
row['stock'][:stock_status] = record.repository_stock_cell&.value&.status
@@ -140,7 +154,9 @@ module RepositoryDatatableHelper
if record.repository.is_a?(RepositorySnapshot)
row['consumedStock'] =
if record.repository_stock_consumption_value.present?
- display_cell_value(record.repository_stock_consumption_cell, record.repository.team, repository)
+ serialize_repository_cell_value(record.repository_stock_consumption_cell,
+ record.repository.team,
+ repository)
else
{}
end
@@ -184,24 +200,26 @@ module RepositoryDatatableHelper
'2': escape_input(record.name),
'3': I18n.l(record.created_at, format: :full),
'4': escape_input(record.created_by.full_name),
- 'recordInfoUrl': Rails.application.routes.url_helpers.repository_repository_row_path(repository_snapshot, record)
+ 'recordInfoUrl': Rails.application.routes.url_helpers
+ .repository_repository_row_path(repository_snapshot, record)
}
# Add custom columns
record.repository_cells.each do |cell|
- row[columns_mappings[cell.repository_column.id]] = display_cell_value(cell, team, repository_snapshot)
+ row[columns_mappings[cell.repository_column.id]] =
+ serialize_repository_cell_value(cell, team, repository_snapshot)
end
if has_stock_management
row['stock'] = if record.repository_stock_cell.present?
- display_cell_value(record.repository_stock_cell, team, repository_snapshot)
+ serialize_repository_cell_value(record.repository_stock_cell, team, repository_snapshot)
else
{ value_type: 'RepositoryStockValue' }
end
row['consumedStock'] =
if record.repository_stock_consumption_cell.present?
- display_cell_value(record.repository_stock_consumption_cell, team, repository_snapshot)
+ serialize_repository_cell_value(record.repository_stock_consumption_cell, team, repository_snapshot)
else
{}
end
@@ -220,13 +238,6 @@ module RepositoryDatatableHelper
}
end
- def can_perform_repository_actions(repository)
- can_read_repository?(repository) ||
- can_manage_repository?(repository) ||
- can_create_repositories?(repository.team) ||
- can_manage_repository_rows?(repository)
- end
-
def repository_default_columns(record)
{
'1': assigned_row(record),
@@ -252,7 +263,7 @@ module RepositoryDatatableHelper
}
end
- def display_cell_value(cell, team, repository, options = {})
+ def serialize_repository_cell_value(cell, team, repository, options = {})
serializer_class = "RepositoryDatatable::#{cell.repository_column.data_type}Serializer".constantize
serializer_class.new(
cell.value,
diff --git a/app/javascript/packs/vue/assign_items_to_task_modal.js b/app/javascript/packs/vue/assign_items_to_task_modal.js
index ab7ddad48..e21c428ba 100644
--- a/app/javascript/packs/vue/assign_items_to_task_modal.js
+++ b/app/javascript/packs/vue/assign_items_to_task_modal.js
@@ -20,6 +20,7 @@ function initAssignItemsToTaskModalComponent() {
data() {
return {
visibility: false,
+ rowsToAssign: [],
urls: {
assign: container.data('assign-url'),
projects: container.data('projects-url'),
@@ -29,7 +30,8 @@ function initAssignItemsToTaskModalComponent() {
};
},
methods: {
- showModal() {
+ showModal(repositoryRows) {
+ this.rowsToAssign = repositoryRows;
this.visibility = true;
},
closeModal() {
diff --git a/app/javascript/packs/vue/directives/outside_click.js b/app/javascript/packs/vue/directives/outside_click.js
index fc85316f6..4ccd5258f 100644
--- a/app/javascript/packs/vue/directives/outside_click.js
+++ b/app/javascript/packs/vue/directives/outside_click.js
@@ -3,16 +3,13 @@
// eg v-click-outside="{handler: 'handlerToTrigger', exclude: [refs to ignore on click (eg 'searchInput', 'searchInputBtn')]}"
// eslint-enable-next-line max-len
-let handleOutsideClick;
-
export default {
bind(el, binding, vnode) {
- const { handler, exclude } = binding.value;
-
- handleOutsideClick = (e) => {
+ el._vueClickOutside_ = (e) => {
e.stopPropagation();
let clickedOnExcludedEl = false;
+ const { exclude } = binding.value;
exclude.forEach(refName => {
if (!clickedOnExcludedEl) {
const excludedEl = vnode.context.$refs[refName];
@@ -21,15 +18,17 @@ export default {
});
if (!el.contains(e.target) && !clickedOnExcludedEl) {
+ const { handler } = binding.value;
vnode.context[handler]();
}
};
- document.addEventListener('click', handleOutsideClick);
- document.addEventListener('touchstart', handleOutsideClick);
+ document.addEventListener('click', el._vueClickOutside_);
+ document.addEventListener('touchstart', el._vueClickOutside_);
},
- unbind() {
- document.removeEventListener('click', handleOutsideClick);
- document.removeEventListener('touchstart', handleOutsideClick);
+ unbind(el) {
+ document.removeEventListener('click', el._vueClickOutside_);
+ document.removeEventListener('touchstart', el._vueClickOutside_);
+ el._vueClickOutside_ = null;
}
};
diff --git a/app/javascript/packs/vue/repository_item_sidebar.js b/app/javascript/packs/vue/repository_item_sidebar.js
new file mode 100644
index 000000000..fc151754e
--- /dev/null
+++ b/app/javascript/packs/vue/repository_item_sidebar.js
@@ -0,0 +1,21 @@
+/* global notTurbolinksPreview */
+
+import TurbolinksAdapter from 'vue-turbolinks';
+import ScrollSpy from 'vue2-scrollspy';
+import Vue from 'vue/dist/vue.esm';
+import RepositoryItemSidebar from '../../vue/repository_item_sidebar/RepositoryItemSidebar.vue';
+
+Vue.use(TurbolinksAdapter);
+Vue.use(ScrollSpy);
+Vue.prototype.i18n = window.I18n;
+
+function initRepositoryItemSidebar() {
+ new Vue({
+ el: '#repositoryItemSidebar',
+ components: {
+ RepositoryItemSidebar
+ }
+ });
+}
+
+initRepositoryItemSidebar();
diff --git a/app/javascript/vue/assign_items_to_tasks_modal/container.vue b/app/javascript/vue/assign_items_to_tasks_modal/container.vue
index 7c2eb4edd..aed6d0568 100644
--- a/app/javascript/vue/assign_items_to_tasks_modal/container.vue
+++ b/app/javascript/vue/assign_items_to_tasks_modal/container.vue
@@ -145,11 +145,11 @@ export default {
name: "AssignItemsToTaskModalContainer",
props: {
visibility: Boolean,
- urls: Object
+ urls: Object,
+ rowsToAssign: Array
},
data() {
return {
- rowsToAssign: [],
projects: [],
experiments: [],
tasks: [],
@@ -159,15 +159,11 @@ export default {
projectsLoading: null,
experimentsLoading: null,
tasksLoading: null,
- showCallback: null
};
},
components: {
SelectSearch
},
- created() {
- window.AssignItemsToTaskModalComponent = this;
- },
mounted() {
$(this.$refs.modal).on("shown.bs.modal", () => {
this.projectsLoading = true;
@@ -239,8 +235,6 @@ export default {
methods: {
showModal() {
$(this.$refs.modal).modal("show");
-
- this.rowsToAssign = this.showCallback();
},
hideModal() {
$(this.$refs.modal).modal("hide");
@@ -317,11 +311,9 @@ export default {
}).always(() => {
this.resetSelectors();
this.reloadTable();
+ window.repositoryItemSidebarComponent.reload();
});
},
- setShowCallback(callback) {
- this.showCallback = callback;
- },
reloadTable() {
$('.repository-row-selector:checked').trigger('click');
$('.repository-table')
diff --git a/app/javascript/vue/protocol_import/file_import_modal.vue b/app/javascript/vue/protocol_import/file_import_modal.vue
index 76d7870f3..b4d9f323d 100644
--- a/app/javascript/vue/protocol_import/file_import_modal.vue
+++ b/app/javascript/vue/protocol_import/file_import_modal.vue
@@ -3,10 +3,12 @@
-
+
-
{{ i18n.t('zip_export.consumption_header_html', { repository: this.repository?.name }) }}
+
{{ i18n.t('zip_export.consumption_header_html', { repository: repository?.name }) }}
diff --git a/app/jobs/reports/pdf_job.rb b/app/jobs/reports/pdf_job.rb
index 2bce30b56..e17dbe74c 100644
--- a/app/jobs/reports/pdf_job.rb
+++ b/app/jobs/reports/pdf_job.rb
@@ -34,19 +34,22 @@ module Reports
proxy.set_user(user, scope: :user, store: false)
ApplicationController.renderer.defaults[:http_host] = Rails.application.routes.default_url_options[:host]
renderer = ApplicationController.renderer.new(warden: proxy)
+ Rails.application.config.x.custom_sanitizer_config = build_custom_sanitizer_config
file << renderer.render(
- pdf: 'report', header: { html: { template: "reports/templates/#{template}/header",
- locals: { report: report, user: user, logo: report_logo },
- layout: 'reports/footer_header' } },
- footer: { html: { template: "reports/templates/#{template}/footer",
- locals: { report: report, user: user, logo: report_logo },
- layout: 'reports/footer_header' } },
- assigns: { settings: report.settings },
- locals: { report: report },
- disable_javascript: false,
- template: 'reports/report',
- formats: :pdf
+ pdf: 'report',
+ header: { html: { template: "reports/templates/#{template}/header",
+ locals: { report: report, user: user, logo: report_logo },
+ layout: 'reports/footer_header' } },
+ footer: { html: { template: "reports/templates/#{template}/footer",
+ locals: { report: report, user: user, logo: report_logo },
+ layout: 'reports/footer_header' } },
+ assigns: { settings: report.settings },
+ locals: { report: report },
+ disable_javascript: false,
+ disable_external_links: true,
+ template: 'reports/report',
+ formats: :pdf
)
file.rewind
@@ -69,6 +72,7 @@ module Reports
)
notification.create_user_notification(user)
ensure
+ Rails.application.config.x.custom_sanitizer_config = nil
I18n.backend.date_format = nil
file.close(true)
end
@@ -178,6 +182,15 @@ module Reports
'scinote_logo.svg'
end
+ def build_custom_sanitizer_config
+ sanitizer_config = Constants::INPUT_SANITIZE_CONFIG.deep_dup
+ sanitizer_config[:protocols] = {
+ 'a' => { 'href' => ['http', 'https', :relative] },
+ 'img' => { 'src' => %w(data) }
+ }
+ sanitizer_config
+ end
+
# Overrides method from FailedDeliveryNotifiableJob concern
def failed_notification_title
I18n.t('projects.reports.index.generation.error_pdf_notification_title')
diff --git a/app/models/concerns/tiny_mce_images.rb b/app/models/concerns/tiny_mce_images.rb
index c50adb578..5cf536180 100644
--- a/app/models/concerns/tiny_mce_images.rb
+++ b/app/models/concerns/tiny_mce_images.rb
@@ -110,13 +110,17 @@ module TinyMceImages
next if asset.object == self
next unless asset.can_read?(user)
else
- url = image['src']
- image_type = FastImage.type(url).to_s
- next unless image_type
-
+ image_type = nil
begin
- new_image = Down.download(url, max_size: Rails.configuration.x.file_max_size_mb.megabytes)
- rescue Down::TooLarge => e
+ uri = URI.parse(image['src'])
+ if uri.scheme != 'https'
+ uri.scheme = Rails.application.config.force_ssl ? 'https' : 'http'
+ end
+ image_type = FastImage.type(uri.to_s).to_s
+ next unless image_type
+
+ new_image = Down.download(uri.to_s, max_size: Rails.configuration.x.file_max_size_mb.megabytes)
+ rescue StandardError => e
Rails.logger.error e.message
next
end
diff --git a/app/serializers/repository_datatable/repository_asset_value_serializer.rb b/app/serializers/repository_datatable/repository_asset_value_serializer.rb
index 144bb6327..56ef51457 100644
--- a/app/serializers/repository_datatable/repository_asset_value_serializer.rb
+++ b/app/serializers/repository_datatable/repository_asset_value_serializer.rb
@@ -13,7 +13,8 @@ module RepositoryDatatable
url: rails_blob_path(asset.file, disposition: 'attachment'),
preview_url: asset_file_preview_path(asset),
file_name: escape_input(asset.file_name),
- icon_html: sn_icon_for(asset)
+ icon_html: sn_icon_for(asset),
+ medium_preview_url: asset.previewable? && rails_representation_url(asset.medium_preview)
}
rescue StandardError => e
Rails.logger.error e.message
diff --git a/app/serializers/repository_datatable/repository_date_time_value_serializer.rb b/app/serializers/repository_datatable/repository_date_time_value_serializer.rb
index 32989a920..ded1c5eb5 100644
--- a/app/serializers/repository_datatable/repository_date_time_value_serializer.rb
+++ b/app/serializers/repository_datatable/repository_date_time_value_serializer.rb
@@ -12,9 +12,22 @@ module RepositoryDatatable
if scope.dig(:options, :reminders_enabled) &&
!scope[:repository].is_a?(RepositorySnapshot) &&
- scope[:column].reminder_value.present? && scope[:column].reminder_unit.present?
+ scope[:column].reminder_value.present? &&
+ scope[:column].reminder_unit.present?
reminder_delta = scope[:column].reminder_value.to_i * scope[:column].reminder_unit.to_i
data[:reminder] = reminder_delta + DateTime.now.to_i >= value_object.data.to_i
+ data[:reminder_message] = scope[:column].reminder_message
+ days_left = ((value_object.data - Time.now.utc) / 1.day).ceil
+ if data[:reminder] && days_left.positive?
+ data[:days_left] = days_left
+ date_expiration =
+ "#{days_left} #{I18n.t("repositories.item_card.reminders.day.#{days_left == 1 ? 'one' : 'other'}")}"
+ data[:text] =
+ "#{I18n.t('repositories.item_card.reminders.date_expiration', date_expiration: date_expiration)}\n
+ #{data[:reminder_message]}"
+ elsif data[:reminder]
+ data[:text] = "#{I18n.t('repositories.item_card.reminders.item_expired')}\n#{data[:reminder_message]}"
+ end
end
data
diff --git a/app/serializers/repository_datatable/repository_date_value_serializer.rb b/app/serializers/repository_datatable/repository_date_value_serializer.rb
index 54963518f..2b9464eb7 100644
--- a/app/serializers/repository_datatable/repository_date_value_serializer.rb
+++ b/app/serializers/repository_datatable/repository_date_value_serializer.rb
@@ -10,11 +10,23 @@ module RepositoryDatatable
if scope.dig(:options, :reminders_enabled) &&
!scope[:repository].is_a?(RepositorySnapshot) &&
- scope[:column].reminder_value.present? && scope[:column].reminder_unit.present?
+ scope[:column].reminder_value.present? &&
+ scope[:column].reminder_unit.present?
reminder_delta = scope[:column].reminder_value.to_i * scope[:column].reminder_unit.to_i
data[:reminder] = reminder_delta + DateTime.now.to_i >= value_object.data.to_i
+ data[:reminder_message] = scope[:column].reminder_message
+ days_left = ((value_object.data - Time.now.utc) / 1.day).ceil
+ if data[:reminder] && days_left.positive?
+ data[:days_left] = days_left
+ date_expiration =
+ "#{days_left} #{I18n.t("repositories.item_card.reminders.day.#{days_left == 1 ? 'one' : 'other'}")}"
+ data[:text] =
+ "#{I18n.t('repositories.item_card.reminders.date_expiration', date_expiration: date_expiration)}\n
+ #{data[:reminder_message]}"
+ elsif data[:reminder]
+ data[:text] = "#{I18n.t('repositories.item_card.reminders.item_expired')}\n#{data[:reminder_message]}"
+ end
end
-
data
end
end
diff --git a/app/serializers/repository_datatable/repository_stock_value_serializer.rb b/app/serializers/repository_datatable/repository_stock_value_serializer.rb
index 40bf57d3a..796f2a96e 100644
--- a/app/serializers/repository_datatable/repository_stock_value_serializer.rb
+++ b/app/serializers/repository_datatable/repository_stock_value_serializer.rb
@@ -5,11 +5,23 @@ module RepositoryDatatable
include Canaid::Helpers::PermissionsHelper
def value
- {
+ data = {
stock_formatted: value_object.formatted,
stock_amount: value_object.data,
low_stock_threshold: value_object.low_stock_threshold
}
+ if scope.dig(:options, :reminders_enabled) &&
+ !scope[:repository].is_a?(RepositorySnapshot) &&
+ value_object.data.present? &&
+ value_object.low_stock_threshold.present?
+ data[:reminder] = value_object.low_stock_threshold > value_object.data
+ if data[:reminder] && (data[:stock_amount]).positive?
+ data[:text] = I18n.t('repositories.item_card.reminders.stock_low', stock_formated: data[:stock_formatted])
+ elsif data[:reminder]
+ data[:text] = I18n.t('repositories.item_card.reminders.stock_empty')
+ end
+ end
+ data
end
end
end
diff --git a/app/views/access_permissions/partials/_new_assignments_form.html.erb b/app/views/access_permissions/partials/_new_assignments_form.html.erb
index 0e4641c4a..3ac1abf84 100644
--- a/app/views/access_permissions/partials/_new_assignments_form.html.erb
+++ b/app/views/access_permissions/partials/_new_assignments_form.html.erb
@@ -5,7 +5,7 @@
<%= link_to assignable_path, remote: true, class: 'pull-left spacer', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
-
+
<% end %>
<%= t '.title', resource_name: assignable.name %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 3ec2afd12..2230140a2 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -101,6 +101,7 @@
<%= render "shared/comments/comments_sidebar" %>
+ <%= render "shared/repository_row_sidebar" %>
<%= render partial: 'shared/flash_alerts',
@@ -121,5 +122,7 @@
<%= javascript_include_tag 'prism' %>
+ <%= javascript_include_tag "vue_components_repository_item_sidebar" %>
+