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.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -12
  3. data/Rakefile +8 -1
  4. data/app/assets/javascripts/spotlight/application.js +0 -1
  5. data/app/assets/javascripts/spotlight/spotlight.esm.js +3620 -3847
  6. data/app/assets/javascripts/spotlight/spotlight.esm.js.map +1 -1
  7. data/app/assets/javascripts/spotlight/spotlight.js +3620 -3852
  8. data/app/assets/javascripts/spotlight/spotlight.js.map +1 -1
  9. data/app/assets/stylesheets/spotlight/_accessibility.scss +0 -9
  10. data/app/assets/stylesheets/spotlight/_autocomplete.scss +49 -0
  11. data/app/assets/stylesheets/spotlight/_blacklight_configuration.scss +0 -1
  12. data/app/assets/stylesheets/spotlight/_blacklight_overrides.scss +1 -6
  13. data/app/assets/stylesheets/spotlight/_browse.scss +2 -2
  14. data/app/assets/stylesheets/spotlight/_catalog.scss +40 -41
  15. data/app/assets/stylesheets/spotlight/_curation.scss +1 -1
  16. data/app/assets/stylesheets/spotlight/_exhibit_admin.scss +7 -0
  17. data/app/assets/stylesheets/spotlight/_exhibits_index.scss +8 -5
  18. data/app/assets/stylesheets/spotlight/_featured_browse_categories_block.scss +3 -3
  19. data/app/assets/stylesheets/spotlight/_header.scss +13 -0
  20. data/app/assets/stylesheets/spotlight/_mixins.scss +3 -4
  21. data/app/assets/stylesheets/spotlight/_nestable.scss +2 -12
  22. data/app/assets/stylesheets/spotlight/_pages.scss +11 -9
  23. data/app/assets/stylesheets/spotlight/_report_a_problem.scss +1 -3
  24. data/app/assets/stylesheets/spotlight/_sir-trevor_overrides.scss +2 -2
  25. data/app/assets/stylesheets/spotlight/_spotlight.scss +2 -1
  26. data/app/assets/stylesheets/spotlight/_tag_selector.scss +34 -0
  27. data/app/assets/stylesheets/spotlight/_variables.scss +0 -8
  28. data/app/components/spotlight/analytics/dashboard_component.html.erb +3 -3
  29. data/app/components/spotlight/breadcrumbs_component.html.erb +13 -19
  30. data/app/components/spotlight/bulk_action_component.rb +1 -1
  31. data/app/components/spotlight/document_component.rb +1 -1
  32. data/app/components/spotlight/save_search_component.rb +1 -1
  33. data/app/components/spotlight/select_image_component.html.erb +17 -0
  34. data/app/components/spotlight/select_image_component.rb +24 -0
  35. data/app/components/spotlight/skip_link_component.rb +16 -0
  36. data/app/components/spotlight/tag_selector_component.html.erb +40 -0
  37. data/app/components/spotlight/tag_selector_component.rb +41 -0
  38. data/app/components/spotlight/tag_selector_component.yml +6 -0
  39. data/app/components/spotlight/title_component.html.erb +8 -0
  40. data/app/components/spotlight/title_component.rb +22 -0
  41. data/app/controllers/spotlight/accessibility_controller.rb +2 -2
  42. data/app/controllers/spotlight/catalog_controller.rb +7 -2
  43. data/app/controllers/spotlight/contact_email_controller.rb +8 -2
  44. data/app/controllers/spotlight/languages_controller.rb +9 -4
  45. data/app/helpers/spotlight/application_helper.rb +7 -0
  46. data/app/helpers/spotlight/crop_helper.rb +4 -0
  47. data/app/helpers/spotlight/meta_helper.rb +59 -36
  48. data/app/javascript/spotlight/admin/blacklight_configuration.js +1 -1
  49. data/app/javascript/spotlight/admin/block_mixins/autocompleteable.js +70 -34
  50. data/app/javascript/spotlight/admin/blocks/block.js +1 -0
  51. data/app/javascript/spotlight/admin/blocks/browse_block.js +8 -12
  52. data/app/javascript/spotlight/admin/blocks/browse_group_categories_block.js +14 -18
  53. data/app/javascript/spotlight/admin/blocks/pages_block.js +6 -10
  54. data/app/javascript/spotlight/admin/blocks/resources_block.js +33 -15
  55. data/app/javascript/spotlight/admin/blocks/solr_documents_base_block.js +11 -6
  56. data/app/javascript/spotlight/admin/blocks/solr_documents_embed_block.js +1 -0
  57. data/app/javascript/spotlight/admin/blocks/uploaded_items_block.js +4 -3
  58. data/app/javascript/spotlight/admin/copy_email_addresses.js +2 -0
  59. data/app/javascript/spotlight/admin/crop.js +45 -17
  60. data/app/javascript/spotlight/admin/croppable.js +8 -1
  61. data/app/javascript/spotlight/admin/croppable_modal.js +68 -0
  62. data/app/javascript/spotlight/admin/exhibits.js +15 -10
  63. data/app/javascript/spotlight/admin/form_observer.js +1 -1
  64. data/app/javascript/spotlight/admin/index.js +0 -10
  65. data/app/javascript/spotlight/admin/locks.js +15 -5
  66. data/app/javascript/spotlight/admin/pages.js +1 -1
  67. data/app/javascript/spotlight/admin/search_typeahead.js +62 -55
  68. data/app/javascript/spotlight/admin/spotlight_nestable.js +173 -50
  69. data/app/javascript/spotlight/admin/visibility_toggle.js +1 -11
  70. data/app/javascript/spotlight/controllers/index.js +8 -0
  71. data/app/javascript/spotlight/controllers/tag_selector_controller.js +203 -0
  72. data/app/javascript/spotlight/core.js +4 -6
  73. data/app/javascript/spotlight/index.js +2 -0
  74. data/app/javascript/spotlight/user/browse_group_categories.js +2 -0
  75. data/app/javascript/spotlight/user/carousel.js +3 -1
  76. data/app/javascript/spotlight/user/index.js +0 -2
  77. data/app/models/sir_trevor_rails/block.rb +5 -4
  78. data/app/models/sir_trevor_rails/blocks/solr_documents_block.rb +1 -1
  79. data/app/models/sir_trevor_rails/blocks/solr_documents_embed_block.rb +1 -1
  80. data/app/models/sir_trevor_rails/blocks/uploaded_items_block.rb +1 -1
  81. data/app/models/spotlight/page_configurations.rb +1 -1
  82. data/app/views/catalog/_add_tags.html.erb +2 -2
  83. data/app/views/catalog/_change_visibility.html.erb +1 -1
  84. data/app/views/catalog/_remove_tags.html.erb +2 -2
  85. data/app/views/layouts/spotlight/base.html.erb +24 -13
  86. data/app/views/layouts/spotlight/spotlight.html.erb +6 -6
  87. data/app/views/shared/_masthead.html.erb +4 -31
  88. data/app/views/shared/_site_sidebar.html.erb +1 -1
  89. data/app/views/shared/_user_util_links.html.erb +3 -1
  90. data/app/views/spotlight/accessibility/alt_text.html.erb +2 -2
  91. data/app/views/spotlight/admin_users/index.html.erb +3 -3
  92. data/app/views/spotlight/appearances/edit.html.erb +1 -1
  93. data/app/views/spotlight/browse/_search_box.html.erb +8 -8
  94. data/app/views/spotlight/browse/show.html.erb +1 -1
  95. data/app/views/spotlight/bulk_updates/_download.html.erb +1 -1
  96. data/app/views/spotlight/bulk_updates/_upload.html.erb +1 -1
  97. data/app/views/spotlight/catalog/_admin_header.html.erb +1 -1
  98. data/app/views/spotlight/catalog/_edit_default.html.erb +2 -1
  99. data/app/views/spotlight/catalog/select_image.html.erb +1 -0
  100. data/app/views/spotlight/contacts/_form.html.erb +1 -1
  101. data/app/views/spotlight/exhibits/_contact.html.erb +5 -6
  102. data/app/views/spotlight/exhibits/_delete.html.erb +1 -1
  103. data/app/views/spotlight/exhibits/_languages.html.erb +3 -2
  104. data/app/views/spotlight/featured_images/_form.html.erb +6 -2
  105. data/app/views/spotlight/featured_images/_upload_form.html.erb +1 -1
  106. data/app/views/spotlight/metadata_configurations/_metadata_field.html.erb +1 -1
  107. data/app/views/spotlight/metadata_configurations/edit.html.erb +6 -6
  108. data/app/views/spotlight/pages/show.html.erb +1 -1
  109. data/app/views/spotlight/resources/csv_upload/_form.html.erb +1 -1
  110. data/app/views/spotlight/resources/upload/_form.html.erb +1 -1
  111. data/app/views/spotlight/roles/index.html.erb +1 -1
  112. data/app/views/spotlight/searches/_form.html.erb +1 -1
  113. data/app/views/spotlight/shared/_dd3_item.html.erb +1 -1
  114. data/app/views/spotlight/sir_trevor/blocks/_browse_group_categories_block.html.erb +1 -1
  115. data/app/views/spotlight/sir_trevor/blocks/_solr_documents_block.html.erb +1 -1
  116. data/app/views/spotlight/sir_trevor/blocks/_solr_documents_carousel_block.html.erb +1 -1
  117. data/app/views/spotlight/sir_trevor/blocks/_uploaded_items_block.html.erb +1 -1
  118. data/app/views/spotlight/tags/index.html.erb +2 -3
  119. data/app/views/spotlight/translations/_import.html.erb +2 -2
  120. data/config/importmap.rb +5 -0
  121. data/config/locales/spotlight.en.yml +2 -0
  122. data/config/routes.rb +5 -3
  123. data/lib/generators/spotlight/assets/generator_common_utilities.rb +36 -0
  124. data/lib/generators/spotlight/assets/importmap_generator.rb +87 -0
  125. data/lib/generators/spotlight/assets/propshaft_generator.rb +96 -0
  126. data/lib/generators/spotlight/assets_generator.rb +22 -0
  127. data/lib/generators/spotlight/install_generator.rb +8 -36
  128. data/lib/generators/spotlight/scaffold_resource_generator.rb +1 -1
  129. data/lib/generators/spotlight/templates/assets/spotlight.scss +6 -0
  130. data/lib/generators/spotlight/templates/javascript/jquery-shim.js +1 -0
  131. data/lib/spotlight/engine.rb +7 -6
  132. data/lib/spotlight/version.rb +1 -1
  133. data/spec/support/features/capybara_wait_metadata_helper.rb +13 -0
  134. data/spec/support/features/test_features_helpers.rb +16 -30
  135. data/vendor/assets/javascripts/tiny-slider.js +3 -0
  136. metadata +36 -87
  137. data/app/javascript/spotlight/admin/checkbox_submit.js +0 -75
  138. data/app/javascript/spotlight/admin/exhibit_tag_autocomplete.js +0 -39
  139. data/app/javascript/spotlight/user/report_a_problem.js +0 -30
  140. data/app/views/spotlight/browse/_tophat.html.erb +0 -1
  141. data/app/views/spotlight/catalog/_tophat_default.html.erb +0 -1
  142. data/app/views/spotlight/home_pages/_tophat.html.erb +0 -2
  143. data/app/views/spotlight/pages/_tophat.html.erb +0 -1
  144. data/lib/generators/spotlight/templates/spotlight.js +0 -1
  145. data/lib/generators/spotlight/templates/spotlight.scss +0 -5
  146. data/spec/support/features/capybara_default_max_wait_metadata_helper.rb +0 -20
  147. data/vendor/assets/javascripts/bootstrap-tagsinput.js +0 -530
  148. data/vendor/assets/javascripts/jquery.serializejson.js +0 -234
  149. data/vendor/assets/javascripts/nestable.js +0 -645
  150. data/vendor/assets/javascripts/sir-trevor.js +0 -23508
  151. data/vendor/assets/javascripts/typeahead.bundle.min.js +0 -7
  152. data/vendor/assets/stylesheets/bootstrap-tagsinput.css +0 -46
