blacklight-spotlight 5.3.2 → 6.0.0.rc2

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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -4
  3. data/Rakefile +1 -2
  4. data/app/assets/javascripts/spotlight/spotlight.esm.js +2168 -4560
  5. data/app/assets/javascripts/spotlight/spotlight.esm.js.map +1 -1
  6. data/app/assets/javascripts/spotlight/spotlight.js +2167 -4559
  7. data/app/assets/javascripts/spotlight/spotlight.js.map +1 -1
  8. data/app/assets/stylesheets/spotlight/_blacklight_overrides.scss +4 -0
  9. data/app/assets/stylesheets/spotlight/_browse.scss +0 -4
  10. data/app/assets/stylesheets/spotlight/{_collapse_toggle.scss → _collapse_toggle.css} +2 -2
  11. data/app/assets/stylesheets/spotlight/_exhibit_admin.scss +8 -0
  12. data/app/assets/stylesheets/spotlight/_exhibits_index.scss +2 -1
  13. data/app/assets/stylesheets/spotlight/_footer.scss +2 -2
  14. data/app/assets/stylesheets/spotlight/_header.scss +8 -8
  15. data/app/assets/stylesheets/spotlight/_pages.scss +1 -1
  16. data/app/assets/stylesheets/spotlight/_slideshow_block.scss +15 -0
  17. data/app/assets/stylesheets/spotlight/_spotlight.scss +1 -1
  18. data/app/assets/stylesheets/spotlight/_translations.scss +3 -3
  19. data/app/assets/stylesheets/spotlight/_uploaded_items_block.css +7 -0
  20. data/app/assets/stylesheets/spotlight/_variables.css +15 -0
  21. data/app/assets/stylesheets/spotlight/browse_group_categories_block.scss +0 -76
  22. data/app/components/blacklight/icons/pause_component.rb +15 -0
  23. data/app/components/blacklight/icons/play_arrow_component.rb +15 -0
  24. data/app/components/spotlight/analytics/dashboard_component.html.erb +1 -1
  25. data/app/components/spotlight/custom_field_form_group_component.rb +40 -0
  26. data/app/components/spotlight/document_admin_table_component.html.erb +2 -2
  27. data/app/components/spotlight/edit_sidecar_component.html.erb +18 -0
  28. data/app/components/spotlight/edit_sidecar_component.rb +41 -0
  29. data/app/components/spotlight/masthead_component.html.erb +18 -0
  30. data/app/components/spotlight/masthead_component.rb +46 -0
  31. data/app/components/spotlight/save_search_component.html.erb +1 -1
  32. data/app/components/spotlight/sidecar_form_field_component.html.erb +18 -0
  33. data/app/components/spotlight/sidecar_form_field_component.rb +71 -0
  34. data/app/components/spotlight/solr_document_legacy_embed_component.html.erb +0 -4
  35. data/app/components/spotlight/solr_document_legacy_embed_component.rb +2 -2
  36. data/app/components/spotlight/tag_selector_component.html.erb +1 -1
  37. data/app/components/spotlight/tag_selector_component.rb +3 -2
  38. data/app/components/spotlight/upload_field_form_group_component.rb +33 -0
  39. data/app/controllers/concerns/spotlight/controller.rb +15 -14
  40. data/app/controllers/concerns/spotlight/search_helper.rb +2 -5
  41. data/app/controllers/spotlight/browse_controller.rb +1 -6
  42. data/app/controllers/spotlight/catalog_controller.rb +2 -3
  43. data/app/controllers/spotlight/concerns/application_controller.rb +4 -6
  44. data/app/controllers/spotlight/exhibits_controller.rb +22 -10
  45. data/app/controllers/spotlight/lock_controller.rb +1 -1
  46. data/app/controllers/spotlight/pages_controller.rb +11 -11
  47. data/app/controllers/spotlight/resources/upload_controller.rb +1 -1
  48. data/app/controllers/spotlight/resources_controller.rb +1 -1
  49. data/app/controllers/spotlight/solr_controller.rb +5 -1
  50. data/app/helpers/spotlight/application_helper.rb +14 -26
  51. data/app/helpers/spotlight/crud_link_helpers.rb +0 -12
  52. data/app/helpers/spotlight/main_app_helpers.rb +2 -2
  53. data/app/javascript/spotlight/admin/add_another.js +37 -25
  54. data/app/javascript/spotlight/admin/add_image_selector.js +43 -26
  55. data/app/javascript/spotlight/admin/add_new_button.js +51 -36
  56. data/app/javascript/spotlight/admin/blacklight_configuration.js +76 -54
  57. data/app/javascript/spotlight/admin/block_mixins/autocompleteable.js +102 -101
  58. data/app/javascript/spotlight/admin/block_mixins/formable.js +91 -75
  59. data/app/javascript/spotlight/admin/block_mixins/plustextable.js +46 -48
  60. data/app/javascript/spotlight/admin/blocks/block.js +27 -28
  61. data/app/javascript/spotlight/admin/blocks/browse_block.js +14 -10
  62. data/app/javascript/spotlight/admin/blocks/browse_group_categories_block.js +14 -10
  63. data/app/javascript/spotlight/admin/blocks/pages_block.js +5 -4
  64. data/app/javascript/spotlight/admin/blocks/resources_block.js +61 -41
  65. data/app/javascript/spotlight/admin/blocks/search_result_block.js +24 -7
  66. data/app/javascript/spotlight/admin/blocks/solr_documents_base_block.js +103 -50
  67. data/app/javascript/spotlight/admin/blocks/solr_documents_carousel_block.js +50 -26
  68. data/app/javascript/spotlight/admin/blocks/solr_documents_features_block.js +47 -23
  69. data/app/javascript/spotlight/admin/blocks/uploaded_items_block.js +35 -19
  70. data/app/javascript/spotlight/admin/crop.js +146 -66
  71. data/app/javascript/spotlight/admin/croppable.js +5 -4
  72. data/app/javascript/spotlight/admin/croppable_modal.js +67 -32
  73. data/app/javascript/spotlight/admin/edit_in_place.js +69 -54
  74. data/app/javascript/spotlight/admin/exhibits.js +92 -45
  75. data/app/javascript/spotlight/admin/form_observer.js +92 -78
  76. data/app/javascript/spotlight/admin/multi_image_selector.js +201 -153
  77. data/app/javascript/spotlight/admin/pages.js +13 -7
  78. data/app/javascript/spotlight/admin/progress_monitor.js +65 -50
  79. data/app/javascript/spotlight/admin/readonly_checkbox.js +7 -3
  80. data/app/javascript/spotlight/admin/search_typeahead.js +20 -11
  81. data/app/javascript/spotlight/admin/select_related_input.js +23 -23
  82. data/app/javascript/spotlight/admin/serialize_json.js +120 -0
  83. data/app/javascript/spotlight/admin/sir-trevor/block_controls.js +24 -11
  84. data/app/javascript/spotlight/admin/sir-trevor/block_limits.js +9 -6
  85. data/app/javascript/spotlight/admin/sir-trevor/locales.js +2 -2
  86. data/app/javascript/spotlight/admin/spotlight_nestable.js +2 -5
  87. data/app/javascript/spotlight/admin/translation_progress.js +18 -13
  88. data/app/javascript/spotlight/admin/users.js +151 -64
  89. data/app/javascript/spotlight/user/carousel.js +104 -32
  90. data/app/javascript/spotlight/user/clear_form_button.js +24 -14
  91. data/app/javascript/spotlight/user/index.js +0 -2
  92. data/app/javascript/spotlight/user/zpr_links.js +99 -40
  93. data/app/jobs/concerns/spotlight/gather_documents.rb +1 -1
  94. data/app/jobs/spotlight/add_uploads_from_csv.rb +31 -8
  95. data/app/jobs/spotlight/cleanup_job_trackers_job.rb +5 -3
  96. data/app/models/concerns/spotlight/access_controls_enforcement_search_builder.rb +13 -2
  97. data/app/models/concerns/spotlight/browse_category_search_builder.rb +3 -3
  98. data/app/models/concerns/spotlight/exhibit_documents.rb +2 -2
  99. data/app/models/concerns/spotlight/sitemap.rb +2 -2
  100. data/app/models/concerns/spotlight/solr_document/finder.rb +2 -2
  101. data/app/models/concerns/spotlight/solr_document.rb +1 -47
  102. data/app/models/sir_trevor_rails/blocks/browse_block.rb +0 -4
  103. data/app/models/sir_trevor_rails/blocks/solr_documents_block.rb +2 -1
  104. data/app/models/spotlight/blacklight_configuration.rb +5 -3
  105. data/app/models/spotlight/contact_email.rb +2 -2
  106. data/app/models/spotlight/custom_field.rb +17 -0
  107. data/app/models/spotlight/exhibit.rb +28 -0
  108. data/app/models/spotlight/featured_image.rb +1 -1
  109. data/app/models/spotlight/field_metadata.rb +10 -5
  110. data/app/models/spotlight/job_tracker.rb +0 -18
  111. data/app/models/spotlight/main_navigation.rb +2 -2
  112. data/app/models/spotlight/page.rb +3 -3
  113. data/app/models/spotlight/resources/iiif_harvester.rb +7 -4
  114. data/app/models/spotlight/resources/iiif_manifest.rb +0 -4
  115. data/app/models/spotlight/resources/iiif_manifest_metadata.rb +1 -3
  116. data/app/models/spotlight/resources/iiif_service.rb +21 -6
  117. data/app/models/spotlight/resources/upload.rb +6 -0
  118. data/app/models/spotlight/search.rb +5 -9
  119. data/app/models/spotlight/solr_document_sidecar.rb +18 -0
  120. data/app/services/spotlight/bulk_updates_csv_template_service.rb +1 -1
  121. data/app/services/spotlight/etl/pipeline.rb +2 -2
  122. data/app/services/spotlight/etl/step.rb +2 -2
  123. data/app/views/catalog/_remove_tags.html.erb +2 -2
  124. data/app/views/layouts/spotlight/base.html.erb +3 -6
  125. data/app/views/layouts/spotlight/spotlight.html.erb +2 -2
  126. data/app/views/shared/_analytics.html.erb +4 -3
  127. data/app/views/shared/_masthead.html.erb +2 -18
  128. data/app/views/spotlight/about_pages/_empty.html.erb +1 -1
  129. data/app/views/spotlight/about_pages/_sidebar.html.erb +10 -8
  130. data/app/views/spotlight/browse/_search.html.erb +2 -2
  131. data/app/views/spotlight/browse/_search_box.html.erb +6 -14
  132. data/app/views/spotlight/browse/index.html.erb +1 -1
  133. data/app/views/spotlight/browse/show.html.erb +1 -1
  134. data/app/views/spotlight/catalog/_document_admin_table.html.erb +1 -1
  135. data/app/views/spotlight/catalog/_edit_default.html.erb +1 -1
  136. data/app/views/spotlight/catalog/edit.html.erb +1 -1
  137. data/app/views/spotlight/dashboards/_page.html.erb +1 -1
  138. data/app/views/spotlight/exhibits/_exhibit_card.html.erb +3 -1
  139. data/app/views/spotlight/exhibits/_export.html.erb +17 -1
  140. data/app/views/spotlight/exhibits/_form.html.erb +20 -4
  141. data/app/views/spotlight/exhibits/_missing_exhibits.html.erb +3 -3
  142. data/app/views/spotlight/exhibits/index.html.erb +21 -8
  143. data/app/views/spotlight/feature_pages/_empty.html.erb +1 -1
  144. data/app/views/spotlight/feature_pages/_sidebar.html.erb +17 -15
  145. data/app/views/spotlight/home_pages/_empty.html.erb +7 -7
  146. data/app/views/spotlight/pages/show.html.erb +1 -0
  147. data/app/views/spotlight/resources/upload/_form.html.erb +2 -2
  148. data/app/views/spotlight/sir_trevor/blocks/_browse_group_categories_block.html.erb +5 -16
  149. data/app/views/spotlight/sir_trevor/blocks/_embedded_document.html.erb +3 -5
  150. data/app/views/spotlight/sir_trevor/blocks/_link_to_search_block.html.erb +1 -1
  151. data/app/views/spotlight/sir_trevor/blocks/_solr_documents_carousel_block.html.erb +11 -2
  152. data/app/views/spotlight/sites/_exhibit.html.erb +1 -1
  153. data/config/i18n-tasks.yml +1 -1
  154. data/config/locales/spotlight.ar.yml +1 -2
  155. data/config/locales/spotlight.de.yml +1 -2
  156. data/config/locales/spotlight.en.yml +39 -10
  157. data/config/locales/spotlight.es.yml +1 -2
  158. data/config/locales/spotlight.fr.yml +1 -2
  159. data/config/locales/spotlight.it.yml +1 -2
  160. data/config/locales/spotlight.ja.yml +39 -0
  161. data/config/locales/spotlight.zh.yml +1 -2
  162. data/db/migrate/20261007133748_add_discovery_enabled_to_spotlight_exhibits.rb +5 -0
  163. data/lib/generators/spotlight/assets/importmap_generator.rb +2 -10
  164. data/lib/generators/spotlight/assets/propshaft_generator.rb +5 -4
  165. data/lib/generators/spotlight/install_generator.rb +2 -4
  166. data/lib/generators/spotlight/templates/catalog_controller.rb +0 -1
  167. data/lib/generators/spotlight/templates/config/initializers/spotlight_initializer.rb +26 -4
  168. data/lib/generators/spotlight/templates/solr/conf/schema.xml +4 -3
  169. data/lib/generators/spotlight/templates/solr/conf/solrconfig.xml +3 -8
  170. data/lib/migration/page_language.rb +0 -2
  171. data/lib/spotlight/engine.rb +28 -9
  172. data/lib/spotlight/search_state.rb +7 -11
  173. data/lib/spotlight/upload_field_config.rb +19 -1
  174. data/lib/spotlight/version.rb +1 -1
  175. data/lib/spotlight.rb +1 -1
  176. data/spec/support/features/capybara_wait_metadata_helper.rb +3 -1
  177. data/spec/support/features/test_features_helpers.rb +24 -1
  178. data/spec/support/iiif_manifest_fixtures.rb +33 -0
  179. data/spec/support/stub_dns_resolution.rb +12 -0
  180. metadata +50 -80
  181. data/app/assets/stylesheets/spotlight/_uploaded_items_block.scss +0 -7
  182. data/app/assets/stylesheets/spotlight/_variables.scss +0 -13
  183. data/app/components/blacklight/icons/arrow_alt_circle_left_component.rb +0 -15
  184. data/app/components/blacklight/icons/arrow_alt_circle_right_component.rb +0 -15
  185. data/app/components/blacklight/icons/info_component.rb +0 -15
  186. data/app/components/blacklight/icons/zoom_in_component.rb +0 -15
  187. data/app/components/blacklight/icons/zoom_out_component.rb +0 -15
  188. data/app/components/spotlight/icon_component.rb +0 -13
  189. data/app/javascript/spotlight/user/browse_group_categories.js +0 -54
  190. data/app/mailers/spotlight/invitation_mailer.rb +0 -21
  191. data/app/models/concerns/spotlight/resources/web.rb +0 -38
  192. data/app/services/spotlight/iiif_resource_resolver.rb +0 -78
  193. data/app/views/catalog/_image_default.html.erb +0 -6
  194. data/app/views/shared/_exhibit_navbar.html.erb +0 -2
  195. data/app/views/shared/_header_navbar.html.erb +0 -6
  196. data/app/views/spotlight/catalog/_admin_index_header_default.html.erb +0 -23
  197. data/app/views/spotlight/catalog/_admin_thumbnail_default.html.erb +0 -7
  198. data/app/views/spotlight/catalog/_curation_mode_toggle_default.html.erb +0 -1
  199. data/app/views/spotlight/catalog/_document_row.html.erb +0 -2
  200. data/app/views/spotlight/catalog/_edit_sidecar.html.erb +0 -24
  201. data/app/views/spotlight/catalog/_index_compact_default.html.erb +0 -21
  202. data/app/views/spotlight/custom_fields/form_group/_text.html.erb +0 -23
  203. data/app/views/spotlight/custom_fields/form_group/_vocab.html.erb +0 -23
  204. data/app/views/spotlight/invitation_mailer/exhibit_invitation_notification.html.erb +0 -12
  205. data/lib/generators/spotlight/templates/javascript/jquery-shim.js +0 -1
  206. data/vendor/assets/javascripts/tiny-slider.js +0 -3221
  207. data/vendor/assets/stylesheets/tiny-slider.css +0 -1
  208. /data/app/assets/stylesheets/spotlight/{_breadcrumbs.scss → _breadcrumbs.css} +0 -0
  209. /data/app/assets/stylesheets/spotlight/{_croppable.scss → _croppable.css} +0 -0
  210. /data/app/assets/stylesheets/spotlight/{_modals.scss → _modals.css} +0 -0
  211. /data/app/assets/stylesheets/spotlight/{_utilities.scss → _utilities.css} +0 -0
