From 4a3ce883b146cbb88b38183c1b2ac299ec09d98c Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Sun, 27 Sep 2026 14:32:12 +0100 Subject: [PATCH] serve http,webdav: sort directory listings in the browser Clicking the Name, Size or Modified heading now sorts the listing in the browser and clicking again reverses it, rather than reloading the page with ?sort= and ?order= parameters. Names sort naturally so v1.9.0 comes before v1.10.0, and directories stay first when sorting by name. The ?sort= and ?order= parameters still work for the initial order and are shown in the heading, so existing links and custom templates are unaffected. --- lib/http/templates/index.html | 90 +++++++++++++++-------------------- 1 file changed, 39 insertions(+), 51 deletions(-) diff --git a/lib/http/templates/index.html b/lib/http/templates/index.html index 2c29c035e..a21563895 100644 --- a/lib/http/templates/index.html +++ b/lib/http/templates/index.html @@ -113,8 +113,11 @@ th { th a { color: black; } -th svg { - vertical-align: middle; +th[aria-sort="ascending"] a::after { + content: " \2191"; +} +th[aria-sort="descending"] a::after { + content: " \2193"; } td { white-space: nowrap; @@ -143,23 +146,6 @@ td .goup { .icon { margin-right: 5px; } -.icon.sort { - display: inline-block; - width: 1em; - height: 1em; - position: relative; - top: .2em; -} -.icon.sort .top { - position: absolute; - left: 0; - top: -1px; -} -.icon.sort .bottom { - position: absolute; - bottom: -1px; - left: 0; -} footer { padding: 40px 20px; font-size: 12px; @@ -260,7 +246,7 @@ tr.file:hover td .zip { } - + @@ -294,14 +280,6 @@ tr.file:hover td .zip { - - - - - - - -
@@ -327,15 +305,14 @@ tr.file:hover td .zip { - - - Name + + Name - - Size + + Size - - Modified + + Modified @@ -353,7 +330,7 @@ tr.file:hover td .zip { {{- range .Entries}} - + @@ -431,21 +408,32 @@ tr.file:hover td .zip { } } }; - function toggle(className) { - var order = getUrlParameter('order'); - var elements = document.getElementsByClassName(className); - for(var i = 0, length = elements.length; i < length; i++) { - var currHref = elements[i].href; - if(order=='desc') { - var chg = currHref.replace('desc', 'asc'); - elements[i].href = chg; - } - if(order=='asc') { - var chg = currHref.replace('asc', 'desc'); - elements[i].href = chg; - } - } - }; + // Sort the listing by the column of th, toggling the direction on repeated clicks + function sortBy(th) { + var key = th.getAttribute('data-key'); + var asc = th.getAttribute('aria-sort') != 'ascending'; + document.querySelectorAll('th[aria-sort]').forEach(function(el) { el.removeAttribute('aria-sort'); }); + th.setAttribute('aria-sort', asc ? 'ascending' : 'descending'); + var value = function(tr) { + if (key == 'name') return tr.querySelector('.name').textContent.trim(); + if (key == 'size') return Number(tr.children[2].getAttribute('data-order')); + var time = tr.querySelector('time'); + return time ? time.getAttribute('datetime') : ''; + }; + var rows = Array.prototype.slice.call(document.querySelectorAll('tr.file')); + rows.sort(function(a, b) { + // Directories first when sorting by name + var byDir = key == 'name' ? b.classList.contains('dir') - a.classList.contains('dir') : 0; + var va = value(a), vb = value(b); + var cmp = typeof va == 'string' ? va.localeCompare(vb, undefined, {numeric: true, sensitivity: 'base'}) : va - vb; + return byDir || (asc ? cmp : -cmp); + }); + rows.forEach(function(tr) { tr.parentNode.appendChild(tr); }); + return false; + } + // Show how the server sorted the listing from the ?sort= and ?order= parameters + var sorted = document.querySelector('th[data-key="' + (getUrlParameter('sort') || 'name').replace('dirfirst', '') + '"]'); + if (sorted) sorted.setAttribute('aria-sort', getUrlParameter('order') == 'desc' ? 'descending' : 'ascending'); function readableFileSize(size) { var units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; var i = 0;