blacklight 9.0.0.beta1 → 9.0.0.beta3

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 (220) hide show
  1. checksums.yaml +4 -4
  2. data/.github/matrix.json +47 -0
  3. data/.github/workflows/build.yml +16 -0
  4. data/.github/workflows/lint.yml +25 -0
  5. data/.github/workflows/main.yml +22 -0
  6. data/.github/workflows/release_7_x_scheduled.yml +39 -0
  7. data/.github/workflows/release_8_x_scheduled.yml +39 -0
  8. data/.github/workflows/test.yml +53 -0
  9. data/.rubocop.yml +81 -3
  10. data/.rubocop_todo.yml +43 -67
  11. data/.solr_wrapper.yml +2 -0
  12. data/VERSION +1 -1
  13. data/app/assets/builds/blacklight.css +17 -18
  14. data/app/assets/javascripts/blacklight/blacklight.esm.js +43 -73
  15. data/app/assets/javascripts/blacklight/blacklight.esm.js.map +1 -1
  16. data/app/assets/javascripts/blacklight/blacklight.js +43 -73
  17. data/app/assets/javascripts/blacklight/blacklight.js.map +1 -1
  18. data/app/assets/stylesheets/blacklight/_blacklight_base.scss +0 -1
  19. data/app/assets/stylesheets/blacklight/_bootstrap_overrides.scss +4 -0
  20. data/app/assets/stylesheets/blacklight/_facets.scss +2 -2
  21. data/app/assets/stylesheets/blacklight/_header.scss +4 -0
  22. data/app/assets/stylesheets/blacklight/_modal.scss +9 -8
  23. data/app/assets/stylesheets/blacklight/_pagination.scss +1 -3
  24. data/app/assets/stylesheets/blacklight/_search_history.scss +0 -4
  25. data/app/assets/stylesheets/blacklight/blacklight_defaults.scss +3 -0
  26. data/app/components/blacklight/advanced_search_form_component.rb +2 -2
  27. data/app/components/blacklight/constraint_layout_component.html.erb +1 -6
  28. data/app/components/blacklight/constraint_layout_component.rb +9 -0
  29. data/app/components/blacklight/constraints_component.rb +2 -2
  30. data/app/components/blacklight/document/action_component.rb +1 -3
  31. data/app/components/blacklight/document/bookmark_component.html.erb +2 -2
  32. data/app/components/blacklight/document/bookmark_component.rb +2 -2
  33. data/app/components/blacklight/document/more_like_this_component.rb +2 -2
  34. data/app/components/blacklight/document/page_header_component.rb +2 -2
  35. data/app/components/blacklight/document/thumbnail_component.html.erb +3 -7
  36. data/app/components/blacklight/document/thumbnail_component.rb +7 -6
  37. data/app/components/blacklight/document_component.html.erb +3 -3
  38. data/app/components/blacklight/document_component.rb +5 -5
  39. data/app/components/blacklight/document_title_component.rb +6 -10
  40. data/app/components/blacklight/facet_field_checkboxes_component.rb +4 -18
  41. data/app/components/blacklight/facet_field_component.rb +4 -15
  42. data/app/components/blacklight/facet_field_filter_component.rb +4 -19
  43. data/app/components/blacklight/facet_field_inclusive_constraint_component.rb +4 -23
  44. data/app/components/blacklight/facet_field_list_component.rb +4 -30
  45. data/app/components/blacklight/facet_field_no_layout_component.rb +4 -8
  46. data/app/components/blacklight/facet_field_pagination_component.html.erb +2 -2
  47. data/app/components/blacklight/facet_item_component.rb +4 -72
  48. data/app/components/blacklight/facet_item_pivot_component.rb +1 -1
  49. data/app/components/blacklight/facets/checkboxes_component.rb +26 -0
  50. data/app/components/blacklight/facets/count_component.rb +23 -0
  51. data/app/components/blacklight/{facet_field_component.html.erb → facets/field_component.html.erb} +1 -1
  52. data/app/components/blacklight/facets/field_component.rb +23 -0
  53. data/app/components/blacklight/facets/filters_component.html.erb +4 -0
  54. data/app/components/blacklight/facets/filters_component.rb +39 -0
  55. data/app/components/blacklight/{facet_field_inclusive_constraint_component.html.erb → facets/inclusive_constraint_component.html.erb} +1 -1
  56. data/app/components/blacklight/facets/inclusive_constraint_component.rb +31 -0
  57. data/app/components/blacklight/{facet_field_filter_component.html.erb → facets/index_navigation_component.html.erb} +1 -1
  58. data/app/components/blacklight/facets/index_navigation_component.rb +32 -0
  59. data/app/components/blacklight/facets/item_component.rb +73 -0
  60. data/app/components/blacklight/facets/list_component.html.erb +11 -0
  61. data/app/components/blacklight/facets/list_component.rb +40 -0
  62. data/app/components/blacklight/facets/no_layout_component.rb +16 -0
  63. data/app/components/blacklight/facets/selected_value_component.rb +29 -0
  64. data/app/components/blacklight/facets/suggest_component.html.erb +12 -0
  65. data/app/components/blacklight/facets/suggest_component.rb +22 -0
  66. data/app/components/blacklight/icons/remove_component.rb +2 -2
  67. data/app/components/blacklight/metadata_field_plain_text_layout_component.rb +2 -2
  68. data/app/components/blacklight/response/facet_group_component.html.erb +3 -3
  69. data/app/components/blacklight/response/facet_group_component.rb +9 -1
  70. data/app/components/blacklight/system/dropdown_component.html.erb +1 -1
  71. data/app/components/blacklight/system/dropdown_component.rb +1 -1
  72. data/app/components/blacklight/system/flash_message_component.html.erb +1 -1
  73. data/app/components/blacklight/system/flash_message_component.rb +0 -9
  74. data/app/components/blacklight/system/modal_component.html.erb +1 -2
  75. data/app/components/blacklight/top_navbar_component.html.erb +1 -1
  76. data/app/controllers/concerns/blacklight/bookmarks.rb +3 -3
  77. data/app/controllers/concerns/blacklight/catalog.rb +16 -27
  78. data/app/controllers/concerns/blacklight/controller.rb +1 -1
  79. data/app/controllers/concerns/blacklight/facetable.rb +34 -0
  80. data/app/controllers/concerns/blacklight/search_context.rb +1 -1
  81. data/app/controllers/concerns/blacklight/searchable.rb +1 -1
  82. data/app/helpers/blacklight/catalog_helper_behavior.rb +1 -1
  83. data/app/helpers/blacklight/configuration_helper_behavior.rb +2 -2
  84. data/app/helpers/blacklight/document_helper_behavior.rb +3 -1
  85. data/app/helpers/blacklight/facets_helper_behavior.rb +9 -0
  86. data/app/helpers/blacklight/icon_helper_behavior.rb +2 -2
  87. data/app/javascript/blacklight-frontend/bookmark_toggle.js +7 -2
  88. data/app/javascript/blacklight-frontend/checkbox_submit.js +8 -1
  89. data/app/javascript/blacklight-frontend/debounce.js +1 -1
  90. data/app/javascript/blacklight-frontend/facet_suggest.js +23 -3
  91. data/app/javascript/blacklight-frontend/index.js +0 -2
  92. data/app/javascript/blacklight-frontend/modal.js +2 -6
  93. data/app/javascript/blacklight-frontend/search_context.js +3 -2
  94. data/app/models/facet_search_builder.rb +5 -0
  95. data/app/presenters/blacklight/facet_field_presenter.rb +1 -1
  96. data/app/presenters/blacklight/json_presenter.rb +8 -4
  97. data/app/presenters/blacklight/rendering/helper_method.rb +4 -4
  98. data/app/presenters/blacklight/rendering/join.rb +2 -2
  99. data/app/services/blacklight/facet_search_service.rb +44 -0
  100. data/app/services/blacklight/field_retriever.rb +1 -1
  101. data/app/services/blacklight/search_service.rb +6 -6
  102. data/app/values/blacklight/types.rb +2 -2
  103. data/app/views/catalog/_facet_pivot.html.erb +1 -1
  104. data/app/views/catalog/_home_text.html.erb +2 -2
  105. data/app/views/catalog/_sort_and_per_page.html.erb +1 -1
  106. data/app/views/catalog/facet.html.erb +8 -10
  107. data/config/locales/blacklight.ar.yml +14 -13
  108. data/config/locales/blacklight.ca.yml +1 -0
  109. data/config/locales/blacklight.de.yml +13 -12
  110. data/config/locales/blacklight.en.yml +1 -0
  111. data/config/locales/blacklight.es.yml +13 -12
  112. data/config/locales/blacklight.fr.yml +14 -13
  113. data/config/locales/blacklight.hu.yml +15 -14
  114. data/config/locales/blacklight.it.yml +15 -14
  115. data/config/locales/blacklight.nl.yml +13 -12
  116. data/config/locales/blacklight.pt-BR.yml +15 -14
  117. data/config/locales/blacklight.sq.yml +14 -13
  118. data/config/locales/blacklight.zh.yml +11 -10
  119. data/lib/blacklight/abstract_repository.rb +2 -2
  120. data/lib/blacklight/abstract_search_builder.rb +154 -0
  121. data/lib/blacklight/configuration/context.rb +3 -3
  122. data/lib/blacklight/configuration/facet_field.rb +6 -6
  123. data/lib/blacklight/configuration/field.rb +4 -4
  124. data/lib/blacklight/configuration/fields.rb +0 -1
  125. data/lib/blacklight/configuration/search_field.rb +1 -1
  126. data/lib/blacklight/configuration/view_config.rb +2 -2
  127. data/lib/blacklight/configuration.rb +9 -4
  128. data/lib/blacklight/facet_search_builder.rb +18 -0
  129. data/lib/blacklight/nested_open_struct_with_hash_access.rb +1 -1
  130. data/lib/blacklight/open_struct_with_hash_access.rb +2 -2
  131. data/lib/blacklight/search_builder.rb +1 -159
  132. data/lib/blacklight/search_state/filter_field.rb +4 -4
  133. data/lib/blacklight/search_state/pivot_filter_field.rb +4 -4
  134. data/lib/blacklight/solr/abstract_filter_query_builder.rb +77 -0
  135. data/lib/blacklight/solr/default_filter_query_builder.rb +20 -0
  136. data/lib/blacklight/solr/facet_search_builder_behavior.rb +62 -0
  137. data/lib/blacklight/solr/repository.rb +12 -10
  138. data/lib/blacklight/solr/response/facets.rb +2 -2
  139. data/lib/blacklight/solr/response/params.rb +0 -4
  140. data/lib/blacklight/solr/response.rb +5 -1
  141. data/lib/blacklight/solr/search_builder_behavior.rb +17 -132
  142. data/lib/blacklight.rb +6 -4
  143. data/lib/generators/blacklight/assets/importmap_generator.rb +3 -5
  144. data/lib/generators/blacklight/assets_generator.rb +1 -1
  145. data/lib/generators/blacklight/search_builder_generator.rb +1 -1
  146. data/lib/generators/blacklight/templates/.solr_wrapper.yml +2 -0
  147. data/lib/generators/blacklight/templates/catalog_controller.rb +5 -3
  148. data/lib/generators/blacklight/templates/solr/conf/solrconfig.xml +0 -4
  149. data/package.json +3 -3
  150. data/spec/components/blacklight/advanced_search_form_component_spec.rb +18 -22
  151. data/spec/components/blacklight/constraint_layout_component_spec.rb +8 -8
  152. data/spec/components/blacklight/constraints_component_spec.rb +11 -11
  153. data/spec/components/blacklight/document/action_component_spec.rb +23 -15
  154. data/spec/components/blacklight/document/group_component_spec.rb +10 -15
  155. data/spec/components/blacklight/document/page_header_component_spec.rb +35 -28
  156. data/spec/components/blacklight/document/sidebar_component_spec.rb +5 -11
  157. data/spec/components/blacklight/document_component_spec.rb +98 -65
  158. data/spec/components/blacklight/facet_component_spec.rb +12 -8
  159. data/spec/components/blacklight/facet_item_pivot_component_spec.rb +12 -12
  160. data/spec/components/blacklight/{facet_field_checkboxes_component_spec.rb → facets/checkboxes_component_spec.rb} +13 -13
  161. data/spec/components/blacklight/facets/filters_component_spec.rb +36 -0
  162. data/spec/components/blacklight/facets/index_navigation_component_spec.rb +40 -0
  163. data/spec/components/blacklight/{facet_item_component_spec.rb → facets/item_component_spec.rb} +10 -10
  164. data/spec/components/blacklight/{facet_field_list_component_spec.rb → facets/list_component_spec.rb} +23 -23
  165. data/spec/components/blacklight/facets/suggest_component_spec.rb +68 -0
  166. data/spec/components/blacklight/header_component_spec.rb +2 -4
  167. data/spec/components/blacklight/hidden_search_state_component_spec.rb +7 -7
  168. data/spec/components/blacklight/metadata_field_component_spec.rb +17 -15
  169. data/spec/components/blacklight/response/facet_group_component_spec.rb +37 -0
  170. data/spec/components/blacklight/response/pagination_component_spec.rb +1 -1
  171. data/spec/components/blacklight/response/spellcheck_component_spec.rb +1 -1
  172. data/spec/components/blacklight/search_bar_component_spec.rb +4 -4
  173. data/spec/components/blacklight/search_context/server_applied_params_component_spec.rb +2 -2
  174. data/spec/components/blacklight/search_context/server_item_pagination_component_spec.rb +3 -5
  175. data/spec/components/blacklight/skip_link_component_spec.rb +8 -11
  176. data/spec/components/blacklight/start_over_button_component_spec.rb +4 -4
  177. data/spec/components/blacklight/system/dropdown_component_spec.rb +26 -0
  178. data/spec/components/blacklight/system/flash_message_component_spec.rb +7 -11
  179. data/spec/controllers/catalog_controller_spec.rb +12 -20
  180. data/spec/features/facets_spec.rb +70 -7
  181. data/spec/features/modal_spec.rb +1 -1
  182. data/spec/helpers/blacklight/facets_helper_behavior_spec.rb +10 -0
  183. data/spec/lib/blacklight/configuration/facet_field_spec.rb +2 -2
  184. data/spec/lib/blacklight/parameters_spec.rb +12 -1
  185. data/spec/lib/blacklight/search_state/filter_field_spec.rb +18 -0
  186. data/spec/models/blacklight/configuration_spec.rb +52 -28
  187. data/spec/models/blacklight/facet_search_builder_spec.rb +19 -0
  188. data/spec/models/blacklight/search_builder_spec.rb +1 -11
  189. data/spec/models/blacklight/solr/default_filter_query_builder_spec.rb +72 -0
  190. data/spec/models/blacklight/solr/document_spec.rb +0 -4
  191. data/spec/models/blacklight/solr/facet_search_builder_behavior_spec.rb +929 -0
  192. data/spec/models/blacklight/solr/repository_spec.rb +31 -29
  193. data/spec/models/blacklight/solr/response/facets_spec.rb +86 -40
  194. data/spec/models/blacklight/solr/response/group_response_spec.rb +8 -5
  195. data/spec/models/blacklight/solr/response/group_spec.rb +9 -5
  196. data/spec/models/blacklight/solr/response_spec.rb +96 -64
  197. data/spec/models/blacklight/solr/search_builder_behavior_spec.rb +2 -227
  198. data/spec/models/solr_document_spec.rb +5 -1
  199. data/spec/presenters/blacklight/json_presenter_spec.rb +17 -0
  200. data/spec/services/blacklight/search_service_spec.rb +6 -27
  201. data/spec/spec_helper.rb +0 -1
  202. data/spec/support/view_component_test_helpers.rb +0 -18
  203. data/spec/views/catalog/facet.html.erb_spec.rb +10 -3
  204. data/spec/views/catalog/index.atom.builder_spec.rb +6 -3
  205. data/spec/views/catalog/index.html.erb_spec.rb +3 -1
  206. metadata +57 -29
  207. data/.github/workflows/ruby.yml +0 -98
  208. data/app/assets/stylesheets/blacklight/_icons.scss +0 -4
  209. data/app/components/blacklight/facet_field_list_component.html.erb +0 -19
  210. data/app/components/blacklight/search/facet_suggest_input.html.erb +0 -9
  211. data/app/components/blacklight/search/facet_suggest_input.rb +0 -16
  212. data/app/javascript/blacklight-frontend/modalForm.js +0 -60
  213. data/app/views/catalog/_facet_index_navigation.html.erb +0 -1
  214. data/app/views/catalog/_facet_layout.html.erb +0 -8
  215. data/app/views/catalog/_facet_pagination.html.erb +0 -1
  216. data/spec/components/blacklight/document_metadata_component_spec.rb +0 -0
  217. data/spec/components/blacklight/search/facet_suggest_input_spec.rb +0 -33
  218. data/spec/views/catalog/_facet_index_navigation.html.erb_spec.rb +0 -43
  219. data/spec/views/catalog/_facet_layout.html.erb_spec.rb +0 -41
  220. /data/app/components/blacklight/{facet_field_checkboxes_component.html.erb → facets/checkboxes_component.html.erb} +0 -0
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ module Facets
5
+ class ItemComponent < Blacklight::Component
6
+ attr_reader :label, :href, :hits
7
+
8
+ with_collection_parameter :facet_item
9
+
10
+ def initialize(facet_item:, wrapping_element: 'li', suppress_link: false)
11
+ @facet_item = facet_item
12
+ @label = facet_item.label
13
+ @hits = facet_item.hits
14
+ @href = facet_item.href
15
+ @selected = facet_item.selected?
16
+ @wrapping_element = wrapping_element
17
+ @suppress_link = suppress_link
18
+ end
19
+
20
+ def call
21
+ # if the downstream app has overridden the helper methods we'd usually call,
22
+ # use the helpers to preserve compatibility
23
+ content = if @selected
24
+ render_selected_facet_value
25
+ else
26
+ render_facet_value
27
+ end
28
+
29
+ return '' if content.blank?
30
+ return content unless @wrapping_element
31
+
32
+ content_tag @wrapping_element, content
33
+ end
34
+
35
+ ##
36
+ # Standard display of a facet value in a list. Used in both _facets sidebar
37
+ # partial and catalog/facet expanded list. Will output facet value name as
38
+ # a link to add that to your restrictions, with count in parens.
39
+ #
40
+ # @return [String]
41
+ # @private
42
+ def render_facet_value
43
+ tag.span(class: "facet-label") do
44
+ link_to_unless(@suppress_link, label, href, class: "facet-select", rel: "nofollow")
45
+ end + render_facet_count
46
+ end
47
+
48
+ ##
49
+ # Standard display of a SELECTED facet value (e.g. without a link and with a remove button)
50
+ # @see #render_facet_value
51
+ #
52
+ # @private
53
+ def render_selected_facet_value
54
+ concat render(Blacklight::Facets::SelectedValueComponent.new(label: label, href: href))
55
+ concat render_facet_count(classes: ["selected"])
56
+ end
57
+
58
+ ##
59
+ # Renders a count value for facet limits. Can be over-ridden locally
60
+ # to change style. And can be called by plugins to get consistent display.
61
+ #
62
+ # @param [Hash] options
63
+ # @option options [Array<String>] an array of classes to add to count span.
64
+ # @return [String]
65
+ # @private
66
+ def render_facet_count(options = {})
67
+ classes = (options[:classes] || []) << "facet-count"
68
+
69
+ render Blacklight::Facets::CountComponent.new(hits: hits, classes: classes)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,11 @@
1
+ <%= render(layout.new(facet_field: @facet_field)) do |component| %>
2
+ <% component.with_label do %>
3
+ <%= @facet_field.label %>
4
+ <% end %>
5
+ <% component.with_body do %>
6
+ <%= render(Blacklight::Facets::InclusiveConstraintComponent.new(facet_field: @facet_field)) %>
7
+ <ul class="facet-values list-unstyled">
8
+ <%= render facet_items %>
9
+ </ul>
10
+ <% end %>
11
+ <% end %>
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ module Facets
5
+ class ListComponent < Blacklight::Component
6
+ def initialize(facet_field:, layout: nil)
7
+ @facet_field = facet_field
8
+ @layout = layout == false ? Blacklight::Facets::NoLayoutComponent : Blacklight::Facets::FieldComponent
9
+ end
10
+
11
+ attr_accessor :layout
12
+
13
+ def facet_items(wrapping_element: :li, **item_args)
14
+ facet_item_component_class.with_collection(facet_item_presenters, wrapping_element: wrapping_element, **item_args)
15
+ end
16
+
17
+ def facet_item_presenters
18
+ @facet_field.paginator.items.map do |item|
19
+ facet_item_presenter(item)
20
+ end
21
+ end
22
+
23
+ def render?
24
+ @facet_field.paginator&.items&.any?
25
+ end
26
+
27
+ def facet_item_presenter(facet_item)
28
+ facet_config.item_presenter.new(facet_item, facet_config, helpers, @facet_field.key)
29
+ end
30
+
31
+ def facet_item_component_class
32
+ facet_config.item_component
33
+ end
34
+
35
+ def facet_config
36
+ @facet_field.facet_field
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ module Facets
5
+ class NoLayoutComponent < Blacklight::Component
6
+ renders_one :label
7
+ renders_one :body
8
+
9
+ def initialize(**); end
10
+
11
+ def call
12
+ body.to_s
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ module Facets
5
+ # Standard display of a selected facet value (e.g. without a link and with a remove button)
6
+ class SelectedValueComponent < Blacklight::Component
7
+ def initialize(label:, href:)
8
+ @label = label
9
+ @href = href
10
+ super
11
+ end
12
+
13
+ attr_reader :label, :href
14
+
15
+ def call
16
+ tag.span(class: "facet-label") do
17
+ tag.span(label, class: "selected") + remove_link
18
+ end
19
+ end
20
+
21
+ def remove_link
22
+ link_to(href, class: "remove ps-2", rel: "nofollow") do
23
+ render(Blacklight::Icons::RemoveComponent.new(aria_hidden: true)) +
24
+ tag.span(t(:'blacklight.search.facets.selected.remove'), class: 'visually-hidden')
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ <label for="facet_suggest_<%= key %>">
2
+ <%= I18n.t('blacklight.search.facets.suggest.label', field_label: label) %>
3
+ </label>
4
+ <%= text_field_tag "facet_suggest_#{key}",
5
+ nil,
6
+ class: "facet-suggest form-control mb-3",
7
+ data: {
8
+ facet_field: key,
9
+ facet_search_context: helpers.search_facet_path(id: key)
10
+ },
11
+ placeholder: I18n.t('blacklight.search.form.search.placeholder')
12
+ %>
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ module Facets
5
+ class SuggestComponent < Blacklight::Component
6
+ def initialize(presenter:)
7
+ @presenter = presenter
8
+ end
9
+
10
+ private
11
+
12
+ attr_accessor :presenter
13
+
14
+ delegate :suggest, :key, :label, to: :presenter
15
+
16
+ def render?
17
+ # Draw if suggest is true or not present
18
+ suggest != false
19
+ end
20
+ end
21
+ end
22
+ end
@@ -7,8 +7,8 @@ module Blacklight
7
7
  # Blacklight::Icons::RemoveComponent.svg = '<svg>your SVG here</svg>'
