blacklight 9.0.0.beta8 → 9.1.0

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/.env +3 -3
  3. data/.github/matrix.json +18 -34
  4. data/.github/workflows/lint.yml +1 -1
  5. data/.github/workflows/test.yml +4 -3
  6. data/.rubocop.yml +72 -7
  7. data/.rubocop_todo.yml +142 -268
  8. data/Gemfile +1 -1
  9. data/README.md +4 -11
  10. data/VERSION +1 -1
  11. data/app/assets/builds/blacklight.css +37 -23
  12. data/app/assets/images/blacklight/logo-dark.svg +184 -0
  13. data/app/assets/images/blacklight/logo.svg +184 -0
  14. data/app/assets/javascripts/blacklight/blacklight.esm.js +134 -48
  15. data/app/assets/javascripts/blacklight/blacklight.esm.js.map +1 -1
  16. data/app/assets/javascripts/blacklight/blacklight.js +134 -48
  17. data/app/assets/javascripts/blacklight/blacklight.js.map +1 -1
  18. data/app/assets/stylesheets/blacklight/_balanced_list.scss +1 -1
  19. data/app/assets/stylesheets/blacklight/_bootstrap_overrides.scss +17 -0
  20. data/app/assets/stylesheets/blacklight/_constraints.scss +2 -2
  21. data/app/assets/stylesheets/blacklight/_facets.scss +14 -10
  22. data/app/assets/stylesheets/blacklight/_header.scss +0 -4
  23. data/app/assets/stylesheets/blacklight/blacklight_defaults.scss +5 -9
  24. data/app/components/blacklight/facets/filters_component.rb +1 -1
  25. data/app/components/blacklight/facets/inclusive_constraint_component.html.erb +1 -1
  26. data/app/components/blacklight/facets/suggest_component.html.erb +17 -12
  27. data/app/components/blacklight/icons/checkmark_component.rb +16 -0
  28. data/app/components/blacklight/icons/circle_half_component.rb +16 -0
  29. data/app/components/blacklight/icons/moon_stars_component.rb +17 -0
  30. data/app/components/blacklight/icons/sun_component.rb +16 -0
  31. data/app/components/blacklight/metadata_field_component.rb +1 -1
  32. data/app/components/blacklight/search_navbar_component.html.erb +1 -1
  33. data/app/components/blacklight/theme_switcher_component.html.erb +31 -0
  34. data/app/components/blacklight/theme_switcher_component.rb +6 -0
  35. data/app/components/blacklight/top_navbar_component.html.erb +1 -1
  36. data/app/controllers/concerns/blacklight/bookmarks.rb +8 -14
  37. data/app/controllers/concerns/blacklight/catalog.rb +12 -14
  38. data/app/controllers/concerns/blacklight/controller.rb +54 -11
  39. data/app/controllers/concerns/blacklight/guest_user.rb +51 -0
  40. data/app/controllers/concerns/blacklight/search_context.rb +1 -1
  41. data/app/controllers/concerns/blacklight/search_history.rb +3 -1
  42. data/app/controllers/concerns/blacklight/searchable.rb +18 -0
  43. data/app/helpers/blacklight/layout_helper_behavior.rb +7 -0
  44. data/app/javascript/blacklight-frontend/checkbox_submit.js +2 -2
  45. data/app/javascript/blacklight-frontend/color_theme_switcher.js +85 -0
  46. data/app/javascript/blacklight-frontend/facet_suggest.js +4 -1
  47. data/app/javascript/blacklight-frontend/index.js +2 -0
  48. data/app/models/concerns/blacklight/document/dublin_core.rb +2 -2
  49. data/app/models/concerns/blacklight/document.rb +1 -1
  50. data/app/models/concerns/blacklight/user.rb +36 -0
  51. data/app/presenters/blacklight/field_presenter.rb +1 -1
  52. data/app/presenters/blacklight/rendering/microdata.rb +1 -0
  53. data/app/services/blacklight/bookmarks_search_builder.rb +4 -4
  54. data/app/services/blacklight/search_service.rb +50 -35
  55. data/app/values/blacklight/types.rb +1 -1
  56. data/app/views/catalog/_email_form.html.erb +3 -3
  57. data/app/views/catalog/_home_text.html.erb +1 -1
  58. data/app/views/catalog/_sms_form.html.erb +4 -4
  59. data/app/views/catalog/index.rss.builder +4 -4
  60. data/app/views/catalog/opensearch.json.jbuilder +3 -0
  61. data/app/views/catalog/opensearch.xml.builder +2 -2
  62. data/app/views/layouts/blacklight/base.html.erb +13 -1
  63. data/app/views/shared/_user_util_links.html.erb +18 -5
  64. data/blacklight.gemspec +3 -3
  65. data/config/locales/blacklight.ar.yml +5 -0
  66. data/config/locales/blacklight.ca.yml +5 -0
  67. data/config/locales/blacklight.de.yml +6 -2
  68. data/config/locales/blacklight.en.yml +5 -0
  69. data/config/locales/blacklight.es.yml +5 -0
  70. data/config/locales/blacklight.fr.yml +5 -0
  71. data/config/locales/blacklight.hu.yml +5 -0
  72. data/config/locales/blacklight.it.yml +6 -2
  73. data/config/locales/blacklight.nl.yml +5 -0
  74. data/config/locales/blacklight.pt-BR.yml +6 -2
  75. data/config/locales/blacklight.sq.yml +5 -0
  76. data/config/locales/blacklight.zh.yml +5 -0
  77. data/lib/blacklight/component.rb +1 -1
  78. data/lib/blacklight/configuration/action_config_map_entry.rb +31 -0
  79. data/lib/blacklight/configuration/display_field.rb +1 -0
  80. data/lib/blacklight/configuration/field.rb +6 -6
  81. data/lib/blacklight/configuration/fields.rb +1 -1
  82. data/lib/blacklight/configuration/sort_field.rb +2 -2
  83. data/lib/blacklight/configuration.rb +30 -23
  84. data/lib/blacklight/parameters.rb +1 -1
  85. data/lib/blacklight/routes/searchable.rb +1 -1
  86. data/lib/blacklight/search_builder.rb +208 -10
  87. data/lib/blacklight/search_state.rb +4 -0
  88. data/lib/blacklight/solr/request.rb +8 -1
  89. data/lib/blacklight/solr/response/group_response.rb +1 -1
  90. data/lib/blacklight/solr/response/spelling.rb +8 -8
  91. data/lib/blacklight/solr/response.rb +2 -21
  92. data/lib/blacklight/solr/search_builder_behavior.rb +59 -16
  93. data/lib/blacklight.rb +2 -2
  94. data/lib/generators/blacklight/assets/importmap_generator.rb +2 -2
  95. data/lib/generators/blacklight/assets_generator.rb +1 -1
  96. data/lib/generators/blacklight/controller_generator.rb +1 -1
  97. data/lib/generators/blacklight/document_generator.rb +1 -1
  98. data/lib/generators/blacklight/install_generator.rb +6 -4
  99. data/lib/generators/blacklight/models_generator.rb +1 -1
  100. data/lib/generators/blacklight/search_builder_generator.rb +1 -1
  101. data/lib/generators/blacklight/solr_generator.rb +1 -1
  102. data/lib/generators/blacklight/templates/catalog_controller.rb +21 -2
  103. data/lib/generators/blacklight/templates/solr/conf/schema.xml +264 -178
  104. data/lib/generators/blacklight/templates/solr/conf/solrconfig.xml +6 -14
  105. data/lib/generators/blacklight/test_support_generator.rb +1 -1
  106. data/lib/generators/blacklight/user_generator.rb +66 -5
  107. data/lib/railties/blacklight.rake +4 -4
  108. data/package.json +2 -2
  109. data/spec/components/blacklight/document/action_component_spec.rb +1 -1
  110. data/spec/components/blacklight/document/group_component_spec.rb +1 -1
  111. data/spec/components/blacklight/document_component_spec.rb +9 -9
  112. data/spec/components/blacklight/facet_component_spec.rb +2 -2
  113. data/spec/components/blacklight/facets/filters_component_spec.rb +1 -1
  114. data/spec/components/blacklight/facets/index_navigation_component_spec.rb +1 -2
  115. data/spec/components/blacklight/facets/list_component_spec.rb +1 -1
  116. data/spec/components/blacklight/facets/suggest_component_spec.rb +2 -3
  117. data/spec/components/blacklight/metadata_field_component_spec.rb +26 -0
  118. data/spec/components/blacklight/skip_link_component_spec.rb +2 -2
  119. data/spec/controllers/application_controller_spec.rb +71 -0
  120. data/spec/controllers/bookmarks_controller_spec.rb +18 -5
  121. data/spec/controllers/catalog_controller_spec.rb +7 -5
  122. data/spec/features/advanced_search_spec.rb +29 -16
  123. data/spec/features/alternate_controller_spec.rb +4 -4
  124. data/spec/features/axe_spec.rb +34 -22
  125. data/spec/features/bookmarks_spec.rb +13 -13
  126. data/spec/features/dark_mode_spec.rb +42 -0
  127. data/spec/features/did_you_mean_spec.rb +27 -27
  128. data/spec/features/facet_missing_spec.rb +3 -3
  129. data/spec/features/facets_spec.rb +9 -0
  130. data/spec/features/record_view_spec.rb +14 -14
  131. data/spec/features/search_context_spec.rb +8 -8
  132. data/spec/features/search_filters_spec.rb +16 -16
  133. data/spec/features/search_formats_spec.rb +2 -2
  134. data/spec/features/search_history_spec.rb +11 -11
  135. data/spec/features/search_pagination_spec.rb +17 -17
  136. data/spec/features/search_results_spec.rb +3 -3
  137. data/spec/features/search_sort_spec.rb +4 -4
  138. data/spec/features/search_spec.rb +20 -20
  139. data/spec/helpers/blacklight/url_helper_behavior_spec.rb +15 -15
  140. data/spec/helpers/blacklight_helper_spec.rb +1 -1
  141. data/spec/helpers/catalog_helper_spec.rb +7 -7
  142. data/spec/integration/generators/blacklight/user_generator_spec.rb +60 -0
  143. data/spec/lib/blacklight/search_state/pivot_filter_field_spec.rb +1 -1
  144. data/spec/models/blacklight/configurable_spec.rb +1 -1
  145. data/spec/models/blacklight/configuration_spec.rb +0 -19
  146. data/spec/models/blacklight/document/dublin_core_spec.rb +2 -2
  147. data/spec/models/blacklight/facet_paginator_spec.rb +1 -1
  148. data/spec/models/blacklight/search_builder_spec.rb +26 -4
  149. data/spec/models/blacklight/solr/repository_spec.rb +10 -2
  150. data/spec/models/blacklight/solr/search_builder_behavior_spec.rb +165 -7
  151. data/spec/models/blacklight/user_spec.rb +32 -5
  152. data/spec/models/bookmark_spec.rb +1 -1
  153. data/spec/models/record_mailer_spec.rb +8 -8
  154. data/spec/models/search_spec.rb +1 -1
  155. data/spec/models/solr_document_spec.rb +4 -0
  156. data/spec/presenters/blacklight/index_presenter_spec.rb +1 -0
  157. data/spec/presenters/blacklight/rendering/pipeline_spec.rb +1 -0
  158. data/spec/presenters/blacklight/show_presenter_spec.rb +1 -0
  159. data/spec/presenters/blacklight/thumbnail_presenter_spec.rb +1 -0
  160. data/spec/services/blacklight/search_service_spec.rb +7 -7
  161. data/spec/spec_helper.rb +3 -12
  162. data/spec/support/features/session_helpers.rb +13 -4
  163. data/spec/support/features/turbo_helpers.rb +13 -0
  164. data/spec/support/features.rb +2 -0
  165. data/spec/support/user_helpers.rb +24 -0
  166. data/spec/test_app_templates/lib/generators/test_app_generator.rb +1 -1
  167. data/spec/views/catalog/_document.html.erb_spec.rb +4 -4
  168. data/spec/views/catalog/_paginate_compact.html.erb_spec.rb +2 -2
  169. data/spec/views/catalog/_sort_and_per_page.html.erb_spec.rb +5 -5
  170. data/spec/views/catalog/index.html.erb_spec.rb +2 -2
  171. data/spec/views/catalog/show.html.erb_spec.rb +3 -4
  172. data/spec/views/shared/_user_util_links.html.erb_spec.rb +56 -0
  173. data/template.demo.rb +2 -1
  174. metadata +30 -29
  175. data/.hound.yml +0 -4
  176. data/.jshintrc +0 -22
  177. data/app/assets/images/blacklight/logo.png +0 -0
  178. data/app/controllers/concerns/blacklight/facetable.rb +0 -34
  179. data/app/models/facet_search_builder.rb +0 -5
  180. data/app/services/blacklight/facet_search_service.rb +0 -44
  181. data/lib/blacklight/abstract_search_builder.rb +0 -154
  182. data/lib/blacklight/facet_search_builder.rb +0 -18
  183. data/lib/blacklight/solr/facet_search_builder_behavior.rb +0 -62
  184. data/spec/models/blacklight/facet_search_builder_spec.rb +0 -19
  185. data/spec/models/blacklight/solr/facet_search_builder_behavior_spec.rb +0 -929
