blacklight-spotlight 4.7.1 → 5.0.0.pre.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- 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 +35 -87
- data/app/assets/stylesheets/spotlight/#_accessibility.scss# +0 -12
- 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
|
})
|