blacklight 8.11.0 → 9.0.0.beta2

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 (288) hide show
  1. checksums.yaml +4 -4
  2. data/.env +3 -3
  3. data/.github/matrix.json +11 -42
  4. data/.github/pull_request_template.md +7 -0
  5. data/.github/workflows/main.yml +0 -2
  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 +0 -5
  9. data/.rubocop.yml +71 -3
  10. data/.rubocop_todo.yml +43 -76
  11. data/.solr_wrapper.yml +2 -0
  12. data/README.md +30 -8
  13. data/VERSION +1 -1
  14. data/app/assets/builds/blacklight.css +452 -0
  15. data/app/assets/javascripts/blacklight/blacklight.esm.js +6 -76
  16. data/app/assets/javascripts/blacklight/blacklight.esm.js.map +1 -1
  17. data/app/assets/javascripts/blacklight/blacklight.js +6 -76
  18. data/app/assets/javascripts/blacklight/blacklight.js.map +1 -1
  19. data/app/assets/stylesheets/blacklight/_balanced_list.scss +1 -4
  20. data/app/assets/stylesheets/blacklight/_blacklight_base.scss +1 -3
  21. data/app/assets/stylesheets/blacklight/_bookmark.scss +44 -41
  22. data/app/assets/stylesheets/blacklight/_bootstrap_overrides.scss +0 -25
  23. data/app/assets/stylesheets/blacklight/_constraints.scss +15 -24
  24. data/app/assets/stylesheets/blacklight/_controls.scss +2 -18
  25. data/app/assets/stylesheets/blacklight/_facets.scss +17 -84
  26. data/app/assets/stylesheets/blacklight/_group.scss +2 -5
  27. data/app/assets/stylesheets/blacklight/_header.scss +8 -11
  28. data/app/assets/stylesheets/blacklight/_icons.scss +0 -8
  29. data/app/assets/stylesheets/blacklight/_modal.scss +1 -1
  30. data/app/assets/stylesheets/blacklight/_pagination.scss +0 -4
  31. data/app/assets/stylesheets/blacklight/_search_form.scss +0 -1
  32. data/app/assets/stylesheets/blacklight/_search_history.scss +0 -8
  33. data/app/assets/stylesheets/blacklight/_search_results.scss +1 -15
  34. data/app/assets/stylesheets/blacklight/blacklight_defaults.scss +9 -14
  35. data/app/assets/stylesheets/blacklight/build.scss +4 -0
  36. data/app/components/blacklight/advanced_search_form_component.rb +3 -3
  37. data/app/components/blacklight/constraint_component.rb +1 -1
  38. data/app/components/blacklight/constraint_layout_component.html.erb +2 -2
  39. data/app/components/blacklight/constraints_component.html.erb +2 -2
  40. data/app/components/blacklight/constraints_component.rb +3 -3
  41. data/app/components/blacklight/document/bookmark_component.html.erb +3 -3
  42. data/app/components/blacklight/document/bookmark_component.rb +2 -2
  43. data/app/components/blacklight/document/group_component.html.erb +1 -1
  44. data/app/components/blacklight/document/more_like_this_component.rb +2 -2
  45. data/app/components/blacklight/document/page_header_component.rb +1 -1
  46. data/app/components/blacklight/document/sidebar_component.rb +5 -5
  47. data/app/components/blacklight/document/thumbnail_component.html.erb +3 -7
  48. data/app/components/blacklight/document/thumbnail_component.rb +7 -6
  49. data/app/components/blacklight/document_component.rb +12 -16
  50. data/app/components/blacklight/document_title_component.rb +5 -11
  51. data/app/components/blacklight/facet_field_checkboxes_component.rb +2 -20
  52. data/app/components/blacklight/facet_field_component.rb +2 -17
  53. data/app/components/blacklight/facet_field_filter_component.rb +2 -21
  54. data/app/components/blacklight/facet_field_inclusive_constraint_component.rb +4 -25
  55. data/app/components/blacklight/facet_field_list_component.rb +2 -50
  56. data/app/components/blacklight/facet_field_no_layout_component.rb +2 -10
  57. data/app/components/blacklight/facet_item_component.rb +2 -74
  58. data/app/components/blacklight/facet_item_pivot_component.rb +3 -3
  59. data/app/components/blacklight/facets/checkboxes_component.rb +26 -0
  60. data/app/components/blacklight/facets/count_component.rb +23 -0
  61. data/app/components/blacklight/{facet_field_component.html.erb → facets/field_component.html.erb} +4 -4
  62. data/app/components/blacklight/facets/field_component.rb +23 -0
  63. data/app/components/blacklight/facets/filters_component.html.erb +4 -0
  64. data/app/components/blacklight/facets/filters_component.rb +27 -0
  65. data/app/components/blacklight/{facet_field_inclusive_constraint_component.html.erb → facets/inclusive_constraint_component.html.erb} +1 -1
  66. data/app/components/blacklight/facets/inclusive_constraint_component.rb +31 -0
  67. data/app/components/blacklight/{facet_field_filter_component.html.erb → facets/index_navigation_component.html.erb} +1 -1
  68. data/app/components/blacklight/facets/index_navigation_component.rb +32 -0
  69. data/app/components/blacklight/facets/item_component.rb +73 -0
  70. data/app/components/blacklight/facets/list_component.html.erb +11 -0
  71. data/app/components/blacklight/facets/list_component.rb +38 -0
  72. data/app/components/blacklight/facets/no_layout_component.rb +16 -0
  73. data/app/components/blacklight/facets/selected_value_component.rb +29 -0
  74. data/app/components/blacklight/facets/suggest_component.html.erb +12 -0
  75. data/app/components/blacklight/facets/suggest_component.rb +22 -0
  76. data/app/components/blacklight/metadata_field_plain_text_layout_component.rb +2 -2
  77. data/app/components/blacklight/response/facet_group_component.html.erb +3 -18
  78. data/app/components/blacklight/response/facet_group_component.rb +11 -23
  79. data/app/components/blacklight/response/facet_toggle_button_component.html.erb +16 -0
  80. data/app/components/blacklight/response/facet_toggle_button_component.rb +14 -0
  81. data/app/components/blacklight/response/pagination_component.html.erb +1 -1
  82. data/app/components/blacklight/response/pagination_component.rb +2 -1
  83. data/app/components/blacklight/response/sort_component.rb +1 -0
  84. data/app/components/blacklight/response/view_type_button_component.html.erb +1 -1
  85. data/app/components/blacklight/response/view_type_component.html.erb +1 -1
  86. data/app/components/blacklight/search/per_page_component.html.erb +1 -1
  87. data/app/components/blacklight/search/per_page_component.rb +1 -0
  88. data/app/components/blacklight/search/sidebar_component.html.erb +1 -1
  89. data/app/components/blacklight/search/sidebar_component.rb +1 -1
  90. data/app/components/blacklight/search_bar_component.html.erb +3 -3
  91. data/app/components/blacklight/search_bar_component.rb +2 -2
  92. data/app/components/blacklight/search_button_component.rb +2 -2
  93. data/app/components/blacklight/search_context/server_applied_params_component.html.erb +2 -2
  94. data/app/components/blacklight/search_context/server_applied_params_component.rb +9 -0
  95. data/app/components/blacklight/skip_link_component.html.erb +1 -1
  96. data/app/components/blacklight/skip_link_component.rb +2 -1
  97. data/app/components/blacklight/top_navbar_component.html.erb +2 -2
  98. data/app/controllers/concerns/blacklight/bookmarks.rb +3 -3
  99. data/app/controllers/concerns/blacklight/catalog.rb +9 -23
  100. data/app/controllers/concerns/blacklight/controller.rb +1 -1
  101. data/app/controllers/concerns/blacklight/facetable.rb +34 -0
  102. data/app/controllers/concerns/blacklight/search_context.rb +0 -12
  103. data/app/controllers/concerns/blacklight/searchable.rb +1 -1
  104. data/app/helpers/blacklight/blacklight_helper_behavior.rb +0 -6
  105. data/app/helpers/blacklight/catalog_helper_behavior.rb +0 -10
  106. data/app/helpers/blacklight/configuration_helper_behavior.rb +2 -14
  107. data/app/helpers/blacklight/document_helper_behavior.rb +3 -27
  108. data/app/helpers/blacklight/facets_helper_behavior.rb +9 -0
  109. data/app/helpers/blacklight/icon_helper_behavior.rb +3 -11
  110. data/app/helpers/blacklight/layout_helper_behavior.rb +2 -2
  111. data/app/helpers/blacklight/render_partials_helper_behavior.rb +0 -14
  112. data/app/helpers/blacklight/url_helper_behavior.rb +1 -1
  113. data/app/javascript/{blacklight → blacklight-frontend}/bookmark_toggle.js +1 -1
  114. data/app/javascript/{blacklight → blacklight-frontend}/checkbox_submit.js +3 -0
  115. data/app/javascript/{blacklight → blacklight-frontend}/core.js +2 -10
  116. data/app/javascript/{blacklight → blacklight-frontend}/facet_suggest.js +1 -3
  117. data/app/javascript/blacklight-frontend/index.js +16 -0
  118. data/app/javascript/{blacklight → blacklight-frontend}/modal.js +1 -5
  119. data/app/models/concerns/blacklight/document.rb +0 -11
  120. data/app/models/concerns/blacklight/user.rb +1 -1
  121. data/app/models/facet_search_builder.rb +5 -0
  122. data/app/presenters/blacklight/facet_field_presenter.rb +3 -3
  123. data/app/presenters/blacklight/rendering/helper_method.rb +4 -4
  124. data/app/presenters/blacklight/rendering/join.rb +2 -2
  125. data/app/services/blacklight/facet_search_service.rb +44 -0
  126. data/app/services/blacklight/field_retriever.rb +1 -1
  127. data/app/services/blacklight/search_params_yaml_coder.rb +0 -2
  128. data/app/services/blacklight/search_service.rb +6 -6
  129. data/app/values/blacklight/types.rb +2 -2
  130. data/app/views/bookmarks/_clear_bookmarks_widget.html.erb +0 -2
  131. data/app/views/bookmarks/index.html.erb +1 -1
  132. data/app/views/catalog/_facet_pivot.html.erb +1 -1
  133. data/app/views/catalog/_home_text.html.erb +2 -2
  134. data/app/views/catalog/_results_pagination.html.erb +2 -5
  135. data/app/views/catalog/_search_results.html.erb +3 -3
  136. data/app/views/catalog/_search_results_header.html.erb +1 -1
  137. data/app/views/catalog/_sort_and_per_page.html.erb +1 -1
  138. data/app/views/catalog/_sort_widget.html.erb +1 -0
  139. data/app/views/catalog/facet.html.erb +4 -6
  140. data/app/views/catalog/show.html.erb +2 -3
  141. data/app/views/kaminari/blacklight/_paginator.html.erb +1 -2
  142. data/app/views/layouts/blacklight/base.html.erb +3 -7
  143. data/app/views/search_history/index.html.erb +0 -2
  144. data/blacklight.gemspec +4 -4
  145. data/config/importmap.rb +1 -1
  146. data/config/locales/blacklight.ar.yml +2 -2
  147. data/config/locales/blacklight.ca.yml +124 -124
  148. data/config/locales/blacklight.de.yml +2 -2
  149. data/config/locales/blacklight.en.yml +14 -14
  150. data/config/locales/blacklight.es.yml +2 -2
  151. data/config/locales/blacklight.fr.yml +2 -2
  152. data/config/locales/blacklight.hu.yml +2 -2
  153. data/config/locales/blacklight.it.yml +1 -1
  154. data/config/locales/blacklight.nl.yml +2 -2
  155. data/config/locales/blacklight.pt-BR.yml +1 -1
  156. data/config/locales/blacklight.sq.yml +2 -2
  157. data/config/locales/blacklight.zh.yml +2 -2
  158. data/lib/blacklight/abstract_repository.rb +2 -2
  159. data/lib/blacklight/abstract_search_builder.rb +154 -0
  160. data/lib/blacklight/configuration/context.rb +3 -3
  161. data/lib/blacklight/configuration/display_field.rb +1 -1
  162. data/lib/blacklight/configuration/facet_field.rb +6 -6
  163. data/lib/blacklight/configuration/field.rb +4 -4
  164. data/lib/blacklight/configuration/fields.rb +3 -4
  165. data/lib/blacklight/configuration/search_field.rb +1 -1
  166. data/lib/blacklight/configuration/view_config.rb +2 -4
  167. data/lib/blacklight/configuration.rb +11 -18
  168. data/lib/blacklight/engine.rb +0 -6
  169. data/lib/blacklight/facet_search_builder.rb +18 -0
  170. data/lib/blacklight/nested_open_struct_with_hash_access.rb +1 -1
  171. data/lib/blacklight/open_struct_with_hash_access.rb +5 -5
  172. data/lib/blacklight/search_builder.rb +1 -159
  173. data/lib/blacklight/search_state/filter_field.rb +2 -2
  174. data/lib/blacklight/search_state/pivot_filter_field.rb +4 -4
  175. data/lib/blacklight/solr/abstract_filter_query_builder.rb +77 -0
  176. data/lib/blacklight/solr/default_filter_query_builder.rb +20 -0
  177. data/lib/blacklight/solr/facet_search_builder_behavior.rb +62 -0
  178. data/lib/blacklight/solr/repository.rb +8 -9
  179. data/lib/blacklight/solr/request.rb +1 -7
  180. data/lib/blacklight/solr/response/facets.rb +2 -2
  181. data/lib/blacklight/solr/response/group_response.rb +2 -2
  182. data/lib/blacklight/solr/response/params.rb +0 -4
  183. data/lib/blacklight/solr/response.rb +5 -1
  184. data/lib/blacklight/solr/search_builder_behavior.rb +17 -132
  185. data/lib/blacklight.rb +2 -2
  186. data/lib/generators/blacklight/assets/importmap_generator.rb +6 -24
  187. data/lib/generators/blacklight/assets/propshaft_generator.rb +1 -1
  188. data/lib/generators/blacklight/assets_generator.rb +3 -3
  189. data/lib/generators/blacklight/controller_generator.rb +3 -3
  190. data/lib/generators/blacklight/search_builder_generator.rb +1 -1
  191. data/lib/generators/blacklight/templates/.solr_wrapper.yml +2 -0
  192. data/lib/generators/blacklight/templates/catalog_controller.rb +3 -1
  193. data/lib/generators/blacklight/templates/solr/conf/solrconfig.xml +0 -4
  194. data/lib/generators/blacklight/user_generator.rb +9 -10
  195. data/package.json +15 -5
  196. data/rollup.config.js +1 -1
  197. data/spec/components/blacklight/advanced_search_form_component_spec.rb +18 -22
  198. data/spec/components/blacklight/constraint_layout_component_spec.rb +8 -8
  199. data/spec/components/blacklight/constraints_component_spec.rb +11 -11
  200. data/spec/components/blacklight/document/action_component_spec.rb +21 -17
  201. data/spec/components/blacklight/document/group_component_spec.rb +10 -15
  202. data/spec/components/blacklight/document/page_header_component_spec.rb +35 -28
  203. data/spec/components/blacklight/document/sidebar_component_spec.rb +9 -30
  204. data/spec/components/blacklight/document_component_spec.rb +98 -65
  205. data/spec/components/blacklight/facet_component_spec.rb +12 -8
  206. data/spec/components/blacklight/facet_item_pivot_component_spec.rb +12 -12
  207. data/spec/components/blacklight/{facet_field_checkboxes_component_spec.rb → facets/checkboxes_component_spec.rb} +14 -14
  208. data/spec/components/blacklight/facets/filters_component_spec.rb +36 -0
  209. data/spec/components/blacklight/facets/index_navigation_component_spec.rb +40 -0
  210. data/spec/components/blacklight/{facet_item_component_spec.rb → facets/item_component_spec.rb} +10 -10
  211. data/spec/components/blacklight/{facet_field_list_component_spec.rb → facets/list_component_spec.rb} +24 -24
  212. data/spec/components/blacklight/facets/suggest_component_spec.rb +68 -0
  213. data/spec/components/blacklight/header_component_spec.rb +2 -4
  214. data/spec/components/blacklight/hidden_search_state_component_spec.rb +7 -7
  215. data/spec/components/blacklight/metadata_field_component_spec.rb +17 -15
  216. data/spec/components/blacklight/response/facet_group_component_spec.rb +37 -0
  217. data/spec/components/blacklight/response/pagination_component_spec.rb +1 -1
  218. data/spec/components/blacklight/response/spellcheck_component_spec.rb +1 -1
  219. data/spec/components/blacklight/search_bar_component_spec.rb +5 -5
  220. data/spec/components/blacklight/search_context/server_applied_params_component_spec.rb +2 -2
  221. data/spec/components/blacklight/search_context/server_item_pagination_component_spec.rb +3 -5
  222. data/spec/components/blacklight/skip_link_component_spec.rb +8 -11
  223. data/spec/components/blacklight/start_over_button_component_spec.rb +4 -4
  224. data/spec/components/blacklight/system/flash_message_component_spec.rb +7 -11
  225. data/spec/controllers/catalog_controller_spec.rb +11 -19
  226. data/spec/features/axe_spec.rb +6 -11
  227. data/spec/features/bookmarks_spec.rb +48 -11
  228. data/spec/features/facets_spec.rb +58 -63
  229. data/spec/features/search_context_spec.rb +1 -2
  230. data/spec/features/search_filters_spec.rb +6 -6
  231. data/spec/helpers/blacklight/configuration_helper_behavior_spec.rb +0 -9
  232. data/spec/helpers/blacklight/facets_helper_behavior_spec.rb +10 -0
  233. data/spec/helpers/blacklight/render_partials_helper_behavior_spec.rb +1 -1
  234. data/spec/lib/blacklight/configuration/facet_field_spec.rb +2 -2
  235. data/spec/models/blacklight/configuration_spec.rb +32 -28
  236. data/spec/models/blacklight/facet_search_builder_spec.rb +19 -0
  237. data/spec/models/blacklight/search_builder_spec.rb +1 -11
  238. data/spec/models/blacklight/solr/default_filter_query_builder_spec.rb +72 -0
  239. data/spec/models/blacklight/solr/document_spec.rb +0 -4
  240. data/spec/models/blacklight/solr/facet_search_builder_behavior_spec.rb +929 -0
  241. data/spec/models/blacklight/solr/repository_spec.rb +31 -29
  242. data/spec/models/blacklight/solr/request_spec.rb +7 -0
  243. data/spec/models/blacklight/solr/response/facets_spec.rb +86 -40
  244. data/spec/models/blacklight/solr/response/group_response_spec.rb +8 -5
  245. data/spec/models/blacklight/solr/response/group_spec.rb +9 -5
  246. data/spec/models/blacklight/solr/response_spec.rb +96 -64
  247. data/spec/models/blacklight/solr/search_builder_behavior_spec.rb +2 -227
  248. data/spec/models/solr_document_spec.rb +5 -1
  249. data/spec/services/blacklight/search_service_spec.rb +5 -26
  250. data/spec/spec_helper.rb +0 -1
  251. data/spec/support/view_component_test_helpers.rb +0 -18
  252. data/spec/views/catalog/facet.html.erb_spec.rb +10 -11
  253. data/spec/views/catalog/index.atom.builder_spec.rb +6 -3
  254. data/spec/views/catalog/index.html.erb_spec.rb +8 -3
  255. data/spec/views/catalog/show.html.erb_spec.rb +1 -0
  256. data/tasks/blacklight.rake +8 -5
  257. metadata +76 -81
  258. data/app/assets/stylesheets/blacklight/_autocomplete.scss +0 -25
  259. data/app/assets/stylesheets/blacklight/_mixins.scss +0 -20
  260. data/app/components/blacklight/facet_field_list_component.html.erb +0 -19
  261. data/app/components/blacklight/icons/legacy_icon_component.rb +0 -30
  262. data/app/components/blacklight/search/facet_suggest_input.html.erb +0 -12
  263. data/app/components/blacklight/search/facet_suggest_input.rb +0 -20
  264. data/app/javascript/blacklight/index.js +0 -18
  265. data/app/javascript/blacklight/modalForm.js +0 -60
  266. data/app/views/catalog/_constraints.html.erb +0 -1
  267. data/app/views/catalog/_facet_index_navigation.html.erb +0 -1
  268. data/app/views/catalog/_facet_layout.html.erb +0 -8
  269. data/app/views/catalog/_facet_pagination.html.erb +0 -1
  270. data/app/views/catalog/_facets.html.erb +0 -5
  271. data/app/views/catalog/_search_form.html.erb +0 -7
  272. data/app/views/catalog/_search_header.html.erb +0 -1
  273. data/app/views/catalog/_show_sidebar.html.erb +0 -3
  274. data/app/views/catalog/_show_tools.html.erb +0 -2
  275. data/app/views/catalog/_start_over.html.erb +0 -1
  276. data/app/views/shared/_header_navbar.html.erb +0 -1
  277. data/lib/generators/blacklight/assets/sprockets_generator.rb +0 -68
  278. data/spec/components/blacklight/document_metadata_component_spec.rb +0 -0
  279. data/spec/components/blacklight/search/facet_suggest_input_spec.rb +0 -57
  280. data/spec/views/catalog/_facet_index_navigation.html.erb_spec.rb +0 -43
  281. data/spec/views/catalog/_facet_layout.html.erb_spec.rb +0 -41
  282. data/spec/views/catalog/_search_header.erb_spec.rb +0 -14
  283. data/spec/views/catalog/_show_sidebar.erb_spec.rb +0 -24
  284. data/spec/views/catalog/_show_tools.html.erb_spec.rb +0 -37
  285. /data/app/components/blacklight/{facet_field_checkboxes_component.html.erb → facets/checkboxes_component.html.erb} +0 -0
  286. /data/app/javascript/{blacklight → blacklight-frontend}/button_focus.js +0 -0
  287. /data/app/javascript/{blacklight → blacklight-frontend}/debounce.js +0 -0
  288. /data/app/javascript/{blacklight → blacklight-frontend}/search_context.js +0 -0