@@ -9,11 +9,12 @@ module Blacklight::Catalog
9
9
  include Blacklight::Configurable
10
10
  include Blacklight::SearchContext
11
11
  include Blacklight::Searchable
12
- include Blacklight::Facetable
13
12
 
14
13
  # The following code is executed when someone includes blacklight::catalog in their
15
14
  # own controller.
16
15
  included do
16
+ allow_unauthenticated_access raise: false if respond_to?(:allow_unauthenticated_access)
17
+
17
18
  if respond_to? :helper_method
18
19
  helper_method :sms_mappings, :has_search_parameters?
19
20
  end
@@ -38,7 +39,7 @@ module Blacklight::Catalog
38
39
  # get a single document from the index
39
40
  # to add responses for formats other than html or json see _Blacklight::Document::Export_
40
41
  def show
41
- @document = search_service.fetch(params[:id])
42
+ @document = retrieve_document(params[:id])
42
43
 
43
44
  respond_to do |format|
44
45
  format.html { @search_context = setup_next_and_previous_documents }
@@ -55,7 +56,7 @@ module Blacklight::Catalog
55
56
  def raw
56
57
  raise(ActionController::RoutingError, 'Not Found') unless blacklight_config.raw_endpoint.enabled
57
58
 
58
- @document = search_service.fetch(params[:id])
59
+ @document = retrieve_document(params[:id])
59
60
  render json: @document