8
8
  class RemoveComponent < Blacklight::Icons::IconComponent
9
9
  self.svg = <<~SVG
10
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x fs-4" viewBox="0 0 16 16">
11
- <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708"/>
10
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
11
+ <path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
12
12
  </svg>
13
13
  SVG
14
14
  end
@@ -4,8 +4,8 @@ module Blacklight
4
4
  class MetadataFieldPlainTextLayoutComponent < Blacklight::MetadataFieldLayoutComponent
5
5
  with_collection_parameter :field
6
6
 
7
- def initialize(field:, **kwargs)
8
- super(field: field, **kwargs, value_tag: nil)
7
+ def initialize(field:, **)
8
+ super(field: field, **, value_tag: nil)
9
9
  end
10
10
 
11
11
  # rubocop:disable Rails/OutputSafety
@@ -1,12 +1,12 @@
1
1
  <% # main container for facets/limits menu -%>
2
- <%= content_tag :div, id: @id, class: 'facets sidenav facets-toggleable-md' do %>
2
+ <%= content_tag :div, id: @id, class: container_classes do %>
3
3
  <div class="facets-header">
4
- <%= content_tag :h2, @title, class: 'facets-heading h4' if @title %>
4
+ <%= content_tag :h2, @title, class: header_classes if @title %>
5
5
 
