Add delete, inline preview, and mobile list layout to the file browser

- DELETE /files/{path} recursively removes a file/folder (root itself
  can't be deleted, 404 on missing paths); wired to a new "Delete"
  action in the web UI with a confirmation prompt.
- Files now open inline (Content-Disposition: inline) so browsers can
  play/preview natively-supported types (video, audio, images, PDF)
  directly instead of always forcing a download. A separate
  "Download" link (?download=1) still forces a save-as.
- Narrow screens get a stacked card list (name, size, modified date,
  download/delete actions) instead of a squeezed table, so nothing is
  hidden or requires horizontal scrolling; the table layout is kept
  unchanged for sm+ screens.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-08-21 09:15:40 +02:00
co-authored by Copilot
parent d0e1dab837
commit b2f39bd1f6
5 changed files with 459 additions and 96 deletions
+22
View File
@@ -84,6 +84,28 @@
setStatus(`Could not create folder: ${String(err)}`);
}
});
document.querySelectorAll(".delete-entry-button").forEach((button) => {
button.addEventListener("click", async () => {
const relPath = button.dataset.path || "";
const name = button.dataset.name || relPath;
if (!window.confirm(`Delete "${name}"? This cannot be undone.`)) {
return;
}
setStatus(`Deleting "${name}"…`);
try {
const resp = await fetch(`/web/files/${relPath}`, { method: "DELETE" });
if (!resp.ok) {
const text = await resp.text();
setStatus(`Could not delete "${name}": ${text}`);
return;
}
window.location.reload();
}
catch (err) {
setStatus(`Could not delete "${name}": ${String(err)}`);
}
});
});
// Recursively walk a dropped DataTransferItem (file or directory) into
// a flat list of File objects, using the browser's non-standard but
// widely-supported webkitGetAsEntry/FileSystemEntry APIs to support
+22
View File
@@ -90,6 +90,28 @@
}
});
document.querySelectorAll<HTMLButtonElement>(".delete-entry-button").forEach((button) => {
button.addEventListener("click", async () => {
const relPath = button.dataset.path || "";
const name = button.dataset.name || relPath;
if (!window.confirm(`Delete "${name}"? This cannot be undone.`)) {
return;
}
setStatus(`Deleting "${name}"…`);
try {
const resp = await fetch(`/web/files/${relPath}`, { method: "DELETE" });
if (!resp.ok) {
const text = await resp.text();
setStatus(`Could not delete "${name}": ${text}`);
return;
}
window.location.reload();
} catch (err) {
setStatus(`Could not delete "${name}": ${String(err)}`);
}
});
});
// Recursively walk a dropped DataTransferItem (file or directory) into
// a flat list of File objects, using the browser's non-standard but
// widely-supported webkitGetAsEntry/FileSystemEntry APIs to support