60
61
  end
61
62
 
@@ -82,11 +83,11 @@ module Blacklight::Catalog
82
83
  raise ActionController::RoutingError, 'Not Found' unless @facet
83
84
 
84
85
  @response = if params[:query_fragment].present?
85
- facet_search_service.facet_suggest_response(@facet.key, params[:query_fragment])
86
+ search_service.facet_suggest_response(@facet.key, params[:query_fragment])
86
87
  else
87
- facet_search_service.facet_field_response(@facet.key)
88
+ search_service.facet_field_response(@facet.key)
88
89
  end
89
-
90
+ # @display_facet is a Blacklight::Solr::Response::Facets::FacetField
90
91
  @display_facet = @response.aggregations[@facet.field]
91
92
 
92
93
  # @presenter is a Blacklight::FacetFieldPresenter
@@ -108,7 +109,10 @@ module Blacklight::Catalog
108
109
  def opensearch
109
110
  respond_to do |format|
110
111
  format.xml { render layout: false }
111
- format.json { render json: search_service.opensearch_response }
112
+ format.json do
113
+ @response = retrieve_search_results
114
+ @presenter = json_presenter(@response)
115
+ end
112
116
  end
113
117
  end
114
118
 
@@ -121,7 +125,7 @@ module Blacklight::Catalog
121
125
  # @return [Array] first value is a Blacklight::Solr::Response and the second
