(function(root) { 'use strict'; const ranked = entries => [...entries].sort((a, b) => Number(b.approved) - Number(a.approved) || b.votes.length - a.votes.length); function render(container, { state, host, pid, send, search }) { const doc = container.ownerDocument; if (host) { const label = doc.createElement('label'); label.className = 'pt-toggle'; const toggle = doc.createElement('input'); toggle.type = 'checkbox'; toggle.checked = state.enabled; toggle.onchange = () => send({ type: 'dj', action: 'enable', enabled: toggle.checked }); label.append(toggle, ' Party DJ mode'); container.append(label); } if (!state.enabled) return; const heading = doc.createElement('h3'); heading.textContent = 'Party Queue'; container.append(heading); const form = doc.createElement('div'); form.className = 'party-dj-search'; const input = doc.createElement('input'); input.type = 'search'; input.placeholder = 'Suggest a song'; input.setAttribute('aria-label', 'Search for a party suggestion'); const button = doc.createElement('button'); button.type = 'button'; button.textContent = 'Search'; const results = doc.createElement('div'); let request = 0; button.onclick = async () => { const query = input.value.trim(); if (!query) return; const token = ++request; button.disabled = true; results.textContent = 'Searching…'; try { const videos = await search(query); if (!container.isConnected || token !== request) return; results.replaceChildren(); for (const video of videos.slice(0, 8)) { const b = doc.createElement('button'); b.type = 'button'; b.textContent = `Suggest: ${video.title || video.id}`; b.onclick = () => send({ type: 'dj', action: 'suggest', video }); results.append(b); } if (!videos.length) results.textContent = 'No matches.'; } catch { results.textContent = 'Search unavailable. Try again.'; } finally { button.disabled = false; } }; input.onkeydown = e => { if (e.key === 'Enter') { e.preventDefault(); button.click(); } }; form.append(input, button); container.append(form, results); for (const entry of ranked(state.entries)) { const row = doc.createElement('div'); row.className = 'party-dj-row'; const title = doc.createElement('b'); title.textContent = entry.video.title; row.append(title); const status = doc.createElement('small'); status.textContent = entry.approved ? 'Upcoming' : 'Awaiting host approval'; row.append(status); for (const action of ['vote', ...(host ? [...(entry.approved ? [] : ['approve']), 'remove'] : [])]) { const b = doc.createElement('button'); b.type = 'button'; b.textContent = action === 'vote' ? `👍 ${entry.votes.length}` : action === 'approve' ? 'Approve' : 'Remove'; b.setAttribute('aria-label', `${action} ${entry.video.title}`); if (action === 'vote') { b.disabled = entry.votes.includes(pid); b.setAttribute('aria-pressed', String(b.disabled)); } b.onclick = () => send({ type: 'dj', action, id: entry.video.id }); row.append(b); } container.append(row); } } function register(open) { root.SettingsSections.register({ id: 'party-dj', title: 'Watch party', cluster: 'Live & worship', summary: () => 'Shared suggestions · host approval', render(container) { const p = container.ownerDocument.createElement('p'); p.textContent = 'Party DJ mode is off until the host enables it. Guests suggest songs and vote once per song; approved songs play in vote order.'; const b = container.ownerDocument.createElement('button'); b.type = 'button'; b.className = 'settings-back'; b.textContent = 'Open watch party'; b.onclick = open; container.append(p, b); } }); } const api = { ranked, render, register }; if (typeof module !== 'undefined') module.exports = api; else root.PartyDJ = api; })(typeof window !== 'undefined' ? window : globalThis);