1
0
Fork 0
website/src/markdown.rs

86 lines
3.1 KiB
Rust
Raw Normal View History

use crate::helpers;
use crate::hilighting;
use axum::http::Uri;
2023-07-29 20:22:13 +02:00
use cached::once_cell::sync::Lazy;
2023-06-18 11:34:08 +02:00
use pulldown_cmark::Event;
use pulldown_cmark::Tag;
use pulldown_cmark::{Options, Parser};
2023-07-29 20:22:13 +02:00
use regex::Regex;
static STARTS_WITH_SCHEMA_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\w+:").unwrap());
static EMAIL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^.+?@\w+(\.\w+)*$").unwrap());
2023-06-18 11:34:08 +02:00
pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String {
2023-07-29 20:22:13 +02:00
let mut opt = Options::empty();
opt.insert(Options::ENABLE_FOOTNOTES);
opt.insert(Options::ENABLE_HEADING_ATTRIBUTES);
opt.insert(Options::ENABLE_STRIKETHROUGH);
opt.insert(Options::ENABLE_TABLES);
opt.insert(Options::ENABLE_TASKLISTS);
opt.insert(Options::ENABLE_SMART_PUNCTUATION);
2023-06-18 11:34:08 +02:00
let mut content_html = String::new();
2023-07-29 20:22:13 +02:00
let parser = Parser::new_ext(markdown, opt);
2023-06-18 11:34:08 +02:00
let mut code_block = false;
let mut code_lang = None;
let mut code_accumulator = String::new();
let mut events = Vec::new();
for event in parser {
match event {
Event::Text(text) => {
if code_block {
code_accumulator.push_str(&text);
2023-06-18 11:46:49 +02:00
} else {
2023-06-18 11:34:08 +02:00
events.push(Event::Text(text));
}
}
Event::Start(Tag::Link(t, mut link, title)) => {
if let Some(uri) = base_uri {
2023-07-29 20:22:13 +02:00
if !link.starts_with('#') && !STARTS_WITH_SCHEMA_RE.is_match(&link) && !EMAIL_RE.is_match(&link) {
// convert relative URIs to absolute URIs
link = helpers::uri_with_path(uri, &link).to_string().into();
}
}
events.push(Event::Start(Tag::Link(t, link, title)));
}
Event::Start(Tag::Image(t, mut link, title)) => {
if let Some(uri) = base_uri {
if !link.contains("://") && !link.contains('@') {
// convert relative URIs to absolute URIs
link = helpers::uri_with_path(uri, &link).to_string().into();
}
}
events.push(Event::Start(Tag::Image(t, link, title)));
}
2023-06-18 11:34:08 +02:00
Event::Start(Tag::CodeBlock(kind)) => {
code_block = true;
if let pulldown_cmark::CodeBlockKind::Fenced(lang) = kind {
code_lang = Some(lang);
}
}
Event::End(Tag::CodeBlock(_)) => {
code_block = false;
let lang = code_lang.take().unwrap_or("".into());
2023-07-29 11:51:04 +02:00
let res = hilighting::hilight(&code_accumulator, &lang, Some("base16-ocean.dark"))
.unwrap();
2023-06-18 11:34:08 +02:00
events.push(Event::Html(res.into()));
code_accumulator.clear();
}
_ => events.push(event),
}
}
events.retain(|e| match e {
Event::Text(t) | Event::Html(t) => !t.is_empty(),
_ => true,
});
pulldown_cmark::html::push_html(&mut content_html, events.into_iter());
2023-07-29 12:04:37 +02:00
content_html
2023-06-18 11:34:08 +02:00
}