122
126
  # is a list of documents
123
127
  def action_documents
124
- @documents = search_service.fetch(Array(params[:id]))
128
+ @documents = retrieve_documents(params[:id])
125
129
  raise Blacklight::Exceptions::RecordNotFound if @documents.blank?
126
130
 
127
131
  @documents
@@ -146,12 +150,6 @@ module Blacklight::Catalog
146
150
  blacklight_config.index.json_presenter_class.new(repository_response, blacklight_config)
147
151
  end
148
152
 
149
- # This method may be overridden to customize search behavior.
150
- # @return [Blacklight::Solr::Response] the solr response object
151
- def retrieve_search_results
152
- search_service.search_results
153
- end
154
-
155
153
  #
156
154
  # non-routable methods ->
157
155
  #
@@ -5,6 +5,8 @@
5
5
  module Blacklight::Controller
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ include Blacklight::GuestUser
9
+
8
10
  included do
9
11
  include ActiveSupport::Callbacks
10
12
 
@@ -17,6 +19,7 @@ module Blacklight::Controller
17
19
 
18
20
  if respond_to? :helper_method
19
21
  helper_method :current_user_session, :current_user, :current_or_guest_user
22
+ helper_method :blacklight_account_path, :blacklight_login_path, :blacklight_login_url, :blacklight_logout_link_options, :blacklight_logout_path
20
23
 
21
24
  # extra head content
22
25
  helper_method :has_user_authentication_provider?
@@ -28,8 +31,7 @@ module Blacklight::Controller
28
31
  # Which class to use for the search state. You can subclass SearchState if you
29
32
  # want to override any of the methods (e.g. SearchState#url_for_document)
30
33
  # TODO: move to Searchable
31
- class_attribute :search_state_class
32
- self.search_state_class = Blacklight::SearchState
34
+ class_attribute :search_state_class, default: Blacklight::SearchState
33
35
  end
34
36
 
35
37
  # @private
@@ -56,7 +58,7 @@ module Blacklight::Controller
56
58
  end
57
59
 
58
60
  # This must be on every controller that uses the layout, because it is used in
59
- # the header to draw Blacklight::SearchNavbarComponent (via the shared/header_navbar template)
61
+ # the header to draw Blacklight::SearchNavbarComponent
60
62
  # @return [Blacklight::SearchState] a memoized instance of the parameter state.
61
63
  def search_state
62
64
  @search_state ||= search_state_class.new(params, blacklight_config, self)
@@ -71,8 +73,8 @@ module Blacklight::Controller
71
73
  end
72
74
 
73
75
  def search_action_path *args
