blacklight-spotlight 4.7.1 → 5.0.0.pre.alpha2

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 (157) 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 +3621 -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/blocks/heading_block_component.erb +2 -0
  30. data/app/components/spotlight/blocks/heading_block_component.rb +36 -0
  31. data/app/components/spotlight/breadcrumbs_component.html.erb +13 -19
  32. data/app/components/spotlight/bulk_action_component.rb +1 -1
  33. data/app/components/spotlight/document_component.rb +1 -1
  34. data/app/components/spotlight/save_search_component.rb +1 -1
  35. data/app/components/spotlight/select_image_component.html.erb +17 -0
  36. data/app/components/spotlight/select_image_component.rb +24 -0
  37. data/app/components/spotlight/skip_link_component.rb +16 -0
  38. data/app/components/spotlight/tag_selector_component.html.erb +40 -0
  39. data/app/components/spotlight/tag_selector_component.rb +41 -0
  40. data/app/components/spotlight/tag_selector_component.yml +6 -0
  41. data/app/components/spotlight/title_component.html.erb +8 -0
  42. data/app/components/spotlight/title_component.rb +22 -0
  43. data/app/controllers/spotlight/accessibility_controller.rb +2 -2
  44. data/app/controllers/spotlight/catalog_controller.rb +7 -2
  45. data/app/controllers/spotlight/contact_email_controller.rb +8 -2
  46. data/app/controllers/spotlight/languages_controller.rb +9 -4
  47. data/app/helpers/spotlight/application_helper.rb +7 -0
  48. data/app/helpers/spotlight/crop_helper.rb +4 -0
  49. data/app/helpers/spotlight/meta_helper.rb +59 -36
  50. data/app/javascript/spotlight/admin/blacklight_configuration.js +1 -1
  51. data/app/javascript/spotlight/admin/block_mixins/autocompleteable.js +70 -34
  52. data/app/javascript/spotlight/admin/blocks/block.js +1 -0
  53. data/app/javascript/spotlight/admin/blocks/browse_block.js +8 -12
  54. data/app/javascript/spotlight/admin/blocks/browse_group_categories_block.js +14 -18
  55. data/app/javascript/spotlight/admin/blocks/pages_block.js +6 -10
  56. data/app/javascript/spotlight/admin/blocks/resources_block.js +33 -15
  57. data/app/javascript/spotlight/admin/blocks/solr_documents_base_block.js +11 -6
  58. data/app/javascript/spotlight/admin/blocks/solr_documents_embed_block.js +1 -0
  59. data/app/javascript/spotlight/admin/blocks/uploaded_items_block.js +4 -3
  60. data/app/javascript/spotlight/admin/copy_email_addresses.js +2 -0
  61. data/app/javascript/spotlight/admin/crop.js +45 -17
  62. data/app/javascript/spotlight/admin/croppable.js +8 -1
  63. data/app/javascript/spotlight/admin/croppable_modal.js +68 -0
  64. data/app/javascript/spotlight/admin/exhibits.js +15 -10
  65. data/app/javascript/spotlight/admin/form_observer.js +1 -1
  66. data/app/javascript/spotlight/admin/index.js +0 -10
  67. data/app/javascript/spotlight/admin/locks.js +15 -5
  68. data/app/javascript/spotlight/admin/pages.js +1 -1
  69. data/app/javascript/spotlight/admin/search_typeahead.js +62 -55
  70. data/app/javascript/spotlight/admin/spotlight_nestable.js +173 -50
  71. data/app/javascript/spotlight/admin/visibility_toggle.js +1 -11
  72. data/app/javascript/spotlight/controllers/index.js +8 -0
  73. data/app/javascript/spotlight/controllers/tag_selector_controller.js +203 -0
  74. data/app/javascript/spotlight/core.js +4 -6
  75. data/app/javascript/spotlight/index.js +2 -0
  76. data/app/javascript/spotlight/user/browse_group_categories.js +2 -0
  77. data/app/javascript/spotlight/user/carousel.js +3 -1
  78. data/app/javascript/spotlight/user/index.js +0 -2
  79. data/app/javascript/spotlight/user/zpr_links.js +2 -0
  80. data/app/models/sir_trevor_rails/block.rb +4 -5
  81. data/app/models/sir_trevor_rails/blocks/solr_documents_block.rb +1 -1
  82. data/app/models/sir_trevor_rails/blocks/solr_documents_embed_block.rb +1 -1
  83. data/app/models/sir_trevor_rails/blocks/uploaded_items_block.rb +1 -1
  84. data/app/models/spotlight/page_configurations.rb +1 -1
  85. data/app/services/spotlight/exhibit_import_export_service.rb +2 -2
  86. data/app/views/catalog/_add_tags.html.erb +2 -2
  87. data/app/views/catalog/_change_visibility.html.erb +1 -1
  88. data/app/views/catalog/_remove_tags.html.erb +2 -2
  89. data/app/views/layouts/spotlight/base.html.erb +24 -13
  90. data/app/views/layouts/spotlight/spotlight.html.erb +6 -6
  91. data/app/views/shared/_masthead.html.erb +4 -31
  92. data/app/views/shared/_site_sidebar.html.erb +1 -1
  93. data/app/views/shared/_user_util_links.html.erb +3 -1
  94. data/app/views/spotlight/accessibility/alt_text.html.erb +2 -2
  95. data/app/views/spotlight/admin_users/index.html.erb +3 -3
  96. data/app/views/spotlight/appearances/edit.html.erb +1 -1
  97. data/app/views/spotlight/browse/_search_box.html.erb +8 -8
  98. data/app/views/spotlight/browse/show.html.erb +1 -1
  99. data/app/views/spotlight/bulk_updates/_download.html.erb +1 -1
  100. data/app/views/spotlight/bulk_updates/_upload.html.erb +1 -1
  101. data/app/views/spotlight/catalog/_admin_header.html.erb +1 -1
  102. data/app/views/spotlight/catalog/_edit_default.html.erb +2 -1
  103. data/app/views/spotlight/catalog/select_image.html.erb +1 -0
  104. data/app/views/spotlight/contacts/_form.html.erb +1 -1
  105. data/app/views/spotlight/exhibits/_contact.html.erb +5 -6
  106. data/app/views/spotlight/exhibits/_delete.html.erb +1 -1
  107. data/app/views/spotlight/exhibits/_languages.html.erb +3 -2
  108. data/app/views/spotlight/featured_images/_form.html.erb +6 -2
  109. data/app/views/spotlight/featured_images/_upload_form.html.erb +1 -1
  110. data/app/views/spotlight/metadata_configurations/_metadata_field.html.erb +1 -1
  111. data/app/views/spotlight/metadata_configurations/edit.html.erb +6 -6
  112. data/app/views/spotlight/pages/show.html.erb +1 -1
  113. data/app/views/spotlight/resources/csv_upload/_form.html.erb +1 -1
  114. data/app/views/spotlight/resources/upload/_form.html.erb +1 -1
  115. data/app/views/spotlight/roles/index.html.erb +1 -1
  116. data/app/views/spotlight/searches/_form.html.erb +1 -1
  117. data/app/views/spotlight/shared/_dd3_item.html.erb +1 -1
  118. data/app/views/spotlight/sir_trevor/blocks/_browse_group_categories_block.html.erb +1 -1
  119. data/app/views/spotlight/sir_trevor/blocks/_solr_documents_block.html.erb +1 -1
  120. data/app/views/spotlight/sir_trevor/blocks/_solr_documents_carousel_block.html.erb +1 -1
  121. data/app/views/spotlight/sir_trevor/blocks/_uploaded_items_block.html.erb +1 -1
  122. data/app/views/spotlight/tags/index.html.erb +2 -3
  123. data/app/views/spotlight/translations/_import.html.erb +2 -2
  124. data/config/importmap.rb +5 -0
  125. data/config/locales/spotlight.en.yml +2 -0
  126. data/config/routes.rb +5 -3
  127. data/lib/generators/spotlight/assets/generator_common_utilities.rb +36 -0
  128. data/lib/generators/spotlight/assets/importmap_generator.rb +87 -0
  129. data/lib/generators/spotlight/assets/propshaft_generator.rb +96 -0
  130. data/lib/generators/spotlight/assets_generator.rb +22 -0
  131. data/lib/generators/spotlight/install_generator.rb +8 -36
  132. data/lib/generators/spotlight/scaffold_resource_generator.rb +1 -1
  133. data/lib/generators/spotlight/templates/assets/spotlight.scss +6 -0
  134. data/lib/generators/spotlight/templates/javascript/jquery-shim.js +1 -0
  135. data/lib/spotlight/engine.rb +7 -6
  136. data/lib/spotlight/version.rb +1 -1
  137. data/spec/support/features/capybara_wait_metadata_helper.rb +13 -0
  138. data/spec/support/features/test_features_helpers.rb +16 -30
  139. data/vendor/assets/javascripts/tiny-slider.js +3 -0
  140. metadata +37 -87
  141. data/app/assets/stylesheets/spotlight/#_accessibility.scss# +0 -12
  142. data/app/javascript/spotlight/admin/checkbox_submit.js +0 -75
  143. data/app/javascript/spotlight/admin/exhibit_tag_autocomplete.js +0 -39
  144. data/app/javascript/spotlight/user/report_a_problem.js +0 -30
  145. data/app/views/spotlight/browse/_tophat.html.erb +0 -1
  146. data/app/views/spotlight/catalog/_tophat_default.html.erb +0 -1
  147. data/app/views/spotlight/home_pages/_tophat.html.erb +0 -2
  148. data/app/views/spotlight/pages/_tophat.html.erb +0 -1
  149. data/lib/generators/spotlight/templates/spotlight.js +0 -1
  150. data/lib/generators/spotlight/templates/spotlight.scss +0 -5
  151. data/spec/support/features/capybara_default_max_wait_metadata_helper.rb +0 -20
  152. data/vendor/assets/javascripts/bootstrap-tagsinput.js +0 -530
  153. data/vendor/assets/javascripts/jquery.serializejson.js +0 -234
  154. data/vendor/assets/javascripts/nestable.js +0 -645
  155. data/vendor/assets/javascripts/sir-trevor.js +0 -23508
  156. data/vendor/assets/javascripts/typeahead.bundle.min.js +0 -7
  157. data/vendor/assets/stylesheets/bootstrap-tagsinput.css +0 -46