6
6
  <%= collapse_toggle_button(@panel_id) %>
7
7
  </div>
8
8
 
9
- <div id="<%= @panel_id %>" class="facets-collapse d-lg-block collapse accordion">
9
+ <div id="<%= @panel_id %>" class="<%= body_classes %>">
10
10
  <%= body %>
11
11
  </div>
12
12
  <% end %>
@@ -8,13 +8,20 @@ module Blacklight
8
8
 
9
9
  # @param [String] id a unique identifier for the group
10
10
  # @param [String] title the title of the facet group section
11
- def initialize(id:, title: nil)
11
+ def initialize(id:, title: nil, body_classes: 'facets-collapse d-lg-block collapse accordion',
12
+ header_classes: 'facets-heading h4',
13
+ container_classes: 'facets sidenav facets-toggleable-md')
12
14
  @groupname = id
13
15
  @id = id ? "facets-#{id}" : 'facets'
14
16
  @title = title || I18n.t("blacklight.search.#{@id}.title")
15
17
  @panel_id = id ? "facet-panel-#{id}-collapse" : 'facet-panel-collapse'
18
+ @body_classes = body_classes
19
+ @header_classes = header_classes
20
+ @container_classes = container_classes
16
21
  end
17
22
 
23
+ attr_accessor :body_classes, :header_classes, :container_classes
24
+
18
25
  def collapse_toggle_button(panel_id)
