Feat: Added support for new tags in XMLTV
This commit is contained in:
+12
-1
@@ -17,8 +17,19 @@
|
||||
font-size: 20px; font-weight: 700; color: var(--text);
|
||||
margin-bottom: 8px; line-height: 1.2;
|
||||
}
|
||||
.pm-subtitle {
|
||||
font-size: 13px; color: var(--text); margin-bottom: 10px;
|
||||
font-style: italic;
|
||||
}
|
||||
.pm-time {
|
||||
font-size: 12px; color: var(--accent); margin-bottom: 14px;
|
||||
font-size: 12px; color: var(--accent); margin-bottom: 10px;
|
||||
}
|
||||
.pm-meta {
|
||||
font-size: 12px; color: var(--text-muted); margin-bottom: 10px;
|
||||
}
|
||||
.pm-genres {
|
||||
font-size: 12px; color: var(--text); margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.pm-desc {
|
||||
font-size: 13px; color: var(--text-muted); line-height: 1.6;
|
||||
|
||||
@@ -4,12 +4,20 @@ function openProgramModal(ch, p) {
|
||||
const isLive = p.start <= now && p.stop > now;
|
||||
const dur = Math.round((p.stop - p.start) / 60000);
|
||||
const ep = fmtEpisode(p.season, p.episode);
|
||||
const genres = formatCategories(p.categories);
|
||||
const meta = [p.airDate, p.rating].filter(Boolean).join(' · ');
|
||||
|
||||
document.getElementById('pm-title').textContent = p.title;
|
||||
document.getElementById('pm-channel').textContent = ch.name;
|
||||
document.getElementById('pm-subtitle').textContent = p.subtitle || '';
|
||||
document.getElementById('pm-subtitle').style.display = p.subtitle ? 'block' : 'none';
|
||||
document.getElementById('pm-time').textContent =
|
||||
`${fmtTime(p.start)} — ${fmtTime(p.stop)} · ${dur} ${L.duration_min}` +
|
||||
(ep ? ` · ${ep}` : '');
|
||||
document.getElementById('pm-meta').textContent = meta;
|
||||
document.getElementById('pm-meta').style.display = meta ? 'block' : 'none';
|
||||
document.getElementById('pm-genres').textContent = genres;
|
||||
document.getElementById('pm-genres').style.display = genres ? 'block' : 'none';
|
||||
document.getElementById('pm-desc').textContent = p.desc || '';
|
||||
document.getElementById('pm-desc').style.display = p.desc ? 'block' : 'none';
|
||||
|
||||
|
||||
+9
-2
@@ -49,6 +49,7 @@ function applySearch(q) {
|
||||
if (p.stop < today || p.start > tomorrow) return false;
|
||||
if (p.title.toLowerCase().includes(q)) return true;
|
||||
if (p.desc && p.desc.toLowerCase().includes(q)) return true;
|
||||
if ((p.categories || []).some(cat => cat.toLowerCase().includes(q))) return true;
|
||||
return false;
|
||||
});
|
||||
});
|
||||
@@ -110,7 +111,10 @@ function renderFiltered(filteredChannels, q) {
|
||||
block.className = 'program-block';
|
||||
|
||||
// Highlight programs that match the current search query.
|
||||
const matchesProg = p.title.toLowerCase().includes(q) || (p.desc && p.desc.toLowerCase().includes(q));
|
||||
const matchesProg =
|
||||
p.title.toLowerCase().includes(q) ||
|
||||
(p.desc && p.desc.toLowerCase().includes(q)) ||
|
||||
(p.categories || []).some(cat => cat.toLowerCase().includes(q));
|
||||
if (matchesProg) block.classList.add('search-match');
|
||||
|
||||
if (p.stop < now) block.classList.add('is-past');
|
||||
@@ -168,7 +172,10 @@ function renderMobileFiltered(filteredChannels, q) {
|
||||
const isLive = p.start <= now && p.stop > now;
|
||||
const isPast = p.stop < now;
|
||||
const dur = Math.round((p.stop - p.start) / 60000);
|
||||
const matchesProg = p.title.toLowerCase().includes(q) || (p.desc && p.desc.toLowerCase().includes(q));
|
||||
const matchesProg =
|
||||
p.title.toLowerCase().includes(q) ||
|
||||
(p.desc && p.desc.toLowerCase().includes(q)) ||
|
||||
(p.categories || []).some(cat => cat.toLowerCase().includes(q));
|
||||
const item = document.createElement('div');
|
||||
item.className = 'mobile-program' + (isLive ? ' is-live' : '') + (isPast ? ' is-past' : '') + (matchesProg ? ' search-match' : '');
|
||||
|
||||
|
||||
+7
-4
@@ -8,8 +8,13 @@ function getNowPlaying(chId) {
|
||||
// ── TOOLTIP ───────────────────────────────────────────────────────────────────
|
||||
function showTooltip(e, p) {
|
||||
const ep = fmtEpisode(p.season, p.episode);
|
||||
document.getElementById('tt-title').textContent = p.title + (ep ? ' ' + ep : '');
|
||||
document.getElementById('tt-time').textContent = `${fmtTime(p.start)} — ${fmtTime(p.stop)} · ${Math.round((p.stop-p.start)/60000)} min`;
|
||||
const genres = formatCategories(p.categories);
|
||||
const title = p.title + (p.subtitle ? ` - ${p.subtitle}` : '') + (ep ? ' ' + ep : '');
|
||||
const meta = [p.airDate, p.rating, genres].filter(Boolean).join(' · ');
|
||||
document.getElementById('tt-title').textContent = title;
|
||||
document.getElementById('tt-time').textContent =
|
||||
`${fmtTime(p.start)} — ${fmtTime(p.stop)} · ${Math.round((p.stop-p.start)/60000)} min` +
|
||||
(meta ? ` · ${meta}` : '');
|
||||
document.getElementById('tt-desc').textContent = p.desc || '—';
|
||||
document.getElementById('tooltip').classList.add('visible');
|
||||
moveTooltip(e);
|
||||
@@ -23,5 +28,3 @@ function moveTooltip(e) {
|
||||
function hideTooltip() { document.getElementById('tooltip').classList.remove('visible'); }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+33
-1
@@ -106,6 +106,34 @@ function fmtEpisode(season, episode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseCategories(p) {
|
||||
const seen = new Set();
|
||||
const categories = [];
|
||||
p.querySelectorAll('category').forEach(cat => {
|
||||
const value = (cat.textContent || '').trim();
|
||||
const key = value.toLowerCase();
|
||||
if (!value || seen.has(key)) return;
|
||||
seen.add(key);
|
||||
categories.push(value);
|
||||
});
|
||||
return categories;
|
||||
}
|
||||
|
||||
function formatCategories(categories) {
|
||||
return Array.isArray(categories) && categories.length ? categories.join(' · ') : '';
|
||||
}
|
||||
|
||||
function parseAirDate(value) {
|
||||
if (!value) return null;
|
||||
const match = String(value).trim().match(/^(\d{4})/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function parseRatingValue(p) {
|
||||
const rating = p.querySelector('rating > value')?.textContent || '';
|
||||
return rating.trim() || null;
|
||||
}
|
||||
|
||||
function parseAndRender(doc) {
|
||||
channels = []; programs = {};
|
||||
doc.querySelectorAll('channel').forEach(ch => {
|
||||
@@ -124,9 +152,13 @@ function parseAndRender(doc) {
|
||||
const start = parseXMLTVDate(p.getAttribute('start'));
|
||||
const stop = parseXMLTVDate(p.getAttribute('stop'));
|
||||
const title = p.querySelector('title')?.textContent || '';
|
||||
const subtitle = p.querySelector('sub-title')?.textContent || '';
|
||||
const desc = p.querySelector('desc')?.textContent || '';
|
||||
const categories = parseCategories(p);
|
||||
const airDate = parseAirDate(p.querySelector('date')?.textContent || '');
|
||||
const rating = parseRatingValue(p);
|
||||
const epInfo = parseEpisode(p);
|
||||
if (start && stop) programs[chId].push({ start, stop, title, desc, ...epInfo });
|
||||
if (start && stop) programs[chId].push({ start, stop, title, subtitle, desc, categories, airDate, rating, ...epInfo });
|
||||
});
|
||||
Object.keys(programs).forEach(id => programs[id].sort((a,b) => a.start - b.start));
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
<button class="pm-close" onclick="closeProgramModal()">✕</button>
|
||||
<div class="pm-channel" id="pm-channel"></div>
|
||||
<div class="pm-title" id="pm-title"></div>
|
||||
<div class="pm-subtitle" id="pm-subtitle"></div>
|
||||
<div class="pm-time" id="pm-time"></div>
|
||||
<div class="pm-meta" id="pm-meta"></div>
|
||||
<div class="pm-genres" id="pm-genres"></div>
|
||||
<div class="pm-desc" id="pm-desc"></div>
|
||||
<div class="pm-actions">
|
||||
<button class="pm-btn pm-watch" id="pm-watch">▶ <?= htmlspecialchars($L["watch_now"]) ?></button>
|
||||
|
||||
Reference in New Issue
Block a user