blacklight 6.2.0 → 6.3.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.solr_wrapper +5 -0
  4. data/.travis.yml +3 -5
  5. data/Gemfile +3 -3
  6. data/VERSION +1 -1
  7. data/app/assets/javascripts/blacklight/core.js +19 -5
  8. data/app/helpers/blacklight/blacklight_helper_behavior.rb +48 -20
  9. data/app/helpers/blacklight/configuration_helper_behavior.rb +1 -1
  10. data/app/helpers/blacklight/url_helper_behavior.rb +2 -1
  11. data/app/presenters/blacklight/document_presenter.rb +58 -150
  12. data/app/presenters/blacklight/field_presenter.rb +31 -0
  13. data/app/presenters/blacklight/index_presenter.rb +69 -0
  14. data/app/presenters/blacklight/link_alternate_presenter.rb +29 -0
  15. data/app/presenters/blacklight/rendering/abstract_step.rb +24 -0
  16. data/app/presenters/blacklight/rendering/helper_method.rb +23 -0
  17. data/app/presenters/blacklight/rendering/join.rb +16 -0
  18. data/app/presenters/blacklight/rendering/link_to_facet.rb +35 -0
  19. data/app/presenters/blacklight/rendering/microdata.rb +17 -0
  20. data/app/presenters/blacklight/rendering/pipeline.rb +32 -0
  21. data/app/presenters/blacklight/rendering/terminator.rb +9 -0
  22. data/app/presenters/blacklight/show_presenter.rb +93 -0
  23. data/app/services/blacklight/field_retriever.rb +58 -0
  24. data/app/views/catalog/_document_default.atom.builder +2 -3
  25. data/app/views/catalog/_document_default.rss.builder +2 -2
  26. data/app/views/kaminari/blacklight/_next_page.html.erb +9 -3
  27. data/app/views/kaminari/blacklight/_prev_page.html.erb +10 -3
  28. data/lib/blacklight/configuration.rb +15 -3
  29. data/lib/blacklight/configuration/null_field.rb +13 -0
  30. data/lib/blacklight/configuration/view_config.rb +6 -0
  31. data/lib/generators/blacklight/templates/catalog_controller.rb +1 -1
  32. data/lib/generators/blacklight/templates/config/blacklight.yml +1 -1
  33. data/lib/generators/blacklight/templates/config/jetty.yml +1 -1
  34. data/spec/controllers/alternate_controller_spec.rb +2 -2
  35. data/spec/controllers/application_controller_spec.rb +1 -1
  36. data/spec/controllers/blacklight/facet_spec.rb +3 -3
  37. data/spec/controllers/blacklight/search_fields_spec.rb +7 -7
  38. data/spec/controllers/blacklight/search_helper_spec.rb +44 -44
  39. data/spec/controllers/blacklight/suggest_search_spec.rb +1 -1
  40. data/spec/controllers/bookmarks_controller_spec.rb +6 -6
  41. data/spec/controllers/catalog_controller_spec.rb +125 -125
  42. data/spec/controllers/saved_searches_controller_spec.rb +4 -9
  43. data/spec/controllers/search_history_controller_spec.rb +3 -6
  44. data/spec/controllers/suggest_controller_spec.rb +2 -2
  45. data/spec/features/alternate_controller_spec.rb +3 -3
  46. data/spec/features/bookmarks_spec.rb +6 -6
  47. data/spec/features/did_you_mean_spec.rb +10 -10
  48. data/spec/features/facets_spec.rb +4 -4
  49. data/spec/features/record_view_spec.rb +4 -4
  50. data/spec/features/saved_searches_spec.rb +4 -4
  51. data/spec/features/search_context_spec.rb +4 -4
  52. data/spec/features/search_filters_spec.rb +10 -10
  53. data/spec/features/search_formats_spec.rb +2 -2
  54. data/spec/features/search_history_spec.rb +5 -5
  55. data/spec/features/search_pagination_spec.rb +4 -4
  56. data/spec/features/search_results_spec.rb +7 -7
  57. data/spec/features/search_sort_spec.rb +2 -2
  58. data/spec/features/search_spec.rb +6 -6
  59. data/spec/helpers/blacklight_helper_spec.rb +105 -65
  60. data/spec/helpers/catalog_helper_spec.rb +36 -36
  61. data/spec/helpers/configuration_helper_spec.rb +28 -28
  62. data/spec/helpers/facets_helper_spec.rb +39 -39
  63. data/spec/helpers/hash_as_hidden_fields_spec.rb +1 -1
  64. data/spec/helpers/render_constraints_helper_spec.rb +1 -1
  65. data/spec/helpers/search_history_constraints_helper_spec.rb +7 -7
  66. data/spec/helpers/url_helper_spec.rb +20 -17
  67. data/spec/lib/blacklight/search_state_spec.rb +2 -2
  68. data/spec/lib/blacklight/utils_spec.rb +15 -15
  69. data/spec/lib/blacklight_spec.rb +1 -1
  70. data/spec/lib/tasks/blacklight_task_spec.rb +1 -1
  71. data/spec/models/blacklight/configurable_spec.rb +3 -3
  72. data/spec/models/blacklight/configuration_spec.rb +52 -52
  73. data/spec/models/blacklight/document_spec.rb +10 -10
  74. data/spec/models/blacklight/facet_paginator_spec.rb +5 -5
  75. data/spec/models/blacklight/search_builder_spec.rb +34 -34
  76. data/spec/models/blacklight/user_spec.rb +4 -4
  77. data/spec/models/bookmark_spec.rb +5 -5
  78. data/spec/models/record_mailer_spec.rb +11 -11
  79. data/spec/models/search_spec.rb +1 -1
  80. data/spec/models/solr_document_spec.rb +4 -4
  81. data/spec/presenters/document_presenter_spec.rb +94 -50
  82. data/spec/presenters/index_presenter_spec.rb +147 -0
  83. data/spec/presenters/pipeline_spec.rb +28 -0
  84. data/spec/presenters/show_presenter_spec.rb +287 -0
  85. data/spec/routing/catalog_routing_spec.rb +11 -11
  86. data/spec/spec_helper.rb +10 -1
  87. data/spec/support/backport_test.rb +38 -0
  88. data/spec/test_app_templates/Gemfile.extra +2 -10
  89. data/spec/views/_user_util_links.html.erb_spec.rb +1 -1
  90. data/spec/views/catalog/_constraints.html.erb_spec.rb +3 -3
  91. data/spec/views/catalog/_constraints_element.html.erb_spec.rb +5 -5
  92. data/spec/views/catalog/_document.html.erb_spec.rb +2 -2
  93. data/spec/views/catalog/_document_list.html.erb_spec.rb +1 -1
  94. data/spec/views/catalog/_facet_layout.html.erb_spec.rb +4 -4
  95. data/spec/views/catalog/_facets.html.erb_spec.rb +4 -4
  96. data/spec/views/catalog/_index_default.erb_spec.rb +5 -4
  97. data/spec/views/catalog/_index_header_default.html.erb_spec.rb +4 -3
  98. data/spec/views/catalog/_paginate_compact.html.erb_spec.rb +3 -3
  99. data/spec/views/catalog/_search_header.erb_spec.rb +1 -1
  100. data/spec/views/catalog/_show_default.erb_spec.rb +6 -5
  101. data/spec/views/catalog/_show_sidebar.erb_spec.rb +2 -1
  102. data/spec/views/catalog/_show_tools.html.erb_spec.rb +8 -8
  103. data/spec/views/catalog/_sort_and_per_page.html.erb_spec.rb +2 -2
  104. data/spec/views/catalog/_thumbnail_default.erb_spec.rb +3 -2
  105. data/spec/views/catalog/_view_type_group.html.erb_spec.rb +3 -3
  106. data/spec/views/catalog/facet.html.erb_spec.rb +3 -3
  107. data/spec/views/catalog/index.atom.builder_spec.rb +15 -14
  108. data/spec/views/catalog/index.html.erb_spec.rb +3 -3
  109. data/spec/views/catalog/opensearch.xml.builder_spec.rb +1 -1
  110. data/spec/views/catalog/show.html.erb_spec.rb +5 -4
  111. data/tasks/blacklight.rake +1 -1
  112. metadata +25 -4
  113. data/config/jetty.yml +0 -13
