From 46c58c33040c0b6395382abe73d96bb0b718da9b Mon Sep 17 00:00:00 2001 From: "slu@innovaphone.com" Date: Wed, 9 Sep 2026 10:19:48 +0200 Subject: [PATCH 1/2] Add sort toggle to file browser (name/date/size) Closes #51. The server already returns mtime and size per entry; the client only rendered the fixed directories-first/alphabetical order. A header button now cycles the sort mode client-side, persisted in localStorage. Co-Authored-By: Claude Sonnet 5 --- public/app.js | 8 ++++++++ public/file-browser.js | 28 +++++++++++++++++++++++++++- public/index.html | 3 +++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/public/app.js b/public/app.js index fdefa1b6..6f6ef98b 100644 --- a/public/app.js +++ b/public/app.js @@ -71,6 +71,7 @@ const fileSidebar = document.getElementById('file-sidebar'); const fileSidebarToggle = document.getElementById('file-sidebar-toggle'); const fileSidebarClose = document.getElementById('file-sidebar-close'); const fileSidebarUp = document.getElementById('file-sidebar-up'); +const fileSidebarSort = document.getElementById('file-sidebar-sort'); const fileList = document.getElementById('file-list'); const fileSidebarPath = document.getElementById('file-sidebar-path'); const fileBrowser = new FileBrowser(fileList, fileSidebarPath, messageInput, (filePath) => { @@ -80,6 +81,13 @@ const fileBrowser = new FileBrowser(fileList, fileSidebarPath, messageInput, (fi renderAttachmentPreviews(); }); +const SORT_LABELS = { name: 'Name', date: 'Date', size: 'Size' }; +fileSidebarSort.title = `Sort: ${SORT_LABELS[fileBrowser.sortBy]}`; +fileSidebarSort.addEventListener('click', () => { + const sortBy = fileBrowser.cycleSort(); + fileSidebarSort.title = `Sort: ${SORT_LABELS[sortBy]}`; +}); + fileSidebarToggle.addEventListener('click', () => { const isCollapsed = fileSidebar.classList.toggle('collapsed'); if (!isCollapsed && !fileBrowser.currentPath) { diff --git a/public/file-browser.js b/public/file-browser.js index efd73805..46e9811b 100644 --- a/public/file-browser.js +++ b/public/file-browser.js @@ -37,6 +37,8 @@ function formatSize(bytes) { return `${(bytes / (1024 * 1024)).toFixed(1)}M`; } +const SORT_MODES = ['name', 'date', 'size']; + export class FileBrowser { constructor(container, pathEl, messageInput, onFileInserted = null) { this.container = container; @@ -44,6 +46,10 @@ export class FileBrowser { this.messageInput = messageInput; this.onFileInserted = onFileInserted; this.currentPath = null; + this.items = []; + this.sortBy = SORT_MODES.includes(localStorage.getItem('tau-file-sort')) + ? localStorage.getItem('tau-file-sort') + : 'name'; this.setupDropTarget(); } @@ -66,12 +72,32 @@ export class FileBrowser { this.currentPath = data.path; this.pathEl.textContent = data.path; this.pathEl.title = data.path; - this.render(data.items); + this.items = data.items; + this.render(this.getSortedItems()); } catch (err) { this.container.innerHTML = '
Failed to load
'; } } + getSortedItems() { + const items = [...this.items]; + items.sort((a, b) => { + if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1; + if (this.sortBy === 'date') return b.mtime - a.mtime; + if (this.sortBy === 'size') return (b.size || 0) - (a.size || 0); + return a.name.localeCompare(b.name); + }); + return items; + } + + cycleSort() { + const next = SORT_MODES[(SORT_MODES.indexOf(this.sortBy) + 1) % SORT_MODES.length]; + this.sortBy = next; + localStorage.setItem('tau-file-sort', next); + if (this.items.length) this.render(this.getSortedItems()); + return next; + } + getParentPath() { if (!this.currentPath) return null; const sep = this.currentPath.includes('\\') ? '\\' : '/'; diff --git a/public/index.html b/public/index.html index 9531c217..50c34904 100644 --- a/public/index.html +++ b/public/index.html @@ -151,6 +151,9 @@ @@ -151,17 +151,27 @@ diff --git a/public/style.css b/public/style.css index 83fd5287..7ae55ea9 100644 --- a/public/style.css +++ b/public/style.css @@ -3018,6 +3018,21 @@ body.hide-thinking .thinking-block { display: none !important; } letter-spacing: 0.04em; } +.file-sort-dropdown { + position: relative; +} + +.file-sort-menu { + left: auto; + right: 0; + min-width: 110px; +} + +.file-sidebar-search-row { + display: flex; + padding: 0 12px 8px; +} + .file-sidebar-path { padding: 6px 12px; font-size: 10px;