/* Brain of Reese — Sources page (knowledge base index view). * * Wires the real `GET /api/docs` endpoint (import phase): stat cards + * full-width document table, or the designed empty state when nothing is * indexed yet. Cells are built with DOM APIs (textContent) — never * innerHTML with document-derived data (XSS-safe by construction). */ const tbody = document.querySelector("#docs-tbody"); const emptyEl = document.querySelector("#sources-empty"); const tableWrap = document.querySelector(".table-wrap"); const statDocs = document.querySelector("#stat-docs"); const statChunks = document.querySelector("#stat-chunks"); const statLast = document.querySelector("#stat-last"); function fmtDate(iso) { try { return new Date(iso).toLocaleString(); } catch { return iso; } } /* Viewer link (phase 10) — same encoded URL the chat chips use; both query * values are percent-encoded (paths contain slashes, sometimes spaces). */ export function documentUrl(source, path) { return "/document.html?source=" + encodeURIComponent(source) + "&path=" + encodeURIComponent(path); } async function loadDocs() { let r; try { r = await fetch("/api/docs"); } catch { showEmpty(); return; } if (!r.ok) { showEmpty(); return; } const { documents } = await r.json(); if (!documents.length) { showEmpty(); return; } tbody.replaceChildren(); let totalChunks = 0; let last = ""; for (const d of documents) { totalChunks += d.chunks; if (d.indexed_at > last) last = d.indexed_at; tbody.appendChild(makeRow(d)); } statDocs.textContent = String(documents.length); statChunks.textContent = String(totalChunks); statLast.textContent = last ? fmtDate(last) : "–"; emptyEl.hidden = true; tableWrap.hidden = false; } function makeRow(d) { const tr = document.createElement("tr"); const sourceTd = document.createElement("td"); sourceTd.textContent = d.source; // document-derived text — never innerHTML tr.appendChild(sourceTd); // Path cell: a link to the document viewer (phase 10), full path as the // accessible/hover name (the column is ellipsized). const pathTd = document.createElement("td"); pathTd.title = d.path; // full path on hover (column is ellipsized) const link = document.createElement("a"); link.className = "doc-link"; link.href = documentUrl(d.source, d.path); link.target = "_blank"; // open the full document in a new tab link.rel = "noopener"; link.title = d.path; // full path as the link's hover/accessible name link.textContent = d.path; pathTd.appendChild(link); tr.appendChild(pathTd); for (const value of [d.title, String(d.chunks), fmtDate(d.indexed_at)]) { const td = document.createElement("td"); td.textContent = value; tr.appendChild(td); } return tr; } function showEmpty() { statDocs.textContent = "0"; statChunks.textContent = "0"; statLast.textContent = "–"; emptyEl.hidden = false; if (tableWrap) tableWrap.hidden = true; } loadDocs();