blacklight 7.19.2 → 7.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.env +1 -1
  3. data/.github/workflows/ruby.yml +19 -1
  4. data/.rubocop.yml +4 -0
  5. data/README.md +1 -1
  6. data/VERSION +1 -1
  7. data/app/assets/javascripts/blacklight/blacklight.js +8 -3
  8. data/app/assets/stylesheets/blacklight/_constraints.scss +7 -4
  9. data/app/assets/stylesheets/blacklight/_controls.scss +7 -0
  10. data/app/assets/stylesheets/blacklight/_facets.scss +2 -2
  11. data/app/assets/stylesheets/blacklight/_pagination.scss +1 -1
  12. data/app/assets/stylesheets/blacklight/_twitter_typeahead.scss +1 -0
  13. data/app/components/blacklight/constraints_component.rb +6 -2
  14. data/app/components/blacklight/document_component.rb +4 -4
  15. data/app/components/blacklight/facet_item_component.rb +2 -2
  16. data/app/components/blacklight/metadata_field_layout_component.rb +1 -1
  17. data/app/components/blacklight/response/pagination_component.html.erb +1 -1
  18. data/app/controllers/concerns/blacklight/bookmarks.rb +0 -3
  19. data/app/controllers/concerns/blacklight/catalog.rb +3 -0
  20. data/app/controllers/concerns/blacklight/controller.rb +9 -5
  21. data/app/controllers/concerns/blacklight/search_context.rb +1 -1
  22. data/app/helpers/blacklight/catalog_helper_behavior.rb +1 -3
  23. data/app/javascript/blacklight/core.js +5 -1
  24. data/app/javascript/blacklight/search_context.js +5 -2
  25. data/app/views/catalog/_home_text.html.erb +2 -2
  26. data/app/views/catalog/_paginate_compact.html.erb +1 -0
  27. data/app/views/shared/_header_navbar.html.erb +2 -2
  28. data/blacklight.gemspec +4 -2
  29. data/config/locales/blacklight.de.yml +4 -2
  30. data/config/locales/blacklight.en.yml +6 -2
  31. data/config/locales/blacklight.es.yml +3 -1
  32. data/config/locales/blacklight.fr.yml +3 -1
  33. data/config/locales/blacklight.it.yml +3 -1
  34. data/lib/blacklight/search_state/filter_field.rb +9 -0
  35. data/lib/blacklight/solr/response/group_response.rb +3 -2
  36. data/lib/blacklight/solr/response/pagination_methods.rb +1 -1
  37. data/lib/blacklight/solr/search_builder_behavior.rb +14 -0
  38. data/lib/blacklight.rb +5 -1
  39. data/lib/generators/blacklight/assets_generator.rb +4 -2
  40. data/lib/generators/blacklight/install_generator.rb +4 -1
  41. data/lib/generators/blacklight/user_generator.rb +1 -1
  42. data/package.json +2 -2
  43. data/spec/components/blacklight/facet_item_component_spec.rb +6 -2
  44. data/spec/features/axe_spec.rb +34 -0
  45. data/spec/features/facet_missing_spec.rb +59 -0
  46. data/spec/features/facets_spec.rb +1 -1
  47. data/spec/helpers/blacklight/facets_helper_behavior_spec.rb +2 -2
  48. data/spec/lib/blacklight/search_state/filter_field_spec.rb +27 -0
  49. data/spec/models/blacklight/solr/response/group_response_spec.rb +3 -2
  50. data/spec/models/blacklight/solr/search_builder_spec.rb +13 -0
  51. data/spec/spec_helper.rb +15 -9
  52. data/spec/test_app_templates/lib/generators/test_app_generator.rb +0 -3
  53. metadata +43 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f48da02e969e0c163d09760d711066ce9c0b4d5078636c8f50e19699ec58e8e
4
- data.tar.gz: 77da2b86854e93f08a79ba9e28c86c7c91116b9f6934229eb6936cc6872bfdc6
3
+ metadata.gz: 514bb2df051f28fda2f7b1a2f4b309bd20cf79843dbaa74b519232543f36bc70
4
+ data.tar.gz: 0bbb3de22863a1e3b9e0aa55d0b9e501550f955fcc2630685e78572b7baf3d05
5
5
  SHA512:
6
- metadata.gz: a42155551ab05fa25e433a7aad6e40078ce7cbc2918de03f6ede94aef53ade03b4b16f4411a915b737b1233a631d9cb2baddc9d560aa70b67df237776c01e7f6
7
- data.tar.gz: 6430192899c4db45277e988217aecc04e954d856645941773176e36ecefd3ddda3c2a9988caffb7bde50f8aa76702a10cd00da67216f17c6fb49a2802a678dfe
6
+ metadata.gz: c8a023e934c9d2b1365f71104047b53d60d51073f53fecf80d8e159a66edf9dbb5148db8be095b522d04e9d5939cf623fe824fa9607bfc6ed123c3e5cb08dfb7
7
+ data.tar.gz: e04a8a63ca946e9d31696ecc76c8da4fca3495687174dba352f613f64388a7802df446d2c902507d99f767b163acea71d2dd021f90f60f2aebe97ae8b115e0d4
data/.env CHANGED
@@ -1,5 +1,5 @@
1
1
  ALPINE_RUBY_VERSION=2.6.5
2
- RAILS_VERSION=5.2.4.1
2
+ RAILS_VERSION=5.2.5
3
3
  SOLR_PORT=8983
4
4
  SOLR_URL=http://solr:8983/solr/blacklight-core
5
5
  SOLR_VERSION=latest
@@ -43,6 +43,24 @@ jobs:
43
43
  run: bundle exec rake ci
44
44
  env:
45
45
  ENGINE_CART_RAILS_OPTIONS: '--skip-git --skip-listen --skip-spring --skip-keeps --skip-action-cable --skip-coffee --skip-test'
