Files
2026-05-23 21:31:34 +02:00

204 lines
7.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(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 ' <a href="/files/' + file.id + '/edit" class="btn btn-sm btn-secondary">Im Browser bearbeiten</a>';
}
function buildFileRow(file, mode) {
var tr = document.createElement("tr");
tr.dataset.fileId = String(file.id);
if (mode === "task") {
tr.innerHTML =
"<td>" + escapeHtml(file.original_filename) + "</td>" +
"<td>" + formatSize(file.size_bytes) + "</td>" +
"<td>" + escapeHtml(file.created_at) + " (" + escapeHtml(file.uploader || "") + ")</td>" +
'<td class="file-actions"><a href="/files/' + file.id + '/download" class="btn btn-sm btn-primary">Download</a>' +
editBtn(file) + "</td>";
return tr;
}
var tags = (file.tags || []).map(function (t) {
return '<span class="tag">' + escapeHtml(t) + "</span>";
}).join("");
tr.innerHTML =
"<td>" + escapeHtml(file.original_filename) + "</td>" +
"<td>" + escapeHtml(file.category || "") + "</td>" +
"<td>" + escapeHtml(file.description || "") + "</td>" +
"<td>" + formatSize(file.size_bytes) + "</td>" +
"<td>" + escapeHtml(file.created_at) + " (" + escapeHtml(file.uploader || "") + ")</td>" +
"<td>" + tags + "</td>" +
'<td class="file-actions"><a href="/files/' + file.id + '/download" class="btn btn-sm btn-primary">Download</a>' +
editBtn(file) + "</td>";
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);
});
})();