View raw

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