// 首页三栏卡片:拉取目录 -> 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 { catalog = 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 }); } const cols = cards.layout(catalog); cols.forEach((ids, i) => { const col = document.getElementById(`col-${i}`); ids.forEach((id) => { if (id === 'time') col.appendChild(renderTimeCard()); else col.appendChild(renderGenericCard(id)); }); }); startTimeCardTicker(); }; // 内置大字时间卡:当前时间(时:分 + 小号秒)+ 日期 // 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); } // 原生媒体会话事件:有媒体时在中间栏渲染媒体卡,无媒体时降级重新布局 // 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) { const mediaCol = document.getElementById('col-1'); if (!mediaCol) return; if (info) { mediaCol.innerHTML = ''; mediaCol.appendChild(renderMediaCard(info)); } else { // 无媒体:降级到日历/天气(由 cards.layout 重新计算) // No media: fall back to calendar/weather (recomputed by cards.layout) 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, '''); }