@@ -3,7 +3,7 @@
3
3
  module Spotlight
4
4
  # Displays the "Save this search" button and modal
5
5
  class SaveSearchComponent < ViewComponent::Base
6
- def initialize(button_classes: 'btn btn-secondary')
6
+ def initialize(button_classes: 'btn btn-outline-primary')
7
7
  @button_classes = button_classes
8
8
  super
9
9
  end
@@ -0,0 +1,17 @@
1
+ <%= render Blacklight::System::ModalComponent.new do |component| %>
2
+ <% component.with_title { t('spotlight.pages.form.select_image') } %>
3
+ <% component.with_body do %>
4
+ <div class='p-3 m-2'>
5
+ <input type="hidden" name="select_image_region">
6
+ <p class="instructions"><%= help_text.html_safe %></p>
7
+ <div class="mx-3" id="resource_block_iiif_cropper" data-behavior="iiif-cropper" data-cropper-key="select_image_<%=@block_item_id%>" data-index-id="<%=@index_id%>" data-crop-width="120" data-crop-height="120">
8
+ </div>
9
+ <% end %>
10
+ <% component.with_footer do %>
11
+ <div>
12
+ <input type='button' class='btn btn-outline-primary' value='Cancel' data-bl-dismiss='modal'>
13
+ <input id='save-cropping-selection' type='button' class='btn btn-primary' value='Save changes' data-bl-dismiss='modal'>
14
+ </div>
15
+ <% end %>
16
+ <% end %>
17
+
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ # Component to select section of image
5
+ class SelectImageComponent < ViewComponent::Base
6
+ def initialize(index_id, block_item_id)
7
+ super
8
+ @index_id = index_id
9
+ @block_item_id = block_item_id
10
+ end
11
+
12
+ def render?
13
+ true
14
+ end
15
+
16
+ def initial_crop_selection
17
+ Spotlight::Engine.config.thumbnail_initial_crop_selection
18
+ end
19
+
20
+ def help_text
21
+ t(:'spotlight.pages.form.instructions_html')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ # Blacklight Skip Link Component with conditional search link for Spotlight
5
+ class SkipLinkComponent < Blacklight::SkipLinkComponent
6
+ def initialize(render_search_link: true)
7
+ @render_search_link = render_search_link
8
+
9
+ super
10
+ end
11
+
12
+ def link_to_search
13
+ super if @render_search_link
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,40 @@
1
+ <div data-controller="tag-selector" class="tag-selector" data-tag-selector-translations-value="<%= translation_data.to_json %>" data-tag-selector-tags-value="<%= selected_tags %>">
2
+ <% if form.nil? %>
3
+ <%= text_field_tag field_name, selected_tags_value, class: 'tag-selector-input', placeholder: t('.no_js_placeholder'), data: { tag_selector_target: "tagsField" } %>
4
+ <% else %>
5
+ <%= form.text_field field_name, value: selected_tags_value, class: 'tag-selector-input', placeholder: t('.no_js_placeholder'), data: { tag_selector_target: "tagsField" } %>
6
+ <% end %>
7
+ <input type="hidden" value="<%= selected_tags_value %>" data-tag-selector-target="initialTags">
8
+
9
+ <div class='mb-3'>
10
+ <div class="tag-selection-wrapper" data-tag-selector-target="tagControlWrapper">
11
+ <div class="dropdown w-100 mb-3 d-inline-block" data-action="click@window->tag-selector#clickOutside">
12
+ <div data-tag-selector-target="tagSearchDropdown">
13
+ <div data-tag-selector-target="selectedTags"></div>
14
+ <div data-tag-selector-target="tagSearchInputWrapper" class="border rounded-bottom tag-selection-search-bar d-flex">
15
+ <button type="button" aria-label="toggle dropdown" class="btn btn-link text-secondary me-1 px-2" data-action="click->tag-selector#tagDropdown">
16
+ <span><%= search_icon_svg %></span>
17
+ </button>
18
+ <input class="flex-grow-1" data-action="input->tag-selector#search input->tag-selector#updateSearchResultsPlaceholder input->tag-selector#updateTagToAdd focus->tag-selector#tagDropdown keydown->tag-selector#handleKeydown"
19
+ data-tag-selector-target="tagSearch" placeholder="<%= t('.search') %>" aria-label="<%= t('.search') %>">
20
+ <button type="button" aria-label="toggle dropdown" class="dropdown-toggle btn btn-link text-secondary" data-bs-toggle="dropdown" data-action="click->tag-selector#tagDropdown"></button>
21
+ </div>
22
+ </div>
23
+ <div data-tag-selector-target="dropdownContent" class="dropdown-content d-none tags-group border rounded">
24
+ <% all_tags.each do |tag| %>
25
+ <label class="d-block">
26
+ <input type="checkbox" <%= 'checked' if selected?(tag) %> data-action="click->tag-selector#tagUpdate" data-tag-selector-target="searchResultTags" data-tag="<%= tag %>">
27
+ <%= tag %>
28
+ </label>
29
+ <% end %>
30
+ <% if all_tags.empty? %>
31
+ <label class="no-results"><%= t('.no_results') %></label>
32
+ <% end %>
33
+ <label class="d-none" data-tag-selector-target="addNewTagWrapper">
34
+ <input type="checkbox" disabled data-action="click->tag-selector#tagCreate" data-tag-selector-target="newTag" data-tag=""> Add new tag
35
+ </label>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ # Displays a tag selection input
5
+ # This uses a plain text input that acts-as-taggable-on expects.
6
+ class TagSelectorComponent < ViewComponent::Base
7
+ # selected_tags_value is a comma delimited string of tags
8
+ def initialize(field_name:, all_tags:, selected_tags_value: nil, form: nil)
9
+ @form = form
10
+ @field_name = field_name
11
+ @selected_tags_value = selected_tags_value || ''
12
+ @all_tags = all_tags&.sort_by { |tag| (tag.respond_to?(:name) ? tag.name : tag).downcase }
13
+
14
+ super
15
+ end
16
+
17
+ def selected_tags
18
+ selected_tags_value.split(',').map(&:strip)
19
+ end
20
+
21
+ def search_icon_svg
22
+ render Blacklight::Icons::SearchComponent.new
23
+ end
24
+
25
+ private
26
+
27
+ # To pass to the JS
28
+ def translation_data
29
+ {
30
+ add_new_tag: t('.add_new_tag'),
31
+ remove: t('.remove')
32
+ }
33
+ end
34
+
35
+ def selected?(tag)
36
+ selected_tags.include?(tag.respond_to?(:name) ? tag.name : tag)
37
+ end
38
+
39
+ attr_reader :form, :field_name, :selected_tags_value, :all_tags
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ en:
2
+ add_new_tag: "Add a new tag"
3
+ no_js_placeholder: "Enter tags delimited by commas"
4
+ no_results: "No results found"
5
+ remove: "Remove"
6
+ search: "Type to search and add tags"
@@ -0,0 +1,8 @@
1
+ <div class="container site-title-container">
2
+ <div class="site-title-wrapper">
3
+ <a href="<%= helpers.current_exhibit ? helpers.spotlight.exhibit_path(helpers.current_exhibit) : helpers.spotlight.exhibits_path %>" class="link-unstyled">
4
+ <%= title %>
5
+ <%= subtitle %>
6
+ </a>
7
+ </div>
8
+ </div>
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ # Draws the title in the masthead
5
+ class TitleComponent < ViewComponent::Base
6
+ def initialize(title:, subtitle:)
7
+ @title = title
8
+ @subtitle = subtitle
9
+ super
10
+ end
11
+
12
+ def title
13
+ tag.h1 @title, class: 'site-title h2'
14
+ end
15
+
16
+ def subtitle
17
+ return unless @subtitle
18
+
19
+ tag.small(@subtitle, class: 'd-none d-md-block py-2 fs-4')
20
+ end
21
+ end
22
+ end
@@ -9,7 +9,7 @@ module Spotlight
9
9
 
