arclight 0.0.1 → 1.6.2

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 (204) hide show
  1. checksums.yaml +5 -5
  2. data/.all-contributorsrc +450 -0
  3. data/.codeclimate.yml +5 -0
  4. data/.eslintrc +22 -0
  5. data/.github/workflows/ruby.yml +51 -0
  6. data/.gitignore +7 -0
  7. data/.rspec +0 -1
  8. data/.rubocop.yml +521 -0
  9. data/.rubocop_todo.yml +122 -0
  10. data/.solr_wrapper +6 -0
  11. data/CONTRIBUTING.md +43 -0
  12. data/CONTRIBUTORS.md +79 -0
  13. data/Gemfile +41 -0
  14. data/LICENSE.txt +1 -0
  15. data/README.md +100 -12
  16. data/Rakefile +13 -3
  17. data/app/assets/images/arclight/logo.png +0 -0
  18. data/app/assets/images/blacklight/bookmark.svg +1 -0
  19. data/app/assets/images/blacklight/collection.svg +5 -0
  20. data/app/assets/images/blacklight/compact.svg +1 -0
  21. data/app/assets/images/blacklight/container.svg +5 -0
  22. data/app/assets/images/blacklight/ead.svg +1 -0
  23. data/app/assets/images/blacklight/file.svg +5 -0
  24. data/app/assets/images/blacklight/folder.svg +1 -0
  25. data/app/assets/images/blacklight/list.svg +1 -0
  26. data/app/assets/images/blacklight/minus.svg +1 -0
  27. data/app/assets/images/blacklight/online.svg +5 -0
  28. data/app/assets/images/blacklight/pdf.svg +1 -0
  29. data/app/assets/images/blacklight/plus.svg +1 -0
  30. data/app/assets/images/blacklight/repository.svg +1 -0
  31. data/app/assets/javascripts/arclight/arclight.js +4 -0
  32. data/app/assets/javascripts/arclight/oembed_controller.js +58 -0
  33. data/app/assets/javascripts/arclight/truncate_controller.js +27 -0
  34. data/app/assets/stylesheets/arclight/application.scss +14 -0
  35. data/app/assets/stylesheets/arclight/build.scss +4 -0
  36. data/app/assets/stylesheets/arclight/modules/collection_search.scss +3 -0
  37. data/app/assets/stylesheets/arclight/modules/hierarchy_and_online_contents.scss +155 -0
  38. data/app/assets/stylesheets/arclight/modules/highlights.scss +11 -0
  39. data/app/assets/stylesheets/arclight/modules/icons.scss +20 -0
  40. data/app/assets/stylesheets/arclight/modules/layout.scss +220 -0
  41. data/app/assets/stylesheets/arclight/modules/mastheads.scss +42 -0
  42. data/app/assets/stylesheets/arclight/modules/repositories.scss +25 -0
  43. data/app/assets/stylesheets/arclight/modules/repository_card.scss +9 -0
  44. data/app/assets/stylesheets/arclight/modules/search_form.scss +9 -0
  45. data/app/assets/stylesheets/arclight/modules/search_results.scss +250 -0
  46. data/app/assets/stylesheets/arclight/modules/show_collection.scss +38 -0
  47. data/app/assets/stylesheets/arclight/modules/truncator.scss +58 -0
  48. data/app/assets/stylesheets/arclight/responsive.scss +13 -0
  49. data/app/assets/stylesheets/arclight/variables.scss +25 -0
  50. data/app/components/arclight/access_component.html.erb +14 -0
  51. data/app/components/arclight/access_component.rb +25 -0
  52. data/app/components/arclight/bookmark_component.html.erb +25 -0
  53. data/app/components/arclight/bookmark_component.rb +9 -0
  54. data/app/components/arclight/breadcrumb_component.rb +50 -0
  55. data/app/components/arclight/breadcrumbs_hierarchy_component.html.erb +34 -0
  56. data/app/components/arclight/breadcrumbs_hierarchy_component.rb +25 -0
  57. data/app/components/arclight/collection_context_component.html.erb +12 -0
  58. data/app/components/arclight/collection_context_component.rb +27 -0
  59. data/app/components/arclight/collection_info_component.html.erb +28 -0
  60. data/app/components/arclight/collection_info_component.rb +26 -0
  61. data/app/components/arclight/collection_sidebar_component.html.erb +15 -0
  62. data/app/components/arclight/collection_sidebar_component.rb +39 -0
  63. data/app/components/arclight/constraints_component.html.erb +7 -0
  64. data/app/components/arclight/constraints_component.rb +17 -0
  65. data/app/components/arclight/document_collection_context_component.html.erb +29 -0
  66. data/app/components/arclight/document_collection_context_component.rb +28 -0
  67. data/app/components/arclight/document_collection_hierarchy_component.html.erb +50 -0
  68. data/app/components/arclight/document_collection_hierarchy_component.rb +62 -0
  69. data/app/components/arclight/document_component.html.erb +39 -0
  70. data/app/components/arclight/document_component.rb +49 -0
  71. data/app/components/arclight/document_components_hierarchy_component.html.erb +20 -0
  72. data/app/components/arclight/document_components_hierarchy_component.rb +34 -0
  73. data/app/components/arclight/document_download_component.html.erb +24 -0
  74. data/app/components/arclight/document_download_component.rb +71 -0
  75. data/app/components/arclight/embed_component.html.erb +10 -0
  76. data/app/components/arclight/embed_component.rb +43 -0
  77. data/app/components/arclight/expand_hierarchy_button_component.html.erb +5 -0
  78. data/app/components/arclight/expand_hierarchy_button_component.rb +16 -0
  79. data/app/components/arclight/group_component.html.erb +34 -0
  80. data/app/components/arclight/group_component.rb +23 -0
  81. data/app/components/arclight/header_component.html.erb +5 -0
  82. data/app/components/arclight/header_component.rb +10 -0
  83. data/app/components/arclight/index_metadata_field_component.html.erb +16 -0
  84. data/app/components/arclight/index_metadata_field_component.rb +20 -0
  85. data/app/components/arclight/masthead_component.html.erb +17 -0
  86. data/app/components/arclight/masthead_component.rb +10 -0
  87. data/app/components/arclight/metadata_section_component.html.erb +13 -0
  88. data/app/components/arclight/metadata_section_component.rb +22 -0
  89. data/app/components/arclight/oembed_viewer_component.html.erb +5 -0
  90. data/app/components/arclight/oembed_viewer_component.rb +16 -0
  91. data/app/components/arclight/online_content_filter_component.html.erb +15 -0
  92. data/app/components/arclight/online_content_filter_component.rb +17 -0
  93. data/app/components/arclight/online_status_indicator_component.rb +19 -0
  94. data/app/components/arclight/repository_breadcrumb_component.html.erb +10 -0
  95. data/app/components/arclight/repository_breadcrumb_component.rb +17 -0
  96. data/app/components/arclight/repository_location_component.html.erb +20 -0
  97. data/app/components/arclight/repository_location_component.rb +10 -0
  98. data/app/components/arclight/search_bar_component.html.erb +27 -0
  99. data/app/components/arclight/search_bar_component.rb +32 -0
  100. data/app/components/arclight/search_result_breadcrumbs_component.html.erb +6 -0
  101. data/app/components/arclight/search_result_breadcrumbs_component.rb +37 -0
  102. data/app/components/arclight/search_result_component.html.erb +20 -0
  103. data/app/components/arclight/search_result_component.rb +18 -0
  104. data/app/components/arclight/search_result_title_component.html.erb +15 -0
  105. data/app/components/arclight/search_result_title_component.rb +15 -0
  106. data/app/components/arclight/sidebar_component.html.erb +9 -0
  107. data/app/components/arclight/sidebar_component.rb +19 -0
  108. data/app/components/arclight/upper_metadata_layout_component.rb +10 -0
  109. data/app/components/blacklight/icons/bookmark_component.rb +12 -0
  110. data/app/components/blacklight/icons/collection_component.rb +14 -0
  111. data/app/components/blacklight/icons/compact_component.rb +12 -0
  112. data/app/components/blacklight/icons/container_component.rb +14 -0
  113. data/app/components/blacklight/icons/ead_component.rb +12 -0
  114. data/app/components/blacklight/icons/file_component.rb +14 -0
  115. data/app/components/blacklight/icons/folder_component.rb +12 -0
  116. data/app/components/blacklight/icons/online_component.rb +15 -0
  117. data/app/components/blacklight/icons/pdf_component.rb +12 -0
  118. data/app/components/blacklight/icons/repository_component.rb +12 -0
  119. data/app/controllers/arclight/repositories_controller.rb +40 -0
  120. data/app/helpers/arclight/ead_format_helpers.rb +309 -0
  121. data/app/helpers/arclight/field_config_helpers.rb +26 -0
  122. data/app/helpers/arclight_helper.rb +108 -0
  123. data/app/models/arclight/document_downloads.rb +122 -0
  124. data/app/models/arclight/parent.rb +24 -0
  125. data/app/models/arclight/parents.rb +39 -0
  126. data/app/models/arclight/requests/aeon_external_request.rb +42 -0
  127. data/app/models/arclight/requests/aeon_web_ead.rb +52 -0
  128. data/app/models/arclight/requests/google_form.rb +49 -0
  129. data/app/models/concerns/arclight/catalog.rb +44 -0
  130. data/app/models/concerns/arclight/search_behavior.rb +47 -0
  131. data/app/models/concerns/arclight/solr_document.rb +160 -0
  132. data/app/presenters/arclight/index_presenter.rb +10 -0
  133. data/app/presenters/arclight/show_presenter.rb +54 -0
  134. data/app/views/arclight/.keep +0 -0
  135. data/app/views/arclight/_requests.html.erb +7 -0
  136. data/app/views/arclight/repositories/_repository.html.erb +43 -0
  137. data/app/views/arclight/repositories/index.html.erb +9 -0
  138. data/app/views/arclight/repositories/show.html.erb +37 -0
  139. data/app/views/arclight/requests/_aeon_external_request_endpoint.html.erb +8 -0
  140. data/app/views/arclight/requests/_aeon_web_ead.html.erb +6 -0
  141. data/app/views/arclight/requests/_google_form.html.erb +10 -0
  142. data/app/views/catalog/_document_list.html.erb +8 -0
  143. data/app/views/catalog/_group.html.erb +4 -0
  144. data/app/views/catalog/_group_toggle.html.erb +10 -0
  145. data/app/views/catalog/_search_results_header.html.erb +3 -0
  146. data/app/views/catalog/hierarchy.html.erb +19 -0
  147. data/app/views/catalog/index.html.erb +17 -0
  148. data/app/views/shared/_breadcrumbs.html.erb +3 -0
  149. data/app/views/shared/_main_menu_links.html.erb +6 -0
  150. data/arclight.gemspec +29 -11
  151. data/bin/rails +13 -0
  152. data/config/breadcrumbs.rb +24 -0
  153. data/config/i18n-tasks.yml +134 -0
  154. data/config/importmap.rb +3 -0
  155. data/config/locales/arclight.en.yml +100 -0
  156. data/config/repositories.yml +0 -0
  157. data/config/routes.rb +6 -0
  158. data/docker-compose.yml +16 -0
  159. data/lib/arclight/digital_object.rb +27 -0
  160. data/lib/arclight/engine.rb +47 -0
  161. data/lib/arclight/exceptions.rb +19 -0
  162. data/lib/arclight/hash_absolute_xpath.rb +62 -0
  163. data/lib/arclight/level_label.rb +46 -0
  164. data/lib/arclight/missing_id_strategy.rb +21 -0
  165. data/lib/arclight/normalized_date.rb +49 -0
  166. data/lib/arclight/normalized_id.rb +30 -0
  167. data/lib/arclight/normalized_title.rb +33 -0
  168. data/lib/arclight/repository.rb +116 -0
  169. data/lib/arclight/routes/hierarchy.rb +19 -0
  170. data/lib/arclight/routes.rb +8 -0
  171. data/lib/arclight/traject/ead2_component_config.rb +335 -0
  172. data/lib/arclight/traject/ead2_config.rb +316 -0
  173. data/lib/arclight/traject/nokogiri_namespaceless_reader.rb +22 -0
  174. data/lib/arclight/version.rb +3 -1
  175. data/lib/arclight/year_range.rb +111 -0
  176. data/lib/arclight.rb +11 -1
  177. data/lib/generators/arclight/install_generator.rb +163 -0
  178. data/lib/generators/arclight/templates/arclight.scss +6 -0
  179. data/lib/generators/arclight/templates/catalog_controller.rb +423 -0
  180. data/lib/generators/arclight/templates/config/downloads.yml +25 -0
  181. data/lib/generators/arclight/templates/config/locales/arclight.en.yml +61 -0
  182. data/lib/generators/arclight/templates/config/repositories.yml +53 -0
  183. data/lib/generators/arclight/update_generator.rb +22 -0
  184. data/lib/tasks/index.rake +84 -0
  185. data/package.json +30 -0
  186. data/solr/conf/_rest_managed.json +3 -0
  187. data/solr/conf/admin-extra.html +31 -0
  188. data/solr/conf/elevate.xml +36 -0
  189. data/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
  190. data/solr/conf/protwords.txt +21 -0
  191. data/solr/conf/schema.xml +400 -0
  192. data/solr/conf/solrconfig.xml +328 -0
  193. data/solr/conf/spellings.txt +2 -0
  194. data/solr/conf/stopwords.txt +58 -0
  195. data/solr/conf/stopwords_en.txt +58 -0
  196. data/solr/conf/synonyms.txt +31 -0
  197. data/solr/conf/xslt/example.xsl +132 -0
  198. data/solr/conf/xslt/example_atom.xsl +67 -0
  199. data/solr/conf/xslt/example_rss.xsl +66 -0
  200. data/solr/conf/xslt/luke.xsl +337 -0
  201. data/tasks/arclight.rake +79 -0
  202. data/template.rb +10 -0
  203. metadata +440 -20
  204. data/.travis.yml +0 -5
