Rename Search page id from 'downloads' to 'search', bump to 2.43
Phase 3b of the Search/Artists unification. The Search page's
internal id was 'downloads', which clashed with the actual Downloads
page (id 'active-downloads') and confused anyone reading the code.
Renamed to 'search' across HTML, navigation, DOM selectors, and the
deep-link route list.
Backwards compat: navigateToPage('downloads') aliases to 'search'
at the top of the function; /downloads URL still serves index.html
and the client router resolves the page correctly; profile ACL
checks accept both 'search' and 'downloads' so existing profiles
with 'downloads' in allowed_pages keep working without migration.
Sidebar label unchanged. Zero visual change — pure internal tidy.
This commit is contained in:
parent
68d46c5aba
commit
6992e2e5b5
7 changed files with 50 additions and 24 deletions
|
|
@ -68,7 +68,7 @@ class TestSpaRoutes:
|
|||
"""Deep-link paths for valid client pages should serve index.html."""
|
||||
|
||||
@pytest.mark.parametrize("page", [
|
||||
'dashboard', 'sync', 'downloads', 'discover', 'artists',
|
||||
'dashboard', 'sync', 'search', 'downloads', 'discover', 'artists',
|
||||
'automations', 'library', 'import', 'settings', 'help',
|
||||
'issues', 'stats', 'watchlist', 'wishlist', 'active-downloads',
|
||||
'artist-detail', 'playlist-explorer', 'hydrabase', 'tools',
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ _log_dir = Path(_log_path).parent
|
|||
logger = setup_logging(_log_level, _log_path)
|
||||
|
||||
# App version — single source of truth for backup metadata, version-info endpoint, etc.
|
||||
_SOULSYNC_BASE_VERSION = "2.42"
|
||||
_SOULSYNC_BASE_VERSION = "2.43"
|
||||
|
||||
def _build_version_string():
|
||||
"""Append short commit hash to version when available (e.g. 2.35+abc1234)."""
|
||||
|
|
@ -320,7 +320,7 @@ def get_spotify_client_for_profile(profile_id=None):
|
|||
return spotify_client # Fall back to global
|
||||
|
||||
# Valid page IDs for profile permission validation
|
||||
VALID_PAGE_IDS = {'dashboard', 'sync', 'downloads', 'discover', 'artists', 'automations', 'library', 'import', 'settings', 'help'}
|
||||
VALID_PAGE_IDS = {'dashboard', 'sync', 'search', 'downloads', 'discover', 'artists', 'automations', 'library', 'import', 'settings', 'help'}
|
||||
|
||||
def check_download_permission():
|
||||
"""Check if current profile has download permission. Returns error response or None if allowed."""
|
||||
|
|
@ -22809,6 +22809,17 @@ def get_version_info():
|
|||
"title": "What's New in SoulSync",
|
||||
"subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes",
|
||||
"sections": [
|
||||
{
|
||||
"title": "Search Page Renamed to /search",
|
||||
"description": "The Search page's internal id is now 'search' instead of 'downloads', which no longer conflicts with the real Downloads page. URL /downloads still works for bookmarks and external links",
|
||||
"features": [
|
||||
"• Sidebar label unchanged (still reads 'Search') — no visual change",
|
||||
"• URL now /search; /downloads stays as an alias so no existing link breaks",
|
||||
"• DOM id renamed to #search-page; all internal references follow",
|
||||
"• Profile ACL stored as 'search' going forward; existing profiles with 'downloads' in allowed_pages still resolve through a legacy-compat check",
|
||||
"• Phase 3b of the Search/Artists unification project",
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "Search Source Picker — Pick Where You're Searching",
|
||||
"description": "The Search page's Enhanced/Basic toggle is replaced by a single 'Search from' dropdown so you can explicitly pick which source to query instead of fanning out to every provider",
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@
|
|||
<option value="">Default (Discover)</option>
|
||||
<option value="dashboard">Dashboard</option>
|
||||
<option value="sync">Sync</option>
|
||||
<option value="downloads">Search</option>
|
||||
<option value="search">Search</option>
|
||||
<option value="discover">Discover</option>
|
||||
<option value="artists">Artists</option>
|
||||
<option value="automations">Automations</option>
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
<div id="new-profile-allowed-pages" class="profile-page-checkboxes">
|
||||
<label><input type="checkbox" value="dashboard" checked> Dashboard</label>
|
||||
<label><input type="checkbox" value="sync" checked> Sync</label>
|
||||
<label><input type="checkbox" value="downloads" checked> Search</label>
|
||||
<label><input type="checkbox" value="search" checked> Search</label>
|
||||
<label><input type="checkbox" value="discover" checked> Discover</label>
|
||||
<label><input type="checkbox" value="artists" checked> Artists</label>
|
||||
<label><input type="checkbox" value="automations" checked> Automations</label>
|
||||
|
|
@ -196,7 +196,7 @@
|
|||
<span class="nav-icon"><svg class="nav-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/><line x1="4" y1="4" x2="9" y2="9"/></svg></span>
|
||||
<span class="nav-text">Sync</span>
|
||||
</button>
|
||||
<button class="nav-button" data-page="downloads">
|
||||
<button class="nav-button" data-page="search">
|
||||
<span class="nav-icon"><svg class="nav-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><polyline points="8 11 11 14 14 11"/></svg></span>
|
||||
<span class="nav-text">Search</span>
|
||||
</button>
|
||||
|
|
@ -1843,7 +1843,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Downloads Page -->
|
||||
<div class="page" id="downloads-page">
|
||||
<div class="page" id="search-page">
|
||||
<!--
|
||||
This top-level container replicates the QSplitter from downloads.py,
|
||||
creating the two-panel layout for the page.
|
||||
|
|
|
|||
|
|
@ -5454,10 +5454,11 @@ const _gsState = {
|
|||
function _gsUpdateVisibility() {
|
||||
const bar = document.getElementById('gsearch-bar');
|
||||
if (!bar) return;
|
||||
// Hide on downloads page where enhanced search already exists
|
||||
const onDownloads = typeof currentPage !== 'undefined' && currentPage === 'downloads';
|
||||
bar.style.display = onDownloads ? 'none' : '';
|
||||
if (onDownloads && _gsState.active) _gsDeactivate();
|
||||
// Hide on the Search page where the unified search already exists. Accept the
|
||||
// legacy 'downloads' id for callers that predate the page rename.
|
||||
const onSearchPage = typeof currentPage !== 'undefined' && (currentPage === 'search' || currentPage === 'downloads');
|
||||
bar.style.display = onSearchPage ? 'none' : '';
|
||||
if (onSearchPage && _gsState.active) _gsDeactivate();
|
||||
}
|
||||
|
||||
function _gsDeactivate() {
|
||||
|
|
@ -5866,8 +5867,8 @@ async function _gsClickTrack(artistName, trackName, albumName, trackId, imageUrl
|
|||
);
|
||||
} catch (e) {
|
||||
console.error('Error opening track download:', e);
|
||||
// Fallback: navigate to enhanced search
|
||||
navigateToPage('downloads');
|
||||
// Fallback: navigate to the unified Search page
|
||||
navigateToPage('search');
|
||||
setTimeout(() => {
|
||||
const input = document.getElementById('enhanced-search-input');
|
||||
if (input) { input.value = `${artistName} ${trackName}`.trim(); input.dispatchEvent(new Event('input')); }
|
||||
|
|
|
|||
|
|
@ -3599,10 +3599,15 @@ function closeHelperSearch() {
|
|||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
const WHATS_NEW = {
|
||||
'2.43': [
|
||||
// --- April 23, 2026 (later) ---
|
||||
{ date: 'April 23, 2026 (later)' },
|
||||
{ title: 'Search Page Renamed to /search', desc: 'The Search page\'s internal id is now "search" instead of the confusing "downloads" (which clashed with the actual Downloads page). Sidebar label unchanged. URL is now /search; /downloads still resolves so old bookmarks keep working. Profile ACL "Page Access" now saves as "search"; existing profiles with "downloads" in allowed_pages still resolve through a legacy-compat check. Phase 3b of the Search/Artists unification project', page: 'search' },
|
||||
],
|
||||
'2.42': [
|
||||
// --- April 23, 2026 ---
|
||||
{ date: 'April 23, 2026' },
|
||||
{ title: 'Search Source Picker — Pick Where You\'re Searching', desc: 'The Search page\'s Enhanced/Basic toggle is replaced by a single "Search from" dropdown at the top. Choose All sources (Auto — keeps today\'s multi-source fan-out), Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz, or Soulseek (raw files). Picking a specific source hits only that provider — no more surprise Spotify rate-limit hits from flows that didn\'t need Spotify. "Soulseek" routes to the raw-file search (what "Basic" used to do), so one picker now covers both modes. Loading text reflects the selected source (e.g., "Searching across Apple Music..."). Phase 3 of the Search/Artists unification project', page: 'downloads' },
|
||||
{ title: 'Search Source Picker — Pick Where You\'re Searching', desc: 'The Search page\'s Enhanced/Basic toggle is replaced by a single "Search from" dropdown at the top. Choose All sources (Auto — keeps today\'s multi-source fan-out), Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz, or Soulseek (raw files). Picking a specific source hits only that provider — no more surprise Spotify rate-limit hits from flows that didn\'t need Spotify. "Soulseek" routes to the raw-file search (what "Basic" used to do), so one picker now covers both modes. Loading text reflects the selected source (e.g., "Searching across Apple Music..."). Phase 3 of the Search/Artists unification project', page: 'search' },
|
||||
],
|
||||
'2.41': [
|
||||
// --- April 22, 2026 (late night) ---
|
||||
|
|
|
|||
|
|
@ -189,7 +189,9 @@ let currentProfile = null;
|
|||
|
||||
function getProfileHomePage() {
|
||||
if (!currentProfile) return 'dashboard';
|
||||
if (currentProfile.home_page) return currentProfile.home_page;
|
||||
// Legacy profiles stored the Search page as 'downloads'.
|
||||
const home = currentProfile.home_page === 'downloads' ? 'search' : currentProfile.home_page;
|
||||
if (home) return home;
|
||||
return currentProfile.is_admin ? 'dashboard' : 'discover';
|
||||
}
|
||||
|
||||
|
|
@ -206,7 +208,11 @@ function isPageAllowed(pageId) {
|
|||
if (pageId === 'settings') return currentProfile.is_admin;
|
||||
const ap = currentProfile.allowed_pages;
|
||||
if (!ap) return true; // null = all pages
|
||||
return ap.includes(pageId);
|
||||
if (ap.includes(pageId)) return true;
|
||||
// Legacy compat: the Search page used to be saved as 'downloads'.
|
||||
if (pageId === 'search' && ap.includes('downloads')) return true;
|
||||
if (pageId === 'downloads' && ap.includes('search')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function canDownload() {
|
||||
|
|
@ -1993,7 +1999,7 @@ function initializeNavigation() {
|
|||
}
|
||||
|
||||
const _DEEPLINK_VALID_PAGES = new Set([
|
||||
'dashboard', 'sync', 'downloads', 'discover', 'artists', 'automations',
|
||||
'dashboard', 'sync', 'search', 'downloads', 'discover', 'artists', 'automations',
|
||||
'library', 'import', 'settings', 'help', 'issues', 'stats', 'watchlist',
|
||||
'wishlist', 'active-downloads', 'artist-detail', 'playlist-explorer',
|
||||
'hydrabase', 'tools'
|
||||
|
|
@ -2141,6 +2147,9 @@ function initializeDownloadManagerToggle() {
|
|||
}
|
||||
|
||||
function navigateToPage(pageId, options = {}) {
|
||||
// Backwards-compat alias — the Search page used to live under id 'downloads'.
|
||||
if (pageId === 'downloads') pageId = 'search';
|
||||
|
||||
if (pageId === currentPage) return;
|
||||
|
||||
// Permission guard — redirect to home page if not allowed
|
||||
|
|
@ -2179,7 +2188,7 @@ function navigateToPage(pageId, options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
// Show/hide global search bar (hide on downloads page where enhanced search exists)
|
||||
// Show/hide global search bar (hide on search page where the unified search lives)
|
||||
if (typeof _gsUpdateVisibility === 'function') _gsUpdateVisibility();
|
||||
|
||||
// Show/hide discover download sidebar based on page
|
||||
|
|
@ -2236,7 +2245,7 @@ async function loadPageData(pageId) {
|
|||
initializeSyncPage();
|
||||
await loadSyncData();
|
||||
break;
|
||||
case 'downloads':
|
||||
case 'search':
|
||||
initializeSearch();
|
||||
initializeSearchModeToggle();
|
||||
initializeFilters();
|
||||
|
|
|
|||
|
|
@ -1330,9 +1330,9 @@ function initializeSearchModeToggle() {
|
|||
dropdown.classList.remove('hidden');
|
||||
updateToggleButtonState();
|
||||
}
|
||||
// Hide the page header + search mode toggle to reclaim space
|
||||
const header = document.querySelector('#downloads-page .downloads-header');
|
||||
const modeToggle = document.querySelector('.search-mode-toggle-container');
|
||||
// Hide the page header + source picker to reclaim space
|
||||
const header = document.querySelector('#search-page .downloads-header');
|
||||
const modeToggle = document.querySelector('.search-source-picker-container');
|
||||
const slskdPlaceholder = document.querySelector('#enhanced-search-section .search-results-container');
|
||||
if (header) header.classList.add('enh-results-active-hide');
|
||||
if (modeToggle) modeToggle.classList.add('enh-results-active-hide');
|
||||
|
|
@ -1346,8 +1346,8 @@ function initializeSearchModeToggle() {
|
|||
updateToggleButtonState();
|
||||
}
|
||||
// Restore hidden elements
|
||||
const header = document.querySelector('#downloads-page .downloads-header');
|
||||
const modeToggle = document.querySelector('.search-mode-toggle-container');
|
||||
const header = document.querySelector('#search-page .downloads-header');
|
||||
const modeToggle = document.querySelector('.search-source-picker-container');
|
||||
const slskdPlaceholder = document.querySelector('#enhanced-search-section .search-results-container');
|
||||
if (header) header.classList.remove('enh-results-active-hide');
|
||||
if (modeToggle) modeToggle.classList.remove('enh-results-active-hide');
|
||||
|
|
|
|||
Loading…
Reference in a new issue