WIP: ToC implementation #69
4 changed files with 110 additions and 37 deletions
|
@ -1,8 +1,10 @@
|
|||
use crate::misc::date::Date;
|
||||
use base64::engine::general_purpose;
|
||||
use base64::Engine;
|
||||
use comrak::nodes::{AstNode, NodeValue};
|
||||
use comrak::{format_html, parse_document, Arena, ComrakOptions, ListStyleType, Options};
|
||||
use comrak::nodes::{AstNode, NodeCode, NodeMath, NodeValue};
|
||||
use comrak::{
|
||||
format_html, parse_document, Anchorizer, Arena, ComrakOptions, ListStyleType, Options,
|
||||
};
|
||||
use lol_html::html_content::ContentType;
|
||||
use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings};
|
||||
use ramhorns::Content;
|
||||
|
@ -117,6 +119,7 @@ impl Metadata {
|
|||
pub struct File {
|
||||
pub metadata: Metadata,
|
||||
pub content: String,
|
||||
pub toc_data: String,
|
||||
}
|
||||
|
||||
/// Options used for parser and compiler MD --> HTML
|
||||
|
@ -312,6 +315,8 @@ pub fn read_md(
|
|||
html_content = custom_img_size(&html_content);
|
||||
(html_content, mail_obfsucated) = mail_obfuscation(&html_content);
|
||||
|
||||
let toc = toc_to_html(&generate_toc(root));
|
||||
|
||||
let mut final_metadata = Metadata {
|
||||
info: metadata,
|
||||
mermaid: check_mermaid(root, mermaid_name),
|
||||
|
@ -324,6 +329,7 @@ pub fn read_md(
|
|||
File {
|
||||
metadata: final_metadata,
|
||||
content: html_content,
|
||||
toc_data: toc,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -508,3 +514,87 @@ fn mail_obfuscation(html: &str) -> (String, bool) {
|
|||
(new_html, modified)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TOCEntry {
|
||||
id: String,
|
||||
title: String,
|
||||
depth: u8,
|
||||
}
|
||||
|
||||
fn generate_toc<'a>(root: &'a AstNode<'a>) -> Vec<TOCEntry> {
|
||||
/// See <https://github.com/kivikakk/comrak/blob/b67d406d3b101b93539c37a1ca75bff81ff8c149/src/html.rs#L446>
|
||||
fn collect_text<'a>(node: &'a AstNode<'a>, output: &mut String) {
|
||||
match node.data.borrow().value {
|
||||
NodeValue::Text(ref literal)
|
||||
| NodeValue::Code(NodeCode { ref literal, .. })
|
||||
| NodeValue::Math(NodeMath { ref literal, .. }) => {
|
||||
*output = literal.to_string();
|
||||
}
|
||||
_ => {
|
||||
for n in node.children() {
|
||||
if !output.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
collect_text(n, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut toc = vec![];
|
||||
|
||||
let mut anchorizer = Anchorizer::new();
|
||||
|
||||
// Collect headings first to avoid mutable borrow conflicts
|
||||
let headings: Vec<_> = root
|
||||
.children()
|
||||
.filter_map(|node| {
|
||||
if let NodeValue::Heading(ref nch) = &node.data.borrow().value {
|
||||
Some((*nch, node))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Now process each heading
|
||||
for (nch, node) in headings {
|
||||
let mut title = String::with_capacity(20);
|
||||
collect_text(node, &mut title);
|
||||
|
||||
toc.push(TOCEntry {
|
||||
id: anchorizer.anchorize(title.clone()),
|
||||
title,
|
||||
depth: nch.level,
|
||||
});
|
||||
}
|
||||
|
||||
toc
|
||||
}
|
||||
|
||||
fn toc_to_html(toc: &[TOCEntry]) -> String {
|
||||
if toc.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let mut html = Vec::with_capacity(20 + 20 * toc.len());
|
||||
|
||||
html.extend_from_slice(b"<ul>");
|
||||
|
||||
for entry in toc {
|
||||
// TODO: Use depth
|
||||
html.extend_from_slice(
|
||||
format!(
|
||||
"<li><a href=\"{}\">{} (dbg/depth/{})</a></li>",
|
||||
entry.id, entry.title, entry.depth
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
}
|
||||
|
||||
html.extend_from_slice(b"</ul>");
|
||||
|
||||
String::from_utf8(html).unwrap()
|
||||
}
|
||||
|
|
|
@ -81,5 +81,6 @@ fn read_pdf(data: Vec<u8>) -> File {
|
|||
style="width: 100%; height: 79vh";
|
||||
>"#
|
||||
),
|
||||
toc_data: String::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,7 +186,6 @@ fn get_posts(location: &str) -> Vec<Post> {
|
|||
struct BlogPostTemplate {
|
||||
navbar: NavBar,
|
||||
post: Option<File>,
|
||||
toc: String,
|
||||
}
|
||||
|
||||
#[get("/blog/p/{id}")]
|
||||
|
@ -199,7 +198,7 @@ pub async fn page(path: web::Path<(String,)>, config: web::Data<Config>) -> impl
|
|||
|
||||
fn build_post(file: &str, config: Config) -> String {
|
||||
let mut post = None;
|
||||
let (infos, toc) = get_post(
|
||||
let infos = get_post(
|
||||
&mut post,
|
||||
file,
|
||||
&config.fc.name.unwrap_or_default(),
|
||||
|
@ -214,18 +213,12 @@ fn build_post(file: &str, config: Config) -> String {
|
|||
..NavBar::default()
|
||||
},
|
||||
post,
|
||||
toc,
|
||||
},
|
||||
infos,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_post(
|
||||
post: &mut Option<File>,
|
||||
filename: &str,
|
||||
name: &str,
|
||||
data_dir: &str,
|
||||
) -> (InfosPage, String) {
|
||||
fn get_post(post: &mut Option<File>, filename: &str, name: &str, data_dir: &str) -> InfosPage {
|
||||
let blog_dir = format!("{data_dir}/{BLOG_DIR}/{POST_DIR}");
|
||||
let ext = ".md";
|
||||
|
||||
|
@ -234,13 +227,8 @@ fn get_post(
|
|||
&TypeFileMetadata::Blog,
|
||||
);
|
||||
|
||||
let default = (
|
||||
filename,
|
||||
&format!("Blog d'{name}"),
|
||||
Vec::new(),
|
||||
String::new(),
|
||||
);
|
||||
let (title, desc, tags, toc) = match post {
|
||||
let default = (filename, &format!("Blog d'{name}"), Vec::new());
|
||||
let (title, desc, tags) = match post {
|
||||
Some(data) => (
|
||||
match &data.metadata.info.blog.as_ref().unwrap().title {
|
||||
Some(text) => text,
|
||||
|
@ -254,16 +242,10 @@ fn get_post(
|
|||
Some(tags) => tags.clone(),
|
||||
None => default.2,
|
||||
},
|
||||
match &data.metadata.info.blog.as_ref().unwrap().toc {
|
||||
// TODO: Generate TOC
|
||||
Some(true) => String::new(),
|
||||
_ => default.3,
|
||||
},
|
||||
),
|
||||
None => default,
|
||||
};
|
||||
|
||||
(
|
||||
InfosPage {
|
||||
title: Some(format!("Post: {title}")),
|
||||
desc: Some(desc.clone()),
|
||||
|
@ -273,9 +255,7 @@ fn get_post(
|
|||
.chain(tags.iter().map(|t| t.name.as_str()))
|
||||
.collect::<Vec<_>>(),
|
||||
)),
|
||||
},
|
||||
toc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[routes]
|
||||
|
|
|
@ -28,7 +28,9 @@
|
|||
<main>
|
||||
{{^post}}
|
||||
<p>This post doesn't exist... sorry</p>
|
||||
{{/post}} {{#post}} {{&toc}}
|
||||
{{/post}} {{#post}} {{#metadata}} {{#info}} {{#blog}} {{#toc}}
|
||||
<aside>{{&toc_data}}</aside>
|
||||
{{/toc}} {{/blog}} {{/info}} {{/metadata}}
|
||||
<article>{{&content}}</article>
|
||||
{{/post}}
|
||||
</main>
|
||||
|
|
Loading…
Reference in a new issue