@@ -33,8 +33,8 @@ module Spotlight
33
33
  errors.add :email, 'is not valid' if parsed.nil? || parsed.address != email || parsed.local == email
34
34
  end
35
35
 
36
- def send_devise_notification(notification, *args)
37
- notice = notification_mailer.send(notification, self, *args, exhibit:)
36
+ def send_devise_notification(notification, *)
37
+ notice = notification_mailer.send(notification, self, *, exhibit:)
38
38
  if notice.respond_to? :deliver_now
39
39
  notice.deliver_now
40
40
  else
@@ -59,6 +59,19 @@ module Spotlight
59
59
  end
60
60
  end
61
61
 
62
+ # The component used to render the field on the upload and edit forms,
63
+ # from the `component` option of the field's entry in Spotlight::Engine.config.custom_field_types
64
+ def component
65
+ component = field_type_config[:component]
66
+ return Spotlight::SidecarFormFieldComponent if component.blank?
67
+
68
+ component.is_a?(String) ? component.constantize : component
69
+ end
70
+
71
+ def form_field_type
72
+ field_type_config.fetch(:form_field_type, :text_field)
73
+ end
74
+
62
75
  def solr_field
63
76
  if field&.starts_with?(solr_field_prefix)
64
77
  # backwards compatibility with pre-0.9 custom fields
