Show artist breadcrumb on sidebar Library button when on artist-detail page
Artist-detail is a "pseudo-page" reachable from Library, the unified
Search page, and the global search popover. It has no [data-page]
match in the sidebar, so navigateToPage's bulk-active-removal left
every nav button unhighlighted while the user was viewing an artist —
the sidebar offered no visual anchor for where they were.
Now:
- navigateToPage('artist-detail') falls back to highlighting the
Library button when no [data-page] match exists, anchoring the
sidebar to the canonical home for artist detail views.
- A new _updateSidebarLibraryBreadcrumb() helper rewrites the Library
button label between plain "Library" and a "Library / Artist Name"
breadcrumb based on currentPage + artistDetailPageState. Long names
(>14 chars) truncate with an ellipsis; the full name shows on hover
via the title attribute.
- Called from navigateToPage (entering / leaving the page) and from
loadArtistDetailData (covers same-page artist switches in the
similar-artist chain where currentPage stays 'artist-detail').
CSS adds .nav-text-root / .nav-text-sep / .nav-text-context selectors
so the "Library" anchor word stays visually dominant while the
separator and artist name dim to a secondary tier — readable but not
competing for attention.
Pure visual change. No backend touched. No new tests (DOM-only).
This commit is contained in:
parent
6db2cbe2a7
commit
0c4fad033d
4 changed files with 119 additions and 0 deletions
|
|
@ -3441,6 +3441,11 @@ function closeHelperSearch() {
|
|||
// projects that span multiple commits before shipping. Strip the flag at
|
||||
// release time and add a real `date:` line at the top of the version block.
|
||||
const WHATS_NEW = {
|
||||
'2.4.2': [
|
||||
// --- post-2.4.1 dev work — entries hidden by _getLatestWhatsNewVersion until the build version bumps ---
|
||||
{ date: 'Unreleased — 2.4.2 dev cycle' },
|
||||
{ title: 'Sidebar Library Button Shows Artist Breadcrumb', desc: 'when you open an artist detail page (from library, search, or the global search popover), the sidebar Library button now lights up and rewrites its label to "Library / Artist Name" — long names truncate with an ellipsis and the full name shows on hover. revertes to plain "Library" when you leave. purely visual, no functionality change.', page: 'library' },
|
||||
],
|
||||
'2.4.1': [
|
||||
// --- May 1, 2026 — patch release ---
|
||||
{ date: 'May 1, 2026 — 2.4.1 release' },
|
||||
|
|
|
|||
|
|
@ -2189,6 +2189,13 @@ function navigateToPage(pageId, options = {}) {
|
|||
const navButton = document.querySelector(`[data-page="${pageId}"]`);
|
||||
if (navButton) {
|
||||
navButton.classList.add('active');
|
||||
} else if (pageId === 'artist-detail') {
|
||||
// Artist-detail is a "pseudo-page" reachable from Library, Search,
|
||||
// or the global search popover — it has no [data-page] match. Treat
|
||||
// it as a Library context so the sidebar still anchors the user
|
||||
// somewhere instead of showing a blank active state.
|
||||
const libraryBtn = document.querySelector('[data-page="library"]');
|
||||
if (libraryBtn) libraryBtn.classList.add('active');
|
||||
}
|
||||
|
||||
// Update pages
|
||||
|
|
@ -2199,6 +2206,12 @@ function navigateToPage(pageId, options = {}) {
|
|||
|
||||
currentPage = pageId;
|
||||
|
||||
// Refresh the Library button label so artist-detail shows a breadcrumb
|
||||
// ("Library / Artist Name") and other pages show plain "Library".
|
||||
if (typeof _updateSidebarLibraryBreadcrumb === 'function') {
|
||||
_updateSidebarLibraryBreadcrumb();
|
||||
}
|
||||
|
||||
if (!options.skipPushState) {
|
||||
const urlPath = pageId === 'dashboard' ? '/' : '/' + pageId;
|
||||
if (window.location.pathname !== urlPath) {
|
||||
|
|
|
|||
|
|
@ -707,6 +707,63 @@ let discographyFilterState = {
|
|||
ownership: 'all' // 'all', 'owned', 'missing'
|
||||
};
|
||||
|
||||
// Maximum visible characters of an artist name in the sidebar Library
|
||||
// breadcrumb. Names longer than this get truncated with an ellipsis so the
|
||||
// nav button width stays consistent across the rest of the sidebar.
|
||||
const _SIDEBAR_BREADCRUMB_ARTIST_MAXLEN = 14;
|
||||
|
||||
|
||||
function _updateSidebarLibraryBreadcrumb() {
|
||||
// Rewrite the Library nav button label between plain "Library" and a
|
||||
// "Library / <Artist>" breadcrumb depending on whether the user is on
|
||||
// the artist-detail pseudo-page. Pure visual — touches no app state.
|
||||
const btn = document.querySelector('[data-page="library"]');
|
||||
if (!btn) return;
|
||||
const textEl = btn.querySelector('.nav-text');
|
||||
if (!textEl) return;
|
||||
|
||||
const onArtistDetail = (typeof currentPage === 'string' && currentPage === 'artist-detail');
|
||||
const artistName = onArtistDetail ? (artistDetailPageState.currentArtistName || '') : '';
|
||||
|
||||
if (!onArtistDetail || !artistName) {
|
||||
// Default state: plain "Library" label. Use textContent so we wipe
|
||||
// any previously-injected breadcrumb spans cleanly.
|
||||
textEl.textContent = 'Library';
|
||||
textEl.removeAttribute('data-breadcrumb');
|
||||
return;
|
||||
}
|
||||
|
||||
// Truncate long names so the button width stays consistent.
|
||||
let display = artistName;
|
||||
if (display.length > _SIDEBAR_BREADCRUMB_ARTIST_MAXLEN) {
|
||||
display = display.slice(0, _SIDEBAR_BREADCRUMB_ARTIST_MAXLEN - 1).trimEnd() + '…';
|
||||
}
|
||||
|
||||
// Render via inline spans so CSS can style the root / separator / context
|
||||
// independently. Escape via textContent on individual spans.
|
||||
textEl.setAttribute('data-breadcrumb', '1');
|
||||
textEl.textContent = '';
|
||||
const root = document.createElement('span');
|
||||
root.className = 'nav-text-root';
|
||||
root.textContent = 'Library';
|
||||
const sep = document.createElement('span');
|
||||
sep.className = 'nav-text-sep';
|
||||
sep.textContent = ' / ';
|
||||
const ctx = document.createElement('span');
|
||||
ctx.className = 'nav-text-context';
|
||||
ctx.textContent = display;
|
||||
ctx.title = artistName; // full name on hover
|
||||
textEl.appendChild(root);
|
||||
textEl.appendChild(sep);
|
||||
textEl.appendChild(ctx);
|
||||
}
|
||||
|
||||
// Expose so init.js navigateToPage can call it without a circular import.
|
||||
if (typeof window !== 'undefined') {
|
||||
window._updateSidebarLibraryBreadcrumb = _updateSidebarLibraryBreadcrumb;
|
||||
}
|
||||
|
||||
|
||||
// Friendly labels for the dynamic "← Back to X" button on the artist-detail page.
|
||||
// Page id (the value of currentPage) -> button label.
|
||||
const _ARTIST_DETAIL_BACK_LABELS = {
|
||||
|
|
@ -895,6 +952,14 @@ function initializeArtistDetailPage() {
|
|||
async function loadArtistDetailData(artistId, artistName) {
|
||||
console.log(`🔄 Loading artist detail data for: ${artistName} (ID: ${artistId})`);
|
||||
|
||||
// Refresh the sidebar Library breadcrumb so it picks up the new artist
|
||||
// name. Covers same-page navigation between artists (similar-artist
|
||||
// chain) where navigateToPage doesn't fire because the page id stays
|
||||
// 'artist-detail'.
|
||||
if (typeof _updateSidebarLibraryBreadcrumb === 'function') {
|
||||
_updateSidebarLibraryBreadcrumb();
|
||||
}
|
||||
|
||||
// Reset discography filters to defaults
|
||||
resetDiscographyFilters();
|
||||
|
||||
|
|
|
|||
|
|
@ -491,6 +491,42 @@ body {
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Library breadcrumb — when on the artist-detail pseudo-page, the Library
|
||||
button label rewrites to "Library / <Artist>". The accent color stays on
|
||||
the "Library" root; the separator and artist name are dimmed so the
|
||||
anchor word remains scannable at a glance. JS truncates long names and
|
||||
sets the full name as a `title` attribute for hover. */
|
||||
.nav-text[data-breadcrumb] {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.nav-text[data-breadcrumb] .nav-text-root {
|
||||
color: inherit;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.nav-text[data-breadcrumb] .nav-text-sep {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-weight: 400;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.nav-button.active .nav-text[data-breadcrumb] .nav-text-sep {
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
.nav-text[data-breadcrumb] .nav-text-context {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
.nav-button.active .nav-text[data-breadcrumb] .nav-text-context {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
/* Sidebar Spacer */
|
||||
.sidebar-spacer {
|
||||
flex: 1;
|
||||
|
|
|
|||
Loading…
Reference in a new issue