10
10
  def alt_text
11
11
  # Sort by newest except for the homepage, which is always first
12
- pages_with_alt = @exhibit.pages.order(Arel.sql('id = 1 DESC, created_at DESC')).select { |elem| elem.content.any?(&:alt_text?) }
12
+ pages_with_alt = @exhibit.pages.order(Arel.sql('id = 1 DESC, created_at DESC')).select { |elem| elem.content.any?(&:supports_alt_text?) }
13
13
  @pages = pages_with_alt.map { |page| get_alt_info(page) }
14
14
  @has_alt_text = @pages.sum { |page| page[:has_alt_text] }
15
15
  @total_alt_items = @pages.sum { |page| page[:can_have_alt_text] }
@@ -23,7 +23,7 @@ module Spotlight
23
23
  can_have_alt_text = 0
24
24
  has_alt_text = 0
25
25
  page.content.each do |content|
26
- next unless content.alt_text?
26
+ next unless content.supports_alt_text?
27
27
 
28
28
  content.item&.each_value do |item|
29
29
  can_have_alt_text += 1
@@ -30,7 +30,6 @@ module Spotlight
30
30
  if Blacklight::VERSION > '8'
31
31
  blacklight_config.show.document_component = Spotlight::DocumentComponent
32
32
  else
33
- blacklight_config.show.partials.unshift 'tophat'
34
33
  blacklight_config.show.partials.unshift 'curation_mode_toggle'
