feat: timer scrobble (12s), artist topsong star+menu, play count toggle
Scrobbling: - Replace end-of-track scrobble with 12s timer approach - Android: schedule on onTrackChanged, cancel on pause - Web: schedule in playCurrent, cancel in togglePlay pause + onEnded - Skip/new track resets the timer automatically (new song ID) Artist top songs: - Add star button and 3-dots context menu to every top song row - Topsong grid expanded to 5 columns to fit action buttons Settings — play count toggle: - New "Bibliothek" section with toggle switch - When on: shows play count in album/playlist song rows - Artist page always shows play count regardless of toggle - Persisted in ns_cfg localStorage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
f79817410b
commit
7e07fef7ba
7 files changed
+57
-13
No files matched your search
+11
-3
@@ -2,7 +2,7 @@ import { S } from './state.js';
|
||||
import { esc, formatDur, toast } from './utils.js';
|
||||
import { api, coverUrl, itunesSearch, itunesLookupAlbum, itunesLookupArtistSongs, itunesLookupArtistAlbums, itunesTopCharts, itunesTopSongs } from './api.js';
|
||||
// Circular imports — safe (only used inside function bodies)
|
||||
import { playQueue, dlSongLocal, playPreview } from './player.js';
|
||||
import { playQueue, dlSongLocal, playPreview, toggleStar } from './player.js';
|
||||
import { showCtxMenu, openEditPlModal, openDlModal, closeSidebar, switchNavMode } from './ui.js';
|
||||
import { closeNowPlaying, closeNpMenu } from './nowplaying.js';
|
||||
import { makeCard, makeAlbumCard, makeSongItem, makeGlobalTrackItem, mkSection } from './components.js';
|
||||
@@ -396,12 +396,20 @@ export async function loadArtistDetail(artist) {
|
||||
topSongs.forEach((s, i) => {
|
||||
const cv = coverUrl(s.coverArt, 128);
|
||||
const row = document.createElement('div'); row.className = 'topsong-item';
|
||||
if(s.starred && !s._starred) s._starred = true;
|
||||
row.innerHTML = `<div class="topsong-num">${i+1}</div>
|
||||
<div class="topsong-thumb">${cv ? `<img src="${cv}" loading="lazy">` : '<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:18px">🎵</div>'}</div>
|
||||
<div><div class="topsong-title">${esc(s.title)}</div><div style="font-size:11px;color:var(--text2)">${esc(s.album||'')}</div></div>
|
||||
<div class="topsong-plays">${s.playCount ? s.playCount.toLocaleString() : '—'}</div>`;
|
||||
<div style="min-width:0;flex:1"><div class="topsong-title">${esc(s.title)}</div><div style="font-size:11px;color:var(--text2)">${esc(s.album||'')}</div></div>
|
||||
<div class="topsong-plays">${s.playCount ? s.playCount.toLocaleString() : '—'}</div>
|
||||
<div class="song-actions" style="flex-shrink:0">
|
||||
<button class="icon-btn${s._starred?' starred':''}" title="Favorit"><svg width="12" height="12" viewBox="0 0 24 24" fill="${s._starred?'currentColor':'none'}" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg></button>
|
||||
<button class="icon-btn song-menu-btn" title="Mehr"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/></svg></button>
|
||||
</div>`;
|
||||
row.onclick = () => playQueue(topSongs, i);
|
||||
row.addEventListener('contextmenu', e => { e.preventDefault(); showCtxMenu(e, s, topSongs, i); });
|
||||
const btns = row.querySelectorAll('.icon-btn');
|
||||
btns[0].onclick = e => { e.stopPropagation(); toggleStar(s, btns[0]); };
|
||||
btns[1].onclick = e => { e.stopPropagation(); showCtxMenu(e, s, topSongs, i); };
|
||||
list.appendChild(row);
|
||||
});
|
||||
tsEl.appendChild(list);
|
||||
|
||||
Reference in new issue
Block a user