hyrax 2.8.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +1 -1
  4. data/app/assets/javascripts/hyrax/autocomplete.es6 +29 -0
  5. data/app/assets/javascripts/hyrax/editor.es6 +9 -10
  6. data/app/controllers/concerns/hyrax/works_controller_behavior.rb +18 -7
  7. data/app/helpers/hyrax/hyrax_helper_behavior.rb +1 -0
  8. data/app/helpers/hyrax/work_form_helper.rb +48 -0
  9. data/app/jobs/iiif_manifest_cache_prewarm_job.rb +16 -0
  10. data/app/models/concerns/hyrax/solr_document/metadata.rb +1 -0
  11. data/app/models/concerns/hyrax/solr_document/ordered_members.rb +46 -0
  12. data/app/models/concerns/hyrax/solr_document_behavior.rb +10 -0
  13. data/app/presenters/hyrax/displays_image.rb +25 -21
  14. data/app/presenters/hyrax/iiif_manifest_presenter.rb +232 -0
  15. data/app/presenters/hyrax/member_presenter_factory.rb +1 -7
  16. data/app/services/hyrax/caching_iiif_manifest_builder.rb +53 -0
  17. data/app/services/hyrax/identifier/builder.rb +45 -0
  18. data/app/services/hyrax/identifier/dispatcher.rb +61 -0
  19. data/app/services/hyrax/identifier/registrar.rb +41 -0
  20. data/app/services/hyrax/manifest_builder_service.rb +88 -0
  21. data/app/services/hyrax/versioning_service.rb +9 -0
  22. data/app/views/hyrax/base/_form.html.erb +1 -1
  23. data/app/views/hyrax/base/_form_progress.html.erb +4 -0
  24. data/app/views/hyrax/base/_guts4form.html.erb +7 -1
  25. data/app/views/hyrax/batch_uploads/_form.html.erb +1 -1
  26. data/app/views/hyrax/dashboard/_sidebar.html.erb +1 -1
  27. data/config/features.rb +4 -0
  28. data/hyrax.gemspec +1 -0
  29. data/lib/generators/hyrax/templates/config/initializers/hyrax.rb +5 -0
  30. data/lib/hyrax.rb +1 -0
  31. data/lib/hyrax/configuration.rb +15 -0
  32. data/lib/hyrax/engine.rb +1 -0
  33. data/lib/hyrax/specs/shared_specs.rb +1 -0
  34. data/lib/hyrax/specs/shared_specs/identifiers.rb +27 -0
  35. data/lib/hyrax/version.rb +1 -1
  36. data/template.rb +1 -1
  37. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 140c7caa34751f7a525eda269abcee6331a9e7d200863e2cbee29d315966de65
4
- data.tar.gz: 7085c394edd0aa1e90baef7d13f90245d8fb0fb65659e54549cedda0487445e9
3
+ metadata.gz: c050894909dcdbe63b34640d24f9f665e3818850cf2f4c5a0d92af23ae51cf62
4
+ data.tar.gz: 42a5a185ee2d26c9ab54d544a8261fc2bd24c01b99903092e5d54a9c5dcee054
5
5
  SHA512:
6
- metadata.gz: 691b84f363281552cbd84f85b767bb5b460ba749e0449aed0498594143767a6cd4dae187a5dd9c88bf3b7bd06a568375da70367b3b8faa674a59f39deaf4df06
7
- data.tar.gz: e19605afaa51b569ea79e7e99d65fd0576b172cd6dfeb38f815c4dd3a7ac18678bb6b3ff4ac4a5af2b6358bf55c459baba0c4a98b393d20f7d64388f576c894f
6
+ metadata.gz: a0fab1e2979598561c1f4afd61e2434c0af3529005c7f6394f9605d5c30e4e95df45a7c23cf665ac6ee647360a27ef286870a7456b8b57a0ec17e905f8d474af
7
+ data.tar.gz: 55d6ffdace586396f41b327dceca10da11eeedbcee3d0be0abeafa3635574425e79fa6b8dd0ba831870b9f6d464e0067da889d46e5318051d63f00a502cff892
data/.gitignore CHANGED
@@ -78,3 +78,4 @@ _yardoc
78
78
  lib/bundler/man
79
79
  spec/reports
80
80
  /spec/examples.txt
81
+ node_modules
data/README.md CHANGED
@@ -158,7 +158,7 @@ NOTE: The steps need to be done in order to create a new Hyrax based app.
158
158
  Generate a new Rails application using the template.
159
159
 
160
160
  ```
161
- rails _5.2.4.3_ new my_app -m https://raw.githubusercontent.com/samvera/hyrax/v2.8.0/template.rb
161
+ rails _5.2.4.3_ new my_app -m https://raw.githubusercontent.com/samvera/hyrax/v2.9.0/template.rb
162
162
  ```
163
163
 
164
164
  Generating a new Rails application using Hyrax's template above takes cares of a number of steps for you, including:
