Compare commits

...

2 commits

Author SHA1 Message Date
ba4b5b6b54 Respect toc attribute in metadata
Some checks are pending
ci/woodpecker/push/publish Pipeline is pending approval
2024-06-21 20:43:42 +02:00
2400d33f56 wip: quick and dumb implementation of toc 2024-06-21 20:43:42 +02:00
4 changed files with 110 additions and 37 deletions

View file

@ -1,8 +1,10 @@
use crate::misc::date::Date; use crate::misc::date::Date;
use base64::engine::general_purpose; use base64::engine::general_purpose;
use base64::Engine; use base64::Engine;
use comrak::nodes::{AstNode, NodeValue}; use comrak::nodes::{AstNode, NodeCode, NodeMath, NodeValue};
use comrak::{format_html, parse_document, Arena, ComrakOptions, ListStyleType, Options}; use comrak::{
format_html, parse_document, Anchorizer, Arena, ComrakOptions, ListStyleType, Options,
};
use lol_html::html_content::ContentType; use lol_html::html_content::ContentType;
use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings}; use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings};
use ramhorns::Content; use ramhorns::Content;
@ -119,6 +121,7 @@ impl Metadata {
pub struct File { pub struct File {
pub metadata: Metadata, pub metadata: Metadata,
pub content: String, pub content: String,
pub toc_data: String,
} }
/// Options used for parser and compiler MD --> HTML /// Options used for parser and compiler MD --> HTML
@ -290,6 +293,8 @@ pub fn read_md(
html_content = custom_img_size(&html_content); html_content = custom_img_size(&html_content);
(html_content, mail_obfsucated) = mail_obfuscation(&html_content); (html_content, mail_obfsucated) = mail_obfuscation(&html_content);
let toc = toc_to_html(&generate_toc(root));
let mut final_metadata = Metadata { let mut final_metadata = Metadata {
info: metadata, info: metadata,
mermaid: check_mermaid(root, mermaid_name), mermaid: check_mermaid(root, mermaid_name),
@ -302,6 +307,7 @@ pub fn read_md(
File { File {
metadata: final_metadata, metadata: final_metadata,
content: html_content, content: html_content,
toc_data: toc,
} }
} }
@ -488,3 +494,87 @@ fn mail_obfuscation(html: &str) -> (String, bool) {
(new_html, is_modified) (new_html, is_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()
}

View file

@ -81,5 +81,6 @@ fn read_pdf(data: Vec<u8>) -> File {
style="width: 100%; height: 79vh"; style="width: 100%; height: 79vh";
>"# >"#
), ),
toc_data: String::new(),
} }
} }

View file

@ -186,7 +186,6 @@ fn get_posts(location: &str) -> Vec<Post> {
struct BlogPostTemplate { struct BlogPostTemplate {
navbar: NavBar, navbar: NavBar,
post: Option<File>, post: Option<File>,
toc: String,
} }
#[get("/blog/p/{id}")] #[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 { fn build_post(file: &str, config: Config) -> String {
let mut post = None; let mut post = None;
let (infos, toc) = get_post( let infos = get_post(
&mut post, &mut post,
file, file,
&config.fc.name.unwrap_or_default(), &config.fc.name.unwrap_or_default(),
@ -214,18 +213,12 @@ fn build_post(file: &str, config: Config) -> String {
..NavBar::default() ..NavBar::default()
}, },
post, post,
toc,
}, },
infos, infos,
) )
} }
fn get_post( fn get_post(post: &mut Option<File>, filename: &str, name: &str, data_dir: &str) -> InfosPage {
post: &mut Option<File>,
filename: &str,
name: &str,
data_dir: &str,
) -> (InfosPage, String) {
let blog_dir = format!("{data_dir}/{BLOG_DIR}/{POST_DIR}"); let blog_dir = format!("{data_dir}/{BLOG_DIR}/{POST_DIR}");
let ext = ".md"; let ext = ".md";
@ -234,13 +227,8 @@ fn get_post(
&TypeFileMetadata::Blog, &TypeFileMetadata::Blog,
); );
let default = ( let default = (filename, &format!("Blog d'{name}"), Vec::new());
filename, let (title, desc, tags) = match post {
&format!("Blog d'{name}"),
Vec::new(),
String::new(),
);
let (title, desc, tags, toc) = match post {
Some(data) => ( Some(data) => (
match &data.metadata.info.blog.as_ref().unwrap().title { match &data.metadata.info.blog.as_ref().unwrap().title {
Some(text) => text, Some(text) => text,
@ -254,28 +242,20 @@ fn get_post(
Some(tags) => tags.clone(), Some(tags) => tags.clone(),
None => default.2, None => default.2,
}, },
match &data.metadata.info.blog.as_ref().unwrap().toc {
// TODO: Generate TOC
Some(true) => String::new(),
_ => default.3,
},
), ),
None => default, None => default,
}; };
( InfosPage {
InfosPage { title: Some(format!("Post: {title}")),
title: Some(format!("Post: {title}")), desc: Some(desc.clone()),
desc: Some(desc.clone()), kw: Some(make_kw(
kw: Some(make_kw( &["blog", "blogging", "write", "writing"]
&["blog", "blogging", "write", "writing"] .into_iter()
.into_iter() .chain(tags.iter().map(|t| t.name.as_str()))
.chain(tags.iter().map(|t| t.name.as_str())) .collect::<Vec<_>>(),
.collect::<Vec<_>>(), )),
)), }
},
toc,
)
} }
#[routes] #[routes]

View file

@ -28,7 +28,9 @@
<main> <main>
{{^post}} {{^post}}
<p>This post doesn't exist... sorry</p> <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> <article>{{&content}}</article>
{{/post}} {{/post}}
</main> </main>