Skip to content

Conversation

@abartov
Copy link
Collaborator

@abartov abartov commented Feb 1, 2026

Summary

Implements selective download and print functionality for collections, allowing users to choose between downloading/printing the entire collection or only selected items.

Changes

Backend

  • Modified CollectionsController#download and CollectionsController#print to accept manifestation_ids[] parameter for selective operations
  • Added helper method print_url_by_entity to generate print URLs

Frontend

  • Created print modal (_print_modal.html.haml) similar to download modal with item selection
  • Updated download modal to include toggle between full/partial collection
  • Added manifestation checkboxes list with select-all functionality
  • Made modals larger (90vh) for better visibility
  • Positioned modals closer to top of viewport (2vh margin)
  • Used single scroll for entire modal content (no separate list scrollbar)
  • Made button text conditional: simple "הורדה"/"הדפסה" for manifestations, descriptive text for collections

Localization

  • Added I18n strings for Hebrew and English:
    • download_or_print_choose_scope
    • full_collection
    • selected_items
    • items_to_save
    • items_to_print
    • included_items
    • format_to_save
    • download_full_collection
    • print_full_collection

Testing

  • Added comprehensive system spec (collection_selective_download_print_spec.rb) testing:
    • Full collection download/print
    • Selective item download/print
    • Select all functionality
    • Individual checkbox selection
    • Multiple format downloads
  • Fixed existing download_modal_spec.rb tests by making button text conditional

Test Results

All specs passing:

  • spec/system/collection_selective_download_print_spec.rb: 7 examples, 0 failures
  • spec/system/download_modal_spec.rb: 2 examples, 0 failures

Fixes

Closes #by-sxo

🤖 Generated with Claude Code

abartov and others added 17 commits January 31, 2026 21:26
Implement feature to allow users to choose between downloading/printing
the entire collection or only selected items.

Changes:
- Modified CollectionsController download and print actions to accept
  manifestation_ids[] parameter for selective operations
- Created print modal similar to download modal with item selection
- Updated download modal to include toggle between full/partial collection
- Added manifestation checkboxes list with select-all functionality
- Added I18n strings for Hebrew and English
- Added helper method print_url_by_entity
- Updated collection_top partial to trigger print modal instead of direct print
- Added system spec for testing selective download/print functionality

Fixes #by-sxo

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Make modals larger (modal-lg) to accommodate item lists
- Add scrollable area for manifestation lists (max-height: 400px)
- Apply fixes to both download and print modals

Fixes issue where item list was clipped when modal didn't resize.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Set min-height: 60vh on .by-card-content-v02 to make modal much larger
- Increase scrollable list height to 50vh (from 400px) for better visibility
- Add overflow-x: hidden to prevent horizontal scrolling
- Apply to both download and print modals

This ensures the modal is large from the start and can accommodate
many items without clipping.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Move .bottom-button-area outside of .by-card-content-v02
- Make it a sibling element instead of child
- Use flex: 0 0 auto to prevent button area from growing/shrinking
- Use margin-top: auto to push buttons to bottom
- Make content area scrollable with flex: 1 1 auto

This ensures download/print and cancel buttons stay at the bottom
of the modal even when the item list is displayed.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Accidentally removed max-height and overflow styles when restructuring
button positioning. Restore the inline styles to make lists scrollable:
- max-height: 40vh (reduced from 50vh to account for button area)
- overflow-y: auto
- overflow-x: hidden

Without these, the lists were not visible/scrollable.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Override .flex-popup height: auto with explicit height: 75vh
to ensure modal is large enough to display item lists properly.

Applied to both download and print modals.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
The .flex-popup was correctly 75vh, but child .by-popup-v02
was only 273px instead of filling the parent.

Added inline styles to .by-popup-v02:
- height: 100% - fill parent
- display: flex; flex-direction: column - proper flex layout

Verified with Capybara:
- .by-popup-v02: 946px (was 273px)
- .by-card-content-v02: 813px (was 140px)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
The .field-v02 class has height: 28px which was constraining
the .texts-list container, causing the manifestation list to
overflow and overlap the format radio buttons below it.

