]> git.draconx.ca Git - homepage.git/blob - layouts/listing.xhtml
406e42ba79ac2eaac0fba108141b4fa2ec6f0833
[homepage.git] / layouts / listing.xhtml
1 <%
2 # Nick's web site: Generate directory listing.
3 #
4 # Copyright © 2021 Nick Bowler
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <https://www.gnu.org/licenses/>
18
19 mydir = rep_uri
20
21 files = {}
22 @items.find_all(@item[:pattern]).each do |item|
23   t = item[:updated_at]
24
25   item.reps.each do |rep|
26     next if rep == @rep
27
28     p = rep_uri(rep)
29     d, f = File.split(p)
30     next unless "#{d}/" == mydir
31
32     if p =~ %r{/$}
33       displaysize = Dir.children(File.dirname(rep.raw_path)).length - 1
34       size = displaysize - 1000000
35       type = :DIR
36     else
37       size = File.size(rep.raw_path)
38       displaysize = human_filesize(size)
39       type = nil
40     end
41
42     files[f] = {
43       sorttime: if t then t.to_f else 0.0 end,
44       displaytime: if t then t.getutc.strftime "%Y-%m-%d %H:%M UTC" end,
45       displaysize: displaysize,
46       size: size,
47       type: type,
48     }
49   end
50 end
51
52 if @items["#{File.dirname(mydir)}/index.lst"]
53   files[".."] = { type: :UP }
54 end
55
56 def render_entry(files, key)
57   f = files[key]
58   return <<~EOF
59     <td>#{if f[:type]
60       "<img alt='#{f[:type]}' width='16' height='16' src='#{case f[:type]
61             when :DIR; "/images/folder.svg"
62             when :UP;  "/images/return.svg"
63             else raise "no icon for filetype #{f[:type]}"
64             end}' />"
65     end}</td>
66     <td><a href='#{key}'>#{
67       if key == ".." then "[Parent Directory]" else key end
68     }</a></td>
69     <td>#{f[:displaytime]}</td>
70     <td>#{f[:displaysize]}</td>
71   EOF
72 end
73 %>
74 <div>
75 <script type='x'><![CDATA[]]x><!--]]></script>
76 <input style='display: none' type='radio' name='filelist-sort' id='filelist-name-sort' checked='checked' />
77 <input style='display: none' type='radio' name='filelist-sort' id='filelist-date-sort' />
78 <input style='display: none' type='radio' name='filelist-sort' id='filelist-size-sort' />
79 <input style='display: none' type='checkbox' id='filelist-name-rev' />
80 <input style='display: none' type='checkbox' id='filelist-date-rev' />
81 <input style='display: none' type='checkbox' id='filelist-size-rev' />
82 <script type='x'>--></script>
83 <table class='filelist'>
84   <thead>
85     <tr>
86       <th />
87       <th class='name'>
88         <label for='filelist-name-sort'><span>Name</span></label>
89         <script type='x'><![CDATA[]]x><!--]]></script>
90         <label for='filelist-name-rev' style='display: none'>
91           <span>Name</span>
92           <img alt='FWD' width='16' height='16' src='/images/down.svg' />
93           <img alt='REV' width='16' height='16' src='/images/up.svg' style='display: none' />
94         </label>
95         <script type='x'>--></script>
96       </th>
97       <th class='date'>
98         <label for='filelist-date-sort'><span>Last Modified</span></label>
99         <script type='x'><![CDATA[]]x><!--]]></script>
100         <label for='filelist-date-rev' style='display: none'>
101           <span>Last Modified</span>
102           <img alt='FWD' width='16' height='16' src='/images/down.svg' />
103           <img alt='REV' width='16' height='16' src='/images/up.svg' style='display: none' />
104         </label>
105         <script type='x'>--></script>
106       </th>
107       <th class='size'>
108         <label for='filelist-size-sort'><span>Size</span></label>
109         <script type='x'><![CDATA[]]x><!--]]></script>
110         <label for='filelist-size-rev' style='display: none'>
111           <span>Size</span>
112           <img alt='FWD' width='16' height='16' src='/images/down.svg' />
113           <img alt='REV' width='16' height='16' src='/images/up.svg' style='display: none' />
114         </label>
115         <script type='x'>--></script>
116       </th>
117     </tr>
118   </thead>
119   <tbody>
120 <%=
121     parentrow = if files[".."] then "#{render_entry(files, "..")}" end
122     files.delete("..")
123     if parentrow then "<tr>#{parentrow}</tr>" end
124 %>
125 <%
126 by_name = files.keys.sort{ |a, b| strverscmp(a, b) }
127 by_name.each_index do |i|
128   entry = render_entry(files, by_name[i])
129   if i+1 == by_name.length
130     entry.sub!(%r{(.*)</td>}m,
131                "\\1<script type='x'><![CDATA[]]x><!--]]></script></td>")
132   end
133 %>
134     <tr><%= entry %></tr>
135 <% end %>
136   </tbody>
137
138   <tbody style='display: none'>
139 <%
140 def meta_cmp(files, key, a, b)
141   av, bv = files[a][key], files[b][key]
142   return av <=> bv if av != bv
143   return strverscmp(a, b)
144 end
145
146 by_date = files.keys.sort { |a, b| meta_cmp(files, :sorttime, a, b) }
147 by_size = files.keys.sort { |a, b| meta_cmp(files, :size, a, b) }
148
149 listnames = [ "namerev", "date", "daterev", "size", "sizerev" ]
150 lists = [ by_name.reverse, by_date, by_date.reverse, by_size, by_size.reverse ]
151 if parentrow
152 %>
153   <tr class='<%= listnames.join(" ") %>'><%= parentrow %></tr>
154 <%
155 end
156 evenmap = (0..(lists.length-1)).map { false }
157 even = false
158
159 while not (elems = lists.map(&:first)).compact.empty?
160   matches = (0..(lists.length-1)).to_a.keep_if { |x| evenmap[x] == even }
161   if !matches.empty?
162     elems = elems.values_at(*matches).compact
163     mode = elems.group_by{|a| a}.max{|a, b| a[1].length <=> b[1].length}[0]
164     matches = []
165
166     lists.each_index do |i|
167       if evenmap[i] == even and lists[i].first.eql? mode
168         lists[i].shift
169         evenmap[i] ^= true
170         evenmap[i] = nil if lists[i].empty?
171
172         matches << i
173       end
174     end
175 %>
176     <tr class='<%= listnames.values_at(*matches).join(" ") %>'>
177       <%= render_entry(files, mode) %>
178     </tr>
179 <%
180   else
181 %>
182     <tr><td /></tr>
183 <%
184   end
185
186   even ^= true
187 end
188 %>
189     <tr><td><script type='x'>--></script></tr>
190   </tbody>
191 </table>
192 </div>