@@ -70,6 +83,10 @@ module Spotlight
70
83
 
71
84
  protected
72
85
 
86
+ def field_type_config
87
+ Spotlight::Engine.config.custom_field_types.fetch(field_type.to_s.to_sym, {})
88
+ end
89
+
73
90
  def blacklight_configuration
74
91
  exhibit&.blacklight_configuration
75
92
  end
@@ -5,6 +5,8 @@ module Spotlight
5
5
  ##
6
6
  # Spotlight exhibit
7
7
  class Exhibit < ActiveRecord::Base
8
+ PUBLISHING_STATUSES = %w[unpublished previewable discoverable].freeze
9
+
8
10
  class_attribute :themes_selector
9
11
  include Spotlight::ExhibitAnalytics
10
12
  include Spotlight::ExhibitDefaults
@@ -17,6 +19,8 @@ module Spotlight
17
19
 
18
20
  scope :published, -> { where(published: true) }
19
21
  scope :unpublished, -> { where(published: false) }
22
+ scope :previewable, -> { published.where(discovery_enabled: false) }
23
+ scope :discoverable, -> { published.where(discovery_enabled: true) }
20
24
  scope :ordered_by_weight, -> { order(:weight) }
21
25
 
22
26
  paginates_per 48
@@ -96,6 +100,30 @@ module Spotlight
96
100
  searches.published.any?
