[Web] Generate sidebar from markdown headings

master
Araozu 2023-04-05 18:30:03 -05:00
parent 04b373283c
commit 2f26e4aad1
8 changed files with 129 additions and 24 deletions

View File

@ -1,6 +1,6 @@
use markdown::mdast::Code;
use crate::highlighter::highlight;
use super::highlighter::highlight;
use super::Printable;

View File

@ -2,19 +2,17 @@ use misti::TokenType;
#[macro_export]
macro_rules! replace {
($classes:literal, $token:ident, $offset:ident, $output:ident) => {
{
let start_pos = $token.position;
let end_pos = $token.get_end_position();
($classes:literal, $token:ident, $offset:ident, $output:ident) => {{
let start_pos = $token.position;
let end_pos = $token.get_end_position();
let range = (start_pos + $offset)..(end_pos + $offset);
let html = format!("<span class=\"token {}\">{}</span>", $classes, $token.value);
let range = (start_pos + $offset)..(end_pos + $offset);
let html = format!("<span class=\"token {}\">{}</span>", $classes, $token.value);
$offset += 28 + $classes.len();
$offset += 28 + $classes.len();
$output.replace_range(range, html.as_str());
}
};
$output.replace_range(range, html.as_str());
}};
}
pub fn highlight(input: &String) -> String {
@ -22,10 +20,7 @@ pub fn highlight(input: &String) -> String {
let tokens = misti::tokenize(&input);
if tokens.is_err() {
eprintln!(
"Found a lexical error processing code.\n{:?}",
tokens
);
eprintln!("Found a lexical error processing code.\n{:?}", tokens);
return input.clone();
}
@ -38,6 +33,9 @@ pub fn highlight(input: &String) -> String {
match &token.token_type {
TokenType::Datatype => replace!("class-name", token, offset, output),
TokenType::Number => replace!("number", token, offset, output),
TokenType::Identifier if token.value == "true" || token.value == "false" => {
replace!("keyword", token, offset, output)
}
TokenType::String => {
let start_pos = token.position;
let end_pos = token.get_end_position();
@ -58,7 +56,6 @@ pub fn highlight(input: &String) -> String {
output
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -1,7 +1,7 @@
use markdown::mdast::InlineCode;
use super::highlighter::highlight;
use super::Printable;
use crate::highlighter::highlight;
impl Printable for InlineCode {
fn to_html(&self) -> String {
@ -14,7 +14,10 @@ impl Printable for InlineCode {
.replace(">", "&gt;");
*/
format!("<code class=\"border border-border-color dark:border-transparent\">{}</code>", highlight(&self.value))
format!(
"<code class=\"border border-border-color dark:border-transparent\">{}</code>",
highlight(&self.value)
)
}
fn get_text(&self) -> String {

View File

@ -10,6 +10,8 @@ mod root;
mod strong;
mod text;
mod highlighter;
pub trait Printable {
fn to_html(&self) -> String;
fn get_text(&self) -> String;

View File

@ -1,12 +1,13 @@
use clap::Parser;
use generator::Printable;
use sidebar::SidebarGenerator;
use std::fs::File;
use std::io::Write;
use std::{fs, path::Path};
mod generator;
mod sidebar;
mod utils;
mod highlighter;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
@ -116,6 +117,7 @@ fn process_markdown(file: &Path, input_folder: &Path, output_folder: &Path) -> R
// let html_text = to_html(markdown_text.as_str());
let md_ast = markdown::to_mdast(&markdown_text, &markdown::ParseOptions::gfm()).unwrap();
let html_text = md_ast.to_html();
let sidebar_html = md_ast.generate_sidebar();
// Read template.html
let mut template_path = output_folder.clone();
@ -124,9 +126,13 @@ fn process_markdown(file: &Path, input_folder: &Path, output_folder: &Path) -> R
let template_contents = fs::read(template_path).unwrap();
let template_contents = String::from_utf8(template_contents).unwrap();
let final_output = template_contents.replace("{{markdown}}", &html_text);
let final_output = template_contents
.replace("{{markdown}}", &html_text)
.replace("{{sidebar}}", &sidebar_html);
//
// Write to disk
//
let _ = File::create(&output_file)
.unwrap()
.write_all(final_output.as_bytes())

View File

@ -0,0 +1,92 @@
use std::fmt::Display;
use markdown::mdast::{Heading, Node};
use crate::{generator::Printable, utils};
pub trait SidebarGenerator {
fn generate_sidebar(&self) -> String;
}
impl SidebarGenerator for Node {
fn generate_sidebar(&self) -> String {
match self {
Node::Root(root) => {
let children_nodes = root
.children
.clone()
.into_iter()
.filter_map(|x| match x {
Node::Heading(h) if h.depth <= 3 => Some(h),
_ => None,
})
.collect();
// A top level topic that contains other topics
let topic = extract_topics(&children_nodes, 0, 1);
match topic {
Some((t, _)) => {
let html: String = t.children.iter().map(|x| x.get_html()).collect();
format!("<ul>{}</ul>", html)
}
None => String::from("D:"),
}
}
_ => panic!("??"),
}
}
}
#[derive(Debug)]
struct Topic {
text: String,
children: Box<Vec<Topic>>,
}
impl Topic {
pub fn get_html(&self) -> String {
let extra = if self.children.len() > 0 {
let children_html: String = self.children.iter().map(|x| x.get_html()).collect();
format!("<ol class=\"px-4\">{}</ol>", children_html)
} else {
String::from("")
};
let html_fragment_link = utils::to_html_fragment(&self.text);
format!(
"<li class=\"my-2\"><a href=\"#{}\" class=\"inline-block w-full\">{}</a>{}</li>",
html_fragment_link, self.text, extra
)
}
}
// Return the next heading and all its children
// current_level: the depth of the heading to match
fn extract_topics<'a>(
headings: &'a Vec<Heading>,
current_pos: usize,
current_level: u8,
) -> Option<(Topic, usize)> {
match headings.get(current_pos) {
Some(h) if h.depth == current_level => {
let mut new_vec = Vec::new();
let mut next_pos = current_pos + 1;
while let Some((topic, next)) = extract_topics(headings, next_pos, current_level + 1) {
new_vec.push(topic);
next_pos = next;
}
let title = h.get_text();
let topic = Topic {
text: title,
children: Box::new(new_vec),
};
Some((topic, next_pos))
}
_ => None,
}
}

View File

@ -3,7 +3,7 @@ use markdown::mdast::Node;
use crate::generator::Printable;
pub fn to_html_fragment(text: &String) -> String {
text.clone().to_lowercase().replace(" ", "-")
text.clone().replace(" ", "-")
}
pub fn collect_children_html(vec: &Vec<Node>) -> String {

View File

@ -17,12 +17,17 @@
</head>
<body>
<div class="marked p-4 mx-auto max-w-screen-lg">
{{markdown}}
<div class="mx-auto max-w-screen-lg grid" style="grid-template-columns: 15rem auto;">
<div class="text-sm h-screen pt-12 sticky top-0">
{{sidebar}}
</div>
<div class="marked">
{{markdown}}
</div>
</div>
<script>
// Inter - Google fonts - load here as to not interrupt the page
// Inter - Google fonts - load here to avoid interrupting the page
const linkEl = document.createElement("link");
linkEl.href = "https://fonts.googleapis.com/css2?family=Inter:wght@100;300;400;500;700&display=swap";
linkEl.rel = "stylesheet";