74
- if args.first.is_a? Hash
75
- args.first[:only_path] = true if args.first[:only_path].nil?
76
+ if args.first.is_a?(Hash) && args.first[:only_path].nil?
77
+ args.first[:only_path] = true
76
78
  end
77
79
 
78
80
  search_action_url(*args)
@@ -86,10 +88,10 @@ module Blacklight::Controller
86
88
 
87
89
  # Here's a stub implementation we'll add if it isn't provided for us
88
90
  def current_or_guest_user
89
- if defined? super
90
- super
91
+ if defined?(super) && (user = super)
92
+ user
91
93
  elsif has_user_authentication_provider?
92
- current_user
94
+ current_user || guest_user
93
95
  end
94
96
  end
95
97
  alias blacklight_current_or_guest_user current_or_guest_user
@@ -98,13 +100,13 @@ module Blacklight::Controller
98
100
  #
99
101
  #
100
102
  def has_user_authentication_provider?
101
- respond_to? :current_user
103
+ respond_to?(:current_user) || blacklight_login_path.present? || blacklight_logout_path.present?
102
104
  end
103
105
 
104
106
  ##
105
107
  # When a user logs in, transfer any saved searches or bookmarks to the current_user
106
108
  def transfer_guest_to_user
107
- return unless respond_to?(:current_user) && respond_to?(:guest_user) && current_user && guest_user
109
+ return unless respond_to?(:current_user) && current_user && guest_user
108
110
 
109
111
  current_user_searches = current_user.searches.pluck(:query_params)
110
112
  current_user_bookmarks = current_user.bookmarks.pluck(:document_id)
@@ -134,10 +136,51 @@ module Blacklight::Controller
134
136
 
135
137
  redirect_to(root_url) && return unless has_user_authentication_provider?
136
138
 
137
- redirect_to new_user_session_url(referer: request.fullpath)
139
+ store_location_for_rails_authentication
140
+ redirect_to(root_url) && return unless blacklight_login_url
141
+
142
+ redirect_to blacklight_login_url(referer: request.fullpath)
138
143
  end
139
144
 
140
145
  def determine_layout
141
146
  'blacklight'
142
147
  end
148
+
149
+ def blacklight_login_path(options = {})
150
+ if respond_to?(:new_user_session_path)
151
+ new_user_session_path(options)
152
+ elsif respond_to?(:new_session_path)
153
+ new_session_path(options)
154
+ end
155
+ end
156
+
157
+ def blacklight_login_url(options = {})
158
+ if respond_to?(:new_user_session_url)
159
+ new_user_session_url(options)
160
+ elsif respond_to?(:new_session_url)
161
+ new_session_url(options)
162
+ end
163
+ end
164
+
165
+ def blacklight_logout_path
166
+ if respond_to?(:destroy_user_session_path)
167
+ destroy_user_session_path
168
+ elsif respond_to?(:session_path)
169
+ session_path
170
+ end
171
+ end
172
+
173
+ def blacklight_logout_link_options
174
+ return {} unless respond_to?(:session_path)
175
+
176
+ { data: { turbo_method: :delete } }
177
+ end
178
+
179
+ def blacklight_account_path
180
+ edit_user_registration_path if respond_to?(:edit_user_registration_path)
181
+ end
182
+
183
+ def store_location_for_rails_authentication
184
+ session[:return_to_after_authenticating] = request.url if respond_to?(:new_session_url)
185
+ end
143
186
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blacklight
4
+ # Provides a persisted "guest" user for anonymous visitors so they can
5
+ # accumulate bookmarks and searches that are transferred to a real account
6
+ # upon sign-in (see Blacklight::Controller#transfer_guest_to_user).
7
+ #
8
+ # This is the fallback used by Rails' built-in authentication. When Devise
9
+ # together with the +devise-guests+ gem is used, that pair provides its own
10
+ # +#guest_user+ / +#current_or_guest_user+ (and knows how to build a valid
11
+ # Devise guest, e.g. skipping password validation). They sit higher in the
12
+ # ancestor chain than this concern, so +#guest_user+ defers to +super+ when
13
+ # available and only falls back to the implementation below otherwise. The
14
+ # helper methods below are intentionally named to avoid colliding with
15
+ # +devise-guests+' own +#create_guest_user+.
16
+ module GuestUser
17
+ extend ActiveSupport::Concern
18
+
19
+ # Returns a persisted guest user for the current session, creating one on
20
+ # first access. Keyed off the session so it survives across requests.
21
+ # Returns +nil+ if a guest cannot be created (e.g. no user model).
22
+ def guest_user
23
+ return super if defined?(super)
24
+ return @blacklight_guest_user if defined?(@blacklight_guest_user) && @blacklight_guest_user
25
+
26
+ @blacklight_guest_user = find_blacklight_guest_user || create_blacklight_guest_user
27
+ end
28
+
29
+ private
30
+
31
+ def find_blacklight_guest_user
32
+ return unless (id = session[:blacklight_guest_user_id])
33
+
34
+ blacklight_guest_user_class.find_by(id: id)
35
+ end
36
+
37
+ def create_blacklight_guest_user
38
+ user = blacklight_guest_user_class.create_guest_user!
39
+ session[:blacklight_guest_user_id] = user.id
40
+ user
41
+ rescue ActiveRecord::RecordInvalid
42
+ nil
43
+ end
44
+
45
+ # The model class that guest users are created from. Defaults to +User+,
46
+ # consistent with Blacklight::TokenBasedUser. Override to use another model.
47
+ def blacklight_guest_user_class
48
+ ::User
49
+ end
50
+ end
51
+ end
@@ -25,7 +25,7 @@ module Blacklight::SearchContext
25
25
 
