"use strict"; const $ = selector => document.querySelector(selector); const esc = value => String(value ?? "").replace(/[&<>'"]/g, char => ({"&":"&","<":"<",">":">","'":"'",'"':"""}[char])); const snippet = value => esc(value).replace(/\x01/g, "").replace(/\x02/g, ""); const sizeLabel = value => { const units = ["B", "kB", "MB", "GB", "TB"]; let amount = Number(value), index = 0; while (amount >= 1024 && index < units.length - 1) { amount /= 1024; index++; } return `${amount.toLocaleString("de-DE", {maximumFractionDigits: index ? 1 : 0})} ${units[index]}`; }; const modifiedLabel = value => new Intl.DateTimeFormat("de-DE", {dateStyle:"medium",timeZone:"UTC"}).format(new Date(value * 1000)); function icon(name) { const paths = { download: '', close: '', file: '', }; return ``; } const statusLabel = row => ({pending:"Text wird erfasst…",ocr:row.attempts ? "Texterkennung wird erneut versucht…" : "Texterkennung läuft…",failed:"Text konnte noch nicht erfasst werden.","name-only":"Suche nach Dateinamen verfügbar."}[row.state] || ""); let session = null, offset = 0, listMode = false, timer, debounce, request, serial = 0, lastResult = "", activeDocument = null, initialFilters = null; const pageSize = 40; async function api(path, options = {}) { const response = await fetch(path, {...options,credentials:"same-origin",headers:{"Content-Type":"application/json",...(options.headers || {})}}); let body = {}; try { body = await response.json(); } catch (_) { /* Empty response. */ } if (response.status === 401) { showLogin(); throw new Error("Bitte erneut anmelden."); } if (!response.ok) throw Object.assign(new Error(body.error || "Anfrage fehlgeschlagen"), {status:response.status}); return body; } function notice(message) { $("#toast").textContent = message; $("#toast").hidden = false; setTimeout(() => { $("#toast").hidden = true; }, 4000); } function showLogin() { session = null; activeDocument = null; lastResult = ""; clearInterval(timer); clearTimeout(debounce); request?.abort(); $("#documents").replaceChildren(); $("#document-preview").replaceChildren(); delete $("#document-preview").dataset.url; if ($("#document-dialog").open) $("#document-dialog").close(); $("#portal-view").hidden = true; $("#login-view").hidden = false; $("#login-form input[name=username]").focus(); } function showFiles(value) { session = value; $("#session-user").textContent = value.user; $("#admin-link").hidden = value.role !== "domain-admin"; $("#login-view").hidden = true; $("#portal-view").hidden = false; const params = new URLSearchParams(location.search); initialFilters = {source:params.get("source") || "",type:params.get("type") || ""}; offset = 0; for (const [id,key] of [["search-query","q"],["search-scope","scope"],["sort-filter","sort"]]) { if (params.has(key)) $("#" + id).value = params.get(key); } refresh(); clearInterval(timer); timer = setInterval(() => { if (!document.hidden && session) { refresh(false); if (activeDocument) loadDetail(activeDocument, false); } }, 2000); } function filters() { return new URLSearchParams({q:$("#search-query").value,scope:$("#search-scope").value || "all",source:$("#source-filter").value,type:$("#type-filter").value,sort:$("#sort-filter").value || "recent",limit:pageSize,offset}); } function updateOptions(select, items, firstLabel) { const current = select.value || (initialFilters && initialFilters[select.id === "source-filter" ? "source" : "type"]) || ""; const html = `` + items.map(item => ``).join(""); if (select.innerHTML !== html) { select.innerHTML = html; select.value = current; if (!select.value) select.value = ""; } } async function refresh(userAction = true) { if (!session) return; if (!userAction && request) return; request?.abort(); const controller = new AbortController(); request = controller; const current = ++serial; if (userAction) $("#search-error").hidden = true; try { const params = filters(); const result = await api("/api/documents?" + params, {signal:controller.signal}); if (current !== serial || !session) return; history.replaceState({}, "", "/" + (params.get("q") || params.get("source") || params.get("type") ? "?" + params : "")); updateOptions($("#source-filter"), result.sources.map(source => ({id:source.id,label:source.kind === "private" ? "Mein Private-Ordner" : source.label})), "Alle Ordner"); updateOptions($("#type-filter"), result.types.map(type => ({id:type,label:type.toUpperCase()})), "Alle Dateitypen"); if (initialFilters) { const changed = $("#source-filter").value !== params.get("source") || $("#type-filter").value !== params.get("type"); initialFilters = null; if (changed) { request = null; refresh(); return; } } $("#result-summary").textContent = `${result.total.toLocaleString("de-DE")} ${result.total === 1 ? "Datei" : "Dateien"}`; $("#index-progress").hidden = !result.pending; $("#index-progress").textContent = result.pending ? `Text wird für ${result.pending} ${result.pending === 1 ? "Datei" : "Dateien"} erfasst.` : ""; $("#previous-page").disabled = offset === 0; $("#next-page").disabled = !result.hasMore; $("#page-label").textContent = result.total ? `Seite ${Math.floor(offset / pageSize) + 1}` : ""; const signature = JSON.stringify(result.items); if (signature !== lastResult) { lastResult = signature; $("#documents").innerHTML = result.items.length ? result.items.map(row => `
${esc(row.kind === "private" ? "Private" : row.source)} · ${sizeLabel(row.size)}${modifiedLabel(row.modified)} UTC${icon("download")}
`).join("") : '

Keine Dateien gefunden

Suchbegriff oder Filter ändern.

'; } $("#search-error").hidden = true; } catch (error) { if (error.name !== "AbortError" && session) { $("#search-error").textContent = error.message; $("#search-error").hidden = false; } } finally { if (request === controller) request = null; } } async function loadDetail(id, open = true) { if (open) activeDocument = id; try { const row = await api("/api/documents/" + id); if (activeDocument !== id || !session) return; $("#document-title").textContent = row.name; $("#document-title").title = row.name; $("#document-location").textContent = `${row.source} / ${row.path} · ${sizeLabel(row.size)}${row.pages ? ` · ${row.pages} ${row.pages === 1 ? "Seite" : "Seiten"}` : ""}`; const isPdf = row.extension === "pdf"; $("#document-dialog").classList.toggle("pdf-dialog", isPdf); $("#document-download").href = `/api/documents/${id}/download`; $("#document-download").download = row.name; $("#document-download").innerHTML = icon("download"); $("#document-close").innerHTML = icon("close"); const previewUrl = isPdf ? `/assets/vendor/pdfjs/6.3.289-app1/web/viewer.html?file=${encodeURIComponent(`/api/documents/${id}/content?v=${row.version}`)}#zoom=page-width` : row.hasPreview ? `/api/documents/${id}/preview?v=${row.version}` : ""; if ($("#document-preview").dataset.url !== previewUrl) { $("#document-preview").dataset.url = previewUrl; $("#document-preview").innerHTML = isPdf ? `` : previewUrl ? `Dokumentvorschau` : `
${icon("file")}

Keine Bildvorschau verfügbar.

`; } if (open && !$("#document-dialog").open) $("#document-dialog").showModal(); } catch (error) { if (activeDocument === id) { if ($("#document-dialog").open) $("#document-dialog").close(); activeDocument = null; if (session) notice(error.message); } } } $("#document-dialog").addEventListener("close", () => { activeDocument = null; $("#document-preview").replaceChildren(); delete $("#document-preview").dataset.url; }); $("#document-close").addEventListener("click", () => $("#document-dialog").close()); $("#documents").addEventListener("click", event => { const button = event.target.closest("[data-document]"); if (button) loadDetail(button.dataset.document); }); $("#documents").addEventListener("error", event => { if (event.target.tagName === "IMG") event.target.hidden = true; }, true); $("#document-search").addEventListener("submit", event => { event.preventDefault(); clearTimeout(debounce); offset = 0; refresh(); }); $("#search-query").addEventListener("input", () => { clearTimeout(debounce); offset = 0; debounce = setTimeout(() => refresh(), 200); }); for (const id of ["search-scope", "source-filter", "type-filter", "sort-filter"]) $("#" + id).addEventListener("change", () => { offset = 0; refresh(); }); $("#reset-filters").addEventListener("click", () => { $("#search-query").value = ""; $("#search-scope").value = "all"; $("#source-filter").value = ""; $("#type-filter").value = ""; offset = 0; refresh(); }); $("#previous-page").addEventListener("click", () => { offset = Math.max(0, offset - pageSize); refresh(); }); $("#next-page").addEventListener("click", () => { offset += pageSize; refresh(); }); for (const [id,mode] of [["view-grid",false],["view-list",true]]) $("#" + id).addEventListener("click", () => { listMode = mode; $("#documents").classList.toggle("document-list", listMode); $("#view-grid").setAttribute("aria-pressed", String(!listMode)); $("#view-list").setAttribute("aria-pressed", String(listMode)); }); $("#login-form").addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget, button = form.querySelector("button"); $("#login-error").hidden = true; button.disabled = true; try { const data = new FormData(form); const value = await api("/api/login", {method:"POST",body:JSON.stringify({username:data.get("username"),password:data.get("password")})}); form.reset(); showFiles(value); } catch (error) { $("#login-error").textContent = error.message; $("#login-error").hidden = false; } finally { button.disabled = false; } }); $("#logout").addEventListener("click", async () => { try { await api("/api/logout", {method:"POST",body:"{}"}); } finally { showLogin(); } }); api("/api/session").then(showFiles).catch(showLogin);