fix: dispArtistById prefers Navidrome sortName over kuromoji translation
Added _artistSortMap populated from getArtists (artist listing) and getArtist (artist detail) responses. dispArtistById now checks the Navidrome sort tag first, falls back to kuromoji only when missing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
da68afe0e0
commit
4c3ec5726c
3 files changed
+21
-6
No files matched your search
+14
-2
@@ -91,7 +91,8 @@ export async function ndAuth() {
|
|||||||
// Fetched once per session from the backend, cached in memory.
|
// Fetched once per session from the backend, cached in memory.
|
||||||
let _romajiIndex = null;
|
let _romajiIndex = null;
|
||||||
let _romajiMap = null; // Map<songId, { titleRomaji, artistRomaji, albumRomaji }>
|
let _romajiMap = null; // Map<songId, { titleRomaji, artistRomaji, albumRomaji }>
|
||||||
let _artistRomajiMap = null; // Map<artistId, artistRomaji>
|
let _artistRomajiMap = null; // Map<artistId, artistRomaji> — kuromoji backend
|
||||||
|
let _artistSortMap = null; // Map<artistId, sortName> — Navidrome sort tag (preferred)
|
||||||
let _albumRomajiMap = null; // Map<albumName(JP), albumRomaji> — for album cards without song ID
|
let _albumRomajiMap = null; // Map<albumName(JP), albumRomaji> — for album cards without song ID
|
||||||
let _romajiIndexFetch = null; // in-flight promise dedup
|
let _romajiIndexFetch = null; // in-flight promise dedup
|
||||||
|
|
||||||
@@ -121,7 +122,16 @@ export async function fetchRomajiIndex() {
|
|||||||
return _romajiIndexFetch;
|
return _romajiIndexFetch;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invalidateRomajiIndex() { _romajiIndex = null; _romajiMap = null; _artistRomajiMap = null; _albumRomajiMap = null; }
|
export function invalidateRomajiIndex() { _romajiIndex = null; _romajiMap = null; _artistRomajiMap = null; _artistSortMap = null; _albumRomajiMap = null; }
|
||||||
|
|
||||||
|
// Populate _artistSortMap from Navidrome artist objects (OpenSubsonic sortName field).
|
||||||
|
// Called whenever getArtists or getArtist data is loaded so dispArtistById can use it.
|
||||||
|
export function cacheArtistSortNames(artists) {
|
||||||
|
if (!_artistSortMap) _artistSortMap = new Map();
|
||||||
|
for (const a of artists) {
|
||||||
|
if (a.id && a.sortName && !_isJp(a.sortName)) _artistSortMap.set(String(a.id), a.sortName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── ROMAJI OVERRIDES ───
|
// ─── ROMAJI OVERRIDES ───
|
||||||
// Stored server-side (shared across all clients). Cached in memory per session.
|
// Stored server-side (shared across all clients). Cached in memory per session.
|
||||||
@@ -164,6 +174,8 @@ export function dispArtistById(id, name) {
|
|||||||
if (!S.cfg.showRomaji) return name || '';
|
if (!S.cfg.showRomaji) return name || '';
|
||||||
const ov = getRomajiOverrides();
|
const ov = getRomajiOverrides();
|
||||||
if (name && ov[name]) return ov[name];
|
if (name && ov[name]) return ov[name];
|
||||||
|
const sort = _artistSortMap?.get(String(id));
|
||||||
|
if (sort) return sort;
|
||||||
return _artistRomajiMap?.get(String(id)) || name || '';
|
return _artistRomajiMap?.get(String(id)) || name || '';
|
||||||
}
|
}
|
||||||
export function dispAlbumName(name) {
|
export function dispAlbumName(name) {
|
||||||
|
|||||||
+5
-3
@@ -1,6 +1,6 @@
|
|||||||
import { S } from '../state.js';
|
import { S } from '../state.js';
|
||||||
import { esc, toast } from '../utils.js';
|
import { esc, toast } from '../utils.js';
|
||||||
import { api, coverUrl, itunesSearch, itunesLookupAlbum, itunesLookupArtistSongs, itunesLookupArtistAlbums, fetchRomajiIndex, dispArtistById } from '../api.js';
|
import { api, coverUrl, itunesSearch, itunesLookupAlbum, itunesLookupArtistSongs, itunesLookupArtistAlbums, fetchRomajiIndex, dispArtistById, cacheArtistSortNames } from '../api.js';
|
||||||
import { makeCard, makeAlbumCard, makeGlobalTrackItem, makeSongItem } from '../ui/components.js';
|
import { makeCard, makeAlbumCard, makeGlobalTrackItem, makeSongItem } from '../ui/components.js';
|
||||||
import { playQueue, dlSongLocal, toggleStar, parseArtists } from '../player/player.js';
|
import { playQueue, dlSongLocal, toggleStar, parseArtists } from '../player/player.js';
|
||||||
import { showCtxMenu, openDlModal } from '../ui/ui.js';
|
import { showCtxMenu, openDlModal } from '../ui/ui.js';
|
||||||
@@ -99,7 +99,9 @@ export async function loadArtistDetail(artist) {
|
|||||||
// We reveal the hero with the album cover immediately, then patch in the artist photo async.
|
// We reveal the hero with the album cover immediately, then patch in the artist photo async.
|
||||||
const [albumRes] = await Promise.allSettled([albumP]);
|
const [albumRes] = await Promise.allSettled([albumP]);
|
||||||
|
|
||||||
const _firstAlbums = albumRes.status === 'fulfilled' ? (albumRes.value?.artist?.album || []) : [];
|
const _ndArtist = albumRes.status === 'fulfilled' ? albumRes.value?.artist : null;
|
||||||
|
if (_ndArtist) cacheArtistSortNames([_ndArtist]);
|
||||||
|
const _firstAlbums = _ndArtist?.album || [];
|
||||||
const _bgCover = _firstAlbums[0] ? coverUrl(_firstAlbums[0].coverArt, 512) : null;
|
const _bgCover = _firstAlbums[0] ? coverUrl(_firstAlbums[0].coverArt, 512) : null;
|
||||||
|
|
||||||
const _revealHero = () => requestAnimationFrame(() => {
|
const _revealHero = () => requestAnimationFrame(() => {
|
||||||
@@ -171,7 +173,7 @@ export async function loadArtistDetail(artist) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Top Songs ──
|
// ── Top Songs ──
|
||||||
const ndAlbums = albumRes.status === 'fulfilled' ? (albumRes.value?.artist?.album || []) : [];
|
const ndAlbums = _ndArtist?.album || [];
|
||||||
const topSongs = topRes.status === 'fulfilled' ? (topRes.value?.topSongs?.song || []) : [];
|
const topSongs = topRes.status === 'fulfilled' ? (topRes.value?.topSongs?.song || []) : [];
|
||||||
const iTracks = iTracksRes.status === 'fulfilled' ? (iTracksRes.value || []).filter(t => t.wrapperType === 'track') : [];
|
const iTracks = iTracksRes.status === 'fulfilled' ? (iTracksRes.value || []).filter(t => t.wrapperType === 'track') : [];
|
||||||
const iAlbums = iAlbumsRes.status === 'fulfilled' ? (iAlbumsRes.value || []) : [];
|
const iAlbums = iAlbumsRes.status === 'fulfilled' ? (iAlbumsRes.value || []) : [];
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
import { S } from '../state.js';
|
import { S } from '../state.js';
|
||||||
import { esc } from '../utils.js';
|
import { esc } from '../utils.js';
|
||||||
import { api, apiCached, apiIsCached, coverUrl, fetchRomajiIndex, dispArtistById } from '../api.js';
|
import { api, apiCached, apiIsCached, coverUrl, fetchRomajiIndex, dispArtistById, cacheArtistSortNames } from '../api.js';
|
||||||
import { makeCard, makeAlbumCard, makeSongItem } from '../ui/components.js';
|
import { makeCard, makeAlbumCard, makeSongItem } from '../ui/components.js';
|
||||||
import { playQueue } from '../player/player.js';
|
import { playQueue } from '../player/player.js';
|
||||||
// Circular — safe (called inside event handlers)
|
// Circular — safe (called inside event handlers)
|
||||||
@@ -202,6 +202,7 @@ export async function loadArtists() {
|
|||||||
try {
|
try {
|
||||||
const r = await apiCached('getArtists');
|
const r = await apiCached('getArtists');
|
||||||
const all = (r.artists?.index || []).flatMap(i => i.artist || []);
|
const all = (r.artists?.index || []).flatMap(i => i.artist || []);
|
||||||
|
cacheArtistSortNames(all);
|
||||||
if (S.cfg.showRomaji) await fetchRomajiIndex().catch(() => {});
|
if (S.cfg.showRomaji) await fetchRomajiIndex().catch(() => {});
|
||||||
g.innerHTML = '';
|
g.innerHTML = '';
|
||||||
all.forEach(a => {
|
all.forEach(a => {
|
||||||
|
|||||||
Reference in new issue
Block a user