zotero/scss/components/_collection-tree.scss
Dan Stillman 15c2c95470 Support multiple-collection selection (#5954)
Allow selecting multiple collections, saved searches, or library roots in the
collection tree -- within a library or across libraries -- and show the union
of their items. The selection is threaded through the pane as an array
(getCollectionTreeRows(), changeCollectionTreeRows(), etc.); the item pane, tag
selector, reports, and export operate on all selected rows.

Adding items (new items and notes, drag-and-drop, the attachment dialog,
import, Add by Identifier) targets every selected collection. Only rows that can
share an items view may be combined: collections, saved searches, and library
roots mix freely, and multiple Recently Read rows can be combined across
libraries, but other special views (Trash, Duplicates, etc.) and rows from
different visibility groups can't be shown together, so a selection mixing them
keeps only the focused row. In-window advanced search runs across all selected
collections.

Advanced search value autocomplete is now scoped to the searched library, fixing
a long-standing TODO where suggestions were drawn from all libraries regardless
of the search scope; for a cross-library selection it spans the selected
libraries.

For a cross-library selection, the tag selector shows the union of tags
(colored tags only when a single library is in scope, since colors are
per-library), and deleting a tag spans all selected libraries while
rename/color/split are disabled. If advanced search is open, the
collection and saved-search conditions are omitted, since each is scoped
to a single library.
2026-06-18 14:05:55 -04:00

183 lines
3.9 KiB
SCSS

// Themed icons -- separate SVGs per color in dark/, light/, white/
$themed-icons: (
collection, duplicates, feed, feed-error, feed-library, feed-updating, groups, library,
library-group, publications, trash-full, trash, unfiled, retracted, search,
);
// Universal icons -- single SVG with context-fill, colored via CSS
// Map of icon name to fill color variable
$universal-icons: (
recent: --tag-purple,
);
@mixin -themed-icon-rules {
@each $icon in $themed-icons {
.icon-css.icon-#{$icon} {
@include focus-states using ($color) {
@include svgicon($icon, $color, "16", "collection-tree");
}
}
}
}
@mixin -universal-icon-rules {
@each $icon, $fill in $universal-icons {
.icon-css.icon-#{$icon} {
@include svgicon($icon, "universal", "16", "collection-tree");
@include focus-states using ($color) {
@if $color == "white" {
fill: white;
}
@else {
fill: var(#{$fill});
}
}
}
}
}
@include focus-states using ($color) {
@include -themed-icon-rules;
}
.focus-states-target {
.row {
@include -themed-icon-rules;
}
}
@include -universal-icon-rules;
.focus-states-target {
.row {
@include -universal-icon-rules;
}
}
#zotero-collections-pane {
min-width: $min-width-collections-pane;
max-width: var(--max-width-collections-pane);
width: $min-width-collections-pane;
flex-grow: 0;
flex-shrink: 1;
background: var(--material-sidepane);
}
#zotero-collections-tree-container {
height: 5.2em;
}
#zotero-collections-tree {
width: 100%;
#main-window & {
// virtualized-table has a default background (--material-background)
// which is what we want in most places, including dialogs that include
// #zotero-collections-tree, however main window collection tree is an
// exception
background: transparent;
}
.virtualized-table {
overflow-y: auto;
flex: 1 0;
text-overflow: ellipsis;
.row {
.icon-css:not(.twisty) {
width: 16px;
height: 16px;
}
@each $icon in $themed-icons {
.icon-css.icon-#{$icon} {
@include focus-states(
".row.selected",
".virtualized-table:focus-within"
) using ($color) {
@include svgicon($icon, $color, "16", "collection-tree");
}
}
}
&.selected {
border-radius: 0;
&.first-selected {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
&.last-selected {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
}
}
.row.flashing:not(.drop) {
animation: row-flash 0.2s ease-in-out 2;
}
.row.flashing.drop {
animation: row-flash-selected 0.2s ease-in-out 2;
}
}
.virtualized-table-body {
padding: 0 8px 8px;
scrollbar-color: var(--color-scrollbar) var(--color-scrollbar-background);
}
// Highlight takes priority over selection
.row.highlighted.selected {
background: var(--highlight-color, var(--accent-highlight));
}
.cell.primary {
display: flex;
align-items: center;
// Stretch the cell to full height
align-self: stretch;
:not(.cell-text) {
flex-shrink: 0
}
.cell-text {
flex-shrink: 1;
text-overflow: ellipsis;
overflow: hidden;
margin-left: 4px;
}
input.cell-text {
// Stretch the input to full height and be centered vertically
align-self: stretch;
border: none;
outline: color-mix(in srgb, var(--color-accent) 80%, var(--material-background)) solid 2px;
padding: 1px 2px;
margin-right: 5px;
width: 100%;
font-size: inherit;
pointer-events: auto;
background: var(--material-background);
}
.cell-icon {
min-width: 16px;
}
}
}
// flashing effect applied to the row container when it is about to be expanded on dragover
// two different background colors are used for containers with .drop class and without
@keyframes row-flash {
0%, 100% { background-color: transparent; }
50% { background-color: var(--accent-azure); }
}
@keyframes row-flash-selected {
0%, 100% { background-color: var(--fill-secondary); }
50% { background-color: var(--accent-blue50); }
}