174 lines
6.5 KiB
JavaScript
174 lines
6.5 KiB
JavaScript
|
|
'use strict';
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Mood / genre tag filter
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
const MOOD_TAGS = [
|
|||
|
|
{ label: 'Fokus', tag: 'ambient' },
|
|||
|
|
{ label: 'Lo-fi', tag: 'lofi' },
|
|||
|
|
{ label: 'Jazz', tag: 'jazz' },
|
|||
|
|
{ label: 'Klassik', tag: 'classical' },
|
|||
|
|
{ label: 'Ambient', tag: 'ambient' },
|
|||
|
|
{ label: 'Metal', tag: 'metal' },
|
|||
|
|
{ label: 'Elektronisch', tag: 'electronic' },
|
|||
|
|
{ label: 'Wort', tag: 'talk' },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
function initMoodChips() {
|
|||
|
|
const container = $('mood-chips');
|
|||
|
|
if (!container) return;
|
|||
|
|
MOOD_TAGS.forEach(({ label, tag }) => {
|
|||
|
|
const btn = document.createElement('button');
|
|||
|
|
btn.className = 'mood-chip';
|
|||
|
|
btn.textContent = label;
|
|||
|
|
btn.onclick = () => {
|
|||
|
|
const input = $('search-input');
|
|||
|
|
if (input) input.value = tag;
|
|||
|
|
doSearch();
|
|||
|
|
};
|
|||
|
|
container.appendChild(btn);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Curated station lists
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
const CURATED_LISTS = [
|
|||
|
|
{
|
|||
|
|
id: 'focus',
|
|||
|
|
label: 'Fokus',
|
|||
|
|
stations: [
|
|||
|
|
{ name: 'SomaFM Drone Zone', url: 'https://ice6.somafm.com/dronezone-256-mp3' },
|
|||
|
|
{ name: 'SomaFM Groove Salad', url: 'https://ice5.somafm.com/groovesalad-128-aac' },
|
|||
|
|
{ name: 'Nightride FM', url: 'https://stream.nightride.fm/nightride.mp3' },
|
|||
|
|
{ name: 'Nightride FM Chillsynth', url: 'https://stream.nightride.fm/chillsynth.mp3' },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'lofi',
|
|||
|
|
label: 'Lo-fi / Chill',
|
|||
|
|
stations: [
|
|||
|
|
{ name: 'SomaFM Groove Salad Classic', url: 'https://ice6.somafm.com/gsclassic-128-mp3' },
|
|||
|
|
{ name: 'SomaFM Secret Agent', url: 'https://ice4.somafm.com/secretagent-128-mp3' },
|
|||
|
|
{ name: 'dublab DE', url: 'https://dublabde.out.airtime.pro/dublabde_a' },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'dark',
|
|||
|
|
label: 'Dark / Industrial',
|
|||
|
|
stations: [
|
|||
|
|
{ name: 'SomaFM Doomed', url: 'https://ice2.somafm.com/doomed-256-mp3' },
|
|||
|
|
{ name: 'Nightride FM Darksynth', url: 'https://stream.nightride.fm/darksynth.mp3' },
|
|||
|
|
{ name: 'Radio Caprice Industrial', url: 'http://79.120.39.202:9095/' },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'classical',
|
|||
|
|
label: 'Klassik',
|
|||
|
|
stations: [
|
|||
|
|
{ name: 'BR Klassik', url: 'https://dispatcher.rndfnk.com/br/brklassik/live/mp3/high' },
|
|||
|
|
{ name: 'SWR Kultur', url: 'https://f111.rndfnk.com/ard/swr/swr2/live/mp3/256/stream.mp3?aggregator=web' },
|
|||
|
|
{ name: 'Deutschlandfunk Kultur', url: 'https://st02.sslstream.dlf.de/dlf/02/high/aac/stream.aac?aggregator=web' },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
function initCuratedLists() {
|
|||
|
|
const container = document.getElementById('curated-lists');
|
|||
|
|
if (!container) return;
|
|||
|
|
|
|||
|
|
if (INITIAL_FEATURED && INITIAL_FEATURED.length) {
|
|||
|
|
const section = document.createElement('div');
|
|||
|
|
section.className = 'curated-section';
|
|||
|
|
section.innerHTML = `<div class="curated-label">★ Empfohlen</div>`;
|
|||
|
|
const ul = document.createElement('ul');
|
|||
|
|
ul.className = 'curated-stations';
|
|||
|
|
INITIAL_FEATURED.forEach(s => {
|
|||
|
|
const li = document.createElement('li');
|
|||
|
|
li.innerHTML = `<button class="btn btn-sm" onclick="playStation('${escapeAttr(s.url)}', '${escapeAttr(s.name)}', null)">▶</button>
|
|||
|
|
<span class="curated-name">${escapeHtml(s.name)}</span>
|
|||
|
|
${s.description ? `<span class="muted" style="font-size:0.78rem">${escapeHtml(s.description)}</span>` : ''}`;
|
|||
|
|
ul.appendChild(li);
|
|||
|
|
});
|
|||
|
|
section.appendChild(ul);
|
|||
|
|
container.appendChild(section);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CURATED_LISTS.forEach(list => {
|
|||
|
|
const section = document.createElement('div');
|
|||
|
|
section.className = 'curated-section';
|
|||
|
|
section.innerHTML = `<div class="curated-label">${list.label}</div>`;
|
|||
|
|
const ul = document.createElement('ul');
|
|||
|
|
ul.className = 'curated-stations';
|
|||
|
|
list.stations.forEach(s => {
|
|||
|
|
const li = document.createElement('li');
|
|||
|
|
li.innerHTML = `<button class="btn btn-sm" onclick="playStation('${escapeAttr(s.url)}', '${escapeAttr(s.name)}', null)">▶</button>
|
|||
|
|
<span class="curated-name">${escapeHtml(s.name)}</span>`;
|
|||
|
|
ul.appendChild(li);
|
|||
|
|
});
|
|||
|
|
section.appendChild(ul);
|
|||
|
|
container.appendChild(section);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Donation hint
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
const DONATION_HINT_THRESHOLD = 10;
|
|||
|
|
const DONATION_HINT_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|||
|
|
|
|||
|
|
function maybeShowDonationHint(stationUrl, stationName) {
|
|||
|
|
const station = INITIAL_SAVED.find(s => s.url === stationUrl);
|
|||
|
|
if (!station || station.play_count < DONATION_HINT_THRESHOLD) return;
|
|||
|
|
|
|||
|
|
const key = `diora_donation_hint_${stationUrl}`;
|
|||
|
|
const last = parseInt(localStorage.getItem(key) || '0', 10);
|
|||
|
|
if (Date.now() - last < DONATION_HINT_COOLDOWN_MS) return;
|
|||
|
|
|
|||
|
|
// Past the threshold just means "this station is a regular" — from then on
|
|||
|
|
// every single play would otherwise qualify, so only actually show the hint
|
|||
|
|
// on roughly 1 in 10 of those plays instead of every time.
|
|||
|
|
if (Math.random() >= 0.1) return;
|
|||
|
|
|
|||
|
|
const existing = document.getElementById('donation-hint');
|
|||
|
|
if (existing) existing.remove();
|
|||
|
|
|
|||
|
|
const el = document.createElement('div');
|
|||
|
|
el.id = 'donation-hint';
|
|||
|
|
el.innerHTML = `
|
|||
|
|
<span>Du hörst <strong>${escapeHtml(stationName)}</strong> oft – vielleicht magst du den Sender unterstützen ♥</span>
|
|||
|
|
<button onclick="dismissDonationHint('${escapeAttr(stationUrl)}')" title="Ausblenden">✕</button>
|
|||
|
|
`;
|
|||
|
|
document.body.appendChild(el);
|
|||
|
|
|
|||
|
|
setTimeout(() => dismissDonationHint(stationUrl), 12000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function dismissDonationHint(stationUrl) {
|
|||
|
|
localStorage.setItem(`diora_donation_hint_${stationUrl}`, Date.now());
|
|||
|
|
const el = document.getElementById('donation-hint');
|
|||
|
|
if (el) { el.classList.add('hiding'); setTimeout(() => el.remove(), 400); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Station notes
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
async function editNotes(pk, current) {
|
|||
|
|
const note = await customPrompt('Notiz zum Sender:', current || '');
|
|||
|
|
if (note === null) return; // cancelled
|
|||
|
|
fetch(`/radio/notes/${pk}/`, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken() },
|
|||
|
|
body: JSON.stringify({ notes: note }),
|
|||
|
|
}).then(r => {
|
|||
|
|
if (r.ok) {
|
|||
|
|
const cell = document.querySelector(`#saved-row-${pk} .notes-cell`);
|
|||
|
|
if (cell) cell.textContent = note;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|