// 首页三栏卡片:拉取目录 -> cards.layout 分配三栏 -> 渲染(time 卡大字 + 其他占位) // Home three-column cards: fetch catalog -> cards.layout assigns 3 columns -> render // (big time card + generic placeholders for the rest) window.updateHomeCards = async function () { const page = document.querySelector('[data-page="home"]'); page.innerHTML = `
`; let catalog = []; try { // 优先用预加载缓存,未就绪则实时加载 // Prefer the preloaded cache; fetch live when not ready yet catalog = window.preload.cards || JSON.parse(await bridge.call('fetchCards')); } catch (e) { // 桥接不可用(如未接线)时按空目录处理 // Treat bridge failure as an empty catalog catalog = []; } if (!Array.isArray(catalog)) catalog = []; // 兜底:无目录或缺失 time 卡时补内置大字时间卡,保证首页始终有时间卡 // Fallback: prepend builtin big time card when the catalog is empty or missing the time card if (!catalog.some((c) => c.id === 'time')) { catalog.push({ id: 'time', name: '大字时间', priority: 0 }); } renderCards(catalog); startTimeCardTicker(); }; // 卡片 Grid 渲染:media 卡 span 2(跨栏),time/其他卡 span 1 // Card grid render: media card spans 2 columns, time/other cards span 1 function renderCards(catalog) { const grid = document.getElementById('tri-col'); grid.textContent = ''; const items = []; // 按 priority 排序(数字小者优先) // Sort by priority (smaller first) const sorted = [...catalog].sort((a, b) => (a.priority || 0) - (b.priority || 0)); // time 卡(内置,span 1) if (sorted.some(c => c.id === 'time')) items.push({ span: 1, el: renderTimeCard() }); // media 卡(有媒体时 span 2,跨栏更美观) if (currentMedia) items.push({ span: 2, el: renderMediaCard(currentMedia) }); // 其他卡(排除 time)span 1 sorted.filter(c => c.id !== 'time').forEach(c => { items.push({ span: 1, el: renderGenericCard(c.id) }); }); items.forEach(item => { item.el.classList.add('span-' + item.span); grid.appendChild(item.el); }); } // 内置大字时间卡:当前时间(时:分 + 小号秒)+ 日期 // Builtin big time card: current time (HH:MM + small seconds) + date function renderTimeCard() { const el = document.createElement('div'); el.className = 'card time-card'; const now = new Date(); el.innerHTML = `
${fmtTime(now)}${fmtSec(now)}
${fmtDate(now)}
`; return el; } // 其他卡片占位:仅显示 id,后续 Task 接具体卡片渲染 // Other cards placeholder: show id only; concrete renderers land in later tasks function renderGenericCard(id) { const el = document.createElement('div'); el.className = 'card'; el.textContent = id; return el; } function fmtTime(d) { const hh = String(d.getHours()).padStart(2, '0'); const mm = String(d.getMinutes()).padStart(2, '0'); return `${hh}:${mm}`; } function fmtSec(d) { return `:${String(d.getSeconds()).padStart(2, '0')}`; } function fmtDate(d) { const days = ['日', '一', '二', '三', '四', '五', '六']; return `${d.getMonth() + 1}月${d.getDate()}日 周${days[d.getDay()]}`; } // 每秒刷新一次大字时间卡(含秒显) // Refresh the big time card every second (including the seconds display) let timeTicker = null; function startTimeCardTicker() { if (timeTicker) return; timeTicker = setInterval(() => { const card = document.querySelector('.time-card'); if (!card) return; const now = new Date(); card.querySelector('.hm').textContent = fmtTime(now); card.querySelector('.sec').textContent = fmtSec(now); card.querySelector('.sub').textContent = fmtDate(now); }, 1000); } // 当前媒体信息(null 表示无媒体);切页后 updateHomeCards 据此恢复媒体卡 // Current media info (null = none); updateHomeCards restores the media card from it let currentMedia = null; // 原生媒体会话事件:有媒体时在中间栏渲染媒体卡,无媒体时降级重新布局 // Native media session event: render media card in middle column when present, // otherwise fall back to re-layout (calendar/weather fill the media card slot) window.HearthEvents = window.HearthEvents || {}; window.HearthEvents.mediaSessionChanged = function (info) { currentMedia = info; const grid = document.getElementById('tri-col'); if (!grid) return; // 重新渲染:有媒体时 media 卡 span 2,无媒体时其他卡填满 // Re-render: media card spans 2 when present, others fill otherwise window.updateHomeCards(); }; // 媒体卡:正在播放标签 + 标题/艺术家 + 进度条 // Media card: playing caption + title/artist + progress bar function renderMediaCard(info) { const el = document.createElement('div'); el.className = 'card media-card'; const pct = info.duration ? (info.position / info.duration) * 100 : 0; // title/artist 来自任意应用元数据,转义后插入,防 XSS // title/artist come from arbitrary app metadata; escape before insert (XSS) el.innerHTML = `
正在播放
${escapeHtml(info.title)}
${escapeHtml(info.artist)}
`; return el; } // 转义 HTML 特殊字符,避免媒体元数据注入标签 // Escape HTML special chars to prevent metadata injection function escapeHtml(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }