From bc4daa2900c702913be7dc3e46441b192f44772a Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Tue, 16 Feb 2021 21:03:20 -0500 Subject: [PATCH 1/1] Implement sortable file listing tables. Just for fun, let's add clickable headers to sort the file listings by name, modification time, and file size, both in forward and reverse, entirely implemented using XHTML and CSS. --- content/images/down.svg | 200 ++++++++++++++++++++++++++++++++++++++++ content/images/up.svg | 196 +++++++++++++++++++++++++++++++++++++++ content/style.scss | 85 +++++++++++++++++ layouts/default.xsl | 2 +- layouts/listing.xhtml | 165 +++++++++++++++++++++++++++------ 5 files changed, 621 insertions(+), 27 deletions(-) create mode 100644 content/images/down.svg create mode 100644 content/images/up.svg diff --git a/content/images/down.svg b/content/images/down.svg new file mode 100644 index 0000000..95b82af --- /dev/null +++ b/content/images/down.svg @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + Go Down + + + go + lower + down + arrow + pointer + > + + + + + Andreas Nilsson + + + + + + + + + + + + + + + + + + + diff --git a/content/images/up.svg b/content/images/up.svg new file mode 100644 index 0000000..54263df --- /dev/null +++ b/content/images/up.svg @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + Go Up + + + go + higher + up + arrow + pointer + > + + + + + Andreas Nilsson + + + + + + + + + + + + + + + + + diff --git a/content/style.scss b/content/style.scss index 0c56cc4..22df5d9 100644 --- a/content/style.scss +++ b/content/style.scss @@ -137,12 +137,97 @@ table.cc { } } +$sortcols: name, date, size; +@each $col in $sortcols { + #filelist-#{$col}-sort { + &:checked { + & ~ table.filelist { + /* Update table header state */ + th.#{$col} { + label~label { + display: -moz-inline-box !important; + display: inline-block !important; + } + label { display: none; } + } + + /* Show only appropriate items from the sort body (forward) */ + tbody+tbody>tr.#{$col} { display: table-row; } + tbody+tbody>tr { display: none; } + } + + & ~ #filelist-#{$col}-rev:checked ~ table.filelist { + /* Show only appropriate items from sort body (reversed) */ + tbody+tbody>tr.#{$col}rev { display: table-row; } + tbody+tbody>tr { display: none; } + } + + /* Unhide associated checkbox for keyboard navigation */ + & ~ #filelist-#{$col}-rev { display: block !important; } + } + + &:focus ~ table.filelist th>label~label>span { + border: 1px dotted; + padding: 0; + } + + display: block !important; + position: absolute; + z-index: -1; + opacity: 0; + } + + #filelist-#{$col}-rev { + &:checked ~ table.filelist { + /* Update table header state */ + th.#{$col} { + img+img { + display: -moz-inline-box !important; + display: inline !important; + } + img { display: none; } + } + } + + &:focus ~ table.filelist th>label~label>img { + border: 1px dotted; + padding: 0; + } + + position: absolute; + z-index: -2; + opacity: 0; + } +} + +/* Enable the sorted tables only when non-default option is selected */ +#filelist-name-rev, #filelist-date-sort, #filelist-size-sort { + &:checked~table.filelist>tbody { + &+tbody { display: table-row-group !important; } + display: none; + } +} + table.filelist { &>tr>*:first-child, &>*>tr>*:first-child { &+td { min-width: 50%; } width: 0; } + th>label>* { padding: 1px; } + th>label, th>label>* { + white-space: nowrap; + vertical-align: middle; + display: -moz-inline-box; + display: inline-block; + cursor: pointer; + } + th img { margin-left: 0.5ex; } + + tbody+tbody { + border-bottom: solid 1px $ruledefaultcolour; + } + img { display: block; height: 1.5em; diff --git a/layouts/default.xsl b/layouts/default.xsl index 075ad18..84dcf9d 100644 --- a/layouts/default.xsl +++ b/layouts/default.xsl @@ -31,7 +31,7 @@ + cdata-section-elements='style script' /> diff --git a/layouts/listing.xhtml b/layouts/listing.xhtml index aad5608..406e42b 100644 --- a/layouts/listing.xhtml +++ b/layouts/listing.xhtml @@ -30,50 +30,163 @@ files = {} next unless "#{d}/" == mydir if p =~ %r{/$} - sz = Dir.children(File.dirname(rep.raw_path)).length - 1 - type = "DIR" + displaysize = Dir.children(File.dirname(rep.raw_path)).length - 1 + size = displaysize - 1000000 + type = :DIR else - sz = human_filesize(File.size(rep.raw_path)) + size = File.size(rep.raw_path) + displaysize = human_filesize(size) type = nil end files[f] = { - mtime: if t then t.getutc.strftime "%Y-%m-%d %H:%M UTC" end, - size: sz, + sorttime: if t then t.to_f else 0.0 end, + displaytime: if t then t.getutc.strftime "%Y-%m-%d %H:%M UTC" end, + displaysize: displaysize, + size: size, type: type, } end end if @items["#{File.dirname(mydir)}/index.lst"] - files[".."] = { type: "UP" } + files[".."] = { type: :UP } +end + +def render_entry(files, key) + f = files[key] + return <<~EOF + #{if f[:type] + "#{f[:type]}" + end} + #{ + if key == ".." then "[Parent Directory]" else key end + } + #{f[:displaytime]} + #{f[:displaysize]} + EOF end %> +
+ + + + + + + + - + + + + + -<% files.keys.sort{ |a, b| strverscmp(a, b) }.each do |key| %> - - #{parentrow}" end +%> +<% +by_name = files.keys.sort{ |a, b| strverscmp(a, b) } +by_name.each_index do |i| + entry = render_entry(files, by_name[i]) + if i+1 == by_name.length + entry.sub!(%r{(.*)}m, + "\\1") + end +%> + <%= entry %> <% end %> - - - - + + + +<% +def meta_cmp(files, key, a, b) + av, bv = files[a][key], files[b][key] + return av <=> bv if av != bv + return strverscmp(a, b) +end + +by_date = files.keys.sort { |a, b| meta_cmp(files, :sorttime, a, b) } +by_size = files.keys.sort { |a, b| meta_cmp(files, :size, a, b) } + +listnames = [ "namerev", "date", "daterev", "size", "sizerev" ] +lists = [ by_name.reverse, by_date, by_date.reverse, by_size, by_size.reverse ] +if parentrow +%> + <%= parentrow %> +<% +end +evenmap = (0..(lists.length-1)).map { false } +even = false + +while not (elems = lists.map(&:first)).compact.empty? + matches = (0..(lists.length-1)).to_a.keep_if { |x| evenmap[x] == even } + if !matches.empty? + elems = elems.values_at(*matches).compact + mode = elems.group_by{|a| a}.max{|a, b| a[1].length <=> b[1].length}[0] + matches = [] + + lists.each_index do |i| + if evenmap[i] == even and lists[i].first.eql? mode + lists[i].shift + evenmap[i] ^= true + evenmap[i] = nil if lists[i].empty? + + matches << i + end + end +%> + + <%= render_entry(files, mode) %> -<% end %> +<% + else +%> + +<% + end + + even ^= true +end +%> +
NameLast ModifiedSize
+ + + + + + + + + + + + + + + +
-<% if files[key][:type] %> -
- <%= files[key][:type] %> -
+<%= + parentrow = if files[".."] then "#{render_entry(files, "..")}" end + files.delete("..") + if parentrow then "
<%= - if key == ".." then "[Parent Directory]" else key end - %><%= files[key][:mtime] %><%= files[key][:size] %>
+
-- 2.43.2