zotero/scss/elements/_quickSearchTextbox.scss
Dan Stillman bc73c713d7 Replace the best-match top-K cutoff with a ranked Relevance column
Semantic similarity has no natural relevance threshold, so instead of
asking the user to pick an arbitrary result count, show every scored
item and surface the ranking directly: a Relevance column appears and
becomes the sort while a best-match search is active, and the previous
sort and columns return when it clears. The merged results are scored
in a single pass in the row provider, so ranks are global across a
multi-collection selection, child items (attachments, notes,
annotations) rank via their top-level item, and equal scores get equal
ranks that order deterministically via the secondary sort fields. Items
without a stored embedding are filtered out.

Each cell renders the score's position within the model's display range
as a bar, so relevant results read as full and the irrelevant tail
reads as empty. The ranges are provisional per-model display constants.
Sorting uses the ranks, which are also exposed to assistive technology
and as the cell tooltip. On a focused selected row the bar
switches to white so the fill doesn't vanish into the accent selection
background.
2026-08-05 15:26:55 -04:00

235 lines
6.3 KiB
SCSS

// | 6px padding | 16px icon width | 2px padding | 8px dropmarker width | 4px padding |
quick-search-textbox {
--search-textbox-width: 146px;
font-size: var(--zotero-font-size);
margin-right: 0;
width: var(--search-textbox-width);
height: 28px;
max-width: 300px;
flex-grow: 1;
// Let the wrapper shrink below its content width when the items pane is squeezed (e.g., by a
// wide item pane). Otherwise it keeps its intrinsic width and overflows to the right, pushing
// the trailing Advanced Search button out under the item pane where it's clipped.
#search-wrapper {
min-width: 0;
}
// While the Advanced Search indicator is shown, size this element to the indicator's content
// (full label + buttons) rather than the 146px quick-search width, and allow shrinking below
// it (min-width: 0) only when the toolbar overflows -- so the label stays full on a normal
// window and crops just enough on a narrow one, keeping the buttons to its right (collapse/
// close and the stacked item-pane toggle) unclipped. The normal search box keeps its default
// size (it isn't shown then).
&[advanced-search-open] {
width: auto;
min-width: 0;
// Take only the indicator's width (don't grow toward max-width), so the buttons sit right
// after the label and the toggle isn't pushed out; the toolbar's spacer absorbs the slack
flex-grow: 0;
// A XUL <deck> sizes to its largest panel and doesn't clamp the panel to the deck's
// width, so the indicator keeps its full width and overflows. Since only the indicator
// is shown while advanced search is open, lay the deck out as flex and let the indicator
// (and its label) shrink, hiding the inactive quick-search panel.
#search-deck {
min-width: 0;
display: flex;
}
#search-wrapper {
display: none;
}
#advanced-search-indicator {
flex: 1;
min-width: 0;
}
}
*[zoteroFontSize=small] {
font-size: 11px;
}
:is(search-textbox)::part(search-sign),
:is(search-textbox)::part(search-icon) {
@include svgicon-menu("magnifier", "universal", "16");
color: var(--fill-secondary);
// right: 4px gap + 2px padding + 8px dropmarker width + 4px padding
// left, top, and bottom: 5px padding (6px on Figma, including 1px search-textbox border)
// Use explicit left/right padding here because this element is flipped in RTL (see _search.scss)
padding: 5px 18px 5px 5px;
margin: 0;
}
:is(search-textbox)::part(search-input) {
padding: 0;
}
}
#zotero-tb-search {
height: 100%;
}
#zotero-tb-search-dropmarker {
position: relative;
// 6px padding + 16px icon width + 2px padding + 8px dropmarker width + 4px padding
width: 36px;
margin-inline-end: -36px;
z-index: 2;
}
#zotero-tb-search-menu-button {
width: 36px;
height: 28px;
min-height: 28px;
padding-block: 0;
// inline-start: 6px padding + 16px icon width + 2px padding - 1px outer border
padding-inline: 23px 4px !important;
margin: 0;
border: 0;
border-radius: 5px;
appearance: none;
min-width: 0;
cursor: default;
@include svgicon-menu("chevron-6", "universal", "8");
color: var(--fill-secondary);
background: transparent;
@include focus-ring;
.button-menu-dropmarker {
display: none; /* Hide automatic dropmarker */
}
}
#zotero-tb-search-textbox {
padding: 0;
padding-inline-end: 6px;
margin: 0;
position: relative;
inset-inline-start: 0;
width: var(--search-textbox-width);
height: 28px;
z-index: 1;
width: 100%;
// Keep input text and the clear icon out from under the Advanced Search button
&:has(~ #zotero-tb-search-advanced-button) {
padding-inline-end: 30px;
}
}
// Match the specificity of the #zotero-items-toolbar toolbarbutton rules
#zotero-tb-search #zotero-tb-search-advanced-button {
position: relative;
width: 28px;
height: 28px;
min-width: 0;
margin: 0;
margin-inline-start: -28px;
margin-inline-end: 2px;
padding: 0;
z-index: 2;
@include svgicon-menu("filter", "universal", "16");
color: var(--fill-secondary);
@include focus-ring;
// Show the hover effect on a chip around the icon, inset within the field,
// rather than on the whole button
&:hover, &:active {
background-color: transparent;
}
.toolbarbutton-icon {
width: 24px;
height: 24px;
// The glyph is weighted slightly to the right within the SVG, so shift it
// a pixel left to optically center it (physical sides -- doesn't flip in RTL)
padding: 5px 6px 5px 4px;
border-radius: 4px;
// The button is 4px wider than the chip and left-aligns it, which puts the
// focus ring off-center; shift the chip 2px to center it within the button
margin-inline-start: 2px;
}
&:hover .toolbarbutton-icon {
background-color: var(--fill-quinary);
}
&:active .toolbarbutton-icon {
background-color: var(--fill-quarternary);
}
}
#zotero-tb-search-spinner {
margin: 0 8px;
// make sure the spinner always occupies space
display: inline;
visibility: hidden;
}
#search-deck {
width: 100%;
}
#search-wrapper {
align-items: center;
}
#advanced-search-indicator {
gap: 8px;
align-items: center;
// Cover the inline-end toolbar padding and the existing bottom border
margin-inline-end: -$toolbar-padding-inline;
padding-inline: $toolbar-padding-inline;
margin-bottom: -1px;
background: var(--material-toolbar);
border-inline-start: var(--material-border);
// Prevent text from shifting when border shows after panel is collapsed
border-block: var(--material-border-transparent);
// This is styled like it's part of the Advanced Search panel, which can't drag the window,
// so match that behavior
-moz-window-dragging: no-drag;
& > label {
flex: 1;
// Let the label shrink and crop on a narrow window so the collapse/close buttons to its
// right stay visible
min-width: 0;
padding-inline: 4px;
font-weight: 700;
}
.zotero-clicky {
width: 28px;
height: 28px;
margin: 0;
padding: 4px;
}
.advanced-collapse-button {
@include svgicon-menu("minimize", "universal", "20");
}
.advanced-close-button {
@include svgicon-menu("x", "universal", "20");
// Inset to match the filter button's chip in the closed state (it sits 2px
// in from the field edge), so the edge doesn't shift when toggling between
// the search field and this header
margin-inline-end: 2px;
}
&[data-collapsed="true"] {
border-bottom: var(--material-border);
.advanced-collapse-button {
@include svgicon-menu("maximize", "universal", "20");
}
}
}