feat(artist pin): pin by Deezer link/ID when search can't surface the artist
Deezer's /search/artist is a ranked, incomplete fuzzy match — low-profile
same-name artists (e.g. Nana id 80239872) never appear in results. Add a
"paste Deezer link or ID" field to the pin picker that looks the artist up
directly via /artist/{id} (new deezerLookupArtist) and pins it. Same reliable
route as the importer direct link vs yt-dlp search.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f251d555f
commit
ba2306656e
3 files changed
+62
-6
No files matched your search
+47
-5
@@ -4,8 +4,19 @@
|
||||
// NovaSonic would otherwise guess the wrong one (fans / top songs / albums / image).
|
||||
// The mapping is stored server-side (shared across users) — see api.js.
|
||||
|
||||
import { deezerSearch, setArtistOverride, removeArtistOverride, getArtistOverride } from '../api.js';
|
||||
import { esc, fmtCount } from '../utils.js';
|
||||
import { deezerSearch, deezerLookupArtist, setArtistOverride, removeArtistOverride, getArtistOverride } from '../api.js';
|
||||
import { esc, fmtCount, toast } from '../utils.js';
|
||||
|
||||
// Pull a Deezer artist id out of whatever the user pastes: a full URL
|
||||
// (deezer.com/en/artist/80239872, /de/artist/…?…), or a bare id ("80239872").
|
||||
function _parseDeezerArtistId(raw) {
|
||||
const s = String(raw || '').trim();
|
||||
if (!s) return null;
|
||||
const m = s.match(/artist\/(\d+)/i);
|
||||
if (m) return m[1];
|
||||
if (/^\d+$/.test(s)) return s;
|
||||
return null;
|
||||
}
|
||||
|
||||
let _overlay = null;
|
||||
let _ctx = null; // { ndArtistId, ndName }
|
||||
@@ -50,7 +61,20 @@ async function _render() {
|
||||
const clearBtn = cur
|
||||
? `<button onclick="clearArtistPin()" style="width:100%;padding:10px;margin-bottom:10px;border-radius:9px;border:1px solid var(--border);background:transparent;color:var(--danger);font-family:var(--font);font-weight:600;cursor:pointer">Pin entfernen (aktuell: ${esc(cur.name || 'gepinnt')})</button>`
|
||||
: '';
|
||||
body.innerHTML = clearBtn + `<div style="text-align:center;color:var(--text2);padding:24px 0">Suche…</div>`;
|
||||
// Deezer's /search/artist is a ranked, incomplete fuzzy match — low-profile same-name artists
|
||||
// (e.g. Nana id 80239872) never surface. Paste the Deezer link/id to pin directly (reliable route).
|
||||
const linkBox = `
|
||||
<div style="display:flex;gap:8px;margin-bottom:12px">
|
||||
<input id="artist-pin-link" type="text" placeholder="Deezer-Link oder ID einfügen…"
|
||||
style="flex:1;min-width:0;padding:10px 12px;border-radius:9px;border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--font);font-size:13px">
|
||||
<button id="artist-pin-link-btn" style="padding:0 16px;border-radius:9px;border:none;background:var(--accent);color:#fff;font-family:var(--font);font-weight:700;cursor:pointer;white-space:nowrap">Pinnen</button>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--text2);margin:-6px 2px 12px">Nicht in der Liste? Öffne den Künstler auf deezer.com und füge den Link ein.</div>`;
|
||||
// Render the shell once (clear button + link box stay put); only the results list re-renders,
|
||||
// so a value the user is typing into the link field survives the async search resolving.
|
||||
body.innerHTML = clearBtn + linkBox + `<div id="artist-pin-results"><div style="text-align:center;color:var(--text2);padding:24px 0">Suche…</div></div>`;
|
||||
_wireLinkBox();
|
||||
const resultsEl = body.querySelector('#artist-pin-results');
|
||||
|
||||
let results = [];
|
||||
try { results = (await deezerSearch(_ctx.ndName, 'musicArtist', 8)) || []; } catch {}
|
||||
@@ -67,8 +91,26 @@ async function _render() {
|
||||
</div>
|
||||
</div>`).join('');
|
||||
|
||||
body.innerHTML = clearBtn + (rows || `<div style="text-align:center;color:var(--text2);padding:24px 0">Keine Deezer-Künstler gefunden.</div>`);
|
||||
body.querySelectorAll('.artist-pin-cand').forEach(el => el.addEventListener('click', () => _pick(el.dataset)));
|
||||
resultsEl.innerHTML = rows || `<div style="text-align:center;color:var(--text2);padding:24px 0">Keine Deezer-Künstler gefunden.</div>`;
|
||||
resultsEl.querySelectorAll('.artist-pin-cand').forEach(el => el.addEventListener('click', () => _pick(el.dataset)));
|
||||
}
|
||||
|
||||
// Wire the "paste a Deezer link/id" box → look the artist up directly and pin it.
|
||||
function _wireLinkBox() {
|
||||
const input = _overlay.querySelector('#artist-pin-link');
|
||||
const btn = _overlay.querySelector('#artist-pin-link-btn');
|
||||
if (!input || !btn) return;
|
||||
const go = async () => {
|
||||
const id = _parseDeezerArtistId(input.value);
|
||||
if (!id) { toast('Kein gültiger Deezer-Link / keine ID'); return; }
|
||||
btn.disabled = true; btn.textContent = '…';
|
||||
const a = await deezerLookupArtist(id);
|
||||
btn.disabled = false; btn.textContent = 'Pinnen';
|
||||
if (!a) { toast('Deezer-Künstler nicht gefunden'); return; }
|
||||
await _pick({ id: String(a.artistId), name: a.artistName || _ctx.ndName, img: a.artworkUrl100 || '', fans: a.fanCount != null ? String(a.fanCount) : '' });
|
||||
};
|
||||
btn.addEventListener('click', go);
|
||||
input.addEventListener('keydown', e => { if (e.key === 'Enter') go(); });
|
||||
}
|
||||
|
||||
async function _pick(ds) {
|
||||
|
||||
Reference in new issue
Block a user