97
101
  end
98
102
 
103
+ # Published, but only viewable by people with the exhibit link
104
+ def previewable?
105
+ published? && !discovery_enabled?
106
+ end
107
+
108
+ # Published, and included on the home page and in the sitemap
109
+ def discoverable?
110
+ published? && discovery_enabled?
111
+ end
112
+
113
+ def publishing_status
114
+ return 'unpublished' unless published?
115
+
116
+ discovery_enabled? ? 'discoverable' : 'previewable'
117
+ end
118
+
119
+ def publishing_status=(status)
120
+ status = status.to_s
121
+ return unless PUBLISHING_STATUSES.include?(status)
122
+
123
+ self.published = status != 'unpublished'
124
+ self.discovery_enabled = status != 'previewable'
125
+ end
126
+
99
127
  def themes
100
128
  @themes ||= begin
101
129
  return Spotlight::Engine.config.exhibit_themes unless self.class.themes_selector
@@ -38,7 +38,7 @@ module Spotlight
38
38
  # If the image is local, this step will fail, which is okay since the only
39
39
  # consumer is CSV uploads and the URL is intended to be remote
40
40
  def remote_image_url=(url)
41
- super url unless url.starts_with? '/'
41
+ super unless url.starts_with? '/'
42
42
  end
43
43
 
44
44
  def document
@@ -24,11 +24,16 @@ module Spotlight
24
24
  end
25
25
 
26
26
  def search_params
27
- search_service.search_builder.merge(rows: 0, 'facet.limit' => FACET_LIMIT + 1)
27
+ search_service.search_builder.with(search_state).merge(rows: 0, 'facet.limit' => FACET_LIMIT + 1)
28
28
  end
29
29
 
30
30
  private
31
31
 
32
+ # Field metadata queries aren't scoped to a user query, so use an empty search state
33
+ def search_state
34
+ Blacklight::SearchState.new({}, blacklight_config)
35
+ end
36
+
32
37
  def field_name(key)
33
38
  if blacklight_config.facet_fields[key]
34
39
  blacklight_config.facet_fields[key].field
@@ -38,15 +43,15 @@ module Spotlight
38
43
  end
39
44
 
40
45
  def solr_response
41
- @solr_response ||= repository.search(search_params.merge('facet.query' => facet_fields.map { |_key, fields| "#{fields.field}:[* TO *]" },
42
- 'rows' => 0,
43
- 'facet' => true))
46
+ @solr_response ||= repository.search(params: search_params.merge('facet.query' => facet_fields.map { |_key, fields| "#{fields.field}:[* TO *]" },
47
+ 'rows' => 0,
48
+ 'facet' => true))
44
49
  end
45
50
 
46
51
  # This gets the number of *documents* with a field
47
52
  def document_counts
48
53
  @document_count ||= solr_response.facet_queries.each_with_object({}) do |(k, v), h|
49
- h[k.split(/:/).first] = v
54
+ h[k.split(':').first] = v
50
55
  end
51
56
  end
52
57
 
@@ -43,12 +43,6 @@ module Spotlight
43
43
  @job_status ||= ActiveJob::Status.get(job_id)