@@ -8,30 +8,26 @@ SirTrevor.Blocks.BrowseGroupCategories = (function(){
8
8
  return Core.Block.Resources.extend({
9
9
  type: "browse_group_categories",
10
10
  icon_name: "browse",
11
- bloodhoundOptions: function() {
12
- var that = this;
13
- return {
14
- prefetch: {
15
- url: this.autocomplete_url(),
16
- ttl: 0,
17
- filter: function(response) {
18
- // Let the dom know that the response has been returned
19
- $(that.inner).attr('data-browse-groups-fetched', true);
20
- return response;
21
- }
22
- }
23
- };
24
- },
25
11
 
26
12
  autocomplete_control: function() {
27
- return `<input type="text" class="st-input-string form-control item-input-field" data-twitter-typeahead="true" placeholder="${i18n.t("blocks:browse_group_categories:autocomplete")}"/>`
13
+ const autocompleteID = this.blockID + '-autocomplete';
14
+ return `<auto-complete src="${this.autocomplete_url()}" for="${autocompleteID}-popup" fetch-on-empty>
15
+ <input type="text" name="${autocompleteID}" placeholder="${i18n.t("blocks:browse_group_categories:autocomplete")}" data-default-typeahead>
16
+ <ul id="${autocompleteID}-popup"></ul>
17
+ <div id="${autocompleteID}-popup-feedback" class="sr-only visually-hidden"></div>
18
+ </auto-complete>`
28
19
  },
29
20
  autocomplete_template: function(obj) {
30
21
  return `<div class="autocomplete-item${!obj.published ? ' blacklight-private' : ''}">
31
- <span class="autocomplete-title">${obj.title}</span><br/></div>`
22
+ <span class="autocomplete-title">${this.highlight(obj.title)}</span><br/></div>`
32
23
  },
33
24
 
34
- autocomplete_url: function() { return $(this.inner).closest('form[data-autocomplete-exhibit-browse-groups-path]').data('autocomplete-exhibit-browse-groups-path').replace("%25QUERY", "%QUERY"); },
25
+ autocomplete_url: function() {
26
+ return document.getElementById(this.instanceID).closest('form[data-autocomplete-exhibit-browse-groups-path]').dataset.autocompleteExhibitBrowseGroupsPath;
27
+ },
28
+ autocomplete_fetch: function(url) {
29
+ return this.fetchOnceAndFilterLocalResults(url);
30
+ },
35
31
  _itemPanel: function(data) {
36
32
  var index = "item_" + this.globalIndex++;
37
33
  var checked;
@@ -42,7 +38,7 @@ SirTrevor.Blocks.BrowseGroupCategories = (function(){
42
38
  }
43
39
  var resource_id = data.slug || data.id;
44
40
  var markup = `
45
- <li class="field form-inline dd-item dd3-item" data-resource-id="${resource_id}" data-id="${index}" id="${this.formId("item_" + data.id)}">
41
+ <li class="field dd-item dd3-item" data-resource-id="${resource_id}" data-id="${index}" id="${this.formId(index)}">
46
42
  <input type="hidden" name="item[${index}][id]" value="${resource_id}" />
47
43
  <input type="hidden" name="item[${index}][title]" value="${data.title}" />
48
44
  <input data-property="weight" type="hidden" name="item[${index}][weight]" value="${data.weight}" />
@@ -7,20 +7,16 @@ SirTrevor.Blocks.FeaturedPages = (function(){
7
7
 
8
8
  icon_name: "pages",
9
9
 
10
- autocomplete_url: function() { return $(this.inner).closest('form[data-autocomplete-exhibit-pages-path]').data('autocomplete-exhibit-pages-path').replace("%25QUERY", "%QUERY"); },
10
+ autocomplete_url: function() { return document.getElementById(this.instanceID).closest('form[data-autocomplete-exhibit-pages-path]').dataset.autocompleteExhibitPagesPath; },
11
+ autocomplete_fetch: function(url) {
12
+ return this.fetchOnceAndFilterLocalResults(url);
13
+ },
11
14
  autocomplete_template: function(obj) {
15
+ const description = obj.description ? `<small>&nbsp;&nbsp;${obj.description}</small>` : '';
12
16
  const thumbnail = obj.thumbnail_image_url ? `<div class="document-thumbnail"><img class="img-thumbnail" src="${obj.thumbnail_image_url}" /></div>` : ''
13
17
  return `<div class="autocomplete-item${!obj.published ? ' blacklight-private' : ''}">${thumbnail}
14
- <span class="autocomplete-title">${obj.title}</span><br/><small>&nbsp;&nbsp;${obj.description}</small></div>`
18
+ <span class="autocomplete-title">${this.highlight(obj.title)}</span><br/>${description}</div>`
15
19
  },
16
- bloodhoundOptions: function() {
17
- return {
18
- prefetch: {
19
- url: this.autocomplete_url(),
20
- ttl: 0
21
- }
22
- };
23
- }
24
20
  });
25
21
 
26
22
  })();
@@ -8,11 +8,12 @@ Core.Block.Resources = (function(){
8
8
  formable: true,
9
9
  autocompleteable: true,
10
10
  show_heading: true,
11
+ show_image_selection: true,
11
12
  title: function() { return i18n.t("blocks:" + this.type + ":title"); },
12
13
  description: function() { return i18n.t("blocks:" + this.type + ":description"); },
13
14
  alt_text_guidelines: function() {
14
15
  if (this.showAltText()) {
15
- return i18n.t("blocks:alt_text_guidelines:intro");
16
+ return i18n.t("blocks:alt_text_guidelines:intro");
16
17
  }
17
18
  return "";
18
19
  },
@@ -20,7 +21,7 @@ Core.Block.Resources = (function(){
20
21
  if (this.showAltText()) {
21
22
  var link_url = i18n.t("blocks:alt_text_guidelines:link_url");
22
23
  var link_label = i18n.t("blocks:alt_text_guidelines:link_label");
23
- return '<a target="_blank" href="' + link_url + '">' + link_label + '</a>';
24
+ return '<a target="_blank" href="' + link_url + '">' + link_label + '</a>';
24
25
  }
25
26
  return "";
26
27
  },
@@ -59,7 +60,16 @@ Core.Block.Resources = (function(){
59
60
  .map(word => word.charAt(0).toUpperCase() + word.slice(1))
60
61
  .join('');
61
62
  },
62
-
63
+ _itemSelectImageLink: function(block_item_id, doc_id, index) {
64
+ // If image selection is not possible for this block, then do not show
65
+ // image selection link
66
+ if (!this.show_image_selection) return ``;
67
+ var url = $('form[data-exhibit-path]').data('exhibit-path') + '/select_image?';
68
+ var markup = `
69
+ <a name="selectimage" href="${url}block_item_id=${block_item_id}&index_id=${index}" data-blacklight-modal="trigger">Select image area</a>
70
+ `;
71
+ return markup;
72
+ },
63
73
  _itemPanel: function(data) {
64
74
  var index = "item_" + this.globalIndex++;
65
75
  var checked;
@@ -69,8 +79,9 @@ Core.Block.Resources = (function(){
69
79
  checked = "";
70
80
  }
71
81
  var resource_id = data.slug || data.id;
82
+ var block_item_id = this.formId(index);
72
83
  var markup = `
73
- <li class="field form-inline dd-item dd3-item" data-resource-id="${resource_id}" data-id="${index}" id="${this.formId("item_" + data.id)}">
84
+ <li class="field dd-item dd3-item" data-cropper="select_image_${block_item_id}" data-resource-id="${resource_id}" data-id="${index}" id="${block_item_id}" data-input-prefix="item[${index}]">
74
85
  <input type="hidden" name="item[${index}][id]" value="${resource_id}" />
75
86
  <input type="hidden" name="item[${index}][title]" value="${data.title}" />
76
87
  ${this._itemPanelIiifFields(index, data)}
@@ -79,13 +90,20 @@ Core.Block.Resources = (function(){
79
90
  <div class="dd-handle dd3-handle">${i18n.t("blocks:resources:panel:drag")}</div>
80
91
  <div class="card-header item-grid">
81
92
  <div class="d-flex">
82
- <div class="checkbox">
83
- <input name="item[${index}][display]" type="hidden" value="false" />
84
- <input name="item[${index}][display]" id="${this.formId(this.display_checkbox + '_' + data.id)}" type="checkbox" ${checked} class="item-grid-checkbox" value="true" />
85
- <label class="sr-only visually-hidden" for="${this.formId(this.display_checkbox + '_' + data.id)}">${i18n.t("blocks:resources:panel:display")}</label>
86
- </div>
87
- <div class="pic">
88
- <img class="img-thumbnail" src="${(data.thumbnail_image_url || ((data.iiif_tilesource || "").replace("/info.json", "/full/!100,100/0/default.jpg")))}" />
93
+ <div class="d-inline-block">
94
+ <div class="d-flex">
95
+ <div class="checkbox">
96
+ <input name="item[${index}][display]" type="hidden" value="false" />
97
+ <input name="item[${index}][display]" id="${this.formId(this.display_checkbox + '_' + data.id)}" type="checkbox" ${checked} class="item-grid-checkbox" value="true" />
98
+ <label class="sr-only visually-hidden" for="${this.formId(this.display_checkbox + '_' + data.id)}">${i18n.t("blocks:resources:panel:display")}</label>
99
+ </div>
100
+ <div class="pic">
101
+ <img class="img-thumbnail" src="${(data.thumbnail_image_url || ((data.iiif_tilesource || "").replace("/info.json", "/full/!100,100/0/default.jpg")))}" />
102
+ </div>
103
+ </div>
104
+ <div class="d-inline-block">
105
+ ${this._itemSelectImageLink(block_item_id,data.id, index)}
106
+ </div>
89
107
  </div>
90
108
  <div class="main">
91
109
  <div class="title card-title">${data.title}</div>
@@ -118,7 +136,7 @@ Core.Block.Resources = (function(){
118
136
  },
119
137
 
120
138
  afterPanelRender: function(data, panel) {
121
-
139
+
122
140
  },
123
141
 
124
142
  afterPanelDelete: function() {
@@ -183,7 +201,7 @@ Core.Block.Resources = (function(){
183
201
  <div class="me-2 mr-2">
184
202
  <label class="col-form-label pb-0 pt-1" for="${this.formId(this.alt_text_textarea + '_' + data.id)}">${i18n.t("blocks:resources:alt_text:alternative_text")}</label>
185
203
  <div class="form-check mb-1 justify-content-end">
186
- <input class="form-check-input" type="checkbox"
204
+ <input class="form-check-input" type="checkbox"
187
205
  id="${this.formId(this.decorative_checkbox + '_' + data.id)}" name="item[${index}][decorative]" ${isDecorative ? 'checked' : ''}>
188
206
  <label class="form-check-label" for="${this.formId(this.decorative_checkbox + '_' + data.id)}">${i18n.t("blocks:resources:alt_text:decorative")}</label>
189
207
  </div>
@@ -223,7 +241,6 @@ Core.Block.Resources = (function(){
223
241
 
224
242
  onBlockRender: function() {
225
243
  SpotlightNestable.init($('[data-behavior="nestable"]', this.inner));
226
-
227
244
  $('[data-input-select-target]', this.inner).selectRelatedInput();
228
245
  },
229
246
 
@@ -232,7 +249,8 @@ Core.Block.Resources = (function(){
232
249
  $.each(Object.keys(data.item || {}).map(function(k) { return data.item[k]}).sort(function(a,b) { return a.weight - b.weight; }), function(index, item) {
233
250
  context.createItemPanel(item);
234
251
  });
235
- },
252
+
253
+ }
236
254
  });
237
255
 
238
256
  })();
@@ -5,11 +5,11 @@ SirTrevor.Blocks.SolrDocumentsBase = (function(){
5
5
 
6
6
  return Core.Block.Resources.extend({
7
7
  plustextable: true,
8
- autocomplete_url: function() { return this.$instance().closest('form[data-autocomplete-exhibit-catalog-path]').data('autocomplete-exhibit-catalog-path').replace("%25QUERY", "%QUERY"); },
8
+ autocomplete_url: function() { return this.$instance().closest('form[data-autocomplete-exhibit-catalog-path]').data('autocomplete-exhibit-catalog-path') },
9
9
  autocomplete_template: function(obj) {
10
10
  const thumbnail = obj.thumbnail ? `<div class="document-thumbnail"><img class="img-thumbnail" src="${obj.thumbnail}" /></div>` : ''
11
11
  return `<div class="autocomplete-item${obj.private ? ' blacklight-private' : ''}">${thumbnail}
12
- <span class="autocomplete-title">${obj.title}</span><br/><small>&nbsp;&nbsp;${obj.description}</small></div>`
12
+ <span class="autocomplete-title">${this.highlight(obj.title)}</span><br/><small>&nbsp;&nbsp;${this.highlight(obj.description)}</small></div>`
13
13
  },
14
14
  transform_autocomplete_results: function(response) {
15
15
  return $.map(response['docs'], function(doc) {
@@ -50,16 +50,21 @@ SirTrevor.Blocks.SolrDocumentsBase = (function(){
50
50
 
51
51
  // Sets the first version of the IIIF information from autocomplete data.
52
52
  _itemPanelIiifFields: function(index, autocomplete_data) {
53
- return [
54
- // '<input type="hidden" name="item[' + index + '][iiif_region]" value="' + (data.iiif_region) + '"/>',
55
- // for legacy compatiblity:
53
+ var iiifFields = [
56
54
  '<input type="hidden" name="item[' + index + '][thumbnail_image_url]" value="' + (autocomplete_data.thumbnail_image_url || autocomplete_data.thumbnail || "") + '"/>',
57
55
  '<input type="hidden" name="item[' + index + '][full_image_url]" value="' + (autocomplete_data.full_image_url || autocomplete_data.thumbnail_image_url || autocomplete_data.thumbnail || "") + '"/>',
58
56
  '<input type="hidden" name="item[' + index + '][iiif_tilesource]" value="' + (autocomplete_data.iiif_tilesource) + '"/>',
59
57
  '<input type="hidden" name="item[' + index + '][iiif_manifest_url]" value="' + (autocomplete_data.iiif_manifest_url) + '"/>',
60
58
  '<input type="hidden" name="item[' + index + '][iiif_canvas_id]" value="' + (autocomplete_data.iiif_canvas_id) + '"/>',
61
59
  '<input type="hidden" name="item[' + index + '][iiif_image_id]" value="' + (autocomplete_data.iiif_image_id) + '"/>',
62
- ].join("\n");
60
+ ];
61
+
62
+ // The region input is required for widgets that enable image cropping but not otherwise
63
+ if(this.show_image_selection) {
64
+ iiifFields.push('<input type="hidden" name="item[' + index + '][iiif_region]" value="' + (autocomplete_data.iiif_region || "") + '"/>');
65
+ }
66
+
67
+ return iiifFields.join("\n");
63
68
  },
64
69
  // Overwrites the hidden inputs from _itemPanelIiifFields with data from the
65
70
  // manifest. Called by afterPanelRender - the manifest_data here is built
@@ -5,6 +5,7 @@ SirTrevor.Blocks.SolrDocumentsEmbed = (function(){
5
5
  return SirTrevor.Blocks.SolrDocumentsBase.extend({
6
6
  type: "solr_documents_embed",
7
7
  icon_name: "item_embed",
8
+ show_image_selection: false,
8
9
 
9
10
  item_options: function() { return "" },
10
11
 
@@ -6,7 +6,8 @@ SirTrevor.Blocks.UploadedItems = (function(){
6
6
  plustextable: true,
7
7
  uploadable: true,
8
8
  autocompleteable: false,
9
-
9
+ show_image_selection: false,
10
+
10
11
  id_key: 'file',
11
12
 
12
13
  type: 'uploaded_items',
@@ -70,7 +71,7 @@ SirTrevor.Blocks.UploadedItems = (function(){
70
71
  var dataUrl = data.url || data.file.url;
71
72
 
72
73
  var markup = `
73
- <li class="field form-inline dd-item dd3-item" data-id="${index}" id="${this.formId("item_" + dataId)}">
74
+ <li class="field dd-item dd3-item" data-id="${index}" id="${this.formId(index)}">
74
75
  <input type="hidden" name="item[${index}][id]" value="${dataId}" />
75
76
  <input type="hidden" name="item[${index}][title]" value="${dataTitle}" />
76
77
  <input type="hidden" name="item[${index}][url]" data-item-grid-thumbnail="true" value="${dataUrl}"/>
@@ -152,7 +153,7 @@ SirTrevor.Blocks.UploadedItems = (function(){
152
153
  <div class="col-lg-3 ps-md-2 pl-md-2">
153
154
  <label class="col-form-label text-nowrap pb-0 pt-1 justify-content-md-start justify-content-lg-end d-flex" for="${this.formId(this.alt_text_textarea + '_' + data.id)}">${i18n.t("blocks:resources:alt_text:alternative_text")}</label>
154
155
  <div class="form-check d-flex justify-content-md-start justify-content-lg-end">
155
- <input class="form-check-input" type="checkbox"
156
+ <input class="form-check-input" type="checkbox"
156
157
  id="${this.formId(this.decorative_checkbox + '_' + data.id)}" name="item[${index}][decorative]" ${isDecorative ? 'checked' : ''}>
157
158
  <label class="form-check-label" for="${this.formId(this.decorative_checkbox + '_' + data.id)}">${i18n.t("blocks:resources:alt_text:decorative")}</label>
158
159
  </div>
@@ -1,3 +1,5 @@
1
+ import Clipboard from 'clipboard';
2
+
1
3
  export default class {
2
4
  connect() {
3
5
  new Clipboard('.copy-email-addresses');
@@ -2,22 +2,37 @@ import { addImageSelector } from 'spotlight/admin/add_image_selector'
2
2
  import Core from 'spotlight/core'
3
3
 
4
4
  export default class Crop {
5
- constructor(cropArea) {
5
+ constructor(cropArea, preserveAspectRatio = true) {
6
6
  this.cropArea = cropArea;
7
7
  this.cropArea.data('iiifCropper', this);
8
+ // This element will also have the IIIF input elements contained
9
+ // There may be multiple elements with data-cropper attributes, but
10
+ // there should only one element with this data-cropper attribute value.
8
11
  this.cropSelector = '[data-cropper="' + cropArea.data('cropperKey') + '"]';
9
12
  this.cropTool = $(this.cropSelector);
10
- this.formPrefix = this.cropTool.data('form-prefix');
11
- this.iiifUrlField = $('#' + this.formPrefix + '_iiif_tilesource');
12
- this.iiifRegionField = $('#' + this.formPrefix + '_iiif_region');
13
- this.iiifManifestField = $('#' + this.formPrefix + '_iiif_manifest_url');
14
- this.iiifCanvasField = $('#' + this.formPrefix + '_iiif_canvas_id');
15
- this.iiifImageField = $('#' + this.formPrefix + '_iiif_image_id');
16
-
13
+ // Exhibit and masthead cropping requires the ratio between image width and height
14
+ // to be consistent, whereas item widget cropping allows any combination of
15
+ // image width and height.
16
+ this.preserveAspectRatio = preserveAspectRatio;
17
+ // Get the IIIF input elements used to store/reference IIIF information
18
+ this.inputPrefix = this.cropTool.data('input-prefix');
19
+ this.iiifUrlField = this.iiifInputElement(this.inputPrefix, 'iiif_tilesource', this.cropTool);
20
+ this.iiifRegionField = this.iiifInputElement(this.inputPrefix, 'iiif_region', this.cropTool);
21
+ this.iiifManifestField = this.iiifInputElement(this.inputPrefix, 'iiif_manifest_url', this.cropTool);
22
+ this.iiifCanvasField = this.iiifInputElement(this.inputPrefix, 'iiif_canvas_id', this.cropTool);
23
+ this.iiifImageField = this.iiifInputElement(this.inputPrefix, 'iiif_image_id', this.cropTool);
24
+ // Get the closest form element
17
25
  this.form = cropArea.closest('form');
18
26
  this.tileSource = null;
19
27
  }
20
28
 
29
+ // Return the iiif input element based on the fieldname.
30
+ // Multiple input fields with the same name on the page may be related
31
+ // to a cropper. We thus need to pass in a parent element.
32
+ iiifInputElement(inputPrefix, fieldName, inputParentElement) {
33
+ return $('input[name="' + inputPrefix + '[' + fieldName + ']"]', inputParentElement);
34
+ }
35
+
21
36
  // Render the cropper environment and add hooks into the autocomplete and upload forms
22
37
  render() {
23
38
  this.setupAutoCompletes();
@@ -152,15 +167,21 @@ export default class Crop {
152
167
  if (this.cropperMap) {
153
168
  return;
154
169
  }
155
- this.cropperMap = L.map(this.cropArea.attr('id'), {
170
+
171
+ var cropperOptions = {
156
172
  editable: true,
157
173
  center: [0, 0],
158
174
  crs: L.CRS.Simple,
159
- zoom: 0,
160
- editOptions: {
175
+ zoom: 0
176
+ }
177
+
178
+ if(this.preserveAspectRatio) {
179
+ cropperOptions['editOptions'] = {
161
180
  rectangleEditorClass: this.aspectRatioPreservingRectangleEditor(this.aspectRatio())
162
- }
163
- });
181
+ };
182
+ }
183
+
184
+ this.cropperMap = L.map(this.cropArea.attr('id'), cropperOptions);
164
185
  this.invalidateMapSizeOnTabToggle();
165
186
  }
166
187
 
@@ -228,9 +249,12 @@ export default class Crop {
228
249
  }
229
250
 
230
251
  var input = $('[data-behavior="autocomplete"]', this.cropTool);
231
- var panel = $(input.data('target-panel'));
232
-
233
- addImageSelector(input, panel, this.iiifManifestField.val(), !this.iiifImageField.val());
252
+
253
+ // Not every page which uses this module has autocomplete linked directly to the cropping tool
254
+ if(input.length) {
255
+ var panel = $(input.data('target-panel'));
256
+ addImageSelector(input, panel, this.iiifManifestField.val(), !this.iiifImageField.val());
257
+ }
234
258
  }
235
259
 
236
260
  invalidateMapSizeOnTabToggle() {
@@ -280,7 +304,11 @@ export default class Crop {
280
304
  }
281
305
 
282
306
  setUploadId(id) {
283
- $('#' + this.formPrefix + "_upload_id").val(id);
307
+ // This input is currently used for exhibit masthead or thumbnail image upload.
308
+ // The name should be sufficient in this case, as we don't use this part of the
309
+ // code for solr document widgets where we enable cropping.
310
+ // If we require more specificity, we can scope this to this.cropTool.
311
+ $('input[name="' + this.inputPrefix + '[upload_id]"]').val(id);
284
312
  }
285
313
 
286
314
  aspectRatioPreservingRectangleEditor(aspect) {
@@ -1,10 +1,17 @@
1
1
  import Crop from 'spotlight/admin/crop';
2
+ import CroppableModal from 'spotlight/admin/croppable_modal';
2
3
 
3
- export default class {
4
+ export default class Croppable {
4
5
  connect() {
6
+ // For exhibit masthead or thumbnail pages, where
7
+ // the div exists on page load
5
8
  $('[data-behavior="iiif-cropper"]').each(function() {
6
9
  var cropElement = $(this)
7
10
  new Crop(cropElement).render()
8
11
  })
12
+
13
+ // In the case of individual document thumbnails, selection
14
+ // of the image is through a modal. Here we attach the event
15
+ new CroppableModal().attachModalHandlers();
9
16
  }
10
17
  }
@@ -0,0 +1,68 @@
1
+ import Crop from 'spotlight/admin/crop';
2
+
3
+ export default class CroppableModal {
4
+
5
+ attachModalHandlers() {
6
+ // Attach handler for when modal first loads, to show the cropper
7
+ this.attachModalLoadBehavior();
8
+ // Attach handler for save by checking if clicking in the modal is on a save button
9
+ this.attachModalSaveHandler();
10
+ }
11
+
12
+ attachModalLoadBehavior() {
13
+ // Listen for event thrown when modal is displayed with content
14
+ document.addEventListener('loaded.blacklight.blacklight-modal', function(e) {
15
+ var dataCropperDiv = $('#blacklight-modal [data-behavior="iiif-cropper"]');
16
+
17
+ if(dataCropperDiv) {
18
+ new Crop(dataCropperDiv, false).render();
19
+ }
20
+ });
21
+ }
22
+
23
+ // Field names are of the format item[item_0][iiif_image_id]
24
+ iiifInputField(itemIndex, fieldName, parentElement) {
25
+ var itemPrefix = 'item[' + itemIndex + ']';
26
+ var selector = 'input[name="' + itemPrefix + '[' + fieldName + ']"]';
27
+ return $(selector, parentElement);
28
+ }
29
+
30
+ attachModalSaveHandler() {
31
+ var context = this;
32
+
33
+ document.addEventListener('show.blacklight.blacklight-modal', function(e) {
34
+ $('#save-cropping-selection').on('click', () => {
35
+ context.saveCroppedRegion();
36
+ });
37
+ });
38
+ }
39
+
40
+ saveCroppedRegion() {
41
+ //On hitting "save changes", we need to copy over the value
42
+ //to the iiif thumbnail url input field as well as the image source itself
43
+ var context = this;
44
+ var dataCropperDiv = $('#blacklight-modal [data-behavior="iiif-cropper"]');
45
+
46
+ if(dataCropperDiv) {
47
+ var dataCropperKey = dataCropperDiv.data("cropper-key");
48
+ var itemIndex = dataCropperDiv.data("index-id");
49
+ // Get the element on the main edit page whose select image link opened up the modal
50
+ var itemElement = $('[data-cropper="' + dataCropperKey + '"]');
51
+ // Get the hidden input field on the main edit page corresponding to this item
52
+ var thumbnailSaveField = context.iiifInputField(itemIndex, 'thumbnail_image_url', itemElement);
53
+ var fullimageSaveField = context.iiifInputField(itemIndex, 'full_image_url', itemElement);
54
+ var iiifTilesource = context.iiifInputField(itemIndex, 'iiif_tilesource', itemElement).val();
55
+ var regionValue = context.iiifInputField(itemIndex, 'iiif_region', itemElement).val();
56
+ // Extract the region string to incorporate into the thumbnail URL
57
+ var urlPrefix = iiifTilesource.substring(0, iiifTilesource.lastIndexOf('/info.json'));
58
+ var thumbnailUrl = urlPrefix + '/' + regionValue + '/!400,400/0/default.jpg';
59
+ // Set the hidden input value to the thumbnail URL
60
+ // Also set the full image - which is used by widgets like carousel or slideshow
61
+ thumbnailSaveField.val(thumbnailUrl);
62
+ fullimageSaveField.val(urlPrefix + '/' + regionValue + '/!800,800/0/default.jpg');
63
+ // Also change img url for thumbnail image
64
+ var itemImage = $('img.img-thumbnail', itemElement);
65
+ itemImage.attr('src', thumbnailUrl);
66
+ }
67
+ }
68
+ }
@@ -41,21 +41,26 @@ export default class {
41
41
  $(inputContainer).insertAfter(contacts.last());
42
42
  });
43
43
 
44
- $('.contact-email-delete').on('ajax:success', function() {
45
- $(this).closest('.contact').fadeOut(250, function() { $(this).remove(); });
46
- });
47
-
48
- $('.contact-email-delete').on('ajax:error', function(event, _xhr, _status, error) {
49
- var errSpan = $(this).closest('.contact').find('.contact-email-delete-error');
50
- errSpan.show();
51
- errSpan.find('.error-msg').first().text(error || event.detail[1]);
52
- });
44
+ if (document.getElementById('another-email')) {
45
+ document.addEventListener('turbo:submit-end', this.contactToDeleteNotFoundHandler);
46
+ }
53
47
 
54
- $('.btn-with-tooltip').tooltip();
48
+ if ($.fn.tooltip) {
49
+ $('.btn-with-tooltip').tooltip();
50
+ }
55
51
 
56
52
  // Put focus in saved search title input when Save this search modal is shown
57
53
  $('#save-modal').on('shown.bs.modal', function () {
58
54
  $('#search_title').focus();
59
55
  });
60
56
  }
57
+
58
+ contactToDeleteNotFoundHandler(e) {
59
+ const contact = e.detail.formSubmission?.delegate?.element?.querySelector('.contact')
60
+ if (contact && e.detail?.fetchResponse?.response?.status === 404) {
61
+ const error = contact.querySelector('.contact-email-delete-error');
62
+ error.style.display = 'block';
63
+ error.querySelector('.error-msg').textContent = 'Not Found';
64
+ }
65
+ }
61
66
  }
@@ -68,7 +68,7 @@ export default class {
68
68
  connect() {
69
69
  // Instantiate the singleton SerializedForm plugin
70
70
  var serializedForm = $.SerializedForm();
71
- $(window).on('beforeunload page:before-change turbolinks:before-visit', function(event) {
71
+ $(window).on('beforeunload page:before-change turbolinks:before-visit turbo:before-visit', function(event) {
72
72
  // Don't handle the same event twice #turbolinks
73
73
  if (event.handled !== true) {
74
74
  if ( serializedForm.observedFormsStatusHasChanged() ) {
@@ -1,18 +1,9 @@
1
- // These scripts are in the vendor directory
2
- import 'nestable'
3
- import 'bootstrap-tagsinput'
4
- import 'jquery.serializejson'
5
- import 'leaflet-iiif'
6
- import 'Leaflet.Editable'
7
- import 'Path.Drag'
8
-
9
1
  import AddAnother from 'spotlight/admin/add_another'
10
2
  import AddNewButton from 'spotlight/admin/add_new_button'
11
3
  import BlacklightConfiguration from 'spotlight/admin/blacklight_configuration'
12
4
  import CopyEmailAddress from 'spotlight/admin/copy_email_addresses'
13
5
  import Croppable from 'spotlight/admin/croppable'
14
6
  import EditInPlace from 'spotlight/admin/edit_in_place'
15
- import ExhibitTagAutocomplete from 'spotlight/admin/exhibit_tag_autocomplete'
16
7
  import Exhibits from 'spotlight/admin/exhibits'
17
8
  import FormObserver from 'spotlight/admin/form_observer'
18
9
  import Locks from 'spotlight/admin/locks'
@@ -62,7 +53,6 @@ export default class {
62
53
  new CopyEmailAddress().connect()
63
54
  new Croppable().connect()
64
55
  new EditInPlace().connect()
65
- new ExhibitTagAutocomplete().connect()
66
56
  new Exhibits().connect()
67
57
  new FormObserver().connect()
68
58
  new Locks().connect()
@@ -1,12 +1,22 @@
1
1
  export default class {
2
2
  delete_lock(el) {
3
- $.ajax({ url: $(el).data('lock'), type: 'POST', data: { _method: "delete" }, async: false});
4
- $(el).removeAttr('data-lock');
3
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
4
+
5
+ fetch(el.dataset.lock, {
6
+ method: 'DELETE',
7
+ headers: {
8
+ 'X-CSRF-Token': csrfToken
9
+ }
10
+ });
11
+
12
+ el.removeAttribute('data-lock');
5
13
  }
6
14
 
7
15
  connect() {
8
- $('[data-lock]').on('click', (e) => {
9
- this.delete_lock(e.target);
10
- })
16
+ document.querySelectorAll('[data-lock]').forEach(element => {
17
+ element.addEventListener('click', (e) => {
18
+ this.delete_lock(e.target);
19
+ });
20
+ });
11
21
  }
12
22
  }
@@ -5,7 +5,7 @@ import Core from 'spotlight/core'
5
5
  export default class {
6
6
  connect(){
7
7
  SirTrevor.setDefaults({
8
- iconUrl: Spotlight.sirTrevorIcon || window.sirTrevorIcon,
8
+ iconUrl: Spotlight.sirTrevorIcon,
9
9
  uploadUrl: $('[data-attachment-endpoint]').data('attachment-endpoint'),
10
10
  ajaxOptions: {
11
11
  headers: {