19
26
  render button_component.new(panel_id: panel_id)
20
27
  end
@@ -24,6 +31,7 @@ module Blacklight
24
31
  end
25
32
 
26
33
  def render?
34
+ # debugger
27
35
  body.present?
28
36
  end
29
37
  end
@@ -1,7 +1,7 @@
1
1
  <%= content_tag :div, id: @id, class: @classes.join(' ') do %>
2
2
  <%= button %>
3
3
 
4
- <div class="dropdown-menu" role="menu">
4
+ <div class="dropdown-menu">
5
5
  <% options.each do |option| %>
6
6
  <%= option %>
7
7
  <% end %>
@@ -6,7 +6,7 @@ module Blacklight
6
6
  renders_one :button, DropdownButtonComponent
7
7
 
8
8
  renders_many :options, (lambda do |text:, url:, selected: false|
9
- link_to(text, url, class: "dropdown-item #{'active' if selected}", role: 'menuitem', aria: { current: ('page' if selected) })
9
+ link_to(text, url, class: "dropdown-item #{'active' if selected}", aria: { current: ('page' if selected) })
10
10
  end)
11
11
 
12
12
  # rubocop:disable Metrics/ParameterLists
@@ -1,6 +1,6 @@
1
1
  <div class="alert alert-dismissible <%= @classes %>">
