blacklight-spotlight 4.7.0 → 5.0.0.pre.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +30 -12
- data/Rakefile +8 -1
- data/app/assets/javascripts/spotlight/application.js +0 -1
- data/app/assets/javascripts/spotlight/spotlight.esm.js +3620 -3847
- data/app/assets/javascripts/spotlight/spotlight.esm.js.map +1 -1
- data/app/assets/javascripts/spotlight/spotlight.js +3620 -3852
- data/app/assets/javascripts/spotlight/spotlight.js.map +1 -1
- data/app/assets/stylesheets/spotlight/_accessibility.scss +0 -9
- data/app/assets/stylesheets/spotlight/_autocomplete.scss +49 -0
- data/app/assets/stylesheets/spotlight/_blacklight_configuration.scss +0 -1
- data/app/assets/stylesheets/spotlight/_blacklight_overrides.scss +1 -6
- data/app/assets/stylesheets/spotlight/_browse.scss +2 -2
- data/app/assets/stylesheets/spotlight/_catalog.scss +40 -41
- data/app/assets/stylesheets/spotlight/_curation.scss +1 -1
- data/app/assets/stylesheets/spotlight/_exhibit_admin.scss +7 -0
- data/app/assets/stylesheets/spotlight/_exhibits_index.scss +8 -5
- data/app/assets/stylesheets/spotlight/_featured_browse_categories_block.scss +3 -3
- data/app/assets/stylesheets/spotlight/_header.scss +13 -0
- data/app/assets/stylesheets/spotlight/_mixins.scss +3 -4
- data/app/assets/stylesheets/spotlight/_nestable.scss +2 -12
- data/app/assets/stylesheets/spotlight/_pages.scss +11 -9
- data/app/assets/stylesheets/spotlight/_report_a_problem.scss +1 -3
- data/app/assets/stylesheets/spotlight/_sir-trevor_overrides.scss +2 -2
- data/app/assets/stylesheets/spotlight/_spotlight.scss +2 -1
- data/app/assets/stylesheets/spotlight/_tag_selector.scss +34 -0
- data/app/assets/stylesheets/spotlight/_variables.scss +0 -8
- data/app/components/spotlight/analytics/dashboard_component.html.erb +3 -3
- data/app/components/spotlight/breadcrumbs_component.html.erb +13 -19
- data/app/components/spotlight/bulk_action_component.rb +1 -1
- data/app/components/spotlight/document_component.rb +1 -1
- data/app/components/spotlight/save_search_component.rb +1 -1
- data/app/components/spotlight/select_image_component.html.erb +17 -0
- data/app/components/spotlight/select_image_component.rb +24 -0
- data/app/components/spotlight/skip_link_component.rb +16 -0
- data/app/components/spotlight/tag_selector_component.html.erb +40 -0
- data/app/components/spotlight/tag_selector_component.rb +41 -0
- data/app/components/spotlight/tag_selector_component.yml +6 -0
- data/app/components/spotlight/title_component.html.erb +8 -0
- data/app/components/spotlight/title_component.rb +22 -0
- data/app/controllers/spotlight/accessibility_controller.rb +2 -2
- data/app/controllers/spotlight/catalog_controller.rb +7 -2
- data/app/controllers/spotlight/contact_email_controller.rb +8 -2
- data/app/controllers/spotlight/languages_controller.rb +9 -4
- data/app/helpers/spotlight/application_helper.rb +7 -0
- data/app/helpers/spotlight/crop_helper.rb +4 -0
- data/app/helpers/spotlight/meta_helper.rb +59 -36
- data/app/javascript/spotlight/admin/blacklight_configuration.js +1 -1
- data/app/javascript/spotlight/admin/block_mixins/autocompleteable.js +70 -34
- data/app/javascript/spotlight/admin/blocks/block.js +1 -0
- data/app/javascript/spotlight/admin/blocks/browse_block.js +8 -12
- data/app/javascript/spotlight/admin/blocks/browse_group_categories_block.js +14 -18
- data/app/javascript/spotlight/admin/blocks/pages_block.js +6 -10
- data/app/javascript/spotlight/admin/blocks/resources_block.js +33 -15
- data/app/javascript/spotlight/admin/blocks/solr_documents_base_block.js +11 -6
- data/app/javascript/spotlight/admin/blocks/solr_documents_embed_block.js +1 -0
- data/app/javascript/spotlight/admin/blocks/uploaded_items_block.js +4 -3
- data/app/javascript/spotlight/admin/copy_email_addresses.js +2 -0
- data/app/javascript/spotlight/admin/crop.js +45 -17
- data/app/javascript/spotlight/admin/croppable.js +8 -1
- data/app/javascript/spotlight/admin/croppable_modal.js +68 -0
- data/app/javascript/spotlight/admin/exhibits.js +15 -10
- data/app/javascript/spotlight/admin/form_observer.js +1 -1
- data/app/javascript/spotlight/admin/index.js +0 -10
- data/app/javascript/spotlight/admin/locks.js +15 -5
- data/app/javascript/spotlight/admin/pages.js +1 -1
- data/app/javascript/spotlight/admin/search_typeahead.js +62 -55
- data/app/javascript/spotlight/admin/spotlight_nestable.js +173 -50
- data/app/javascript/spotlight/admin/visibility_toggle.js +1 -11
- data/app/javascript/spotlight/controllers/index.js +8 -0
- data/app/javascript/spotlight/controllers/tag_selector_controller.js +203 -0
- data/app/javascript/spotlight/core.js +4 -6
- data/app/javascript/spotlight/index.js +2 -0
- data/app/javascript/spotlight/user/browse_group_categories.js +2 -0
- data/app/javascript/spotlight/user/carousel.js +3 -1
- data/app/javascript/spotlight/user/index.js +0 -2
- data/app/models/sir_trevor_rails/block.rb +5 -4
- data/app/models/sir_trevor_rails/blocks/solr_documents_block.rb +1 -1
- data/app/models/sir_trevor_rails/blocks/solr_documents_embed_block.rb +1 -1
- data/app/models/sir_trevor_rails/blocks/uploaded_items_block.rb +1 -1
- data/app/models/spotlight/page_configurations.rb +1 -1
- data/app/views/catalog/_add_tags.html.erb +2 -2
- data/app/views/catalog/_change_visibility.html.erb +1 -1
- data/app/views/catalog/_remove_tags.html.erb +2 -2
- data/app/views/layouts/spotlight/base.html.erb +24 -13
- data/app/views/layouts/spotlight/spotlight.html.erb +6 -6
- data/app/views/shared/_masthead.html.erb +4 -31
- data/app/views/shared/_site_sidebar.html.erb +1 -1
- data/app/views/shared/_user_util_links.html.erb +3 -1
- data/app/views/spotlight/accessibility/alt_text.html.erb +2 -2
- data/app/views/spotlight/admin_users/index.html.erb +3 -3
- data/app/views/spotlight/appearances/edit.html.erb +1 -1
- data/app/views/spotlight/browse/_search_box.html.erb +8 -8
- data/app/views/spotlight/browse/show.html.erb +1 -1
- data/app/views/spotlight/bulk_updates/_download.html.erb +1 -1
- data/app/views/spotlight/bulk_updates/_upload.html.erb +1 -1
- data/app/views/spotlight/catalog/_admin_header.html.erb +1 -1
- data/app/views/spotlight/catalog/_edit_default.html.erb +2 -1
- data/app/views/spotlight/catalog/select_image.html.erb +1 -0
- data/app/views/spotlight/contacts/_form.html.erb +1 -1
- data/app/views/spotlight/exhibits/_contact.html.erb +5 -6
- data/app/views/spotlight/exhibits/_delete.html.erb +1 -1
- data/app/views/spotlight/exhibits/_languages.html.erb +3 -2
- data/app/views/spotlight/featured_images/_form.html.erb +6 -2
- data/app/views/spotlight/featured_images/_upload_form.html.erb +1 -1
- data/app/views/spotlight/metadata_configurations/_metadata_field.html.erb +1 -1
- data/app/views/spotlight/metadata_configurations/edit.html.erb +6 -6
- data/app/views/spotlight/pages/show.html.erb +1 -1
- data/app/views/spotlight/resources/csv_upload/_form.html.erb +1 -1
- data/app/views/spotlight/resources/upload/_form.html.erb +1 -1
- data/app/views/spotlight/roles/index.html.erb +1 -1
- data/app/views/spotlight/searches/_form.html.erb +1 -1
- data/app/views/spotlight/shared/_dd3_item.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_browse_group_categories_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_solr_documents_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_solr_documents_carousel_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_uploaded_items_block.html.erb +1 -1
- data/app/views/spotlight/tags/index.html.erb +2 -3
- data/app/views/spotlight/translations/_import.html.erb +2 -2
- data/config/importmap.rb +5 -0
- data/config/locales/spotlight.en.yml +2 -0
- data/config/routes.rb +5 -3
- data/lib/generators/spotlight/assets/generator_common_utilities.rb +36 -0
- data/lib/generators/spotlight/assets/importmap_generator.rb +87 -0
- data/lib/generators/spotlight/assets/propshaft_generator.rb +96 -0
- data/lib/generators/spotlight/assets_generator.rb +22 -0
- data/lib/generators/spotlight/install_generator.rb +8 -36
- data/lib/generators/spotlight/scaffold_resource_generator.rb +1 -1
- data/lib/generators/spotlight/templates/assets/spotlight.scss +6 -0
- data/lib/generators/spotlight/templates/javascript/jquery-shim.js +1 -0
- data/lib/spotlight/engine.rb +7 -6
- data/lib/spotlight/version.rb +1 -1
- data/spec/support/features/capybara_wait_metadata_helper.rb +13 -0
- data/spec/support/features/test_features_helpers.rb +16 -30
- data/vendor/assets/javascripts/tiny-slider.js +3 -0
- metadata +36 -87
- data/app/javascript/spotlight/admin/checkbox_submit.js +0 -75
- data/app/javascript/spotlight/admin/exhibit_tag_autocomplete.js +0 -39
- data/app/javascript/spotlight/user/report_a_problem.js +0 -30
- data/app/views/spotlight/browse/_tophat.html.erb +0 -1
- data/app/views/spotlight/catalog/_tophat_default.html.erb +0 -1
- data/app/views/spotlight/home_pages/_tophat.html.erb +0 -2
- data/app/views/spotlight/pages/_tophat.html.erb +0 -1
- data/lib/generators/spotlight/templates/spotlight.js +0 -1
- data/lib/generators/spotlight/templates/spotlight.scss +0 -5
- data/spec/support/features/capybara_default_max_wait_metadata_helper.rb +0 -20
- data/vendor/assets/javascripts/bootstrap-tagsinput.js +0 -530
- data/vendor/assets/javascripts/jquery.serializejson.js +0 -234
- data/vendor/assets/javascripts/nestable.js +0 -645
- data/vendor/assets/javascripts/sir-trevor.js +0 -23508
- data/vendor/assets/javascripts/typeahead.bundle.min.js +0 -7
- data/vendor/assets/stylesheets/bootstrap-tagsinput.css +0 -46
@@ -1,66 +1,73 @@
|
|
1
1
|
import { addImageSelector } from 'spotlight/admin/add_image_selector'
|
2
2
|
|
3
|
-
(
|
4
|
-
$.fn.spotlightSearchTypeAhead = function( options ) {
|
5
|
-
$.each(this, function(){
|
6
|
-
addAutocompleteBehavior($(this));
|
7
|
-
});
|
3
|
+
const docStore = new Map();
|
8
4
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
hint: (typeAheadInput.data('autocomplete-hint') || false),
|
15
|
-
autoselect: (typeAheadInput.data('autocomplete-autoselect') || true)
|
16
|
-
}, options);
|
17
|
-
typeAheadInput.typeahead(settings, {
|
18
|
-
displayKey: settings.displayKey,
|
19
|
-
source: settings.bloodhound.ttAdapter(),
|
20
|
-
templates: {
|
21
|
-
suggestion: settings.template
|
22
|
-
}
|
23
|
-
})
|
24
|
-
}
|
25
|
-
return this;
|
26
|
-
}
|
27
|
-
})( jQuery );
|
5
|
+
function highlight(value, query) {
|
6
|
+
if (query.trim() === '') return value;
|
7
|
+
const queryValue = query.trim();
|
8
|
+
return queryValue ? value.replace(new RegExp(queryValue, 'gi'), '<strong>$&</strong>') : value;
|
9
|
+
}
|
28
10
|
|
29
|
-
function
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
11
|
+
function templateFunc(obj, query) {
|
12
|
+
const thumbnail = obj.thumbnail ? `<div class="document-thumbnail"><img class="img-thumbnail" src="${obj.thumbnail}" /></div>` : '';
|
13
|
+
const privateClass = obj.private ? ' blacklight-private' : '';
|
14
|
+
const title = highlight(obj.title, query);
|
15
|
+
const description = obj.description ? `<small> ${highlight(obj.description, query)}</small>` : '';
|
16
|
+
return `<div class="autocomplete-item${privateClass}">${thumbnail}
|
17
|
+
<span class="autocomplete-title">${title}</span><br/>${description}
|
18
|
+
</div>`;
|
19
|
+
}
|
20
|
+
|
21
|
+
function autoCompleteElementTemplate(obj, query) {
|
22
|
+
return `<li role="option" data-autocomplete-value="${obj.id}">${templateFunc(obj, query)}</li>`;
|
23
|
+
}
|
24
|
+
|
25
|
+
function getAutoCompleteElementDataMap(autoCompleteElement) {
|
26
|
+
if (!docStore.has(autoCompleteElement.id)) {
|
27
|
+
docStore.set(autoCompleteElement.id, new Map());
|
28
|
+
}
|
29
|
+
return docStore.get(autoCompleteElement.id);
|
30
|
+
}
|
48
31
|
|
49
|
-
function
|
50
|
-
const
|
51
|
-
|
52
|
-
|
32
|
+
async function fetchResult(url) {
|
33
|
+
const result = await fetchAutocompleteJSON(url);
|
34
|
+
const docs = result.docs || [];
|
35
|
+
const query = this.querySelector('input').value || '';
|
36
|
+
const autoCompleteElementDataMap = getAutoCompleteElementDataMap(this);
|
37
|
+
return docs.map(doc => {
|
38
|
+
autoCompleteElementDataMap.set(doc.id, doc);
|
39
|
+
return autoCompleteElementTemplate(doc, query);
|
40
|
+
}).join('');
|
53
41
|
}
|
54
42
|
|
55
43
|
export function addAutocompletetoFeaturedImage(){
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
44
|
+
const autocompletePath = $('form[data-autocomplete-exhibit-catalog-path]').data('autocomplete-exhibit-catalog-path');
|
45
|
+
const featuredImageTypeaheads = $('[data-featured-image-typeahead]');
|
46
|
+
if (featuredImageTypeaheads.length === 0) return;
|
47
|
+
|
48
|
+
$.each(featuredImageTypeaheads, function(index, autoCompleteInput) {
|
49
|
+
const autoCompleteElement = autoCompleteInput.closest('auto-complete');
|
50
|
+
|
51
|
+
autoCompleteElement.setAttribute('src', autocompletePath);
|
52
|
+
autoCompleteElement.fetchResult = fetchResult;
|
53
|
+
autoCompleteElement.addEventListener('auto-complete-change', e => {
|
54
|
+
const data = getAutoCompleteElementDataMap(autoCompleteElement).get(e.relatedTarget.value);
|
55
|
+
if (!data) return;
|
56
|
+
|
57
|
+
const inputElement = $(e.relatedTarget);
|
58
|
+
const panel = document.querySelector(e.relatedTarget.dataset.targetPanel);
|
59
|
+
e.relatedTarget.value = data.title;
|
60
|
+
addImageSelector(inputElement, $(panel), data.iiif_manifest, true);
|
61
|
+
$(inputElement.data('id-field')).val(data['global_id']);
|
62
|
+
inputElement.attr('type', 'text');
|
64
63
|
});
|
64
|
+
});
|
65
|
+
}
|
66
|
+
|
67
|
+
export async function fetchAutocompleteJSON(url) {
|
68
|
+
const res = await(fetch(url.toString()));
|
69
|
+
if (!res.ok) {
|
70
|
+
throw new Error(await res.text());
|
65
71
|
}
|
72
|
+
return await res.json();
|
66
73
|
}
|
@@ -1,68 +1,191 @@
|
|
1
|
+
import Sortable from 'sortablejs';
|
2
|
+
|
1
3
|
const Module = (function() {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
4
|
+
const nestableContainerSelector = '[data-behavior="nestable"]';
|
5
|
+
const sortableOptions = {
|
6
|
+
animation: 150,
|
7
|
+
draggable: '.dd-item',
|
8
|
+
handle: '.dd-handle',
|
9
|
+
fallbackOnBody: true,
|
10
|
+
swapThreshold: 0.65,
|
11
|
+
emptyInsertThreshold: 15,
|
12
|
+
onStart: onStartHandler,
|
13
|
+
onEnd: onEndHandler,
|
14
|
+
onMove: onMoveHandler,
|
15
|
+
}
|
16
|
+
const draggableClass = 'dd-item';
|
17
|
+
const nestedSortableClass = 'dd-list';
|
18
|
+
const nestedSortableSelector = '.dd-list';
|
19
|
+
const nestedSortableNodeName = 'ol';
|
20
|
+
const findNode = (id, container) => container.querySelector(`[data-id="${id}"]`);
|
21
|
+
const setWeight = (node, weight) => weightField(node).value = weight;
|
22
|
+
const setParent = (node, parentId) => parentPageField(node).value = parentId;
|
23
|
+
const weightField = node => findProperty(node, "weight");
|
24
|
+
const parentPageField = node => findProperty(node, "parent_page");
|
25
|
+
const findProperty = (node, property) => node.querySelector(`input[data-property="${property}"]`);
|
26
|
+
let nestedId = 0;
|
27
|
+
|
28
|
+
return {
|
29
|
+
init: function(nestedContainers) {
|
30
|
+
if (nestedContainers === undefined) {
|
31
|
+
nestedContainers = document.querySelectorAll(nestableContainerSelector);
|
15
32
|
}
|
16
|
-
};
|
17
|
-
function updateWeightsAndRelationships(nestedList){
|
18
|
-
nestedList.on('change', function(event){
|
19
|
-
var container = $(event.currentTarget);
|
20
|
-
var data = $(this).nestable('serialize');
|
21
|
-
var weight = 0;
|
22
|
-
for(var i in data){
|
23
|
-
var parent_id = data[i]['id'];
|
24
|
-
const parent_node = findNode(parent_id, container);
|
25
|
-
setWeight(parent_node, weight++);
|
26
|
-
if(data[i]['children']){
|
27
|
-
var children = data[i]['children'];
|
28
|
-
for(var child in children){
|
29
|
-
var id = children[child]['id']
|
30
|
-
var child_node = findNode(id, container);
|
31
|
-
setWeight(child_node, weight++);
|
32
|
-
setParent(child_node, parent_id);
|
33
|
-
}
|
34
|
-
} else {
|
35
|
-
setParent(parent_node, "");
|
36
|
-
}
|
37
|
-
}
|
38
|
-
});
|
39
33
|
|
34
|
+
// nestedContainers could be a jQuery selector result, normalize to an array.
|
35
|
+
const containersToInit = Array.from(nestedContainers);
|
36
|
+
containersToInit.forEach((container) => {
|
37
|
+
// Sir Trevor listens for drag and drop events and will error on Sortable events.
|
38
|
+
// Don't let them bubble past the Sortable wrapper.
|
39
|
+
container.addEventListener('drop', stopPropagationHandler);
|
40
|
+
|
41
|
+
const nestedSortables = [
|
42
|
+
...(container.matches(nestedSortableSelector) ? [container] : []),
|
43
|
+
...Array.from(container.querySelectorAll(nestedSortableSelector))
|
44
|
+
];
|
45
|
+
const group = `nested-${nestedId++}`;
|
46
|
+
|
47
|
+
nestedSortables.forEach(sortable => {
|
48
|
+
new Sortable(sortable, { ...sortableOptions, group: group });
|
49
|
+
});
|
50
|
+
});
|
40
51
|
}
|
41
|
-
|
42
|
-
|
52
|
+
};
|
53
|
+
|
54
|
+
function stopPropagationHandler(evt) {
|
55
|
+
evt.stopPropagation();
|
56
|
+
}
|
57
|
+
|
58
|
+
function onStartHandler(evt) {
|
59
|
+
makeEmptyChildSortablesForEligibleParents(getNestableContainer(evt.item), getMaxNestingLevelSetting(evt.item));
|
60
|
+
}
|
61
|
+
|
62
|
+
function onEndHandler(evt) {
|
63
|
+
const nestableContainer = getNestableContainer(evt.item);
|
64
|
+
removeEmptySortables(nestableContainer);
|
65
|
+
updateWeightsAndRelationships(nestableContainer);
|
66
|
+
}
|
67
|
+
|
68
|
+
function onMoveHandler(evt) {
|
69
|
+
// The usage of data-max-depth is one off of the standard notion of depth (# edges to root)
|
70
|
+
// E.g., data-max-depth=2 allows for one level of nesting.
|
71
|
+
// evt.dragged is a draggable in a Sortable (e.g., a dd-item)
|
72
|
+
// evt.to is the Sortable to insert into (e.g., a dd-list)
|
73
|
+
const maxAllowedDepth = getMaxNestingLevelSetting(evt.to) - 1;
|
74
|
+
const newDepth = getSortableDepth(evt.to) + getHeight(evt.dragged);
|
75
|
+
|
76
|
+
// Be careful here. Returning true is different than returning nothing in SortableJS.
|
77
|
+
if (newDepth > maxAllowedDepth) {
|
78
|
+
return false;
|
43
79
|
}
|
80
|
+
}
|
81
|
+
|
82
|
+
// Get the depth of the sortable element from the root container
|
83
|
+
function getSortableDepth(sortableElement) {
|
84
|
+
const originatingGroup = Sortable.get(sortableElement).options.group.name;
|
85
|
+
let depth = 0;
|
86
|
+
let parentSortableElement = sortableElement;
|
44
87
|
|
45
|
-
|
46
|
-
|
88
|
+
while ((parentSortableElement = parentSortableElement.parentElement.closest(nestedSortableSelector))) {
|
89
|
+
const parentSortable = Sortable.get(parentSortableElement);
|
90
|
+
if (parentSortable?.options.group.name === originatingGroup) {
|
91
|
+
depth++;
|
92
|
+
}
|
47
93
|
}
|
48
94
|
|
49
|
-
|
50
|
-
|
95
|
+
return depth;
|
96
|
+
}
|
97
|
+
|
98
|
+
// Find the max child depth in the tree, starting from the draggableElement
|
99
|
+
function findMaxDepth(draggableElement) {
|
100
|
+
const childSortableElement = draggableElement.querySelector(nestedSortableSelector);
|
101
|
+
if (!childSortableElement) {
|
102
|
+
return 1;
|
51
103
|
}
|
52
104
|
|
53
|
-
|
54
|
-
|
55
|
-
|
105
|
+
const children = childSortableElement.querySelectorAll(`.${draggableClass}`);
|
106
|
+
const childDepths = Array.from(children).map(findMaxDepth);
|
107
|
+
return 1 + Math.max(0, ...childDepths);
|
108
|
+
}
|
109
|
+
|
110
|
+
function getHeight(draggableElement) {
|
111
|
+
return findMaxDepth(draggableElement) - 1;
|
112
|
+
}
|
113
|
+
|
114
|
+
function getNestableContainer(element) {
|
115
|
+
return element.closest(nestableContainerSelector);
|
116
|
+
}
|
117
|
+
|
118
|
+
function getMaxNestingLevelSetting(element) {
|
119
|
+
return getNestableContainer(element).getAttribute('data-max-depth') || 1;
|
120
|
+
}
|
121
|
+
|
122
|
+
// Create empty child sortables for all potential parents as appropriate for the given nesting level
|
123
|
+
function makeEmptyChildSortablesForEligibleParents(container, nestingLevel) {
|
124
|
+
if (nestingLevel <= 1) {
|
125
|
+
return;
|
56
126
|
}
|
57
127
|
|
58
|
-
|
59
|
-
|
60
|
-
|
128
|
+
const sortableElement = container.querySelector(nestedSortableSelector);
|
129
|
+
const sortable = Sortable.get(sortableElement);
|
130
|
+
if (!sortable) {
|
131
|
+
return;
|
61
132
|
}
|
62
133
|
|
63
|
-
|
64
|
-
|
134
|
+
const group = sortable.options.group.name;
|
135
|
+
const draggableElements = Array.from(sortableElement.children)
|
136
|
+
.filter(child => child.classList.contains(draggableClass));
|
137
|
+
|
138
|
+
draggableElements.forEach(draggableElement => {
|
139
|
+
if (!draggableElement.querySelector(nestedSortableSelector)) {
|
140
|
+
const emptySortableElement = document.createElement(nestedSortableNodeName);
|
141
|
+
emptySortableElement.className = nestedSortableClass;
|
142
|
+
draggableElement.appendChild(emptySortableElement);
|
143
|
+
new Sortable(emptySortableElement, { ...sortableOptions, group: group });
|
144
|
+
}
|
145
|
+
makeEmptyChildSortablesForEligibleParents(draggableElement, nestingLevel - 1);
|
146
|
+
});
|
147
|
+
}
|
148
|
+
|
149
|
+
// Remove any empty sortables within the container. They could be empty lists, which are invalid for accessibility.
|
150
|
+
function removeEmptySortables(container) {
|
151
|
+
const sortableElements = container.querySelectorAll(nestedSortableSelector);
|
152
|
+
sortableElements.forEach(sortableElement => {
|
153
|
+
if (sortableElement.innerHTML.trim() === '') {
|
154
|
+
const sortable = Sortable.get(sortableElement);
|
155
|
+
if (sortable) {
|
156
|
+
sortable.destroy();
|
157
|
+
sortableElement.remove();
|
158
|
+
}
|
159
|
+
}
|
160
|
+
});
|
161
|
+
}
|
162
|
+
|
163
|
+
// Traverse all sortables within a container and update the weight and parent_page inputs
|
164
|
+
function updateWeightsAndRelationships(container) {
|
165
|
+
const sortableElement = container.matches(nestedSortableSelector) ? container : container.querySelector(nestedSortableSelector);
|
166
|
+
const nestingLevelSetting = getMaxNestingLevelSetting(sortableElement);
|
167
|
+
const sortable = Sortable.get(sortableElement);
|
168
|
+
const stack = [{nodes: sortable.toArray(), parentId: ''}];
|
169
|
+
let weight = 0;
|
170
|
+
|
171
|
+
while (stack.length > 0) {
|
172
|
+
const {nodes, parentId} = stack.pop();
|
173
|
+
|
174
|
+
nodes.forEach((nodeId) => {
|
175
|
+
const node = findNode(nodeId, container);
|
176
|
+
setWeight(node, weight++);
|
177
|
+
|
178
|
+
if (nestingLevelSetting > 1) {
|
179
|
+
setParent(node, parentId);
|
180
|
+
const children = node.querySelector(nestedSortableSelector);
|
181
|
+
if (children) {
|
182
|
+
const sortableElement = Sortable.get(children);
|
183
|
+
stack.push({nodes: sortableElement.toArray(), parentId: nodeId});
|
184
|
+
}
|
185
|
+
}
|
186
|
+
});
|
65
187
|
}
|
188
|
+
}
|
66
189
|
})();
|
67
190
|
|
68
191
|
export default Module
|
@@ -1,23 +1,13 @@
|
|
1
|
-
//
|
2
|
-
// See: https://github.com/projectblacklight/blacklight/blob/main/app/javascript/blacklight/bookmark_toggle.js
|
3
|
-
|
4
|
-
import CheckboxSubmit from 'spotlight/admin/checkbox_submit'
|
5
|
-
|
1
|
+
// Blacklight's BookmarkToggle is doing the real work, this only adds/removes the "blacklight-private" class.
|
6
2
|
const VisibilityToggle = (e) => {
|
7
3
|
if (e.target.matches('[data-checkboxsubmit-target="checkbox"]')) {
|
8
4
|
const form = e.target.closest('form')
|
9
5
|
if (form) {
|
10
|
-
if (!Blacklight.BookmarkToggle) new CheckboxSubmit(form).clicked(e)
|
11
|
-
|
12
6
|
// Add/remove the "private" label to the document row when visibility is toggled
|
13
7
|
const docRow = form.closest('tr')
|
14
8
|
if (docRow) docRow.classList.toggle('blacklight-private')
|
15
9
|
}
|
16
10
|
}
|
17
11
|
}
|
18
|
-
|
19
|
-
VisibilityToggle.selector = 'form.visibility-toggle'
|
20
|
-
|
21
12
|
document.addEventListener('click', VisibilityToggle)
|
22
|
-
|
23
13
|
export default VisibilityToggle
|
@@ -0,0 +1,203 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
|
5
|
+
static targets = [
|
6
|
+
'addNewTagWrapper',
|
7
|
+
'dropdownContent',
|
8
|
+
'initialTags',
|
9
|
+
'newTag',
|
10
|
+
'searchResultTags',
|
11
|
+
'selectedTags',
|
12
|
+
'tagControlWrapper',
|
13
|
+
'tagSearch',
|
14
|
+
'tagsField',
|
15
|
+
'tagSearchDropdown',
|
16
|
+
'tagSearchInputWrapper'
|
17
|
+
]
|
18
|
+
|
19
|
+
static values = {
|
20
|
+
tags: Array,
|
21
|
+
translations: Object
|
22
|
+
}
|
23
|
+
|
24
|
+
tagDropdown (event) {
|
25
|
+
this.dropdownContentTarget.classList.toggle('d-none')
|
26
|
+
}
|
27
|
+
|
28
|
+
clickOutside (event) {
|
29
|
+
const isShown = !this.dropdownContentTarget.classList.contains('d-none')
|
30
|
+
const inSelected = event.target.classList.contains('pill-close')
|
31
|
+
const inContainer = this.tagControlWrapperTarget.contains(event.target)
|
32
|
+
if (!inContainer && !inSelected && isShown) {
|
33
|
+
this.tagDropdown(event)
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
handleKeydown (event) {
|
38
|
+
if (event.key === 'Enter') {
|
39
|
+
event.preventDefault()
|
40
|
+
const hidden = this.dropdownContentTarget.classList.contains('d-none')
|
41
|
+
if (hidden) return;
|
42
|
+
|
43
|
+
const tagElementToAdd = this.dropdownContentTarget.querySelector('.active')?.firstElementChild
|
44
|
+
if (tagElementToAdd) tagElementToAdd.click()
|
45
|
+
}
|
46
|
+
|
47
|
+
if (event.key === ',') {
|
48
|
+
event.preventDefault()
|
49
|
+
if (this.tagSearchTarget.value.length === 0) return
|
50
|
+
|
51
|
+
if (!this.addNewTagWrapperTarget.classList.contains('d-none')) {
|
52
|
+
this.addNewTagWrapperTarget.click()
|
53
|
+
this.tagSearchTarget.focus()
|
54
|
+
return
|
55
|
+
}
|
56
|
+
|
57
|
+
const exactMatch = this.dropdownContentTarget.querySelector('.active')?.firstElementChild
|
58
|
+
if (exactMatch?.checked === false) {
|
59
|
+
exactMatch.click()
|
60
|
+
this.resetSearch()
|
61
|
+
}
|
62
|
+
this.tagSearchTarget.focus()
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
addNewTag (event) {
|
67
|
+
if (this.addNewTagWrapperTarget.classList.contains('d-none') || this.newTagTarget.dataset.tag.length === 0) {
|
68
|
+
return
|
69
|
+
}
|
70
|
+
|
71
|
+
this.tagsValue = this.tagsValue.concat([this.newTagTarget.dataset.tag])
|
72
|
+
this.resetSearch()
|
73
|
+
}
|
74
|
+
|
75
|
+
resetSearch() {
|
76
|
+
this.tagSearchTarget.value = ''
|
77
|
+
this.newTagTarget.innerHTML = ''
|
78
|
+
this.newTagTarget.dataset.tag = ''
|
79
|
+
this.newTagTarget.disabled = true
|
80
|
+
this.addNewTagWrapperTarget.classList.add('d-none')
|
81
|
+
this.searchResultTagsTargets.forEach(target => this.showElement(target.parentElement))
|
82
|
+
}
|
83
|
+
|
84
|
+
tagUpdate (event) {
|
85
|
+
const target = event.target ? event.target : event
|
86
|
+
if (target.checked) {
|
87
|
+
this.tagsValue = this.tagsValue.concat([target.dataset.tag])
|
88
|
+
} else {
|
89
|
+
this.tagsValue = this.tagsValue.filter(tag => tag !== target.dataset.tag)
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
updateSearchResultsPlaceholder(event) {
|
94
|
+
const placeholderElement = this.dropdownContentTarget.querySelector('.no-results')
|
95
|
+
if (!placeholderElement) return
|
96
|
+
|
97
|
+
const hasVisibleTags = this.dropdownContentTarget.querySelector('label:not(.d-none):not(.no-results)')
|
98
|
+
placeholderElement.classList.toggle('d-none', hasVisibleTags)
|
99
|
+
}
|
100
|
+
|
101
|
+
tagCreate(event) {
|
102
|
+
event.preventDefault()
|
103
|
+
const newTagCheckbox = document.createElement('label')
|
104
|
+
newTagCheckbox.innerHTML = `<input type="checkbox" checked data-action="click->${this.identifier}#tagUpdate" data-tag-selector-target="searchResultTags" data-tag="${this.newTagTarget.dataset.tag}"> ${this.newTagTarget.dataset.tag}`
|
105
|
+
const existingTags = Array.from(this.dropdownContentTarget.querySelectorAll('label:not(#add-new-tag-wrapper)'))
|
106
|
+
const insertPosition = existingTags.findIndex(tag => tag.textContent.trim().localeCompare(this.newTagTarget.dataset.tag) > 0)
|
107
|
+
if (insertPosition === -1) {
|
108
|
+
this.addNewTagWrapperTarget.insertAdjacentElement('beforebegin', newTagCheckbox)
|
109
|
+
} else {
|
110
|
+
existingTags[insertPosition].insertAdjacentElement('beforebegin', newTagCheckbox)
|
111
|
+
}
|
112
|
+
|
113
|
+
this.tagsValue = this.tagsValue.concat([this.newTagTarget.dataset.tag])
|
114
|
+
this.tagSearchTarget.value = ''
|
115
|
+
this.tagSearchTarget.dispatchEvent(new Event('input'))
|
116
|
+
}
|
117
|
+
|
118
|
+
|
119
|
+
tagsValueChanged() {
|
120
|
+
const isEmpty = this.tagsValue.length === 0
|
121
|
+
|
122
|
+
this.selectedTagsTarget.classList.toggle('d-none', isEmpty)
|
123
|
+
this.tagSearchInputWrapperTarget.classList.toggle('rounded', isEmpty)
|
124
|
+
this.tagSearchInputWrapperTarget.classList.toggle('rounded-bottom', !isEmpty)
|
125
|
+
|
126
|
+
if (!isEmpty) {
|
127
|
+
this.selectedTagsTarget.innerHTML = `<ul class="list-unstyled border rounded-top mb-0 p-1 px-2">${this.renderTagPills()}</ul>`
|
128
|
+
}
|
129
|
+
|
130
|
+
const newValue = this.tagsValue.join(', ')
|
131
|
+
if (this.tagsFieldTarget.value !== newValue) {
|
132
|
+
this.tagsFieldTarget.value = newValue
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
normalizeTag (tag) {
|
137
|
+
const normalizeRegex = /[^\w\s]/gi
|
138
|
+
return tag.replace(normalizeRegex, '').toLowerCase().trim()
|
139
|
+
}
|
140
|
+
|
141
|
+
showElement (element) {
|
142
|
+
element.classList.add('d-block')
|
143
|
+
element.classList.remove('d-none')
|
144
|
+
}
|
145
|
+
|
146
|
+
hideElement (element) {
|
147
|
+
element.classList.remove('d-block')
|
148
|
+
element.classList.add('d-none')
|
149
|
+
}
|
150
|
+
|
151
|
+
search(event) {
|
152
|
+
const searchTerm = this.normalizeTag(event.target.value)
|
153
|
+
this.dropdownContentTarget.classList.remove('d-none')
|
154
|
+
|
155
|
+
const exactMatch = this.searchResultTagsTargets.some(target => {
|
156
|
+
const compareTerm = this.normalizeTag(target.dataset.tag)
|
157
|
+
const isMatch = compareTerm.includes(searchTerm)
|
158
|
+
target.parentElement.classList.remove('active')
|
159
|
+
this[isMatch ? 'showElement' : 'hideElement'](target.parentElement)
|
160
|
+
return compareTerm === searchTerm
|
161
|
+
})
|
162
|
+
|
163
|
+
this[searchTerm.length > 0 && !exactMatch ? 'showElement' : 'hideElement'](this.addNewTagWrapperTarget)
|
164
|
+
this.addNewTagWrapperTarget.classList.remove('active')
|
165
|
+
this.dropdownContentTarget.querySelector('label:not(.d-none)')?.classList.add('active')
|
166
|
+
}
|
167
|
+
|
168
|
+
updateTagToAdd (event) {
|
169
|
+
const tagAlreadyAdded = this.tagsValue.some(tag =>
|
170
|
+
this.normalizeTag(tag) === this.normalizeTag(event.target.value)
|
171
|
+
)
|
172
|
+
this.newTagTarget.dataset.tag = event.target.value.trim()
|
173
|
+
this.newTagTarget.nextSibling.textContent = ` ${this.translationsValue.add_new_tag}: ${event.target.value}`
|
174
|
+
this.newTagTarget.disabled = !this.newTagTarget.dataset.tag.length || tagAlreadyAdded
|
175
|
+
}
|
176
|
+
|
177
|
+
deselect (event) {
|
178
|
+
event.preventDefault()
|
179
|
+
|
180
|
+
const clickedTag = event.target.closest('button').dataset.tag
|
181
|
+
const target = this.searchResultTagsTargets.find((tag) => tag.dataset.tag === clickedTag)
|
182
|
+
target ? target.click() : this.tagsValue = this.tagsValue.filter(tag => tag !== clickedTag)
|
183
|
+
}
|
184
|
+
|
185
|
+
renderTagPills () {
|
186
|
+
return this.tagsValue.map((tag) => {
|
187
|
+
return `
|
188
|
+
<li class="d-inline-flex gap-2 align-items-center my-2">
|
189
|
+
<span class="bg-light badge rounded-pill border selected-item d-inline-flex align-items-center text-dark">
|
190
|
+
<span class="selected-item-label d-inline-flex">${tag}</span>
|
191
|
+
<button
|
192
|
+
type="button"
|
193
|
+
data-action="${this.identifier}#deselect"
|
194
|
+
data-tag="${tag}"
|
195
|
+
class="btn-close close ms-1 ml-1"
|
196
|
+
aria-label="${this.translationsValue.remove} ${tag}"
|
197
|
+
><span aria-hidden="true" class="visually-hidden">×</span></button>
|
198
|
+
</span>
|
199
|
+
</li>
|
200
|
+
`
|
201
|
+
}).join('')
|
202
|
+
}
|
203
|
+
}
|
@@ -1,3 +1,5 @@
|
|
1
|
+
import SirTrevor from "sir-trevor"
|
2
|
+
|
1
3
|
const Spotlight = function() {
|
2
4
|
var buffer = [];
|
3
5
|
return {
|
@@ -6,6 +8,7 @@ const Spotlight = function() {
|
|
6
8
|
},
|
7
9
|
|
8
10
|
activate: function() {
|
11
|
+
this.sirTrevorIcon = window.sirTrevorIcon;
|
9
12
|
for(var i = 0; i < buffer.length; i++) {
|
10
13
|
buffer[i].call();
|
11
14
|
}
|
@@ -23,10 +26,5 @@ const Spotlight = function() {
|
|
23
26
|
|
24
27
|
// This allows us to configure Spotlight in app/views/layouts/base.html.erb
|
25
28
|
window.Spotlight = Spotlight
|
26
|
-
|
29
|
+
window.SirTrevor = SirTrevor
|
27
30
|
export default Spotlight
|
28
|
-
|
29
|
-
Blacklight.onLoad(function() {
|
30
|
-
Spotlight.activate();
|
31
|
-
});
|
32
|
-
|
@@ -1,8 +1,10 @@
|
|
1
1
|
import UserIndex from 'spotlight/user'
|
2
2
|
import AdminIndex from 'spotlight/admin'
|
3
3
|
import Core from 'spotlight/core'
|
4
|
+
import SpotlightControllers from 'spotlight/controllers'
|
4
5
|
|
5
6
|
Core.onLoad(() => {
|
7
|
+
new SpotlightControllers().connect()
|
6
8
|
new UserIndex().connect()
|
7
9
|
new AdminIndex().connect()
|
8
10
|
})
|