From ea917b4148c7afbcb9cac4ff4da7d7ade5435f3b Mon Sep 17 00:00:00 2001 From: arnef Date: Mon, 31 Aug 2026 05:57:35 +0200 Subject: [PATCH] feat(web): add file sorting to files browser --- internal/web/files.go | 46 ++- internal/web/templates/files.templ | 33 +- internal/web/templates/files_templ.go | 452 ++++++++++++++++++-------- web/static/app.css | 2 +- web/static/files.js | 28 +- web/ts/files.ts | 28 +- 6 files changed, 419 insertions(+), 170 deletions(-) diff --git a/internal/web/files.go b/internal/web/files.go index 91b35b8..c33920b 100644 --- a/internal/web/files.go +++ b/internal/web/files.go @@ -211,6 +211,16 @@ func (s *Server) handleFilesGet(w http.ResponseWriter, r *http.Request, username return } + // Determine sort order (name asc/desc) from query param, default to asc + sortBy := r.URL.Query().Get("sort_by") + sortDir := r.URL.Query().Get("sort_dir") + if sortBy == "" { + sortBy = "name" + } + if sortDir == "" { + sortDir = "asc" + } + // os.ReadDir already returns entries sorted by filename, so filtering // into two passes keeps each group (directories, then files) // alphabetically sorted while grouping directories first. @@ -234,6 +244,40 @@ func (s *Server) handleFilesGet(w http.ResponseWriter, r *http.Request, username files = append(files, entry) } } + + // Sort entries based on sortBy and sortDir + less := func(i, j templates.FileEntry) bool { + switch sortBy { + case "name": + if sortDir == "desc" { + return i.Name > j.Name + } + return i.Name < j.Name + default: + if sortDir == "desc" { + return i.ModTime > j.ModTime + } + return i.ModTime < j.ModTime + } + } + + // Sort dirs + for i := 0; i < len(dirs)-1; i++ { + for j := i + 1; j < len(dirs); j++ { + if less(dirs[j], dirs[i]) { + dirs[i], dirs[j] = dirs[j], dirs[i] + } + } + } + // Sort files + for i := 0; i < len(files)-1; i++ { + for j := i + 1; j < len(files); j++ { + if less(files[j], files[i]) { + files[i], files[j] = files[j], files[i] + } + } + } + entries := append(dirs, files...) var breadcrumbs []templates.Breadcrumb @@ -248,7 +292,7 @@ func (s *Server) handleFilesGet(w http.ResponseWriter, r *http.Request, username } w.Header().Set("Content-Type", "text/html; charset=utf-8") - _ = templates.FilesPage(username, breadcrumbs, entries, relPath).Render(context.Background(), w) + _ = templates.FilesPage(username, breadcrumbs, entries, relPath, sortBy, sortDir).Render(context.Background(), w) } func (s *Server) handleFilesUpload(w http.ResponseWriter, r *http.Request, root, fullPath string) { diff --git a/internal/web/templates/files.templ b/internal/web/templates/files.templ index ec0c99e..da3d9a8 100644 --- a/internal/web/templates/files.templ +++ b/internal/web/templates/files.templ @@ -16,7 +16,7 @@ type FileEntry struct { RelPath string // relative path (no leading slash) used to build the link } -templ FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry, currentPath string) { +templ FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry, currentPath string, sortBy string, sortDir string) { @Layout("Files", username) {

Files

@@ -33,6 +33,10 @@ templ FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry, Upload folder +
@@ -48,6 +52,8 @@ templ FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry, id="file-drop-zone" data-current-path={ currentPath } data-upload-url={ "/web/files/" + currentPath } + data-sort-by={ sortBy } + data-sort-dir={ sortDir } class="bg-white rounded-lg border-2 border-dashed border-gray-200 p-2 transition-colors" > @@ -60,9 +66,29 @@ templ FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry, - Name + + if sortBy == "name" { + if sortDir == "asc" { + Name + } else { + Name + } + } else { + Name + } + Size - Modified + + if sortBy == "modtime" { + if sortDir == "asc" { + Modified + } else { + Modified + } + } else { + Modified + } + @@ -163,4 +189,3 @@ templ fileDeleteButton(e FileEntry) { Delete } - diff --git a/internal/web/templates/files_templ.go b/internal/web/templates/files_templ.go index bb4eaaf..f036127 100644 --- a/internal/web/templates/files_templ.go +++ b/internal/web/templates/files_templ.go @@ -24,7 +24,7 @@ type FileEntry struct { RelPath string // relative path (no leading slash) used to build the link } -func FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry, currentPath string) templ.Component { +func FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry, currentPath string, sortBy string, sortDir string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -57,176 +57,276 @@ func FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry, c }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Files