2
2
  <%= message %>
3
- <%= tag.button button_contents, type: "button", class: "btn-close",
3
+ <%= tag.button '', type: "button", class: "btn-close",
4
4
  data: { dismiss: "alert", bs_dismiss: "alert" },
5
5
  aria: { label: "Close" } %>
6
6
  </div>
@@ -16,15 +16,6 @@ module Blacklight
16
16
  with_message { @message } if @message
17
17
  end
18
18
 
19
- # Bootstrap 4 requires the span, but Bootstrap 5 should not have it.
20
- # See https://getbootstrap.com/docs/4.6/components/alerts/#dismissing
21
- # https://getbootstrap.com/docs/5.1/components/alerts/#dismissing
22
- def button_contents
23
- return if helpers.controller.blacklight_config.bootstrap_version == 5
24
-
25
- tag.span '&times;'.html_safe, aria: { hidden: true }
26
- end
27
-
28
19
  def alert_class(type)
29
20
  case type.to_s
30
21
  when 'success' then "alert-success"
@@ -6,8 +6,7 @@
6
6
  <h1 class="modal-title"><%= title %></h1>
7
7
  <% end) %>
8
8
 
9
- <button type="button" class="blacklight-modal-close btn-close close" data-bl-dismiss="modal" aria-label="<%= t('blacklight.modal.close') %>">
10
- <span aria-hidden="true" class="visually-hidden">&times;</span>
9
+ <button type="button" class="blacklight-modal-close btn-close" data-bl-dismiss="modal" aria-label="<%= t('blacklight.modal.close') %>">
11
10
  </button>
