[OCaml] Mobile-friendly clone of cgit.
feat render image files as inline images in file views
Image files (PNG, JPG, JPEG, GIF, SVG, WebP, ICO, BMP, AVIF) are now displayed as <img> elements pointing to the raw file URL instead of being shown as syntax-highlighted source. The raw file handler now resolves the trail to determine the filename and serves images with correct MIME types (critical for SVG rendering). On mobile, images scale to full viewport width; on desktop they are capped at 80% of the container.
Changed files
lib/handlers.ml
@@ -251,13 +251,31 @@
251
251
Views.Repo.files context trail nodes
252
252
| `Blob blob -> Views.Repo.file context trail blob
253
253
254
Added:
let mime_of_filename filename =
255
Added:
match Filename.extension filename |> String.lowercase_ascii with
256
Added:
| ".png" -> "image/png"
257
Added:
| ".jpg" | ".jpeg" -> "image/jpeg"
258
Added:
| ".gif" -> "image/gif"
259
Added:
| ".svg" -> "image/svg+xml"
260
Added:
| ".webp" -> "image/webp"
261
Added:
| ".ico" -> "image/x-icon"
262
Added:
| ".bmp" -> "image/bmp"
263
Added:
| ".avif" -> "image/avif"
264
Added:
| _ -> "text/plain; charset=utf-8"
265
Added:
254
266
let raw_file repository _context id =
267
Added:
let* trail = Resolvers.Tree.find_path repository id in
255
268
let* object_ = Resolvers.blob_or_tree repository id in
256
269
match object_ with
257
270
| `Blob blob ->
271
Added:
let content_type =
272
Added:
match List.rev trail with
273
Added:
| (name, _) :: _ -> mime_of_filename name
274
Added:
| [] -> "text/plain; charset=utf-8"
275
Added:
in
258
276
Lwt.return
259
277
(Dream.response
260
Removed:
~headers:[ ("Content-Type", "text/plain; charset=utf-8") ]
278
Added:
~headers:[ ("Content-Type", content_type) ]
261
279
blob.content)
262
280
| `Tree _ ->
263
281
error_response (Resolvers.Bad_request "object is a tree, not a blob")
lib/static/styles.css
@@ -1223,3 +1223,22 @@
1223
1223
.error-nav {
1224
1224
margin-top: 2em;
1225
1225
}
1226
Added:
1227
Added:
/* Image file preview */
1228
Added:
1229
Added:
.image-preview {
1230
Added:
display: flex;
1231
Added:
justify-content: center;
1232
Added:
padding: 1em;
1233
Added:
}
1234
Added:
1235
Added:
.file-image {
1236
Added:
max-width: 100%;
1237
Added:
height: auto;
1238
Added:
}
1239
Added:
1240
Added:
@media (min-width: 768px) {
1241
Added:
.file-image {
1242
Added:
max-width: 80%;
1243
Added:
}
1244
Added:
}
lib/views/repo.ml
@@ -277,6 +277,13 @@
277
277
~toolbar:[ path_trail context.repo trail ]
278
278
[ Ui.items_of ~id:"file-tree" (tree_row context.repo) entries ]
279
279
280
Added:
let is_image_filename filename =
281
Added:
match Filename.extension filename |> String.lowercase_ascii with
282
Added:
| ".png" | ".jpg" | ".jpeg" | ".gif" | ".svg" | ".webp" | ".ico" | ".bmp"
283
Added:
| ".avif" ->
284
Added:
true
285
Added:
| _ -> false
286
Added:
280
287
let file context trail (blob : Resolvers.Blob.t) =
281
288
let filename =
282
289
match List.rev trail with (name, _) :: _ -> Some name | [] -> None
@@ -290,6 +297,14 @@
290
297
in
291
298
let body =
292
299
match filename with
300
Added:
| Some filename when is_image_filename filename ->
301
Added:
let src =
302
Added:
match List.rev trail with
303
Added:
| (_, hash) :: _ -> Components.url (Raw_file (context.repo, hash))
304
Added:
| [] -> ""
305
Added:
in
306
Added:
Ui.block ~class_:"image-preview"
307
Added:
[ Ui.image ~class_:"file-image" ~alt:filename ~src () ]
293
308
| Some filename when Readme.is_readme_filename filename ->
294
309
Readme.render ~filename blob.content
295
310
| _ ->