26
26
  # Returns a list of Searches from the ids in the user's history.
27
27
  def searches_from_history
28
- session[:history].blank? ? ::Search.none : ::Search.where(id: session[:history]).order("updated_at desc")
28
+ session[:history].blank? ? ::Search.none : ::Search.where(id: session[:history]).order(updated_at: :desc)
29
29
  end
30
30
 
31
31
  # GET previous and next document json for the document specified by
@@ -7,6 +7,8 @@ module Blacklight
7
7
  include Blacklight::SearchContext
8
8
 
9
9
  included do
10
+ allow_unauthenticated_access raise: false if respond_to?(:allow_unauthenticated_access)
11
+
10
12
  copy_blacklight_config_from(CatalogController)
11
13
  end
12
14
 
@@ -23,7 +25,7 @@ module Blacklight
23
25
  flash[:error] = I18n.t('blacklight.search_history.clear.failure')
24
26
  end
25
27
 
26
- redirect_back fallback_location: blacklight.search_history_path
28
+ redirect_back_or_to(blacklight.search_history_path)
27
29
  end
28
30
  end
29
31
  end
@@ -24,6 +24,24 @@ module Blacklight::Searchable
24
24
  search_service_class.new(config: blacklight_config, search_state: search_state, **search_service_context)
25
25
  end
26
26
 
27
+ # This method may be overridden to customize search behavior.
28
+ # @return [Blacklight::Solr::Response] the solr response object
29
+ def retrieve_search_results
30
+ search_service.search_results
31
+ end
32
+
33
+ # This method may be overridden to customize search behavior.
34
+ # @return [Blacklight::Document] the solr document object
35
+ def retrieve_document(id)
36
+ search_service.fetch(id)
37
+ end
38
+
39
+ # This method may be overridden to customize search behavior.
40
+ # @return [Array<Blacklight::Document>] an array of solr document objects
41
+ def retrieve_documents(ids)
42
+ search_service.fetch(Array(ids))
43
+ end
44
+
27
45
  # Override this method on the class that includes Blacklight::Searchable to provide more context to the search service if necessary.
28
46
  # 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
47
  # Then the search builder could use some property about the current user to construct a constraint on the search.
@@ -106,5 +106,12 @@ module Blacklight
106
106
  def extra_body_classes
107
107
  @extra_body_classes ||= ["blacklight-#{controller.controller_name}", "blacklight-#{[controller.controller_name, controller.action_name].join('-')}"]
108
108
  end
109
+
110
+ ##
111
+ # True if dark mode support is enabled in the configuration.
112
+ # @see Blacklight::Configuration#dark_mode_support
113
+ def dark_mode_support?
114
+ blacklight_config.dark_mode_support
115
+ end
109
116
  end
110
117
  end
@@ -10,7 +10,7 @@
10
10
  attribute (HTML5-style doc-*) that contains the id/primary key
11
11
  of the object in question -- used by plugin for a unique value for
12
12
  DOM id's.
13
- Uses HTML for a checkbox compatible with Bootstrap 4.
13
+ Uses HTML for a checkbox compatible with Bootstrap.
14
14
  new CheckboxSubmit(document.querySelector('form.something')).render()
15
15
  */