@@ -0,0 +1,309 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # A module to add EAD to HTML transformation rules for Arclight
6
+ module EadFormatHelpers # rubocop:disable Metrics/ModuleLength
7
+ extend ActiveSupport::Concern
8
+
9
+ def render_html_tags(args)
10
+ values = Array(args[:value])
11
+ values.map! do |value|
12
+ transform_ead_to_html(value)
13
+ end
14
+ values.map! { |value| wrap_in_paragraph(value) } if values.count > 1
15
+ safe_join(values.map(&:html_safe))
16
+ end
17
+
18
+ private
19
+
20
+ def transform_ead_to_html(value)
21
+ Loofah.xml_fragment(condense_whitespace(value))
22
+ .scrub!(ead_to_html_scrubber)
23
+ .scrub!(:strip).to_html
24
+ end
25
+
26
+ # rubocop:disable Metrics/AbcSize
27
+ def ead_to_html_scrubber
28
+ Loofah::Scrubber.new do |node|
29
+ format_render_attributes(node) if node.attr('render').present?
30
+ convert_to_span(node) if CONVERT_TO_SPAN_TAGS.include? node.name
31
+ convert_to_br(node) if CONVERT_TO_BR_TAG.include? node.name
32
+ format_links(node) if %w[extptr extref extrefloc ptr ref].include? node.name
33
+ format_lists(node) if %w[list chronlist].include? node.name
34
+ format_indexes(node) if node.name == 'index'
35
+ format_tables(node) if node.name == 'table'
36
+ node
37
+ end
38
+ end
39
+ # rubocop:enable Metrics/AbcSize
40
+
41
+ # Tags that should be converted to <span> tags because of formatting conflicts between XML and HTML
42
+ CONVERT_TO_SPAN_TAGS = ['title'].freeze
43
+
44
+ # Tags that should be converted to HTML <br/> tags
45
+ CONVERT_TO_BR_TAG = ['lb'].freeze
46
+
47
+ def convert_to_span(node)
48
+ node.name = 'span'
49
+ end
50
+
51
+ def convert_to_br(node)
52
+ node.name = 'br'
53
+ end
54
+
55
+ def condense_whitespace(str)
56
+ str.squish.strip.gsub(/>[\n\s]+</, '><')
57
+ end
58
+
59
+ def wrap_in_paragraph(value)
60
+ if value.start_with?('<')
61
+ value
62
+ else
63
+ content_tag(:p, value)
64
+ end
65
+ end
66
+
67
+ def format_render_attributes(node) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
68
+ case node.attr('render')
69
+ when 'altrender'
70
+ node.name = 'span'
71
+ node['class'] = node['altrender']
72
+ when 'bold'
73
+ node.name = 'strong'
74
+ when 'bolddoublequote'
75
+ node.name = 'strong'
76
+ node.prepend_child '"'
77
+ node.add_child '"'
78
+ when 'bolditalic'
79
+ node.name = 'strong'
80
+ node.wrap('<em/>')
81
+ when 'boldsinglequote'
82
+ node.name = 'strong'
83
+ node.prepend_child '\''
84
+ node.add_child '\''
85
+ when 'boldsmcaps'
86
+ node.name = 'strong'
87
+ node.wrap('<small/>')
88
+ node['class'] = 'text-uppercase'
89
+ when 'boldunderline'
90
+ node.name = 'strong'
91
+ node['class'] = 'text-underline'
92
+ when 'doublequote'
93
+ node.name = 'span'
94
+ node.prepend_child '"'
95
+ node.add_child '"'
96
+ when 'italic', 'nonproport'
97
+ node.name = 'em'
98
+ when 'singlequote'
99
+ node.name = 'span'
100
+ node.prepend_child '\''
101
+ node.add_child '\''
102
+ when 'smcaps'
103
+ node.name = 'small'
104
+ node['class'] = 'text-uppercase'
105
+ when 'sub'
106
+ node.name = 'sub'
107
+ when 'super'
108
+ node.name = 'sup'
109
+ when 'underline'
110
+ node.name = 'span'
111
+ node['class'] = 'text-underline'
112
+ end
113
+ end
114
+
115
+ def format_links(node)
116
+ node.remove_attribute('target')
117
+ node.remove_attribute('type')
118
+ if %w[extptr extref extrefloc].include? node.name
119
+ node['target'] = '_blank'
120
+ node['class'] = 'external-link'
121
+ end
122
+ node.content = node['title'] if (%w[extptr ptr].include? node.name) && node['title'].present?
123
+ node.name = 'a' if node['href'].present?
124
+ end
125
+
126
+ def format_lists(node)
127
+ format_simple_lists(node) if node.name == 'list' && node['type'] != 'deflist'
128
+ format_deflists(node) if node.name == 'list' && node['type'] == 'deflist'
129
+ format_chronlists(node) if node.name == 'chronlist'
130
+ end
131
+
132
+ def format_simple_lists(node)
133
+ node.name = 'ul' if (%w[simple marked].include? node['type']) || node['type'].blank?
134
+ node.name = 'ol' if node['type'] == 'ordered'
135
+ node.remove_attribute('type')
136
+ head_node = node.at_css('head')
137
+ format_list_head(head_node) if head_node.present?
138
+ items = node.css('item')
139
+ items.each { |item_node| item_node.name = 'li' }
140
+ end
141
+
142
+ def format_list_head(node)
143
+ node['class'] = 'list-head'
144
+ node.name = 'div'
145
+ node.parent.previous = node # move it from within the list to above it
146
+ end
147
+
148
+ def format_deflists(node)
149
+ listhead_node = node.at_css('listhead')
150
+ labels = node.css('label')
151
+ items = node.css('item')
152
+ defitems = node.css('defitem')
153
+ node.remove_attribute('type')
154
+
155
+ if listhead_node.present?
156
+ format_deflist_as_table(node, labels, items, defitems)
157
+ else
158
+ format_deflist_as_dl(node, labels, items, defitems)
159
+ end
160
+ end
161
+
162
+ def format_deflist_as_table(node, labels, items, defitems)
163
+ node.name = 'table'
164
+ node['class'] = 'table deflist'
165
+ listhead_node = node.at_css('listhead')
166
+ format_deflist_table_head(listhead_node)
167
+ node.at_css('thead').next = '<tbody/>'
168
+ labels.each { |label_node| label_node.name = 'td' }
169
+ items.each { |item_node| item_node.name = 'td' }
170
+ defitems.each do |defitem_node|
171
+ defitem_node.name = 'tr'
172
+ defitem_node.parent = node.at_css('tbody')
173
+ end
174
+ end
175
+
176
+ def format_deflist_table_head(listhead_node)
177
+ listhead_node.at_css('head01').name = 'th'
178
+ listhead_node.at_css('head02').name = 'th'
179
+ listhead_node.name = 'tr'
180
+ listhead_node.wrap('<thead/>')
181
+ end
182
+
183
+ def format_deflist_as_dl(node, labels, items, defitems)
184
+ node.name = 'dl'
185
+ node['class'] = 'deflist'
186
+ labels.each { |label_node| label_node.name = 'dt' }
187
+ items.each { |item_node| item_node.name = 'dd' }
188
+ defitems.each { |defitem_node| defitem_node.swap(defitem_node.children) } # unwrap
189
+ end
190
+
191
+ def format_chronlists(node)
192
+ node.name = 'table'
193
+ node['class'] = 'table chronlist'
194
+ eventgrps = node.css('eventgrp')
195
+ single_events = node.css('chronitem > event')
196
+ multi_events = node.css('eventgrp > event')
197
+ format_chronlist_header(node)
198
+ node.at_css('thead').next = '<tbody/>'
199
+ format_chronlist_caption(node)
200
+ format_chronlist_chronitems(node)
201
+ format_chronlist_dates(node)
202
+ format_chronlist_events(eventgrps, single_events, multi_events)
203
+ end
204
+
205
+ def format_chronlist_header(node)
206
+ node.add_child('<thead><tr><th>Date</th><th>Event</th></tr></thead>')
207
+ table_head = node.at_css('thead')
208
+ node.children.first.add_previous_sibling(table_head)
209
+ listhead_node = node.at_css('listhead')
210
+ return if listhead_node.blank?
211
+
212
+ node.at_css('thead tr th:nth-of-type(1)').content = node.at_css('listhead/head01').content
213
+ node.at_css('thead tr th:nth-of-type(2)').content = node.at_css('listhead/head02').content
214
+ listhead_node.remove
215
+ end
216
+
217
+ def format_chronlist_caption(node)
218
+ head_node = node.at_css('head')
219
+ return if head_node.blank?
220
+
221
+ head_node.name = 'caption'
222
+ head_node['class'] = 'chronlist-head'
223
+ node.children.first.add_previous_sibling(head_node) # make the caption first
224
+ end
225
+
226
+ def format_chronlist_chronitems(node)
227
+ chronitems = node.css('chronitem')
228
+ chronitems.each do |chronitem_node|
229
+ chronitem_node.name = 'tr'
230
+ chronitem_node.parent = node.at_css('tbody')
231
+ end
232
+ end
233
+
234
+ def format_chronlist_dates(node)
235
+ dates = node.css('date')
236
+ dates.each do |date_node|
237
+ date_node.name = 'td'
238
+ date_node['class'] = 'chronlist-item-date'
239
+ end
240
+ end
241
+
242
+ def format_chronlist_events(eventgrps, single_events, multi_events)
243
+ eventgrps.each do |eventgrp_node|
244
+ eventgrp_node.name = 'td'
245
+ eventgrp_node['class'] = 'chronlist-item-event'
246
+ end
247
+ single_events.each do |event_node|
248
+ event_node.name = 'td'
249
+ event_node['class'] = 'chronlist-item-event'
250
+ end
251
+ multi_events.each { |event_node| event_node.name = 'div' }
252
+ end
253
+
254
+ # Format EAD <index> elements
255
+ def format_indexes(node)
256
+ index_head = node.at_css('head')
257
+ index_head&.name = 'h3'
258
+ index_head&.add_class('index-head')
259
+ index_head['id'] = ['index-', index_head.text].join.parameterize if index_head.present?
260
+ format_indexentries(node)
261
+ node.name = 'div'
262
+ end
263
+
264
+ # Grab all of the <indexentry> children as a nodeset, move them into
265
+ # a <table>, wrap each entry in a <tr> & each value in a <td>.
266
+ def format_indexentries(node)
267
+ indexentries = node.css('indexentry')
268
+ return if indexentries.blank?
269
+
270
+ indexentries.first.previous = '<table class="table indexentries" />'
271
+ indexentries.map do |i|
272
+ i.parent = node.at_css('table.table.indexentries')
273
+ i.wrap('<tr/>')
274
+ i.element_children.map { |c| c.wrap('<td/>') }
275
+ # Assuming two columns in an index, create a blank cell for entries
276
+ # with a missing value.
277
+ i.add_child('<td/>') if i.element_children.count == 1
278
+ end
279
+ end
280
+
281
+ # Format EAD <table> elements, converting <tgroup> to HTML <table>.
282
+ # Ignoring <colspec>, @colname, @colwidth complex rendering
283
+ # logic for now.
284
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
285
+ def format_tables(node)
286
+ node.remove_attribute('frame')
287
+ node.name = 'div' if node.css('tgroup').present?
288
+ format_table_head(node)
289
+ tgroups = node.css('tgroup')
290
+ tgroups&.map do |t|
291
+ t.name = 'table'
292
+ t.remove_attribute('cols')
293
+ t.add_class('table')
294
+ t.css('row').map { |r| r.name = 'tr' }
295
+ t.css('thead entry').map { |e| e.name = 'th' }
296
+ t.css('tbody entry').map { |e| e.name = 'td' }
297
+ end
298
+ end
299
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
300
+
301
+ def format_table_head(node)
302
+ table_head = node.at_css('head')
303
+ return if table_head.blank?
304
+
305
+ table_head.name = 'h3'
306
+ table_head.add_class('table-head')
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # A module to add configuration helpers for certain fields used by Arclight
6
+ module FieldConfigHelpers
7
+ include Arclight::EadFormatHelpers
8
+
9
+ def item_requestable?(document)
10
+ document.requestable?
11
+ end
12
+ Arclight.deprecation.deprecate_methods(self, item_requestable?: 'Call e.g. `document.requestable?` instead')
13
+
14
+ def link_to_name_facet(args)
15
+ options = args[:config]&.separator_options || {}
16
+ values = args[:value] || []
17
+
18
+ values.map do |value|
19
+ link_to(
20
+ value,
21
+ search_action_path(f: { names: [value] })
22
+ )
23
+ end.to_sentence(options).html_safe
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Generic Helpers used in Arclight
5
+ module ArclightHelper
6
+ include Arclight::EadFormatHelpers
7
+ include Arclight::FieldConfigHelpers
8
+ include Blacklight::DocumentHelperBehavior
9
+ include Blacklight::LayoutHelperBehavior
10
+
11
+ def repository_collections_path(repository)
12
+ search_action_url(
13
+ f: {
14
+ repository: [repository.name],
15
+ level: ['Collection']
16
+ }
17
+ )
18
+ end
19
+
20
+ # Returns the i18n-ed string to be used as the h1 in search results
21
+ def search_results_header_text
22
+ if (repo = repository_faceted_on).present?
23
+ t('arclight.search.repository_header', repository: repo.name)
24
+ elsif collection_active?
25
+ t('arclight.search.collections_header')
26
+ else
27
+ t('blacklight.search.header')
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Classes used for customized show page in arclight
33
+ def show_content_classes
34
+ 'col-12 col-lg-8 show-document order-2'
35
+ end
36
+
37
+ def show_sidebar_classes
38
+ 'col-lg-4 order-1 collection-sidebar'
39
+ end
40
+
41
+ def collection_active?
42
+ search_state.filter('level').values == ['Collection']
43
+ end
44
+
45
+ def collection_active_class
46
+ 'active' if collection_active?
47
+ end
48
+
49
+ def grouped?
50
+ respond_to?(:search_state) && search_state&.params_for_search&.dig('group') == 'true'
51
+ end
52
+
53
+ def search_with_group
54
+ search_state.params_for_search.merge('group' => 'true').except('page')
55
+ end
56
+
57
+ def search_without_group
58
+ search_state.params_for_search.except('group', 'page')
59
+ end
60
+
61
+ def on_repositories_index?
62
+ controller_name == 'repositories' && action_name == 'index'
63
+ end
64
+
65
+ # the Repositories menu item is only active on the Repositories index page
66
+ def repositories_active_class
67
+ 'active' if on_repositories_index?
68
+ end
69
+
70
+ # If we have a facet on the repository, then return the Repository object for it
71
+ #
72
+ # @return [Repository]
73
+ def repository_faceted_on
74
+ repos = search_state.filter('repository').values
75
+ return unless repos.one?
76
+
77
+ Arclight::Repository.find_by(name: repos.first)
78
+ end
79
+
80
+ # determine which icon to show in search results header
81
+ # these icon names will need to be updated when the icons are determined
82
+ def document_or_parent_icon(document)
83
+ case document.level&.downcase
84
+ when 'collection'
85
+ 'collection'
86
+ when 'file'
87
+ 'file'
88
+ when 'series', 'subseries'
89
+ 'folder'
90
+ else
91
+ 'container'
92
+ end
93
+ end
94
+
95
+ ##
96
+ # Override of BL core https://github.com/projectblacklight/blacklight/blob/v8.1.0/app/helpers/blacklight/document_helper_behavior.rb#L55
97
+ # Remove document_type check. It isn't a method on arclight documents
98
+ # Check if the document is in the user's bookmarks
99
+ # @param [Blacklight::Document] document
100
+ # @return [Boolean]
101
+ def bookmarked?(document)
102
+ current_bookmarks.any? { |x| x.document_id == document.id }
103
+ end
104
+
105
+ def current_context_document
106
+ @document
107
+ end
108
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # Model the Download links that can be configured (via YAML) for a collection
6
+ # or container
7
+ class DocumentDownloads
8
+ attr_reader :document
9
+
10
+ def initialize(document, id = nil)
11
+ @document = document
12
+ @id = id
13
+ end
14
+
15
+ # Accessor for the ID
16
+ # @return [String]
17
+ def id
18
+ @id || document.unitid
19
+ end
20
+
21
+ # Factory method for the File objects
22
+ # @return [Array<Arclight::DocumentDownloads::File>]
23
+ def files
24
+ data = self.class.config[id] || self.class.config['default']
25
+ disabled = data.delete('disabled')
26
+ return [] if disabled
27
+
28
+ @files ||= data.map do |file_type, file_data|
29
+ self.class.file_class.new(type: file_type, data: file_data, document: document)
30
+ end.compact
31
+ end
32
+
33
+ class << self
34
+ def config
35
+ @config ||= begin
36
+ YAML.safe_load_file(config_filename)
37
+ rescue Errno::ENOENT
38
+ {}
39
+ end
40
+ end
41
+
42
+ def config_filename
43
+ Rails.root.join('config/downloads.yml')
44
+ end
45
+
46
+ # Accessor for the File Class
47
+ # @return [Class]
48
+ def file_class
49
+ Arclight::DocumentDownloads::File
50
+ end
51
+ end
52
+
53
+ ##
54
+ # Model a single file configured in downloads.yml
55
+ class File
56
+ attr_reader :type, :document
57
+
58
+ def initialize(type:, data:, document:)
59
+ @type = type
60
+ @data = data
61
+ @document = document
62
+ end
63
+
64
+ def href
65
+ return data['href'] if data['href']
66
+
67
+ format_template
68
+ end
69
+
70
+ def size
71
+ return data['size'] if data['size']
72
+
73
+ document.public_send(data['size_accessor'].to_sym) if data['size_accessor']
74
+ end
75
+
76
+ private
77
+
78
+ attr_reader :data
79
+
80
+ def format_template
81
+ return unless template
82
+
83
+ template % template_interpolations
84
+ end
85
+
86
+ def template
87
+ data['template']
88
+ end
89
+
90
+ def template_interpolations
91
+ escaped_document_interpolations.merge(custom_interpolations).symbolize_keys
92
+ end
93
+
94
+ def custom_interpolations
95
+ { 'repository_id' => document.repository_config&.slug }
96
+ end
97
+
98
+ def template_variables
99
+ template.scan(Regexp.union(/%{(\w+)}/, /%<(\w+)>/)).flatten.compact.uniq
100
+ end
101
+
102
+ def escaped_document_interpolations
103
+ non_custom_template_variables.index_with do |method_name|
104
+ escape_non_url_doc_value(document.public_send(method_name))
105
+ end
106
+ end
107
+
108
+ def escape_non_url_doc_value(value)
109
+ value = Array.wrap(value).first || '' # Handle single values/arrays and nil
110
+ return value if value.start_with?(/https?:/)
111
+
112
+ CGI.escape(value)
113
+ end
114
+
115
+ def non_custom_template_variables
116
+ template_variables.reject do |v|
117
+ custom_interpolations.keys.include?(v)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # Logic containing information about Solr_Ead "Parent"
6
+ # https://github.com/awead/solr_ead/blob/8cf7ffaa66e0e4c9c0b12f5646d6c2e20984cd99/lib/solr_ead/behaviors.rb#L54-L57
7
+ class Parent
8
+ attr_reader :id, :label, :eadid, :level
9
+
10
+ alias global_id id
11
+ Arclight.deprecation.deprecate_methods(self, global_id: 'Call `id` instead')
12
+
13
+ def initialize(id:, label:, eadid:, level:)
14
+ @id = id
15
+ @label = label
16
+ @eadid = eadid
17
+ @level = level
18
+ end
19
+
20
+ def collection?
21
+ level == 'collection'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # Object for parsing and formalizing Solr_Ead "Parents"
6
+ # https://github.com/awead/solr_ead/blob/8cf7ffaa66e0e4c9c0b12f5646d6c2e20984cd99/lib/solr_ead/behaviors.rb#L54-L57
7
+ class Parents
8
+ attr_reader :ids, :legacy_ids, :labels, :levels
9
+
10
+ def initialize(ids:, legacy_ids:, labels:, eadid:, levels:)
11
+ @ids = ids
12
+ @legacy_ids = legacy_ids
13
+ @labels = labels
14
+ @eadid = eadid
15
+ @levels = levels
16
+ end
17
+
18
+ def eadid
19
+ Arclight::NormalizedId.new(@eadid).to_s
20
+ end
21
+
22
+ ##
23
+ # @return [Array[Arclight::Parent]]
24
+ def as_parents
25
+ (ids.presence || legacy_ids).map.with_index { |id, idx| Arclight::Parent.new(id: id, label: labels[idx], eadid: eadid, level: levels[idx]) }
26
+ end
27
+
28
+ ##
29
+ # @param [SolrDocument] document
30
+ def self.from_solr_document(document)
31
+ ids = document.parent_ids
32
+ legacy_ids = document.legacy_parent_ids.map { |legacy_id| document.root == legacy_id ? legacy_id : "#{document.root}#{legacy_id}" }
33
+ labels = document.parent_labels
34
+ eadid = document.eadid
35
+ levels = document.parent_levels
36
+ new(ids: ids, legacy_ids: legacy_ids, labels: labels, eadid: eadid, levels: levels)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ module Requests
5
+ ##
6
+ # This class should be used to turn configuration into a URL and
7
+ # POST form specifically aimed at Aeon's external request
8
+ # endpoint (https://support.atlas-sys.com/hc/en-us/articles/360011820054-External-Request-Endpoint)
9
+ class AeonExternalRequest
10
+ def initialize(document, presenter)
11
+ @document = document
12
+ @presenter = presenter
13
+ end
14
+
15
+ def config
16
+ @config ||= @document.repository_config.request_config_for_type('aeon_external_request_endpoint')
17
+ end
18
+
19
+ def url
20
+ "#{config['request_url']}?#{url_params}"
21
+ end
22
+
23
+ def form_mapping
24
+ static_mappings.merge(dynamic_mappings)
25
+ end
26
+
27
+ def static_mappings
28
+ config['request_mappings']['static']
29
+ end
30
+
31
+ def dynamic_mappings
32
+ config['request_mappings']['accessor'].transform_values do |v|
33
+ @document.public_send(v.to_sym)
34
+ end
35
+ end
36
+
37
+ def url_params
38
+ config['request_mappings']['url_params'].to_query
39
+ end
40
+ end
41
+ end
42
+ end