12
11
  </div>
13
12
 
@@ -1,4 +1,4 @@
1
- <nav class="navbar navbar-expand-md navbar-dark bg-dark topbar" aria-label="<%= aria_label %>">
1
+ <nav class="navbar navbar-expand-md bg-dark topbar" aria-label="<%= aria_label %>">
2
2
  <div class="<%= container_classes %>">
3
3
  <%= logo_link %>
4
4
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-bs-toggle="collapse" data-target="#user-util-collapse" data-bs-target="#user-util-collapse" aria-controls="user-util-collapse" aria-expanded="false" aria-label="Toggle navigation">
@@ -36,8 +36,8 @@ module Blacklight::Bookmarks
36
36
 
37
37
  # Blacklight uses #search_action_url to figure out the right URL for
38
38
  # the global search box
39
- def search_action_url *args
40
- search_catalog_url(*args)
39
+ def search_action_url(*)
40
+ search_catalog_url(*)
41
41
  end
42
42
 
43
43
  # @return [Hash] a hash of context information to pass through to the search service
@@ -110,7 +110,7 @@ module Blacklight::Bookmarks
110
110
 
111
111
  success = @bookmarks.all? do |bookmark|
112
112
  bookmark = current_or_guest_user.bookmarks.find_by(bookmark)
113
- bookmark && bookmark.delete && bookmark.destroyed?
113
+ bookmark&.delete && bookmark.destroyed?
114
114
  end
115
115
 
116
116
  if success
@@ -9,13 +9,13 @@ module Blacklight::Catalog
9
9
  include Blacklight::Configurable
10
10
  include Blacklight::SearchContext
11
11
  include Blacklight::Searchable
12
+ include Blacklight::Facetable
12
13
 
13
14
  # The following code is executed when someone includes blacklight::catalog in their
14
15
  # own controller.
15
16
  included do
16
17
  if respond_to? :helper_method
17
18
  helper_method :sms_mappings, :has_search_parameters?
18
- helper_method :search_facet_path
19
19
  end
20
20
 
21
21
  record_search_parameters
@@ -80,17 +80,19 @@ module Blacklight::Catalog
80
80
 
81
81
  # displays values and pagination links for a single facet field
82
82
  def facet
83
+ # @facet is a Blacklight::Configuration::FacetField
83
84
  @facet = blacklight_config.facet_fields[params[:id]]
84
85
  raise ActionController::RoutingError, 'Not Found' unless @facet
85
86
 
86
- query_fragment = params[:query_fragment] || ''
87
- @response = if query_fragment.present?
88
- search_service.facet_suggest_response(@facet.key, params[:query_fragment])
87
+ @response = if params[:query_fragment].present?
88
+ facet_search_service.facet_suggest_response(@facet.key, params[:query_fragment])
89
89
  else
90
- @response = search_service.facet_field_response(@facet.key)
90
+ facet_search_service.facet_field_response(@facet.key)
91
91
  end