16
16
  export default class CheckboxSubmit {
@@ -36,7 +36,7 @@ export default class CheckboxSubmit {
36
36
  }).then((json) => {
37
37
  this.labelTarget.removeAttribute('disabled')
38
38
  this.checkboxTarget.removeAttribute('disabled')
39
- // For accessibility return keyboard focus
39
+ // For accessibility return keyboard focus
40
40
  // back to the checkbox after form submission
41
41
  this.checkboxTarget.focus()
42
42
  this.updateStateFor(!this.checked)
@@ -0,0 +1,85 @@
1
+ /*!
2
+ * Color mode toggler for Blacklight
3
+ * Based on Bootstrap's color mode toggler (https://getbootstrap.com/docs/5.3/customize/color-modes/#javascript)
4
+ */
5
+
6
+ import Core from 'blacklight-frontend/core'
7
+
8
+ const ColorThemeSwitcher = (() => {
9
+ 'use strict'
10
+
11
+ const getStoredTheme = () => localStorage.getItem('theme')
12
+ const setStoredTheme = theme => localStorage.setItem('theme', theme)
13
+
14
+ const getPreferredTheme = () => {
15
+ const storedTheme = getStoredTheme()
16
+ if (storedTheme) {
17
+ return storedTheme
18
+ }
19
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
20
+ }
21
+
22
+ const setTheme = theme => {
23
+ if (theme === 'auto') {
24
+ document.documentElement.setAttribute('data-bs-theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
25
+ } else {
26
+ document.documentElement.setAttribute('data-bs-theme', theme)
27
+ }
28
+ }
29
+
30
+ const showActiveTheme = (theme, focus = false) => {
31
+ const themeSwitcher = document.querySelector('#bl-theme-switcher')
32
+ if (!themeSwitcher) return
33
+
34
+ // Reset all dropdown items
35
+ document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
36
+ element.classList.remove('active')
37
+ element.setAttribute('aria-pressed', 'false')
38
+ const check = element.querySelector('.bl-theme-check')
39
+ if (check) check.classList.add('d-none')
40
+ })
41
+
42
+ // Activate the selected item
43
+ const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
44
+ if (btnToActive) {
45
+ btnToActive.classList.add('active')
46
+ btnToActive.setAttribute('aria-pressed', 'true')
47
+ const check = btnToActive.querySelector('.bl-theme-check')
48
+ if (check) check.classList.remove('d-none')
49
+ }
50
+
51
+ // Swap the toggle button icon
52
+ themeSwitcher.querySelectorAll('.bl-theme-icon').forEach(icon => icon.classList.add('d-none'))
53
+ const activeIcon = themeSwitcher.querySelector(`.bl-theme-icon[data-bl-theme-icon="${theme}"]`)
54
+ if (activeIcon) activeIcon.classList.remove('d-none')
55
+
56
+ themeSwitcher.setAttribute('aria-label', `Toggle theme (${theme})`)
57
+
58
+ if (focus) {
59
+ themeSwitcher.focus()
60
+ }
61
+ }
62
+
63
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
64
+ const storedTheme = getStoredTheme()
65
+ if (storedTheme !== 'light' && storedTheme !== 'dark') {
66
+ setTheme(getPreferredTheme())
67
+ }
68
+ })
69
+
70
+ document.addEventListener('click', e => {
71
+ const btn = e.target.closest('[data-bs-theme-value]')
72
+ if (!btn) return
73
+
74
+ const theme = btn.getAttribute('data-bs-theme-value')
75
+ setStoredTheme(theme)
76
+ setTheme(theme)
77
+ showActiveTheme(theme, true)
78
+ })
79
+
80
+ Core.onLoad(() => showActiveTheme(getPreferredTheme()))
81
+
82
+ return { setTheme, getPreferredTheme, showActiveTheme }
83
+ })()
84
+
85
+ export default ColorThemeSwitcher
@@ -16,10 +16,13 @@ const FacetSuggest = async (e) => {
16
16
 
17
17
  // Drop facet.page so a filtered suggestion list will always start on page 1
18
18
  url.searchParams.delete('facet.page');
19
+ // add our queryFragment for facet filtering
20
+ url.searchParams.append('query_fragment', queryFragment);
21
+
19
22
  const facetSearchParams = url.searchParams.toString();
20
23
  const basePathComponent = url.pathname.split('/')[1];
21
24
 
22
- const urlToFetch = `/${basePathComponent}/facet_suggest/${facetField}/${queryFragment}?${facetSearchParams}`;
25
+ const urlToFetch = `/${basePathComponent}/facet_suggest/${facetField}?${facetSearchParams}`;
23
26
 
24
27
  const response = await fetch(urlToFetch);
25
28
  if (response.ok) {
@@ -1,5 +1,6 @@
1
1
  import BookmarkToggle from 'blacklight-frontend/bookmark_toggle'
2
2
  import ButtonFocus from 'blacklight-frontend/button_focus'
3
+ import ColorThemeSwitcher from 'blacklight-frontend/color_theme_switcher'
3
4
  import FacetSuggest from 'blacklight-frontend/facet_suggest'
4
5
  import Modal from 'blacklight-frontend/modal'
5
6
  import SearchContext from 'blacklight-frontend/search_context'
@@ -8,6 +9,7 @@ import Core from 'blacklight-frontend/core'
8
9
  export default {
9
10
  BookmarkToggle,
10
11
  ButtonFocus,
12
+ ColorThemeSwitcher,
11
13
  FacetSuggest,
12
14
  Modal,
13
15
  SearchContext,
@@ -36,8 +36,8 @@ module Blacklight::Document::DublinCore
36
36
  xml.target!
37
37
  end
38
38
 
39
- alias_method :export_as_xml, :export_as_oai_dc_xml
40
- alias_method :export_as_dc_xml, :export_as_oai_dc_xml
39
+ alias export_as_xml export_as_oai_dc_xml
40
+ alias export_as_dc_xml export_as_oai_dc_xml
41
41
 
42
42
  private
43
43
 
@@ -32,7 +32,7 @@ module Blacklight::Document
32
32
  end
33
33
 
34
34
  attr_reader :response, :_source
35
- alias_method :solr_response, :response
35
+ alias solr_response response
36
36
 
37
37
  delegate :[], :key?, :keys, :to_h, :as_json, to: :_source
38
38
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Blacklight::User
4
4
  extend ActiveSupport::Concern
5
+
5
6
  # SEE ALSO: The lib/blacklight/generator/user_generator.rb class for where this
6
7
  # is generated into the hosting application.
7
8
  included do
@@ -11,6 +12,41 @@ module Blacklight::User
11
12
  has_many :searches, dependent: :destroy, as: :user
12
13
  end
13
14
 
15
+ class_methods do
16
+ # The column that holds the user's login/identifier. Rails' authentication
17
+ # generator uses +email_address+, while Devise and others use +email+.
18
+ def blacklight_email_column
19
+ column_names.include?("email_address") ? :email_address : :email
20
+ end
21
+
22
+ # Create a persisted "guest" user for an anonymous visitor, so they can
23
+ # accumulate bookmarks and searches that are transferred to a real account
24
+ # upon sign-in. Works whether the model uses +email+ or +email_address+,
25
+ # and supplies a random password when the model authenticates with a
26
+ # password (either +has_secure_password+'s +password_digest+ column or
27
+ # Devise's +encrypted_password+ column).
28
+ def create_guest_user!
29
+ attributes = { blacklight_email_column => "guest_#{Time.current.to_i}_#{SecureRandom.hex(4)}@example.com" }
30
+
31
+ if authenticates_with_password?
32
+ password = SecureRandom.hex(16)
33
+ attributes[:password] = password
34
+ attributes[:password_confirmation] = password
35
+ end
36
+
37
+ create!(attributes)
38
+ end
39
+
40
+ # True when the model stores a password (Rails' +has_secure_password+ via
41
+ # +password_digest+, or Devise's +database_authenticatable+ via
42
+ # +encrypted_password+). In either case the model exposes a virtual
43
+ # +password+ / +password_confirmation+ attribute that must be populated to
44
+ # satisfy validation.
45
+ def authenticates_with_password?
46
+ column_names.intersect?(%w[password_digest encrypted_password])
47
+ end
48
+ end
49
+
14
50
  def bookmarks_for_documents documents = []
15
51
  if documents.any?
16
52
  bookmarks.where(document_type: documents.first.class.base_class.to_s, document_id: documents.map(&:id))
@@ -31,7 +31,7 @@ module Blacklight
31
31
 
32
32
  attr_reader :view_context, :document, :field_config, :except_operations, :options
33
33
 
34
- delegate :key, :component, to: :field_config
34
+ delegate :key, :component, :layout_component, to: :field_config
35
35
 
36
36
  # @return [Array<String>]
37
37
  def render
@@ -4,6 +4,7 @@ module Blacklight
4
4
  module Rendering
5
5
  class Microdata < AbstractStep
6
6
  include ActionView::Helpers::TagHelper
7
+
7
8
  def render
8
9
  return next_step(values) unless config.itemprop && html?
9
10
 
@@ -6,17 +6,17 @@ module Blacklight
6
6
  ##
7
7
  # Filters the query to only include the bookmarked items
8
8
  #
9
- # @param [Hash] solr_parameters
9
+ # @param [Blacklight::Solr::Request] request_parameters
10
10
  #
11
11
  # @return [void]
12
- def bookmarked(solr_parameters)
13
- solr_parameters[:fq] ||= []
12
+ def bookmarked(request_parameters)
14
13
  bookmarks = @scope.context.fetch(:bookmarks)
15
14
  return unless bookmarks
16
15
 
17
16
  document_ids = bookmarks.collect { |b| b.document_id.to_s }
18
- solr_parameters[:fq] += ["{!terms f=id}#{document_ids.join(',')}"]
17
+ request_parameters.append_ids_filter(blacklight_config.document_model.unique_key, document_ids)
19
18
  end
19
+
20
20
  self.default_processor_chain += [:bookmarked]
21
21
  end
22
22
  end