add page breadcrumbs
This commit is contained in:
parent
5b8f100121
commit
92ebc29daf
|
@ -25,7 +25,7 @@
|
||||||
% id = "01H8VWEFHZA94G0DNPD79YV535"
|
% id = "01H8VWEFHZA94G0DNPD79YV535"
|
||||||
+ …
|
+ …
|
||||||
|
|
||||||
% content.link = "about/emoji"
|
% content.link = "about-treehouse/emoji"
|
||||||
id = "01H8VWEFHZ7Z71WJ347WFMC9YT"
|
id = "01H8VWEFHZ7Z71WJ347WFMC9YT"
|
||||||
+ by the way did you know this website has custom emojis? and quite a lot of them, too
|
+ 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::{
|
use crate::{
|
||||||
cli::parse::parse_tree_with_diagnostics,
|
cli::parse::parse_tree_with_diagnostics,
|
||||||
config::Config,
|
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,
|
tree::SemaRoots,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,6 +38,12 @@ struct Generator {
|
||||||
tree_files: Vec<PathBuf>,
|
tree_files: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ParsedTree {
|
||||||
|
tree_path: String,
|
||||||
|
file_id: FileId,
|
||||||
|
target_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
impl Generator {
|
impl Generator {
|
||||||
fn add_directory_rec(&mut self, directory: &Path) -> anyhow::Result<()> {
|
fn add_directory_rec(&mut self, directory: &Path) -> anyhow::Result<()> {
|
||||||
for entry in WalkDir::new(directory) {
|
for entry in WalkDir::new(directory) {
|
||||||
|
@ -98,22 +108,8 @@ impl Generator {
|
||||||
Ok(())
|
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 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![];
|
let mut parsed_trees = vec![];
|
||||||
|
|
||||||
for path in &self.tree_files {
|
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 {
|
for parsed_tree in parsed_trees {
|
||||||
|
let breadcrumbs = breadcrumbs_to_html(config, navigation_map, &parsed_tree.tree_path);
|
||||||
|
|
||||||
let mut tree = String::new();
|
let mut tree = String::new();
|
||||||
// Temporarily steal the tree out of the treehouse.
|
// Temporarily steal the tree out of the treehouse.
|
||||||
let roots = treehouse
|
let roots = treehouse
|
||||||
|
@ -164,7 +181,7 @@ impl Generator {
|
||||||
.expect("tree should have been added to the treehouse");
|
.expect("tree should have been added to the treehouse");
|
||||||
branches_to_html(
|
branches_to_html(
|
||||||
&mut tree,
|
&mut tree,
|
||||||
&mut treehouse,
|
treehouse,
|
||||||
config,
|
config,
|
||||||
parsed_tree.file_id,
|
parsed_tree.file_id,
|
||||||
&roots.branches,
|
&roots.branches,
|
||||||
|
@ -174,15 +191,20 @@ impl Generator {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct TemplateData<'a> {
|
pub struct TemplateData<'a> {
|
||||||
pub config: &'a Config,
|
pub config: &'a Config,
|
||||||
|
pub breadcrumbs: String,
|
||||||
pub tree: 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) {
|
let templated_html = match handlebars.render("tree", &template_data) {
|
||||||
Ok(html) => html,
|
Ok(html) => html,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
Self::wrangle_handlebars_error_into_diagnostic(
|
Self::wrangle_handlebars_error_into_diagnostic(
|
||||||
&mut treehouse,
|
treehouse,
|
||||||
tree_template,
|
tree_template,
|
||||||
error.line_no,
|
error.line_no,
|
||||||
error.column_no,
|
error.column_no,
|
||||||
|
@ -201,7 +223,7 @@ impl Generator {
|
||||||
std::fs::write(parsed_tree.target_path, templated_html)?;
|
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");
|
info!("copying static directory to target directory");
|
||||||
copy_dir(paths.static_dir, paths.target_dir.join("static"))?;
|
copy_dir(paths.static_dir, paths.target_dir.join("static"))?;
|
||||||
|
|
||||||
info!("generating standalone pages");
|
info!("parsing tree");
|
||||||
let mut generator = Generator::default();
|
let mut generator = Generator::default();
|
||||||
generator.add_directory_rec(paths.content_dir)?;
|
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");
|
info!("generating navigation map");
|
||||||
let navigation_map = build_navigation_map(&treehouse, "index");
|
let navigation_map = build_navigation_map(&treehouse, "index");
|
||||||
|
@ -232,6 +254,15 @@ pub fn regenerate(paths: &Paths<'_>) -> anyhow::Result<()> {
|
||||||
navigation_map.to_javascript(),
|
navigation_map.to_javascript(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
info!("generating standalone pages");
|
||||||
|
generator.generate_all_files(
|
||||||
|
&mut treehouse,
|
||||||
|
&config,
|
||||||
|
paths,
|
||||||
|
&navigation_map,
|
||||||
|
parsed_trees,
|
||||||
|
)?;
|
||||||
|
|
||||||
treehouse.report_diagnostics()?;
|
treehouse.report_diagnostics()?;
|
||||||
|
|
||||||
let duration = start.elapsed();
|
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};
|
use std::fmt::{self, Display, Write};
|
||||||
|
|
||||||
|
pub mod breadcrumbs;
|
||||||
mod markdown;
|
mod markdown;
|
||||||
pub mod navmap;
|
pub mod navmap;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
|
|
|
@ -257,6 +257,7 @@ th {
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav .logo {
|
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 {
|
.tree ul {
|
||||||
padding-left: clamp(12px, 2vw, 24px);
|
padding-left: clamp(12px, 2vw, 24px);
|
||||||
}
|
}
|
||||||
|
@ -195,6 +244,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.breadcrumb::before {
|
||||||
|
background-image:
|
||||||
|
/* breadcrumb */
|
||||||
|
url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTYgMTIgNC00LTQtNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDdjZGJmIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=')
|
||||||
|
}
|
||||||
|
|
||||||
.tree details>summary {
|
.tree details>summary {
|
||||||
background-image:
|
background-image:
|
||||||
/* expand */
|
/* 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" />
|
fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
{{#if breadcrumbs}}
|
||||||
|
<ol class="breadcrumbs">
|
||||||
|
{{{ breadcrumbs }}}
|
||||||
|
</ol>
|
||||||
|
{{/if}}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<noscript>
|
<noscript>
|
||||||
|
|
Loading…
Reference in a new issue