(** The page shell: everything that surrounds a page's own content. Pages hand this module a {!body_data} description and receive a complete document. The shell decides which navigation applies, whether a toolbar is present, and what goes in the head — pages never assemble those themselves. *) (* Page identity and site settings are defined in [Components] so navigation can be built without depending on this module. They are re-exported here because callers name them [Layout.Summary], [Layout.site], and so on. *) type page = Components.page = Summary | Commits | Files type site = Components.site = { user_name : string; root_title : string; nav_logo : string; } let site = Components.site type body_data = { title : string; (** heading for pages that show one *) repo : string option; (** [None] on the repository list and project dirs *) subtitle : string; active : page; toolbar : Ui.node list; content : Ui.node list; home_href : string option; (** where the site nav's home link points *) } let stylesheets = [ "/static/styles.css"; "/static/syntax-theme.css"; "/static/readme.css" ] let head page_title = Ui.document_head ~title:page_title ((Ui.meta_viewport :: List.map Ui.stylesheet stylesheets) @ [ Ui.icon "/static/git_icon.svg" ]) (* A freshly initialised repository carries git's placeholder description; treat it as no description at all rather than showing boilerplate. *) let meaningful_subtitle subtitle = if subtitle = Resolvers.default_repo_description then "" else subtitle (** Inside a repository only the description is shown, since the navigation already names the repository. Elsewhere the site identifies itself. *) let header ~has_repo page_title subtitle = match (has_repo, meaningful_subtitle subtitle) with | true, "" -> Ui.nothing | true, subtitle -> Ui.page_banner ~id:"page-header" [ Ui.paragraph_text subtitle ] | false, subtitle -> Ui.page_banner ~id:"page-header" ([ Ui.image ~class_:"site-logo" ~src:"/static/git_icon.svg" (); Ui.heading [ Ui.text page_title ]; ] @ if subtitle = "" then [] else [ Ui.paragraph_text ~class_:"subtitle" subtitle ]) let footer user_name = let now = Unix.(time () |> localtime) in let year = string_of_int (now.tm_year + 1900) in Ui.page_footer ~class_:"site-footer" [ Ui.paragraph_text ~class_:"copyright" (if user_name = "" then Printf.sprintf "Copyright %s" year else Printf.sprintf "Copyright %s %s" year user_name); Ui.paragraph ~class_:"validation" [ Ui.text_link ~href:"https://validator.w3.org/check/referer" "Validate"; ]; ] let body site page_data = (* The sticky toolbar shifts everything below it; the class lets CSS offset sticky descendants by the right amount. *) let body_class = match (page_data.repo, page_data.toolbar) with | Some _, _ :: _ -> Some "has-toolbar" | _ -> None in let navigation, toolbar, banner, compact_nav = match page_data.repo with | None -> ( Components.site_nav ~title:site.root_title ~logo:site.nav_logo ?home_href:page_data.home_href (), Ui.nothing, Ui.nothing, Ui.nothing ) | Some repo -> ( Components.repo_nav ~active:page_data.active ~logo:site.nav_logo repo, Components.toolbar page_data.toolbar, header ~has_repo:true page_data.title page_data.subtitle, Components.compact_repo_nav ~active:page_data.active repo ) in Ui.document_body ?class_:body_class [ Ui.skip_link ~href:"#main" "Skip to content"; navigation; toolbar; Ui.page_content ~id:"main" (banner :: page_data.content); compact_nav; footer site.user_name; ] let render ?(page_title = "Ogit") site body_data = Ui.document ~head:(head page_title) ~body:(body site body_data) ()