[OCaml] Mobile-friendly clone of cgit.
feat Link files by path instead of object id
Tree rows, breadcrumbs, raw links, and image sources now use the File_at and Raw_at routes. Path links survive new commits that touch the file, and the raw handler no longer needs a whole-tree search to recover a filename for MIME detection.
lib/views/repo_view.ml
@@ -104,9 +104,12 @@
104
104
105
105
let tree_display_limit = 3
106
106
107
Removed:
let rec tree_row repo (node : Resolvers.Tree.tree_node) =
107
Added:
let rec tree_row repo ~prefix (node : Resolvers.Tree.tree_node) =
108
108
let entry = node.entry in
109
Removed:
let route = Routes.File (repo, entry.hash) in
109
Added:
(* Entry names may span several segments when single-child directories were
110
Added:
collapsed, so appending them to the prefix always yields the full path. *)
111
Added:
let path = if prefix = "" then entry.name else prefix ^ "/" ^ entry.name in
112
Added:
let route = Routes.File_at (repo, path) in
110
113
(* Dotfiles stay visible but are de-emphasised. *)
111
114
let modifier =
112
115
if String.length entry.name > 0 && entry.name.[0] = '.' then "tree-hidden"
@@ -125,11 +128,13 @@
125
128
if omitted = 0 then [] else [ Components.overflow_row ~route omitted ]
126
129
in
127
130
Components.directory ~modifier ~route ~name:entry.name
128
Removed:
(List.map (tree_row repo) shown @ overflow)
131
Added:
(List.map (tree_row repo ~prefix:path) shown @ overflow)
129
132
130
133
(** {1 Trails} *)
131
134
132
Removed:
(** The path from the repository root to the entry being viewed. *)
135
Added:
(** The path from the repository root to the entry being viewed. Each crumb
136
Added:
links to the accumulated path, so intermediate directories resolve by name
137
Added:
rather than by object id. *)
133
138
let path_trail repo (trail : (string * string) list) =
134
139
let repo_name =
135
140
match List.rev (String.split_on_char '/' repo) with
@@ -138,10 +143,16 @@
138
143
in
139
144
let root = Ui.crumb ~href:(Components.url (Files repo)) repo_name in
140
145
let entries =
141
Removed:
List.map
142
Removed:
(fun (name, hash) ->
143
Removed:
Ui.crumb ~href:(Components.url (File (repo, hash))) name)
144
Removed:
trail
146
Added:
let rec go prefix acc = function
147
Added:
| [] -> List.rev acc
148
Added:
| (name, _hash) :: rest ->
149
Added:
let path = if prefix = "" then name else prefix ^ "/" ^ name in
150
Added:
let crumb =
151
Added:
Ui.crumb ~href:(Components.url (File_at (repo, path))) name
152
Added:
in
153
Added:
go path (crumb :: acc) rest
154
Added:
in
155
Added:
go "" [] trail
145
156
in
146
157
Ui.breadcrumb ~class_:"path-pill" ~link_class:"path-pill-link"
147
158
~separator_class:"path-pill-sep" ~separator:"/" (root :: entries)
@@ -235,9 +246,12 @@
235
246
]
236
247
237
248
let files context trail (entries : Resolvers.Tree.tree_node list) =
249
Added:
(* Rows link to their full path, so a listing below the root needs the trail
250
Added:
as the path prefix. *)
251
Added:
let prefix = String.concat "/" (List.map fst trail) in
238
252
render_page context ~active:Files
239
253
~toolbar:[ path_trail context.repo trail ]
240
Removed:
[ Ui.items_of ~id:"file-tree" (tree_row context.repo) entries ]
254
Added:
[ Ui.items_of ~id:"file-tree" (tree_row context.repo ~prefix) entries ]
241
255
242
256
let is_image_filename filename =
243
257
match Filename.extension filename |> String.lowercase_ascii with
@@ -250,21 +264,20 @@
250
264
let filename =
251
265
match List.rev trail with (name, _) :: _ -> Some name | [] -> None
252
266
in
267
Added:
let full_path = String.concat "/" (List.map fst trail) in
268
Added:
let raw_url =
269
Added:
if full_path = "" then None
270
Added:
else Some (Components.url (Raw_at (context.repo, full_path)))
271
Added:
in
253
272
let raw_link =
254
Removed:
match List.rev trail with
255
Removed:
| (_, hash) :: _ ->
256
Removed:
Ui.paragraph
257
Removed:
[ Components.route_link (Raw_file (context.repo, hash)) "View raw" ]
258
Removed:
| [] -> Ui.nothing
273
Added:
match raw_url with
274
Added:
| Some href -> Ui.paragraph [ Ui.text_link ~href "View raw" ]
275
Added:
| None -> Ui.nothing
259
276
in
260
277
let body =
261
278
match filename with
262
279
| Some filename when is_image_filename filename ->
263
Removed:
let src =
264
Removed:
match List.rev trail with
265
Removed:
| (_, hash) :: _ -> Components.url (Raw_file (context.repo, hash))
266
Removed:
| [] -> ""
267
Removed:
in
280
Added:
let src = Option.value raw_url ~default:"" in
268
281
Ui.block ~class_:"image-preview"
269
282
[ Ui.image ~class_:"file-image" ~alt:filename ~src () ]
270
283
| Some filename when Prose.Render.is_doc_filename filename ->