44
44
  end
45
45
 
46
- def progress_label
47
- return number_with_delimiter(progress) unless total?
48
-
49
- "#{number_with_delimiter(progress)} / #{number_with_delimiter(total)}"
50
- end
51
-
52
46
  def progress
53
47
  data[:progress] || job_status[:progress] || 0
54
48
  end
@@ -61,12 +55,6 @@ module Spotlight
61
55
  total(default: 0).positive?
62
56
  end
63
57
 
64
- def percent
65
- return nil unless total?
66
-
67
- (100.0 * progress) / total
68
- end
69
-
70
58
  def status
71
59
  @status ||= super
72
60
  @status ||= 'missing'
@@ -104,11 +92,5 @@ module Spotlight
104
92
  self
105
93
  end
106
94
  end
107
-
108
- private
109
-
110
- def number_with_delimiter(*args)
111
- ActiveSupport::NumberHelper.number_to_delimited(*args)
112
- end
113
95
  end
114
96
  end
@@ -22,8 +22,8 @@ module Spotlight
22
22
  label.presence || default_label
23
23
  end
24
24
 
25
- def default_label(**options)
26
- I18n.t(:"spotlight.main_navigation.#{nav_type}", **options)
25
+ def default_label(**)
26
+ I18n.t(:"spotlight.main_navigation.#{nav_type}", **)
27
27
  end
28
28
 
29
29
  private
@@ -74,7 +74,7 @@ module Spotlight
74
74
 
75
75
  def content=(content)
76
76
  if content.is_a? Array
77
- super content.to_json
77
+ super(content.to_json)
78
78
  else
79
79
  super
80
80
  end
@@ -167,12 +167,12 @@ module Spotlight
167
167
  end
168
168
  end
169
169
 
170
- private
171
-
172
170
  def default_locale?
173
171
  locale.to_sym == I18n.default_locale
174
172
  end
175
173
 
174
+ private
175
+
176
176
  def parent_page_for(locale)
177
177
  parent_page&.translated_page_for(locale)
178
178
  end
@@ -33,13 +33,16 @@ module Spotlight
33
33
 
34
34
  def url_is_iiif?(url)
35
35
  valid_content_types = ['application/json', 'application/ld+json']
36
- req = Spotlight::Resources::IiifService.http_client.head(url)
37
- req = Spotlight::Resources::IiifService.http_client.get(url) if req.status == 405
38
- return unless req.success?
36
+ req = Spotlight::Resources::IiifService.fetch(:head, url)
37
+ req = Spotlight::Resources::IiifService.fetch(:get, url) if req.code == '405'
38
+ return unless req.is_a?(Net::HTTPSuccess)
39
39
 
40
40
  valid_content_types.any? do |valid_type|
41
- req.headers['content-type'].include?(valid_type)
41
+ req['content-type'].to_s.include?(valid_type)
42
42
  end
43
+ rescue *Spotlight::Resources::IiifService::FETCH_ERRORS => e
44
+ Rails.logger.warn("Unable to fetch #{url}: #{e.message}")
45
+ false
43
46
  end
44
47
  end
45
48
  end
@@ -28,10 +28,6 @@ module Spotlight
28
28
  solr_hash
29
29
  end
30
30
 
31
- def with_exhibit(e)
32
- @exhibit = e
33
- end
34
-
35
31
  def compound_id
36
32
  Digest::MD5.hexdigest("#{exhibit.id}-#{url}")
37
33
  end
@@ -120,13 +120,11 @@ module Spotlight
120
120
  if value.key? '@value'
121
121
  value['@value']
122
122
  # IIIF v3 multilingual(?), multivalued(?) values
123
- # If all of the values are associated with the none key, the client must display all of those values.
124
- elsif value.keys == ['none']
125
- value['none']
126
123
  # If any of the values have a language associated with them, the client must display all of the values associated with the language
127
124
  # that best matches the language preference.
128
125
  elsif value.key? default_json_ld_language
129
126
  value[default_json_ld_language]
127
+ # If all of the values are associated with the none key, the client must display all of those values.
130
128
  # If some of the values have a language associated with them, but none match the language preference, the client must display all
131
129
  # of the values that do not have a language associated with them.
132
130
  elsif value.key? 'none'
@@ -2,12 +2,21 @@
2
2
 
3
3
  require 'iiif/presentation'
4
4
  require 'iiif/v3/presentation'
5
+ require 'ssrf_filter'
5
6
  module Spotlight
6
7
  module Resources
7
8
  ###
8
9
  # Wrapper around IIIF-Presentation's IIIF::Service that provides the
9
10
  # ability to recursively traverse through all collections and manifests
10
11
  class IiifService
12
+ # Errors from a failed fetch, covering what Faraday::ConnectionFailed used to wrap
13
+ FETCH_ERRORS = [
14
+ SsrfFilter::Error, URI::Error, IOError, SocketError, SystemCallError, Timeout::Error,
15
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
16
+ OpenSSL::SSL::SSLError, Zlib::Error
17
+ ].freeze
18
+ MAX_REDIRECTS = 3
19
+
11
20
  def initialize(url)
12
21
  @url = url
13
22
  end
@@ -32,11 +41,17 @@ module Spotlight
32
41
  recursive_manifests(new(url))
33
42
  end
34
43
 
35
- def self.http_client
36
- Faraday.new do |b|
37
- b.response :follow_redirects
38
- b.adapter Faraday.default_adapter
44
+ # SsrfFilter only resolves redirects whose Location starts with "/", so
45
+ # follow them here instead; each hop still goes through SsrfFilter.
46
+ def self.fetch(verb, url, **)
47
+ (MAX_REDIRECTS + 1).times do
48
+ response = SsrfFilter.public_send(verb, url, max_redirects: 0, allow_unfollowed_redirects: true, **)
49
+ return response unless response.is_a?(Net::HTTPRedirection) && response['location']
50
+
51
+ url = URI.join(url, response['location']).to_s
39
52
  end
