"use strict";
const $ = selector => document.querySelector(selector);
const esc = value => String(value ?? "").replace(/[&<>'"]/g, char => ({"&":"&","<":"<",">":">","'":"'",'"':"""}[char]));
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 ``;
}
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 => `
`).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 ? `
` : `${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);