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:
+40
-2
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user