Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f8521e4
Add compound metadata schema reader and flag
laritakr Jun 4, 2026
f8e1f04
Render compound metadata fields on forms
laritakr Jun 4, 2026
9f7d349
Index and reload compound metadata fields
laritakr Jun 4, 2026
c5c8637
Render compound metadata on show pages
laritakr Jun 4, 2026
9f36077
Ship sample compound metadata on Hyrax
laritakr Jun 4, 2026
dbc22f2
Document compound metadata fields
laritakr Jun 4, 2026
f23f344
Rename agent sample compound to agents
laritakr Jun 4, 2026
5b8b54f
Fix compound metadata display on collections
laritakr Jun 4, 2026
a4363ab
Validate required compound sub-fields
laritakr Jun 4, 2026
bb0ca49
Resolve compound card schema from Solr doc
laritakr Jun 4, 2026
70cc171
Show collection compounds in flexible mode
laritakr Jun 4, 2026
c341780
Build collection terms on the class term list
laritakr Jun 5, 2026
18777a1
Fix compound card display in flexible mode
laritakr Jun 5, 2026
62d1e90
Drop duplicate label from compound cards
laritakr Jun 5, 2026
ff1b7e9
Align collection metadata values past long URLs
laritakr Jun 5, 2026
032264b
Improve compound metadata show rendering
laritakr Jun 5, 2026
d1458c0
Dynamic solr doc method creation for compounds
laritakr Jun 5, 2026
803338e
Rename the sample agents compound to participants
laritakr Jun 5, 2026
4b52b20
Define real SolrDocument readers for compounds
laritakr Jun 5, 2026
d82788a
Show collections with declared but empty compounds
laritakr Jun 5, 2026
a54d3f7
Rename compound "subfield" to "subproperty"
laritakr Jun 6, 2026
e0766b3
Declare compound subproperties as composable properties
laritakr Jun 6, 2026
5505a46
Support flat compound subproperties in flexible schemas
laritakr Jun 6, 2026
ccea414
Link and search collections in work_or_url compounds
laritakr Jun 6, 2026
57ceb21
Place compounds by their form primary flag
laritakr Jun 6, 2026
13f2f1e
Merge branch 'main' into rework-yaml-structure
laritakr Jun 6, 2026
d2f95a7
Guard nil flexible schema when resolving attributes
laritakr Jun 6, 2026
845551f
Share row-coercion plumbing across field behaviors
laritakr Jun 6, 2026
654c03d
Clean up compound vs. field-behavior documentation
laritakr Jun 6, 2026
5f1cfdd
Merge pull request #7483 from samvera/rework-yaml-structure
laritakr Jun 6, 2026
fe4aece
Remove config flag for compound metadata
laritakr Jun 6, 2026
fafb610
Clean up a few comments
laritakr Jun 6, 2026
20b843b
Re-render Valkyrie work form on failed validation
laritakr Jun 8, 2026
197e231
Apply flexible profile changes to compound forms
laritakr Jun 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 151 additions & 1 deletion .koppie/config/metadata_profiles/m3_profile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -927,4 +927,154 @@ properties:
display: true
primary: false
range: http://www.w3.org/2001/XMLSchema#string
property_uri: http://vocabulary.samvera.org/ns#transcriptIds
property_uri: http://vocabulary.samvera.org/ns#transcriptIds
# Sample compound (hierarchical) metadata, demonstrating the Hyrax compound
# foundation in flexible mode. A compound is a `type: hash, multiple: true`
# parent; its members are declared as separate properties pointing back with
# `subproperty_of: <parent>`, each carrying its own `available_on`, indexing,
# and form placement. `subproperty_of` entries are not standalone resource
# attributes. See documentation/compound_fields.md.
participants:
available_on:
class:
- GenericWork
- CollectionResource
data_type: array
type: hash
range: http://www.w3.org/2001/XMLSchema#string
display_label:
default: Participants
form:
primary: true
property_uri: http://id.loc.gov/vocabulary/relators/asn
# Group metadata (label) for subproperties declaring `group: <key>`.
groups:
identity:
label: Identity
view:
render_as: compound
html_dl: true
# Subproperties omit `available_on`/`range`/`display_label`: they inherit the
# parent compound's class scope and are folded into it by the schema loader.
participant_title:
type: string
subproperty_of: participants
group: identity
form:
cols: 4
participant_name:
type: string
subproperty_of: participants
group: identity
required: true
indexing: [participant_name_sim, participant_name_tesim]
form:
cols: 4
participant_role:
type: controlled
subproperty_of: participants
group: identity
required: true
values: [Author, Editor, Contributor, Creator, Data Collector, Funder, Publisher, Other]
indexing: [participant_role_sim, participant_role_tesim]
form:
cols: 4
identifiers:
available_on:
class:
- GenericWork
- CollectionResource
data_type: array
type: hash
range: http://www.w3.org/2001/XMLSchema#string
display_label:
default: Identifiers
form:
primary: false
property_uri: http://purl.org/dc/terms/identifier
view:
render_as: compound
html_dl: true
identifier_value:
type: string
subproperty_of: identifiers
required: true
indexing: [identifier_value_sim, identifier_value_tesim]
form:
cols: 6
identifier_type:
type: controlled
subproperty_of: identifiers
required: true
values: [DOI, Handle, ISBN, ISSN, URL, URN, ARK, Other]
indexing: [identifier_type_sim]
form:
cols: 6
compound_rights:
available_on:
class:
- GenericWork
- CollectionResource
data_type: array
type: hash
range: http://www.w3.org/2001/XMLSchema#string
display_label:
default: Rights
form:
primary: false
property_uri: http://purl.org/dc/terms/rights
view:
render_as: compound
html_dl: true
compound_rights_statement:
type: controlled
subproperty_of: compound_rights
authority: rights_statements
indexing: [compound_rights_statement_sim, compound_rights_statement_tesim]
form:
cols: 6
compound_rights_license:
type: controlled
subproperty_of: compound_rights
authority: licenses
indexing: [compound_rights_license_sim, compound_rights_license_tesim]
form:
cols: 6
compound_rights_notes:
type: string
subproperty_of: compound_rights
indexing: [compound_rights_notes_tesim]
form:
cols: 12
relationships:
available_on:
class:
- GenericWork
- CollectionResource
data_type: array
type: hash
range: http://www.w3.org/2001/XMLSchema#string
display_label:
default: Relationships
form:
primary: true
property_uri: http://purl.org/dc/terms/relation
view:
render_as: compound
html_dl: true
display: card
relationship_item:
type: work_or_url
subproperty_of: relationships
required: true
indexing: [relationships_item_ssim]
form:
cols: 6
relationship_type:
type: controlled
subproperty_of: relationships
required: true
values: [References, Is Referenced By, Is Part Of, Has Part, Is Version Of, Replaces, Requires, Other]
indexing: [relationships_type_sim]
form:
cols: 6
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Rails/OutputSafety:
- 'app/presenters/hyrax/fixity_status_presenter.rb'
- 'app/presenters/hyrax/presents_attributes.rb'
- 'app/renderers/hyrax/renderers/attribute_renderer.rb'
- 'app/renderers/hyrax/renderers/compound_attribute_renderer.rb'
- 'spec/views/hyrax/my/works/_list_works.html.erb_spec.rb'