@@ -10,6 +10,34 @@ export default class Autocomplete {
10
10
  * @param {string} url - The url for the autocompete search endpoint
11
11
  */
12
12
  setup (element, fieldName, url) {
13
+ if(element.data('autocomplete-type') && element.data('autocomplete-type').length > 0) {
14
+ this.byDataAttribute(element, url)
15
+ } else {
16
+ this.byFieldName(element, fieldName, url)
17
+ }
18
+ }
19
+
20
+ byDataAttribute(element, url) {
21
+ let type = element.data('autocomplete-type')
22
+ let exlude = element.data('exclude-work')
23
+ if(type === 'resource' && exclude.length > 0) {
24
+ new Resource(
25
+ element,
26
+ url,
27
+ { excluding: exclude }
28
+ )
29
+ } else if(type === 'resource' ) {
30
+ new Resource(
31
+ element,
32
+ url)
33
+ } else if(type === 'linked') {
34
+ new LinkedData(element, url)
35
+ } else {
36
+ new Default(element, url)
37
+ }
38
+ }
39
+
40
+ byFieldName(element, fieldName, url) {
13
41
  switch (fieldName) {
14
42
  case 'work':
15
43
  new Resource(
@@ -30,4 +58,5 @@ export default class Autocomplete {
30
58
  break
31
59
  }
32
60
  }
61
+
33
62
  }
@@ -53,17 +53,16 @@ export default class {
53
53
  $('[data-autocomplete]').each((function() {
54
54
  var elem = $(this)
55
55
  autocomplete.setup(elem, elem.data('autocomplete'), elem.data('autocompleteUrl'))
56
+ elem.parents('.multi_value.form-group').manage_fields({
57
+ add: function(e, element) {
58
+ var elem = $(element)
59
+ // Don't mark an added element as readonly even if previous element was
60
+ // Enable before initializing, as otherwise LinkedData fields remain disabled
61
+ elem.attr('readonly', false)
62
+ autocomplete.setup(elem, elem.data('autocomplete'), elem.data('autocompleteUrl'))
63
+ }
64
+ })
56
65
  }))
57
-
58
- $('.multi_value.form-group').manage_fields({
59
- add: function(e, element) {
60
- var elem = $(element)
61
- // Don't mark an added element as readonly even if previous element was
62
- // Enable before initializing, as otherwise LinkedData fields remain disabled
63
- elem.attr('readonly', false)
64
- autocomplete.setup(elem, elem.data('autocomplete'), elem.data('autocompleteUrl'))
65
- }
66
- })
67
66
  }
68
67
 
69
68
  // initialize any controlled vocabulary widgets
@@ -10,10 +10,11 @@ module Hyrax
10
10
  with_themed_layout :decide_layout
11
11
  copy_blacklight_config_from(::CatalogController)
12
12
 
13
- class_attribute :_curation_concern_type, :show_presenter, :work_form_service, :search_builder_class
13
+ class_attribute :_curation_concern_type, :show_presenter, :work_form_service, :search_builder_class, :iiif_manifest_builder
14
14
  self.show_presenter = Hyrax::WorkShowPresenter
15
15
  self.work_form_service = Hyrax::WorkFormService
16
16
  self.search_builder_class = WorkSearchBuilder
17
+ self.iiif_manifest_builder = (Flipflop.cache_work_iiif_manifest? ? Hyrax::CachingIiifManifestBuilder.new : Hyrax::ManifestBuilderService.new)
17
18
  attr_accessor :curation_concern
18
19
  helper_method :curation_concern, :contextual_path
19
20
 
@@ -127,14 +128,28 @@ module Hyrax
127
128
 
128
129
  def manifest
129
130
  headers['Access-Control-Allow-Origin'] = '*'
131
+
132
+ json = iiif_manifest_builder.manifest_for(presenter: iiif_manifest_presenter)
133
+
130
134
  respond_to do |wants|
131
- wants.json { render json: manifest_builder.to_h }
132
- wants.html { render json: manifest_builder.to_h }
135
+ wants.json { render json: json }
136
+ wants.html { render json: json }
133
137
  end
134
138
  end
135
139
 
136
140
  private
137
141
 
142
+ def iiif_manifest_builder
143
+ self.class.iiif_manifest_builder
144
+ end
145
+
146
+ def iiif_manifest_presenter
147
+ IiifManifestPresenter.new(curation_concern_from_search_results).tap do |p|
148
+ p.hostname = request.hostname
149
+ p.ability = current_ability
150
+ end
151
+ end
152
+
138
153
  def user_collections
139
154
  collections_service.search_results(:deposit)
140
155
  end
@@ -157,10 +172,6 @@ module Hyrax
157
172
  @form = work_form_service.build(curation_concern, current_ability, self)
158
173
  end
159
174
 
160
- def manifest_builder
161
- ::IIIFManifest::ManifestFactory.new(presenter)
162
- end
163
-
164
175
  def actor
165
176
  @actor ||= Hyrax::CurationConcern.actor
166
177
  end
@@ -12,6 +12,7 @@ module Hyrax
12
12
  include Hyrax::ChartsHelper
13
13
  include Hyrax::DashboardHelperBehavior
14
14
  include Hyrax::IiifHelper
15
+ include Hyrax::WorkFormHelper
15
16
 
16
17
  # Which translations are available for the user to select
17
18
  # @return [Hash<String,String>] locale abbreviations as keys and flags as values
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module WorkFormHelper
4
+ ##
5
+ # This helper allows downstream applications and engines to add/remove/reorder the tabs to be
6
+ # rendered on the work form.
7
+ #
8
+ # @example with additional tabs
9
+ # Override this helper and ensure that it loads after Hyrax's helpers.
10
+ # module WorksHelper
11
+ # def form_tabs_for(form:)
12
+ # super + ["my_new_tab"]
13
+ # end
14
+ # end
15
+ # Add the new section partial at app/views/hyrax/base/_form_my_new_tab.html.erb
16
+ #
17
+ # @todo The share tab isn't included because it wasn't in guts4form. guts4form should be
18
+ # cleaned up so share is treated the same as other tabs and can be included below.
19
+ # @param form [Hyrax::Forms::WorkForm]
20
+ # @return [Array<String>] the list of names of tabs to be rendered in the form
21
+ def form_tabs_for(form:)
22
+ if form.instance_of? Hyrax::Forms::BatchUploadForm
23
+ %w[files metadata relationships]
24
+ else
25
+ %w[metadata files relationships]
26
+ end
27
+ end
28
+
29
+ ##
30
+ # This helper allows downstream applications and engines to add additional sections to be
31
+ # rendered after the visibility section in the Save Work panel on the work form.
32
+ #
33
+ # @example with additional sections
34
+ # Override this helper and ensure that it loads after Hyrax's helpers.
35
+ # module WorksHelper
36
+ # def form_progress_sections_for(*)
37
+ # super + ["my_new_section"]
38
+ # end
39
+ # end
40
+ # Add the new section partial at app/views/hyrax/base/_form_progress_my_new_section.html.erb
41
+ #
42
+ # @param form [Hyrax::Forms::WorkForm]
43
+ # @return [Array<String>] the list of names of sections to be rendered in the form_progress panel
44
+ def form_progress_sections_for(*)
45
+ []
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IiifManifestCachePrewarmJob < Hyrax::ApplicationJob
4
+ ##
5
+ # @param work [ActiveFedora::Base]
6
+ def perform(work)
7
+ presenter = Hyrax::IiifManifestPresenter.new(work)
8
+ manifest_builder.manifest_for(presenter: presenter)
9
+ end
10
+
11
+ private
12
+
13
+ def manifest_builder
14
+ Hyrax::CachingIiifManifestBuilder.new
15
+ end
16
+ end
@@ -54,6 +54,7 @@ module Hyrax
54
54
  attribute :read_groups, Solr::Array, ::Ability.read_group_field
55
55
  attribute :collection_ids, Solr::Array, 'collection_ids_tesim'
56
56
  attribute :admin_set, Solr::Array, solr_name('admin_set')
57
+ attribute :member_ids, Solr::Array, "member_ids_ssim"
57
58
  attribute :member_of_collection_ids, Solr::Array, solr_name('member_of_collection_ids', :symbol)
58
59
  attribute :description, Solr::Array, solr_name('description')
59
60
  attribute :title, Solr::Array, solr_name('title')
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module SolrDocument
4
+ ##
5
+ # Decorates an object responding to `#id` with an `#ordered_member_ids` method.
6
+ #
7
+ # @note this decorator is intended for use with data representations other
8
+ # than the core model objects, as an alternative to a direct query of the
9
+ # canonical database. for example, it can be used with `SolrDocument` to
10
+ # quickly retrieve member order in a way that is compatible with the
11
+ # fast access required in Blacklight's search contexts.
12
+ #
13
+ # @example
14
+ # base_document = SolrDocument.new(my_work.to_solr)
15
+ # solr_document = Hyrax::SolrDocument::OrderedMembers.decorate(base_document)
16
+ #
17
+ # solr_document.ordered_member_ids # => ['abc', '123']
18
+ #
19
+ class OrderedMembers < Draper::Decorator
20
+ delegate_all
21
+
22
+ ##
23
+ # @note the purpose of this method is to provide fast access to member
24
+ # order. currently this is achieved by accessing indexed list proxies
25
+ # from Solr. however, this strategy may change in the future.
26
+ #
27
+ # @return [Enumerable<String>] ids in the order of their membership,
28
+ # only includes ids of ordered members.
29
+ def ordered_member_ids
30
+ return [] if id.blank?
31
+ @ordered_member_ids ||= query_for_ordered_ids
32
+ end
33
+
34
+ private
35
+
36
+ def query_for_ordered_ids(limit: 10_000,
37
+ proxy_field: 'proxy_in_ssi',
38
+ target_field: 'ordered_targets_ssim')
39
+ ActiveFedora::SolrService
40
+ .query("#{proxy_field}:#{id}", rows: limit, fl: target_field)
41
+ .flat_map { |x| x.fetch(target_field, nil) }
42
+ .compact
43
+ end
44
+ end
45
+ end
46
+ end
@@ -61,10 +61,20 @@ module Hyrax
61
61
  @model ||= ModelWrapper.new(hydra_model, id)
62
62
  end
63
63
 
64
+ ##
65
+ # @return [Boolean]
64
66
  def collection?
65
67
  hydra_model == ::Collection
66
68
  end
67
69
 
70
+ ##
71
+ # @return [Boolean]
72
+ def file_set?
73
+ hydra_model == ::FileSet
74
+ end
75
+
76
+ ##
77
+ # @return [Boolean]
68
78
  def admin_set?
69
79
  hydra_model == ::AdminSet
70
80
  end
@@ -11,20 +11,11 @@ module Hyrax
11
11
  # @return [IIIFManifest::DisplayImage] the display image required by the manifest builder.
12
12
  def display_image
13
13
  return nil unless solr_document.image? && current_ability.can?(:read, solr_document)
14
-
15
- latest_file_id = lookup_original_file_id
16
-
17
14
  return nil unless latest_file_id
18
15
 
19
- url = Hyrax.config.iiif_image_url_builder.call(
20
- latest_file_id,
21
- request.base_url,
22
- Hyrax.config.iiif_image_size_default
23
- )
24
-
25
16
  # @see https://github.com/samvera-labs/iiif_manifest
26
- IIIFManifest::DisplayImage.new(url,
27
- format: image_format([]),
17
+ IIIFManifest::DisplayImage.new(display_image_url(request.base_url),
18
+ format: image_format(alpha_channels),
28
19
  width: width,
29
20
  height: height,
30
21
  iiif_endpoint: iiif_endpoint(latest_file_id))
@@ -32,16 +23,24 @@ module Hyrax
32
23
 
33
24
  private
34
25
 
35
- def iiif_endpoint(file_id)
26
+ def display_image_url(base_url)
27
+ Hyrax.config.iiif_image_url_builder.call(
28
+ latest_file_id,
29
+ base_url,
30
+ Hyrax.config.iiif_image_size_default
31
+ )
32
+ end
33
+
34
+ def iiif_endpoint(file_id, base_url: request.base_url)
36
35
  return unless Hyrax.config.iiif_image_server?
37
36
  IIIFManifest::IIIFEndpoint.new(
38
- Hyrax.config.iiif_info_url_builder.call(file_id, request.base_url),
37
+ Hyrax.config.iiif_info_url_builder.call(file_id, base_url),
39
38
  profile: Hyrax.config.iiif_image_compliance_level_uri
40
39
  )
41
40
  end
42
41
 
43
42
  def image_format(channels)
44
- channels.find { |c| c.include?('rgba') }.nil? ? 'jpg' : 'png'
43
+ channels&.find { |c| c.include?('rgba') }.nil? ? 'jpg' : 'png'
45
44
  end
46
45
 
47
46
  def unindexed_current_file_version
@@ -49,13 +48,18 @@ module Hyrax
49
48
  ActiveFedora::File.uri_to_id(::FileSet.find(id).current_content_version_uri)
50
49
  end
51
50
 
52
- def lookup_original_file_id
53
- result = original_file_id
54
- if result.blank?
55
- Rails.logger.warn "original_file_id for #{id} not found, falling back to Fedora."
56
- result = ActiveFedora::File.uri_to_id(::FileSet.find(id).current_content_version_uri)
57
- end
58
- result
51
+ def latest_file_id
52
+ @latest_file_id ||=
53
+ begin
54
+ result = original_file_id
55
+
56
+ if result.blank?
57
+ Rails.logger.warn "original_file_id for #{id} not found, falling back to Fedora."
58
+ result = Hyrax::VersioningService.versioned_file_id ::FileSet.find(id).original_file
59
+ end
60
+
61
+ result
62
+ end
59
63
  end
60
64
  end
61
65
  end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ ##
5
+ # This presenter wraps objects in the interface required by `IIIFManifiest`.
6
+ # It will accept either a Work-like resource or a SolrDocument.
7
+ #
8
+ # @example with a work
9
+ #
10
+ # monograph = Monograph.new
11
+ # presenter = IiifManifestPresenter.new(monograph)
12
+ # presenter.title # => []
13
+ #
14
+ # monograph.title = ['Comet in Moominland']
15
+ # presenter.title # => ['Comet in Moominland']
16
+ #
17
+ # @see https://www.rubydoc.info/gems/iiif_manifest
18
+ class IiifManifestPresenter < Draper::Decorator
19
+ delegate_all
20
+
21
+ ##
22
+ # @!attribute [w] ability
23
+ # @return [Ability]
24
+ # @!attribute [w] hostname
25
+ # @return [String]
26
+ attr_writer :ability, :hostname
27
+
28
+ class << self
29
+ ##
30
+ # @param [Hyrax::Resource, SolrDocument]
31
+ def for(model)
32
+ klass = model.file_set? ? DisplayImagePresenter : IiifManifestPresenter
33
+
34
+ klass.new(model)
35
+ end
36
+ end
37
+
38
+ ##
39
+ # @return [#can?]
40
+ def ability
41
+ @ability ||= NullAbility.new
42
+ end
43
+
44
+ ##
45
+ # @return [String]
46
+ def description
47
+ Array(super).first || ''
48
+ end
49
+
50
+ ##
51
+ # @return [Boolean]
52
+ def file_set?
53
+ model.try(:file_set?) || Array(model[:has_model_ssim]).include?('FileSet')
54
+ end
55
+
56
+ ##
57
+ # @return [Array<DisplayImagePresenter>]
58
+ def file_set_presenters
59
+ member_presenters.select(&:file_set?)
60
+ end
61
+
62
+ ##
63
+ # IIIF metadata for inclusion in the manifest
64
+ # Called by the `iiif_manifest` gem to add metadata
65
+ #
66
+ # @todo should this use the simple_form i18n keys?! maybe the manifest
67
+ # needs its own?
68
+ #
69
+ # @return [Array<Hash{String => String}>] array of metadata hashes
70
+ def manifest_metadata
71
+ metadata_fields.map do |field_name|
72
+ {
73
+ 'label' => I18n.t("simple_form.labels.defaults.#{field_name}"),
74
+ 'value' => Array(self[field_name]).map { |value| scrub(value.to_s) }
75
+ }
76
+ end
77
+ end
78
+
79
+ ##
80
+ # @return [String] the URL where the manifest can be found
81
+ def manifest_url
82
+ return '' if id.blank?
83
+
84
+ Rails.application.routes.url_helpers.polymorphic_url([:manifest, model], host: hostname)
85
+ end
86
+
87
+ ##
88
+ # @return [Array<#to_s>]
89
+ def member_ids
90
+ Hyrax::SolrDocument::OrderedMembers.decorate(model).ordered_member_ids
91
+ end
92
+
93
+ ##
94
+ # @note cache member presenters to avoid querying repeatedly; we expect this
95
+ # presenter to live only as long as the request.
96
+ #
97
+ # @note skips presenters for objects the current `@ability` cannot read.
98
+ # the default ability has all permissions.
99
+ #
100
+ # @return [Array<IiifManifestPresenter>]
101
+ def member_presenters
102
+ @member_presenters_cache ||= Factory.build_for(ids: member_ids, presenter_class: self.class).map do |presenter|
103
+ next unless ability.can?(:read, presenter.model)
104
+
105
+ presenter.hostname = hostname
106
+ presenter.ability = ability
107
+ presenter
108
+ end.compact
109
+ end
110
+
111
+ ##
112
+ # @return [Array<Hash{String => String}>]
113
+ def sequence_rendering
114
+ Array(try(:rendering_ids)).map do |file_set_id|
115
+ rendering = file_set_presenters.find { |p| p.id == file_set_id }
116
+ next unless rendering
117
+
118
+ { '@id' => Hyrax::Engine.routes.url_helpers.download_url(rendering.id, host: hostname),
119
+ 'format' => rendering.mime_type.present? ? rendering.mime_type : I18n.t("hyrax.manifest.unknown_mime_text"),
120
+ 'label' => I18n.t("hyrax.manifest.download_text") + (rendering.label || '') }
121
+ end.flatten
122
+ end
123
+
124
+ ##
125
+ # @return [Boolean]
126
+ def work?
127
+ object.try(:work?) || !file_set?
128
+ end
129
+
130
+ ##
131
+ # @return [Array<IiifManifestPresenter>]
132
+ def work_presenters
133
+ member_presenters.select(&:work?)
134
+ end
135
+
136
+ ##
137
+ # @note ideally, this value will be cheap to retrieve, and will reliably
138
+ # change any time the manifest JSON will change. the current implementation
139
+ # is more blunt than this, changing only when the work itself changes.
140
+ #
141
+ # @return [String] a string tag suitable for cache keys for this manifiest
142
+ def version
143
+ object.try(:modified_date)&.to_s || ''
144
+ end
145
+
146
+ ##
147
+ # An Ability-like object that gives `true` for all `can?` requests
148
+ class NullAbility
149
+ ##
150
+ # @return [Boolean] true
151
+ def can?(*)
152
+ true
153
+ end
154
+ end
155
+
156
+ class Factory < PresenterFactory
157
+ ##
158
+ # @return [Array]
159
+ def build
160
+ ids.map do |id|
161
+ solr_doc = load_docs.find { |doc| doc.id == id }
162
+ presenter_class.for(solr_doc) if solr_doc
163
+ end.compact
164
+ end
165
+
166
+ private
167
+
168
+ ##
169
+ # cache the docs in this method, rather than #build;
170
+ # this can probably be pushed up to the parent class
171
+ def load_docs
172
+ @cached_docs ||= super
173
+ end
174
+ end
175
+
176
+ ##
177
+ # a Presenter for producing `IIIFManifest::DisplayImage` objects
178
+ #
179
+ class DisplayImagePresenter < Draper::Decorator
180
+ delegate_all
181
+
182
+ include Hyrax::DisplaysImage
183
+
184
+ ##
185
+ # @!attribute [w] ability
186
+ # @return [Ability]
187
+ # @!attribute [w] hostname
188
+ # @return [String]
189
+ attr_writer :ability, :hostname
190
+
191
+ ##
192
+ # Creates a display image only where #model is an image.
193
+ #
194
+ # @return [IIIFManifest::DisplayImage] the display image required by the manifest builder.
195
+ def display_image
196
+ return nil unless model.image?
197
+ return nil unless latest_file_id
198
+
199
+ IIIFManifest::DisplayImage
200
+ .new(display_image_url(hostname),
201
+ format: image_format(alpha_channels),
202
+ width: width,
203
+ height: height,
204
+ iiif_endpoint: iiif_endpoint(latest_file_id, base_url: hostname))
205
+ end
206
+
207
+ def hostname
208
+ @hostname || 'localhost'
209
+ end
210
+
211
+ ##
212
+ # @return [Boolean] false
213
+ def work?
214
+ false
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ def hostname
221
+ @hostname || 'localhost'
222
+ end
223
+
224
+ def metadata_fields
225
+ Hyrax.config.iiif_metadata_fields
226
+ end
227
+
228
+ def scrub(value)
229
+ Loofah.fragment(value).scrub!(:whitewash).to_s
230
+ end
231
+ end
232
+ end
@@ -36,14 +36,8 @@ module Hyrax
36
36
  @work_presenters ||= member_presenters(ordered_ids - file_set_ids, work_presenter_class)
37
37
  end
38
38
 
39
- # TODO: Extract this to ActiveFedora::Aggregations::ListSource
40
39
  def ordered_ids
41
- @ordered_ids ||= begin
42
- ActiveFedora::SolrService.query("proxy_in_ssi:#{id}",
43
- rows: 10_000,
44
- fl: "ordered_targets_ssim")
45
- .flat_map { |x| x.fetch("ordered_targets_ssim", []) }
46
- end
40
+ @ordered_ids ||= Hyrax::SolrDocument::OrderedMembers.decorate(@work).ordered_member_ids
47
41
  end
48
42
 
49
43
  private
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ ##
5
+ # constructs IIIF Manifests and holds them in the Rails cache,
6
+ # this approach avoids long manifest build times for some kinds of requests,
7
+ # at the cost of introducing cache invalidation issues.
8
+ class CachingIiifManifestBuilder < ManifestBuilderService
9
+ KEY_PREFIX = 'iiif-cache-v1'
10
+
11
+ attr_accessor :expires_in
12
+
13
+ ##
14
+ # @api public
15
+ #
16
+ # @param iiif_manifest_factory [Class] a class that initializes with presenter
17
+ # object and returns an object that responds to `#to_h`
18
+ # @param expires_in [Integer] the number of seconds until the cache expires
19
+ # @see Hyrax::Configuration#iiif_manifest_cache_duration
20
+ def initialize(iiif_manifest_factory: ::IIIFManifest::ManifestFactory, expires_in: Hyrax.config.iiif_manifest_cache_duration)
21
+ self.expires_in = expires_in
22
+
23
+ super(iiif_manifest_factory: iiif_manifest_factory)
24
+ end
25
+
26
+ ##
27
+ # @see ManifestBuilderService#as_json
28
+ def manifest_for(presenter:)
29
+ Rails.cache.fetch(manifest_cache_key(presenter: presenter), expires_in: expires_in) do
30
+ super
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ ##
37
+ # @note adding a version_for suffix helps us manage cache expiration,
38
+ # reducing false cache hits
39
+ #
40
+ # @param presenter [Hyrax::IiifManifestPresenter]
41
+ #
42
+ # @return [String]
43
+ def manifest_cache_key(presenter:)
44
+ "#{KEY_PREFIX}_#{presenter.id}/#{version_for(presenter)}"
45
+ end
46
+
47
+ ##
48
+ # @return [String]
49
+ def version_for(presenter)
50
+ presenter.version
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Identifier
4
+ ##
5
+ # Builds an identifier string.
6
+ #
7
+ # Implementations must accept a `prefix:` to `#initialize`, and a `hint:` to
8
+ # `#build`. Either or both may be used at the preference of the specific
9
+ # implementer or ignored entirely when `#build` is called.
10
+ #
11
+ # @example
12
+ # builder = Hyrax::Identifier::Builder.new(prefix: 'moomin')
13
+ # builder.build(hint: '1') # => "moomin/1"
14
+ class Builder
15
+ ##
16
+ # @!attribute prefix [rw]
17
+ # @return [String] the prefix to use when building identifiers
18
+ attr_accessor :prefix
19
+
20
+ ##
21
+ # @param prefix [String] the prefix to use when building identifiers
22
+ def initialize(prefix: 'pfx')
23
+ @prefix = prefix
24
+ end
25
+
26
+ ##
27
+ # @note this default builder requires a `hint` which it appends to the
28
+ # prefix to generate the identifier string.
29
+ #
30
+ # @param hint [#to_s] a string-able object which may be used by the builder
31
+ # to generate an identifier. Hints may be required by some builders, while
32
+ # others may ignore them to generate an identifier by other means.
33
+ #
34
+ # @return [String]
35
+ # @raise [ArgumentError] if an identifer can't be built from the provided
36
+ # hint.
37
+ def build(hint: nil)
38
+ raise(ArgumentError, "No hint provided to #{self.class}#build") if
39
+ hint.nil?
40
+
41
+ "#{prefix}/#{hint}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Identifier
4
+ class Dispatcher
5
+ ##
6
+ # @!attribute [rw] registrar
7
+ # @return [Hyrax::Identifier::Registrar]
8
+ attr_accessor :registrar
9
+
10
+ ##
11
+ # @param registrar [Hyrax::Identifier::Registrar]
12
+ def initialize(registrar:)
13
+ @registrar = registrar
14
+ end
15
+
16
+ class << self
17
+ ##
18
+ # @param type [Symbol]
19
+ # @param registrar_opts [Hash]
20
+ # @option registrar_opts [Hyrax::Identifier::Builder] :builder
21
+ #
22
+ # @return [Hyrax::Identifier::Dispatcher] a dispatcher with an registrar for the
23
+ # given type
24
+ # @see IdentifierRegistrar.for
25
+ def for(type, **registrar_opts)
26
+ new(registrar: Hyrax::Identifier::Registrar.for(type, **registrar_opts))
27
+ end
28
+ end
29
+
30
+ ##
31
+ # Assigns an identifier to the object.
32
+ #
33
+ # This involves two steps:
34
+ # - Registering the identifier with the registrar service via `registrar`.
35
+ # - Storing the new identifier on the object, in the provided `attribute`.
36
+ #
37
+ # @note the attribute for identifier storage must be multi-valued, and will
38
+ # be overwritten during assignment.
39
+ #
40
+ # @param attribute [Symbol] the attribute in which to store the identifier.
41
+ # This attribute will be overwritten during assignment.
42
+ # @param object [ActiveFedora::Base, Hyrax::Resource] the object to assign an identifier.
43
+ #
44
+ # @return [ActiveFedora::Base, Hyrax::Resource] object
45
+ def assign_for(object:, attribute: :identifier)
46
+ record = registrar.register!(object: object)
47
+ object.public_send("#{attribute}=".to_sym, [record.identifier])
48
+ object
49
+ end
50
+
51
+ ##
52
+ # Assigns an identifier and saves the object.
53
+ #
54
+ # @see #assign_for
55
+ def assign_for!(object:, attribute: :identifier)
56
+ assign_for(object: object, attribute: attribute).save!
57
+ object
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Identifier
4
+ class Registrar
5
+ class << self
6
+ ##
7
+ # @param type [Symbol]
8
+ # @param opts [Hash]
9
+ # @option opts [Hyrax::Identifier::Builder] :builder
10
+ #
11
+ # @return [Hyrax::Identifier::Registrar] a registrar for the given type
12
+ def for(type, **opts)
13
+ return Hyrax.config.identifier_registrars[type].new(**opts) if Hyrax.config.identifier_registrars.include?(type)
14
+ raise ArgumentError, "Hyrax::Identifier::Registrar not found to handle #{type}"
15
+ end
16
+ end
17
+
18
+ ##
19
+ # @!attribute builder [rw]
20
+ # @return [Hyrax::Identifier::Builder]
21
+ attr_accessor :builder
22
+
23
+ ##
24
+ # @param builder [Hyrax::Identifier::Builder]
25
+ def initialize(builder:)
26
+ @builder = builder
27
+ end
28
+
29
+ ##
30
+ # @abstract
31
+ #
32
+ # @param object [#id]
33
+ #
34
+ # @return [#identifier]
35
+ # @raise [NotImplementedError] when the method is abstract
36
+ def register!(*)
37
+ raise NotImplementedError
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ ##
5
+ # A class responsible for converting a Hyrax::Work like thing into a IIIF
6
+ # manifest.
7
+ #
8
+ # @see !{.as_json}
9
+ class ManifestBuilderService
10
+ ##
11
+ # @api public
12
+ #
13
+ # @param presenter [Hyrax::WorkShowPresenter] the work presenter from which
14
+ # we'll build a manifest.
15
+ # @param iiif_manifest_factory [Class] a class that initializes with presenter
16
+ # object and returns an object that responds to `#to_h`
17
+ #
18
+ # @note While the :presenter may be a Hyrax::WorkShowPresenter it is likely
19
+ # defined by Hyrax::WorksControllerBehavior.show_presenter
20
+ #
21
+ # @return [Hash] a Ruby hash representation of a IIIF manifest document
22
+ #
23
+ # @see Hyrax::WorksControllerBehavior
24
+ def self.manifest_for(presenter:, iiif_manifest_factory: ::IIIFManifest::ManifestFactory)
25
+ new(iiif_manifest_factory: iiif_manifest_factory)
26
+ .manifest_for(presenter: presenter)
27
+ end
28
+
29
+ ##
30
+ # @!attribute [r] manifest_factory
31
+ # @return [#to_h]
32
+ attr_reader :manifest_factory
33
+
34
+ ##
35
+ # @api public
36
+ #
37
+ # @param iiif_manifest_factory [Class] a class that initializes with presenter
38
+ # object and returns an object that responds to `#to_h`
39
+ def initialize(iiif_manifest_factory: ::IIIFManifest::ManifestFactory)
40
+ @manifest_factory = iiif_manifest_factory
41
+ end
42
+
43
+ ##
44
+ # @api public
45
+ #
46
+ # @param presenter [Hyrax::WorkShowPresenter]
47
+ #
48
+ # @return [Hash] a Ruby hash representation of a IIIF manifest document
49
+ def manifest_for(presenter:)
50
+ sanitized_manifest(presenter: presenter)
51
+ end
52
+
53
+ private
54
+
55
+ ##
56
+ # @api private
57
+ # @param presenter [Hyrax::WorkShowPresenter]
58
+ def sanitized_manifest(presenter:)
59
+ # ::IIIFManifest::ManifestBuilder#to_h returns a
60
+ # IIIFManifest::ManifestBuilder::IIIFManifest, not a Hash.
61
+ # to get a Hash, we have to call its #to_json, then parse.
62
+ #
63
+ # wild times. maybe there's a better way to do this with the
64
+ # ManifestFactory interface?
65
+ manifest = manifest_factory.new(presenter).to_h
66
+ hash = JSON.parse(manifest.to_json)
67
+
68
+ hash['label'] = sanitize_value(hash['label']) if hash.key?('label')
69
+ hash['description'] = Array(hash['description'])&.collect { |elem| sanitize_value(elem) } if hash.key?('description')
70
+
71
+ hash['sequences']&.each do |sequence|
72
+ sequence['canvases']&.each do |canvas|
73
+ canvas['label'] = sanitize_value(canvas['label'])
74
+ end
75
+ end
76
+
77
+ hash
78
+ end
79
+
80
+ ##
81
+ # @api private
82
+ # @param [#to_s] text
83
+ # @return [String] a sanitized verison of `text`
84
+ def sanitize_value(text)
85
+ Loofah.fragment(text.to_s).scrub!(:prune).to_s
86
+ end
87
+ end
88
+ end
@@ -22,5 +22,14 @@ module Hyrax
22
22
  return if version.nil?
23
23
  VersionCommitter.create(version_id: version.uri, committer_login: user_key)
24
24
  end
25
+
26
+ # @param [ActiveFedora::File | Hyrax::FileMetadata] content
27
+ def self.versioned_file_id(file)
28
+ versions = file.versions.all
29
+
30
+ return ActiveFedora::Base.uri_to_id(versions.last.uri) if versions.present?
31
+
32
+ file.id
33
+ end
25
34
  end
26
35
  end
@@ -19,7 +19,7 @@
19
19
  <p class="switch-upload-type">To create a separate work for each of the files, go to <%= link_to "Batch upload", hyrax.new_batch_upload_path %></p>
20
20
  <% end %>
21
21
  <% end %>
22
- <%= render 'hyrax/base/guts4form', f: f %>
22
+ <%= render 'hyrax/base/guts4form', f: f, tabs: form_tabs_for(form: f.object) %>
23
23
  <% end %>
24
24
 
25
25
  <script type="text/javascript">
@@ -26,6 +26,10 @@
26
26
  <%= f.input :on_behalf_of, collection: current_user.can_make_deposits_for.map(&:user_key), prompt: "Yourself" %>
27
27
  </div>
28
28
  <% end %>
29
+
30
+ <% form_progress_sections_for(form: f.object).each do |section| %>
31
+ <%= render "form_progress_#{section}", f: f %>
32
+ <% end %>
29
33
  </div>
30
34
  <div class="panel-footer text-center">
31
35
  <% if ::Flipflop.show_deposit_agreement? %>
@@ -1,5 +1,11 @@
1
1
  <% # we will yield to content_for for each tab, e.g. :files_tab %>
2
- <% tabs ||= %w[metadata files relationships] # default tab order %>
2
+ <%# Not passing tabs local param to this partial is deprecated and the tabs param will be required in Hyrax 3.0 %>
3
+ <% unless defined?(tabs) %>
4
+ <% Deprecation.warn(self, "Passing the tabs local param to the _guts4form partial will be required in Hyrax 3.0. " \
5
+ "Consider removing overriding view partials and customizing the tab list " \
6
+ "by overriding the new form_tabs_for(form:) helper.") %>
7
+ <% tabs = form_tabs_for(form: f.object) # default tab order %>
8
+ <% end %>
3
9
  <div class="row">
4
10
  <div class="col-xs-12 col-sm-8">
5
11
  <div class="panel panel-default tabs" role="main">
@@ -10,7 +10,7 @@
10
10
  <%= link_to t("hyrax.batch_uploads.files.button_label"), [main_app, :new, Hyrax.primary_work_type.model_name.singular_route_key] %>
11
11
  </p>
12
12
  <% end %>
13
- <%= render 'hyrax/base/guts4form', f: f, tabs: %w[files metadata relationships] %>
13
+ <%= render 'hyrax/base/guts4form', f: f, tabs: form_tabs_for(form: f.object) %>
14
14
  <%= f.hidden_field :payload_concern, value: @form.payload_concern %>
15
15
  <% end %>
16
16
 
@@ -1,6 +1,6 @@
1
1
  <% menu = Hyrax::MenuPresenter.new(self) %>
2
2
  <div class="sidebar-toggle"><span class="fa fa-chevron-circle-right"></span></div>
3
- <nav>
3
+ <nav aria-label="sidebar-nav">
4
4
  <ul class="nav nav-pills nav-stacked">
5
5
  <li>
6
6
  <div class="profile">
@@ -42,4 +42,8 @@ Flipflop.configure do
42
42
  feature :hide_users_list,
43
43
  default: true,
44
44
  description: "Do not show users list unless user has authenticated."
45
+
46
+ feature :cache_work_iiif_manifest,
47
+ default: false,
48
+ description: "Use Rails.cache to cache the JSON document for IIIF manifests"
45
49
  end
@@ -39,6 +39,7 @@ SUMMARY
39
39
  spec.add_dependency 'browse-everything', '>= 0.16'
40
40
  spec.add_dependency 'carrierwave', '~> 1.0'
41
41
  spec.add_dependency 'clipboard-rails', '~> 1.5'
42
+ spec.add_dependency 'draper', '~> 4.0'
42
43
  spec.add_dependency 'dry-equalizer', '~> 0.2'
43
44
  spec.add_dependency 'dry-struct', '>= 0.1', '< 2.0'
44
45
  spec.add_dependency 'dry-transaction', '~> 0.11'
@@ -266,6 +266,11 @@ Hyrax.config do |config|
266
266
  # mount point.
267
267
  #
268
268
  # config.whitelisted_ingest_dirs = []
269
+
270
+ ## Remote identifiers configuration
271
+ # Add registrar implementations by uncommenting and adding to the hash below.
272
+ # See app/services/hyrax/identifier/registrar.rb for the registrar interface
273
+ # config.identifier_registrars = {}
269
274
  end
270
275
 
271
276
  Date::DATE_FORMATS[:standard] = "%m/%d/%Y"
@@ -8,6 +8,7 @@ require 'font-awesome-rails'
8
8
  require 'tinymce-rails'
9
9
  require 'blacklight'
10
10
  require 'blacklight/gallery'
11
+ require 'iiif_manifest'
11
12
  require 'noid-rails'
12
13
  require 'hydra/head'
13
14
  require 'hydra-editor'
@@ -431,6 +431,16 @@ module Hyrax
431
431
  end
432
432
  attr_writer :iiif_metadata_fields
433
433
 
434
+ # Duration in which we should cache the generated IIIF manifest.
435
+ # Default is 30 days (in seconds).
436
+ #
437
+ # @return [Integer] number of seconds
438
+ # @see https://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-fetch
439
+ def iiif_manifest_cache_duration
440
+ @iiif_manifest_cache_duration ||= 30.days.to_i
441
+ end
442
+ attr_writer :iiif_manifest_cache_duration
443
+
434
444
  # Should a button with "Share my work" show on the front page to users who are not logged in?
435
445
  attr_writer :display_share_button_when_not_logged_in
436
446
  def display_share_button_when_not_logged_in?
@@ -514,6 +524,11 @@ module Hyrax
514
524
  ->(id:, extent:) { Samvera::NestingIndexer.reindex_relationships(id: id, extent: extent) }
515
525
  end
516
526
 
527
+ attr_writer :identifier_registrars
528
+ def identifier_registrars
529
+ @identifier_registrars ||= {}
530
+ end
531
+
517
532
  private
518
533
 
519
534
  # @param [Symbol, #to_s] model_name - symbol representing the model
@@ -5,6 +5,7 @@ module Hyrax
5
5
  # These gems must be required outside of an initializer or they don't get loaded.
6
6
  require 'awesome_nested_set'
7
7
  require 'breadcrumbs_on_rails'
8
+ require 'draper'
8
9
  require 'jquery-ui-rails'
9
10
  require 'flot-rails'
10
11
  require 'almond-rails'
@@ -1,2 +1,3 @@
1
1
  require 'hyrax/specs/shared_specs/derivative_service'
2
+ require 'hyrax/specs/shared_specs/identifiers'
2
3
  require 'hyrax/specs/shared_specs/workflow_method'
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples 'a Hyrax::Identifier::Builder' do
4
+ subject(:builder) { described_class.new }
5
+
6
+ describe '#build' do
7
+ it 'returns an identifier string' do
8
+ expect(builder.build(hint: 'moomin'))
9
+ .to respond_to :to_str
10
+ end
11
+ end
12
+ end
13
+
14
+ RSpec.shared_examples 'a Hyrax::Identifier::Registrar' do
15
+ subject(:registrar) { described_class.new(builder: builder) }
16
+ let(:builder) { instance_double(Hyrax::Identifier::Builder, build: 'moomin') }
17
+ let(:object) { instance_double(GenericWork, id: 'moomin_id') }
18
+
19
+ it { is_expected.to have_attributes(builder: builder) }
20
+
21
+ describe '#register!' do
22
+ it 'creates an identifier record' do
23
+ expect(registrar.register!(object: object).identifier)
24
+ .to respond_to :to_str
25
+ end
26
+ end
27
+ end
@@ -1,3 +1,3 @@
1
1
  module Hyrax
2
- VERSION = '2.8.0'.freeze
2
+ VERSION = '2.9.0'.freeze
3
3
  end
@@ -1,6 +1,6 @@
1
1
  # Hack for https://github.com/rails/rails/issues/35153
2
2
  gsub_file 'Gemfile', /^gem ["']sqlite3["']$/, 'gem "sqlite3", "~> 1.3.0"'
3
- gem 'hyrax', '2.6.0'
3
+ gem 'hyrax', '2.9.0'
4
4
  run 'bundle install'
5
5
  generate 'hyrax:install', '-f'
6
6
  rails_command 'db:migrate'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyrax
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.0
4
+ version: 2.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2020-06-26 00:00:00.000000000 Z
17
+ date: 2020-07-28 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: rails
@@ -162,6 +162,20 @@ dependencies:
162
162
  - - "~>"
163
163
  - !ruby/object:Gem::Version
164
164
  version: '1.5'
165
+ - !ruby/object:Gem::Dependency
166
+ name: draper
167
+ requirement: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - "~>"
170
+ - !ruby/object:Gem::Version
171
+ version: '4.0'
172
+ type: :runtime
173
+ prerelease: false
174
+ version_requirements: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: '4.0'
165
179
  - !ruby/object:Gem::Dependency
166
180
  name: dry-equalizer
167
181
  requirement: !ruby/object:Gem::Requirement
@@ -1460,6 +1474,7 @@ files:
1460
1474
  - app/helpers/hyrax/title_helper.rb
1461
1475
  - app/helpers/hyrax/trophy_helper.rb
1462
1476
  - app/helpers/hyrax/url_helper.rb
1477
+ - app/helpers/hyrax/work_form_helper.rb
1463
1478
  - app/indexers/concerns/hyrax/indexes_basic_metadata.rb
1464
1479
  - app/indexers/concerns/hyrax/indexes_linked_metadata.rb
1465
1480
  - app/indexers/hyrax/admin_set_indexer.rb
@@ -1497,6 +1512,7 @@ files:
1497
1512
  - app/jobs/hyrax/grant_read_to_members_job.rb
1498
1513
  - app/jobs/hyrax/revoke_edit_from_members_job.rb
1499
1514
  - app/jobs/hyrax/revoke_edit_job.rb
1515
+ - app/jobs/iiif_manifest_cache_prewarm_job.rb
1500
1516
  - app/jobs/import_export_job.rb
1501
1517
  - app/jobs/import_url_job.rb
1502
1518
  - app/jobs/ingest_job.rb
@@ -1541,6 +1557,7 @@ files:
1541
1557
  - app/models/concerns/hyrax/solr_document/characterization.rb
1542
1558
  - app/models/concerns/hyrax/solr_document/export.rb
1543
1559
  - app/models/concerns/hyrax/solr_document/metadata.rb
1560
+ - app/models/concerns/hyrax/solr_document/ordered_members.rb
1544
1561
  - app/models/concerns/hyrax/solr_document_behavior.rb
1545
1562
  - app/models/concerns/hyrax/suppressible.rb
1546
1563
  - app/models/concerns/hyrax/user.rb
@@ -1619,6 +1636,7 @@ files:
1619
1636
  - app/presenters/hyrax/file_usage.rb
1620
1637
  - app/presenters/hyrax/fixity_status_presenter.rb
1621
1638
  - app/presenters/hyrax/homepage_presenter.rb
1639
+ - app/presenters/hyrax/iiif_manifest_presenter.rb
1622
1640
  - app/presenters/hyrax/inspect_work_presenter.rb
1623
1641
  - app/presenters/hyrax/lease_presenter.rb
1624
1642
  - app/presenters/hyrax/member_presenter_factory.rb
@@ -1705,6 +1723,7 @@ files:
1705
1723
  - app/services/hyrax/analytics.rb
1706
1724
  - app/services/hyrax/batch_create_failure_service.rb
1707
1725
  - app/services/hyrax/batch_create_success_service.rb
1726
+ - app/services/hyrax/caching_iiif_manifest_builder.rb
1708
1727
  - app/services/hyrax/change_content_depositor_service.rb
1709
1728
  - app/services/hyrax/collection_member_service.rb
1710
1729
  - app/services/hyrax/collection_size_service.rb
@@ -1733,6 +1752,9 @@ files:
1733
1752
  - app/services/hyrax/fixity_check_failure_service.rb
1734
1753
  - app/services/hyrax/form_metadata_service.rb
1735
1754
  - app/services/hyrax/graph_exporter.rb
1755
+ - app/services/hyrax/identifier/builder.rb
1756
+ - app/services/hyrax/identifier/dispatcher.rb
1757
+ - app/services/hyrax/identifier/registrar.rb
1736
1758
  - app/services/hyrax/iiif_authorization_service.rb
1737
1759
  - app/services/hyrax/import_url_failure_service.rb
1738
1760
  - app/services/hyrax/indexes_thumbnails.rb
@@ -1743,6 +1765,7 @@ files:
1743
1765
  - app/services/hyrax/local_file_service.rb
1744
1766
  - app/services/hyrax/lock_manager.rb
1745
1767
  - app/services/hyrax/lockable.rb
1768
+ - app/services/hyrax/manifest_builder_service.rb
1746
1769
  - app/services/hyrax/messenger_service.rb
1747
1770
  - app/services/hyrax/microdata.rb
1748
1771
  - app/services/hyrax/multiple_membership_checker.rb
@@ -2371,6 +2394,7 @@ files:
2371
2394
  - lib/hyrax/search_state.rb
2372
2395
  - lib/hyrax/specs/shared_specs.rb
2373
2396
  - lib/hyrax/specs/shared_specs/derivative_service.rb
2397
+ - lib/hyrax/specs/shared_specs/identifiers.rb
2374
2398
  - lib/hyrax/specs/shared_specs/workflow_method.rb
2375
2399
  - lib/hyrax/transactions.rb
2376
2400
  - lib/hyrax/transactions/container.rb