(function () { if (window.__fc_addsource_injected) return; window.__fc_addsource_injected = true; // Cached probe result for the current URL so click-handlers know which // action to dispatch without round-tripping again. let currentProbe = null; evaluate(); const reEval = () => evaluate(); window.addEventListener('popstate', reEval); const origPush = history.pushState; history.pushState = function () { origPush.apply(this, arguments); reEval(); }; async function evaluate() { const url = window.location.href; const platform = getPlatformFromUrl(url); const onArtist = platform && isArtistPage(url, platform); const btn = document.getElementById('fc-add-source-btn'); if (!onArtist) { if (btn) btn.remove(); currentProbe = null; return; } // On artist pages, ask the backend what state the URL is in BEFORE // injecting the button — so the chip can render the right state on // first paint instead of flashing the generic "Add" copy and // updating afterwards. let probe; try { probe = await browser.runtime.sendMessage({ type: 'PROBE_SOURCE', url }); } catch (e) { probe = { error: e?.message || 'probe failed' }; } currentProbe = probe; if (probe?.state === 'unknown_platform') { if (btn) btn.remove(); return; } renderButton(probe); } function renderButton(probe) { let btn = document.getElementById('fc-add-source-btn'); if (!btn) { btn = document.createElement('button'); btn.id = 'fc-add-source-btn'; btn.addEventListener('click', onClick); document.body.appendChild(btn); } // Reset state classes so re-renders (SPA navigation) don't stack. btn.className = 'fc-add-source-btn'; btn.classList.add(`fc-add-source-btn--${stateModifier(probe)}`); btn.textContent = labelFor(probe); btn.disabled = false; } function stateModifier(probe) { if (!probe || probe.error) return 'new'; return ({ source_match: 'source-match', artist_match: 'artist-match', new: 'new', })[probe.state] || 'new'; } function labelFor(probe) { if (!probe || probe.error) return '+ Add to FabledCurator'; const platformName = platformDisplayName(probe.platform); const artistName = probe.artist?.name; switch (probe.state) { case 'source_match': return `✓ In FabledCurator · ${platformName}`; case 'artist_match': return `+ Add ${platformName} source to ${artistName || 'artist'}`; case 'new': default: return '+ Add to FabledCurator'; } } function platformDisplayName(key) { return PLATFORMS[key]?.name || key || ''; } async function onClick() { const btn = document.getElementById('fc-add-source-btn'); if (!btn) return; btn.disabled = true; const original = btn.textContent; const probe = currentProbe; if (probe?.state === 'source_match') { btn.textContent = 'Opening…'; try { const r = await browser.runtime.sendMessage({ type: 'OPEN_ARTIST_PAGE', slug: probe.artist?.slug, }); if (r?.error) showToast(`Error: ${r.error}`, 'error'); } catch (e) { showToast(`Error: ${e.message}`, 'error'); } finally { btn.disabled = false; btn.textContent = original; } return; } btn.textContent = 'Adding…'; try { const r = await browser.runtime.sendMessage({ type: 'ADD_AS_SOURCE', url: window.location.href, }); if (r?.error) { showToast(`Error: ${r.error}`, 'error'); } else { const verb = r.created_source ? 'Added' : 'Already a source for'; showToast(`${verb} ${r.artist?.name || 'artist'} (${r.source?.platform || ''})`, 'success'); // Re-probe so the chip flips green without waiting for the next // navigation. evaluate(); return; } } catch (e) { showToast(`Error: ${e.message}`, 'error'); } finally { btn.disabled = false; btn.textContent = original; } } function showToast(text, kind) { const t = document.createElement('div'); t.className = `fc-toast fc-toast--${kind}`; t.textContent = text; document.body.appendChild(t); setTimeout(() => t.remove(), 4000); } })();