35
34
  end
36
35
  end
@@ -135,6 +134,12 @@ module Spotlight
135
134
  end
136
135
  end
137
136
 
137
+ def select_image
138
+ @index_id = params[:index_id]
139
+ @block_item_id = params[:block_item_id]
140
+ respond_to :html
141
+ end
142
+
138
143
  protected
139
144
 
140
145
  def load_document
@@ -194,7 +199,7 @@ module Spotlight
194
199
  def solr_document_params
195
200
  params.require(:solr_document).permit(:exhibit_tag_list,
196
201
  uploaded_resource: [:url],
197
- sidecar: [:public, { data: [editable_solr_document_params] }])
202
+ sidecar: [:public, { data: editable_solr_document_params }])
198
203
  end
199
204
 
200
205
  def editable_solr_document_params
@@ -12,13 +12,19 @@ module Spotlight
12
12
 
13
13
  def destroy
14
14
  @contact_email.destroy
15
- render json: { success: true, error: nil }
15
+ respond_to do |format|
16
+ format.turbo_stream { render turbo_stream: turbo_stream.remove(@contact_email) }
17
+ format.json { render json: { success: true, error: nil } }
18
+ end
16
19
  end
17
20
 
18
21
  private
19
22
 
20
23
  def record_not_found(_error)