53
+
54
+ raise SsrfFilter::TooManyRedirects, "Got #{MAX_REDIRECTS} redirects fetching #{url}"
40
55
  end
41
56
 
42
57
  protected
@@ -64,8 +79,8 @@ module Spotlight
64
79
 
65
80
  class << self
66
81
  def iiif_response(url)
67
- Spotlight::Resources::IiifService.http_client.get(url).body
68
- rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
82
+ fetch(:get, url, http_options: { response_body_encoding: true }).body
83
+ rescue *FETCH_ERRORS => e
69
84
  Rails.logger.warn("HTTP GET for #{url} failed with #{e}")
70
85
  {}.to_json
71
86
  end
@@ -23,6 +23,12 @@ module Spotlight
23
23
  end
24
24
  end
25
25
 
26
+ def self.as_strong_params(exhibit)
27
+ fields(exhibit).map do |field|
28
+ field.is_multiple? ? { field.field_name => [] } : field.field_name
29
+ end
30
+ end
31
+
26
32
  def self.indexing_pipeline
27
33
  @indexing_pipeline ||= super.dup.tap do |pipeline|
28
34
  pipeline.transforms = [
@@ -52,14 +52,14 @@ module Spotlight
52
52
 
53
53
  def documents(&)
54
54
  start = 0
55
- response = repository.search(search_params.start(start))
55
+ response = repository.search(params: search_params.start(start))
56
56
 
57
57
  return to_enum(:documents) { response['response']['numFound'] } unless block_given?
58
58
 
59
59
  while response.documents.present?
60
60
  response.documents.each(&)
61
61
  start += response.documents.length
62
- response = repository.search(search_params.start(start))
62
+ response = repository.search(params: search_params.start(start))
63
63
  end
64
64
  end
65
65
 
@@ -75,20 +75,16 @@ module Spotlight
75
75
 
76
76
  delegate :blacklight_config, to: :exhibit
77
77
 
78
- def display_masthead?
79
- masthead&.display?
80
- end
81
-
82
78
  def search_params
83
- search_service.search_builder.with(base_search_state).merge(facet: false)
79
+ search_service.search_builder.with(search_state).merge(facet: false)
84
80
  end
85
81
 
86
82
  def merge_params_for_search(params, blacklight_config)
87
83
  user_query = Blacklight::SearchState.new(params, blacklight_config, nil).to_h
88
- base_search_state.params_for_search(user_query).merge(user_query.slice(:page))
84
+ search_state.params_for_search(user_query).merge(user_query.slice(:page))
89
85
  end
90
86
 
91
- def base_search_state
87
+ def search_state
92
88
  Blacklight::SearchState.new((query_params || {}).with_indifferent_access, blacklight_config, nil)
93
89
  end
94
90
 
@@ -22,6 +22,8 @@ module Spotlight
22
22
 
23
23
  delegate :has_key?, :key?, to: :data
24
24
 
25
+ before_save :strip_blank_values_from_data
26
+
25
27
  def to_solr
26
28
  { document.class.unique_key.to_sym => document.id,
27
29
  visibility_field => public? }
@@ -48,6 +50,22 @@ module Spotlight
48
50
 
49
51
  private
50
52
 
53
+ # Remove blank entries (e.g. from an empty "Add another value" input) from multivalued fields
54
+ def strip_blank_values_from_data
55
+ self.data = strip_blank_values(data)
56
+ end
57
+
58
+ def strip_blank_values(value)
59
+ case value
60
+ when Array
61
+ value.compact_blank
62
+ when Hash
63
+ value.transform_values { |v| strip_blank_values(v) }
64
+ else
65
+ value
66
+ end
67
+ end
68
+
51
69
  def document_type_class
52
70
  return default_document_type unless document_type.is_a?(String)
53
71
 
@@ -74,7 +74,7 @@ module Spotlight
74
74
  until next_cursor_mark == cursor_mark || next_cursor_mark.nil?
75
75
  cursor_mark = next_cursor_mark
76
76
  response = exhibit.blacklight_config.repository.search(
77
- solr_params.merge(
77
+ params: solr_params.merge(
78
78
  'q' => '*',
79
79
  'rows' => Spotlight::Engine.config.bulk_actions_batch_size,
80
80
  'cursorMark' => cursor_mark,
@@ -58,8 +58,8 @@ module Spotlight
58
58
  # @param [Hash] data the initial data structure to pass through to the transform steps
59
59
  # @yield (optioanlly..) each transformed document after it is transformed but before
60
60
  # it is sent to the loaders
61
- def call(context, data: {}, cache: nil, &block)
62
- executor(context, cache:).call(data:, &block)
61
+ def call(context, data: {}, cache: nil, &)
62
+ executor(context, cache:).call(data:, &)
63
63
  end
64
64
 
65
65
  ##
@@ -37,8 +37,8 @@ module Spotlight
37
37
  end
38
38
  # rubocop:enable Metrics/MethodLength
39
39
 
40
- def finalize(*args)
41
- action.finalize(*args) if action.respond_to? :finalize
40
+ def finalize(*)
41
+ action.finalize(*) if action.respond_to? :finalize
42
42
  end
43
43
 
44
44
  private
@@ -20,8 +20,8 @@
20
20
  <% end %>
21
21
 
22
22
  <div class="form-group mb-3 mt-4">
23
- <%= label_tag :tags, t(:'spotlight.bulk_actions.remove_tags.label') %>
24
- <%= render Spotlight::TagSelectorComponent.new(field_name: :tags, all_tags: current_exhibit.owned_tags) %>
23
+ <%= label_tag 'remove_tags', t(:'spotlight.bulk_actions.remove_tags.label') %>
24
+ <%= render Spotlight::TagSelectorComponent.new(field_name: :tags, id: 'remove_tags', all_tags: current_exhibit.owned_tags) %>
25
25
  </div>
26
26
  <%= render Blacklight::HiddenSearchStateComponent.new(params: search_state.params_for_search) %>
27
27
  </div>
@@ -15,8 +15,6 @@
15
15
  <% end %>
16
16
  <%= javascript_tag "window.sirTrevorIcon = '#{asset_path('spotlight/blocks/sir-trevor-icons.svg')}'" %>
17
17
 
18
- <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
19
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.serializeJSON/3.2.1/jquery.serializejson.min.js"></script>
20
18
  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
21
19
  <%= javascript_tag 'L.Icon.Default.prototype.options.imagePath = "https://unpkg.com/leaflet@1.9.4/dist/images/"' %>
22
20
  <%= javascript_include_tag 'leaflet-iiif', "data-turbo-track": "reload", defer: true %>
@@ -25,7 +23,7 @@
25
23
  <% if defined? Importmap %>
26
24
  <%= javascript_importmap_tags %>
27
25
  <% else %>
28
- <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
26
+ <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
29
27
  <% end %>
30
28
  <%= csrf_meta_tags %>
31
29
  <%= content_for(:head) %>
@@ -36,15 +34,14 @@
36
34
  <% add_exhibit_meta_content %>
37
35
  <% end %>
38
36
  <%= yield(:meta) %>
39
- <%= javascript_tag "window.addEventListener('load', () => $.fx.off = true)" if Rails.env.test? %>
40
37
  </head>
41
38
  <body class="<%= render_body_class %>">
42
39
  <%= render partial: 'shared/body_preamble' %>
43
40
  <%= render blacklight_config.skip_link_component.new(render_search_link: should_render_spotlight_search_bar?) do %>
44
41
  <%= content_for(:skip_links) %>
45
42
  <% end %>
46
- <%= render partial: 'shared/header_navbar' %>
47
- <%= render partial: 'shared/masthead' %>
43
+ <%= render blacklight_config.header_component.new(blacklight_config:) %>
44
+ <%= render Spotlight::MastheadComponent.new(current_exhibit:, current_masthead:, resource_masthead: resource_masthead?) %>
48
45
  <%= content_for?(:header_content) ? yield(:header_content) : "" %>
49
46
 
50
47
  <main id="main-container" class="<%= container_classes %>" aria-label="<%= t('blacklight.main.aria.main_container') %>">
@@ -1,10 +1,10 @@
1
1
  <% content_for(:content) do %>
2
2
  <% if content? :sidebar %>
3
- <aside id="sidebar" class="<%= sidebar_classes %> <%= content_for(:sidebar_position) %>" aria-label="<%= t('blacklight.search.documents.aria.limit_search') %>">
3
+ <aside id="sidebar" class="<%= sidebar_classes %> <%= content_for(:sidebar_position) %>">
4
4
  <%= content_for(:sidebar) %>
5
5
  </aside>
6
6
 
7
- <section id="content" class="<%= main_content_classes %> <%= content_for(:content_position) %>" aria-label="<%= t('blacklight.search.search_results') %>">
7
+ <section id="content" class="<%= main_content_classes %> <%= content_for(:content_position) %>">
8
8
  <%= yield %>
9
9
  </section>
10
10
  <% else %>
@@ -1,14 +1,15 @@
1
- <% if Spotlight::Engine.config.ga_web_property_id %>
1
+ <% ga_id = Spotlight::Engine.config.ga_measurement_id.presence || Spotlight::Engine.config.ga_web_property_id %>
2
+ <% if ga_id.present? %>
2
3
  <!-- Google tag (gtag.js) -->
3
4
  <% if Spotlight::Engine.config.ga_debug_mode %>
4
5
  <script>window.analyticsDebug = true;</script>
5
6
  <% end %>
6
- <script async src="https://www.googletagmanager.com/gtag/js?id=<%= Spotlight::Engine.config.ga_web_property_id %>"></script>
7
+ <script async src="https://www.googletagmanager.com/gtag/js?id=<%= ga_id %>"></script>
7
8
  <script>
8
9
  window.dataLayer = window.dataLayer || [];
9
10
  function gtag() { dataLayer.push(arguments); }
10
11
  var opts = window.analyticsDebug ? {'debug_mode': true} : {};
11
12
  gtag("js", new Date());
12
- gtag("config", "<%= Spotlight::Engine.config.ga_web_property_id %>", opts);
13
+ gtag("config", "<%= ga_id %>", opts);
13
14
  </script>
14
15
  <% end %>
@@ -1,18 +1,2 @@
1
- <% if show_contact_form? && (current_exhibit.nil? || !current_page?(spotlight.new_exhibit_contact_form_path(current_exhibit))) %>
2
- <div id="report-problem-form" class="collapse">
3
- <%= render 'spotlight/shared/report_a_problem' %>
4
- </div>
5
- <% end %>
6
-
7
- <header class="masthead <%= 'image-masthead' if current_masthead %> <%= 'resource-masthead' if resource_masthead? %>">
8
- <% if current_masthead %>
9
- <span class="background-container" style="background-image: url('<%= current_masthead.iiif_url %>')"></span>
10
- <span class="background-container-gradient"></span>
11
- <% end %>
12
-
13
- <%= render current_exhibit ? 'shared/exhibit_navbar' : 'shared/site_navbar' if resource_masthead? %>
14
- <%= render Spotlight::Engine.config.spotlight.title_component.new(title: content_for(:masthead) || masthead_heading_content, subtitle: masthead_subheading_content) %>
15
- <%= render current_exhibit ? 'shared/exhibit_navbar' : 'shared/site_navbar' unless resource_masthead? %>
16
- </header>
17
-
18
- <%= render Spotlight::BreadcrumbsComponent.new(breadcrumbs:) %>
1
+ <% Spotlight.deprecator.warn "Rendering deprecated partial shared/masthead. Use MastheadComponent instead" %>
2
+ <%= render Spotlight::MastheadComponent.new(current_exhibit:, current_masthead:, resource_masthead: resource_masthead?) %>
@@ -19,7 +19,7 @@
19
19
  Use the checkbox in the <b>Options</b> tab to publish or unpublish the page.
20
20
  </li>
21
21
  <li>
22
- Visit the Spotlight <%= link_to 'documentation pages', 'https://github.com/projectblacklight/spotlight/wiki' %> to learn more about creating exhibit pages.
22
+ Visit the Spotlight <%= link_to 'documentation pages', 'https://github.com/projectblacklight/spotlight/wiki', class: "alert-link" %> to learn more about creating exhibit pages.
23
23
  </li>
24
24
  </ol>
25
25
  </div>
@@ -1,12 +1,14 @@
1
1
  <% cache_unless current_user, [current_exhibit, @page, I18n.locale] do %>
2
- <h2 class="nav-heading h4"><%= current_exhibit.main_navigations.about.label_or_default %></h2>
3
- <ul class="nav sidenav flex-column">
4
- <% current_exhibit.about_pages.for_locale.published.each do |page| %>
5
- <li class="nav-item <%= 'active' if current_page? [spotlight, current_exhibit, page] %>">
6
- <%= link_to page.title, [spotlight, current_exhibit, page], class: 'nav-link' %>
7
- </li>
8
- <% end %>
9
- </ul>
2
+ <nav aria-labelledby="about-pages-h">
3
+ <h2 id="about-pages-h" class="nav-heading h4"><%= current_exhibit.main_navigations.about.label_or_default %></h2>
4
+ <ul class="nav sidenav flex-column">
5
+ <% current_exhibit.about_pages.for_locale.published.each do |page| %>
6
+ <li class="nav-item <%= 'active' if current_page? [spotlight, current_exhibit, page] %>">
7
+ <%= link_to page.title, [spotlight, current_exhibit, page], class: 'nav-link' %>
8
+ </li>
9
+ <% end %>
10
+ </ul>
11
+ </nav>
10
12
  <%= cache [current_exhibit, I18n.locale] do %>
11
13
  <%= render 'contacts' if current_exhibit.contacts.published.present? %>
12
14
  <% end %>
@@ -5,11 +5,11 @@
5
5
  <div class="image-overlay">
6
6
  <%= image_tag(search.thumbnail_image_url || 'spotlight/default_browse_thumbnail.jpg', class: 'img-responsive', alt: '') %>
7
7
  <div class="text-overlay">
8
- <h2 class="browse-category-title">
8
+ <h3 class="browse-category-title">
9
9
  <span class="title"><%= search.title %></span>
10
10
  <% if search.subtitle.present? %><span class="category-subtitle"><%= search.subtitle %></span><% end %>
11
11
  <small class="item-count"><%= t :'spotlight.browse.search.item_count', count: search.count %></small>
12
- </h2>
12
+ </h3>
13
13
  </div>
14
14
  </div>
15
15
  <% end %>
@@ -1,7 +1,7 @@
1
1
  <div class="search-box-container">
2
2
  <%= form_tag exhibit_browse_path(current_exhibit, search), method: :get, class: 'browse-search-form search-query-form form-horizontal', role: 'search', 'aria-labelledby' => 'browse-search-form' do %>
3
3
  <%= render Blacklight::HiddenSearchStateComponent.new(params: search_state.params_for_search.except(:q, :search_field, :exhibit_id, :qt, :page)) %>
4
- <div class="form-group mb-5 row">
4
+ <div class="form-group mb-3 row">
5
5
  <label id="browse-search-form" class="col-sm-4 col-form-label h6" for="browse_q"><%= t(:'.label') %></label>
6
6
  <div class="col-sm-6">
7
7
  <div class="input-group">
@@ -18,22 +18,14 @@
18
18
  </div>
19
19
  </div>
20
20
  <% end %>
21
- <% if params[:q].present? %>
22
- <div class="browse-search-expand">
21
+ <div class="text-center">
22
+ <p><%= link_to(t(:'.view_search'), path_to_saved_search(search)) %></p>
23
+ <% if params[:q].present? %>
23
24
  <% if @response.size > 0 %>
24
25
  <p><%= t(:'.success.result_number_html', search_size: @response.size, parent_search_count: parent_search_count) %></p>
25
- <p><%= t(:'.success.expand_html',
26
- expand_search_url: search_exhibit_catalog_path(current_exhibit, q: params[:q]),
27
- browse_query: params[:q]) %>
28
- </p>
29
26
  <% else %>
30
27
  <p><%= t(:'.zero_results.result_number') %></p>
31
- <p><%= t(:'.zero_results.expand_html',
32
- clear_search_url: exhibit_browse_path(current_exhibit, search),
33
- expand_search_url: search_exhibit_catalog_path(current_exhibit, q: params[:q]),
34
- browse_query: params[:q]) %>
35
- </p>
36
28
  <% end %>
37
- </div>
38
- <% end %>
29
+ <% end %>
30
+ </div>
39
31
  </div>
@@ -1,6 +1,6 @@
1
1
  <% title = current_exhibit.main_navigations.browse.label_or_default %>
2
2
  <% set_html_page_title(title) %>
3
- <h1 class="visually-hidden"><%= title %></h1>
3
+ <h2 class="visually-hidden"><%= title %></h2>
4
4
 
5
5
  <% if @groups.any? # active %>
6
6
  <ul class="browse-group-navigation nav nav-pills justify-content-center">