feat: add search filter bar to playlist detail and favorites views

This commit is contained in:
Honey committed 2026-05-13 13:58:08 +02:00
1 parent d5470a6440
commit e78df7bdb9
4 files changed
+37 -2

No files matched your search

+8
View File
@@ -232,6 +232,10 @@
<button class="sort-chip" data-sort="artist" onclick="loadStarred('artist')">Künstler</button> <button class="sort-chip" data-sort="artist" onclick="loadStarred('artist')">Künstler</button>
<button class="sort-chip" data-sort="plays" onclick="loadStarred('plays')">Meistgespielt</button> <button class="sort-chip" data-sort="plays" onclick="loadStarred('plays')">Meistgespielt</button>
</div> </div>
<div class="list-search-wrap">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input id="starred-search" class="list-search" type="text" placeholder="In Favoriten suchen…" oninput="filterStarred(this.value)">
</div>
<div id="starred-songs" class="song-list"></div> <div id="starred-songs" class="song-list"></div>
</div> </div>
@@ -265,6 +269,10 @@
</div> </div>
</div> </div>
</div> </div>
<div class="list-search-wrap">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input id="playlist-search" class="list-search" type="text" placeholder="In Playlist suchen…" oninput="filterPlaylist(this.value)">
</div>
<div class="song-list" id="playlist-songs"></div> <div class="song-list" id="playlist-songs"></div>
</div> </div>
+2 -2
View File
@@ -15,7 +15,7 @@ import {
navigate, _navGoBack, _navLoad, onLocalSearch, onMobileSearch, navigate, _navGoBack, _navLoad, onLocalSearch, onMobileSearch,
onGlobalSearch, s_goArtist, s_goAlbum, playerBarGoArtist, playerBarGoAlbum, onGlobalSearch, s_goArtist, s_goAlbum, playerBarGoArtist, playerBarGoAlbum,
searchSimilarArtist, searchAndPreview, loadItunesAlbum, expandArtistBio, searchSimilarArtist, searchAndPreview, loadItunesAlbum, expandArtistBio,
loadAlbums, loadStarred, loadRandom, loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist,
} from './views.js'; } from './views.js';
import { loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll } from './spotify.js'; import { loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll } from './spotify.js';
import { import {
@@ -282,7 +282,7 @@ Object.assign(window, {
openSettings, closeSettings, saveSettings, confirmDisconnect, checkBackend, openSettings, closeSettings, saveSettings, confirmDisconnect, checkBackend,
openSidebar, closeSidebar, switchNavMode, showCtxMenu, openSidebar, closeSidebar, switchNavMode, showCtxMenu,
// Views // Views
loadAlbums, loadStarred, loadRandom, loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll, loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist, loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll,
executeSpotifyDownload, executeSpotifyDownload,
openPlModal, closePlModal, addSongsToPlaylist, createAndAddPlaylist, createPlaylistPrompt, openPlModal, closePlModal, addSongsToPlaylist, createAndAddPlaylist, createPlaylistPrompt,
showSongInfo, closeInfoModal, showSongInfo, closeInfoModal,
+22
View File
@@ -557,11 +557,22 @@ export async function loadPlaylistDetail(pl) {
}; };
sl.innerHTML = ''; sl.innerHTML = '';
sl.classList.add('pl-draggable'); sl.classList.add('pl-draggable');
const plSearch = document.getElementById('playlist-search');
if (plSearch) plSearch.value = '';
songs.forEach((s,i) => sl.appendChild(makeSongItem(s, i, songs, { noArtistLink: true, noAlbumLink: true }))); songs.forEach((s,i) => sl.appendChild(makeSongItem(s, i, songs, { noArtistLink: true, noAlbumLink: true })));
_initPlaylistDragDrop(sl, songs, S._currentPlaylist); _initPlaylistDragDrop(sl, songs, S._currentPlaylist);
} catch(e) { sl.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; } } catch(e) { sl.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
} }
export function filterPlaylist(val) {
const q = val.trim().toLowerCase();
document.querySelectorAll('#playlist-songs .song-item').forEach(item => {
const title = (item.querySelector('.song-title')?.textContent || '').toLowerCase();
const artist = (item.querySelector('.song-artist')?.textContent || '').toLowerCase();
item.style.display = (!q || title.includes(q) || artist.includes(q)) ? '' : 'none';
});
}
function _initPlaylistDragDrop(container, songs, pl) { function _initPlaylistDragDrop(container, songs, pl) {
let dragSrc = null; let dragSrc = null;
let saveTimer = null; let saveTimer = null;
@@ -645,6 +656,8 @@ export async function loadStarred(sort = _starredSort) {
c.classList.toggle('active', c.dataset.sort === sort)); c.classList.toggle('active', c.dataset.sort === sort));
const el = document.getElementById('starred-songs'); const el = document.getElementById('starred-songs');
el.innerHTML = '<div class="spinner"></div>'; el.innerHTML = '<div class="spinner"></div>';
const searchEl = document.getElementById('starred-search');
if (searchEl) searchEl.value = '';
try { try {
const r = await api('getStarred2'); const r = await api('getStarred2');
let songs = (r.starred2?.song || []).map(s => ({...s, _starred: true})); let songs = (r.starred2?.song || []).map(s => ({...s, _starred: true}));
@@ -659,6 +672,15 @@ export async function loadStarred(sort = _starredSort) {
} catch(e) { el.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; } } catch(e) { el.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
} }
export function filterStarred(val) {
const q = val.trim().toLowerCase();
document.querySelectorAll('#starred-songs .song-item').forEach(item => {
const title = (item.querySelector('.song-title')?.textContent || '').toLowerCase();
const artist = (item.querySelector('.song-artist')?.textContent || '').toLowerCase();
item.style.display = (!q || title.includes(q) || artist.includes(q)) ? '' : 'none';
});
}
export async function loadRandom() { export async function loadRandom() {
const el = document.getElementById('random-songs'); const el = document.getElementById('random-songs');
el.innerHTML = '<div class="spinner"></div>'; el.innerHTML = '<div class="spinner"></div>';
+5
View File
@@ -718,6 +718,11 @@ nav{flex:1;overflow-y:auto;padding:4px 10px}
.sort-chip{padding:6px 14px;background:var(--surface);border:1px solid var(--border2);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;color:var(--text2);font-family:var(--font);transition:border-color .15s,color .15s,background .15s} .sort-chip{padding:6px 14px;background:var(--surface);border:1px solid var(--border2);border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;color:var(--text2);font-family:var(--font);transition:border-color .15s,color .15s,background .15s}
.sort-chip:hover{border-color:var(--accent);color:var(--accent)} .sort-chip:hover{border-color:var(--accent);color:var(--accent)}
.sort-chip.active{background:var(--accent);border-color:var(--accent);color:#000} .sort-chip.active{background:var(--accent);border-color:var(--accent);color:#000}
.list-search-wrap{position:relative;margin-bottom:12px}
.list-search-wrap svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--text3);pointer-events:none}
.list-search{width:100%;box-sizing:border-box;padding:8px 12px 8px 32px;background:var(--surface);border:1px solid var(--border2);border-radius:8px;color:var(--text1);font-size:13px;font-family:var(--font);outline:none;transition:border-color .15s}
.list-search:focus{border-color:var(--accent)}
.list-search::placeholder{color:var(--text3)}
.topsong-item{display:grid;grid-template-columns:28px 44px 1fr auto;align-items:center;gap:10px;padding:7px 10px;border-radius:8px;cursor:pointer;transition:background .15s} .topsong-item{display:grid;grid-template-columns:28px 44px 1fr auto;align-items:center;gap:10px;padding:7px 10px;border-radius:8px;cursor:pointer;transition:background .15s}
.topsong-item:hover{background:var(--surface)} .topsong-item:hover{background:var(--surface)}
.topsong-num{font-size:11px;color:var(--text3);font-family:var(--mono);text-align:center} .topsong-num{font-size:11px;color:var(--text3);font-family:var(--mono);text-align:center}