21
- render json: { success: false, error: 'Not Found' }, status: :not_found
24
+ respond_to do |format|
25
+ format.turbo_stream { head :not_found }
26
+ format.json { render json: { success: false, error: 'Not Found' }, status: :not_found }
27
+ end
22
28
  end
23
29
  end
24
30
  end
@@ -33,10 +33,15 @@ module Spotlight
33
33
  def destroy
34
34
  @language.destroy
35
35
 
36
- redirect_to(
37
- spotlight.edit_exhibit_path(@exhibit, tab: 'language'),
38
- notice: t(:'helpers.submit.language.destroyed', model: @language.model_name.human.downcase)
39
- )
36
+ respond_to do |format|
37
+ format.turbo_stream { render turbo_stream: turbo_stream.remove(@language) }
38
+ format.html do
39
+ redirect_to(
40
+ spotlight.edit_exhibit_path(@exhibit, tab: 'language'),
41
+ notice: t(:'helpers.submit.language.destroyed', model: @language.model_name.human.downcase)
42
+ )
43
+ end
44
+ end
40
45
  end
41
46
 
42
47
  private
@@ -30,6 +30,13 @@ module Spotlight
30
30
  current_site.title.presence
31
31
  end
32
32
 
33
+ def content?(field)
34
+ return content_for?(field) unless Rails.configuration.action_view.annotate_rendered_view_with_filenames && content_for(field).present?
35
+
36
+ stripped_content = content_for(field).gsub(/<!-- BEGIN .+? -->/, '').gsub(/<!-- END .+? -->/, '').strip
37
+ stripped_content.present?
38
+ end
39
+
33
40
  # Returns the url for the current page in the new locale. This may be
34
41
  # overridden in downstream applications where our naive use of `url_for`
35
42
  # is insufficient to generate the expected routes
@@ -34,5 +34,9 @@ module Spotlight
34
34
  def form_prefix(f)
35
35
  f.object_name.parameterize(separator: '_')
36
36
  end
37
+
38
+ def input_prefix(f)
39
+ f.object_name
40
+ end
37
41
  end
38
42
  end
@@ -3,17 +3,37 @@
3
3
  module Spotlight
4
4
  # HTML <meta> tag helpers
5
5
  module MetaHelper
6
+ def description(description)
7
+ content_for(:meta) { ActionController::Base.helpers.tag.meta(name: 'description', content: description) } if description
8
+ end
9
+
10
+ def card(type, &block)
11
+ card = {}
12
+ block.call(card) if block_given?
13
+ content_for(:meta) { build_tags(card, type) }
14
+ end
15
+
16
+ # rubocop:disable Rails/OutputSafety
17
+ def build_tags(attributes, tag_field)
18
+ type_fields = { 'og' => 'property', 'twitter' => 'name' }
19
+ attributes.map do |key, value|
20
+ ActionController::Base.helpers.tag.meta("#{type_fields[tag_field]}": "#{tag_field}:#{key}", content: value) if value
21
+ end.compact.join("\n").html_safe
22
+ end
23
+ # rubocop:enable Rails/OutputSafety
24
+
6
25
  def add_exhibit_meta_content