Added inline style height: auto to .field-v02.texts-list to
allow the container to expand and properly push content below.

Verified with Capybara:
- Manifestation area height: 570px (was 51px)
- Radio buttons moved down 569px (was only 51px)
- No overlap detected

Applied to both download and print modals.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1. Changed .by-card-content-v02 overflow-y from 'auto' to 'hidden'
   - Prevents overall modal scrolling
   - Only the item list itself scrolls (has max-height: 40vh)

2. Changed .field-v02.texts-list background to gray
   - background-color: #eeeced (matches by-card-v02)
   - border: none (removes white field border)

Applied to both download and print modals.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1. Added slightly lighter gray background to list items
   - ol background-color: #f5f3f4 (vs #eeeced for container)
   - Provides subtle visual distinction

2. Added horizontal rule in download modal
   - <hr> tag between manifestation list and file formats
   - Provides clear separation between sections

Applied to both download and print modals.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Moved HR to correct position: between manifestation area and
  format section (reduced indentation to match download_instructions)
- Added ID #list-separator for JavaScript control
- Updated JavaScript to show/hide HR along with manifestation list
- HR starts hidden and only appears when user selects "partial" mode

This ensures the separator appears between the item list and file
formats, not between the count and items.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
The _reboot.scss sets hr height:0, making it invisible.
Added inline styles to override:
- height: 1px
- border: none
- border-top: 1px solid #d9cdd5 (matching site border color)
- margin: 15px 0 (spacing above and below)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Changed border color from #d9cdd5 (light) to #999 (medium gray)
for better visibility and clearer section separation.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Changed overflow-y from 'hidden' back to 'auto' on .by-card-content-v02.

The issue: With overflow-y: hidden, when content exceeded container
height (1075px content in 1004px space), radio buttons at the bottom
were clipped and invisible.

The solution: Allow content area to scroll when needed. The action
buttons stay visible at bottom because they're in a separate flex
container (.bottom-button-area) outside the scrollable content.

This provides:
- Scrollable content when there's overflow (can see all radios)
- Action buttons always visible at bottom (fixed position)
- Item list scrolls internally (max-height: 40vh)

Applied to both download and print modals.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Changes:
1. Removed max-height constraint from item lists
   - No separate scroll for list, everything scrolls together
   - Smoother single scrollbar experience

2. Increased modal height from 75vh to 90vh
   - Takes up more viewport space
   - More content visible at once

3. Positioned modal closer to top
   - Added margin-top: 2vh to .modal-dialog
   - Modal appears near top of screen instead of centered

Applied to both download and print modals.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
The button text was unconditionally set to "שמירת האסופה בפורמט הנבחר"
(download_full_collection) even for manifestation pages. This broke
existing tests that expected the button to say "הורדה" (download).

Changes:
- Made button text conditional based on entity type
- For Manifestations: Use t(:download) / t(:print)
- For Collections: Use t(:download_full_collection) / t(:print_full_collection)
- Applied to both download and print modals

Fixes spec/system/download_modal_spec.rb failures.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Copilot AI review requested due to automatic review settings February 1, 2026 08:38
#downloadDlg.modal{'aria-hidden' => 'true', role: 'dialog', tabindex: '-1'}
= render partial: 'shared/download', locals: {entity: @collection}

#printDlg.modal{'aria-hidden' => 'true', role: 'dialog', tabindex: '-1'}
Copy link

Choose a reason for hiding this comment

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