@@ -22,9 +22,6 @@ module Blacklight
22
22
  class DocumentComponent < Blacklight::Component
23
23
  with_collection_parameter :document
24
24
 
25
- # ViewComponent 3 changes iteration counters to begin at 0 rather than 1
26
- COLLECTION_INDEX_OFFSET = ViewComponent::VERSION::MAJOR < 3 ? 0 : 1
27
-
28
25
  # Content appearing before the document
29
26
  renders_one :header
30
27
 
@@ -37,26 +34,26 @@ module Blacklight
37
34
 
38
35
  # The document title with some reasonable default behavior
39
36
  renders_one :title, (lambda do |*args, component: nil, **kwargs|
40
- component ||= @presenter&.view_config&.title_component || Blacklight::DocumentTitleComponent
37
+ component ||= view_config.title_component || Blacklight::DocumentTitleComponent
41
38
 
42
- component.new(*args, counter: @counter, document: @document, presenter: @presenter, as: @title_component, actions: !@show, link_to_document: !@show, document_component: self, **kwargs)
39
+ component.new(*args, counter: @counter, presenter: @presenter, as: @title_component, actions: !@show, link_to_document: !@show, document_component: self, **kwargs)
43
40
  end)
44
41
 
45
42
  renders_one :embed, (lambda do |static_content = nil, *args, component: nil, **kwargs|
46
43
  next static_content if static_content.present?
47
44
 
48
- component ||= @presenter.view_config&.embed_component
45
+ component ||= view_config.embed_component
49
46
 
50
47
  next unless component
51
48
 
52
- component.new(*args, document: @document, presenter: @presenter, document_counter: @document_counter, **kwargs)
49
+ component.new(*args, presenter: @presenter, document_counter: @document_counter, **kwargs)
53
50
  end)
