diff --git a/CHANGELOG.md b/CHANGELOG.md index 98c83206..fbb800c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -465,6 +465,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Genre Mix loads genres through the scoped catalog (`fetchGenreCatalog` / local index) instead of server-wide `getGenres`, matching the sidebar library filter. +### Artist detail — external link buttons keep border on hover + +**By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#966](https://github.com/Psychotoxical/psysonic/pull/966)** + +* Last.fm, Wikipedia, and Favorite used a hover border color that matched the card background — the rim disappeared instead of highlighting the inner fill like Play/Shuffle/Radio (`btn-surface`). +* Playlist detail — Play and Add Songs now show tooltips like the other header actions; track count uses proper pluralization (`1 song` vs `N songs`) with standard `count` interpolation. +* Suggested Songs rows now render BPM (and other optional columns like genre, play count, last played) — the column switch was missing those cases. + + ### In-page browse — virtual scroll and cover-art priority **By [@cucadmuh](https://github.com/cucadmuh), PR [#783](https://github.com/Psychotoxical/psysonic/pull/783)** diff --git a/src/components/playlist/PlaylistHero.tsx b/src/components/playlist/PlaylistHero.tsx index b04240a3..ea40b1b3 100644 --- a/src/components/playlist/PlaylistHero.tsx +++ b/src/components/playlist/PlaylistHero.tsx @@ -133,7 +133,7 @@ export default function PlaylistHero({ )}
- {t('playlists.songs', { n: songs.length })} + {t('playlists.songs', { count: songs.length })} {songs.length > 0 && · {totalDurationLabel(songs)}} {playlist.public !== undefined && ( @@ -146,7 +146,12 @@ export default function PlaylistHero({
- diff --git a/src/components/playlist/PlaylistSuggestions.tsx b/src/components/playlist/PlaylistSuggestions.tsx index c37690a4..217a70a8 100644 --- a/src/components/playlist/PlaylistSuggestions.tsx +++ b/src/components/playlist/PlaylistSuggestions.tsx @@ -11,9 +11,11 @@ import { usePlaylistLayoutStore } from '../../store/playlistLayoutStore'; import { songToTrack } from '../../utils/playback/songToTrack'; import { getQueueTracksView } from '../../utils/library/queueTrackView'; import { codecLabel } from '../../utils/componentHelpers/playlistDetailHelpers'; +import { formatLastSeen } from '../../utils/componentHelpers/userMgmtHelpers'; import { formatTrackTime } from '../../utils/format/formatDuration'; +import i18n from '../../i18n'; -const PL_CENTERED = new Set(['favorite', 'rating', 'duration']); +const PL_CENTERED = new Set(['favorite', 'rating', 'duration', 'playCount', 'bpm']); interface Props { songs: SubsonicSong[]; @@ -172,6 +174,18 @@ export default function PlaylistSuggestions({ {(song.suffix || (showBitrate && song.bitRate)) && {codecLabel(song, showBitrate)}}
); + case 'genre': return ( +
{song.genre ?? '—'}
+ ); + case 'playCount': return ( +
{song.playCount ?? '—'}
+ ); + case 'lastPlayed': return ( +
{song.played ? formatLastSeen(song.played, i18n.language, '—') : '—'}
+ ); + case 'bpm': return ( +
{song.bpm && song.bpm > 0 ? song.bpm : '—'}
+ ); case 'delete': return (
- {t('playlists.songs', { n: filteredSongCountByPlaylist[pl.id] ?? pl.songCount })} + {t('playlists.songs', { count: filteredSongCountByPlaylist[pl.id] ?? pl.songCount })} {(filteredDurationByPlaylist[pl.id] ?? pl.duration) > 0 && ( <> · {formatHumanHoursMinutes(filteredDurationByPlaylist[pl.id] ?? pl.duration)} )} diff --git a/src/locales/de/playlists.ts b/src/locales/de/playlists.ts index 4bb36c81..f111d701 100644 --- a/src/locales/de/playlists.ts +++ b/src/locales/de/playlists.ts @@ -9,8 +9,10 @@ export const playlists = { emptyPlaylist: 'Diese Playlist ist leer.', addFirstSong: 'Ersten Song hinzufügen', notFound: 'Playlist nicht gefunden.', - songs: '{{n}} Songs', + songs_one: '{{count}} Song', + songs_other: '{{count}} Songs', playAll: 'Alle abspielen', + playTooltip: 'Playlist abspielen', shuffle: 'Zufallswiedergabe', addToQueue: 'Zur Warteschlange', back: 'Zurück zu Playlists', @@ -18,6 +20,7 @@ export const playlists = { confirmDelete: 'Nochmals klicken zum Bestätigen', removeSong: 'Aus Playlist entfernen', addSongs: 'Songs hinzufügen', + addSongsTooltip: 'Bibliothek durchsuchen und Titel hinzufügen', searchPlaceholder: 'Bibliothek durchsuchen…', noResults: 'Keine Ergebnisse.', suggestions: 'Vorgeschlagene Songs', diff --git a/src/locales/en/playlists.ts b/src/locales/en/playlists.ts index 272f0d14..11fb75c9 100644 --- a/src/locales/en/playlists.ts +++ b/src/locales/en/playlists.ts @@ -9,8 +9,10 @@ export const playlists = { emptyPlaylist: 'This playlist is empty.', addFirstSong: 'Add your first song', notFound: 'Playlist not found.', - songs: '{{n}} songs', + songs_one: '{{count}} song', + songs_other: '{{count}} songs', playAll: 'Play All', + playTooltip: 'Play playlist', shuffle: 'Shuffle', addToQueue: 'Add to Queue', back: 'Back to Playlists', @@ -18,6 +20,7 @@ export const playlists = { confirmDelete: 'Click again to confirm', removeSong: 'Remove from playlist', addSongs: 'Add Songs', + addSongsTooltip: 'Search your library to add tracks', searchPlaceholder: 'Search your library…', noResults: 'No results.', suggestions: 'Suggested Songs', diff --git a/src/locales/es/playlists.ts b/src/locales/es/playlists.ts index 2f7e015e..1544bbb6 100644 --- a/src/locales/es/playlists.ts +++ b/src/locales/es/playlists.ts @@ -9,8 +9,10 @@ export const playlists = { emptyPlaylist: 'Esta lista está vacía.', addFirstSong: 'Agrega tu primera canción', notFound: 'Lista no encontrada.', - songs: '{{n}} canciones', + songs_one: '{{count}} canción', + songs_other: '{{count}} canciones', playAll: 'Reproducir Todo', + playTooltip: 'Reproducir lista', shuffle: 'Aleatorio', addToQueue: 'Agregar a Cola', back: 'Volver a Listas', @@ -18,6 +20,7 @@ export const playlists = { confirmDelete: 'Click de nuevo para confirmar', removeSong: 'Quitar de lista', addSongs: 'Agregar Canciones', + addSongsTooltip: 'Buscar en la biblioteca y agregar canciones', searchPlaceholder: 'Buscar en tu biblioteca…', noResults: 'Sin resultados.', suggestions: 'Canciones Sugeridas', diff --git a/src/locales/fr/playlists.ts b/src/locales/fr/playlists.ts index 32da819c..c73d20e5 100644 --- a/src/locales/fr/playlists.ts +++ b/src/locales/fr/playlists.ts @@ -9,8 +9,10 @@ export const playlists = { emptyPlaylist: 'Cette playlist est vide.', addFirstSong: 'Ajouter votre premier titre', notFound: 'Playlist introuvable.', - songs: '{{n}} titres', + songs_one: '{{count}} titre', + songs_other: '{{count}} titres', playAll: 'Tout lire', + playTooltip: 'Lire la playlist', shuffle: 'Aléatoire', addToQueue: 'Ajouter à la file', back: 'Retour aux playlists', @@ -18,6 +20,7 @@ export const playlists = { confirmDelete: 'Cliquer à nouveau pour confirmer', removeSong: 'Retirer de la playlist', addSongs: 'Ajouter des titres', + addSongsTooltip: 'Rechercher dans la bibliothèque et ajouter des titres', searchPlaceholder: 'Rechercher dans la bibliothèque…', noResults: 'Aucun résultat.', suggestions: 'Titres suggérés', diff --git a/src/locales/nb/playlists.ts b/src/locales/nb/playlists.ts index 2e59e242..1ea5969f 100644 --- a/src/locales/nb/playlists.ts +++ b/src/locales/nb/playlists.ts @@ -9,8 +9,10 @@ export const playlists = { emptyPlaylist: 'Denne spillelisten er tom.', addFirstSong: 'Legg til din første sang', notFound: 'Spillelisten ble ikke funnet.', - songs: '{{n}} sanger', + songs_one: '{{count}} sang', + songs_other: '{{count}} sanger', playAll: 'Spill alle', + playTooltip: 'Spill spilleliste', shuffle: 'Bland', addToQueue: 'Legg til i kø', back: 'Tilbake til spillelister', @@ -18,6 +20,7 @@ export const playlists = { confirmDelete: 'Klikk igjen for å bekrefte', removeSong: 'Fjern fra spilleliste', addSongs: 'Legg til sanger', + addSongsTooltip: 'Søk i biblioteket og legg til sanger', searchPlaceholder: 'Søk i biblioteket ditt…', noResults: 'Ingen resultater.', suggestions: 'Foreslåtte sanger', diff --git a/src/locales/nl/playlists.ts b/src/locales/nl/playlists.ts index 87d7c1a7..ba1c93f9 100644 --- a/src/locales/nl/playlists.ts +++ b/src/locales/nl/playlists.ts @@ -9,8 +9,10 @@ export const playlists = { emptyPlaylist: 'Deze playlist is leeg.', addFirstSong: 'Voeg je eerste nummer toe', notFound: 'Playlist niet gevonden.', - songs: '{{n}} nummers', + songs_one: '{{count}} nummer', + songs_other: '{{count}} nummers', playAll: 'Alles afspelen', + playTooltip: 'Playlist afspelen', shuffle: 'Willekeurig', addToQueue: 'Aan wachtrij toevoegen', back: 'Terug naar playlists', @@ -18,6 +20,7 @@ export const playlists = { confirmDelete: 'Nogmaals klikken om te bevestigen', removeSong: 'Uit playlist verwijderen', addSongs: 'Nummers toevoegen', + addSongsTooltip: 'Bibliotheek doorzoeken en nummers toevoegen', searchPlaceholder: 'Doorzoek bibliotheek…', noResults: 'Geen resultaten.', suggestions: 'Aanbevolen nummers', diff --git a/src/locales/ro/playlists.ts b/src/locales/ro/playlists.ts index d1734d63..81da1aa8 100644 --- a/src/locales/ro/playlists.ts +++ b/src/locales/ro/playlists.ts @@ -9,8 +9,10 @@ export const playlists = { emptyPlaylist: 'Acest playlist este gol.', addFirstSong: 'Adaugă prima ta piesă', notFound: 'Playlistul nu a fost găsit.', - songs: '{{n}} piese', + songs_one: '{{count}} piesă', + songs_other: '{{count}} piese', playAll: 'Redă tot', + playTooltip: 'Redă playlistul', shuffle: 'Amestecă', addToQueue: 'Adaugă la Coadă', back: 'Înapoi la Playlisturi', @@ -18,6 +20,7 @@ export const playlists = { confirmDelete: 'Apasă din nou pentru a confirma', removeSong: 'Șterge din playlist', addSongs: 'Adaugă Piese', + addSongsTooltip: 'Caută în librărie și adaugă piese', searchPlaceholder: 'Caută în librărie…', noResults: 'Niciun rezultat.', suggestions: 'Piese sugerate', diff --git a/src/locales/ru/playlists.ts b/src/locales/ru/playlists.ts index 7aee0d16..7b90f20d 100644 --- a/src/locales/ru/playlists.ts +++ b/src/locales/ru/playlists.ts @@ -9,8 +9,10 @@ export const playlists = { emptyPlaylist: 'Плейлист пуст.', addFirstSong: 'Добавьте первый трек', notFound: 'Плейлист не найден.', - songs: 'Треков: {{n}}', + songs_one: '{{count}} трек', + songs_other: '{{count}} треков', playAll: 'Воспроизвести всё', + playTooltip: 'Воспроизвести плейлист', shuffle: 'Перемешать', addToQueue: 'В очередь', back: 'К списку плейлистов', @@ -18,6 +20,7 @@ export const playlists = { confirmDelete: 'Нажмите ещё раз для подтверждения', removeSong: 'Убрать из плейлиста', addSongs: 'Добавить треки', + addSongsTooltip: 'Найти треки в библиотеке и добавить в плейлист', searchPlaceholder: 'Поиск по библиотеке…', noResults: 'Ничего не найдено.', suggestions: 'Рекомендации', diff --git a/src/locales/zh/playlists.ts b/src/locales/zh/playlists.ts index b17b1074..9ed73631 100644 --- a/src/locales/zh/playlists.ts +++ b/src/locales/zh/playlists.ts @@ -9,8 +9,10 @@ export const playlists = { emptyPlaylist: '此播放列表为空。', addFirstSong: '添加第一首歌曲', notFound: '未找到播放列表。', - songs: '{{n}} 首歌曲', + songs_one: '{{count}} 首歌曲', + songs_other: '{{count}} 首歌曲', playAll: '全部播放', + playTooltip: '播放播放列表', shuffle: '随机播放', addToQueue: '添加到队列', back: '返回播放列表', @@ -18,6 +20,7 @@ export const playlists = { confirmDelete: '再次点击确认删除', removeSong: '从播放列表中移除', addSongs: '添加歌曲', + addSongsTooltip: '搜索音乐库并添加歌曲', searchPlaceholder: '搜索音乐库…', noResults: '无结果。', suggestions: '推荐歌曲', diff --git a/src/styles/components/playlist-edit-modal.css b/src/styles/components/playlist-edit-modal.css index fc2b5ba0..b8cb703f 100644 --- a/src/styles/components/playlist-edit-modal.css +++ b/src/styles/components/playlist-edit-modal.css @@ -216,7 +216,7 @@ padding: 5px 12px; border-radius: var(--radius-sm); background: var(--bg-card); - border: 1px solid var(--border); + border: 1px solid var(--ctp-surface1); color: var(--text-secondary); font-size: 12px; font-weight: 600; @@ -227,7 +227,7 @@ .artist-ext-link:hover { background: var(--bg-hover); color: var(--text-primary); - border-color: var(--border-subtle); + border-color: var(--ctp-overlay0); } /* Bio section */