add page breadcrumbs
This commit is contained in:
		
							parent
							
								
									5b8f100121
								
							
						
					
					
						commit
						92ebc29daf
					
				
					 10 changed files with 162 additions and 23 deletions
				
			
		| 
						 | 
				
			
			@ -25,7 +25,7 @@
 | 
			
		|||
    % id = "01H8VWEFHZA94G0DNPD79YV535"
 | 
			
		||||
    + …
 | 
			
		||||
 | 
			
		||||
        % content.link = "about/emoji"
 | 
			
		||||
        % content.link = "about-treehouse/emoji"
 | 
			
		||||
          id = "01H8VWEFHZ7Z71WJ347WFMC9YT"
 | 
			
		||||
        + by the way did you know this website has custom emojis? and quite a lot of them, too
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,11 @@ use walkdir::WalkDir;
 | 
			
		|||
use crate::{
 | 
			
		||||
    cli::parse::parse_tree_with_diagnostics,
 | 
			
		||||
    config::Config,
 | 
			
		||||
    html::{navmap::build_navigation_map, tree::branches_to_html},
 | 
			
		||||
    html::{
 | 
			
		||||
        breadcrumbs::breadcrumbs_to_html,
 | 
			
		||||
        navmap::{build_navigation_map, NavigationMap},
 | 
			
		||||
        tree::branches_to_html,
 | 
			
		||||
    },
 | 
			
		||||
    tree::SemaRoots,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +38,12 @@ struct Generator {
 | 
			
		|||
    tree_files: Vec<PathBuf>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ParsedTree {
 | 
			
		||||
    tree_path: String,
 | 
			
		||||
    file_id: FileId,
 | 
			
		||||
    target_path: PathBuf,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Generator {
 | 
			
		||||
    fn add_directory_rec(&mut self, directory: &Path) -> anyhow::Result<()> {
 | 
			
		||||
        for entry in WalkDir::new(directory) {
 | 
			
		||||
| 
						 | 
				
			
			@ -98,22 +108,8 @@ impl Generator {
 | 
			
		|||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn generate_all_files(&self, config: &Config, paths: &Paths<'_>) -> anyhow::Result<Treehouse> {
 | 
			
		||||
    fn parse_trees(&self, paths: &Paths<'_>) -> anyhow::Result<(Treehouse, Vec<ParsedTree>)> {
 | 
			
		||||
        let mut treehouse = Treehouse::new();
 | 
			
		||||
 | 
			
		||||
        let mut handlebars = Handlebars::new();
 | 
			
		||||
        let tree_template = Self::register_template(
 | 
			
		||||
            &mut handlebars,
 | 
			
		||||
            &mut treehouse,
 | 
			
		||||
            "tree",
 | 
			
		||||
            &paths.template_dir.join("tree.hbs"),
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        struct ParsedTree {
 | 
			
		||||
            tree_path: String,
 | 
			
		||||
            file_id: FileId,
 | 
			
		||||
            target_path: PathBuf,
 | 
			
		||||
        }
 | 
			
		||||
        let mut parsed_trees = vec![];
 | 
			
		||||
 | 
			
		||||
        for path in &self.tree_files {
 | 
			
		||||
| 
						 | 
				
			
			@ -155,7 +151,28 @@ impl Generator {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok((treehouse, parsed_trees))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn generate_all_files(
 | 
			
		||||
        &self,
 | 
			
		||||
        treehouse: &mut Treehouse,
 | 
			
		||||
        config: &Config,
 | 
			
		||||
        paths: &Paths<'_>,
 | 
			
		||||
        navigation_map: &NavigationMap,
 | 
			
		||||
        parsed_trees: impl IntoIterator<Item = ParsedTree>,
 | 
			
		||||
    ) -> anyhow::Result<()> {
 | 
			
		||||
        let mut handlebars = Handlebars::new();
 | 
			
		||||
        let tree_template = Self::register_template(
 | 
			
		||||
            &mut handlebars,
 | 
			
		||||
            treehouse,
 | 
			
		||||
            "tree",
 | 
			
		||||
            &paths.template_dir.join("tree.hbs"),
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        for parsed_tree in parsed_trees {
 | 
			
		||||
            let breadcrumbs = breadcrumbs_to_html(config, navigation_map, &parsed_tree.tree_path);
 | 
			
		||||
 | 
			
		||||
            let mut tree = String::new();
 | 
			
		||||
            // Temporarily steal the tree out of the treehouse.
 | 
			
		||||
            let roots = treehouse
 | 
			
		||||
| 
						 | 
				
			
			@ -164,7 +181,7 @@ impl Generator {
 | 
			
		|||
                .expect("tree should have been added to the treehouse");
 | 
			
		||||
            branches_to_html(
 | 
			
		||||
                &mut tree,
 | 
			
		||||
                &mut treehouse,
 | 
			
		||||
                treehouse,
 | 
			
		||||
                config,
 | 
			
		||||
                parsed_tree.file_id,
 | 
			
		||||
                &roots.branches,
 | 
			
		||||
| 
						 | 
				
			
			@ -174,15 +191,20 @@ impl Generator {
 | 
			
		|||
            #[derive(Serialize)]
 | 
			
		||||
            pub struct TemplateData<'a> {
 | 
			
		||||
                pub config: &'a Config,
 | 
			
		||||
                pub breadcrumbs: String,
 | 
			
		||||
                pub tree: String,
 | 
			
		||||
            }
 | 
			
		||||
            let template_data = TemplateData {
 | 
			
		||||
                config,
 | 
			
		||||
                breadcrumbs,
 | 
			
		||||
                tree,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let template_data = TemplateData { config, tree };
 | 
			
		||||
            let templated_html = match handlebars.render("tree", &template_data) {
 | 
			
		||||
                Ok(html) => html,
 | 
			
		||||
                Err(error) => {
 | 
			
		||||
                    Self::wrangle_handlebars_error_into_diagnostic(
 | 
			
		||||
                        &mut treehouse,
 | 
			
		||||
                        treehouse,
 | 
			
		||||
                        tree_template,
 | 
			
		||||
                        error.line_no,
 | 
			
		||||
                        error.column_no,
 | 
			
		||||
| 
						 | 
				
			
			@ -201,7 +223,7 @@ impl Generator {
 | 
			
		|||
            std::fs::write(parsed_tree.target_path, templated_html)?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(treehouse)
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -220,10 +242,10 @@ pub fn regenerate(paths: &Paths<'_>) -> anyhow::Result<()> {
 | 
			
		|||
    info!("copying static directory to target directory");
 | 
			
		||||
    copy_dir(paths.static_dir, paths.target_dir.join("static"))?;
 | 
			
		||||
 | 
			
		||||
    info!("generating standalone pages");
 | 
			
		||||
    info!("parsing tree");
 | 
			
		||||
    let mut generator = Generator::default();
 | 
			
		||||
    generator.add_directory_rec(paths.content_dir)?;
 | 
			
		||||
    let treehouse = generator.generate_all_files(&config, paths)?;
 | 
			
		||||
    let (mut treehouse, parsed_trees) = generator.parse_trees(paths)?;
 | 
			
		||||
 | 
			
		||||
    info!("generating navigation map");
 | 
			
		||||
    let navigation_map = build_navigation_map(&treehouse, "index");
 | 
			
		||||
| 
						 | 
				
			
			@ -232,6 +254,15 @@ pub fn regenerate(paths: &Paths<'_>) -> anyhow::Result<()> {
 | 
			
		|||
        navigation_map.to_javascript(),
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
    info!("generating standalone pages");
 | 
			
		||||
    generator.generate_all_files(
 | 
			
		||||
        &mut treehouse,
 | 
			
		||||
        &config,
 | 
			
		||||
        paths,
 | 
			
		||||
        &navigation_map,
 | 
			
		||||
        parsed_trees,
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
    treehouse.report_diagnostics()?;
 | 
			
		||||
 | 
			
		||||
    let duration = start.elapsed();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										39
									
								
								crates/treehouse/src/html/breadcrumbs.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								crates/treehouse/src/html/breadcrumbs.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
use std::{borrow::Cow, fmt::Write};
 | 
			
		||||
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
 | 
			
		||||
use super::{navmap::NavigationMap, EscapeAttribute};
 | 
			
		||||
 | 
			
		||||
pub fn breadcrumbs_to_html(
 | 
			
		||||
    config: &Config,
 | 
			
		||||
    navigation_map: &NavigationMap,
 | 
			
		||||
    tree_path: &str,
 | 
			
		||||
) -> String {
 | 
			
		||||
    let mut s = String::new();
 | 
			
		||||
 | 
			
		||||
    if let Some(path) = navigation_map.paths.get(tree_path) {
 | 
			
		||||
        for (i, element) in path.iter().enumerate() {
 | 
			
		||||
            // Skip the index because it's implied by the logo on the left.
 | 
			
		||||
            if element != "index" {
 | 
			
		||||
                s.push_str("<li class=\"breadcrumb\">");
 | 
			
		||||
                {
 | 
			
		||||
                    let element = path
 | 
			
		||||
                        .get(i - 1)
 | 
			
		||||
                        .map(|p| format!("{p}/"))
 | 
			
		||||
                        .and_then(|prefix| element.strip_prefix(prefix.as_str()).map(Cow::Borrowed))
 | 
			
		||||
                        .unwrap_or_else(|| Cow::Owned(format!("/{element}")));
 | 
			
		||||
                    write!(
 | 
			
		||||
                        s,
 | 
			
		||||
                        "<a href=\"{site}/{element}.html\">{element}</a>",
 | 
			
		||||
                        site = EscapeAttribute(&config.site),
 | 
			
		||||
                        element = EscapeAttribute(&element)
 | 
			
		||||
                    )
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                }
 | 
			
		||||
                s.push_str("</li>");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    s
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
use std::fmt::{self, Display, Write};
 | 
			
		||||
 | 
			
		||||
pub mod breadcrumbs;
 | 
			
		||||
mod markdown;
 | 
			
		||||
pub mod navmap;
 | 
			
		||||
pub mod tree;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -257,6 +257,7 @@ th {
 | 
			
		|||
 | 
			
		||||
nav {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nav .logo {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,52 @@
 | 
			
		|||
/*** Breadcrumbs ***/
 | 
			
		||||
 | 
			
		||||
.breadcrumbs {
 | 
			
		||||
    list-style-type: none;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
 | 
			
		||||
    height: min-content;
 | 
			
		||||
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
 | 
			
		||||
    align-items: center;
 | 
			
		||||
 | 
			
		||||
    opacity: 70%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb::before {
 | 
			
		||||
    content: '';
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
 | 
			
		||||
    background-image:
 | 
			
		||||
        /* breadcrumb */
 | 
			
		||||
        url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTYgMTIgNC00LTQtNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNTU0MjNlIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=');
 | 
			
		||||
    background-repeat: no-repeat;
 | 
			
		||||
    background-position: 50% 50%;
 | 
			
		||||
    opacity: 70%;
 | 
			
		||||
 | 
			
		||||
    width: 32px;
 | 
			
		||||
    height: 1.2em;
 | 
			
		||||
 | 
			
		||||
    vertical-align: text-bottom;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb a {
 | 
			
		||||
    --recursive-mono: 1.0;
 | 
			
		||||
    --recursive-wght: 500;
 | 
			
		||||
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb a:hover {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*** Tree ***/
 | 
			
		||||
 | 
			
		||||
.tree ul {
 | 
			
		||||
    padding-left: clamp(12px, 2vw, 24px);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -195,6 +244,12 @@
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
@media (prefers-color-scheme: dark) {
 | 
			
		||||
    .breadcrumb::before {
 | 
			
		||||
        background-image:
 | 
			
		||||
            /* breadcrumb */
 | 
			
		||||
            url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTYgMTIgNC00LTQtNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDdjZGJmIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .tree details>summary {
 | 
			
		||||
        background-image:
 | 
			
		||||
            /* expand */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								static/svg/dark/breadcrumb.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								static/svg/dark/breadcrumb.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="M6 12L10 8L6 4" fill="none" stroke="#d7cdbf" stroke-width="2" />
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 149 B  | 
							
								
								
									
										3
									
								
								static/svg/light/breadcrumb.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								static/svg/light/breadcrumb.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="M6 12L10 8L6 4" fill="none" stroke="#55423e" stroke-width="2" />
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 149 B  | 
| 
						 | 
				
			
			@ -33,6 +33,12 @@
 | 
			
		|||
                    fill="currentColor" />
 | 
			
		||||
            </svg>
 | 
			
		||||
        </a>
 | 
			
		||||
 | 
			
		||||
        {{#if breadcrumbs}}
 | 
			
		||||
        <ol class="breadcrumbs">
 | 
			
		||||
            {{{ breadcrumbs }}}
 | 
			
		||||
        </ol>
 | 
			
		||||
        {{/if}}
 | 
			
		||||
    </nav>
 | 
			
		||||
 | 
			
		||||
    <noscript>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue