-
Notifications
You must be signed in to change notification settings - Fork 136
Add compound (hierarchical) metadata foundation #7479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f8521e4
f8e1f04
9f7d349
c5c8637
9f36077
dbc22f2
f23f344
5b8b54f
a4363ab
bb0ca49
70cc171
c341780
18777a1
62d1e90
ff1b7e9
032264b
d1458c0
803338e
4b52b20
d82788a
a54d3f7
e0766b3
5505a46
ccea414
57ceb21
13f2f1e
d2f95a7
845551f
654c03d
5f1cfdd
fe4aece
fafb610
20b843b
197e231
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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() { | ||
| 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) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
| }); | ||
| })(); | ||
| 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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !important should not be used |
||
| flex-direction: column; | ||
| border: 0 !important; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !important should not be used |
||
| padding: 0 !important; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !important should not be used |
||
| padding: 0 !important; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !important should not be used |
||
| border-top: 1px solid $gray-200 !important; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| } | ||
| } | ||
| 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 |
There was a problem hiding this comment.
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