Keyboard shortcuts for mail and a shortcut list, offline help and refresh on return, push alerts for new mail

This commit is contained in:
nmemmert committed 2026-10-23 09:32:56 -04:00
1 parent 93ae8f8840
commit 2c8afdef79
8 files changed
+71 -7

No files matched your search

+16 -2
View File
@@ -23,7 +23,7 @@ function keys() {
return vapid;
}
const DEFAULTS = { tasks: true, tasksAt: '08:00', events: true, eventMins: 15, tz: 'UTC', subs: [], sent: [], tasksDay: '' };
const DEFAULTS = { mail: true, mailSeen: null, tasks: true, tasksAt: '08:00', events: true, eventMins: 15, tz: 'UTC', subs: [], sent: [], tasksDay: '' };
const file = dir => path.join(dir, 'push.json');
const load = dir => { try { return { ...DEFAULTS, ...JSON.parse(fs.readFileSync(file(dir), 'utf8')) }; } catch { return { ...DEFAULTS, subs: [], sent: [] }; } };
const save = (dir, o) => atomicWrite(file(dir), JSON.stringify(o));
@@ -75,6 +75,19 @@ async function tick(now = new Date(), force = false) {
}
}
}
if (checkEvents && st.mail && users.can(u, 'mail')) { // new unread mail (the first look only notes what is already there)
try {
const now2 = (await mail.newest()).filter(m => !m.read), seen = st.mailSeen;
if (!Array.isArray(seen)) { st.mailSeen = now2.map(m => m.id); dirty = true; }
else {
const fresh = now2.filter(m => !seen.includes(m.id));
if (fresh.length) {
st.mailSeen = seen.concat(fresh.map(m => m.id)).slice(-200); dirty = true;
await send(dir, st, { title: fresh.length === 1 ? (fresh[0].from || 'New mail') : fresh.length + ' new messages', body: fresh.length === 1 ? fresh[0].subject : fresh.slice(0, 3).map(m => m.from || m.subject).join(', '), url: '#/mail', tag: 'mail' });
}
}
} catch (e) { console.warn('[push] mail check failed:', e.message); }
}
if (checkEvents && st.events && users.can(u, 'calendar')) {
try {
const from = now.getTime(), to = from + (st.eventMins + 5) * 60e3;
@@ -100,7 +113,7 @@ function start() { if (!timer) { timer = setInterval(() => tick().catch(e => con
async function handle(req, res) {
const p = new URL(req.url, 'http://x').pathname, dir = users.dir(), st = load(dir);
const pub = () => ({ tasks: st.tasks, tasksAt: st.tasksAt, events: st.events, eventMins: st.eventMins, devices: st.subs.length });
const pub = () => ({ mail: st.mail, tasks: st.tasks, tasksAt: st.tasksAt, events: st.events, eventMins: st.eventMins, devices: st.subs.length });
if (p === '/api/push/key' && req.method === 'GET') return json(res, 200, { publicKey: keys().publicKey, ...pub() });
if (req.method !== 'POST') return json(res, 405, { error: 'Method not allowed' });
const b = JSON.parse((await readBody(req)) || '{}');
@@ -115,6 +128,7 @@ async function handle(req, res) {
if (p === '/api/push/prefs') {
if (typeof b.tasks === 'boolean') st.tasks = b.tasks;
if (typeof b.events === 'boolean') st.events = b.events;
if (typeof b.mail === 'boolean') st.mail = b.mail;
if (/^([01]\d|2[0-3]):[0-5]\d$/.test(b.tasksAt || '')) st.tasksAt = b.tasksAt;
if ([5, 10, 15, 30, 60].includes(b.eventMins)) st.eventMins = b.eventMins;
if (b.tz && validTz(b.tz)) st.tz = b.tz;