54
51
 
55
52
  # The primary metadata section
56
53
  renders_one :metadata, (lambda do |static_content = nil, *args, component: nil, fields: nil, **kwargs|
57
54
  next static_content if static_content.present?
58
55
 
59
- component ||= @presenter&.view_config&.metadata_component || Blacklight::DocumentMetadataComponent
56
+ component ||= view_config.metadata_component || Blacklight::DocumentMetadataComponent
60
57
 
61
58
  component.new(*args, fields: fields || @presenter&.field_presenters || [], **kwargs)
62
59
  end)
@@ -67,9 +64,9 @@ module Blacklight
67
64
  renders_one :thumbnail, (lambda do |image_options_or_static_content = {}, *args, component: nil, **kwargs|
68
65
  next image_options_or_static_content if image_options_or_static_content.is_a? String
69
66
 
70
- component ||= @presenter&.view_config&.thumbnail_component || Blacklight::Document::ThumbnailComponent
67
+ component ||= view_config.thumbnail_component || Blacklight::Document::ThumbnailComponent
71
68
 
72
- component.new(*args, document: @document, presenter: @presenter, counter: @counter, image_options: image_options_or_static_content, **kwargs)
69
+ component.new(*args, presenter: @presenter, counter: @counter, image_options: image_options_or_static_content, **kwargs)
73
70
  end)
