diff --git a/internal/web/files.go b/internal/web/files.go
index 1587f75..c4b4a35 100644
--- a/internal/web/files.go
+++ b/internal/web/files.go
@@ -96,12 +96,42 @@ func (s *Server) handleFiles(w http.ResponseWriter, r *http.Request) {
return
}
s.handleFilesUpload(w, r, root, fullPath)
+ case http.MethodDelete:
+ s.handleFilesDelete(w, r, root, relPath, fullPath)
default:
- w.Header().Set("Allow", "GET, POST")
+ w.Header().Set("Allow", "GET, POST, DELETE")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
+// handleFilesDelete removes the file or folder (recursively) at fullPath,
+// mounted at DELETE /files/{path}. The root itself (relPath == "") can
+// never be deleted this way.
+func (s *Server) handleFilesDelete(w http.ResponseWriter, r *http.Request, root, relPath, fullPath string) {
+ if relPath == "" {
+ http.Error(w, "cannot delete the root folder", http.StatusBadRequest)
+ return
+ }
+ if _, err := os.Lstat(fullPath); err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ http.NotFound(w, r)
+ return
+ }
+ s.logger.Error("stat path to delete", "path", fullPath, "error", err)
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ if err := os.RemoveAll(fullPath); err != nil {
+ s.logger.Error("deleting path", "path", fullPath, "error", err)
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, "deleted %q", filepath.Base(fullPath))
+}
+
// handleFilesMkdir creates a new subdirectory named by the "name" form
// field directly inside fullPath (the current directory), mounted at
// POST /files/{path}?mkdir=1.
@@ -161,7 +191,15 @@ func (s *Server) handleFilesGet(w http.ResponseWriter, r *http.Request, username
return
}
defer f.Close()
- w.Header().Set("Content-Disposition", `attachment; filename="`+filepath.Base(fullPath)+`"`)
+ // Force a download only when explicitly requested (the "Download"
+ // link/button); otherwise serve "inline" so the browser can render
+ // natively-supported types (video, audio, images, PDF, text) right
+ // in the tab instead of always saving to disk.
+ disposition := "inline"
+ if r.URL.Query().Has("download") {
+ disposition = "attachment"
+ }
+ w.Header().Set("Content-Disposition", disposition+`; filename="`+filepath.Base(fullPath)+`"`)
http.ServeContent(w, r, info.Name(), info.ModTime(), f)
return
}
diff --git a/internal/web/templates/files.templ b/internal/web/templates/files.templ
index 42d235d..ec0c99e 100644
--- a/internal/web/templates/files.templ
+++ b/internal/web/templates/files.templ
@@ -20,7 +20,7 @@ templ FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry,
@Layout("Files", username) {
Files
-
+