/** * Sidebar panel: daily notes journal browser. * * Lists the YYYY-MM-DD.md files in /notes/daily/ newest-first, * lets the user click one to open it, and surfaces a "Today" button that * creates/opens today's note. Pure DOM module — same pattern as * snippets-panel.js / search-panel.js. * * @param {HTMLElement} container * @param {object} deps * @param {() => Promise>} [deps.listPendingRecoveries] * @param {(args:{date?:string}) => Promise<{path:string, content:string, created:boolean}>} deps.openToday * @param {() => Promise} deps.listExisting * @param {(filePath:string) => void} deps.onOpenFile * @param {(filePath:string) => Promise} [deps.deleteEntry] optional — deletes a daily note */ function renderDailyNotesPanel(container, deps) { const { openToday, listExisting, onOpenFile } = deps; let entries = []; // [{ path, name }] container.innerHTML = `
`; const todayBtn = container.querySelector('#daily-notes-today'); const refreshBtn = container.querySelector('#daily-notes-refresh'); const statusEl = container.querySelector('#daily-notes-status'); const listEl = container.querySelector('#daily-notes-list'); function basename(p) { return String(p).split(/[/\\]/).pop(); } function statusOf(name) { // YYYY-MM-DD.md → "Sun, 13 Sep 2026" (or local equivalent) const m = /^(\d{4})-(\d{2})-(\d{2})\.md$/.exec(name); if (!m) return name; const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])); if (Number.isNaN(d.getTime())) return name; return d.toLocaleDateString(undefined, { weekday: 'short', day: '2-digit', month: 'short', year: 'numeric', }); } function setStatus(text) { if (statusEl) statusEl.textContent = text || ''; } function renderList() { if (!listEl) return; if (!entries || entries.length === 0) { listEl.innerHTML = '
No daily notes yet. Press Today to start.
'; return; } listEl.innerHTML = entries .map((entry, i) => { return `
`; }) .join(''); // Set dynamic content via textContent so a malicious filename can't // inject HTML. listEl.querySelectorAll('.daily-notes-item').forEach((el) => { const idx = Number(el.dataset.idx); const entry = entries[idx]; el.querySelector('.daily-notes-name').textContent = entry.name.replace(/\.md$/, ''); el.querySelector('.daily-notes-sub').textContent = statusOf(entry.name); el.addEventListener('click', () => { if (onOpenFile) onOpenFile(entry.path); }); el.addEventListener('keydown', (ev) => { if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); el.click(); } }); }); } async function refresh({ keepStatus = false } = {}) { if (typeof listExisting !== 'function') { setStatus('Daily notes list is unavailable.'); return; } if (!keepStatus) setStatus('Loading…'); try { const list = await listExisting(); // listExisting returns absolute paths (newest first). Pair each path // with its basename for display. const paths = Array.isArray(list) ? list : []; entries = paths.filter((p) => /\.md$/i.test(p)).map((p) => ({ path: p, name: basename(p) })); renderList(); if (!keepStatus) setStatus( entries.length === 0 ? '' : `${entries.length} note${entries.length === 1 ? '' : 's'}` ); } catch (err) { setStatus(`Error: ${err && err.message ? err.message : 'unknown'}`); renderList(); } } todayBtn.addEventListener('click', async () => { setStatus('Opening today…'); todayBtn.disabled = true; try { const result = await openToday({}); if (result && result.path && onOpenFile) onOpenFile(result.path); setStatus(result && result.created ? 'Created today.' : 'Today already exists.'); await refresh({ keepStatus: true }); } catch (err) { setStatus(`Error: ${err && err.message ? err.message : 'unknown'}`); } finally { todayBtn.disabled = false; } }); refreshBtn.addEventListener('click', () => { refresh(); }); refresh(); return { refresh, basename, }; } module.exports = { renderDailyNotesPanel };