Add breadcrumbs to file browser pages

- Add Tree.find_path to resolve a tree hash to its path trail from root - Render clickable breadcrumb navigation at the top of every /files page - Each path segment links to its corresponding tree - Add .breadcrumbs CSS styling

Commit
c168ae49d225d4cfa10bcfba6bd2a5b604bc9ede
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/handlers.ml
index 39558628..3a8f9235 100644..100644
@@ -40,12 +40,14 @@
40 40
41 41 let files_at_head repo =
42 42 let* tree = Resolvers.Tree.head repo in
43 Removed: Views.Repo.files repo tree
43 Added: Views.Repo.files repo [] tree
44 44
45 45 let file_id repo id =
46 46 let* res = Resolvers.blob_or_tree repo id in
47 47 match res with
48 Removed: | `Tree tree -> Views.Repo.files repo tree
48 Added: | `Tree tree ->
49 Added: let* trail = Resolvers.Tree.find_path repo id in
50 Added: Views.Repo.files repo trail tree
49 51 | `Blob blob -> Views.Repo.file repo blob
50 52
51 53 let branches repo =
lib/resolvers.ml
index f80137ed..fb3d0aa2 100644..100644
@@ -285,6 +285,45 @@
285 285 let tree_id = Store.Value.Commit.tree commit |> Store.Hash.to_hex in
286 286 of_id repo tree_id
287 287 | _ -> Lwt_result.fail @@ `Msg "HEAD reference does not point to a commit"
288 Added:
289 Added: let find_path repo target_hash =
290 Added: let* store = store repo in
291 Added: let* head_hash = resolve_head_hash store in
292 Added: let head_tree_hash =
293 Added: Lwt_result.bind (Store.read store head_hash) @@ function
294 Added: | Git.Value.Commit commit ->
295 Added: Lwt_result.return (Store.Value.Commit.tree commit |> Store.Hash.to_hex)
296 Added: | _ -> Lwt_result.fail @@ `Msg "HEAD is not a commit"
297 Added: in
298 Added: let* root_hash = head_tree_hash in
299 Added: if root_hash = target_hash then Lwt_result.return []
300 Added: else
301 Added: let rec search trail tree_hash =
302 Added: let* hash = hash_of_hex tree_hash in
303 Added: Lwt_result.bind (Store.read store hash) @@ function
304 Added: | Git.Value.Tree tree ->
305 Added: let entries = Store.Value.Tree.to_list tree in
306 Added: let rec try_entries = function
307 Added: | [] -> Lwt_result.return None
308 Added: | (entry : Store.Value.Tree.entry) :: rest ->
309 Added: let entry_hash = Store.Hash.to_hex entry.node in
310 Added: let step = (entry.name, entry_hash) in
311 Added: if entry_hash = target_hash then
312 Added: Lwt_result.return (Some (List.rev (step :: trail)))
313 Added: else
314 Added: match entry.perm with
315 Added: | `Dir -> (
316 Added: let* found = search (step :: trail) entry_hash in
317 Added: match found with
318 Added: | Some _ as result -> Lwt_result.return result
319 Added: | None -> try_entries rest)
320 Added: | _ -> try_entries rest
321 Added: in
322 Added: try_entries entries
323 Added: | _ -> Lwt_result.return None
324 Added: in
325 Added: let* result = search [] root_hash in
326 Added: Lwt_result.return (match result with Some trail -> trail | None -> [])
288 327 end
289 328
290 329 module Blob = struct
lib/static/styles.css
index 81dd93ca..0c02da76 100644..100644
@@ -134,6 +134,20 @@
134 134 border: 1px solid #303030;
135 135 }
136 136
137 Added: .breadcrumbs {
138 Added: font-family: monospace;
139 Added: padding: 0.5em 0;
140 Added: }
141 Added:
142 Added: .breadcrumbs a {
143 Added: color: skyblue;
144 Added: text-decoration: none;
145 Added: }
146 Added:
147 Added: .breadcrumbs a:hover {
148 Added: text-decoration: underline;
149 Added: }
150 Added:
137 151 .diff-file {
138 152 margin: 1.5em 0;
139 153 border: 1px solid #3a3a3a;
lib/views.ml
index 63273f69..0972dece 100644..100644
@@ -214,7 +214,23 @@
214 214 content = HTML.[ ul [] @@ List.map (li_of_commit repo) commits ];
215 215 }
216 216
217 Removed: let files repo (tree : Resolvers.Tree.t) =
217 Added: let files repo (trail : (string * string) list) (tree : Resolvers.Tree.t) =
218 Added: let breadcrumbs =
219 Added: let root_link = Routes.link_to (Files repo) (txt "/") in
220 Added: let crumbs =
221 Added: List.map
222 Added: (fun (name, hash) ->
223 Added: Routes.link_to (File (repo, hash)) (txt "%s" name))
224 Added: trail
225 Added: in
226 Added: let separator = txt " / " in
227 Added: let rec interleave = function
228 Added: | [] -> []
229 Added: | [ x ] -> [ x ]
230 Added: | x :: rest -> x :: separator :: interleave rest
231 Added: in
232 Added: HTML.(nav [ class_ "breadcrumbs" ] (interleave (root_link :: crumbs)))
233 Added: in
218 234 respond
219 235 @@ Page.render ~page_title:(page_title repo)
220 236 {
@@ -222,7 +238,7 @@
222 238 title = repo;
223 239 subtitle = Resolvers.repo_description repo;
224 240 active = Files;
225 Removed: content = HTML.[ ul [] @@ List.map (li_of_entry repo) tree.entries ];
241 Added: content = HTML.[ breadcrumbs; ul [] @@ List.map (li_of_entry repo) tree.entries ];
226 242 }
227 243
228 244 let file repo (blob : Resolvers.Blob.t) =