feat add raw plaintext endpoint and link on blob file views

Add a /:repo/raw/:hash route that serves blob content as text/plain. The blob file view toolbar now includes a 'raw' link styled as a button-like anchor next to the breadcrumb pill.

Commit
0813dd4847376448456431ccbf12fdce01674f87
Author
Claude Sonnet 4 <agent@anthropic.com>
Author date
Committer
Claude Sonnet 4 <agent@anthropic.com>
Committer date
Changed files
lib/handlers.ml
index 4bacf56c..90b2b3e9 100644..100644
@@ -200,6 +200,17 @@
200 200 let* tags = Resolvers.Reference.tags repository in
201 201 Views.Repo.tags context tags
202 202
203 Added: let raw_file repository _context id =
204 Added: let* object_ = Resolvers.blob_or_tree repository id in
205 Added: match object_ with
206 Added: | `Blob blob ->
207 Added: Lwt.return
208 Added: (Dream.response
209 Added: ~headers:[ ("Content-Type", "text/plain; charset=utf-8") ]
210 Added: blob.content)
211 Added: | `Tree _ ->
212 Added: error_response (Resolvers.Bad_request "object is a tree, not a blob")
213 Added:
203 214 let readme repository context =
204 215 let* readme = Resolvers.Repo.readme repository in
205 216 let blob =
@@ -228,6 +239,7 @@
228 239 get Routes.commit_path (Repo.handle_id config Repo.commit_id);
229 240 get Routes.files_path (Repo.handle config Repo.files_at_head);
230 241 get Routes.file_path (Repo.handle_id config Repo.file_id);
242 Added: get Routes.raw_file_path (Repo.handle_id config Repo.raw_file);
231 243 get Routes.branches_path (Repo.handle config Repo.branches);
232 244 get Routes.tags_path (Repo.handle config Repo.tags);
233 245 get Routes.readme_path (Repo.handle config Repo.readme);
lib/routes.ml
index 32edb3b2..5f6aedc3 100644..100644
@@ -11,6 +11,7 @@
11 11 | Branches of string
12 12 | Tags of string
13 13 | Readme of string
14 Added: | Raw_file of string * string
14 15
15 16 let%path root_path = "/"
16 17 let%path repo_root_path = "/%s/"
@@ -23,6 +24,7 @@
23 24 let%path branches_path = "/%s/branches/"
24 25 let%path tags_path = "/%s/tags/"
25 26 let%path readme_path = "/%s/README"
27 Added: let%path raw_file_path = "/%s/raw/%s"
26 28 let%path static_path = "/static/%*s"
27 29
28 30 let link_to route ?(other_attrs = []) contents =
@@ -40,5 +42,6 @@
40 42 | Branches repo -> path_attr href branches_path repo
41 43 | Tags repo -> path_attr href tags_path repo
42 44 | Readme repo -> path_attr href readme_path repo
45 Added: | Raw_file (repo, hash) -> path_attr href raw_file_path repo hash
43 46 in
44 47 a (path route :: other_attrs) [ contents ]
lib/static/styles.css
index d984adb8..35d210fc 100644..100644
@@ -441,6 +441,27 @@
441 441 color: white;
442 442 }
443 443
444 Added: .toolbar-raw-link {
445 Added: display: inline-flex;
446 Added: align-items: center;
447 Added: justify-content: center;
448 Added: min-width: 44px;
449 Added: min-height: 44px;
450 Added: padding: 0.4em 0.75em;
451 Added: border-radius: 0.25rem;
452 Added: font-family: monospace;
453 Added: font-size: 0.85em;
454 Added: color: white;
455 Added: background-color: #2a2a2a;
456 Added: text-decoration: none;
457 Added: box-sizing: border-box;
458 Added: }
459 Added:
460 Added: .toolbar-raw-link:hover {
461 Added: background-color: white;
462 Added: color: black;
463 Added: }
464 Added:
444 465 /* Pagination controls */
445 466
446 467 .pagination {
lib/views/repo.ml
index 2919bf23..91b2bcb7 100644..100644
@@ -718,10 +718,20 @@
718 718 String.split_on_char '\n' blob.content
719 719 |> List.mapi to_numbered_line |> List.concat
720 720 in
721 Added: let raw_link =
722 Added: match List.rev trail with
723 Added: | (_, hash) :: _ ->
724 Added: HTML.(
725 Added: Routes.link_to
726 Added: (Raw_file (context.repo, hash))
727 Added: ~other_attrs:[ class_ "toolbar-raw-link" ]
728 Added: (txt "raw"))
729 Added: | [] -> HTML.null []
730 Added: in
721 731 render_page context ~active
722 732 HTML.
723 733 [
724 Removed: toolbar [ breadcrumb_pill context.repo trail ];
734 Added: toolbar [ breadcrumb_pill context.repo trail; raw_link ];
725 735 div blob_attrs formatted_blob;
726 736 ]
727 737