Classes should be listed before IDs (.modal should precede #printDlg)

#downloadDlg.modal{'aria-hidden' => 'true', role: 'dialog', tabindex: '-1'}
= render partial: 'shared/download', locals: {entity: @collection}

#printDlg.modal{'aria-hidden' => 'true', role: 'dialog', tabindex: '-1'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace

#downloadDlg.modal{'aria-hidden' => 'true', role: 'dialog', tabindex: '-1'}
= render partial: 'shared/download', locals: {entity: @collection}

#printDlg.modal{'aria-hidden' => 'true', role: 'dialog', tabindex: '-1'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should end with one space before the closing brace or be on its own line

= render partial: 'shared/download', locals: {entity: @collection}

#printDlg.modal{'aria-hidden' => 'true', role: 'dialog', tabindex: '-1'}
= render partial: 'shared/print_modal', locals: {entity: @collection}
Copy link

Choose a reason for hiding this comment

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

Layout/SpaceInsideHashLiteralBraces: Space inside { missing.

= render partial: 'shared/download', locals: {entity: @collection}

#printDlg.modal{'aria-hidden' => 'true', role: 'dialog', tabindex: '-1'}
= render partial: 'shared/print_modal', locals: {entity: @collection}
Copy link

Choose a reason for hiding this comment

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

Layout/SpaceInsideHashLiteralBraces: Space inside } missing.

.headline-3-v02= t(:included_items)
.mainlist.multiselect#texts-to-save
%ol{style: 'background-color: #f5f3f4;'}
- entity.flatten_items.select { |ci| ci.item_type == 'Manifestation' && ci.item.present? }.each do |ci|
Copy link

Choose a reason for hiding this comment

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

Line is too long. [127/120]

%ol{style: 'background-color: #f5f3f4;'}
- entity.flatten_items.select { |ci| ci.item_type == 'Manifestation' && ci.item.present? }.each do |ci|
%li
%input.multiselect_checkbox{type: 'checkbox', name: 'manifestation_ids[]', value: ci.item_id}
Copy link

Choose a reason for hiding this comment

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

Line is too long. [121/120]

%ol{style: 'background-color: #f5f3f4;'}
- entity.flatten_items.select { |ci| ci.item_type == 'Manifestation' && ci.item.present? }.each do |ci|
%li
%input.multiselect_checkbox{type: 'checkbox', name: 'manifestation_ids[]', value: ci.item_id}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace

%ol{style: 'background-color: #f5f3f4;'}
- entity.flatten_items.select { |ci| ci.item_type == 'Manifestation' && ci.item.present? }.each do |ci|
%li
%input.multiselect_checkbox{type: 'checkbox', name: 'manifestation_ids[]', value: ci.item_id}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should end with one space before the closing brace or be on its own line

- entity.flatten_items.select { |ci| ci.item_type == 'Manifestation' && ci.item.present? }.each do |ci|
%li
%input.multiselect_checkbox{type: 'checkbox', name: 'manifestation_ids[]', value: ci.item_id}
- author_names = ci.involved_authorities.select { |ia| ia.role == 'author' }.map { |ia| ia.authority.name }.join(', ')
Copy link

Choose a reason for hiding this comment

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

Line is too long. [146/120]

- text = author_names.present? ? "#{ci.title} / #{author_names}" : ci.title
= text

%hr#list-separator{style: 'display: none; height: 1px; border: none; border-top: 1px solid #999; margin: 15px 0;'}
Copy link

Choose a reason for hiding this comment

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

Line is too long. [128/120]

- entity.flatten_items.select { |ci| ci.item_type == 'Manifestation' && ci.item.present? }.each do |ci|
%li
%input.print-multiselect_checkbox{type: 'checkbox', name: 'manifestation_ids[]', value: ci.item_id}
- author_names = ci.involved_authorities.select { |ia| ia.role == 'author' }.map { |ia| ia.authority.name }.join(', ')
Copy link

Choose a reason for hiding this comment

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

Line is too long. [146/120]

%span.right-side-icon -
%span{:style => "font-weight: bold"}= t(:cancel)
- button_text = entity.is_a?(Collection) ? t(:print_full_collection) : t(:print)
= submit_tag button_text, {class: 'pointer by-button-v02 link-to-all-v02 print_submitter', id: 'print_do_print'}
Copy link

Choose a reason for hiding this comment

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

Line is too long. [126/120]

%span.right-side-icon -
%span{:style => "font-weight: bold"}= t(:cancel)
- button_text = entity.is_a?(Collection) ? t(:print_full_collection) : t(:print)
= submit_tag button_text, {class: 'pointer by-button-v02 link-to-all-v02 print_submitter', id: 'print_do_print'}
Copy link

Choose a reason for hiding this comment

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

Layout/SpaceInsideHashLiteralBraces: Space inside { missing.

%span.right-side-icon -
%span{:style => "font-weight: bold"}= t(:cancel)
- button_text = entity.is_a?(Collection) ? t(:print_full_collection) : t(:print)
= submit_tag button_text, {class: 'pointer by-button-v02 link-to-all-v02 print_submitter', id: 'print_do_print'}
Copy link

Choose a reason for hiding this comment

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

Layout/SpaceInsideHashLiteralBraces: Space inside } missing.

.flex-popup{style: 'height: 90vh;'}
.modal-header
.popup-top
%button.btn-small-outline-v02{'data-dismiss'=>'modal'}
Copy link

Choose a reason for hiding this comment

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

Layout/SpaceAroundOperators: Surrounding space missing for operator =>.

.flex-popup{style: 'height: 90vh;'}
.modal-header
.popup-top
%button.btn-small-outline-v02{'data-dismiss'=>'modal'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace

.flex-popup{style: 'height: 90vh;'}
.modal-header
.popup-top
%button.btn-small-outline-v02{'data-dismiss'=>'modal'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should end with one space before the closing brace or be on its own line

.btn-text-v02
%span.right-arrow 2
%span= t(:back)
.headline-2-v02{:style => "text-align: center;margin: 0; padding-top: 3px;"}= t(:print)
Copy link

Choose a reason for hiding this comment

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

Style/HashSyntax: Use the new Ruby 1.9 hash syntax.

.btn-text-v02
%span.right-arrow 2
%span= t(:back)
.headline-2-v02{:style => "text-align: center;margin: 0; padding-top: 3px;"}= t(:print)
Copy link

Choose a reason for hiding this comment

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

Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.

.btn-text-v02
%span.right-arrow 2
%span= t(:back)
.headline-2-v02{:style => "text-align: center;margin: 0; padding-top: 3px;"}= t(:print)
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace

.btn-text-v02
%span.right-arrow 2
%span= t(:back)
.headline-2-v02{:style => "text-align: center;margin: 0; padding-top: 3px;"}= t(:print)
Copy link

Choose a reason for hiding this comment

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

Hash attribute should end with one space before the closing brace or be on its own line

.by-popup-v02{style: 'height: 100%; display: flex; flex-direction: column;'}
.by-card-header-v02.desktop-only
%span.headline-1-v02= t(:print)
%a.popup-x-v02.linkcolor.pointer{'data-dismiss'=>'modal'} -
Copy link

Choose a reason for hiding this comment

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

Layout/SpaceAroundOperators: Surrounding space missing for operator =>.

.by-popup-v02{style: 'height: 100%; display: flex; flex-direction: column;'}
.by-card-header-v02.desktop-only
%span.headline-1-v02= t(:print)
%a.popup-x-v02.linkcolor.pointer{'data-dismiss'=>'modal'} -
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace

.by-popup-v02{style: 'height: 100%; display: flex; flex-direction: column;'}
.by-card-header-v02.desktop-only
%span.headline-1-v02= t(:print)
%a.popup-x-v02.linkcolor.pointer{'data-dismiss'=>'modal'} -
Copy link

Choose a reason for hiding this comment

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

Hash attribute should end with one space before the closing brace or be on its own line


= hidden_field_tag 'print_scope', 'full', id: 'print_scope'
.bottom-button-area.left-buttons{style: 'flex: 0 0 auto; margin-top: auto;'}
.desktop-only.linkcolor.pointer{'data-dismiss'=>'modal'}
Copy link

Choose a reason for hiding this comment

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

Layout/SpaceAroundOperators: Surrounding space missing for operator =>.


= hidden_field_tag 'print_scope', 'full', id: 'print_scope'
.bottom-button-area.left-buttons{style: 'flex: 0 0 auto; margin-top: auto;'}
.desktop-only.linkcolor.pointer{'data-dismiss'=>'modal'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace


= hidden_field_tag 'print_scope', 'full', id: 'print_scope'
.bottom-button-area.left-buttons{style: 'flex: 0 0 auto; margin-top: auto;'}
.desktop-only.linkcolor.pointer{'data-dismiss'=>'modal'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should end with one space before the closing brace or be on its own line

.bottom-button-area.left-buttons{style: 'flex: 0 0 auto; margin-top: auto;'}
.desktop-only.linkcolor.pointer{'data-dismiss'=>'modal'}
%span.right-side-icon -
%span{:style => "font-weight: bold"}= t(:cancel)
Copy link

Choose a reason for hiding this comment

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

Style/HashSyntax: Use the new Ruby 1.9 hash syntax.

.bottom-button-area.left-buttons{style: 'flex: 0 0 auto; margin-top: auto;'}
.desktop-only.linkcolor.pointer{'data-dismiss'=>'modal'}
%span.right-side-icon -
%span{:style => "font-weight: bold"}= t(:cancel)
Copy link

Choose a reason for hiding this comment

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

Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.

.bottom-button-area.left-buttons{style: 'flex: 0 0 auto; margin-top: auto;'}
.desktop-only.linkcolor.pointer{'data-dismiss'=>'modal'}
%span.right-side-icon -
%span{:style => "font-weight: bold"}= t(:cancel)
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace

.bottom-button-area.left-buttons{style: 'flex: 0 0 auto; margin-top: auto;'}
.desktop-only.linkcolor.pointer{'data-dismiss'=>'modal'}
%span.right-side-icon -
%span{:style => "font-weight: bold"}= t(:cancel)
Copy link

Choose a reason for hiding this comment

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

Hash attribute should end with one space before the closing brace or be on its own line

@@ -0,0 +1,109 @@
.modal-dialog.modal-lg{style: 'margin-top: 2vh;'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace

@@ -0,0 +1,109 @@
.modal-dialog.modal-lg{style: 'margin-top: 2vh;'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should end with one space before the closing brace or be on its own line

- formurl = print_url_by_entity(entity)
- formid = "print_form_#{entity.class.name.downcase}_#{entity.id}"
= form_tag(formurl, method: :get, id: formid, target: '_blank') do
.flex-popup{style: 'height: 90vh;'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace

- formurl = print_url_by_entity(entity)
- formid = "print_form_#{entity.class.name.downcase}_#{entity.id}"
= form_tag(formurl, method: :get, id: formid, target: '_blank') do
.flex-popup{style: 'height: 90vh;'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should end with one space before the closing brace or be on its own line

.headline-2-v02{:style => "text-align: center;margin: 0; padding-top: 3px;"}= t(:print)
.button-placeholder
.modal-body
.by-popup-v02{style: 'height: 100%; display: flex; flex-direction: column;'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace

.headline-2-v02{:style => "text-align: center;margin: 0; padding-top: 3px;"}= t(:print)
.button-placeholder
.modal-body
.by-popup-v02{style: 'height: 100%; display: flex; flex-direction: column;'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should end with one space before the closing brace or be on its own line

.by-card-header-v02.desktop-only
%span.headline-1-v02= t(:print)
%a.popup-x-v02.linkcolor.pointer{'data-dismiss'=>'modal'} -
.by-card-content-v02{style: 'overflow-y: auto; flex: 1 1 auto;'}
Copy link

Choose a reason for hiding this comment

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

Hash attribute should start with one space after the opening brace

@abartov abartov merged commit 6bd9846 into master Feb 1, 2026
5 of 8 checks passed
@abartov abartov deleted the feature/by-sxo-selective-collection-download-print branch February 1, 2026 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants