diff --git a/app/assets/javascripts/spotlight/application.js b/app/assets/javascripts/spotlight/application.js
index 5a57d8335..91e5a33ad 100644
--- a/app/assets/javascripts/spotlight/application.js
+++ b/app/assets/javascripts/spotlight/application.js
@@ -3,6 +3,5 @@
//= require sir-trevor
//= require clipboard
//= require tiny-slider
-//= require typeahead.bundle.min.js
//= require spotlight/spotlight
\ No newline at end of file
diff --git a/app/assets/javascripts/spotlight/spotlight.esm.js b/app/assets/javascripts/spotlight/spotlight.esm.js
index f9cd1b018..1373479d2 100644
--- a/app/assets/javascripts/spotlight/spotlight.esm.js
+++ b/app/assets/javascripts/spotlight/spotlight.esm.js
@@ -1,6 +1,7 @@
import Clipboard from 'clipboard';
import SirTrevor$1 from 'sir-trevor';
import Sortable from 'sortablejs';
+import { Controller } from '@hotwired/stimulus';
// Includes an unreleased RTL support pull request: https://github.com/ganlanyuan/tiny-slider/pull/658
// Includes "export default tns" at the end of the file for spotlight/user/browse_group_categories.js
@@ -4164,46 +4165,6 @@ class EditInPlace {
}
}
-class ExhibitTagAutocomplete {
- connect() {
- $('[data-autocomplete-tag="true"]').each(function(_i, el) {
- var $el = $(el);
- // By default tags input binds on page ready to [data-role=tagsinput],
- // however, that doesn't work with Turbolinks. So we init manually:
- $el.tagsinput();
-
- var tags = new Bloodhound({
- datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.name); },
- queryTokenizer: Bloodhound.tokenizers.whitespace,
- limit: 100,
- prefetch: {
- url: $el.data('autocomplete-url'),
- ttl: 1,
- filter: function(list) {
- // Let the dom know that the response has been returned
- $el.attr('data-autocomplete-fetched', true);
- return $.map(list, function(tag) { return { name: tag }; });
- }
- }
- });
-
- tags.initialize();
-
- $el.tagsinput('input').typeahead({highlight: true, hint: false}, {
- name: 'tags',
- displayKey: 'name',
- source: tags.ttAdapter()
- }).bind('typeahead:selected', $.proxy(function (obj, datum) {
- $el.tagsinput('add', datum.name);
- $el.tagsinput('input').typeahead('val', '');
- })).bind('blur', function() {
- $el.tagsinput('add', $el.tagsinput('input').typeahead('val'));
- $el.tagsinput('input').typeahead('val', '');
- });
- });
- }
-}
-
/*
https://gist.github.com/pjambet/3710461
*/
@@ -6930,7 +6891,6 @@ class AdminIndex {
new CopyEmailAddress().connect();
new Croppable().connect();
new EditInPlace().connect();
- new ExhibitTagAutocomplete().connect();
new Exhibits().connect();
new FormObserver().connect();
new Locks().connect();
@@ -6947,7 +6907,217 @@ class AdminIndex {
}
}
+class TagSelectorController extends Controller {
+
+ static targets = [
+ 'addNewTagWrapper',
+ 'dropdownContent',
+ 'initialTags',
+ 'newTag',
+ 'searchResultTags',
+ 'selectedTags',
+ 'tagControlWrapper',
+ 'tagSearch',
+ 'tagsField',
+ 'tagSearchDropdown',
+ 'tagSearchInputWrapper'
+ ]
+
+ static values = {
+ tags: Array,
+ translations: Object
+ }
+
+ tagDropdown (event) {
+ this.dropdownContentTarget.classList.toggle('d-none');
+ }
+
+ clickOutside (event) {
+ const isShown = !this.dropdownContentTarget.classList.contains('d-none');
+ const inSelected = event.target.classList.contains('pill-close');
+ const inContainer = this.tagControlWrapperTarget.contains(event.target);
+ if (!inContainer && !inSelected && isShown) {
+ this.tagDropdown(event);
+ }
+ }
+
+ handleKeydown (event) {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ const hidden = this.dropdownContentTarget.classList.contains('d-none');
+ if (hidden) return;
+
+ const tagElementToAdd = this.dropdownContentTarget.querySelector('.active')?.firstElementChild;
+ if (tagElementToAdd) tagElementToAdd.click();
+ }
+
+ if (event.key === ',') {
+ event.preventDefault();
+ if (this.tagSearchTarget.value.length === 0) return
+
+ if (!this.addNewTagWrapperTarget.classList.contains('d-none')) {
+ this.addNewTagWrapperTarget.click();
+ this.tagSearchTarget.focus();
+ return
+ }
+
+ const exactMatch = this.dropdownContentTarget.querySelector('.active')?.firstElementChild;
+ if (exactMatch?.checked === false) {
+ exactMatch.click();
+ this.resetSearch();
+ }
+ this.tagSearchTarget.focus();
+ }
+ }
+
+ addNewTag (event) {
+ if (this.addNewTagWrapperTarget.classList.contains('d-none') || this.newTagTarget.dataset.tag.length === 0) {
+ return
+ }
+
+ this.tagsValue = this.tagsValue.concat([this.newTagTarget.dataset.tag]);
+ this.resetSearch();
+ }
+
+ resetSearch() {
+ this.tagSearchTarget.value = '';
+ this.newTagTarget.innerHTML = '';
+ this.newTagTarget.dataset.tag = '';
+ this.newTagTarget.disabled = true;
+ this.addNewTagWrapperTarget.classList.add('d-none');
+ this.searchResultTagsTargets.forEach(target => this.showElement(target.parentElement));
+ }
+
+ tagUpdate (event) {
+ const target = event.target ? event.target : event;
+ if (target.checked) {
+ this.tagsValue = this.tagsValue.concat([target.dataset.tag]);
+ } else {
+ this.tagsValue = this.tagsValue.filter(tag => tag !== target.dataset.tag);
+ }
+ }
+
+ updateSearchResultsPlaceholder(event) {
+ const placeholderElement = this.dropdownContentTarget.querySelector('.no-results');
+ if (!placeholderElement) return
+
+ const hasVisibleTags = this.dropdownContentTarget.querySelector('label:not(.d-none):not(.no-results)');
+ placeholderElement.classList.toggle('d-none', hasVisibleTags);
+ }
+
+ tagCreate(event) {
+ event.preventDefault();
+ const newTagCheckbox = document.createElement('label');
+ newTagCheckbox.innerHTML = ` ${this.newTagTarget.dataset.tag}`;
+ const existingTags = Array.from(this.dropdownContentTarget.querySelectorAll('label:not(#add-new-tag-wrapper)'));
+ const insertPosition = existingTags.findIndex(tag => tag.textContent.trim().localeCompare(this.newTagTarget.dataset.tag) > 0);
+ if (insertPosition === -1) {
+ this.addNewTagWrapperTarget.insertAdjacentElement('beforebegin', newTagCheckbox);
+ } else {
+ existingTags[insertPosition].insertAdjacentElement('beforebegin', newTagCheckbox);
+ }
+
+ this.tagsValue = this.tagsValue.concat([this.newTagTarget.dataset.tag]);
+ this.tagSearchTarget.value = '';
+ this.tagSearchTarget.dispatchEvent(new Event('input'));
+ }
+
+
+ tagsValueChanged() {
+ const isEmpty = this.tagsValue.length === 0;
+
+ this.selectedTagsTarget.classList.toggle('d-none', isEmpty);
+ this.tagSearchInputWrapperTarget.classList.toggle('rounded', isEmpty);
+ this.tagSearchInputWrapperTarget.classList.toggle('rounded-bottom', !isEmpty);
+
+ if (!isEmpty) {
+ this.selectedTagsTarget.innerHTML = `
`;
+ }
+
+ const newValue = this.tagsValue.join(', ');
+ if (this.tagsFieldTarget.value !== newValue) {
+ this.tagsFieldTarget.value = newValue;
+ }
+ }
+
+ normalizeTag (tag) {
+ const normalizeRegex = /[^\w\s]/gi;
+ return tag.replace(normalizeRegex, '').toLowerCase().trim()
+ }
+
+ showElement (element) {
+ element.classList.add('d-block');
+ element.classList.remove('d-none');
+ }
+
+ hideElement (element) {
+ element.classList.remove('d-block');
+ element.classList.add('d-none');
+ }
+
+ search(event) {
+ const searchTerm = this.normalizeTag(event.target.value);
+ this.dropdownContentTarget.classList.remove('d-none');
+
+ const exactMatch = this.searchResultTagsTargets.some(target => {
+ const compareTerm = this.normalizeTag(target.dataset.tag);
+ const isMatch = compareTerm.includes(searchTerm);
+ target.parentElement.classList.remove('active');
+ this[isMatch ? 'showElement' : 'hideElement'](target.parentElement);
+ return compareTerm === searchTerm
+ });
+
+ this[searchTerm.length > 0 && !exactMatch ? 'showElement' : 'hideElement'](this.addNewTagWrapperTarget);
+ this.addNewTagWrapperTarget.classList.remove('active');
+ this.dropdownContentTarget.querySelector('label:not(.d-none)')?.classList.add('active');
+ }
+
+ updateTagToAdd (event) {
+ const tagAlreadyAdded = this.tagsValue.some(tag =>
+ this.normalizeTag(tag) === this.normalizeTag(event.target.value)
+ );
+ this.newTagTarget.dataset.tag = event.target.value.trim();
+ this.newTagTarget.nextSibling.textContent = ` ${this.translationsValue.add_new_tag}: ${event.target.value}`;
+ this.newTagTarget.disabled = !this.newTagTarget.dataset.tag.length || tagAlreadyAdded;
+ }
+
+ deselect (event) {
+ event.preventDefault();
+
+ const clickedTag = event.target.closest('button').dataset.tag;
+ const target = this.searchResultTagsTargets.find((tag) => tag.dataset.tag === clickedTag);
+ target ? target.click() : this.tagsValue = this.tagsValue.filter(tag => tag !== clickedTag);
+ }
+
+ renderTagPills () {
+ return this.tagsValue.map((tag) => {
+ return `
+
+
+ ${tag}
+
+
+
+ `
+ }).join('')
+ }
+}
+
+class SpotlightControllers {
+ connect() {
+ if (typeof Stimulus === "undefined") return
+ Stimulus.register('tag-selector', TagSelectorController);
+ }
+}
+
Spotlight$1.onLoad(() => {
+ new SpotlightControllers().connect();
new UserIndex().connect();
new AdminIndex().connect();
});
diff --git a/app/assets/javascripts/spotlight/spotlight.esm.js.map b/app/assets/javascripts/spotlight/spotlight.esm.js.map
index b858d0f80..9d620f5d9 100644
--- a/app/assets/javascripts/spotlight/spotlight.esm.js.map
+++ b/app/assets/javascripts/spotlight/spotlight.esm.js.map
@@ -1 +1 @@
-{"version":3,"file":"spotlight.esm.js","sources":["../../../../vendor/assets/javascripts/tiny-slider.js","../../../javascript/spotlight/user/browse_group_categories.js","../../../javascript/spotlight/user/carousel.js","../../../javascript/spotlight/user/clear_form_button.js","../../../javascript/spotlight/user/report_a_problem.js","../../../javascript/spotlight/user/zpr_links.js","../../../javascript/spotlight/user/index.js","../../../javascript/spotlight/admin/add_another.js","../../../javascript/spotlight/admin/add_new_button.js","../../../javascript/spotlight/admin/blacklight_configuration.js","../../../javascript/spotlight/admin/copy_email_addresses.js","../../../javascript/spotlight/admin/iiif.js","../../../javascript/spotlight/admin/add_image_selector.js","../../../javascript/spotlight/core.js","../../../javascript/spotlight/admin/crop.js","../../../javascript/spotlight/admin/croppable_modal.js","../../../javascript/spotlight/admin/croppable.js","../../../javascript/spotlight/admin/edit_in_place.js","../../../javascript/spotlight/admin/exhibit_tag_autocomplete.js","../../../../vendor/assets/javascripts/parameterize.js","../../../javascript/spotlight/admin/exhibits.js","../../../javascript/spotlight/admin/form_observer.js","../../../javascript/spotlight/admin/locks.js","../../../javascript/spotlight/admin/multi_image_selector.js","../../../javascript/spotlight/admin/pages.js","../../../javascript/spotlight/admin/progress_monitor.js","../../../javascript/spotlight/admin/readonly_checkbox.js","../../../javascript/spotlight/admin/search_typeahead.js","../../../javascript/spotlight/admin/select_related_input.js","../../../javascript/spotlight/admin/spotlight_nestable.js","../../../javascript/spotlight/admin/tabs.js","../../../javascript/spotlight/admin/translation_progress.js","../../../javascript/spotlight/admin/visibility_toggle.js","../../../javascript/spotlight/admin/users.js","../../../javascript/spotlight/admin/block_mixins/autocompleteable.js","../../../javascript/spotlight/admin/block_mixins/formable.js","../../../javascript/spotlight/admin/block_mixins/plustextable.js","../../../javascript/spotlight/admin/blocks/block.js","../../../javascript/spotlight/admin/blocks/resources_block.js","../../../javascript/spotlight/admin/blocks/browse_block.js","../../../javascript/spotlight/admin/blocks/browse_group_categories_block.js","../../../javascript/spotlight/admin/blocks/iframe_block.js","../../../javascript/spotlight/admin/blocks/link_to_search_block.js","../../../javascript/spotlight/admin/blocks/oembed_block.js","../../../javascript/spotlight/admin/blocks/pages_block.js","../../../javascript/spotlight/admin/blocks/rule_block.js","../../../javascript/spotlight/admin/blocks/search_result_block.js","../../../javascript/spotlight/admin/blocks/solr_documents_base_block.js","../../../javascript/spotlight/admin/blocks/solr_documents_block.js","../../../javascript/spotlight/admin/blocks/solr_documents_carousel_block.js","../../../javascript/spotlight/admin/blocks/solr_documents_embed_block.js","../../../javascript/spotlight/admin/blocks/solr_documents_features_block.js","../../../javascript/spotlight/admin/blocks/solr_documents_grid_block.js","../../../javascript/spotlight/admin/blocks/uploaded_items_block.js","../../../javascript/spotlight/admin/sir-trevor/block_controls.js","../../../javascript/spotlight/admin/sir-trevor/block_limits.js","../../../javascript/spotlight/admin/sir-trevor/locales.js","../../../javascript/spotlight/admin/index.js","../../../javascript/spotlight/index.js"],"sourcesContent":["// Includes an unreleased RTL support pull request: https://github.com/ganlanyuan/tiny-slider/pull/658\n// Includes \"export default tns\" at the end of the file for spotlight/user/browse_group_categories.js\nvar tns = (function (){\nvar win = window;\n\nvar raf = win.requestAnimationFrame\n || win.webkitRequestAnimationFrame\n || win.mozRequestAnimationFrame\n || win.msRequestAnimationFrame\n || function(cb) { return setTimeout(cb, 16); };\n\nvar win$1 = window;\n\nvar caf = win$1.cancelAnimationFrame\n || win$1.mozCancelAnimationFrame\n || function(id){ clearTimeout(id); };\n\nfunction extend() {\n var obj, name, copy,\n target = arguments[0] || {},\n i = 1,\n length = arguments.length;\n\n for (; i < length; i++) {\n if ((obj = arguments[i]) !== null) {\n for (name in obj) {\n copy = obj[name];\n\n if (target === copy) {\n continue;\n } else if (copy !== undefined) {\n target[name] = copy;\n }\n }\n }\n }\n return target;\n}\n\nfunction checkStorageValue (value) {\n return ['true', 'false'].indexOf(value) >= 0 ? JSON.parse(value) : value;\n}\n\nfunction setLocalStorage(storage, key, value, access) {\n if (access) {\n try { storage.setItem(key, value); } catch (e) {}\n }\n return value;\n}\n\nfunction getSlideId() {\n var id = window.tnsId;\n window.tnsId = !id ? 1 : id + 1;\n \n return 'tns' + window.tnsId;\n}\n\nfunction getBody () {\n var doc = document,\n body = doc.body;\n\n if (!body) {\n body = doc.createElement('body');\n body.fake = true;\n }\n\n return body;\n}\n\nvar docElement = document.documentElement;\n\nfunction setFakeBody (body) {\n var docOverflow = '';\n if (body.fake) {\n docOverflow = docElement.style.overflow;\n //avoid crashing IE8, if background image is used\n body.style.background = '';\n //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible\n body.style.overflow = docElement.style.overflow = 'hidden';\n docElement.appendChild(body);\n }\n\n return docOverflow;\n}\n\nfunction resetFakeBody (body, docOverflow) {\n if (body.fake) {\n body.remove();\n docElement.style.overflow = docOverflow;\n // Trigger layout so kinetic scrolling isn't disabled in iOS6+\n // eslint-disable-next-line\n docElement.offsetHeight;\n }\n}\n\n// get css-calc \n\nfunction calc() {\n var doc = document, \n body = getBody(),\n docOverflow = setFakeBody(body),\n div = doc.createElement('div'), \n result = false;\n\n body.appendChild(div);\n try {\n var str = '(10px * 10)',\n vals = ['calc' + str, '-moz-calc' + str, '-webkit-calc' + str],\n val;\n for (var i = 0; i < 3; i++) {\n val = vals[i];\n div.style.width = val;\n if (div.offsetWidth === 100) { \n result = val.replace(str, ''); \n break;\n }\n }\n } catch (e) {}\n \n body.fake ? resetFakeBody(body, docOverflow) : div.remove();\n\n return result;\n}\n\n// get subpixel support value\n\nfunction percentageLayout() {\n // check subpixel layout supporting\n var doc = document,\n body = getBody(),\n docOverflow = setFakeBody(body),\n wrapper = doc.createElement('div'),\n outer = doc.createElement('div'),\n str = '',\n count = 70,\n perPage = 3,\n supported = false;\n\n wrapper.className = \"tns-t-subp2\";\n outer.className = \"tns-t-ct\";\n\n for (var i = 0; i < count; i++) {\n str += '';\n }\n\n outer.innerHTML = str;\n wrapper.appendChild(outer);\n body.appendChild(wrapper);\n\n supported = Math.abs(wrapper.getBoundingClientRect().left - outer.children[count - perPage].getBoundingClientRect().left) < 2;\n\n body.fake ? resetFakeBody(body, docOverflow) : wrapper.remove();\n\n return supported;\n}\n\nfunction mediaquerySupport () {\n if (window.matchMedia || window.msMatchMedia) {\n return true;\n }\n \n var doc = document,\n body = getBody(),\n docOverflow = setFakeBody(body),\n div = doc.createElement('div'),\n style = doc.createElement('style'),\n rule = '@media all and (min-width:1px){.tns-mq-test{position:absolute}}',\n position;\n\n style.type = 'text/css';\n div.className = 'tns-mq-test';\n\n body.appendChild(style);\n body.appendChild(div);\n\n if (style.styleSheet) {\n style.styleSheet.cssText = rule;\n } else {\n style.appendChild(doc.createTextNode(rule));\n }\n\n position = window.getComputedStyle ? window.getComputedStyle(div).position : div.currentStyle['position'];\n\n body.fake ? resetFakeBody(body, docOverflow) : div.remove();\n\n return position === \"absolute\";\n}\n\n// create and append style sheet\nfunction createStyleSheet (media, nonce) {\n // Create the