diff --git a/src/html/home.rs b/src/html/home.rs
index 3248de2..20e2975 100644
--- a/src/html/home.rs
+++ b/src/html/home.rs
@@ -19,9 +19,10 @@ const INTRO: &str = r#"
fn intro() -> impl Renderable {
+ let (_, html) = parse(INTRO);
maud!(
section .p-card.intro-jp lang="ja-JP" {
- (Raw(parse(INTRO)))
+ (Raw(html))
}
)
}
diff --git a/src/html/post.rs b/src/html/post.rs
index 7b5afee..b3724dc 100644
--- a/src/html/post.rs
+++ b/src/html/post.rs
@@ -1,9 +1,14 @@
use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable};
use crate::md::Post;
use crate::html::page;
+use crate::text::md::Outline;
-pub fn post<'fm, 'md, 'post, T>(fm: &'fm Post, content: T) -> impl Renderable + 'post
+pub fn post<'fm, 'md, 'post, T>(
+ fm: &'fm Post,
+ content: T,
+ outline: Outline,
+) -> impl Renderable + 'post
where
'fm: 'post,
'md: 'post,
@@ -20,8 +25,22 @@ pub fn post<'fm, 'md, 'post, T>(fm: &'fm Post, content: T) -> impl Renderable +
label .wiki-aside__slider for="wiki-aside-shown" {
img .wiki-icon src="/static/svg/double-arrow.svg" width="24" height="24";
}
- // Navigation tree
- //
+ section .link-tree {
+ h2 .link-tree__heading {
+ a .link-tree__heading-text href="#top" { "Content" }
+ }
+ nav #table-of-contents .link-tree__nav {
+ ul .link-tree__nav-list {
+ @for (title, id) in outline.0 {
+ li .link-tree__nav-list-item {
+ a .link-tree__nav-list-text.link href=(format!("#{id}")) {
+ (title)
+ }
+ }
+ }
+ }
+ }
+ }
}
article .wiki-article /*class:list={classlist)*/ {
diff --git a/src/html/wiki.rs b/src/html/wiki.rs
index c193905..b3dbee0 100644
--- a/src/html/wiki.rs
+++ b/src/html/wiki.rs
@@ -1,9 +1,14 @@
use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable};
use crate::md::Wiki;
use crate::html::page;
+use crate::text::md::Outline;
-pub fn wiki<'data, 'html, T>(fm: &'data Wiki, content: T) -> impl Renderable + 'html
+pub fn wiki<'data, 'html, T>(
+ fm: &'data Wiki,
+ content: T,
+ outline: Outline,
+) -> impl Renderable + 'html
where
'data: 'html,
T: Renderable + 'data
diff --git a/src/main.rs b/src/main.rs
index 6f22ee9..271113e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,6 +6,7 @@ use grass;
use html::LinkableData;
use hypertext::{Raw, Renderable};
use once_cell::sync::Lazy;
+use text::md::Outline;
mod md;
mod html;
@@ -61,7 +62,7 @@ impl Everything<'_> {
trait Transformable {
- fn transform<'f, 'm, 'html, T>(&'f self, content: T) -> impl Renderable + 'html
+ fn transform<'f, 'm, 'html, T>(&'f self, content: T, outline: Outline) -> impl Renderable + 'html
where
'f: 'html,
'm: 'html,
@@ -69,16 +70,16 @@ trait Transformable {
fn as_link(&self, path: String) -> Option;
- fn render(data: &str) -> String;
+ fn render(data: &str) -> (Outline, String);
}
impl Transformable for md::Post {
- fn transform<'f, 'm, 'html, T>(&'f self, content: T) -> impl Renderable + 'html
+ fn transform<'f, 'm, 'html, T>(&'f self, content: T, outline: Outline) -> impl Renderable + 'html
where
'f: 'html,
'm: 'html,
T: Renderable + 'm {
- html::post(self, content)
+ html::post(self, content, outline)
}
fn as_link(&self, path: String) -> Option {
@@ -90,13 +91,13 @@ impl Transformable for md::Post {
})
}
- fn render(data: &str) -> String {
+ fn render(data: &str) -> (Outline, String) {
text::md::parse(data)
}
}
impl Transformable for md::Slide {
- fn transform<'f, 'm, 'html, T>(&'f self, content: T) -> impl Renderable + 'html
+ fn transform<'f, 'm, 'html, T>(&'f self, content: T, _: Outline) -> impl Renderable + 'html
where
'f: 'html,
'm: 'html,
@@ -113,32 +114,33 @@ impl Transformable for md::Slide {
})
}
- fn render(data: &str) -> String {
- data
+ fn render(data: &str) -> (Outline, String) {
+ let html = data
.split("\n-----\n")
- .map(|chunk| chunk.split("\n---\n").map(text::md::parse).collect::>())
+ .map(|chunk| chunk.split("\n---\n").map(text::md::parse).map(|e| e.1).collect::>())
.map(|stack| match stack.len() > 1 {
true => format!("", stack.into_iter().map(|slide| format!("")).collect::()),
false => format!("", stack[0])
})
- .collect::()
+ .collect::();
+ (Outline(vec![]), html)
}
}
impl Transformable for md::Wiki {
- fn transform<'f, 'm, 'html, T>(&'f self, content: T) -> impl Renderable + 'html
+ fn transform<'f, 'm, 'html, T>(&'f self, content: T, outline: Outline) -> impl Renderable + 'html
where
'f: 'html,
'm: 'html,
T: Renderable + 'm {
- html::wiki(self, content)
+ html::wiki(self, content, outline)
}
fn as_link(&self, _: String) -> Option {
None
}
- fn render(data: &str) -> String {
+ fn render(data: &str) -> (Outline, String) {
text::md::parse(data)
}
}
@@ -182,9 +184,8 @@ fn transform(meta: gen::Source) -> gen::Asset
let link = T::as_link(&fm, Path::new("/").join(&loc).to_str().unwrap().to_owned());
let call = move |_: &Everything| {
- let data = T::render(&md);
- let data = T::transform(&fm, Raw(data)).render().into();
- data
+ let (outline, html) = T::render(&md);
+ T::transform(&fm, Raw(html), outline).render().into()
};
gen::Asset {
@@ -247,8 +248,8 @@ fn main() {
gen::Asset {
kind: gen::AssetKind::Html(Box::new(|_| {
let data = std::fs::read_to_string("content/index.md").unwrap();
- let data = text::md::parse(&data);
- html::home(Raw(data)).render().to_owned().into()
+ let (outline, html) = text::md::parse(&data);
+ html::home(Raw(html)).render().to_owned().into()
})),
out: "index.html".into(),
link: None,
diff --git a/src/text/md.rs b/src/text/md.rs
index 6e56756..c2ee34d 100644
--- a/src/text/md.rs
+++ b/src/text/md.rs
@@ -1,3 +1,5 @@
+use std::collections::HashMap;
+
use hypertext::Renderable;
use once_cell::sync::Lazy;
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd, TextMergeStream};
@@ -30,10 +32,18 @@ static KATEX_B: Lazy = Lazy::new(||
.unwrap()
);
+pub struct Outline(pub Vec<(String, String)>);
-pub fn parse(text: &str) -> String {
- let stream = Parser::new_ext(text, *OPTS);
- let stream = TextMergeStream::new(stream)
+
+pub fn parse(text: &str) -> (Outline, String) {
+ let (outline, stream) = {
+ let stream = Parser::new_ext(text, *OPTS);
+ let mut stream: Vec<_> = TextMergeStream::new(stream).collect();
+ let outline = set_heading_ids(&mut stream);
+ (outline, stream)
+ };
+
+ let stream = stream.into_iter()
.map(make_math)
.map(make_emoji)
.collect::>();
@@ -44,7 +54,42 @@ pub fn parse(text: &str) -> String {
let mut html = String::new();
pulldown_cmark::html::push_html(&mut html, stream.into_iter());
- html
+
+ (outline, html)
+}
+
+fn set_heading_ids(events: &mut [Event]) -> Outline {
+ let mut cnt = HashMap::::new();
+ let mut out = Vec::new();
+ let mut buf = String::new();
+ let mut ptr = None;
+
+ for event in events {
+ match event {
+ Event::Start(ref mut tag @ Tag::Heading {..}) => {
+ ptr = Some(tag);
+ },
+ Event::Text(ref text) if ptr.is_some() => {
+ buf.push_str(text)
+ },
+ Event::End(TagEnd::Heading(..)) => {
+ let txt = std::mem::take(&mut buf);
+ let url = txt.to_lowercase().replace(' ', "-");
+ let url = match cnt.get_mut(&url) {
+ Some(ptr) => { *ptr += 1; format!("{url}-{ptr}") },
+ None => { cnt.insert(url.clone(), 0); url },
+ };
+ match ptr.take().unwrap() {
+ Tag::Heading { ref mut id, .. } => *id = Some(url.clone().into()),
+ _ => unreachable!(),
+ }
+ out.push((txt, url));
+ },
+ _ => (),
+ }
+ };
+
+ Outline(out)
}
fn make_math(event: Event) -> Event {