(function () { "use strict"; var DEFAULT_MAX = 200 * 1024 * 1024; var BLOCKED_MSG = "Dieser Dateityp ist aus Sicherheitsgründen nicht erlaubt."; function ext(name) { var i = name.lastIndexOf("."); return i >= 0 ? name.slice(i).toLowerCase() : ""; } function formatSize(bytes) { if (bytes < 1024) return bytes + " B"; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; return (bytes / (1024 * 1024)).toFixed(1) + " MB"; } function escapeHtml(s) { var d = document.createElement("div"); d.textContent = s; return d.innerHTML; } function showStatus(el, type, message) { el.hidden = false; el.className = "upload-status alert alert-" + type; el.textContent = message; } function isOdt(name) { return name && name.toLowerCase().slice(-4) === ".odt"; } function editBtn(file) { if (!isOdt(file.original_filename)) return ""; return ' Im Browser bearbeiten'; } function buildFileRow(file, mode) { var tr = document.createElement("tr"); tr.dataset.fileId = String(file.id); if (mode === "task") { tr.innerHTML = "" + escapeHtml(file.original_filename) + "" + "" + formatSize(file.size_bytes) + "" + "" + escapeHtml(file.created_at) + " (" + escapeHtml(file.uploader || "") + ")" + 'Download' + editBtn(file) + ""; return tr; } var tags = (file.tags || []).map(function (t) { return '' + escapeHtml(t) + ""; }).join(""); tr.innerHTML = "" + escapeHtml(file.original_filename) + "" + "" + escapeHtml(file.category || "–") + "" + "" + escapeHtml(file.description || "–") + "" + "" + formatSize(file.size_bytes) + "" + "" + escapeHtml(file.created_at) + " (" + escapeHtml(file.uploader || "") + ")" + "" + tags + "" + 'Download' + editBtn(file) + ""; return tr; } function parseList(raw) { if (!raw) return []; return raw.split(",").map(function (s) { return s.trim(); }).filter(Boolean); } function initWidget(widget) { var dropZone = widget.querySelector("[data-drop-zone]"); var fileInput = widget.querySelector("[data-file-input]"); var statusEl = widget.querySelector("[data-upload-status]"); var csrf = widget.dataset.csrf; var taskId = widget.dataset.taskId || ""; var categoryEl = widget.querySelector("[data-upload-category]"); var descriptionEl = widget.querySelector("[data-upload-description]"); var tagsEl = widget.querySelector("[data-upload-tags]"); var listTargetId = widget.dataset.listTarget; var listTarget = listTargetId ? document.getElementById(listTargetId) : null; var rowMode = widget.dataset.rowMode || "full"; var emptyState = widget.dataset.emptyState ? document.getElementById(widget.dataset.emptyState) : null; var maxBytes = parseInt(widget.dataset.maxBytes, 10) || DEFAULT_MAX; var maxMb = parseInt(widget.dataset.maxMb, 10) || 200; var allowed = parseList(widget.dataset.allowed); var blocked = parseList(widget.dataset.blocked); var uploading = false; var queue = []; if (!dropZone || !fileInput || !csrf) return; if (allowed.length && fileInput) { fileInput.setAttribute("accept", allowed.join(",")); } dropZone.addEventListener("click", function (e) { if (e.target === fileInput) return; fileInput.click(); }); dropZone.addEventListener("keydown", function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fileInput.click(); } }); ["dragenter", "dragover"].forEach(function (ev) { dropZone.addEventListener(ev, function (e) { e.preventDefault(); e.stopPropagation(); dropZone.classList.add("drop-zone-active"); }); }); ["dragleave", "drop"].forEach(function (ev) { dropZone.addEventListener(ev, function (e) { e.preventDefault(); e.stopPropagation(); dropZone.classList.remove("drop-zone-active"); }); }); dropZone.addEventListener("drop", function (e) { var files = e.dataTransfer && e.dataTransfer.files; if (files && files.length) enqueue(Array.prototype.slice.call(files)); }); fileInput.addEventListener("change", function () { if (fileInput.files && fileInput.files.length) { enqueue(Array.prototype.slice.call(fileInput.files)); fileInput.value = ""; } }); function enqueue(files) { files.forEach(function (f) { queue.push(f); }); processQueue(); } function processQueue() { if (uploading || !queue.length) return; uploading = true; uploadOne(queue.shift()).finally(function () { uploading = false; processQueue(); }); } function uploadOne(file) { var e = ext(file.name); if (blocked.indexOf(e) !== -1) { showStatus(statusEl, "error", file.name + ": " + BLOCKED_MSG); return Promise.resolve(); } if (allowed.length && allowed.indexOf(e) === -1) { showStatus(statusEl, "error", file.name + ": Dateityp nicht erlaubt."); return Promise.resolve(); } if (file.size > maxBytes) { showStatus(statusEl, "error", file.name + ": zu gross (max. " + maxMb + " MB)."); return Promise.resolve(); } showStatus(statusEl, "info", "Lade hoch: " + file.name + " …"); var fd = new FormData(); fd.append("csrf_token", csrf); fd.append("upload", file); if (categoryEl) fd.append("category", categoryEl.value); if (descriptionEl) fd.append("description", descriptionEl.value); if (tagsEl) fd.append("tags", tagsEl.value); if (taskId) fd.append("task_id", taskId); return fetch("/api/files/upload", { method: "POST", body: fd, credentials: "same-origin" }) .then(function (r) { return r.json(); }) .then(function (data) { if (data.ok && data.file) { showStatus(statusEl, "success", file.name + " erfolgreich hochgeladen."); if (listTarget) { if (emptyState) emptyState.hidden = true; var tbody = listTarget.tagName === "TBODY" ? listTarget : listTarget.querySelector("tbody"); if (tbody) { tbody.insertBefore(buildFileRow(data.file, rowMode), tbody.firstChild); } } } else { showStatus(statusEl, "error", (data.message || "Upload fehlgeschlagen.") + " (" + file.name + ")"); } }) .catch(function () { showStatus(statusEl, "error", "Netzwerkfehler beim Upload von " + file.name + "."); }); } } document.addEventListener("DOMContentLoaded", function () { document.querySelectorAll("[data-upload-widget]").forEach(initWidget); }); })();