74
71
 
75
72
  # A container for partials rendered using the view config partials configuration. Its use is discouraged, but necessary until
@@ -81,7 +78,6 @@ module Blacklight
81
78
 
82
79
  # rubocop:disable Metrics/ParameterLists
83
80
  # @param document [Blacklight::DocumentPresenter]
84
- # @param presenter [Blacklight::DocumentPresenter] alias for document
85
81
  # @param partials [Array, nil] view partial names that should be used to provide content for the `partials` slot
86
82
  # @param id [String] HTML id for the root element
87
83
  # @param classes [Array, String] additional HTML classes for the root element
@@ -91,13 +87,11 @@ module Blacklight
91
87
  # @param document_counter [Number, nil] provided by ViewComponent collection iteration
92
88
  # @param counter_offset [Number] the offset of the start of the collection counter parameter for the component to the overall result set
93
89
  # @param show [Boolean] are we showing only a single document (vs a list of search results); used for backwards-compatibility
94
- def initialize(document: nil, presenter: nil, partials: nil,
90
+ def initialize(document: nil, partials: nil,
95
91
  id: nil, classes: [], component: :article, title_component: nil,
96
92
  counter: nil, document_counter: nil, counter_offset: 0,
97
93
  show: false, **args)
98
- Blacklight.deprecation.warn('the `presenter` argument to DocumentComponent#initialize is deprecated; pass the `presenter` in as document instead') if presenter
99
-
100
- @presenter = presenter || document || args[self.class.collection_parameter]
94
+ @presenter = document || args[self.class.collection_parameter]
101
95
  @document = @presenter.document
102
96
  @view_partials = partials || []
103
97
 
@@ -108,7 +102,7 @@ module Blacklight
108
102
 
109
103
  @counter = counter
110
104
  @document_counter = document_counter || args.fetch(self.class.collection_counter_parameter, nil)
111
- @counter ||= @document_counter + COLLECTION_INDEX_OFFSET + counter_offset if @document_counter.present?
105
+ @counter ||= 1 + @document_counter + counter_offset if @document_counter.present?
112
106
 
113
107
  @show = show
114
108
  end
@@ -139,6 +133,8 @@ module Blacklight
139
133
 
140
134
  private
141
135
 
136
+ delegate :view_config, to: :@presenter
137
+
142
138
  attr_reader :document_counter, :presenter, :view_partials
143
139
 
144
140
  def show?
@@ -7,11 +7,9 @@ module Blacklight
7
7
  renders_many :actions
8
8
 
9
9
  # rubocop:disable Metrics/ParameterLists
10
- def initialize(title = nil, document: nil, presenter: nil, as: :h3, counter: nil, classes: 'index_title document-title-heading col', link_to_document: true, document_component: nil, actions: true)
11
- raise ArgumentError, 'missing keyword: :document or :presenter' if presenter.nil? && document.nil?
12
-
10
+ def initialize(title = nil, presenter:, as: :h3, counter: nil, classes: 'index_title document-title-heading col h5', link_to_document: true, document_component: nil,
11
+ actions: true)
13
12
  @title = title
14
- @document = document
15
13
  @presenter = presenter
16
14
  @as = as || :h3
17
15
  @counter = counter
@@ -22,6 +20,8 @@ module Blacklight
22
20
  end
23
21
  # rubocop:enable Metrics/ParameterLists
24
22
 
23
+ attr_accessor :presenter
24
+
25
25
  # Content for the document title area; should be an inline element
26
26
  def title
27
27
  if @link_to_document
@@ -42,7 +42,7 @@ module Blacklight
42
42
 
43
43
  (@has_actions_slot && get_slot(:actions)) ||
44
44
  ([@document_component&.actions] if @document_component&.actions.present?) ||
45
- [helpers.render_index_doc_actions(presenter.document, wrapping_class: 'index-document-functions col-sm-3 col-lg-2')]
45
+ [helpers.render_index_doc_actions(presenter.document, wrapping_class: 'index-document-functions col-sm-3 col-lg-2 mb-4 mb-sm-0')]
46
46
  end
47
47
 
48
48
  def counter
@@ -52,11 +52,5 @@ module Blacklight
52
52
  t('blacklight.search.documents.counter', counter: @counter)
53
53
  end
54
54
  end
55
-
56
- private
57
-
58
- def presenter
59
- @presenter ||= helpers.document_presenter(@document)
60
- end
61
55
  end
62
56
  end
@@ -1,24 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blacklight
4
- class FacetFieldCheckboxesComponent < Blacklight::Component
5
- def initialize(facet_field:, layout: nil)
6
- @facet_field = facet_field
7
- @layout = layout == false ? FacetFieldNoLayoutComponent : Blacklight::FacetFieldComponent
8
- end
9
-
10
- def render?
11
- presenters.any?
12
- end
13
-
14
- def presenters
15
- return [] unless @facet_field.paginator
16
-
17
- return to_enum(:presenters) unless block_given?
18
-
19
- @facet_field.paginator.items.each do |item|
20
- yield Blacklight::FacetCheckboxItemPresenter.new(item, @facet_field.facet_field, helpers, @facet_field.key, @facet_field.search_state)
21
- end
22
- end
23
- end
4
+ class FacetFieldCheckboxesComponent < Facets::CheckboxesComponent; end
5
+ FacetFieldCheckboxesComponent = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("FacetFieldCheckboxesComponent", "Blacklight::Facets::CheckboxesComponent", ActiveSupport::Deprecation.new)
24
6
  end
@@ -1,21 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blacklight
4
- class FacetFieldComponent < Blacklight::Component
5
- renders_one :label
6
- renders_one :body
7
-
8
- # @param [Blacklight::FacetFieldPresenter] facet_field
9
- def initialize(facet_field:)
10
- @facet_field = facet_field
11
- end
12
-
13
- def html_id
14
- "facet-#{@facet_field.key.parameterize}"
15
- end
16
-
17
- def header_html_id
18
- "#{html_id}-header"
19
- end
20
- end
4
+ class FacetFieldComponent < Facets::FieldComponent; end
5
+ FacetFieldComponent = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("FacetFieldComponent", "Blacklight::Facets::FieldComponent", ActiveSupport::Deprecation.new)
21
6
  end
@@ -1,25 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blacklight
4
- class FacetFieldFilterComponent < Blacklight::Component
5
- def initialize(facet_field:)
6
- @facet_field = facet_field
7
- end
8
-
9
- def render?
10
- @facet_field.facet_field.index_range.any?
11
- end
12
-
13
- def prefix
14
- @facet_field.paginator.prefix
15
- end
16
-
17
- def clear_facet_prefix_url
18
- @facet_field.paginator.params_for_resort_url('index', @facet_field.search_state.to_h.except(@facet_field.paginator.request_keys[:prefix]))
19
- end
20
-
21
- def facet_prefix_url(letter)
22
- @facet_field.paginator.params_for_resort_url('index', @facet_field.search_state.to_h.merge(@facet_field.paginator.request_keys[:prefix] => letter))
23
- end
24
- end
4
+ class FacetFieldFilterComponent < Facets::IndexNavigationComponent; end
5
+ FacetFieldFilterComponent = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("FacetFieldFilterComponent", "Blacklight::Facets::IndexNavigationComponent", ActiveSupport::Deprecation.new)
25
6
  end
@@ -1,29 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blacklight
4
- class FacetFieldInclusiveConstraintComponent < Blacklight::Component
5
- with_collection_parameter :facet_field
6
-
7
- def initialize(facet_field:, values: nil)
8
- @facet_field = facet_field
9
- @values = values
10
- end
11
-
12
- def values
13
- @values ||= @facet_field.values.find { |v| v.is_a? Array }
14
- @values || []
15
- end
16
-
17
- def render?
18
- values.present?
19
- end
20
-
21
- def presenters
22
- return to_enum(:presenters) unless block_given?
23
-
24
- values.each do |item|
25
- yield Blacklight::FacetGroupedItemPresenter.new(values, item, @facet_field.facet_field, helpers, @facet_field.key, @facet_field.search_state)
26
- end
27
- end
28
- end
4
+ class FacetFieldInclusiveConstraintComponent < Facets::InclusiveConstraintComponent; end
5
+ FacetFieldInclusiveConstraintComponent = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("FacetFieldInclusiveConstraintComponent",
6
+ "Blacklight::Facets::InclusiveConstraintComponent",
7
+ ActiveSupport::Deprecation.new)
29
8
  end
@@ -1,54 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blacklight
4
- class FacetFieldListComponent < Blacklight::Component
5
- def initialize(facet_field:, layout: nil)
6
- @facet_field = facet_field
7
- @layout = layout == false ? FacetFieldNoLayoutComponent : Blacklight::FacetFieldComponent
8
- end
9
-
10
- ##
11
- # Renders the list of values
12
- # removes any elements where render_facet_item returns a nil value. This enables an application
13
- # to filter undesireable facet items so they don't appear in the UI
14
- # @deprecated
15
- def render_facet_limit_list(paginator, facet_field, wrapping_element = :li)
16
- return render(facet_items(wrapping_element: wrapping_element)) if paginator == @facet_field.paginator && facet_field == @facet_field.key
17
-
18
- facet_config ||= helpers.facet_configuration_for_field(facet_field)
19
-
20
- collection = paginator.items.map do |item|
21
- facet_item_presenter(item, facet_config, facet_field)
22
- end
23
-
24
- render(facet_item_component_class(facet_config).with_collection(collection, wrapping_element: wrapping_element))
25
- end
26
- Blacklight.deprecation.deprecate_methods(self, render_facet_limit_list: 'Call e.g. `render facet_items` instead')
27
-
28
- def facet_items(wrapping_element: :li, **item_args)
29
- facet_item_component_class.with_collection(facet_item_presenters, wrapping_element: wrapping_element, **item_args)
30
- end
31
-
32
- def facet_item_presenters
33
- @facet_field.paginator.items.map do |item|
34
- facet_item_presenter(item)
35
- end
36
- end
37
-
38
- def render?
39
- @facet_field.paginator&.items&.any?
40
- end
41
-
42
- def facet_item_presenter(facet_item, deprecated_facet_config = nil, facet_field = nil)
43
- (deprecated_facet_config || facet_config).item_presenter.new(facet_item, deprecated_facet_config || facet_config, helpers, facet_field || @facet_field.key)
44
- end
45
-
46
- def facet_item_component_class(deprecated_facet_config = nil)
47
- (deprecated_facet_config || facet_config).item_component
48
- end
49
-
50
- def facet_config
51
- @facet_field.facet_field
52
- end
53
- end
4
+ class FacetFieldListComponent < Facets::ListComponent; end
5
+ FacetFieldListComponent = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("FacetFieldListComponent", "Blacklight::Facets::ListComponent", ActiveSupport::Deprecation.new)
54
6
  end
@@ -1,14 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blacklight
4
- class FacetFieldNoLayoutComponent < Blacklight::Component
5
- renders_one :label
6
- renders_one :body
7
-
8
- def initialize(**); end
9
-
10
- def call
11
- body.to_s
12
- end
13
- end
4
+ class FacetFieldNoLayoutComponent < Facets::NoLayoutComponent; end
5
+ FacetFieldNoLayoutComponent = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("FacetFieldNoLayoutComponent", "Blacklight::Facets::NoLayoutComponent", ActiveSupport::Deprecation.new)
14
6
  end
@@ -1,78 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blacklight
4
- class FacetItemComponent < Blacklight::Component
5
- attr_reader :label, :href, :hits
6
-
7
- with_collection_parameter :facet_item
8
-
9
- def initialize(facet_item:, wrapping_element: 'li', suppress_link: false)
10
- @facet_item = facet_item
11
- @label = facet_item.label
12
- @hits = facet_item.hits
13
- @href = facet_item.href
14
- @selected = facet_item.selected?
15
- @wrapping_element = wrapping_element
16
- @suppress_link = suppress_link
17
- end
18
-
19
- def call
20
- # if the downstream app has overridden the helper methods we'd usually call,
21
- # use the helpers to preserve compatibility
22
- content = if @selected
23
- render_selected_facet_value
24
- else
25
- render_facet_value
26
- end
27
-
28
- return '' if content.blank?
29
- return content unless @wrapping_element
30
-
31
- content_tag @wrapping_element, content
32
- end
33
-
34
- ##
35
- # Standard display of a facet value in a list. Used in both _facets sidebar
36
- # partial and catalog/facet expanded list. Will output facet value name as
37
- # a link to add that to your restrictions, with count in parens.
38
- #
39
- # @return [String]
40
- # @private
41
- def render_facet_value
42
- tag.span(class: "facet-label") do
43
- link_to_unless(@suppress_link, label, href, class: "facet-select", rel: "nofollow")
44
- end + render_facet_count
45
- end
46
-
47
- ##
48
- # Standard display of a SELECTED facet value (e.g. without a link and with a remove button)
49
- # @see #render_facet_value
50
- #
51
- # @private
52
- def render_selected_facet_value
53
- tag.span(class: "facet-label") do
54
- tag.span(label, class: "selected") +
55
- # remove link
56
- link_to(href, class: "remove", rel: "nofollow") do
57
- render(Blacklight::Icons::RemoveComponent.new) +
58
- tag.span(helpers.t(:'blacklight.search.facets.selected.remove'), class: 'sr-only visually-hidden')
59
- end
60
- end + render_facet_count(classes: ["selected"])
61
- end
62
-
63
- ##
64
- # Renders a count value for facet limits. Can be over-ridden locally
65
- # to change style. And can be called by plugins to get consistent display.
66
- #
67
- # @param [Hash] options
68
- # @option options [Array<String>] an array of classes to add to count span.
69
- # @return [String]
70
- # @private
71
- def render_facet_count(options = {})
72
- return '' if hits.blank?
73
-
74
- classes = (options[:classes] || []) << "facet-count"
75
- tag.span(t('blacklight.search.facets.count', number: number_with_delimiter(hits)), class: classes)
76
- end
77
- end
4
+ class FacetItemComponent < Facets::ItemComponent; end
5
+ FacetItemComponent = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("FacetItemComponent", "Blacklight::Facets::ItemComponent", ActiveSupport::Deprecation.new)
78
6
  end
@@ -28,11 +28,11 @@ module Blacklight
28
28
  end
29
29
 
30
30
  def call
31
- facet = Blacklight::FacetItemComponent.new(facet_item: @facet_item, wrapping_element: nil, suppress_link: @suppress_link)
31
+ facet = Blacklight::Facets::ItemComponent.new(facet_item: @facet_item, wrapping_element: nil, suppress_link: @suppress_link)
32
32
 
33
33
  id = "h-#{self.class.mint_id}" if @collapsing && has_items?
34
34
 
35
- content_tag @wrapping_element, role: 'treeitem', class: 'treeitem' do
35
+ content_tag @wrapping_element, class: 'treeitem' do
36
36
  concat(content_tag('span', class: "d-flex flex-row align-items-center") do
37
37
  concat facet_toggle_button(id) if has_items? && @collapsing
38
38
  concat content_tag('span', render(facet), class: "facet-values d-flex flex-row flex-grow-1 #{'facet-leaf-node' if has_items? && @collapsing}", id: id && "#{id}_label")
@@ -75,7 +75,7 @@ module Blacklight
75
75
  def toggle_icon(type)
76
76
  content_tag 'span', class: type do
77
77
  concat @icons[type]
78
- concat content_tag('span', t(type, scope: 'blacklight.search.facets.pivot'), class: 'sr-only visually-hidden')
78
+ concat content_tag('span', t(type, scope: 'blacklight.search.facets.pivot'), class: 'visually-hidden')
79
79
  end
80
80
  end
81
81
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ module Facets
5
+ class CheckboxesComponent < 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
+ def render?
12
+ presenters.any?
13
+ end
14
+
15
+ def presenters
16
+ return [] unless @facet_field.paginator
17
+
18
+ return to_enum(:presenters) unless block_given?
19
+
20
+ @facet_field.paginator.items.each do |item|
21
+ yield Blacklight::FacetCheckboxItemPresenter.new(item, @facet_field.facet_field, helpers, @facet_field.key, @facet_field.search_state)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ module Facets
5
+ # Display count of a facet value
6
+ class CountComponent < Blacklight::Component
7
+ def initialize(hits:, classes:)
8
+ @hits = hits
9
+ @classes = classes
10
+ end
11
+
12
+ attr_reader :hits, :classes
13
+
14
+ def render?
15
+ hits.present?
16
+ end
17
+
18
+ def call
19
+ tag.span(t('blacklight.search.facets.count', number: number_with_delimiter(hits)), class: classes)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,8 +1,8 @@
1
- <div class="card facet-limit blacklight-<%= @facet_field.key %> <%= 'facet-limit-active' if @facet_field.active? %>">
2
- <h3 class="card-header p-0 facet-field-heading" id="<%= header_html_id %>">
1
+ <div class="accordion-item facet-limit blacklight-<%= @facet_field.key %> <%= 'facet-limit-active' if @facet_field.active? %>">
2
+ <h3 class="accordion-header p-0 facet-field-heading h6" id="<%= header_html_id %>">
3
3
  <button
4
4
  type="button"
5
- class="btn w-100 d-block btn-block p-2 text-start text-left collapse-toggle <%= "collapsed" if @facet_field.collapsed? %>"
5
+ class="btn accordion-button <%= "collapsed" if @facet_field.collapsed? %>"
6
6
  data-toggle="collapse"
7
7
  data-bs-toggle="collapse"
8
8
  data-target="#<%= html_id %>"
@@ -14,7 +14,7 @@
14
14
  </button>
15
15
  </h3>
16
16
  <div id="<%= html_id %>" role="region" aria-labelledby="<%= header_html_id %>" class="panel-collapse facet-content collapse <%= "show" unless @facet_field.collapsed? %>">
17
- <div class="card-body">
17
+ <div class="accordion-body">
18
18
  <%= body %>
19
19
 
20
20
  <% if @facet_field.modal_path %>
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ module Facets
5
+ class FieldComponent < Blacklight::Component
6
+ renders_one :label
7
+ renders_one :body
8
+
9
+ # @param [Blacklight::FacetFieldPresenter] facet_field
10
+ def initialize(facet_field:)
11
+ @facet_field = facet_field
12
+ end
13
+
14
+ def html_id
15
+ "facet-#{@facet_field.key.parameterize}"
16
+ end
17
+
18
+ def header_html_id
19
+ "#{html_id}-header"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ <div class="<%= classes %>">
2
+ <%= render Blacklight::Facets::SuggestComponent.new(presenter: presenter) %>
3
+ <%= render Blacklight::Facets::IndexNavigationComponent.new(presenter: presenter) %>
4
+ </div>
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight::Facets
4
+ class FiltersComponent < Blacklight::Component
5
+ # @param [Blacklight::FacetFieldPresenter] presenter
6
+ def initialize(presenter:, classes: 'facet-filters card card-body bg-light p-3 mb-3 border-0')
7
+ @presenter = presenter
8
+ @classes = classes
9
+ end
10
+
11
+ def facet
12
+ @presenter.facet_field
13
+ end
14
+
15
+ attr_reader :classes, :presenter
16
+
17
+ delegate :display_facet, to: :presenter
18
+
19
+ def render?
20
+ facet.suggest != false || render_index_navigation?
21
+ end
22
+
23
+ def render_index_navigation?
24
+ facet.index_range && display_facet.index?
25
+ end
26
+ end
27
+ end
@@ -1,6 +1,6 @@
1
1
  <div class="inclusive_or card card-body bg-light mb-3">
2
2
  <h5><%= t('blacklight.advanced_search.any_of') %></h5>
3
3
  <ul class="list-unstyled facet-values">
4
- <%= render(Blacklight::FacetItemComponent.with_collection(presenters.to_a)) %>
4
+ <%= render Blacklight::Facets::ItemComponent.with_collection(presenters.to_a) %>
5
5
  </ul>
6
6
  </div>
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ module Facets
5
+ class InclusiveConstraintComponent < Blacklight::Component
6
+ with_collection_parameter :facet_field
7
+
8
+ def initialize(facet_field:, values: nil)
9
+ @facet_field = facet_field
10
+ @values = values
11
+ end
12
+
13
+ def values
14
+ @values ||= @facet_field.values.find { |v| v.is_a? Array }
15
+ @values || []
16
+ end
17
+
18
+ def render?
19
+ values.present?
20
+ end
21
+
22
+ def presenters
23
+ return to_enum(:presenters) unless block_given?
24
+
25
+ values.each do |item|
26
+ yield Blacklight::FacetGroupedItemPresenter.new(values, item, @facet_field.facet_field, helpers, @facet_field.key, @facet_field.search_state)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -3,7 +3,7 @@
3
3
  <li class="page-item <%= 'active' if prefix.blank? %>">
4
4
  <%= link_to t('blacklight.search.facets.all'), clear_facet_prefix_url, data: { blacklight_modal: "preserve" }, class: 'page-link' %>
5
5
  </li>
6
- <% @facet_field.facet_field.index_range.each do |letter| %>
6
+ <% index_range.each do |letter| %>
7
7
  <li class="page-item <%= 'active' if prefix == letter %>">
8
8
  <%= link_to(letter, facet_prefix_url(letter), class: 'page-link', data: { blacklight_modal: "preserve" }) %>
9
9
  </li>
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ module Facets
5
+ class IndexNavigationComponent < Blacklight::Component
6
+ # @params [Blacklight::FacetFieldPresenter] presenter
7
+ def initialize(presenter:)
8
+ @presenter = presenter
9
+ end
10
+
11
+ def render?
12
+ index_range && @presenter.display_facet.index?
13
+ end
14
+
15
+ def index_range
16
+ @presenter.facet_field.index_range
17
+ end
18
+
19
+ def prefix
20
+ @presenter.paginator.prefix
21
+ end
22
+
23
+ def clear_facet_prefix_url
24
+ @presenter.paginator.params_for_resort_url('index', @presenter.search_state.to_h.except(@presenter.paginator.request_keys[:prefix]))
25
+ end
26
+
27
+ def facet_prefix_url(letter)
28
+ @presenter.paginator.params_for_resort_url('index', @presenter.search_state.to_h.merge(@presenter.paginator.request_keys[:prefix] => letter))
29
+ end
30
+ end
31
+ end
32
+ end