RSpec/DescribeClass:
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/hyrax.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
//= require hyrax/single_use_links_manager
//= require hyrax/copy_permalink_button
//= require hyrax/redirects
//= require hyrax/compound_metadata
//= require hyrax/dashboard_actions
//= require hyrax/batch
//= require hyrax/flot_stats
Expand Down
98 changes: 98 additions & 0 deletions app/assets/javascripts/hyrax/compound_metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Behavior for compound metadata fields on resource edit forms (add/remove row,
// work_or_url select2). Event-delegated so dynamically-added rows need no
// rebinding. The sentinel guards against the IIFE running twice (Turbolinks
// re-evaluates module scripts on some navigation paths), which would stack
// duplicate listeners. Mirrors hyrax/redirects.js.
(function() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the invocation into the parens that contain the function wrap-iife

if (document.hyraxCompoundsBound) return;
document.hyraxCompoundsBound = true;

// Bind select2 to a `work_or_url` sub-property. The v3 API (Hyrax bundles
// select2-rails 3.x) binds to a hidden input; createSearchChoice lets a
// typed external URL be selected as-is.
function bindWorkOrUrlInputs(root) {
if (typeof jQuery === 'undefined' || !jQuery.fn.select2) return;
jQuery(root).find('[data-hyrax-compound-work-input]').each(function() {
var $el = jQuery(this);
if ($el.hasClass('select2-offscreen') || $el.data('select2')) return; // already bound
$el.select2({
width: '100%',
allowClear: true,
placeholder: 'Search for a work or enter a URL',
minimumInputLength: 2,
// Let a typed value (e.g. an external URL) be selected as-is.
createSearchChoice: function(term) {
return { id: term, text: term };
},
// Render the current value's label (work title or the URL) on load.
initSelection: function(element, callback) {
var val = element.val();
if (!val) return;
callback({ id: val, text: element.data('label') || val });
},
ajax: {
url: $el.data('autocomplete-url'),
dataType: 'json',
quietMillis: 250,
data: function(term, page) { return { q: term }; },
results: function(data, page) {
return {
results: data.map(function(obj) {
return { id: obj.id, text: [].concat(obj.label)[0] };
})
};
}
}
});
});
}

// Bind saved rows once the DOM is ready (at script-eval time the form
// inputs don't exist yet, so the select2 would never attach). Covers both a
// fresh load and Turbolinks navigation.
function bindAll() { bindWorkOrUrlInputs(document); }
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bindAll);
} else {
bindAll();
}
document.addEventListener('turbolinks:load', bindAll);

document.addEventListener('click', function(event) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function has a complexity of 11 complexity

var removeButton = event.target.closest('[data-hyrax-compound-remove-row]');
if (removeButton) {
var row = removeButton.closest('[data-hyrax-compound-row]');
if (!row) return;
var destroyFlag = row.querySelector('[data-hyrax-compound-destroy-flag]');
if (destroyFlag && destroyFlag.value !== undefined) {
// Persisted rows: flip the _destroy flag and hide so the
// populator drops the row server-side.
destroyFlag.value = '1';
row.style.display = 'none';
} else {
row.parentNode.removeChild(row);
}
return;
}

var addButton = event.target.closest('[data-hyrax-compound-add-row]');
if (!addButton) return;
var section = addButton.closest('[data-hyrax-compound]');
if (!section) return;
var template = section.querySelector('[data-hyrax-compound-row-template]');
var rowsHost = section.querySelector('[data-hyrax-compound-rows]');
if (!template || !rowsHost) return;

// Monotonic counter on the section; never recycle an index after a
// row is removed. Fallback to row count when the attribute is missing.
var nextIndex = parseInt(section.dataset.nextIndex, 10);
if (isNaN(nextIndex)) {
nextIndex = rowsHost.querySelectorAll('[data-hyrax-compound-row]').length;
}
var html = template.innerHTML.replace(/__INDEX__/g, nextIndex);
rowsHost.insertAdjacentHTML('beforeend', html);
// Bind select2 on any work_or_url inputs in the row just added.
bindWorkOrUrlInputs(rowsHost.lastElementChild || rowsHost);
section.dataset.nextIndex = String(nextIndex + 1);
});
})();
7 changes: 7 additions & 0 deletions app/assets/stylesheets/hyrax/_collections.scss
Original file line number Diff line number Diff line change
Expand Up @@ -610,11 +610,18 @@ button.branding-banner-remove:hover {

dt {
flex-basis: 115px;
// Hold the label column fixed so every value column (dd) starts at the
// same x; otherwise a long unbreakable value (e.g. a URL link) shrinks
// this column and shifts the value left.
flex-shrink: 0;
font-weight: bold;
}

dd {
flex-basis: auto;
// Let the value column shrink to its flex space so long URLs wrap within
// it rather than forcing the row wider.
min-width: 0;
}

}
Expand Down
50 changes: 50 additions & 0 deletions app/assets/stylesheets/hyrax/_compound_metadata.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Compound metadata display on work and collection show pages.
// See documentation/compound_fields.md.
//
// `!important` throughout: the collection metadata area styles nested divs with
// a descendant selector (`.hyc-metadata div { display: flex; border-bottom }`
// in _collections.scss) that would otherwise turn each entry into a bordered
// flex row. These rules override it regardless of source order.
.hyrax-compound-values {
display: block !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!important should not be used
Properties should be ordered border, display, flex-direction, max-width, padding

flex-direction: column;
border: 0 !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!important should not be used

padding: 0 !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!important should not be used

max-width: 100%;

// Force block flow on the entries/sub-properties; the label/value spans stay
// inline so each sub-property reads as "Label: value" on one wrapping line.
.hyrax-compound-entry,
.hyrax-compound-subproperty {
display: block !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!important should not be used

}

.hyrax-compound-entry,
.hyrax-compound-subproperty,
.hyrax-compound-subproperty-label,
.hyrax-compound-subproperty-value {
border: 0 !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!important should not be used
Properties should be ordered background, border, margin, padding

padding: 0 !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!important should not be used

margin: 0;
background: none;
}

// A light divider between entries.
.hyrax-compound-entry + .hyrax-compound-entry {
margin-top: 0.5rem;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properties should be ordered border-top, margin-top, padding-top

padding-top: 0.5rem !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!important should not be used

border-top: 1px solid $gray-200 !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!important should not be used

}

.hyrax-compound-subproperty {
display: block;
line-height: 1.4;
overflow-wrap: break-word;
word-break: break-word;
}

.hyrax-compound-subproperty-label {
font-weight: 600;
margin-right: 0.25rem;
}
}
2 changes: 1 addition & 1 deletion app/assets/stylesheets/hyrax/_hyrax.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"hyrax/select_work_type", "hyrax/users", "hyrax/dashboard", "hyrax/sidebar",
"hyrax/controlled_vocabulary", "hyrax/accessibility", "hyrax/recent",
"hyrax/viewer", "hyrax/breadcrumbs", "hyrax/facets", "hyrax/card",
"hyrax/badge";
"hyrax/badge", "hyrax/compound_metadata";
@import "hydra-editor/multi_value_fields";
@import "typeahead";
@import "sharing_buttons";
Expand Down
43 changes: 43 additions & 0 deletions app/authorities/qa/authorities/compound_works.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

module Qa::Authorities
##
# Autocomplete authority for the compound `work_or_url` sub-property's work
# picker, mounted at `/authorities/search/compound_works`. Returns readable
# works matched by {Hyrax::CompoundWorkPickerBuilder}.
class CompoundWorks < Qa::Authorities::Base
class_attribute :search_builder_class
self.search_builder_class = Hyrax::CompoundWorkPickerBuilder

def search(_q, controller)
return [] unless controller.current_user

response, _docs = search_response(controller)
response.documents.map do |doc|
{ id: doc.id, label: Array(doc.title).first || doc.id, value: doc.id }
end
end

private

def search_service(controller)
@search_service ||= Hyrax::SearchService.new(
config: controller.blacklight_config,
user_params: controller.params,
search_builder_class: search_builder_class,
scope: controller,
current_ability: controller.current_ability
)
end

def search_response(controller)
access = controller.params[:access] || 'read'

search_service(controller).search_results do |builder|
builder.with({ q: controller.params[:q] })
.with_access(access)
.rows(20)
end
end
end
end
Loading
Loading