92
+
92
93
  @display_facet = @response.aggregations[@facet.field]
93
94
 
95
+ # @presenter is a Blacklight::FacetFieldPresenter
94
96
  @presenter = @facet.presenter.new(@facet, @display_facet, view_context)
95
97
  @pagination = @presenter.paginator
96
98
  respond_to do |format|
@@ -139,15 +141,6 @@ module Blacklight::Catalog
139
141
  params[:search_field].present? || search_state.has_constraints?
140
142
  end
141
143
 
142
- def search_facet_path(options = {})
143
- opts = search_state
144
- .to_h
145
- .merge(action: "facet", only_path: true)
146
- .merge(options)
147
- .except(:page)
148
- url_for opts
149
- end
150
-
151
144
  private
152
145
 
153
146
  #
@@ -236,25 +229,17 @@ module Blacklight::Catalog
236
229
  # Email Action (this will render the appropriate view on GET requests and process the form and send the email on POST requests)
237
230
  def email_action documents
238
231
  mail = RecordMailer.email_record(documents, { to: params[:to], message: params[:message], config: blacklight_config }, url_options)
239
- if mail.respond_to? :deliver_now
240
- mail.deliver_now
241
- else
242
- mail.deliver
243
- end
232
+ mail.deliver_now
244
233
  end
245
234
 
246
235
  # SMS action (this will render the appropriate view on GET requests and process the form and send the email on POST requests)
247
236
  def sms_action documents
248
237
  to = "#{params[:to].gsub(/[^\d]/, '')}@#{params[:carrier]}"
249
238
  mail = RecordMailer.sms_record(documents, { to: to, config: blacklight_config }, url_options)
250
- if mail.respond_to? :deliver_now
251
- mail.deliver_now
252
- else
253
- mail.deliver
254
- end
239
+ mail.deliver_now
255
240
  end
256
241
 
257
- def validate_sms_params
242
+ def sms_params_valid?
258
243
  if params[:to].blank?
259
244
  flash[:error] = I18n.t('blacklight.sms.errors.to.blank')
260
245
  elsif params[:carrier].blank?
@@ -267,12 +252,14 @@ module Blacklight::Catalog
267
252
 
268
253
  flash[:error].blank?
269
254
  end
255
+ alias validate_sms_params sms_params_valid?
256
+ Blacklight.deprecation.deprecate_methods(Blacklight::Catalog, validate_sms_params: 'use Catalog#sms_params_valid? instead')
270
257
 
271
258
  def sms_mappings
272
259
  Blacklight::Engine.config.blacklight.sms_mappings
273
260
  end
274
261
 
275
- def validate_email_params
262
+ def email_params_valid?
276
263
  if params[:to].blank?
277
264
  flash[:error] = I18n.t('blacklight.email.errors.to.blank')
278
265
  elsif !params[:to].match(Blacklight::Engine.config.blacklight.email_regexp)
@@ -281,6 +268,8 @@ module Blacklight::Catalog
281
268
 
282
269
  flash[:error].blank?
283
270
  end
271
+ alias validate_email_params email_params_valid?
272
+ Blacklight.deprecation.deprecate_methods(Blacklight::Catalog, validate_email_params: 'use Catalog#email_params_valid? instead')
284
273
 
285
274
  def start_new_search_session?
286
275
  action_name == "index"
@@ -293,7 +282,7 @@ module Blacklight::Catalog
293
282
  def blacklight_advanced_search_form_search_service
294
283
  form_search_state = search_state_class.new(blacklight_advanced_search_form_params, blacklight_config, self)
295
284
 
296
- search_service_class.new(config: blacklight_config, search_state: form_search_state, user_params: form_search_state.to_h, **search_service_context)
285
+ search_service_class.new(config: blacklight_config, search_state: form_search_state, **search_service_context)
297
286
  end
298
287
 
299
288
  def blacklight_advanced_search_form_params
@@ -130,7 +130,7 @@ module Blacklight::Controller
130
130
  # send the user home if the access was previously denied by the same
131
131
  # request to avoid sending the user back to the login page
132
132
  # (e.g. protected page -> logout -> returned to protected page -> home)
133
- redirect_to(root_url) && flash.discard && return if request.referer && request.referer.ends_with?(request.fullpath)
133
+ redirect_to(root_url) && flash.discard && return if request.referer&.ends_with?(request.fullpath)
134
134
 
135
135
  redirect_to(root_url) && return unless has_user_authentication_provider?
136
136
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The Facetable module can be included onto classes that need to initialize a FacetSearchService.
4
+ # There are two dependencies you must provide on the including class. Typically these
5
+ # would be provided by Blacklight::Controller
6
+ # 1. search_state
7
+ # 2. blacklight_config
8
+ #
9
+ # Additionally, the including class may override the facet_search_service_context method to provide
10
+ # further context to the SearchService. For example you could override this to provide the
11
+ # currently signed in user.
12
+ module Blacklight::Facetable
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ # Which class to use for the search service. You can subclass SearchService if you
17
+ # want to override any of the methods (e.g. SearchService#fetch)
18
+ class_attribute :facet_search_service_class
19
+ self.facet_search_service_class = Blacklight::FacetSearchService
20
+ end
21
+
22
+ # @return [Blacklight::FacetSearchService]
23
+ def facet_search_service
24
+ facet_search_service_class.new(config: blacklight_config, search_state: search_state, user_params: search_state.to_h, **facet_search_service_context)
25
+ end
26
+
27
+ # Override this method on the class that includes Blacklight::Facetable to provide more context to the search service if necessary.
28
+ # For example, if your search builder needs to be aware of the current user, override this method to return a hash including the current user.
29
+ # Then the search builder could use some property about the current user to construct a constraint on the search.
30
+ # @return [Hash] a hash of context information to pass through to the search service
31
+ def facet_search_service_context
32
+ search_service_context
33
+ end
34
+ end
@@ -40,7 +40,7 @@ module Blacklight::SearchContext
40
40
  @page_link_data[:prev] = page_links_document_path(documents.first, index)