46
+ test_bootstrap5:
47
+ runs-on: ubuntu-latest
48
+ strategy:
49
+ matrix:
50
+ ruby: [3.0]
51
+ steps:
52
+ - uses: actions/checkout@v2
53
+ - name: Set up Ruby
54
+ uses: ruby/setup-ruby@v1
55
+ with:
56
+ ruby-version: ${{ matrix.ruby }}
57
+ - name: Install dependencies
58
+ run: bundle install
59
+ - name: Run tests
60
+ run: bundle exec rake ci
61
+ env:
62
+ BOOTSTRAP_VERSION: '~> 5.0'
63
+ ENGINE_CART_RAILS_OPTIONS: '--skip-git --skip-listen --skip-spring --skip-keeps --skip-action-cable --skip-coffee --skip-test'
46
64
  test_rails6_0:
47
65
  runs-on: ubuntu-latest
48
66
  strategy:
@@ -87,7 +105,7 @@ jobs:
87
105
  runs-on: ubuntu-latest
88
106
  strategy:
89
107
  matrix:
90
- ruby: [2.7]
108
+ ruby: [2.7, 3.0]
91
109
  steps:
92
110
  - uses: actions/checkout@v2
93
111
  - name: Set up Ruby
data/.rubocop.yml CHANGED
@@ -50,6 +50,10 @@ Metrics/ModuleLength:
50
50
  - 'app/controllers/concerns/blacklight/catalog.rb'
51
51
  - 'lib/blacklight/solr/search_builder_behavior.rb'
52
52
 
53
+ Metrics/AbcSize:
54
+ Exclude:
55
+ - "lib/blacklight/search_state/filter_field.rb"
56
+
53
57
  Naming/HeredocDelimiterNaming:
54
58
  Enabled: false
55
59
 
data/README.md CHANGED
@@ -35,7 +35,7 @@ rails generate blacklight:install
35
35
 
36
36
  * Ruby 2.2+
37
37
  * Bundler
38
- * Rails 5.1+
38
+ * Rails 5.2+
39
39
 
40
40
  ## Contributing Code
41
41
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 7.19.2
1
+ 7.20.0
@@ -36,7 +36,11 @@ Blacklight.listeners().forEach(function (listener) {
36
36
  Blacklight.activate();
37
37
  });
38
38
  });
39
- $('.no-js').removeClass('no-js').addClass('js');
39
+ Blacklight.onLoad(function () {
40
+ const elem = document.querySelector('.no-js');
41
+ elem.classList.remove('no-js');
42
+ elem.classList.add('js');
43
+ });
40
44
  /*global Bloodhound */
41
45
 