@@ -0,0 +1,31 @@
1
+ module Blacklight
2
+ # Renders a field and handles link_to_search or helper_method if supplied
3
+ class FieldPresenter
4
+ def initialize(controller, document, field_config, options)
5
+ @controller = controller
6
+ @document = document
7
+ @field_config = field_config
8
+ @options = options
9
+ end
10
+
11
+ attr_reader :controller, :document, :field_config, :options
12
+
13
+ def render
14
+ if options[:value]
15
+ # This prevents helper methods from drawing.
16
+ config = Configuration::NullField.new(field_config.to_h.except(:helper_method))
17
+ values = Array.wrap(options[:value])
18
+ else
19
+ config = field_config
20
+ values = retrieve_values
21
+ end
22
+ Rendering::Pipeline.render(values, config, document, controller, options)
23
+ end
24
+
25
+ private
26
+
27
+ def retrieve_values
28
+ FieldRetriever.new(document, field_config).fetch
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ module Blacklight
3
+ class IndexPresenter
4
+ # @param [SolrDocument] document
5
+ # @param [ActionController::Base] controller scope for linking and generating urls
6
+ # @param [Blacklight::Configuration] configuration
7
+ def initialize(document, controller, configuration = controller.blacklight_config)
8
+ @document = document
9
+ @configuration = configuration
10
+ @controller = controller
11
+ end
12
+
13
+ ##
14
+ # Render the document index heading
15
+ #
16
+ # @param [Symbol, Proc, String] field Render the given field or evaluate the proc or render the given string
17
+ # @param [Hash] opts
18
+ # TODO: the default field should be `document_show_link_field(doc)'
19
+ def label(field_or_string_or_proc, opts = {})
20
+ config = Configuration::NullField.new
21
+ value = case field_or_string_or_proc
22
+ when Symbol
23
+ config = field_config(field_or_string_or_proc)
24
+ @document[field_or_string_or_proc]
25
+ when Proc
26
+ field_or_string_or_proc.call(@document, opts)
27
+ when String
28
+ field_or_string_or_proc
29
+ end
30
+
31
+ value ||= @document.id
32
+ field_values(config, value: value)
33
+ end
34
+
35
+ ##
36
+ # Render the index field label for a document
37
+ #
38
+ # Allow an extention point where information in the document
39
+ # may drive the value of the field
40
+ # @param [String] field
41
+ # @param [Hash] opts
42
+ # @options opts [String] :value
43
+ def field_value field, options = {}
44
+ field_config = field_config(field)
45
+ field_values(field_config, options)
46
+ end
47
+
48
+ private
49
+
50
+ ##
51
+ # Get the value for a document's field, and prepare to render it.
52
+ # - highlight_field
53
+ # - accessor
54
+ # - solr field
55
+ #
56
+ # Rendering:
57
+ # - helper_method
58
+ # - link_to_search
59
+ # @param [Blacklight::Configuration::Field] solr field configuration
60
+ # @param [Hash] options additional options to pass to the rendering helpers
61
+ def field_values(field_config, options={})
62
+ FieldPresenter.new(@controller, @document, field_config, options).render
63
+ end
64
+
65
+ def field_config(field)
66
+ @configuration.index_fields.fetch(field) { Configuration::NullField.new(field) }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ module Blacklight
2
+ # Create <link rel="alternate"> links from a documents dynamically
3
+ class LinkAlternatePresenter
4
+ include ActionView::Helpers::OutputSafetyHelper
5
+ include ActionView::Helpers::TagHelper
6
+
7
+ def initialize(controller, document, options)
8
+ @controller = controller
9
+ @document = document
10
+ @options = { unique: false, exclude: [] }.merge(options)
11
+ end
12
+
13
+ attr_reader :controller, :document, :options
14
+
15
+ # Renders links to alternate representations
16
+ # provided by export formats. Returns empty string if no links available.
17
+ def render
18
+ seen = Set.new
19
+
20
+ safe_join(document.export_formats.map do |format, spec|
21
+ next if options[:exclude].include?(format) || (options[:unique] && seen.include?(spec[:content_type]))
22
+
23
+ seen.add(spec[:content_type])
24
+
25
+ tag(:link, rel: "alternate", title: format, type: spec[:content_type], href: controller.polymorphic_url(document, format: format))
26
+ end.compact, "\n")
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module Blacklight
2
+ module Rendering
3
+ class AbstractStep
4
+ def initialize(values, config, document, context, options, stack)
5
+ @values = values
6
+ @config = config
7
+ @document = document
8
+ @context = context
9
+ @options = options
10
+ @stack = stack
11
+ end
12
+
13
+ attr_reader :values, :config, :document, :context, :options, :stack
14
+
15
+ protected
16
+
17
+ def next_step(output_values)
18
+ first, *rest = *stack
19
+ first.new(output_values, config, document, context, options, rest).render
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,23 @@
1
+ module Blacklight
2
+ module Rendering
3
+ class HelperMethod < AbstractStep
4
+ def render
5
+ return next_step(values) unless config.helper_method
6
+ return render_helper # short circut the rest of the steps
7
+ end
8
+
9
+ private
10
+
11
+ def render_helper
12
+ context.send(config.helper_method,
13
+ options.merge(document: document,
14
+ field: config.field,
15
+ config: config,
16
+ value: values))
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+
23
+
@@ -0,0 +1,16 @@
1
+ module Blacklight
2
+ module Rendering
3
+ class Join < AbstractStep
4
+ def render
5
+ options = config.separator_options || {}
6
+ next_step(values.map { |x| html_escape(x) }.to_sentence(options).html_safe)
7
+ end
8
+
9
+ private
10
+
11
+ def html_escape(*args)
12
+ ERB::Util.html_escape(*args)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ module Blacklight
2
+ module Rendering
3
+ class LinkToFacet < AbstractStep
4
+ def render
5
+ # TODO: We should rename the config variable, because it creates a link to a facet.
6
+ return next_step(values) unless config.link_to_search
7
+ next_step(render_link)
8
+ end
9
+
10
+ private
11
+
12
+ # This allows the link to wrap an itemprop
13
+ def render_link
14
+ values.map { |v| link(link_field, v) }
15
+ end
16
+
17
+ def link_field
18
+ return config.key if config.link_to_search === true
19
+ config.link_to_search
20
+ end
21
+
22
+ def link(field, v)
23
+ context.link_to v, search_path(field, v)
24
+ end
25
+
26
+ def search_path(field, v)
27
+ context.search_action_path(facet_params(field, v))
28
+ end
29
+
30
+ def facet_params(field, v)
31
+ context.search_state.reset.add_facet_params(field, v)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ module Blacklight
2
+ module Rendering
3
+ class Microdata < AbstractStep
4
+ include ActionView::Helpers::TagHelper
5
+ def render
6
+ return next_step(values) unless config.itemprop
7
+ next_step(values.map { |x| itemprop(x, config.itemprop) })
8
+ end
9
+
10
+ private
11
+
12
+ def itemprop(val, itemprop)
13
+ content_tag :span, val, itemprop: itemprop
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ module Blacklight
2
+ module Rendering
3
+ # The field rendering pipeline
4
+ class Pipeline
5
+ def initialize(values, config, document, context, options)
6
+ @values = values
7
+ @config = config
8
+ @document = document
9
+ @context = context
10
+ @options = options
11
+ end
12
+
13
+ attr_reader :values, :config, :document, :context, :options
14
+
15
+ def self.render(values, config, document, context, options)
16
+ new(values, config, document, context, options).render
17
+ end
18
+
19
+ def render
20
+ first, *rest = *stack
21
+ first.new(values, config, document, context, options, rest).render
22
+ end
23
+
24
+ protected
25
+
26
+ # Ordered list of operations, Terminator must be at the end.
27
+ def stack
28
+ [HelperMethod, LinkToFacet, Microdata, Join, Terminator]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ module Blacklight
2
+ module Rendering
3
+ class Terminator < AbstractStep
4
+ def render
5
+ values
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+ module Blacklight
3
+ class ShowPresenter
4
+ # @param [SolrDocument] document
5
+ # @param [ActionController::Base] controller scope for linking and generating urls
6
+ # @param [Blacklight::Configuration] configuration
7
+ def initialize(document, controller, configuration = controller.blacklight_config)
8
+ @document = document
9
+ @configuration = configuration
10
+ @controller = controller
11
+ end
12
+
13
+ ##
14
+ # Create <link rel="alternate"> links from a documents dynamically
15
+ # provided export formats. Returns empty string if no links available.
16
+ #
17
+ # @params [Hash] options
18
+ # @option options [Boolean] :unique ensures only one link is output for every
19
+ # content type, e.g. as required by atom
20
+ # @option options [Array<String>] :exclude array of format shortnames to not include in the output
21
+ # @deprecated moved to ShowPresenter#link_rel_alternates
22
+ def link_rel_alternates(options = {})
23
+ LinkAlternatePresenter.new(@controller, @document, options).render
24
+ end
25
+
26
+ ##
27
+ # Get the document's "title" to display in the <title> element.
28
+ # (by default, use the #document_heading)
29
+ #
30
+ # @see #document_heading
31
+ # @return [String]
32
+ def html_title
33
+ if view_config.html_title_field
34
+ fields = Array.wrap(view_config.html_title_field)
35
+ f = fields.detect { |field| @document.has? field }
36
+ f ||= 'id'
37
+ field_values(field_config(f))
38
+ else
39
+ heading
40
+ end
41
+ end
42
+
43
+ ##
44
+ # Get the value of the document's "title" field, or a placeholder
45
+ # value (if empty)
46
+ #
47
+ # @param [SolrDocument] document
48
+ # @return [String]
49
+ def heading
50
+ fields = Array.wrap(view_config.title_field)
51
+ f = fields.detect { |field| @document.has? field }
52
+ f ||= @configuration.document_model.unique_key
53
+ field_values(field_config(f), value: @document[f])
54
+ end
55
+
56
+ ##
57
+ # Render the show field value for a document
58
+ #
59
+ # Allow an extention point where information in the document
60
+ # may drive the value of the field
61
+ # @param [String] field
62
+ # @param [Hash] options
63
+ # @options opts [String] :value
64
+ def field_value field, options={}
65
+ field_values(field_config(field), options)
66
+ end
67
+
68
+ private
69
+
70
+ ##
71
+ # Get the value for a document's field, and prepare to render it.
72
+ # - highlight_field
73
+ # - accessor
74
+ # - solr field
75
+ #
76
+ # Rendering:
77
+ # - helper_method
78
+ # - link_to_search
79
+ # @param [Blacklight::Configuration::Field] solr field configuration
80
+ # @param [Hash] options additional options to pass to the rendering helpers
81
+ def field_values(field_config, options={})
82
+ FieldPresenter.new(@controller, @document, field_config, options).render
83
+ end
84
+
85
+ def view_config
86
+ @configuration.view_config(:show)
87
+ end
88
+
89
+ def field_config(field)
90
+ @configuration.show_fields.fetch(field) { Configuration::NullField.new(field) }
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,58 @@
1
+ module Blacklight
2
+ class FieldRetriever
3
+ # @param [SolrDocument] document
4
+ # @param [Blacklight::Configuration::Field] field_config solr field configuration
5
+ def initialize(document, field_config)
6
+ @document = document
7
+ @field_config = field_config
8
+ end
9
+
10
+ attr_reader :document, :field_config
11
+ delegate :field, to: :field_config
12
+
13
+ # @return [Array]
14
+ def fetch
15
+ Array.wrap(
16
+ case
17
+ when field_config.highlight
18
+ retrieve_highlight
19
+ when field_config.accessor
20
+ retieve_using_accessor
21
+ when field_config
22
+ retrieve_simple
23
+ end
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def retrieve_simple
30
+ # regular document field
31
+ if field_config.default and field_config.default.is_a? Proc
32
+ document.fetch(field_config.field, &field_config.default)
33
+ else
34
+ document.fetch(field_config.field, field_config.default)
35
+ end
36
+ end
37
+
38
+ def retieve_using_accessor
39
+ # implicit method call
40
+ if field_config.accessor === true
41
+ document.send(field)
42
+ # arity-1 method call (include the field name in the call)
43
+ elsif !field_config.accessor.is_a?(Array) && document.method(field_config.accessor).arity != 0
44
+ document.send(field_config.accessor, field)
45
+ # chained method calls
46
+ else
47
+ Array(field_config.accessor).inject(document) do |result, method|
48
+ result.send(method)
49
+ end
50
+ end
51
+ end
52
+
53
+ def retrieve_highlight
54
+ # retrieve the document value from the highlighting response
55
+ document.highlight_field(field_config.field).map(&:html_safe) if document.has_highlight_field? field_config.field
56
+ end
57
+ end
58
+ end