From 1a45f420ef7d75543caad4bde48d680352ee9eb9 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 26 Aug 2026 16:56:07 -0400 Subject: [PATCH 01/33] Revise native oembed_controller.js to handle Kaltura embeds --- app/javascript/arclight/oembed_controller.js | 32 ++++++++++++++++++++ config/importmap.rb | 4 ++- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 app/javascript/arclight/oembed_controller.js diff --git a/app/javascript/arclight/oembed_controller.js b/app/javascript/arclight/oembed_controller.js new file mode 100644 index 00000000..e0de530b --- /dev/null +++ b/app/javascript/arclight/oembed_controller.js @@ -0,0 +1,32 @@ +import { Controller } from '@hotwired/stimulus' + +const PARTNER_ID = '1758271' +const UICONF_ID = '56928822' +const PLAYER_HOST = 'https://cdnapisec.kaltura.com' + +export default class OembedController extends Controller { + static values = { + url: String + } + + connect() { + if (this.element.getAttribute('loaded') === 'loaded') return + + const entryId = this.urlValue.match(/(\d_[a-z0-9]{8})/)?.[1] + if (!entryId) { + console.warn(`Could not parse Kaltura entry id from ${this.urlValue}`) + return // leave the original link markup as the fallback + } + + const iframe = document.createElement('iframe') + iframe.src = `${PLAYER_HOST}/p/${PARTNER_ID}/embedPlaykitJs/uiconf_id/${UICONF_ID}?iframeembed=true&entry_id=${entryId}` + iframe.setAttribute('allowfullscreen', '') + iframe.setAttribute('allow', 'autoplay; fullscreen; encrypted-media') + iframe.setAttribute('title', this.element.querySelector('a')?.textContent || 'Media player') + iframe.style.cssText = 'width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0;' + + // Instead of replaceChildren(iframe), append and keep the link: + this.element.querySelector('.al-digital-object')?.insertAdjacentElement('beforebegin', iframe) + this.element.setAttribute('loaded', 'loaded') + } +} diff --git a/config/importmap.rb b/config/importmap.rb index dc7d2fa9..9674b3a9 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -11,7 +11,7 @@ pin "bootstrap", to: "https://ga.jspm.io/npm:bootstrap@5.3.2/dist/js/bootstrap.js" 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/oembed_controller", to: "arclight/oembed_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. @@ -20,3 +20,5 @@ 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" +pin "arclight/oembed_controller", to: "arclight/oembed_controller.js" +pin "arclight/iiif_controller", to: "arclight/iiif_controller.js" From 264bd12910eeb3bac3a17ccd0d06ef083bdaecac Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Fri, 28 Aug 2026 10:34:37 -0400 Subject: [PATCH 02/33] Refactored .js for video embed with better guards and fallbacks --- app/javascript/arclight/oembed_controller.js | 137 +++++++++++++++---- 1 file changed, 112 insertions(+), 25 deletions(-) diff --git a/app/javascript/arclight/oembed_controller.js b/app/javascript/arclight/oembed_controller.js index e0de530b..2c1d3a91 100644 --- a/app/javascript/arclight/oembed_controller.js +++ b/app/javascript/arclight/oembed_controller.js @@ -3,30 +3,117 @@ import { Controller } from '@hotwired/stimulus' const PARTNER_ID = '1758271' const UICONF_ID = '56928822' const PLAYER_HOST = 'https://cdnapisec.kaltura.com' +const ENTRY_ID_PATTERN = /^\d_[a-z0-9]{8}$/i + +// Kaltura-backed hosts (kaltura.com and MediaSpace instances) +const KALTURA_HOST_SUFFIXES = ['.kaltura.com', '.mivideo.it.umich.edu'] export default class OembedController extends Controller { - static values = { - url: String - } - - connect() { - if (this.element.getAttribute('loaded') === 'loaded') return - - const entryId = this.urlValue.match(/(\d_[a-z0-9]{8})/)?.[1] - if (!entryId) { - console.warn(`Could not parse Kaltura entry id from ${this.urlValue}`) - return // leave the original link markup as the fallback - } - - const iframe = document.createElement('iframe') - iframe.src = `${PLAYER_HOST}/p/${PARTNER_ID}/embedPlaykitJs/uiconf_id/${UICONF_ID}?iframeembed=true&entry_id=${entryId}` - iframe.setAttribute('allowfullscreen', '') - iframe.setAttribute('allow', 'autoplay; fullscreen; encrypted-media') - iframe.setAttribute('title', this.element.querySelector('a')?.textContent || 'Media player') - iframe.style.cssText = 'width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0;' - - // Instead of replaceChildren(iframe), append and keep the link: - this.element.querySelector('.al-digital-object')?.insertAdjacentElement('beforebegin', iframe) - this.element.setAttribute('loaded', 'loaded') - } -} + static values = { + url: String + } + + connect() { + if (this.element.getAttribute('loaded') === 'loaded') return + + const entryId = this.findKalturaEntryId() + if (entryId) { + this.loadKalturaEmbed(entryId) + return + } + + this.loadOEmbed() + } + + findKalturaEntryId() { + let url + + try { + url = new URL(this.urlValue, window.location.href) + } catch (error) { + console.warn(`Invalid oEmbed URL: ${this.urlValue}`, error) + return null + } + + const isKalturaHost = url.hostname === 'kaltura.com' + || KALTURA_HOST_SUFFIXES.some((suffix) => url.hostname.endsWith(suffix)) + if (!isKalturaHost) return null + + const queryEntryId = url.searchParams.get('entry_id') + if (queryEntryId && ENTRY_ID_PATTERN.test(queryEntryId)) { + return queryEntryId + } + + return url.pathname + .match(/(?:^|\/)(\d_[a-z0-9]{8})(?:\/|$)/i)?.[1] || null + } + + loadKalturaEmbed(entryId) { + const linkContainer = this.element.querySelector('.al-digital-object') + if (!linkContainer) { + console.warn('Could not find .al-digital-object for Kaltura embed') + return + } + + const params = new URLSearchParams({ + iframeembed: 'true', + entry_id: entryId + }) + + const iframe = document.createElement('iframe') + iframe.src = `${PLAYER_HOST}/p/${PARTNER_ID}/embedPlaykitJs/uiconf_id/${UICONF_ID}?${params}` + iframe.setAttribute('allowfullscreen', '') + iframe.setAttribute('allow', 'autoplay; fullscreen; encrypted-media') + iframe.setAttribute( + 'title', + this.element.querySelector('a')?.textContent?.trim() || 'Media player' + ) + iframe.style.cssText = 'width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0;' + + // Keep the original link as a fallback. + linkContainer.insertAdjacentElement('beforebegin', iframe) + this.element.setAttribute('loaded', 'loaded') + } + + loadOEmbed() { + fetch(this.urlValue) + .then((response) => { + if (response.ok) return response.text() + throw new Error(`HTTP error, status = ${response.status}`) + }) + .then((body) => { + const endpoint = this.findOEmbedEndpoint(body) + if (!endpoint) { + console.warn(`No oEmbed endpoint found in at ${this.urlValue}`) + return null + } + + return fetch(endpoint) + }) + .then((response) => { + if (!response) return null + if (response.ok) return response.json() + throw new Error(`HTTP error, status = ${response.status}`) + }) + .then((json) => { + if (!json?.html) return + + this.element.innerHTML = json.html + this.element.setAttribute('loaded', 'loaded') + }) + .catch((error) => { + console.error(error) + }) + } + + findOEmbedEndpoint(body) { + const doc = new DOMParser().parseFromString(body, 'text/html') + const endpoint = doc + .querySelector('link[rel="alternate"][type="application/json+oembed"]') + ?.getAttribute('href') + + if (!endpoint) return null + + return new URL(endpoint, this.urlValue).toString() + } +} \ No newline at end of file From 1a282c132bcf820356859a96345cc35753a61e17 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Fri, 28 Aug 2026 10:36:27 -0400 Subject: [PATCH 03/33] External link icon for Kaltura URL, border and spacing between sections --- app/assets/stylesheets/_contents.scss | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/assets/stylesheets/_contents.scss b/app/assets/stylesheets/_contents.scss index 9b57af5f..26b2dda7 100644 --- a/app/assets/stylesheets/_contents.scss +++ b/app/assets/stylesheets/_contents.scss @@ -61,3 +61,30 @@ .card .al-online-content-icon .blacklight-icons svg { margin-top: -0.2rem; } + +.al-show-sub-heading { + font-size: 1.5rem; + text-transform: unset; + color: #555555; + border-top: 1px dotted #e2e2e2; + padding-top: 1.5rem; + margin-top: 1rem; +} + +.al-digital-object a::after { + content: ""; + display: inline-block; + width: 1em; + height: 1em; + margin-left: 0.25rem; + vertical-align: -0.125em; + background-color: currentColor; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: contain; + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0z'/%3E%3C/svg%3E"); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0z'/%3E%3C/svg%3E"); +} From 47d92ed352166b1bfd5665d12d1d37d640b095b4 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 2 Sep 2026 17:41:50 -0400 Subject: [PATCH 04/33] Remove .al-show-sub-heading, it's too broad a target --- app/assets/stylesheets/_contents.scss | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/assets/stylesheets/_contents.scss b/app/assets/stylesheets/_contents.scss index 26b2dda7..91c8d02f 100644 --- a/app/assets/stylesheets/_contents.scss +++ b/app/assets/stylesheets/_contents.scss @@ -62,15 +62,6 @@ margin-top: -0.2rem; } -.al-show-sub-heading { - font-size: 1.5rem; - text-transform: unset; - color: #555555; - border-top: 1px dotted #e2e2e2; - padding-top: 1.5rem; - margin-top: 1rem; -} - .al-digital-object a::after { content: ""; display: inline-block; From 768593b2b05116c781e8162a0470831a6994596c Mon Sep 17 00:00:00 2001 From: Greg Kostin Date: Wed, 26 Aug 2026 11:09:13 -0400 Subject: [PATCH 05/33] move ead2002 directory to root of project --- data/ead/.keep | 0 {data/ead2002 => ead2002}/dtd2schema.xsl | 0 {data/ead2002 => ead2002}/ead.dtd | 0 {data/ead2002 => ead2002}/ead2002.xsd | 0 {data/ead2002 => ead2002}/related_optional/eadlocal/eadlocal.ent | 0 {data/ead2002 => ead2002}/related_optional/sgml/eadcatalog | 0 {data/ead2002 => ead2002}/related_optional/sgml/eadsgml.dcl | 0 .../related_optional/xml/charentities/iso-cyr1.ent | 0 .../related_optional/xml/charentities/iso-cyr2.ent | 0 .../related_optional/xml/charentities/iso-dia.ent | 0 .../related_optional/xml/charentities/iso-grk1.ent | 0 .../related_optional/xml/charentities/iso-grk2.ent | 0 .../related_optional/xml/charentities/iso-grk3.ent | 0 .../related_optional/xml/charentities/iso-grk4.ent | 0 .../related_optional/xml/charentities/iso-lat1.ent | 0 .../related_optional/xml/charentities/iso-lat2.ent | 0 .../related_optional/xml/charentities/iso-num.ent | 0 .../related_optional/xml/charentities/iso-pub.ent | 0 .../related_optional/xml/charentities/iso-tech.ent | 0 {data/ead2002 => ead2002}/related_optional/xml/eadcatalog.xml | 0 20 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 data/ead/.keep rename {data/ead2002 => ead2002}/dtd2schema.xsl (100%) rename {data/ead2002 => ead2002}/ead.dtd (100%) rename {data/ead2002 => ead2002}/ead2002.xsd (100%) rename {data/ead2002 => ead2002}/related_optional/eadlocal/eadlocal.ent (100%) rename {data/ead2002 => ead2002}/related_optional/sgml/eadcatalog (100%) rename {data/ead2002 => ead2002}/related_optional/sgml/eadsgml.dcl (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-cyr1.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-cyr2.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-dia.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-grk1.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-grk2.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-grk3.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-grk4.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-lat1.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-lat2.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-num.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-pub.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/charentities/iso-tech.ent (100%) rename {data/ead2002 => ead2002}/related_optional/xml/eadcatalog.xml (100%) diff --git a/data/ead/.keep b/data/ead/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/data/ead2002/dtd2schema.xsl b/ead2002/dtd2schema.xsl similarity index 100% rename from data/ead2002/dtd2schema.xsl rename to ead2002/dtd2schema.xsl diff --git a/data/ead2002/ead.dtd b/ead2002/ead.dtd similarity index 100% rename from data/ead2002/ead.dtd rename to ead2002/ead.dtd diff --git a/data/ead2002/ead2002.xsd b/ead2002/ead2002.xsd similarity index 100% rename from data/ead2002/ead2002.xsd rename to ead2002/ead2002.xsd diff --git a/data/ead2002/related_optional/eadlocal/eadlocal.ent b/ead2002/related_optional/eadlocal/eadlocal.ent similarity index 100% rename from data/ead2002/related_optional/eadlocal/eadlocal.ent rename to ead2002/related_optional/eadlocal/eadlocal.ent diff --git a/data/ead2002/related_optional/sgml/eadcatalog b/ead2002/related_optional/sgml/eadcatalog similarity index 100% rename from data/ead2002/related_optional/sgml/eadcatalog rename to ead2002/related_optional/sgml/eadcatalog diff --git a/data/ead2002/related_optional/sgml/eadsgml.dcl b/ead2002/related_optional/sgml/eadsgml.dcl similarity index 100% rename from data/ead2002/related_optional/sgml/eadsgml.dcl rename to ead2002/related_optional/sgml/eadsgml.dcl diff --git a/data/ead2002/related_optional/xml/charentities/iso-cyr1.ent b/ead2002/related_optional/xml/charentities/iso-cyr1.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-cyr1.ent rename to ead2002/related_optional/xml/charentities/iso-cyr1.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-cyr2.ent b/ead2002/related_optional/xml/charentities/iso-cyr2.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-cyr2.ent rename to ead2002/related_optional/xml/charentities/iso-cyr2.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-dia.ent b/ead2002/related_optional/xml/charentities/iso-dia.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-dia.ent rename to ead2002/related_optional/xml/charentities/iso-dia.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-grk1.ent b/ead2002/related_optional/xml/charentities/iso-grk1.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-grk1.ent rename to ead2002/related_optional/xml/charentities/iso-grk1.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-grk2.ent b/ead2002/related_optional/xml/charentities/iso-grk2.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-grk2.ent rename to ead2002/related_optional/xml/charentities/iso-grk2.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-grk3.ent b/ead2002/related_optional/xml/charentities/iso-grk3.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-grk3.ent rename to ead2002/related_optional/xml/charentities/iso-grk3.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-grk4.ent b/ead2002/related_optional/xml/charentities/iso-grk4.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-grk4.ent rename to ead2002/related_optional/xml/charentities/iso-grk4.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-lat1.ent b/ead2002/related_optional/xml/charentities/iso-lat1.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-lat1.ent rename to ead2002/related_optional/xml/charentities/iso-lat1.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-lat2.ent b/ead2002/related_optional/xml/charentities/iso-lat2.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-lat2.ent rename to ead2002/related_optional/xml/charentities/iso-lat2.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-num.ent b/ead2002/related_optional/xml/charentities/iso-num.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-num.ent rename to ead2002/related_optional/xml/charentities/iso-num.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-pub.ent b/ead2002/related_optional/xml/charentities/iso-pub.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-pub.ent rename to ead2002/related_optional/xml/charentities/iso-pub.ent diff --git a/data/ead2002/related_optional/xml/charentities/iso-tech.ent b/ead2002/related_optional/xml/charentities/iso-tech.ent similarity index 100% rename from data/ead2002/related_optional/xml/charentities/iso-tech.ent rename to ead2002/related_optional/xml/charentities/iso-tech.ent diff --git a/data/ead2002/related_optional/xml/eadcatalog.xml b/ead2002/related_optional/xml/eadcatalog.xml similarity index 100% rename from data/ead2002/related_optional/xml/eadcatalog.xml rename to ead2002/related_optional/xml/eadcatalog.xml From 01ef8ada87aaeed1e82882c7d3d0448923f1d53b Mon Sep 17 00:00:00 2001 From: Greg Kostin Date: Wed, 26 Aug 2026 11:10:27 -0400 Subject: [PATCH 06/33] keep empty data directory in project root --- data/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/.keep diff --git a/data/.keep b/data/.keep new file mode 100644 index 00000000..e69de29b From ec0716c571cec4ee79c288f99ba6e31c3b529226 Mon Sep 17 00:00:00 2001 From: Greg Kostin Date: Wed, 26 Aug 2026 12:35:48 -0400 Subject: [PATCH 07/33] changes to compose --- compose.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/compose.yml b/compose.yml index 93549f64..3ce21c58 100644 --- a/compose.yml +++ b/compose.yml @@ -19,7 +19,8 @@ services: env_file: - .env volumes: - - finding-aid-data:/rails/data + - .:/rails + - ./data:/rails/data redis: image: redis:7 healthcheck: @@ -35,6 +36,8 @@ services: command: redis-server --appendonly yes resque: depends_on: + solr: + condition: service_healthy redis: condition: service_healthy build: @@ -48,7 +51,8 @@ services: - .env command: bundle exec rake environment resque:work volumes: - - finding-aid-data:/rails/data + - .:/rails + - ./data:/rails/data resque-web: depends_on: redis: @@ -101,8 +105,8 @@ services: ZOO_MY_ID: 1 ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181 volumes: + data: solr-data: redis-data: zk-data: zk-datalog: - finding-aid-data: From 908b7ea2e4d8b51a25af8dfa44167c1079ac1093 Mon Sep 17 00:00:00 2001 From: Greg Kostin Date: Wed, 26 Aug 2026 13:47:46 -0400 Subject: [PATCH 08/33] changes to read me --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ef35542..6e48228c 100644 --- a/README.md +++ b/README.md @@ -29,17 +29,39 @@ Use hybrid for quick rebuilds of Rails (outside a container) without repeatedly ##### Services shell ```shell +# Build resque-web and resque images docker compose build +# Start zookeeper, solr, redis, resque and resque-web containers docker compose up ``` -##### Command shell +##### Rails shell ```shell # Create the blacklight-collection /bin/bash ./solr/dev-init.sh -# Recompiles Rails and connects it to Solr container +# Bundle install the gems outside of the container +bundle install +# Migrate the database and prepare it for GUI finding aid indexing +# NOTE: This command is run via bin/docker-entrypoint when resque is brought up +# bin/rails db:prepare +# Development Rails server using Solr container SOLR_URL=http://localhost:8983/solr/blacklight-collection bin/dev ``` +##### Command shell +```shell +# List all available tasks +bin/rails --task +``` +```shell +# Index sample-ead +rsync -av --progress sample-ead/ data/ead/ +FINDING_AID_DATA=./data bin/rails arclight:ingest_everything +``` +##### Browser Resque Web +```shell +# Open http://localhost:5678 in your browser +``` + ### Troubleshooting If you encounter this indexing error post-Boxrunner merge ``` From a6511e7e3a634fa84a1de32a0b27a7a1f5625963 Mon Sep 17 00:00:00 2001 From: Greg Kostin Date: Wed, 26 Aug 2026 13:58:35 -0400 Subject: [PATCH 09/33] use Rails development environment --- README.md | 3 +++ compose.yml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/README.md b/README.md index 6e48228c..52db0ff8 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,9 @@ bundle install # Development Rails server using Solr container SOLR_URL=http://localhost:8983/solr/blacklight-collection bin/dev ``` +##### Browser Resque Web +Open http://localhost:5678 in your browser + ##### Command shell ```shell # List all available tasks diff --git a/compose.yml b/compose.yml index 3ce21c58..e8cb4989 100644 --- a/compose.yml +++ b/compose.yml @@ -43,6 +43,7 @@ services: build: context: . environment: + - RAILS_ENV=development - SOLR_URL=http://solr:8983/solr/blacklight-collection - REDIS_URL=redis://redis:6379/0 - QUEUE=* @@ -60,6 +61,7 @@ services: build: context: . environment: + - RAILS_ENV=development - REDIS_URL=redis://redis:6379/0 - RESQUE_NAMESPACE=resque:boxwalker env_file: From f80e469dcaff5a65b8b4cf6912623b06c21eed07 Mon Sep 17 00:00:00 2001 From: Greg Kostin Date: Wed, 26 Aug 2026 15:53:57 -0400 Subject: [PATCH 10/33] changes --- Dockerfile | 4 ++++ README.md | 4 ---- compose.yml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index fcf1badc..86053201 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,10 @@ ARG RUBY_VERSION=3.4.9 FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y procps && \ + rm -rf /var/lib/apt/lists/* + # Rails app lives here WORKDIR /rails diff --git a/README.md b/README.md index 52db0ff8..f3f84ab1 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,6 @@ bin/rails --task rsync -av --progress sample-ead/ data/ead/ FINDING_AID_DATA=./data bin/rails arclight:ingest_everything ``` -##### Browser Resque Web -```shell -# Open http://localhost:5678 in your browser -``` ### Troubleshooting If you encounter this indexing error post-Boxrunner merge diff --git a/compose.yml b/compose.yml index e8cb4989..7a8cdfe4 100644 --- a/compose.yml +++ b/compose.yml @@ -20,7 +20,6 @@ services: - .env volumes: - .:/rails - - ./data:/rails/data redis: image: redis:7 healthcheck: @@ -50,10 +49,9 @@ services: - FINDING_AID_DATA=/rails/data env_file: - .env - command: bundle exec rake environment resque:work volumes: - .:/rails - - ./data:/rails/data + command: bundle exec rake environment resque:work resque-web: depends_on: redis: @@ -68,6 +66,8 @@ services: - .env ports: - '5678:5678' + volumes: + - .:/rails command: bundle exec rackup -o 0.0.0.0 -p 5678 config/resque_web.ru solr: depends_on: From 4e0b24c64d433dceef6c6b3efd682bf52c30c98e Mon Sep 17 00:00:00 2001 From: Greg Kostin Date: Wed, 26 Aug 2026 16:12:38 -0400 Subject: [PATCH 11/33] change to production environment --- Dockerfile | 4 ---- config/environments/production.rb | 5 +++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 86053201..fcf1badc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,10 +11,6 @@ ARG RUBY_VERSION=3.4.9 FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base -RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y procps && \ - rm -rf /var/lib/apt/lists/* - # Rails app lives here WORKDIR /rails diff --git a/config/environments/production.rb b/config/environments/production.rb index 22faeb78..161639b7 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -50,8 +50,9 @@ config.cache_store = :solid_cache_store # Replace the default in-process and non-durable queuing backend for Active Job. - config.active_job.queue_adapter = :solid_queue - config.solid_queue.connects_to = { database: { writing: :queue } } + # config.active_job.queue_adapter = :solid_queue + # config.solid_queue.connects_to = { database: { writing: :queue } } + config.active_job.queue_adapter = :resque # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. From 3c4f0f5a268669d1f927f6a350a39fcc7d753286 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Tue, 25 Aug 2026 16:14:24 -0400 Subject: [PATCH 12/33] Brief refactoring in response to Copilot/Claude code review --- lib/um_arclight/traject/ead2_component_config.rb | 7 ------- lib/um_arclight/traject/ead2_config.rb | 13 ++++++++----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/um_arclight/traject/ead2_component_config.rb b/lib/um_arclight/traject/ead2_component_config.rb index 946d51a5..dc459e03 100644 --- a/lib/um_arclight/traject/ead2_component_config.rb +++ b/lib/um_arclight/traject/ead2_component_config.rb @@ -32,13 +32,6 @@ provide "component_identifier_format", "%s_%s" end -each_record do |_record, context| - next unless settings["repository"] - context.clipboard[:repository] = Arclight::Repository.find_by( - slug: settings["repository"] - ).name -end - NAME_ELEMENTS = %w[corpname famname name persname].freeze SEARCHABLE_NOTES_FIELDS = %w[ diff --git a/lib/um_arclight/traject/ead2_config.rb b/lib/um_arclight/traject/ead2_config.rb index 27b2a64c..4fb222a1 100644 --- a/lib/um_arclight/traject/ead2_config.rb +++ b/lib/um_arclight/traject/ead2_config.rb @@ -68,11 +68,14 @@ end each_record do |_record, context| - next unless settings["repository"] - - context.clipboard[:repository] = Arclight::Repository.find_by( - slug: settings["repository"] - ).name + slug = settings["repository"] + raise "REPOSITORY_ID is not set; cannot index repository fields" if slug.blank? + repository = Arclight::Repository.find_by(slug: slug) + if repository.nil? + raise "No repository found for slug #{slug.inspect} — check REPOSITORY_ID " \ + "against the slugs defined in config/repositories.yml" + end + context.clipboard[:repository] = repository.name end # ================== From a8f5de9ca9a0b03c6422299f6bcbba59fbec2720 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Thu, 27 Aug 2026 14:58:01 -0400 Subject: [PATCH 13/33] Add class and improved output for nil repo ID/slug error --- lib/um_arclight/traject/ead2_config.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/um_arclight/traject/ead2_config.rb b/lib/um_arclight/traject/ead2_config.rb index 4fb222a1..0039934f 100644 --- a/lib/um_arclight/traject/ead2_config.rb +++ b/lib/um_arclight/traject/ead2_config.rb @@ -67,12 +67,18 @@ provide "logger", Logger.new($stderr) end +class TrajectEADIndexError < RuntimeError; end + each_record do |_record, context| slug = settings["repository"] - raise "REPOSITORY_ID is not set; cannot index repository fields" if slug.blank? + if slug.blank? + raise TrajectEADIndexError, + "REPOSITORY_ID is not set; cannot index repository fields" + end repository = Arclight::Repository.find_by(slug: slug) if repository.nil? - raise "No repository found for slug #{slug.inspect} — check REPOSITORY_ID " \ + raise TrajectEADIndexError, + "No repository found for slug #{slug.inspect} — check REPOSITORY_ID " \ "against the slugs defined in config/repositories.yml" end context.clipboard[:repository] = repository.name From 0c39bb891e66a340cf70093c00593d9bc9d509bb Mon Sep 17 00:00:00 2001 From: Samuel Sciolla Date: Mon, 31 Aug 2026 16:31:07 -0400 Subject: [PATCH 14/33] Make /opt/app-data directory and specify permissions; mount source code-related directories only, mount data separately to /opt/app-data --- Dockerfile | 3 +++ compose.yml | 30 ++++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index fcf1badc..1fdcfba9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -85,6 +85,9 @@ FROM base # Run and own only the runtime files as a non-root user for security RUN groupadd --system --gid 1000 rails && \ useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash + +RUN mkdir /opt/app-data +RUN chown 1000:1000 /opt/app-data USER 1000:1000 # Copy built artifacts: gems, application diff --git a/compose.yml b/compose.yml index 7a8cdfe4..8df3d641 100644 --- a/compose.yml +++ b/compose.yml @@ -15,11 +15,21 @@ services: environment: - SOLR_URL=http://solr:8983/solr/blacklight-collection - REDIS_URL=redis://redis:6379/0 - - FINDING_AID_DATA=/rails/data + - FINDING_AID_DATA=/opt/app-data env_file: - .env volumes: - - .:/rails + - ./data:/opt/app-data + # These directories are mounted into the volume so files can be changed during development without + # rebuilding. Changes to other files or directories will require a full re-build. + - ./app:/rails/app + - ./config:/rails/config + - ./db:/rails/db + - ./lib:/rails/lib + - ./public:/rails/public + - ./spec:/rails/spec + - ./storage:/rails/storage + - ./test:/rails/test redis: image: redis:7 healthcheck: @@ -46,11 +56,21 @@ services: - SOLR_URL=http://solr:8983/solr/blacklight-collection - REDIS_URL=redis://redis:6379/0 - QUEUE=* - - FINDING_AID_DATA=/rails/data + - FINDING_AID_DATA=/opt/app-data env_file: - .env volumes: - - .:/rails + - ./data:/opt/app-data + # These directories are mounted into the volume so files can be changed during development without + # rebuilding. Changes to other files or directories will require a full re-build. + - ./app:/rails/app + - ./config:/rails/config + - ./db:/rails/db + - ./lib:/rails/lib + - ./public:/rails/public + - ./spec:/rails/spec + - ./storage:/rails/storage + - ./test:/rails/test command: bundle exec rake environment resque:work resque-web: depends_on: @@ -66,8 +86,6 @@ services: - .env ports: - '5678:5678' - volumes: - - .:/rails command: bundle exec rackup -o 0.0.0.0 -p 5678 config/resque_web.ru solr: depends_on: From f0e4603022df033fbd162c88245acb1d5fbb56fa Mon Sep 17 00:00:00 2001 From: Samuel Sciolla Date: Tue, 1 Sep 2026 11:31:21 -0400 Subject: [PATCH 15/33] Remove source code mappings to fix assets pipeline --- compose.yml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/compose.yml b/compose.yml index 8df3d641..aa0d97b3 100644 --- a/compose.yml +++ b/compose.yml @@ -20,16 +20,6 @@ services: - .env volumes: - ./data:/opt/app-data - # These directories are mounted into the volume so files can be changed during development without - # rebuilding. Changes to other files or directories will require a full re-build. - - ./app:/rails/app - - ./config:/rails/config - - ./db:/rails/db - - ./lib:/rails/lib - - ./public:/rails/public - - ./spec:/rails/spec - - ./storage:/rails/storage - - ./test:/rails/test redis: image: redis:7 healthcheck: @@ -61,16 +51,6 @@ services: - .env volumes: - ./data:/opt/app-data - # These directories are mounted into the volume so files can be changed during development without - # rebuilding. Changes to other files or directories will require a full re-build. - - ./app:/rails/app - - ./config:/rails/config - - ./db:/rails/db - - ./lib:/rails/lib - - ./public:/rails/public - - ./spec:/rails/spec - - ./storage:/rails/storage - - ./test:/rails/test command: bundle exec rake environment resque:work resque-web: depends_on: From 248c8dd5655453ad9aa87be457eb25babd49b841 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 2 Sep 2026 12:38:11 -0400 Subject: [PATCH 16/33] Convert search in banner from two layers to one --- app/assets/stylesheets/_search_results.scss | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/assets/stylesheets/_search_results.scss b/app/assets/stylesheets/_search_results.scss index 90d34810..8be3565d 100644 --- a/app/assets/stylesheets/_search_results.scss +++ b/app/assets/stylesheets/_search_results.scss @@ -294,3 +294,17 @@ $bi-exclamation-triangle-fill: url("data:image/svg+xml,%3Csvg xmlns='http://www. background-color: var.$color-background-primary; border-color: var.$color-background-primary; } + +.search-query-form { + display: flex; + gap: 0.5rem; + // add margins for narrow viewports + margin-top: 0.5rem; + margin-bottom: 0.5rem; + .input-group { + width: unset; + } + select[name="search_field"] { + width: auto; + } +} \ No newline at end of file From 5c84f4519dcbe8e25b5e36e5243e642be4ab9ebf Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Fri, 28 Aug 2026 12:34:51 -0400 Subject: [PATCH 17/33] Select item button in leaf display toolbar, alongside Request and Clear selected --- app/assets/stylesheets/_collection.scss | 14 ++++++++------ .../um_aeon_request_checkbox_component.html.erb | 1 + .../um_aeon_request_checkbox_component.rb | 5 +++-- .../requests/_aeon_hidden_form_request.html.erb | 11 ++++++++--- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/_collection.scss b/app/assets/stylesheets/_collection.scss index ba634b49..57bf6b06 100644 --- a/app/assets/stylesheets/_collection.scss +++ b/app/assets/stylesheets/_collection.scss @@ -10,18 +10,20 @@ border-bottom: solid 1px var.$color-teal-300; 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 + to top, var.$gray-cool, var.$gray-white, var.$near-white, var.$color-background-primary ); padding-top: 1rem; padding-bottom: 0.5rem; + gap: 1rem; .al-request { - margin-right: 2rem; - margin-bottom: 0.5rem; - } + display: flex; + align-items: center; + gap: 1rem; - .btn-primary { - margin-right: 0.5rem; + .request-checkbox { + margin-bottom: 0; // cancel Bootstrap's .checkbox bottom margin + } } } diff --git a/app/components/um_aeon_request_checkbox_component.html.erb b/app/components/um_aeon_request_checkbox_component.html.erb index a1a03df3..48016bc8 100644 --- a/app/components/um_aeon_request_checkbox_component.html.erb +++ b/app/components/um_aeon_request_checkbox_component.html.erb @@ -5,6 +5,7 @@ + Request "<%= document.aeon_item_sub_title_visually_hidden %>" diff --git a/app/components/um_aeon_request_checkbox_component.rb b/app/components/um_aeon_request_checkbox_component.rb index 901874fa..8a8e5e32 100644 --- a/app/components/um_aeon_request_checkbox_component.rb +++ b/app/components/um_aeon_request_checkbox_component.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true class UmAeonRequestCheckboxComponent < ViewComponent::Base - def initialize(document:) + def initialize(document:, label: "Request") @document = document + @label = label end - attr_reader :document + attr_reader :document, :label 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 c70e3154..80b2f9f9 100644 --- a/app/views/arclight/requests/_aeon_hidden_form_request.html.erb +++ b/app/views/arclight/requests/_aeon_hidden_form_request.html.erb @@ -13,11 +13,16 @@ class: 'al-request-form' do %> <% aeon_external_request.form_mapping.each do |mapping| %> - + <% end %> + + <% if document.is_checkbox_requestable? && !document.children? %> + <%= render UmAeonRequestCheckboxComponent.new(document: document, label: "Select item") %> + <% end %> <% end %> - - + + + From c5597874a943eef73fbff9121178e0406d8189f7 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 2 Sep 2026 14:06:37 -0400 Subject: [PATCH 18/33] Remove unintended visible label from checkboxes, minor style changes --- app/assets/stylesheets/_collection.scss | 4 ++++ app/assets/stylesheets/_contents.scss | 8 ++++++++ .../um_aeon_request_checkbox_component.html.erb | 4 +++- app/components/um_aeon_request_checkbox_component.rb | 3 +-- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/_collection.scss b/app/assets/stylesheets/_collection.scss index 57bf6b06..c2b4e068 100644 --- a/app/assets/stylesheets/_collection.scss +++ b/app/assets/stylesheets/_collection.scss @@ -1,6 +1,10 @@ @use "bootstrap-context" as bs; @use "variables" as var; +span.request-checkbox-label { + margin-left: 0.5rem; +} + .al-show-actions-toolbar { display: flex; flex-flow: row wrap; diff --git a/app/assets/stylesheets/_contents.scss b/app/assets/stylesheets/_contents.scss index 91c8d02f..97bf5f0d 100644 --- a/app/assets/stylesheets/_contents.scss +++ b/app/assets/stylesheets/_contents.scss @@ -79,3 +79,11 @@ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0z'/%3E%3C/svg%3E"); -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0z'/%3E%3C/svg%3E"); } + +.col .al-document-container { + margin-top: 0.3rem; +} + +h3.document-title-heading { + margin-top: 0.3rem; +} diff --git a/app/components/um_aeon_request_checkbox_component.html.erb b/app/components/um_aeon_request_checkbox_component.html.erb index 48016bc8..0dd6aa9e 100644 --- a/app/components/um_aeon_request_checkbox_component.html.erb +++ b/app/components/um_aeon_request_checkbox_component.html.erb @@ -5,7 +5,9 @@ - + <% if label.present? %> + + <% end %> Request "<%= document.aeon_item_sub_title_visually_hidden %>" diff --git a/app/components/um_aeon_request_checkbox_component.rb b/app/components/um_aeon_request_checkbox_component.rb index 8a8e5e32..ec921b3d 100644 --- a/app/components/um_aeon_request_checkbox_component.rb +++ b/app/components/um_aeon_request_checkbox_component.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true class UmAeonRequestCheckboxComponent < ViewComponent::Base - def initialize(document:, label: "Request") + def initialize(document:, label: nil) @document = document @label = label end - attr_reader :document, :label end From ed0912eb088065c4cac82b4ee0b542d4c228ac5c Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Tue, 1 Sep 2026 13:07:58 -0400 Subject: [PATCH 19/33] Restrictions preview in component (leaf) display to match prod --- app/components/um_access_component.html.erb | 16 ++++++------- app/components/um_document_component.html.erb | 2 +- ...um_restrictions_preview_component.html.erb | 23 +++++++++++++++++++ .../um_restrictions_preview_component.rb | 15 ++++++++++++ app/controllers/catalog_controller.rb | 18 +++++++-------- config/locales/arclight.en.yml | 1 + .../traject/ead2_component_config.rb | 6 ++--- 7 files changed, 59 insertions(+), 22 deletions(-) create mode 100644 app/components/um_restrictions_preview_component.html.erb create mode 100644 app/components/um_restrictions_preview_component.rb diff --git a/app/components/um_access_component.html.erb b/app/components/um_access_component.html.erb index 3c57add1..c919f055 100644 --- a/app/components/um_access_component.html.erb +++ b/app/components/um_access_component.html.erb @@ -1,13 +1,11 @@ <% access_content = capture do %> <%= render Arclight::MetadataSectionComponent.with_collection(section_names, - metadata_attr: { layout: UmUpperMetadataLayoutComponent }, - presenter: presenter, classes: "") %> + metadata_attr: { layout: UmUpperMetadataLayoutComponent }, + presenter: presenter, classes: "") %> <% end %> -<% if access_content.present? %> -
-

<%= t 'arclight.views.show.sections.access_field' %>

- <%= render UmMaterialsAccessComponent.new(document: presenter.document) %> - <%= access_content %> -
-<% end %> +
+

<%= t 'arclight.views.show.sections.access_field' %>

+ <%= render UmMaterialsAccessComponent.new(document: presenter.document) %> + <%= access_content %> +
diff --git a/app/components/um_document_component.html.erb b/app/components/um_document_component.html.erb index 7c20792d..bd5a2c3d 100644 --- a/app/components/um_document_component.html.erb +++ b/app/components/um_document_component.html.erb @@ -23,7 +23,7 @@ <%= render Arclight::MetadataSectionComponent.with_collection(component_metadata_partials, metadata_attr: { layout: UmUpperMetadataLayoutComponent }, presenter: presenter, classes: "") unless document.collection? %> - +<%= render UmRestrictionsPreviewComponent.new(document: document) %> <%= embed %> <% if document.collection? %> diff --git a/app/components/um_restrictions_preview_component.html.erb b/app/components/um_restrictions_preview_component.html.erb new file mode 100644 index 00000000..7c4b864f --- /dev/null +++ b/app/components/um_restrictions_preview_component.html.erb @@ -0,0 +1,23 @@ +
+
+
+ <%= t("blacklight.search.fields.restrictions") %>: +
+ + + <% restrictions.each do |restriction| %> +
+ <%= helpers.render_html_tags(value: restriction) %> +
+ <% end %> +
+ +

+ <%= link_to( + t("arclight.views.show.sections.access_field"), + "##{t('arclight.views.show.sections.access_field').parameterize}", + class: "smooth-scroll", + data: { turbolinks: false } + ) %> +

+
diff --git a/app/components/um_restrictions_preview_component.rb b/app/components/um_restrictions_preview_component.rb new file mode 100644 index 00000000..8d4b1954 --- /dev/null +++ b/app/components/um_restrictions_preview_component.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class UmRestrictionsPreviewComponent < ViewComponent::Base + def initialize(document:) + @document = document + end + + def render? + restrictions.present? + end + + def restrictions + @document.fetch("accessrestrict_html_tesm", []) + end +end \ No newline at end of file diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 16668c97..ee0e09b2 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -47,12 +47,12 @@ class CatalogController < ApplicationController ## These settings are the Blacklight defaults (see SearchHelper#solr_doc_params) or ## parameters included in the Blacklight-jetty document requestHandler. config.default_document_solr_params = { - qt: "document", - fl: "*,collection:[subquery]", - 'collection.q': "{!terms f=id v=$row._root_}", - 'collection.defType': "lucene", - 'collection.fl': "*", - 'collection.rows': 1 + qt: "document", + fl: "*,collection:[subquery]", + 'collection.q': "{!terms f=id v=$row._root_}", + 'collection.defType': "lucene", + 'collection.fl': "*", + 'collection.rows': 1 } config.header_component = Arclight::HeaderComponent @@ -361,7 +361,7 @@ class CatalogController < ApplicationController } config.add_indexed_terms_field "indexes", field: "indexes_html_tesm", - helper_method: :render_html_tags + helper_method: :render_html_tags # ========================== # COMPONENT SHOW PAGE FIELDS @@ -422,7 +422,7 @@ class CatalogController < ApplicationController } config.add_component_indexed_terms_field "indexes", field: "indexes_html_tesm", - helper_method: :render_html_tags + helper_method: :render_html_tags # ================= # ACCESS TAB FIELDS @@ -450,4 +450,4 @@ class CatalogController < ApplicationController # Group header values config.add_group_header_field "abstract_or_scope", accessor: true, truncate: true, helper_method: :render_html_tags end -end +end \ No newline at end of file diff --git a/config/locales/arclight.en.yml b/config/locales/arclight.en.yml index d52fd59f..b1e0297c 100644 --- a/config/locales/arclight.en.yml +++ b/config/locales/arclight.en.yml @@ -9,6 +9,7 @@ en: component_indexed_terms_field: Subjects indexed_terms_field: Subjects terms_field: Terms + component_terms_field: Terms cite_field: Preferred Citation access_field: Using These Materials using_field: Using These Materials diff --git a/lib/um_arclight/traject/ead2_component_config.rb b/lib/um_arclight/traject/ead2_component_config.rb index dc459e03..786941ad 100644 --- a/lib/um_arclight/traject/ead2_component_config.rb +++ b/lib/um_arclight/traject/ead2_component_config.rb @@ -235,14 +235,14 @@ # Get the from the closest ancestor that has one (includes top-level) to_field "parent_access_restrict_tesm" do |record, accumulator| accumulator.concat Array - .wrap(record.xpath('(./ancestor::*/accessrestrict)[last()]/*[local-name()!="head"]') + .wrap(record.xpath('(./ancestor::*/accessrestrict | ./ancestor::*/descgrp/accessrestrict)[last()]/*[local-name()!="head"]') .map(&:text)) end # Get the from self OR the closest ancestor that has one (includes top-level) -to_field "parent_access_terms_tesm" do |record, accumulator| +to_field "parent_access_restrict_tesm" do |record, accumulator| accumulator.concat Array - .wrap(record.xpath('(./ancestor-or-self::*/userestrict)[last()]/*[local-name()!="head"]') + .wrap(record.xpath('(./ancestor-or-self::*/userestrict | ./ancestor-or-self::*/descgrp/userestrict)[last()]/*[local-name()!="head"]') .map(&:text)) end From 553379786b3aa2f4cf491f98d06e8e0f5652f33f Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Thu, 3 Sep 2026 17:05:35 -0400 Subject: [PATCH 20/33] Resolve conflicts --- app/assets/stylesheets/_contents.scss | 9 +++++++++ .../um_restrictions_preview_component.html.erb | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/_contents.scss b/app/assets/stylesheets/_contents.scss index 97bf5f0d..f398429b 100644 --- a/app/assets/stylesheets/_contents.scss +++ b/app/assets/stylesheets/_contents.scss @@ -80,6 +80,15 @@ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0z'/%3E%3C/svg%3E"); } +.restrictions-preview { + background-color: var(--color-maize-200); + padding: 1rem; + border-top: 1px solid var(--color-teal-200); + margin-bottom: 2rem; + padding-top: 1.5rem; + margin-top: 1rem; +} + .col .al-document-container { margin-top: 0.3rem; } diff --git a/app/components/um_restrictions_preview_component.html.erb b/app/components/um_restrictions_preview_component.html.erb index 7c4b864f..4407438d 100644 --- a/app/components/um_restrictions_preview_component.html.erb +++ b/app/components/um_restrictions_preview_component.html.erb @@ -1,6 +1,7 @@
+ <%= t("blacklight.search.fields.restrictions") %>:
@@ -14,7 +15,7 @@

<%= link_to( - t("arclight.views.show.sections.access_field"), + "More about accessing and using these materials...", "##{t('arclight.views.show.sections.access_field').parameterize}", class: "smooth-scroll", data: { turbolinks: false } From 64bcdfd9af139b6e18ee4ef10d7b0b342805fce5 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Tue, 1 Sep 2026 15:59:25 -0400 Subject: [PATCH 21/33] Still resolving conflict with _contents.scss --- app/assets/stylesheets/_contents.scss | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/_contents.scss b/app/assets/stylesheets/_contents.scss index f398429b..90868d5c 100644 --- a/app/assets/stylesheets/_contents.scss +++ b/app/assets/stylesheets/_contents.scss @@ -81,14 +81,14 @@ } .restrictions-preview { + position: relative; background-color: var(--color-maize-200); - padding: 1rem; - border-top: 1px solid var(--color-teal-200); + padding: 1.5rem 1rem 1rem; + margin-top: 2rem; // space that holds the border + gap margin-bottom: 2rem; - padding-top: 1.5rem; - margin-top: 1rem; } +<<<<<<< HEAD .col .al-document-container { margin-top: 0.3rem; } @@ -96,3 +96,14 @@ h3.document-title-heading { margin-top: 0.3rem; } +======= +.restrictions-preview::before { + content: ""; + position: absolute; + top: -1rem; // how far the line sits above the box + left: 0; + right: 0; + border-top: 1px dotted #cccccc; +} + +>>>>>>> a530e4f (Add vertical space and horizontal bar above restrictions box) From 922221ff446e397c41fdd20bf3b4245d228e084e Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Tue, 1 Sep 2026 16:28:21 -0400 Subject: [PATCH 22/33] Use locale file rather than catalog controller to translate parent restrictions to restrictions --- app/controllers/catalog_controller.rb | 2 +- config/locales/arclight.en.yml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index ee0e09b2..b1f44673 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -433,7 +433,7 @@ class CatalogController < ApplicationController config.add_terms_field "terms", field: "userestrict_html_tesm", helper_method: :render_html_tags # Component Show Page Access Tab - Terms and Condition Section - config.add_component_terms_field "restrictions", field: "accessrestrict_html_tesm", helper_method: :render_html_tags + # config.add_component_terms_field "restrictions", field: "accessrestrict_html_tesm", helper_method: :render_html_tags config.add_component_terms_field "terms", field: "userestrict_html_tesm", helper_method: :render_html_tags config.add_component_terms_field "parent_restrictions", field: "parent_access_restrict_tesm", helper_method: :render_html_tags config.add_component_terms_field "parent_terms", field: "parent_access_terms_tesm", helper_method: :render_html_tags diff --git a/config/locales/arclight.en.yml b/config/locales/arclight.en.yml index b1e0297c..4c24141e 100644 --- a/config/locales/arclight.en.yml +++ b/config/locales/arclight.en.yml @@ -63,7 +63,9 @@ en: restrictions: Restrictions terms: 'Use & Restrictions' - parent_restrictions: Parent restrictions + show: + parent_restrictions: Restrictions + parent_terms: 'Use & Restrictions' parent_terms: Parent terms of access repository_location: Location of this collection before_you_visit: Before you visit From 0f3531d951e152ff4d6d4b02906092d4b7930686 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Tue, 1 Sep 2026 16:32:11 -0400 Subject: [PATCH 23/33] rubocop manual fixes --- app/components/um_restrictions_preview_component.rb | 2 +- app/controllers/catalog_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/um_restrictions_preview_component.rb b/app/components/um_restrictions_preview_component.rb index 8d4b1954..5fe32d80 100644 --- a/app/components/um_restrictions_preview_component.rb +++ b/app/components/um_restrictions_preview_component.rb @@ -12,4 +12,4 @@ def render? def restrictions @document.fetch("accessrestrict_html_tesm", []) end -end \ No newline at end of file +end diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index b1f44673..1c27b898 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -450,4 +450,4 @@ class CatalogController < ApplicationController # Group header values config.add_group_header_field "abstract_or_scope", accessor: true, truncate: true, helper_method: :render_html_tags end -end \ No newline at end of file +end From 30c9145ef993937ec470ed199c48dbb37eb6af19 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 2 Sep 2026 16:20:46 -0400 Subject: [PATCH 24/33] Corrections responding to code review: moved solr query out of component, restored conditional --- app/components/um_access_component.html.erb | 2 ++ app/components/um_access_snippet_component.rb | 2 +- app/components/um_restrictions_preview_component.rb | 2 +- app/controllers/catalog_controller.rb | 2 +- app/models/solr_document.rb | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/components/um_access_component.html.erb b/app/components/um_access_component.html.erb index c919f055..98368895 100644 --- a/app/components/um_access_component.html.erb +++ b/app/components/um_access_component.html.erb @@ -4,8 +4,10 @@ presenter: presenter, classes: "") %> <% end %> +<% if access_content.present? %>

<%= t 'arclight.views.show.sections.access_field' %>

<%= render UmMaterialsAccessComponent.new(document: presenter.document) %> <%= access_content %>
+<% end %> diff --git a/app/components/um_access_snippet_component.rb b/app/components/um_access_snippet_component.rb index 87900a88..662b9c92 100644 --- a/app/components/um_access_snippet_component.rb +++ b/app/components/um_access_snippet_component.rb @@ -4,6 +4,6 @@ def materials_access end def restrictions_value - @document.restrictions + @document.restrictions.first end end diff --git a/app/components/um_restrictions_preview_component.rb b/app/components/um_restrictions_preview_component.rb index 5fe32d80..88da2ddd 100644 --- a/app/components/um_restrictions_preview_component.rb +++ b/app/components/um_restrictions_preview_component.rb @@ -10,6 +10,6 @@ def render? end def restrictions - @document.fetch("accessrestrict_html_tesm", []) + @document.restrictions end end diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 1c27b898..81cbc1d1 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -433,7 +433,7 @@ class CatalogController < ApplicationController config.add_terms_field "terms", field: "userestrict_html_tesm", helper_method: :render_html_tags # Component Show Page Access Tab - Terms and Condition Section - # config.add_component_terms_field "restrictions", field: "accessrestrict_html_tesm", helper_method: :render_html_tags + # removed config.add_component_terms_field "restrictions" to avoid duplicating [Access to this material is restricted to the reading room of the Bentley Historical Library.] in components restrictions_preview and using_these_materials config.add_component_terms_field "terms", field: "userestrict_html_tesm", helper_method: :render_html_tags config.add_component_terms_field "parent_restrictions", field: "parent_access_restrict_tesm", helper_method: :render_html_tags config.add_component_terms_field "parent_terms", field: "parent_access_terms_tesm", helper_method: :render_html_tags diff --git a/app/models/solr_document.rb b/app/models/solr_document.rb index 33377442..d7f3b63e 100644 --- a/app/models/solr_document.rb +++ b/app/models/solr_document.rb @@ -43,7 +43,7 @@ def has_online_content? end def restrictions - fetch("accessrestrict_html_tesm", [])[0] + fetch("accessrestrict_html_tesm", []) end def accessrestrict From 632e282ce9e679e132822ab99d932b449e64d1be Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 2 Sep 2026 16:36:50 -0400 Subject: [PATCH 25/33] Clean up conflict markup --- app/assets/stylesheets/_contents.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/stylesheets/_contents.scss b/app/assets/stylesheets/_contents.scss index 90868d5c..8e653c49 100644 --- a/app/assets/stylesheets/_contents.scss +++ b/app/assets/stylesheets/_contents.scss @@ -88,7 +88,6 @@ margin-bottom: 2rem; } -<<<<<<< HEAD .col .al-document-container { margin-top: 0.3rem; } @@ -96,7 +95,7 @@ h3.document-title-heading { margin-top: 0.3rem; } -======= + .restrictions-preview::before { content: ""; position: absolute; @@ -106,4 +105,3 @@ h3.document-title-heading { border-top: 1px dotted #cccccc; } ->>>>>>> a530e4f (Add vertical space and horizontal bar above restrictions box) From da8061e0a169857fd5b989a3a580a1c0bcea0ac8 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 2 Sep 2026 16:59:38 -0400 Subject: [PATCH 26/33] Ensure restrictions preview only appears on single items with restrictions --- app/components/um_restrictions_preview_component.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/um_restrictions_preview_component.rb b/app/components/um_restrictions_preview_component.rb index 88da2ddd..277f3918 100644 --- a/app/components/um_restrictions_preview_component.rb +++ b/app/components/um_restrictions_preview_component.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true class UmRestrictionsPreviewComponent < ViewComponent::Base + delegate :restrictions, to: :document + def initialize(document:) @document = document end def render? - restrictions.present? + restrictions.present? && !document.children? end - def restrictions - @document.restrictions - end -end + attr_reader :document +end \ No newline at end of file From df20a169ef3db2ce996a7af3ea53f51f4397ee09 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 2 Sep 2026 17:03:41 -0400 Subject: [PATCH 27/33] Tighten up conmment --- app/controllers/catalog_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 81cbc1d1..ca59c2e3 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -433,7 +433,7 @@ class CatalogController < ApplicationController config.add_terms_field "terms", field: "userestrict_html_tesm", helper_method: :render_html_tags # Component Show Page Access Tab - Terms and Condition Section - # removed config.add_component_terms_field "restrictions" to avoid duplicating [Access to this material is restricted to the reading room of the Bentley Historical Library.] in components restrictions_preview and using_these_materials + # Removed config.add_component_terms_field "restrictions" to avoid duplicating content in components restrictions_preview and using_these_materials config.add_component_terms_field "terms", field: "userestrict_html_tesm", helper_method: :render_html_tags config.add_component_terms_field "parent_restrictions", field: "parent_access_restrict_tesm", helper_method: :render_html_tags config.add_component_terms_field "parent_terms", field: "parent_access_terms_tesm", helper_method: :render_html_tags From b5fd900c3d37e76ff89e3d878e5fae3bb41023b5 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 2 Sep 2026 17:05:18 -0400 Subject: [PATCH 28/33] rubocop correction --- app/components/um_restrictions_preview_component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/um_restrictions_preview_component.rb b/app/components/um_restrictions_preview_component.rb index 277f3918..035492a8 100644 --- a/app/components/um_restrictions_preview_component.rb +++ b/app/components/um_restrictions_preview_component.rb @@ -12,4 +12,4 @@ def render? end attr_reader :document -end \ No newline at end of file +end From 2c373031ad28ee492910e29e722a8cb6e89284dc Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Thu, 3 Sep 2026 17:06:54 -0400 Subject: [PATCH 29/33] Resolve additional conflicts --- app/assets/stylesheets/_contents.scss | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/assets/stylesheets/_contents.scss b/app/assets/stylesheets/_contents.scss index 8e653c49..cd7c1d9a 100644 --- a/app/assets/stylesheets/_contents.scss +++ b/app/assets/stylesheets/_contents.scss @@ -58,10 +58,27 @@ padding: 1rem; } +#using-these-materials, #embed.mb-2 { + border-top: 1px dotted var.$color-neutral-100; + padding-top: 1rem; +} + +.al-digital-object { + margin-bottom: 1rem; +} + .card .al-online-content-icon .blacklight-icons svg { margin-top: -0.2rem; } +.col .al-document-container { + margin-top: 0.3rem; +} + +h3.document-title-heading { + margin-top: 0.3rem; +} + .al-digital-object a::after { content: ""; display: inline-block; From 57a6fc1c7bc924b90a2eafb7c4b0ae36f7830658 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Thu, 3 Sep 2026 16:52:43 -0400 Subject: [PATCH 30/33] Resolve conflicts --- app/assets/stylesheets/_contents.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/_contents.scss b/app/assets/stylesheets/_contents.scss index cd7c1d9a..42109a92 100644 --- a/app/assets/stylesheets/_contents.scss +++ b/app/assets/stylesheets/_contents.scss @@ -53,8 +53,8 @@ } #about-this-level { - background-color: #f5f5f5; - border-top: 1px dotted #cccccc; + background-color: var.$color-blue-100; + border-top: 1px dotted var.$color-neutral-100; padding: 1rem; } @@ -99,7 +99,7 @@ h3.document-title-heading { .restrictions-preview { position: relative; - background-color: var(--color-maize-200); + background-color: var.$color-maize-200; padding: 1.5rem 1rem 1rem; margin-top: 2rem; // space that holds the border + gap margin-bottom: 2rem; @@ -119,6 +119,6 @@ h3.document-title-heading { top: -1rem; // how far the line sits above the box left: 0; right: 0; - border-top: 1px dotted #cccccc; + border-top: 1px dotted var.$color-neutral-100; } From e2737a0ede8db60a4d432e08bd830d99801874e4 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 2 Sep 2026 17:41:50 -0400 Subject: [PATCH 31/33] Remove .al-show-sub-heading, it's too broad a target --- app/assets/stylesheets/_contents.scss | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/assets/stylesheets/_contents.scss b/app/assets/stylesheets/_contents.scss index 42109a92..972de375 100644 --- a/app/assets/stylesheets/_contents.scss +++ b/app/assets/stylesheets/_contents.scss @@ -58,15 +58,6 @@ padding: 1rem; } -#using-these-materials, #embed.mb-2 { - border-top: 1px dotted var.$color-neutral-100; - padding-top: 1rem; -} - -.al-digital-object { - margin-bottom: 1rem; -} - .card .al-online-content-icon .blacklight-icons svg { margin-top: -0.2rem; } From 00c4cb5b69942c4a7760b64e75ce2730f1c76c92 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Thu, 10 Sep 2026 16:15:33 -0400 Subject: [PATCH 32/33] Minor refactor of oembed js --- app/javascript/arclight/oembed_controller.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/javascript/arclight/oembed_controller.js b/app/javascript/arclight/oembed_controller.js index 2c1d3a91..8b712265 100644 --- a/app/javascript/arclight/oembed_controller.js +++ b/app/javascript/arclight/oembed_controller.js @@ -5,8 +5,9 @@ const UICONF_ID = '56928822' const PLAYER_HOST = 'https://cdnapisec.kaltura.com' const ENTRY_ID_PATTERN = /^\d_[a-z0-9]{8}$/i -// Kaltura-backed hosts (kaltura.com and MediaSpace instances) -const KALTURA_HOST_SUFFIXES = ['.kaltura.com', '.mivideo.it.umich.edu'] +// Kaltura-backed hosts (kaltura.com and MediaSpace instances). +// Matches the host itself and any subdomain. +const KALTURA_HOSTS = ['kaltura.com', 'mivideo.it.umich.edu'] export default class OembedController extends Controller { static values = { @@ -35,17 +36,19 @@ export default class OembedController extends Controller { return null } - const isKalturaHost = url.hostname === 'kaltura.com' - || KALTURA_HOST_SUFFIXES.some((suffix) => url.hostname.endsWith(suffix)) + const isKalturaHost = KALTURA_HOSTS.some( + (host) => url.hostname === host || url.hostname.endsWith(`.${host}`) + ) if (!isKalturaHost) return null const queryEntryId = url.searchParams.get('entry_id') - if (queryEntryId && ENTRY_ID_PATTERN.test(queryEntryId)) { - return queryEntryId - } + if (queryEntryId && ENTRY_ID_PATTERN.test(queryEntryId)) return queryEntryId + // MediaSpace share links put the entry ID in the path, e.g. + // https://mivideo.it.umich.edu/media/Some+Title/1_abc12345 return url.pathname - .match(/(?:^|\/)(\d_[a-z0-9]{8})(?:\/|$)/i)?.[1] || null + .split('/') + .find((segment) => ENTRY_ID_PATTERN.test(segment)) ?? null } loadKalturaEmbed(entryId) { From 044c5431c479a29540c1b823e2ad1dae6a035242 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Fri, 11 Sep 2026 09:51:40 -0400 Subject: [PATCH 33/33] Refactoring pass --- app/javascript/arclight/oembed_controller.js | 220 +++++++++---------- 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/app/javascript/arclight/oembed_controller.js b/app/javascript/arclight/oembed_controller.js index 8b712265..f86e05cf 100644 --- a/app/javascript/arclight/oembed_controller.js +++ b/app/javascript/arclight/oembed_controller.js @@ -1,4 +1,4 @@ -import { Controller } from '@hotwired/stimulus' +import {Controller} from '@hotwired/stimulus' const PARTNER_ID = '1758271' const UICONF_ID = '56928822' @@ -10,113 +10,113 @@ const ENTRY_ID_PATTERN = /^\d_[a-z0-9]{8}$/i const KALTURA_HOSTS = ['kaltura.com', 'mivideo.it.umich.edu'] export default class OembedController extends Controller { - static values = { - url: String - } - - connect() { - if (this.element.getAttribute('loaded') === 'loaded') return - - const entryId = this.findKalturaEntryId() - if (entryId) { - this.loadKalturaEmbed(entryId) - return - } - - this.loadOEmbed() - } - - findKalturaEntryId() { - let url - - try { - url = new URL(this.urlValue, window.location.href) - } catch (error) { - console.warn(`Invalid oEmbed URL: ${this.urlValue}`, error) - return null - } - - const isKalturaHost = KALTURA_HOSTS.some( - (host) => url.hostname === host || url.hostname.endsWith(`.${host}`) - ) - if (!isKalturaHost) return null - - const queryEntryId = url.searchParams.get('entry_id') - if (queryEntryId && ENTRY_ID_PATTERN.test(queryEntryId)) return queryEntryId - - // MediaSpace share links put the entry ID in the path, e.g. - // https://mivideo.it.umich.edu/media/Some+Title/1_abc12345 - return url.pathname - .split('/') - .find((segment) => ENTRY_ID_PATTERN.test(segment)) ?? null - } - - loadKalturaEmbed(entryId) { - const linkContainer = this.element.querySelector('.al-digital-object') - if (!linkContainer) { - console.warn('Could not find .al-digital-object for Kaltura embed') - return - } - - const params = new URLSearchParams({ - iframeembed: 'true', - entry_id: entryId - }) - - const iframe = document.createElement('iframe') - iframe.src = `${PLAYER_HOST}/p/${PARTNER_ID}/embedPlaykitJs/uiconf_id/${UICONF_ID}?${params}` - iframe.setAttribute('allowfullscreen', '') - iframe.setAttribute('allow', 'autoplay; fullscreen; encrypted-media') - iframe.setAttribute( - 'title', - this.element.querySelector('a')?.textContent?.trim() || 'Media player' - ) - iframe.style.cssText = 'width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0;' - - // Keep the original link as a fallback. - linkContainer.insertAdjacentElement('beforebegin', iframe) - this.element.setAttribute('loaded', 'loaded') - } - - loadOEmbed() { - fetch(this.urlValue) - .then((response) => { - if (response.ok) return response.text() - throw new Error(`HTTP error, status = ${response.status}`) - }) - .then((body) => { - const endpoint = this.findOEmbedEndpoint(body) - if (!endpoint) { - console.warn(`No oEmbed endpoint found in at ${this.urlValue}`) - return null - } - - return fetch(endpoint) - }) - .then((response) => { - if (!response) return null - if (response.ok) return response.json() - throw new Error(`HTTP error, status = ${response.status}`) - }) - .then((json) => { - if (!json?.html) return - - this.element.innerHTML = json.html - this.element.setAttribute('loaded', 'loaded') - }) - .catch((error) => { - console.error(error) - }) - } - - findOEmbedEndpoint(body) { - const doc = new DOMParser().parseFromString(body, 'text/html') - const endpoint = doc - .querySelector('link[rel="alternate"][type="application/json+oembed"]') - ?.getAttribute('href') - - if (!endpoint) return null - - return new URL(endpoint, this.urlValue).toString() - } + static values = { + url: String + } + + connect() { + if (this.element.getAttribute('loaded') === 'loaded') return + + const entryId = this.findKalturaEntryId() + if (entryId) { + this.loadKalturaEmbed(entryId) + return + } + + this.loadOEmbed() + } + + findKalturaEntryId() { + let url + + try { + url = new URL(this.urlValue, window.location.href) + } catch (error) { + console.warn(`Invalid oEmbed URL: ${this.urlValue}`, error) + return null + } + + const isKalturaHost = KALTURA_HOSTS.some( + (host) => url.hostname === host || url.hostname.endsWith(`.${host}`) + ) + if (!isKalturaHost) return null + + const queryEntryId = url.searchParams.get('entry_id') + if (queryEntryId && ENTRY_ID_PATTERN.test(queryEntryId)) return queryEntryId + +// MediaSpace share links put the entry ID in the path, e.g. +// https://mivideo.it.umich.edu/media/Some+Title/1_abc12345 + return url.pathname + .split('/') + .find((segment) => ENTRY_ID_PATTERN.test(segment)) ?? null + } + + loadKalturaEmbed(entryId) { + const linkContainer = this.element.querySelector('.al-digital-object') + if (!linkContainer) { + console.warn('Could not find .al-digital-object for Kaltura embed') + return + } + + const params = new URLSearchParams({ + iframeembed: 'true', + entry_id: entryId + }) + + const iframe = document.createElement('iframe') + iframe.src = `${PLAYER_HOST}/p/${PARTNER_ID}/embedPlaykitJs/uiconf_id/${UICONF_ID}?${params}` + iframe.setAttribute('allowfullscreen', '') + iframe.setAttribute('allow', 'autoplay; fullscreen; encrypted-media') + iframe.setAttribute( + 'title', + this.element.querySelector('a')?.textContent?.trim() || 'Media player' + ) + iframe.style.cssText = 'width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0;' + +// Keep the original link as a fallback. + linkContainer.insertAdjacentElement('beforebegin', iframe) + this.element.setAttribute('loaded', 'loaded') + } + + loadOEmbed() { + fetch(this.urlValue) + .then((response) => { + if (response.ok) return response.text() + throw new Error(`HTTP error, status = ${response.status}`) + }) + .then((body) => { + const endpoint = this.findOEmbedEndpoint(body) + if (!endpoint) { + console.warn(`No oEmbed endpoint found in at ${this.urlValue}`) + return null + } + + return fetch(endpoint) + }) + .then((response) => { + if (!response) return null + if (response.ok) return response.json() + throw new Error(`HTTP error, status = ${response.status}`) + }) + .then((json) => { + if (!json?.html) return + + this.element.innerHTML = json.html + this.element.setAttribute('loaded', 'loaded') + }) + .catch((error) => { + console.error(error) + }) + } + + findOEmbedEndpoint(body) { + const doc = new DOMParser().parseFromString(body, 'text/html') + const endpoint = doc + .querySelector('link[rel="alternate"][type="application/json+oembed"]') + ?.getAttribute('href') + + if (!endpoint) return null + + return new URL(endpoint, this.urlValue).toString() + } } \ No newline at end of file