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.
Files changed (153) 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 +35 -87
  137. data/app/assets/stylesheets/spotlight/#_accessibility.scss# +0 -12
  138. data/app/javascript/spotlight/admin/checkbox_submit.js +0 -75
  139. data/app/javascript/spotlight/admin/exhibit_tag_autocomplete.js +0 -39
  140. data/app/javascript/spotlight/user/report_a_problem.js +0 -30
  141. data/app/views/spotlight/browse/_tophat.html.erb +0 -1
  142. data/app/views/spotlight/catalog/_tophat_default.html.erb +0 -1
  143. data/app/views/spotlight/home_pages/_tophat.html.erb +0 -2
  144. data/app/views/spotlight/pages/_tophat.html.erb +0 -1
  145. data/lib/generators/spotlight/templates/spotlight.js +0 -1
  146. data/lib/generators/spotlight/templates/spotlight.scss +0 -5
  147. data/spec/support/features/capybara_default_max_wait_metadata_helper.rb +0 -20
  148. data/vendor/assets/javascripts/bootstrap-tagsinput.js +0 -530
  149. data/vendor/assets/javascripts/jquery.serializejson.js +0 -234
  150. data/vendor/assets/javascripts/nestable.js +0 -645
  151. data/vendor/assets/javascripts/sir-trevor.js +0 -23508
  152. data/vendor/assets/javascripts/typeahead.bundle.min.js +0 -7
  153. 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(index)}">
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(index)}">
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(index)}">
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: {