Skip to main content

contributor_graphs/
html.rs

1use crate::model::{Contributor, RepoMeta};
2use crate::theme::Theme;
3
4const TEMPLATE: &str = include_str!("assets/template.html");
5
6pub struct HtmlOptions {
7    pub accent: String,
8    pub by_affiliation: bool,
9    pub unaffiliated_label: String,
10    /// Custom themes to register in the page (built-ins live in the template).
11    pub custom_themes: Vec<Theme>,
12    /// Theme ids to offer in the menu, in order.
13    pub theme_order: Vec<String>,
14    /// Initial theme id; `None` follows the OS light/dark preference. A saved
15    /// choice in `localStorage` still wins.
16    pub default_theme: Option<String>,
17    /// Hide the theme switcher and pin the page to one theme.
18    pub lock_theme: bool,
19}
20
21impl Default for HtmlOptions {
22    fn default() -> Self {
23        HtmlOptions {
24            accent: "#2f6feb".into(),
25            by_affiliation: false,
26            unaffiliated_label: "Unaffiliated".into(),
27            custom_themes: Vec::new(),
28            theme_order: vec!["light".into(), "dark".into(), "wikipedia".into()],
29            default_theme: None,
30            lock_theme: false,
31        }
32    }
33}
34
35pub fn render_html(meta: &RepoMeta, contributors: &[Contributor], opts: &HtmlOptions) -> String {
36    let custom: Vec<serde_json::Value> = opts.custom_themes.iter().map(Theme::to_json).collect();
37    let data = serde_json::json!({
38        "repo": meta,
39        "contributors": contributors,
40        "accent": opts.accent,
41        "byAffiliation": opts.by_affiliation,
42        "unaffiliated": opts.unaffiliated_label,
43        "themes": custom,
44        "themeOrder": opts.theme_order,
45        "defaultTheme": opts.default_theme,
46        "lockTheme": opts.lock_theme,
47    });
48    // `<\/` keeps any `</script>` inside the JSON from terminating the tag.
49    let json = serde_json::to_string(&data)
50        .expect("serialize data")
51        .replace("</", "<\\/");
52    let title = format!("{} ยท contributors", meta.name);
53    TEMPLATE
54        .replace("__PAGE_TITLE__", &html_escape(&title))
55        .replace("__DATA__", &json)
56}
57
58fn html_escape(s: &str) -> String {
59    s.replace('&', "&amp;")
60        .replace('<', "&lt;")
61        .replace('>', "&gt;")
62}