41
41
  @page_link_data[:next] = page_links_document_path(documents.last, index + 2)
42
42
  end
43
- if response&.total && response.total.positive?
43
+ if response&.total&.positive?
44
44
  @page_link_data[:counterRaw] = counter_param
45
45
  @page_link_data[:counterDelimited] = helpers.number_with_delimiter(counter_param.to_i)
46
46
  @page_link_data[:totalRaw] = response.total
@@ -21,7 +21,7 @@ module Blacklight::Searchable
21
21
 
22
22
  # @return [Blacklight::SearchService]
23
23
  def search_service
24
- search_service_class.new(config: blacklight_config, search_state: search_state, user_params: search_state.to_h, **search_service_context)
24
+ search_service_class.new(config: blacklight_config, search_state: search_state, **search_service_context)
25
25
  end
26
26
 
27
27
  # Override this method on the class that includes Blacklight::Searchable to provide more context to the search service if necessary.
@@ -169,7 +169,7 @@ module Blacklight::CatalogHelperBehavior
169
169
  constraints += search_state.filters.collect { |filter| render_search_to_page_title_filter(filter.key, filter.values) }
170
170
  end
171
171
 
172
- constraints.join(' / ')
172
+ constraints.join(t('blacklight.search.page_title.joiner'))
173
173
  end
174
174
 
175
175
  ##
@@ -96,7 +96,7 @@ module Blacklight::ConfigurationHelperBehavior
96
96
  #
97
97
  # @param [Blacklight::Solr::Configuration::Field] field_config
98
98
  # @return [Boolean]
99
- def should_render_field?(field_config, *args)
100
- blacklight_configuration_context.evaluate_if_unless_configuration field_config, *args
99
+ def should_render_field?(field_config, *)
100
+ blacklight_configuration_context.evaluate_if_unless_configuration(field_config, *)
101
101
  end
102
102
  end
@@ -38,10 +38,12 @@ module Blacklight::DocumentHelperBehavior
38
38
 
39
39
  ##
40
40
  # Check if the document is in the user's bookmarks
41
+ # Note: we do string comparison of the classes, because hot reloading in the development environment may cause each to have
42
+ # separate versions of the class loaded (see document.class.object_id and Bookmark#document_type.object_id)
41
43
  # @param [Blacklight::Document] document
42
44
  # @return [Boolean]
43
45
  def bookmarked? document
44
- current_bookmarks.any? { |x| x.document_id == document.id && x.document_type == document.class }
46
+ current_bookmarks.any? { |x| x.document_id == document.id && x.document_type.to_s == document.class.to_s }
45
47
  end
46
48
 
47
49
  ##
@@ -7,6 +7,15 @@ module Blacklight::FacetsHelperBehavior
7
7
  facet_config.presenter.new(facet_config, display_facet, self)
8
8
  end
9
9
 
10
+ def search_facet_path(options = {})
11
+ opts = search_state
12
+ .to_h
13
+ .merge(action: "facet", only_path: true)
14
+ .merge(options)
15
+ .except(:page)
16
+ url_for opts
17
+ end
18
+
10
19
  private
11
20
 
12
21
  def facet_value_for_facet_item item
@@ -7,7 +7,7 @@ module Blacklight::IconHelperBehavior
7
7
  # Returns the raw SVG (String) for a Blacklight Icon
8
8
  # @param [String, Symbol] icon_name
9
9
  # @return [String]
10
- def blacklight_icon(icon_name, **kwargs)
11
- render "Blacklight::Icons::#{icon_name.to_s.camelize}Component".constantize.new(**kwargs)
10
+ def blacklight_icon(icon_name, **)
11
+ render "Blacklight::Icons::#{icon_name.to_s.camelize}Component".constantize.new(**)
12
12
  end
13
13
  end
@@ -1,12 +1,17 @@
1
1
  import CheckboxSubmit from 'blacklight-frontend/checkbox_submit'
2
2
 
3
3
  const BookmarkToggle = (e) => {
4
- if (e.target.matches('[data-checkboxsubmit-target="checkbox"]')) {
5
- const form = e.target.closest('form')
4
+ const elementType = e.target.getAttribute('data-checkboxsubmit-target');
5
+ if (elementType == 'checkbox' || elementType == 'label') {
6
+ const form = e.target.closest('form');
6
7
  if (form) new CheckboxSubmit(form).clicked(e);
8
+ if (e.code == 'Space') e.preventDefault();
7
9
  }
8
10
  };
9
11
 
10
12
  document.addEventListener('click', BookmarkToggle);
13
+ document.addEventListener('keydown', function (e) {
14
+ if (e.key === 'Enter' || e.code == 'Space') { BookmarkToggle(e); } }
15
+ );
11
16
 
12
17
  export default BookmarkToggle