[OCaml] Mobile-friendly clone of cgit.
1
(** The page shell: everything that surrounds a page's own content.
2
3
Pages hand this module a {!body_data} description and receive a complete
4
document. The shell decides which navigation applies, whether a toolbar is
5
present, and what goes in the head — pages never assemble those themselves.
6
*)
7
8
(* Page identity and site settings are defined in [Components] so navigation can
9
be built without depending on this module. They are re-exported here because
10
callers name them [Layout.Summary], [Layout.site], and so on. *)
11
12
type page = Components.page = Summary | Commits | Files
13
14
type site = Components.site = {
15
user_name : string;
16
root_title : string;
17
nav_logo : string;
18
}
19
20
let site = Components.site
21
22
type body_data = {
23
title : string; (** heading for pages that show one *)
24
repo : string option; (** [None] on the repository list and project dirs *)
25
subtitle : string;
26
active : page;
27
toolbar : Ui.node list;
28
content : Ui.node list;
29
home_href : string option; (** where the site nav's home link points *)
30
}
31
32
let stylesheets =
33
[ "/static/styles.css"; "/static/syntax-theme.css"; "/static/readme.css" ]
34
35
let head page_title =
36
Ui.document_head ~title:page_title
37
((Ui.meta_viewport :: List.map Ui.stylesheet stylesheets)
38
@ [ Ui.icon "/static/git_icon.svg" ])
39
40
(* A freshly initialised repository carries git's placeholder description; treat
41
it as no description at all rather than showing boilerplate. *)
42
let meaningful_subtitle subtitle =
43
if subtitle = Resolvers.default_repo_description then "" else subtitle
44
45
(** Inside a repository only the description is shown, since the navigation
46
already names the repository. Elsewhere the site identifies itself. *)
47
let header ~has_repo page_title subtitle =
48
match (has_repo, meaningful_subtitle subtitle) with
49
| true, "" -> Ui.nothing
50
| true, subtitle ->
51
Ui.page_banner ~id:"page-header" [ Ui.paragraph_text subtitle ]
52
| false, subtitle ->
53
Ui.page_banner ~id:"page-header"
54
([
55
Ui.image ~class_:"site-logo" ~src:"/static/git_icon.svg" ();
56
Ui.heading [ Ui.text page_title ];
57
]
58
@
59
if subtitle = "" then []
60
else [ Ui.paragraph_text ~class_:"subtitle" subtitle ])
61
62
let footer user_name =
63
let now = Unix.(time () |> localtime) in
64
let year = string_of_int (now.tm_year + 1900) in
65
Ui.page_footer ~class_:"site-footer"
66
[
67
Ui.paragraph_text ~class_:"copyright"
68
(if user_name = "" then Printf.sprintf "Copyright %s" year
69
else Printf.sprintf "Copyright %s %s" year user_name);
70
Ui.paragraph ~class_:"validation"
71
[
72
Ui.text_link ~href:"https://validator.w3.org/check/referer" "Validate";
73
];
74
]
75
76
let body site page_data =
77
(* The sticky toolbar shifts everything below it; the class lets CSS offset
78
sticky descendants by the right amount. *)
79
let body_class =
80
match (page_data.repo, page_data.toolbar) with
81
| Some _, _ :: _ -> Some "has-toolbar"
82
| _ -> None
83
in
84
let navigation, toolbar, banner, compact_nav =
85
match page_data.repo with
86
| None ->
87
( Components.site_nav ~title:site.root_title ~logo:site.nav_logo
88
?home_href:page_data.home_href (),
89
Ui.nothing,
90
Ui.nothing,
91
Ui.nothing )
92
| Some repo ->
93
( Components.repo_nav ~active:page_data.active ~logo:site.nav_logo repo,
94
Components.toolbar page_data.toolbar,
95
header ~has_repo:true page_data.title page_data.subtitle,
96
Components.compact_repo_nav ~active:page_data.active repo )
97
in
98
Ui.document_body ?class_:body_class
99
[
100
Ui.skip_link ~href:"#main" "Skip to content";
101
navigation;
102
toolbar;
103
Ui.page_content ~id:"main" (banner :: page_data.content);
104
compact_nav;
105
footer site.user_name;
106
]
107
108
let render ?(page_title = "Ogit") site body_data =
109
Ui.document ~head:(head page_title) ~body:(body site body_data) ()
110