diff --git a/app/assets/stylesheets/_base.scss b/app/assets/stylesheets/_base.scss index 46df279..6859400 100644 --- a/app/assets/stylesheets/_base.scss +++ b/app/assets/stylesheets/_base.scss @@ -131,3 +131,11 @@ mark, .mark { margin-bottom: 2rem; padding-top: 1rem; } + +/* Navbars */ + +.sticky-toolbar-top { + position: sticky !important; + top: 0; + z-index: 2; +} diff --git a/app/assets/stylesheets/_collection.scss b/app/assets/stylesheets/_collection.scss index f26f6b0..ba634b4 100644 --- a/app/assets/stylesheets/_collection.scss +++ b/app/assets/stylesheets/_collection.scss @@ -8,12 +8,20 @@ position: relative; margin-bottom: .5rem; border-bottom: solid 1px var.$color-teal-300; - box-shadow: 0px 3px 3px -3px rgba(23, 130, 166, 0.58); - background-image: linear-gradient(to top, var.$gray-cool, var.$gray-white, var.$near-white, var.$color-background-primary); + box-shadow: 0 3px 3px -3px rgba(23, 130, 166, 0.58); + background-image: linear-gradient( + to top, var.$gray-cool, var.$gray-white, var.$near-white, var.$color-background-primary + ); + padding-top: 1rem; + padding-bottom: 0.5rem; .al-request { margin-right: 2rem; - margin-bottom: 1rem; + margin-bottom: 0.5rem; + } + + .btn-primary { + margin-right: 0.5rem; } } @@ -80,3 +88,27 @@ .al-show-actions-box svg.bi { color: var.$color-blue-500; } + +.collection-name-wrapper { + margin-top: 1rem; + position: relative; +} + +.collection-name-wrapper a { + text-decoration: none; + font-weight: 700; + line-height: 1.25; + font-size: 1.35rem; +} + +.al-collection-content-icon { + font-size: 1.5rem; +} + +span.breadcrumb-text { + font-size: 1rem; +} + +.breadcrumb-item + .breadcrumb-item::before { + content: "\00BB"; +} diff --git a/app/assets/stylesheets/_contents.scss b/app/assets/stylesheets/_contents.scss new file mode 100644 index 0000000..26407b2 --- /dev/null +++ b/app/assets/stylesheets/_contents.scss @@ -0,0 +1,55 @@ +@use "variables" as var; + +#contents .documents li { + background-color: unset !important; +} + +.contents-action { + min-width: 2rem; + min-height: 2rem; + + i { + font-size: 2rem; + } +} + +.nested-contents-container { + margin-left: 3rem; +} + +.contents-item-card { + padding: 1rem; + margin-bottom: 1rem; +} + +.blended { + color: var.$color-neutral-100; + opacity: 0.0; +} + +#contents button[aria-expanded="true"] { + i::before { + content: "\F2E6" !important; + } +} + +#contents button[aria-expanded="false"] { + i::before { + content: "\F4FA" !important; + } +} + +.btn-outline-request { + border: 1px solid var.$color-neutral-100; + color: var.$color-blue-400; +} + +.button-placeholder { + padding: 0.875rem; +} + +#about-this-level { + background-color: #f5f5f5; + border-top: 1px dotted #cccccc; + padding: 1rem; +} diff --git a/app/assets/stylesheets/_search_results.scss b/app/assets/stylesheets/_search_results.scss index f66c426..b44de36 100644 --- a/app/assets/stylesheets/_search_results.scss +++ b/app/assets/stylesheets/_search_results.scss @@ -193,10 +193,6 @@ article.document .blacklight-icons.blacklight-icons-online { } } -.truncator button { - display: unset; -} - #content .al-grouped-results .al-grouped-title-bar { background-color: var.$facet-gray; border: 1px solid rgba(0, 0, 0, 0.075); diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 1904a75..ad41cc7 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -3,6 +3,7 @@ @use "banners"; @use "buttons"; @use "card"; +@use "contents"; @use "collection"; @use "context_navigation"; @use "footer"; diff --git a/app/components/um_aeon_request_checkbox_component.html.erb b/app/components/um_aeon_request_checkbox_component.html.erb new file mode 100644 index 0000000..1a11d81 --- /dev/null +++ b/app/components/um_aeon_request_checkbox_component.html.erb @@ -0,0 +1,11 @@ +
+ +
diff --git a/app/components/um_aeon_request_checkbox_component.rb b/app/components/um_aeon_request_checkbox_component.rb new file mode 100644 index 0000000..901874f --- /dev/null +++ b/app/components/um_aeon_request_checkbox_component.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class UmAeonRequestCheckboxComponent < ViewComponent::Base + def initialize(document:) + @document = document + end + + attr_reader :document +end diff --git a/app/components/um_breadcrumbs_hierarchy_component.html.erb b/app/components/um_breadcrumbs_hierarchy_component.html.erb new file mode 100644 index 0000000..bd029fd --- /dev/null +++ b/app/components/um_breadcrumbs_hierarchy_component.html.erb @@ -0,0 +1,20 @@ + diff --git a/app/components/um_breadcrumbs_hierarchy_component.rb b/app/components/um_breadcrumbs_hierarchy_component.rb new file mode 100644 index 0000000..c179f5b --- /dev/null +++ b/app/components/um_breadcrumbs_hierarchy_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class UmBreadcrumbsHierarchyComponent < Arclight::BreadcrumbsHierarchyComponent + def render? + document.present? && !document.collection? + end +end diff --git a/app/components/um_collection_sidebar_component.html.erb b/app/components/um_collection_sidebar_component.html.erb index 0f4b2bd..c769e08 100644 --- a/app/components/um_collection_sidebar_component.html.erb +++ b/app/components/um_collection_sidebar_component.html.erb @@ -11,5 +11,19 @@ %> <% end %> + + \ No newline at end of file diff --git a/app/components/um_contents_document_collection_hierarchy_component.html.erb b/app/components/um_contents_document_collection_hierarchy_component.html.erb new file mode 100644 index 0000000..98b6676 --- /dev/null +++ b/app/components/um_contents_document_collection_hierarchy_component.html.erb @@ -0,0 +1,77 @@ +
  • +
    +
    + <% if document.children? %> + <%= content_tag( + :button, + class: "btn btn-light contents-action", + aria: { label: t("arclight.hierarchy.view_all"), expanded: start_expanded? }, + data: { + bs_toggle: "collapse", + toggle: "collapse", + bs_target: "#collapsible-contents-hierarchy-#{document.id}" + } + ) do %> + + <% end %> + <% else %> + + <% end %> +
    +
    +
    + <% if document.collection_has_requestable_components? and document.is_checkbox_requestable? %> +
    + <%= render UmAeonRequestCheckboxComponent.new(document: document) %> +
    + <% end %> +
    +
    +

    + <% if helpers.is_interesting_component?(document) %> + <%= helpers.link_to_document document %> + <% else %> + <%= presenter.label %> + <% end %> + <% if document.children? %> + + <%= document.number_of_children %> <%= t(:'um_arclight.views.index.number_of_components', count: document.number_of_children) %> + + <% end %> +

    + <%= content_tag("p", "aria-roledescription" => "subtitle") do %> + <% document.extent.each do |extent_chunk| %> + <%= extent_chunk %> + <% end %> + <% end unless document.extent.empty? %> +
    +
    + <%= tag.span class: "al-document-container container-wrapper col-auto" do %> + <%= presenter.document.containers.join(", ") %> + <% end if presenter.document.containers.present? %> + <%= helpers.render_index_doc_actions document, wrapping_class: "col-auto" %> +
    + <%= content_tag('div', class: 'al-document-creator') do %> + <%= document.creator %> + <% end if document.creator %> +
    <%= abstract_or_scope %>
    +
    +
    + <% if document.children? %> + <%= content_tag( + :div, + id: "collapsible-contents-hierarchy-#{document.id}", + class: "collapse #{'show' if start_expanded?}" + ) do %> +
    + <%= helpers.turbo_frame_tag( + "al-hierarchy-#{document.id}-document", + loading: 'lazy', + src: helpers.hierarchy_solr_document_path( + id: document.id, key: '-document', contents: true + ) + ) %> +
    + <% end %> + <% end %> +
  • diff --git a/app/components/um_contents_document_collection_hierarchy_component.rb b/app/components/um_contents_document_collection_hierarchy_component.rb new file mode 100644 index 0000000..612b249 --- /dev/null +++ b/app/components/um_contents_document_collection_hierarchy_component.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class UmContentsDocumentCollectionHierarchyComponent < Arclight::DocumentCollectionHierarchyComponent + def abstract_or_scope + render Arclight::IndexMetadataFieldComponent.new( + field: Blacklight::FieldPresenter.new( + view_context, + document, + Blacklight::Configuration::Field.new( + field: "abstract_or_scope", + key: "abstract_or_scope", + accessor: true, + truncate: true, + helper_method: :render_html_tags + ) + ) + ) + end + + def start_expanded? + document.number_of_children == 1 + end +end diff --git a/app/components/um_document_component.html.erb b/app/components/um_document_component.html.erb index 05086ce..7c20792 100644 --- a/app/components/um_document_component.html.erb +++ b/app/components/um_document_component.html.erb @@ -1,4 +1,4 @@ -
    +
    <%= render 'arclight/requests', document: document %>
    @@ -6,6 +6,8 @@ <%= render document.repository_config %>
    +<%= render breadcrumb_component.new(presenter: presenter) %> +
    <%= content_tag :h1 do %> <%= document.normalized_title %> @@ -32,10 +34,18 @@ metadata_attr: { layout: UmUpperMetadataLayoutComponent }, presenter: presenter, heading: true, classes: "") %>
    -<% elsif document.children? %> +<% end %> + +<% if document.children? %>

    <%= t 'arclight.views.show.contents' %>

    - <%= helpers.turbo_frame_tag "al-hierarchy-#{document.id}-document", loading: 'lazy', src: helpers.hierarchy_solr_document_path(id: document.id, paginate: true, key: '-document', per_page: 50) %> + <%= helpers.turbo_frame_tag( + "al-hierarchy-#{document.id}-document", + loading: 'lazy', + src: helpers.hierarchy_solr_document_path( + id: document.id, key: '-document', paginate: true, contents: true + ) + ) %>
    <% end %> diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 969b669..6772e97 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -83,7 +83,7 @@ class CatalogController < ApplicationController # config.show.title_field = 'title_display' config.show.document_component = UmDocumentComponent config.show.sidebar_component = UmSidebarComponent - config.show.breadcrumb_component = Arclight::BreadcrumbsHierarchyComponent + config.show.breadcrumb_component = UmBreadcrumbsHierarchyComponent config.show.embed_component = Arclight::EmbedComponent config.show.access_component = UmAccessComponent config.show.online_status_component = Arclight::OnlineStatusIndicatorComponent @@ -96,7 +96,6 @@ class CatalogController < ApplicationController background_field related_field indexed_terms_field - access_field ] config.show.collection_access_items = %i[ diff --git a/app/helpers/um_arclight_helper.rb b/app/helpers/um_arclight_helper.rb index 3a915bb..cbb0a5d 100644 --- a/app/helpers/um_arclight_helper.rb +++ b/app/helpers/um_arclight_helper.rb @@ -10,4 +10,21 @@ def formatted_last_indexed(timestamp) date = DateTime.parse(timestamp) date.strftime("%F") end + + # Keys are the display keys registered in the Blacklight config. + # Fields are the Solr document keys associated with the display keys. + SKIPPABLE_KEYS = [ + "physdesc", + "creators", + "abstract", + "scopecontent", + "extent", + "note", + "odd" + ] + def is_interesting_component?(document) + blacklight_config.component_fields.find do |key, field_config| + SKIPPABLE_KEYS.exclude?(field_config.key) && document.fetch(field_config.field, nil).present? + end || document.is_linkable? + end end diff --git a/app/javascript/aeonform.js b/app/javascript/aeonform.js new file mode 100644 index 0000000..c9dd75b --- /dev/null +++ b/app/javascript/aeonform.js @@ -0,0 +1,251 @@ +/* aeonform.js */ + +const CHECKBOX_SELECTOR = 'input[type="checkbox"][name="Request"]'; +const FORM_ID = "EADRequestFormId"; +const FALLBACK_BUCKET = "null-collection"; + +const state = { + collectionId: null, + selected: new Set(), + items: new Map(), +}; + +const storageKeys = () => ({ + selected: `${state.collectionId}_selectedItems`, + items: `${state.collectionId}_collectionItems`, +}); + +function collectionIdFromIdentifier(identifier) { + const idx = identifier?.indexOf("_aspace_") ?? -1; + return idx > 0 ? identifier.slice(0, idx) : null; +} + +function currentCollectionId() { + const idno = document.querySelector(`#${FORM_ID} input[name="idno"]`)?.value; + if (idno) return idno; + + const checkbox = document.querySelector(CHECKBOX_SELECTOR); + const fromCheckbox = checkbox && collectionIdFromIdentifier(checkbox.value); + if (fromCheckbox) return fromCheckbox; + + const doc = document.querySelector("[data-document-id]"); + const fromDoc = doc && collectionIdFromIdentifier(doc.dataset.documentId); + if (fromDoc) return fromDoc; + + if (checkbox) { + console.warn( + "aeonform: Request checkboxes present but no collection could be resolved; " + + "using shared fallback bucket." + ); + } + return FALLBACK_BUCKET; +} + +function assertSingleCollection() { + const ids = new Set( + [...document.querySelectorAll(CHECKBOX_SELECTOR)] + .map((cb) => collectionIdFromIdentifier(cb.value)) + .filter(Boolean) + ); + if (ids.size > 1) { + console.warn("aeonform: page mixes collections:", [...ids]); + } +} + +function readJSON(key, fallback) { + try { + return JSON.parse(sessionStorage.getItem(key)) ?? fallback; + } catch { + return fallback; + } +} + +function loadState() { + state.collectionId = currentCollectionId(); + const keys = storageKeys(); + state.selected = new Set(readJSON(keys.selected, [])); + state.items = new Map(Object.entries(readJSON(keys.items, {}))); +} + +let warnedPersistenceFailure = false; +function saveState() { + const keys = storageKeys(); + try { + sessionStorage.setItem(keys.selected, JSON.stringify([...state.selected])); + sessionStorage.setItem(keys.items, JSON.stringify(Object.fromEntries(state.items))); + } catch { + // Continue with in-memory state when session storage is unavailable. + if (!warnedPersistenceFailure) { + warnedPersistenceFailure = true; + console.warn("aeonform: sessionStorage write failed; selections won't persist across navigation."); + } + } +} + +function ensureCurrentCollection() { + if (state.collectionId !== currentCollectionId()) { + loadState(); + restoreCheckboxes(); + updateCount(); + } +} + +function harvestItemMetadata() { + let dirty = false; + + document.querySelectorAll(CHECKBOX_SELECTOR).forEach((checkbox) => { + const identifier = checkbox.value; + if (state.items.has(identifier)) return; + + const suffix = `_${identifier}`; + const metadata = {}; + checkbox.closest("label")?.querySelectorAll("input").forEach((input) => { + if (input.name && input.name.endsWith(suffix)) { + metadata[input.name] = input.value; + } + }); + + state.items.set(identifier, metadata); + dirty = true; + }); + + if (dirty) saveState(); +} + +function updateCount() { + const span = document.getElementById("selected-items-count"); + if (!span) return; + + const count = state.selected.size; + span.innerHTML = count > 0 + ? `${count}` + `Request ${count} ${count === 1 ? "item" : "items"}` + : "" +} + +function restoreCheckboxes(root = document) { + root.querySelectorAll(CHECKBOX_SELECTOR).forEach((checkbox) => { + checkbox.checked = state.selected.has(checkbox.value); + }); +} + +function clearAll() { + document.querySelectorAll(CHECKBOX_SELECTOR).forEach((cb) => { cb.checked = false; }); + state.selected.clear(); + state.items.clear(); + saveState(); + updateCount(); +} + +function hiddenInput(name, value) { + const input = document.createElement("input"); + input.type = "hidden"; + input.name = name; + input.value = value; + return input; +} + +function submitAeonRequest(sourceForm) { + ensureCurrentCollection(); + + if (state.selected.size === 0) { + alert( + "Please select one or more items to request.\n" + + "(You may need to scroll down to see the checkboxes)" + ); + return; + } + + document.querySelectorAll(".aeon-submitted-form").forEach((f) => f.remove()); + + const form = sourceForm.cloneNode(true); + form.id = `${FORM_ID}-${Date.now()}`; + form.classList.add("aeon-submitted-form"); + form.hidden = true; + form.querySelectorAll("[id]").forEach((el) => el.removeAttribute("id")); + + // Avoids duplicate/stale request values). + form.querySelectorAll('input[name="Request"]').forEach((el) => el.remove()); + + state.selected.forEach((identifier) => { + form.append(hiddenInput("Request", identifier)); + Object.entries(state.items.get(identifier) ?? {}).forEach(([name, value]) => { + form.append(hiddenInput(name, value)); + }); + }); + + document.body.append(form); + form.submit(); + clearAll(); +} + +document.addEventListener("change", (event) => { + const checkbox = event.target.closest?.(CHECKBOX_SELECTOR); + if (!checkbox) return; + + ensureCurrentCollection(); + harvestItemMetadata(); + + if (checkbox.checked) { + state.selected.add(checkbox.value); + } else { + state.selected.delete(checkbox.value); + } + + saveState(); + updateCount(); +}); + +document.addEventListener("submit", (event) => { + const form = event.target; + if (form.id !== FORM_ID) return; + + event.preventDefault(); + submitAeonRequest(form); +}); + +document.addEventListener("click", (event) => { + if (event.target.closest("[data-aeon-submit]")) { + event.preventDefault(); + document.getElementById(FORM_ID)?.requestSubmit(); + return; + } + if (event.target.closest("[data-aeon-clear]")) { + event.preventDefault(); + clearAll(); + } +}); + +function initialize() { + loadState(); + assertSingleCollection(); + harvestItemMetadata(); + restoreCheckboxes(); + updateCount(); +} + +document.addEventListener("turbo:load", initialize); + +if (document.readyState !== "loading") { + initialize(); +} else { + document.addEventListener("DOMContentLoaded", initialize); +} + +document.addEventListener("turbo:render", () => { + ensureCurrentCollection(); + restoreCheckboxes(); + updateCount(); +}); + +document.addEventListener("turbo:before-cache", () => { + const span = document.getElementById("selected-items-count"); + if (span) span.innerHTML = ""; + document.querySelectorAll(CHECKBOX_SELECTOR).forEach((cb) => { cb.checked = false; }); +}); + +document.addEventListener("turbo:frame-load", (event) => { + ensureCurrentCollection(); + harvestItemMetadata(); + restoreCheckboxes(event.target); + updateCount(); +}); diff --git a/app/javascript/application.js b/app/javascript/application.js index 50b93b4..011f78d 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -4,7 +4,8 @@ import "controllers" import * as bootstrap from "bootstrap" import githubAutoCompleteElement from "@github/auto-complete-element" import Blacklight from "blacklight" - import "arclight" +import "aeonform" import BlacklightRangeLimit from "blacklight-range-limit"; BlacklightRangeLimit.init({onLoadHandler: Blacklight.onLoad }); + diff --git a/app/javascript/arclight/truncate_controller.js b/app/javascript/arclight/truncate_controller.js new file mode 100644 index 0000000..817417e --- /dev/null +++ b/app/javascript/arclight/truncate_controller.js @@ -0,0 +1,25 @@ +import { Controller } from '@hotwired/stimulus' +export default class extends Controller { + static targets = ['content'] + connect() { + this._check = this.checkTruncation.bind(this) + requestAnimationFrame(this._check) + window.addEventListener('resize', this._check) + } + disconnect() { + window.removeEventListener('resize', this._check) + } + checkTruncation() { + const el = this.contentTarget + const isTruncated = el.scrollHeight > el.clientHeight + 1 + if (isTruncated) { + this.element.classList.add('truncated') + } else { + this.element.classList.remove('truncated') + this.element.classList.remove('expanded') + } + } + trigger() { + this.element.classList.toggle('expanded') + } +} diff --git a/app/models/solr_document.rb b/app/models/solr_document.rb index d9264fb..f88c6ef 100644 --- a/app/models/solr_document.rb +++ b/app/models/solr_document.rb @@ -14,6 +14,10 @@ class SolrDocument # Recommendation: Use field names from Dublin Core use_extension(Blacklight::Document::DublinCore) + def component? + parent_ids.present? + end + def physloc fetch("collection_physloc_tesim", [])[0] end @@ -31,14 +35,75 @@ def repository_id end def has_online_content? - fetch("has_online_content_ssim", [])[0] + online_content? end def restrictions fetch("accessrestrict_html_tesm", [])[0] end + def accessrestrict + fetch("accessrestrict_tesim", []) + end + + def userestrict + fetch("userestrict_tesim", []) + end + + def phystech + fetch("phystech_tesim", []) + end + + def restricted_component? + component? && (accessrestrict.present? || userestrict.present? || phystech.present?) + end + def document_id fetch("ead_ssi", nil)&.strip end + + def is_linkable? + online_content? || number_of_children > 0 || restricted_component? + end + + def collection_has_requestable_components? + repository_config.request_config_present_for_type?("aeon_hidden_form_request") + end + + def container_types + fetch("container_types_ssim", []) + end + + def is_checkbox_requestable? + config_present = repository_config.request_config_present_for_type?("aeon_hidden_form_request") + container_requestable = container_types.any? do |container_type| + %w[box folder reel map-case tube object volume bundle].any? do |type| + container_type.casecmp(type) == 0 + end + end + config_present && !container_types.empty? && container_requestable + end + + # TODO: should the Aeon stuff live in a separate class? + def aeon_item_sub_title_value + subtitle = ActionController::Base.helpers.strip_tags(normalized_title) + subtitle += " (#{extent.join(", ")})" if extent + subtitle + end + + def aeon_item_sub_title_visually_hidden + aeon_item_sub_title_value + end + + def aeon_item_volume_value + containers.join(", ") + end + + def aeon_item_citation_value + reference # May need to fall back to the ID + end + + def aeon_item_info_1_value + accessrestrict + end end diff --git a/app/views/arclight/requests/_aeon_hidden_form_request.html.erb b/app/views/arclight/requests/_aeon_hidden_form_request.html.erb index b48edad..c70e315 100644 --- a/app/views/arclight/requests/_aeon_hidden_form_request.html.erb +++ b/app/views/arclight/requests/_aeon_hidden_form_request.html.erb @@ -18,6 +18,6 @@ <% end %> - - + + diff --git a/app/views/catalog/hierarchy.html.erb b/app/views/catalog/hierarchy.html.erb index 87b40ef..c5a0e13 100644 --- a/app/views/catalog/hierarchy.html.erb +++ b/app/views/catalog/hierarchy.html.erb @@ -8,6 +8,12 @@ blacklight_config: blacklight_config, nest_path: params[:nest_path]) %> + <% elsif params[:contents].present? %> + <% else %> <%= render Arclight::DocumentCollectionContextComponent.with_collection(presenters, diff --git a/config/importmap.rb b/config/importmap.rb index 6f29700..dc7d2fa 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -12,10 +12,11 @@ pin "arclight", to: "arclight/arclight.js" # TODO: We may be able to move these to a single importmap for arclight. pin "arclight/oembed_controller", to: "arclight/oembed_controller.js" -pin "arclight/truncate_controller", to: "arclight/truncate_controller.js" # chart.js is dependency of blacklight-range-limit, currently is not working # as vendored importmaps, but instead must be pinned to CDN. You may want to update # versions perioidically. pin "chart.js", to: "https://ga.jspm.io/npm:chart.js@4.2.0/dist/chart.js" # single dependency of chart.js: pin "@kurkle/color", to: "https://ga.jspm.io/npm:@kurkle/color@0.3.2/dist/color.esm.js" +pin "aeonform" +pin "arclight/truncate_controller", to: "arclight/truncate_controller.js" diff --git a/lib/um_arclight/traject/ead2_component_config.rb b/lib/um_arclight/traject/ead2_component_config.rb index a738ad9..d79e214 100644 --- a/lib/um_arclight/traject/ead2_component_config.rb +++ b/lib/um_arclight/traject/ead2_component_config.rb @@ -299,6 +299,12 @@ to_field "#{selector}_tesim", extract_xpath("./did/#{selector}") end +to_field "container_types_ssim" do |record, accumulator| + record.xpath("./did/container[normalize-space(@type)]").each do |node| + accumulator << node["type"] + end +end + # ============================= # Each component child document # diff --git a/lib/um_arclight/traject/ead2_config.rb b/lib/um_arclight/traject/ead2_config.rb index f8a66d9..964715d 100644 --- a/lib/um_arclight/traject/ead2_config.rb +++ b/lib/um_arclight/traject/ead2_config.rb @@ -304,6 +304,21 @@ to_field "authors_creators_tesim", extract_xpath("/ead/eadheader/filedesc/titlestmt//author") # search plain text to_field "authors_creators_ssm", extract_xpath("/ead/eadheader/filedesc/titlestmt//author", to_text: false) # show render_html_tags +# Used for evaluating xpath components to find +class NokogiriXpathExtensions + def is_component(node_set) + node_set.find_all do |node| + component_elements = (1..12).map { |i| "c#{'%02d' % i}" } + component_elements.push "c" + component_elements.include? node.name + end + end +end + +to_field "child_component_count_isi" do |record, accumulator| + accumulator << record.xpath("/ead/archdesc/dsc/*[is_component(.)]", NokogiriXpathExtensions.new).count +end + # ============================= # Each component child document #