// ── tim — time calculator plugin ── const $ = id => document.getElementById(id); const h = window.hyprcrd; const overlay = $('tim-overlay'); function show() { overlay?.classList.remove('hidden'); $('tim-dur-start')?.focus(); } function hide() { overlay?.classList.add('hidden'); } function toggle() { overlay?.classList.contains('hidden') ? show() : hide(); } function pad4(n) { return String(n).padStart(4, '0'); } // Parse HHMM string → total minutes since midnight, or null on invalid function parseHHMM(s) { if (!s) return null; s = String(s).trim().replace(':', '').padStart(4, '0'); if (!/^\d{3,4}$/.test(s)) return null; const h = parseInt(s.slice(0, -2)); const m = parseInt(s.slice(-2)); if (m >= 60) return null; return h * 60 + m; } function minutesToHHMM(mins) { mins = ((mins % (24 * 60)) + 24 * 60) % (24 * 60); return pad4(Math.floor(mins / 60) * 100 + (mins % 60)); } function formatDuration(mins) { if (mins < 0) mins += 24 * 60; const h = Math.floor(mins / 60), m = mins % 60; return `${h}h ${String(m).padStart(2, '0')}m`; } // ── Tab switching ── document.querySelectorAll('.tim-tab').forEach(t => { t.addEventListener('click', () => { document.querySelectorAll('.tim-tab').forEach(x => x.classList.toggle('active', x === t)); document.querySelectorAll('.tim-tab-body').forEach(p => { p.classList.toggle('hidden', p.dataset.timPanel !== t.dataset.timTab); }); const firstInput = document.querySelector(`[data-tim-panel="${t.dataset.timTab}"] input`); firstInput?.focus(); }); }); // ── Duration tab ── function calcDuration() { const res = $('tim-dur-result'); const s = parseHHMM($('tim-dur-start').value); const e = parseHHMM($('tim-dur-end').value); if (s == null || e == null) { res.className = 'tim-result'; res.textContent = ''; return; } const diff = e - s; res.className = 'tim-result ok'; res.textContent = formatDuration(diff); } // ── Add tab ── function calcAdd() { const res = $('tim-add-result'); const t = parseHHMM($('tim-add-time').value); const d = parseHHMM($('tim-add-dur').value); if (t == null || d == null) { res.className = 'tim-result'; res.textContent = ''; return; } res.className = 'tim-result ok'; res.textContent = minutesToHHMM(t + d); } // ── Workday tab ── function calcWorkday() { const res = $('tim-wd-result'); const s = parseHHMM($('tim-wd-start').value); const bf = parseHHMM($('tim-wd-break-from').value); const bb = parseHHMM($('tim-wd-break-back').value); const target = parseHHMM($('tim-wd-target').value) ?? 480; if (s == null || bf == null || bb == null) { res.className = 'tim-result'; res.textContent = ''; return; } const workedBeforeBreak = bf - s; if (workedBeforeBreak < 0) { res.className = 'tim-result err'; res.textContent = 'Break must be after start'; return; } const remaining = target - workedBeforeBreak; if (remaining < 0) { res.className = 'tim-result ok'; res.textContent = 'Target already reached at ' + minutesToHHMM(s + target); return; } const endOfDay = (bb + remaining) % (24 * 60); res.className = 'tim-result ok'; res.textContent = `End at ${minutesToHHMM(endOfDay)} (${formatDuration(remaining)} left after break)`; } // Single delegated input handler on the overlay — routes to the right // calc by input id. Robust against DOM order / re-renders. overlay?.addEventListener('input', e => { const id = e.target?.id || ''; if (id.startsWith('tim-dur-')) calcDuration(); else if (id.startsWith('tim-add-')) calcAdd(); else if (id.startsWith('tim-wd-')) calcWorkday(); }); // Focus → select existing text so typing replaces it. overlay?.addEventListener('focusin', e => { if (e.target.tagName === 'INPUT' && e.target.value) e.target.select(); }); // Tab cycles within the currently active tab panel's inputs. overlay?.addEventListener('keydown', e => { if (e.key !== 'Tab') return; const panel = e.target.closest('.tim-tab-body:not(.hidden)'); if (!panel) return; const inputs = [...panel.querySelectorAll('input')]; if (!inputs.length) return; const idx = inputs.indexOf(e.target); if (idx === -1) return; e.preventDefault(); const next = e.shiftKey ? (idx - 1 + inputs.length) % inputs.length : (idx + 1) % inputs.length; inputs[next].focus(); }); // ── Global bindings ── $('tim-close')?.addEventListener('click', hide); overlay?.addEventListener('click', e => { if (e.target === overlay) hide(); }); window.addEventListener('keydown', e => { if (e.key === 'Escape' && !overlay?.classList.contains('hidden')) { e.preventDefault(); e.stopPropagation(); hide(); } }, { capture: true }); // Expose toggle for terminal / shortcuts window._timToggle = toggle; // Register toolbar command — the md-toolbar delegated click handler in // editor.js routes clicks on [data-md="tim"] through this. h?.editor?.registerCommand('tim', () => toggle()); function injectToolbarButton() { const toolbar = document.getElementById('md-toolbar'); if (!toolbar) return; if (toolbar.querySelector('[data-md="tim"]')) return; const btn = document.createElement('button'); btn.setAttribute('data-md', 'tim'); btn.setAttribute('data-help', 'tim — time calculator\nDuration, add, workday end.\nShortcut: I'); btn.className = 'toolbar-btn'; btn.innerHTML = ''; const spacer = toolbar.querySelector('.flex-1'); if (spacer) toolbar.insertBefore(btn, spacer); else toolbar.appendChild(btn); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', injectToolbarButton); } else { injectToolbarButton(); } h?.registerPlugin({ name: 'tim', version: '1.0.0' });