Files
hub/public/boot.js
T
nmemmertandClaude Sonnet 5.5 301e8017d9
build-container / build (push) Successful in 3m52s
Parent controls for children, name-tile sign-in, restricted Learn search, simple mode
- Sign-in page shows name tiles on remembered devices; tap a name, then PIN or passkey
- Music and Movies are separate on the server; Plex can be locked to a child's managed profile
- Activity log, weekly summary and alerts to parents; per-area and weekend limits
- Requests from children (Ask button) answered in Settings; bonus screen time from rewards
- Restricted Learn search (Simple English Wikipedia, blocked words) replaces web search for children
- Simple mode (big-button home) for young children or grandparents
- Child usage kept in memory instead of read from disk on every request
- Tests and README updated

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 16:49:48 -04:00

42 lines
2.5 KiB
JavaScript

'use strict';
// ---- Boot
(async () => {
try { cfg = { ...cfg, ...(await (await fetch('/api/config')).json()) }; } catch {}
const loadedBuild = cfg.build;
// When a new version is deployed, an open page keeps running the old one. Tell the person, and offer a reload.
const checkForUpdate = async () => {
try {
const c = await (await fetch('/api/config', { cache: 'no-store' })).json();
if (c.build && loadedBuild && c.build !== loadedBuild && !document.getElementById('upd')) {
const b = document.createElement('div'); b.id = 'upd';
b.innerHTML = 'A new version of NE Hub is available. <button class="btn" id="updgo">Reload</button>';
document.body.appendChild(b); document.getElementById('updgo').onclick = () => location.reload();
}
} catch {}
};
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') checkForUpdate(); });
setInterval(checkForUpdate, 10 * 60e3);
await loadMe();
await loadSettings();
sgStart(); if (typeof chStart === 'function') chStart(); if (typeof kidStart === 'function') kidStart(); if (typeof devStart === 'function') devStart(); if (typeof simpleStart === 'function') simpleStart();
addEventListener('hashchange', route);
restorePlayer();
route();
shareCheck();
// Unread mail and tasks due (the badge): checked on a timer only while the app is on screen, and again the moment you come back to it.
const badgePoll = () => { mailPoll(); taskPoll(); };
let lastBadge = Date.now(); badgePoll();
setInterval(() => { if (document.visibilityState === 'visible') { lastBadge = Date.now(); badgePoll(); } }, 2 * 60e3);
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && Date.now() - lastBadge > 30e3) { lastBadge = Date.now(); badgePoll(); } });
if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js').catch(() => {});
})();
// Keyboard (desktop): space play/pause, arrows seek, N/P next/previous.
addEventListener('keydown', e => {
const tag = e.target.tagName || '', k = e.key;
if (e.metaKey || e.ctrlKey || e.altKey || !cur() || document.body.classList.contains('onvideo') || /^(INPUT|TEXTAREA|SELECT)$/.test(tag) || (k === ' ' && /^(BUTTON|A)$/.test(tag))) return;
if (k === ' ') { e.preventDefault(); toggle(); }
else if (k === 'ArrowRight') pl.a.currentTime += 10; else if (k === 'ArrowLeft') pl.a.currentTime = Math.max(0, pl.a.currentTime - 10);
else if (k === 'n' || k === 'N') next(); else if (k === 'p' || k === 'P') prev();
});