7
26
  exhibit_twitter_card_content
8
27
  exhibit_opengraph_content
9
28
  end
10
29
 
11
30
  def exhibit_twitter_card_content
12
- twitter_card('summary') do |card|
13
- card.url exhibit_root_url(current_exhibit)
14
- card.title current_exhibit.title
15
- card.description current_exhibit.subtitle
16
- card.image meta_image if current_exhibit.thumbnail
31
+ card('twitter') do |card|
32
+ card['card'] = 'summary'
33
+ card['url'] = spotlight.exhibit_root_url(current_exhibit)
34
+ card['title'] = current_exhibit.title
35
+ card['description'] = current_exhibit.subtitle
36
+ card['image'] = meta_image if current_exhibit.thumbnail
17
37
  end
18
38
  end
19
39
 
@@ -22,10 +42,10 @@ module Spotlight
22
42
  end
23
43
 
24
44
  def exhibit_opengraph_content
25
- opengraph do |graph|
26
- graph.title current_exhibit.title
27
- graph.image meta_image if current_exhibit.thumbnail
28
- graph.site_name site_title
45
+ card('og') do |graph|
46
+ graph['title'] = current_exhibit.title
47
+ graph['image'] = meta_image if current_exhibit.thumbnail
48
+ graph['site_name'] = site_title
29
49
  end
30
50
  end
31
51
 
@@ -35,20 +55,21 @@ module Spotlight
35
55
  end
36
56
 
37
57
  def page_twitter_card_content(page)
38
- twitter_card('summary_large_image') do |card|
39
- card.title page.title
40
- card.image page.thumbnail.iiif_url if page.thumbnail
58
+ card('twitter') do |card|
59
+ card['card'] = 'summary_large_image'
60
+ card['title'] = page.title
61
+ card['image'] = page.thumbnail.iiif_url if page.thumbnail
41
62
  end
42
63
  end
43
64
 
44
65
  def page_opengraph_content(page)
45
- opengraph do |graph|
46
- graph.type 'article'
47
- graph.site_name application_name
48
- graph.title page.title
49
- graph.send('image', page.thumbnail.iiif_url) if page.thumbnail
50
- graph.send('article:published_time', page.created_at.iso8601)
51
- graph.send('article:modified_time', page.updated_at.iso8601)
66
+ card('og') do |graph|
67
+ graph['type'] = 'article'
68
+ graph['site_name'] = application_name
69
+ graph['title'] = page.title
70
+ graph['image'] = page.thumbnail.iiif_url if page.thumbnail
71
+ graph['article:published_time'] = page.created_at.iso8601
72
+ graph['article:modified_time'] = page.updated_at.iso8601
52
73
  end
53
74
  end
54
75
 
@@ -58,20 +79,21 @@ module Spotlight
58
79
  end
59
80
 
60
81
  def browse_twitter_card_content(browse)
61
- twitter_card('summary_large_image') do |card|
62
- card.title browse.title
63
- card.image browse.thumbnail.iiif_url if browse.thumbnail
82
+ card('twitter') do |card|
83
+ card['card'] = 'summary_large_image'
84
+ card['title'] = browse.title
85
+ card['image'] = browse.thumbnail.iiif_url if browse.thumbnail
64
86
  end
65
87
  end
66
88
 
67
89
  def browse_opengraph_content(browse)
68
- opengraph do |graph|
69
- graph.type 'article'
70
- graph.site_name application_name
71
- graph.title browse.title
72
- graph.send('image', browse.thumbnail.iiif_url) if browse.thumbnail
73
- graph.send('article:published_time', browse.created_at.iso8601)
74
- graph.send('article:modified_time', browse.updated_at.iso8601)
90
+ card('og') do |graph|
91
+ graph['type'] = 'article'
92
+ graph['site_name'] = application_name
93
+ graph['title'] = browse.title
94
+ graph['image'] = browse.thumbnail.iiif_url if browse.thumbnail
95
+ graph['article:published_time'] = browse.created_at.iso8601
96
+ graph['article:modified_time'] = browse.updated_at.iso8601
75
97
  end
76
98
  end
77
99
 
@@ -83,19 +105,20 @@ module Spotlight
83
105
  def document_twitter_card_content(document)
84
106
  presenter = document_presenter(document)
85
107
 
86
- twitter_card('summary_large_image') do |card|
87
- card.title presenter.heading
88
- card.image document.first(blacklight_config.index.thumbnail_field)
108
+ card('twitter') do |card|
109
+ card['card'] = 'summary_large_image'
110
+ card['title'] = presenter.heading
111
+ card['image'] = document.first(blacklight_config.index.thumbnail_field)
89
112
  end
90
113
  end
91
114
 
92
115
  def document_opengraph_content(document)
93
116
  presenter = document_presenter(document)
94
117
 
95
- opengraph do |graph|
96
- graph.site_name application_name
97
- graph.title presenter.heading
98
- graph.send('image', document.first(blacklight_config.index.thumbnail_field))
118
+ card('og') do |graph|
119
+ graph['site_name'] = application_name
120
+ graph['title'] = presenter.heading
121
+ graph['image'] = document.first(blacklight_config.index.thumbnail_field)
99
122
  end
100
123
  end
101
124
  end
@@ -59,4 +59,4 @@ export default class {
59
59
  });
60
60
  });
61
61
  }
62
- }
62
+ }
@@ -1,3 +1,5 @@
1
+ import { fetchAutocompleteJSON } from 'spotlight/admin/search_typeahead';
2
+
1
3
  (function ($){
2
4
  SirTrevor.BlockMixins.Autocompleteable = {
3
5
  mixinName: "Autocompleteable",
@@ -7,59 +9,93 @@
7
9
  this.on("onRender", this.addAutocompletetoSirTrevorForm);
8
10
 
9
11
  if (this['autocomplete_url'] === undefined) {
10
- this.autocomplete_url = function() { return $('form[data-autocomplete-url]').data('autocomplete-url').replace("%25QUERY", "%QUERY"); };
12
+ this.autocomplete_url = function() { return $('form[data-autocomplete-url]').data('autocomplete-url'); };
13
+ }
14
+
15
+ if (this['autocomplete_fetch'] === undefined) {
16
+ this.autocomplete_fetch = this.fetchAutocompleteResults;
11
17
  }
12
18
 
13
19
  if (this['transform_autocomplete_results'] === undefined) {
14
20
  this.transform_autocomplete_results = (val) => val
15
21
  }
16
22
 
23
+ if (this['highlight'] === undefined) {
24
+ this.highlight = function(value) {
25
+ if (!value) return '';
26
+ const queryValue = this.getQueryValue().trim();
27
+ return queryValue ? value.replace(new RegExp(queryValue, 'gi'), '<strong>$&</strong>') : value;
28
+ }
29
+ }
30
+
17
31
  if (this['autocomplete_control'] === undefined) {
18
- this.autocomplete_control = function() { return `<input type="text" class="st-input-string form-control item-input-field" data-twitter-typeahead="true" placeholder="${i18n.t("blocks:autocompleteable:placeholder")}"/>` };
32
+ this.autocomplete_control = function() {
33
+ const autocompleteID = this.autocompleteID();
34
+ return `
35
+ <auto-complete src="${this.autocomplete_url()}" for="${autocompleteID}-popup" fetch-on-empty>
36
+ <input type="text" name="${autocompleteID}" placeholder="${i18n.t("blocks:autocompleteable:placeholder")}" data-default-typeahead>
37
+ <ul id="${autocompleteID}-popup"></ul>
38
+ <div id="${autocompleteID}-popup-feedback" class="sr-only visually-hidden"></div>
39
+ </auto-complete>
40
+ ` };
19
41
  }
20
42
 
21
- if (this['bloodhoundOptions'] === undefined) {
22
- this.bloodhoundOptions = function() {
23
- return {
24
- remote: {
25
- url: this.autocomplete_url(),
26
- filter: this.transform_autocomplete_results
27
- }
28
- };
29
- };
43
+ if (this['autocomplete_element_template'] === undefined) {
44
+ this.autocomplete_element_template = function(item) {
45
+ return `<li role="option" data-autocomplete-value="${item.id}">${this.autocomplete_template(item)}</li>`
46
+ }
30
47
  }
31
48
  },
32
49
 
33
- addAutocompletetoSirTrevorForm: function() {
34
- $('[data-twitter-typeahead]', this.inner).spotlightSearchTypeAhead({bloodhound: this.bloodhound(), template: this.autocomplete_template}).on('typeahead:selected typeahead:autocompleted', this.autocompletedHandler()).on( 'focus', function() {
35
- if($(this).val() === '') {
36
- $(this).data().ttTypeahead.input.trigger('queryChanged', '');
37
- }
38
- });
50
+ queryTokenizer: function(query) {
51
+ return query.trim().toLowerCase().split(/\s+/).filter(Boolean);
39
52
  },
40
53
 
41
- autocompletedHandler: function(e, data) {
42
- var context = this;
54
+ filterResults: function(data, query) {
55
+ const queryStrings = this.queryTokenizer(query);
56
+ return data.filter(item => {
57
+ const lowerTitle = item.title.toLowerCase();
58
+ return queryStrings.some(queryString => lowerTitle.includes(queryString));
59
+ });
60
+ },
43
61
 
44
- return function(e, data) {
45
- $(this).typeahead("val", "");
46
- $(this).val("");
62
+ fetchAutocompleteResults: async function(url) {
63
+ const result = await fetchAutocompleteJSON(url);
64
+ const transformed = this.transform_autocomplete_results(result);
65
+ this.fetchedData = {};
66
+ transformed.map(item => this.fetchedData[item.id] = item);
67
+ return transformed.map(item => this.autocomplete_element_template(item)).join('');
68
+ },
47
69
 
48
- context.createItemPanel($.extend(data, {display: "true"}));
70
+ fetchOnceAndFilterLocalResults: async function(url) {
71
+ if (this.fetchedData === undefined) {
72
+ await this.fetchAutocompleteResults(url);
49
73
  }
74
+ const query = url.searchParams.get('q');
75
+ const data = Object.values(this.fetchedData);
76
+ const filteredData = query ? this.filterResults(data, query) : data;
77
+ return filteredData.map(item => this.autocomplete_element_template(item)).join('');
78
+ },
79
+
80
+ autocompleteID: function() {
81
+ return this.blockID + '-autocomplete';
50
82
  },
51
83
 
52
- bloodhound: function() {
53
- var block = this;
54
- var results = new Bloodhound(Object.assign({
55
- datumTokenizer: function(d) {
56
- return Bloodhound.tokenizers.whitespace(d.title);
57
- },
58
- queryTokenizer: Bloodhound.tokenizers.whitespace,
59
- limit: 100,
60
- }, block.bloodhoundOptions()));
61
- results.initialize();
62
- return results;
84
+ getQueryValue: function() {
85
+ const completer = this.inner.querySelector("auto-complete > input");
86
+ return completer.value;
87
+ },
88
+
89
+ addAutocompletetoSirTrevorForm: function() {
90
+ const completer = this.inner.querySelector("auto-complete");
91
+ completer.fetchResult = this.autocomplete_fetch.bind(this);
92
+ completer.addEventListener('auto-complete-change', (e) => {
93
+ const data = this.fetchedData[e.relatedTarget.value];
94
+ if (e.relatedTarget.value && data) {
95
+ e.value = e.relatedTarget.value = '';
96
+ this.createItemPanel({ ...data, display: "true" });
97
+ }
98
+ });
63
99
  },
64
100
  },
65
101
 
@@ -1,4 +1,5 @@
1
1
  import Core from 'spotlight/core'
2
+
2
3
  (function ($){
3
4
  Core.Block = SirTrevor.Block.extend({
4
5
  scribeOptions: {
@@ -8,22 +8,18 @@ SirTrevor.Blocks.Browse = (function(){
8
8
  icon_name: "browse",
9
9
 
10
10
  autocomplete_url: function() {
11
- return $(this.inner).closest('form[data-autocomplete-exhibit-searches-path]').data('autocomplete-exhibit-searches-path').replace("%25QUERY", "%QUERY");
11
+ return document.getElementById(this.instanceID).closest('form[data-autocomplete-exhibit-searches-path]').dataset.autocompleteExhibitSearchesPath;
12
+ },
13
+
14
+ autocomplete_fetch: function(url) {
15
+ return this.fetchOnceAndFilterLocalResults(url);
12
16
  },
13
17
 
14
18
  autocomplete_template: function(obj) {
15
19
  const thumbnail = obj.thumbnail_image_url ? `<div class="document-thumbnail"><img class="img-thumbnail" src="${obj.thumbnail_image_url}" /></div>` : ''
20
+ const description = obj.description ? `<small>&nbsp;&nbsp;${obj.description}</small>` : '';
16
21
  return `<div class="autocomplete-item${!obj.published ? ' blacklight-private' : ''}">${thumbnail}
17
- <span class="autocomplete-title">${obj.full_title}</span><br/><small>&nbsp;&nbsp;${obj.description}</small></div>`
18
- },
19
-
20
- bloodhoundOptions: function() {
21
- return {
22
- prefetch: {
23
- url: this.autocomplete_url(),
24
- ttl: 0
25
- }
26
- };
22
+ <span class="autocomplete-title">${this.highlight(obj.full_title)}</span>${description}</div>`;
27
23
  },
28
24
 
29
25
  _itemPanel: function(data) {
@@ -36,7 +32,7 @@ SirTrevor.Blocks.Browse = (function(){
36
32
  }
37
33
  var resource_id = data.slug || data.id;
38
34
  var markup = `
39
- <li class="field form-inline dd-item dd3-item" data-resource-id="${resource_id}" data-id="${index}" id="${this.formId(index)}">
35
+ <li class="field dd-item dd3-item" data-resource-id="${resource_id}" data-id="${index}" id="${this.formId(index)}">
40
36
  <input type="hidden" name="item[${index}][id]" value="${resource_id}" />
41
37
  <input type="hidden" name="item[${index}][full_title]" value="${(data.full_title || data.title)}" />
42
38
  <input data-property="weight" type="hidden" name="item[${index}][weight]" value="${data.weight}" />