42
46
  Blacklight.onLoad(function () {
@@ -460,8 +464,8 @@ Blacklight.handleSearchContextMethod = function (event) {
460
464
 
461
465
  let href = link.getAttribute('data-context-href');
462
466
  let target = link.getAttribute('target');
463
- let csrfToken = Rails.csrfToken();
464
- let csrfParam = Rails.csrfParam();
467
+ let csrfToken = document.querySelector('meta[name=csrf-token]')?.content
468
+ let csrfParam = document.querySelector('meta[name=csrf-param]')?.content
465
469
  let form = document.createElement('form');
466
470
  form.method = 'post';
467
471
  form.action = href;
@@ -495,3 +499,4 @@ Blacklight.handleSearchContextMethod = function (event) {
495
499
  Blacklight.onLoad(function () {
496
500
  Blacklight.doSearchContextBehavior();
497
501
  });
502
+
@@ -1,27 +1,30 @@
1
1
  .constraints-container {
2
2
  @extend .mb-2;
3
+ @extend .d-flex;
3
4
  }
4
5
 
5
6
  .applied-filter {
7
+ @extend .mx-1;
8
+
6
9
  .constraint-value {
7
10
  cursor: default;
8
11
  text-overflow: ellipsis;
9
12
  overflow: hidden;
10
13
 
11
14
  @media (max-width: breakpoint-min(sm)) {
12
- max-width: breakpoint-min(sm) / 2;
15
+ max-width: breakpoint-min(sm) * .5;
13
16
  }
14
17
 
15
18
  @media (min-width: breakpoint-min(sm)) and (max-width: breakpoint-max(sm)) {
16
- max-width: breakpoint-min(sm) / 2;
19
+ max-width: breakpoint-min(sm) * .5;
17
20
  }
18
21
 
19
22
  @media (min-width: breakpoint-min(md)) and (max-width: breakpoint-max(md)) {
20
- max-width: breakpoint-min(md) / 2;
23
+ max-width: breakpoint-min(md) * .5;
21
24
  }
22
25
 
23
26
  @media (min-width: breakpoint-min(lg)) {
24
- max-width: breakpoint-min(lg) / 2;
27
+ max-width: breakpoint-min(lg) * .5;
25
28
  }
26
29
 
27
30
  &:hover, &:active {
@@ -1,3 +1,10 @@
1
+ .search-widgets {
2
+ @extend .d-flex;
3
+ > * {
4
+ @extend .mx-1;
5
+ }
6
+ }
7
+
1
8
  .sort-pagination,
2
9
  .pagination-search-widgets {
3
10
  border-bottom: $pagination-border-width solid $pagination-border-color;
@@ -83,7 +83,7 @@
83
83
  .remove {
84
84
  color: $text-muted;
85
85
  font-weight: bold;
86
- padding-left: $spacer / 2;
86
+ padding-left: $spacer * .5;
87
87
  text-decoration: none;
88
88
 
89
89
  &:hover {
@@ -104,7 +104,7 @@
104
104
  padding-right: 1em;
105
105
  text-indent: -15px;
106
106
  padding-left: 15px;
107
- padding-bottom: $spacer / 2;
107
+ padding-bottom: $spacer * .5;
108
108
  @include hyphens-auto;
109
109
  }
110
110
 
@@ -6,7 +6,7 @@
6
6
  }
7
7
 
8
8
  .record-padding {
9
- margin-top: $spacer / 2;
9
+ margin-top: $spacer * .5;
10
10
  }
11
11
 
12
12
  .pagination {
@@ -1,4 +1,5 @@
1
1
  .twitter-typeahead {
2
+ display: inline-flex !important;
2
3
  flex-grow: 2;
3
4
  z-index: $zindex-typeahead;
4
5
 
@@ -63,10 +63,14 @@ module Blacklight
63
63
 
64
64
  Deprecation.silence(Blacklight::SearchState) do
65
65
  @search_state.filters.map do |facet|
66
+ missing_facet = @search_state.params.dig("f", "-#{facet.key}:").present?
66
67
  facet.values.map do |val|
67
- next if val.blank? # skip empty string
68
+ next if val.blank? && !missing_facet
68
69
 
69
- if val.is_a?(Array)
70
+ if missing_facet && val.blank?
71
+ missing = I18n.t("blacklight.search.facets.missing")
72
+ yield facet_item_presenter(facet.config, missing, facet.key)
73
+ elsif val.is_a?(Array)
70
74
  yield inclusive_facet_item_presenter(facet.config, val, facet.key) if val.any?(&:present?)
71
75
  else
72
76
  yield facet_item_presenter(facet.config, val, facet.key)
@@ -121,10 +121,10 @@ module Blacklight
121
121
  end
122
122
 
123
123
  def before_render
124
- set_slot(:title) unless title
125
- set_slot(:thumbnail, component: @thumbnail_component || presenter.view_config&.thumbnail_component) unless thumbnail || show?
126
- set_slot(:metadata, component: @metadata_component, fields: presenter.field_presenters) unless metadata
127
- set_slot(:embed, component: @embed_component || presenter.view_config&.embed_component) unless embed
124
+ set_slot(:title, nil) unless title
125
+ set_slot(:thumbnail, nil, component: @thumbnail_component || presenter.view_config&.thumbnail_component) unless thumbnail || show?
126
+ set_slot(:metadata, nil, component: @metadata_component, fields: presenter.field_presenters) unless metadata
127
+ set_slot(:embed, nil, component: @embed_component || presenter.view_config&.embed_component) unless embed
128
128
  end
129
129
 
130
130
  private
@@ -72,7 +72,7 @@ module Blacklight
72
72
  # @private
73
73
  def render_facet_value
74
74
  tag.span(class: "facet-label") do
75
- link_to_unless(@suppress_link, @label, @href, class: "facet-select")
75
+ link_to_unless(@suppress_link, @label, @href, class: "facet-select", rel: "nofollow")
76
76
  end + render_facet_count
77
77
  end
78
78
 
@@ -85,7 +85,7 @@ module Blacklight
85
85
  tag.span(class: "facet-label") do
86
86
  tag.span(@label, class: "selected") +
87
87
  # remove link
88
- link_to(@href, class: "remove") do
88
+ link_to(@href, class: "remove", rel: "nofollow") do
89
89
  tag.span('✖', class: "remove-icon", aria: { hidden: true }) +
90
90
  tag.span(@view_context.t(:'blacklight.search.facets.selected.remove'), class: 'sr-only visually-hidden')
91
91
  end
@@ -23,7 +23,7 @@ module Blacklight
23
23
  end
24
24
 
25
25
  def value(*args, **kwargs, &block)
26
- return set_slot(:values, *args, **kwargs, &block) if block_given?
26
+ return set_slot(:values, nil, *args, **kwargs, &block) if block_given?
27
27
 
28
28
  Deprecation.warn(Blacklight::MetadataFieldLayoutComponent, 'The `value` content area is deprecated; render from the values slot instead')
29
29
 
@@ -1,3 +1,3 @@
1
- <%= content_tag :nav, class: 'pagination', role: 'region', **@html_attr do %>
1
+ <%= content_tag :section, class: 'pagination', **@html_attr do %>
2
2
  <%= pagination %>
3
3
  <% end %>
@@ -48,9 +48,6 @@ module Blacklight::Bookmarks
48
48
  format.html {}
49
49
  format.rss { render layout: false }
50
50
  format.atom { render layout: false }
51
- format.json do
52
- render json: render_search_results_as_json
53
- end
54
51
 
55
52
  additional_response_formats(format)
56
53
  document_export_formats(format)
@@ -2,6 +2,9 @@
2
2
  module Blacklight::Catalog
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ # MimeResponds is part of ActionController::Base, but not ActionController::API
6
+ include ActionController::MimeResponds
7
+
5
8
  include Blacklight::Base
6
9
  include Blacklight::Facet
7
10
  include Blacklight::Searchable
@@ -3,6 +3,7 @@
3
3
  # as this module is mixed-in to the application controller in the hosting app on installation.
4
4
  module Blacklight::Controller
5
5
  extend ActiveSupport::Concern
6
+ extend Deprecation
6
7
 
7
8
  included do
8
9
  include Blacklight::SearchFields
@@ -38,11 +39,6 @@ module Blacklight::Controller
38
39
  # TODO: move to Searchable
39
40
  class_attribute :search_service_class
40
41
  self.search_service_class = Blacklight::SearchService
41
-
42
- # This callback runs when a user first logs in
43
-
44
- define_callbacks :logging_in_user
45
- set_callback :logging_in_user, :before, :transfer_guest_user_actions_to_current_user
46
42
  end
47
43
 
48
44
  # @private
@@ -136,6 +132,13 @@ module Blacklight::Controller
136
132
 
137
133
  ##
138
134
  # When a user logs in, transfer any saved searches or bookmarks to the current_user
135
+ def transfer_guest_to_user
136
+ Deprecation.silence(Blacklight::Controller) do
137
+ transfer_guest_user_actions_to_current_user
138
+ end
139
+ end
140
+
141
+ # @deprecated use canonical `transfer_guest_to_user` method instead
139
142
  def transfer_guest_user_actions_to_current_user
140
143
  return unless respond_to?(:current_user) && respond_to?(:guest_user) && current_user && guest_user
141
144
 
@@ -155,6 +158,7 @@ module Blacklight::Controller
155
158
  # let guest_user know we've moved some bookmarks from under it
156
159
  guest_user.reload if guest_user.persisted?
157
160
  end
161
+ deprecation_deprecate :transfer_guest_user_actions_to_current_user
158
162
 
159
163
  ##
160
164
  # To handle failed authorization attempts, redirect the user to the
@@ -73,7 +73,7 @@ module Blacklight::SearchContext
73
73
  #
74
74
  def agent_is_crawler?
75
75
  crawler_proc = blacklight_config.crawler_detector
76
- return false if crawler_proc.nil? || current_user.present?
76
+ return false if crawler_proc.nil? || (defined?(current_user) && current_user.present?)
77
77
 
78
78
  crawler_proc.call(request)
79
79
  end
@@ -47,11 +47,9 @@ module Blacklight::CatalogHelperBehavior
47
47
  entry_name = if entry_name
48
48
  entry_name.pluralize(collection.size, I18n.locale)
49
49
  else
50
- collection.entry_name(count: collection.size).to_s.downcase
50
+ collection.entry_name(count: collection.size).to_s
51
51
  end
52
52
 
53
- entry_name = entry_name.pluralize unless collection.total_count == 1
54
-
55
53
  # grouped response objects need special handling
56
54
  end_num = if collection.respond_to?(:groups) && render_grouped_response?(collection)
57
55
  collection.groups.length
@@ -38,4 +38,8 @@ Blacklight.listeners().forEach(function(listener) {
38
38
  })
39
39
  })
40
40
 
41
- $('.no-js').removeClass('no-js').addClass('js');
41
+ Blacklight.onLoad(function () {
42
+ const elem = document.querySelector('.no-js')
43
+ elem.classList.remove('no-js')
44
+ elem.classList.add('js')
45
+ })
@@ -15,6 +15,9 @@ Blacklight.doSearchContextBehavior = function() {
15
15
  })
16
16
  };
17
17
 
18
+ Blacklight.csrfToken = () => document.querySelector('meta[name=csrf-token]')?.content
19
+ Blacklight.csrfParam = () => document.querySelector('meta[name=csrf-param]')?.content
20
+
18
21
  // this is the Rails.handleMethod with a couple adjustments, described inline:
19
22
  // first, we're attaching this directly to the event handler, so we can check for meta-keys
20
23
  Blacklight.handleSearchContextMethod = function(event) {
@@ -27,8 +30,8 @@ Blacklight.handleSearchContextMethod = function(event) {
27
30
  // instead of using the normal href, we need to use the context href instead
28
31
  let href = link.getAttribute('data-context-href')
29
32
  let target = link.getAttribute('target')
30
- let csrfToken = Rails.csrfToken()
31
- let csrfParam = Rails.csrfParam()
33
+ let csrfToken = Blacklight.csrfToken()
34
+ let csrfParam = Blacklight.csrfParam()
32
35
  let form = document.createElement('form')
33
36
  form.method = 'post'
34
37
  form.action = href
@@ -1,11 +1,11 @@
1
- <div class="jumbotron text-center">
1
+ <div class="jumbotron text-center p-5 mb-4 bg-light rounded-3">
2
2
  <h1 class="jumbotron-heading"><%= t('blacklight.welcome') %></h1>
3
3
 
4
4
  <p class="lead">Blacklight is a multi-institutional open-source collaboration building a better discovery platform framework.</p>
5
5
 
6
6
  <p>
7
7
  <%= link_to 'Read the Documentation', 'https://github.com/projectblacklight/blacklight/wiki', class: 'btn btn-primary' %>
8
- <%= link_to 'See Examples', 'http://projectblacklight.org', class: 'btn btn-outline-secondary' %>
8
+ <%= link_to 'See Examples', 'http://projectblacklight.org', class: 'btn btn-light' %>
9
9
  </p>
10
10
  </div>
11
11
 
@@ -2,5 +2,6 @@
2
2
  response: paginate_compact,
3
3
  theme: :blacklight_compact,
4
4
  page_entries_info: page_entries_info(paginate_compact),
5
+ role: nil,
5
6
  html: { aria: {} })
6
7
  %>
@@ -11,8 +11,8 @@
11
11
  </div>
12
12
  </nav>
13
13
 
14
- <div class="navbar-search navbar navbar-light bg-light" role="navigation">
14
+ <%= content_tag :div, class: 'navbar-search navbar navbar-light bg-light', role: 'navigation', aria: { label: t('blacklight.search.header') } do %>
15
15
  <div class="<%= container_classes %>">
16
16
  <%= render_search_bar %>
17
17
  </div>
18
- </div>
18
+ <% end %>
data/blacklight.gemspec CHANGED
@@ -32,14 +32,16 @@ Gem::Specification.new do |s|
32
32
  s.add_dependency "deprecation"
33
33
  s.add_dependency "i18n", '>= 1.7.0' # added named parameters
34
34
  s.add_dependency "ostruct", '>= 0.3.2'
35
- s.add_dependency "view_component", '>= 2.28.0'
35
+ s.add_dependency "view_component", '~> 2.42.0'
36
36
 
37
37
  s.add_development_dependency "rsolr", ">= 1.0.6", "< 3" # Library for interacting with rSolr.
38
38
  s.add_development_dependency "rspec-rails", "~> 4.0.0.beta2"
39
39
  s.add_development_dependency "rspec-its"
40
40
  s.add_development_dependency "rspec-collection_matchers", ">= 1.0"
41
+ s.add_development_dependency 'axe-core-rspec'
41
42
  s.add_development_dependency "capybara", '~> 3'
42
- s.add_development_dependency 'apparition'
43
+ s.add_development_dependency 'webdrivers'
44
+ s.add_development_dependency 'selenium-webdriver'
43
45
  s.add_development_dependency 'engine_cart', '~> 2.1'
44
46
  s.add_development_dependency "equivalent-xml"
45
47
  s.add_development_dependency "simplecov"
@@ -169,7 +169,7 @@ de:
169
169
  pagination:
170
170
  title: 'Ergebnisse Navigation'
171
171
  pagination_info:
172
- no_items_found: 'Kein %{entry_name} gefunden'
172
+ no_items_found: 'Keine %{entry_name} gefunden'
173
173
  single_item_found: '<strong>1</strong> %{entry_name} gefunden'
174
174
  pages:
175
175
  one: '<strong>%{start_num}</strong> - <strong>%{end_num}</strong> von <strong>%{total_num}</strong>'
@@ -230,7 +230,9 @@ de:
230
230
  list: "Liste"
231
231
 
232
232
  entry_name:
233
- default: 'Eintrag'
233
+ default:
234
+ one: Eintrag
235
+ other: Enträge
234
236
  grouped:
235
237
  default: 'gruppiertes Ergebnis'
236
238
 
@@ -230,9 +230,13 @@ en:
230
230
  list: "List"
231
231
 
232
232
  entry_name:
233
- default: 'entry'
233
+ default:
234
+ one: 'entry'
235
+ other: 'entries'
234
236
  grouped:
235
- default: 'grouped result'
237
+ default:
238
+ one: 'grouped result'
239
+ other: 'grouped results'
236
240
 
237
241
  did_you_mean: 'Did you mean to type: %{options}?'
238
242
 
@@ -230,7 +230,9 @@ es:
230
230
  list: "Lista"
231
231
 
232
232
  entry_name:
233
- default: 'entrada'
233
+ default:
234
+ one: 'entrada'
235
+ other: 'entradas'
234
236
  grouped:
235
237
  default: 'resultado agrupado'
236
238
 
@@ -234,7 +234,9 @@ fr:
234
234
 
235
235
 
236
236
  entry_name:
237
- default: 'résultat'
237
+ default:
238
+ one: 'résultat'
239
+ other: 'résultats'
238
240
  grouped:
239
241
  default: 'résultat groupé'
240
242
 
@@ -230,7 +230,9 @@ it:
230
230
  list: "Lista"
231
231
 
232
232
  entry_name:
233
- default: 'termine di ricerca'
233
+ default:
234
+ one: 'termine di ricerca'
235
+ other: 'termini di ricerca'
234
236
  grouped:
235
237
  default: 'risultato raggruppato'
236
238
 
@@ -83,10 +83,19 @@ module Blacklight
83
83
  Deprecation.warn(self, 'Normalizing parameters in FilterField#remove is deprecated')
84
84
  collection = collection.values
85
85
  end
86
+
86
87
  params[param][key] = collection - Array(value)
87
88
  params[param].delete(key) if params[param][key].empty?
88
89
  params.delete(param) if params[param].empty?
89
90
 
91
+ # Handle missing field queries.
92
+ missing = I18n.t("blacklight.search.facets.missing")
93
+ if (item.respond_to?(:fq) && item.fq == "-#{key}:[* TO *]") ||
94
+ item == missing
95
+ params[param].delete("-#{key}:")
96
+ params[param].delete(key) if params[param][key] == [""]
97
+ end
98
+
90
99
  new_state.reset(params)
91
100
  end
92
101
 
@@ -45,8 +45,9 @@ class Blacklight::Solr::Response::GroupResponse
45
45
  def entry_name(options)
46
46
  I18n.t(
47
47
  "blacklight.entry_name.grouped.#{key}",
48
- default: :'blacklight.entry_name.grouped.default'
49
- ).pluralize(options[:count])
48
+ default: :'blacklight.entry_name.grouped.default',
49
+ count: options[:count]
50
+ )
50
51
  end
51
52
 
52
53
  def method_missing meth, *args, &block
@@ -24,6 +24,6 @@ module Blacklight::Solr::Response::PaginationMethods
24
24
  ##
25
25
  # Meant to have the same signature as Kaminari::PaginatableArray#entry_name
26
26
  def entry_name(options)
27
- I18n.t('blacklight.entry_name.default').pluralize(options[:count])
27
+ I18n.t('blacklight.entry_name.default', count: options[:count])
28
28
  end
29
29
  end
@@ -10,6 +10,7 @@ module Blacklight::Solr
10
10
  :add_facetting_to_solr, :add_solr_fields_to_query, :add_paging_to_solr,
11
11
  :add_sorting_to_solr, :add_group_config_to_solr,
12
12
  :add_facet_paging_to_solr, :add_adv_search_clauses,
13
+ :add_missing_field_query,
13
14
  :add_additional_filters
14
15
  ]
15
16
  end
@@ -81,6 +82,19 @@ module Blacklight::Solr
81
82
  end
82
83
  end
83
84
 
85
+ ##
86
+ # Build and append a missing field query.
87
+ ##
88
+ def add_missing_field_query(solr_parameters)
89
+ return unless solr_parameters["facet.missing"]
90
+
91
+ solr_parameters[:fq] = [] if solr_parameters[:fq].blank?
92
+
93
+ solr_parameters[:fq].append(*(blacklight_params["f"] || [])
94
+ .select { |f| f.match(/^-/) }
95
+ .map { |k, _v| "#{k}[* TO *]" })
96
+ end
97
+
84
98
  def add_additional_filters(solr_parameters, additional_filters = nil)
85
99
  q = additional_filters || @additional_filters
86
100
 
data/lib/blacklight.rb CHANGED
@@ -96,7 +96,11 @@ module Blacklight
96
96
  end
97
97
 
98
98
  begin
99
- @blacklight_yml = YAML.safe_load(blacklight_erb)
99
+ @blacklight_yml = if RUBY_VERSION > '2.6'
100
+ YAML.safe_load(blacklight_erb, aliases: true)
101
+ else
102
+ YAML.safe_load(blacklight_erb, [], [], true)
103
+ end
100
104
  rescue => e
101
105
  raise("#{blacklight_config_file} was found, but could not be parsed.\n#{e.inspect}")
102
106
  end
@@ -3,9 +3,11 @@ module Blacklight
3
3
  class Assets < Rails::Generators::Base
4
4
  source_root File.expand_path('../templates', __FILE__)
5
5
 
6
+ class_option :'bootstrap-version', type: :string, default: ENV.fetch('BOOTSTRAP_VERSION', '~> 4.0'), desc: "Set the generated app's bootstrap version"
7
+
6
8
  # This could be skipped if you want to use webpacker
7
9
  def add_javascript_dependencies
8
- gem 'bootstrap', '~> 4.0'
10
+ gem 'bootstrap', options[:'bootstrap-version']
9
11
  gem 'twitter-typeahead-rails', '0.11.1.pre.corejavascript'
10
12
  end
11
13
 
@@ -15,7 +17,7 @@ module Blacklight
15
17
 
16
18
  create_file 'app/assets/javascripts/application.js' do
17
19
  <<~CONTENT
18
- //= require jquery
20
+ //= require jquery3
19
21
  //= require rails-ujs
20
22
  //= require turbolinks
21
23
  CONTENT
@@ -10,6 +10,7 @@ module Blacklight
10
10
 
11
11
  class_option :devise, type: :boolean, default: false, aliases: "-d", desc: "Use Devise as authentication logic."
12
12
  class_option :marc, type: :boolean, default: false, aliases: "-m", desc: "Generate MARC-based demo."
13
+ class_option :'bootstrap-version', type: :string, default: nil, desc: "Set the generated app's bootstrap version"
13
14
  class_option :'skip-assets', type: :boolean, default: !defined?(Sprockets), desc: "Skip generating javascript and css assets into the application"
14
15
  class_option :'skip-solr', type: :boolean, default: false, desc: "Skip generating solr configurations."
15
16
 
@@ -33,7 +34,9 @@ module Blacklight
33
34
  # Call external generator in AssetsGenerator, so we can
34
35
  # leave that callable seperately too.
35
36
  def copy_public_assets
36
- generate "blacklight:assets" unless options[:'skip-assets']
37
+ generated_options = "--bootstrap-version #{options[:'bootstrap-version']}" if options[:'bootstrap-version']
38
+
39
+ generate "blacklight:assets", generated_options unless options[:'skip-assets']
37
40
  end
38
41
 
39
42
  def bundle_install
@@ -22,7 +22,7 @@ module Blacklight
22
22
  return unless options[:devise]
23
23
 
24
24
  gem "devise"
25
- gem "devise-guests", "~> 0.6"
25
+ gem "devise-guests", "~> 0.8"
26
26
 
27
27
  inside destination_root do
28
28
  Bundler.with_clean_env do
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blacklight-frontend",
3
- "version": "7.10.0",
3
+ "version": "7.20.0",
4
4
  "description": "[![Build Status](https://travis-ci.com/projectblacklight/blacklight.png?branch=master)](https://travis-ci.com/projectblacklight/blacklight) [![Gem Version](https://badge.fury.io/rb/blacklight.png)](http://badge.fury.io/rb/blacklight) [![Coverage Status](https://coveralls.io/repos/github/projectblacklight/blacklight/badge.svg?branch=master)](https://coveralls.io/github/projectblacklight/blacklight?branch=master)",
5
5
  "main": "app/assets/javascripts/blacklight",
6
6
  "scripts": {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "bloodhound-js": "^1.2.3",
30
- "bootstrap": "^4.3.1",
30
+ "bootstrap": ">=4.3.1 <6.0.0",
31
31
  "jquery": "^3.5.1",
32
32
  "typeahead.js": "^0.11.1"
33
33
  }
@@ -20,7 +20,9 @@ RSpec.describe Blacklight::FacetItemComponent, type: :component do
20
20
 
21
21
  it 'links to the facet and shows the number of hits' do
22
22
  expect(rendered).to have_selector 'li'
23
- expect(rendered).to have_link 'x', href: '/catalog?f=x'
23
+ expect(rendered).to have_link 'x', href: '/catalog?f=x' do |link|
24
+ link['rel'] == 'nofollow'
25
+ end
24
26
  expect(rendered).to have_selector '.facet-count', text: '10'
25
27
  end
26
28
 
@@ -39,7 +41,9 @@ RSpec.describe Blacklight::FacetItemComponent, type: :component do
39
41
  it 'links to the facet and shows the number of hits' do
40
42
  expect(rendered).to have_selector 'li'
41
43
  expect(rendered).to have_selector '.selected', text: 'x'
42
- expect(rendered).to have_link '[remove]', href: '/catalog'
44
+ expect(rendered).to have_link '[remove]', href: '/catalog' do |link|
45
+ link['rel'] == 'nofollow'
46
+ end
43
47
  expect(rendered).to have_selector '.selected.facet-count', text: '10'
44
48
  end
45
49
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'Accessibility testing', api: false, js: true do
4
+ xit 'validates the home page' do
5
+ visit root_path
6
+ expect(page).to be_accessible
7
+ end
8
+
9
+ xit 'validates the catalog page' do
10
+ visit root_path
11
+ fill_in "q", with: 'history'
12
+ click_button 'search'
13
+
14
+ # aria-allowed-role doesn't like nav[role="region"]
15
+ expect(page).to be_accessible(skipping: ['aria-allowed-role'])
16
+
17
+ within '.card.blacklight-language_ssim' do
18
+ click_button 'Language'
19
+ click_link "Tibetan"
20
+ end
21
+
22
+ expect(page).to be_accessible(skipping: ['aria-allowed-role'])
23
+ end
24
+
25
+ xit 'validates the single results page' do
26
+ visit solr_document_path('2007020969')
27
+ expect(page).to be_accessible
28
+ end
29
+
30
+ def be_accessible(skipping: [])
31
+ # typeahead does funny things with the search bar
32
+ be_axe_clean.excluding('.tt-hint').skipping(skipping + [('color-contrast' if Bootstrap::VERSION < '5')])
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Facet missing" do
4
+ before do
5
+ CatalogController.blacklight_config[:default_solr_params]["facet.missing"] = true
6
+ end
7
+
8
+ after do
9
+ CatalogController.blacklight_config[:default_solr_params].delete("facet.missing")
10
+ end
11
+
12
+ context "selecting missing field in facets" do
13
+ it "adds facet missing query and constraints" do
14
+ visit root_path
15
+
16
+ within "#facet-subject_geo_ssim" do
17
+ click_link "[Missing]"
18
+ end
19
+
20
+ within "#facet-subject_geo_ssim" do
21
+ expect(page).to have_selector("span.selected", text: "[Missing")
22
+ expect(page).to have_selector("span.facet-count.selected", text: "13")
23
+ end
24
+
25
+ within "#sortAndPerPage" do
26
+ expect(page).to have_content "1 - 10 of 13"
27
+ end
28
+
29
+ expect(page).to have_css(".constraint-value", text: "Region")
30
+ expect(page).to have_css(".constraint-value", text: "[Missing]")
31
+ end
32
+ end
33
+
34
+ context "unselecting the facet missing facet" do
35
+ it "unselects the missig field facet" do
36
+ visit root_path + "?f[-subject_geo_ssim:[* TO *]][]=&f[subject_geo_ssim][]="
37
+
38
+ within "#facet-subject_geo_ssim" do
39
+ click_link "remove"
40
+ end
41
+
42
+ expect(page).not_to have_link "remove"
43
+ expect(page).to have_content("Welcome!")
44
+ end
45
+ end
46
+
47
+ context "unselecting the facet missing constraint" do
48
+ it "unselects the missig field facet" do
49
+ visit root_path + "?f[-subject_geo_ssim:[* TO *]][]=&f[subject_geo_ssim][]="
50
+
51
+ within ".filter-subject_geo_ssim" do
52
+ click_link "Remove constraint Region: [Missing]"
53
+ end
54
+
55
+ expect(page).not_to have_link "remove"
56
+ expect(page).to have_content("Welcome!")
57
+ end
58
+ end
59
+ end
@@ -69,7 +69,7 @@ RSpec.describe "Facets" do
69
69
  page.find('h3.facet-field-heading button', text: 'Pivot Field').click
70
70
 
71
71
  within '#facet-example_pivot_field' do
72
- expect(page).to have_css('.facet-leaf-node', text: "Book\t30")
72
+ expect(page).to have_css('.facet-leaf-node', text: "Book 30")
73
73
  expect(page).not_to have_css('.facet-select', text: 'Tibetan')
74
74
  page.find('.facet-toggle-handle').click
75
75
  click_link 'Tibetan'
@@ -314,7 +314,7 @@ RSpec.describe Blacklight::FacetsHelperBehavior do
314
314
  end
315
315
 
316
316
  describe "simple case" do
317
- let(:expected_html) { '<span class="facet-label"><a class="facet-select" href="/catalog">Z</a></span><span class="facet-count">10</span>' }
317
+ let(:expected_html) { '<span class="facet-label"><a class="facet-select" rel="nofollow" href="/catalog">Z</a></span><span class="facet-count">10</span>' }
318
318
 
319
319
  it "uses facet_display_value" do
320
320
  result = helper.render_facet_value('simple_field', item)
@@ -323,7 +323,7 @@ RSpec.describe Blacklight::FacetsHelperBehavior do
323
323
  end
324
324
 
325
325
  describe "when :url_method is set" do
326
- let(:expected_html) { '<span class="facet-label"><a class="facet-select" href="/blabla">Z</a></span><span class="facet-count">10</span>' }
326
+ let(:expected_html) { '<span class="facet-label"><a class="facet-select" rel="nofollow" href="/blabla">Z</a></span><span class="facet-count">10</span>' }
327
327
 
328
328
  it "uses that method" do
329
329
  allow(helper).to receive(:facet_configuration_for_field).with('simple_field').and_return(Blacklight::Configuration::FacetField.new(key: 'simple_field', query: nil, date: nil, helper_method: nil, single: false, url_method: :test_method))
@@ -159,6 +159,33 @@ RSpec.describe Blacklight::SearchState::FilterField do
159
159
  expect(new_state.filter('another_field').values).to eq ['3']
160
160
  end
161
161
  end
162
+
163
+ context "With facet.missing field" do
164
+ let(:params) do
165
+ { f: { some_field: [""], "-some_field:": [""] } }
166
+ end
167
+
168
+ it "removes facet.missing facet params" do
169
+ filter = search_state.filter("some_field")
170
+ new_state = filter.remove(OpenStruct.new(fq: "-some_field:[* TO *]"))
171
+
172
+ expect(new_state.params).to eq("f" => {})
173
+ end
174
+ end
175
+
176
+ context "With facet.missing field value" do
177
+ let(:params) do
178
+ { f: { some_field: [""], "-some_field:": [""] } }
179
+ end
180
+
181
+ it "removes facet.missing facet params" do
182
+ missing = I18n.t("blacklight.search.facets.missing")
183
+ filter = search_state.filter("some_field")
184
+ new_state = filter.remove(missing)
185
+
186
+ expect(new_state.params).to eq("f" => {})
187
+ end
188
+ end
162
189
  end
163
190
 
164
191
  describe '#values' do
@@ -69,8 +69,9 @@ RSpec.describe Blacklight::Solr::Response::GroupResponse, api: true do
69
69
  it "accesses a custom field grouped i18n key" do
70
70
  allow(I18n).to receive(:t).with(
71
71
  'blacklight.entry_name.grouped.result_group_ssi',
72
- default: :'blacklight.entry_name.grouped.default'
73
- ).and_return('cool group')
72
+ default: :'blacklight.entry_name.grouped.default',
73
+ count: 2
74
+ ).and_return('cool groups')
74
75
  expect(group.entry_name(count: 2)).to eq 'cool groups'
75
76
  end
76
77
 
@@ -27,6 +27,10 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
27
27
  it "uses the class-level default_processor_chain" do
28
28
  expect(subject.processor_chain).to eq search_builder_class.default_processor_chain
29
29
  end
30
+
31
+ it "appends the :add_missing_field_query processor" do
32
+ expect(subject.processor_chain).to include(:add_missing_field_query)
33
+ end
30
34
  end
31
35
 
32
36
  context 'with merged parameters from the defaults + the search field' do
@@ -822,4 +826,13 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
822
826
  expect(subject.to_hash).to include q: '{!lucene}id:(1 OR 2 OR 3)'
823
827
  end
824
828
  end
829
+
830
+ describe "#add_missing_field_query" do
831
+ it "precesses facet.missing query" do
832
+ subject.with("f" => { "-hello:" => [""] })
833
+ solr_params = { "facet.missing" => true, fq: [] }
834
+
835
+ expect(subject.add_missing_field_query(solr_params)).to eq(["-hello:[* TO *]"])
836
+ end
837
+ end
825
838
  end
data/spec/spec_helper.rb CHANGED
@@ -19,18 +19,24 @@ EngineCart.load_application!
19
19
  require 'rspec/rails'
20
20
  require 'rspec/its'
21
21
  require 'rspec/collection_matchers'
22
- require 'capybara/rspec'
23
- require 'capybara/apparition'
22
+ require 'capybara/rails'
23
+ require 'webdrivers'
24
+ require 'selenium-webdriver'
24
25
  require 'equivalent-xml'
26
+ require 'axe-rspec'
25
27
 
26
- Capybara.javascript_driver = :apparition
27
- Capybara.disable_animation = true
28
- # Capybara.enable_aria_label = true
28
+ Capybara.javascript_driver = :headless_chrome
29
29
 
30
- # Uncomment for a headed browser
31
- # Capybara.register_driver :apparition do |app|
32
- # Capybara::Apparition::Driver.new(app, headless: false)
33
- # end
30
+ Capybara.register_driver :headless_chrome do |app|
31
+ Capybara::Selenium::Driver.load_selenium
32
+ browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
33
+ opts.args << '--headless'
34
+ opts.args << '--disable-gpu'
35
+ opts.args << '--no-sandbox'
36
+ opts.args << '--window-size=1280,1696'
37
+ end
38
+ Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
39
+ end
34
40
 
35
41
  # Requires supporting ruby files with custom matchers and macros, etc,
36
42
  # in spec/support/ and its subdirectories.
@@ -17,9 +17,6 @@ class TestAppGenerator < Rails::Generators::Base
17
17
  options = '--devise'
18
18
  if ENV['BLACKLIGHT_API_TEST']
19
19
  options += ' --skip-assets'
20
- inject_into_class 'app/controllers/application_controller.rb', 'ApplicationController' do
21
- " include ActionController::MimeResponds\n" # see https://github.com/projectblacklight/blacklight/issues/1894
22
- end
23
20
  end
24
21
 
25
22
  generate 'blacklight:install', options
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blacklight
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.19.2
4
+ version: 7.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Rochkind
@@ -14,10 +14,10 @@ authors:
14
14
  - Dan Funk
15
15
  - Naomi Dushay
16
16
  - Justin Coyne
17
- autorequire:
17
+ autorequire:
18
18
  bindir: exe
19
19
  cert_chain: []
20
- date: 2021-05-25 00:00:00.000000000 Z
20
+ date: 2021-11-01 00:00:00.000000000 Z
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
23
  name: rails
@@ -127,16 +127,16 @@ dependencies:
127
127
  name: view_component
128
128
  requirement: !ruby/object:Gem::Requirement
129
129
  requirements:
130
- - - ">="
130
+ - - "~>"
131
131
  - !ruby/object:Gem::Version
132
- version: 2.28.0
132
+ version: 2.42.0
133
133
  type: :runtime
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
- - - ">="
137
+ - - "~>"
138
138
  - !ruby/object:Gem::Version
139
- version: 2.28.0
139
+ version: 2.42.0
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: rsolr
142
142
  requirement: !ruby/object:Gem::Requirement
@@ -199,6 +199,20 @@ dependencies:
199
199
  - - ">="
200
200
  - !ruby/object:Gem::Version
201
201
  version: '1.0'
202
+ - !ruby/object:Gem::Dependency
203
+ name: axe-core-rspec
204
+ requirement: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ type: :development
210
+ prerelease: false
211
+ version_requirements: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
202
216
  - !ruby/object:Gem::Dependency
203
217
  name: capybara
204
218
  requirement: !ruby/object:Gem::Requirement
@@ -214,7 +228,21 @@ dependencies:
214
228
  - !ruby/object:Gem::Version
215
229
  version: '3'
216
230
  - !ruby/object:Gem::Dependency
217
- name: apparition
231
+ name: webdrivers
232
+ requirement: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ type: :development
238
+ prerelease: false
239
+ version_requirements: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ - !ruby/object:Gem::Dependency
245
+ name: selenium-webdriver
218
246
  requirement: !ruby/object:Gem::Requirement
219
247
  requirements:
220
248
  - - ">="
@@ -762,8 +790,10 @@ files:
762
790
  - spec/features/advanced_search_spec.rb
763
791
  - spec/features/alternate_controller_spec.rb
764
792
  - spec/features/autocomplete_spec.rb
793
+ - spec/features/axe_spec.rb
765
794
  - spec/features/bookmarks_spec.rb
766
795
  - spec/features/did_you_mean_spec.rb
796
+ - spec/features/facet_missing_spec.rb
767
797
  - spec/features/facets_spec.rb
768
798
  - spec/features/record_view_spec.rb
769
799
  - spec/features/search_context_spec.rb
@@ -888,7 +918,7 @@ homepage: http://projectblacklight.org/
888
918
  licenses:
889
919
  - Apache 2.0
890
920
  metadata: {}
891
- post_install_message:
921
+ post_install_message:
892
922
  rdoc_options: []
893
923
  require_paths:
894
924
  - lib
@@ -903,8 +933,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
903
933
  - !ruby/object:Gem::Version
904
934
  version: '0'
905
935
  requirements: []
906
- rubygems_version: 3.2.3
907
- signing_key:
936
+ rubygems_version: 3.1.2
937
+ signing_key:
908
938
  specification_version: 4
909
939
  summary: Blacklight provides a discovery interface for any Solr (http://lucene.apache.org/solr)
910
940
  index.
@@ -934,8 +964,10 @@ test_files:
934
964
  - spec/features/advanced_search_spec.rb
935
965
  - spec/features/alternate_controller_spec.rb
936
966
  - spec/features/autocomplete_spec.rb
967
+ - spec/features/axe_spec.rb
937
968
  - spec/features/bookmarks_spec.rb
938
969
  - spec/features/did_you_mean_spec.rb
970
+ - spec/features/facet_missing_spec.rb
939
971
  - spec/features/facets_spec.rb
940
972
  - spec/features/record_view_spec.rb
941
973
  - spec/features/search_context_spec.rb