diff options
Diffstat (limited to 'src/generate_html.rs')
-rw-r--r-- | src/generate_html.rs | 104 |
1 files changed, 78 insertions, 26 deletions
diff --git a/src/generate_html.rs b/src/generate_html.rs index dca68f7..af48d2e 100644 --- a/src/generate_html.rs +++ b/src/generate_html.rs @@ -63,15 +63,31 @@ pub fn generate_html_redirect(path: &str) -> String { pub fn get_html_head(page: &Page, website: &Website) -> String { + let mut include_default_head = true; + let mut html_head = String::new(); + for block in &page.document.blocks { + if let markdown::Block::Fragment { language, content } = block { + if language == "override-html-head" { + html_head.push_str(content); + include_default_head = false; + } + if language == "embed-html-head" { + html_head.push_str(content); + } + } + } + if include_default_head { + html_head.insert_str(0, &website.get_config("html.head")); + } let root = page.root(); - website.get_config("html.head") + html_head .replace("href='/", &format!("href='{root}")) .replace("src='/", &format!("src='{root}")) } pub fn get_table_of_contents(page: &Page) -> String { - if page.headings.iter().filter(|h| h.level != Level::Heading3).count() < 3 { + if page.headings.len() < 3 { return String::new(); } let mut toc = String::from("<details><summary></summary><ul>\n"); @@ -94,7 +110,7 @@ pub fn get_table_of_contents(page: &Page) -> String { pub fn document_to_html(document: &MarkdownDocument, page: &Page, website: &Website) -> String { - let from = &page.name; + let from = &page; let mut html = String::new(); macro_rules! line_to_html { @@ -112,7 +128,7 @@ pub fn document_to_html(document: &MarkdownDocument, page: &Page, website: &Webs for block in &document.blocks { match block { Block::Heading { level, line } => { - let id = make_url_safe(strip_appendix(&line_to_html!(line))); + let id = make_url_safe(strip_appendix(&line.to_string())); match level { Level::Heading1 => tag!("h1", line, format!("id='{id}'")), Level::Heading2 => tag!("h2", line, format!("id='{id}'")), @@ -176,6 +192,7 @@ pub fn document_to_html(document: &MarkdownDocument, page: &Page, website: &Webs } } let label = sanitize_text(label, true); + let path = sanitize_text(&path, false); match extension.to_lowercase().as_str() { "jpg"|"jpeg"|"png"|"webp"|"gif"|"tiff" => html!( "<figure><a href='{path}'><img src='{path}' alt='{label}' title='{label}' /></a></figure>"), @@ -192,7 +209,15 @@ pub fn document_to_html(document: &MarkdownDocument, page: &Page, website: &Webs "embed-html" => html!("{content}"), "embed-css" => wrap!("style", html!("{content}")), "embed-javascript"|"embed-js" => wrap!("script", html!("{content}")), - "hidden"|"todo"|"embed-html-head" => (), + "embed-html-head"|"override-html-head" => (), + "hidden"|"todo" => (), + "poem" => wrap!("div", "class='poem'", for line in content.lines() { + let line = line.trim_end(); + match line.is_empty() { + true => html!("<br>"), + false => html!("<p>{}</p>", sanitize_text(line, true)), + } + }), "recipe" => { let recipe = Recipe::parse(content); html!("<div class='recipe'><ul>"); @@ -207,9 +232,9 @@ pub fn document_to_html(document: &MarkdownDocument, page: &Page, website: &Webs warn!("Gallery on page {from:?} references nonexistent image {file:?}"); continue; } - let large = format!("{root}images/large/{file}"); - // let small = format!("{root}images/small/{file}"); - let thumb = format!("{root}images/thumb/{file}"); + let large = sanitize_text(&format!("{root}images/large/{file}"), false); + // let small = sanitize_text(&format!("{root}images/small/{file}"), false); + let thumb = sanitize_text(&format!("{root}images/thumb/{file}"), false); html!("<a href='{large}'><img src='{thumb}' /></a>"); }), "gallery-nav" => wrap!("div", "class='gallery-nav'", for line in content.lines() { @@ -219,7 +244,7 @@ pub fn document_to_html(document: &MarkdownDocument, page: &Page, website: &Webs let image = image.trim(); let ParsedLink { path, class, label } = parse_internal_link(name, page, website); if website.has_image(image) { - let thumb = format!("{root}images/thumb/{image}"); + let thumb = sanitize_text(&format!("{root}images/thumb/{image}"), false); html!("<a href='{path}' class='{class}'><img src='{thumb}'/><p>{label}</p></a>") } else { warn!("Gallery-nav on page {from:?} references nonexistent image {image:?}"); @@ -228,11 +253,24 @@ pub fn document_to_html(document: &MarkdownDocument, page: &Page, website: &Webs warn!("Gallery-nav on page {from:?} has line without a '::' separator"); } }), - _ => { - html!("<pre class='{language}'>"); - html!("{}", sanitize_text(content, false)); - html!("</pre>"); - }, + _ => wrap!("pre", format!("class='{language}'"), { + if let Some(i) = website.highlighters.languages.get(language) { + let mut source = String::new(); + let highlighter = &website.highlighters.highlighters[*i]; + for span in highlighter.highlight(content) { + if span.tag.is_empty() { + source.push_str(&sanitize_text(&span.text, false)); + } else { + source.push_str(&format!("<span class='{}'>", span.tag.to_lowercase())); + source.push_str(&sanitize_text(&span.text, false)); + source.push_str("</span>"); + } + } + html!("{source}"); + } else { + html!("{}", sanitize_text(content, false)) + } + }) } } Block::Break => html!("<hr>"), @@ -294,8 +332,11 @@ fn line_to_html(line: &Line, page: &Page, website: &Website) -> String { let text = &sanitize_text(text, false); html.push_str(&format!("<code>{text}</code>")) } Token::Math(text) => { let text = &sanitize_text(text, false); html.push_str(&format!("<span class='math'>{text}</span>")) } - Token::InternalLink(name) => { - let ParsedLink { path, class, label } = parse_internal_link(name, page, website); + Token::InternalLink{ label: link_label, path } => { + let ParsedLink { path, class, mut label } = parse_internal_link(path, page, website); + if !link_label.is_empty() { + label = link_label.to_string(); + } html.push_str(&format!("<a href='{path}' class='{class}'>{label}</a>")) } Token::ExternalLink { label, path } => { @@ -316,7 +357,7 @@ struct ParsedLink { } fn parse_internal_link(name: &str, page: &Page, website: &Website) -> ParsedLink { - let from = &page.name; + let from = &page; let (class, label, path) = match name.split_once('#') { Some(("", heading)) => ("heading", heading, format!("#{}", strip_appendix(heading))), Some((page, heading)) => ("page", heading, format!("{page}.html#{}", strip_appendix(heading))), @@ -341,11 +382,12 @@ fn parse_internal_link(name: &str, page: &Page, website: &Website) -> ParsedLink warn!("Page {from:?} contains link to nonexistent internal heading {heading:?}"); } } + let path = url_encode(&path); ParsedLink { path, class, label } } fn parse_external_link(label: &str, path: &str, page: &Page, website: &Website) -> ParsedLink { - let from = &page.name; + let from = &page; let mut path = path.to_owned(); let mut label = label.to_string(); let mut is_internal = true; @@ -372,6 +414,7 @@ fn parse_external_link(label: &str, path: &str, page: &Page, website: &Website) }; } } + let path = url_encode(&path); let label = sanitize_text(&label, true); ParsedLink { path, class: "external", label } } @@ -398,17 +441,26 @@ fn sanitize_text(text: &str, fancy: bool) -> String { }, '<' => output.push_str("<"), '>' => output.push_str(">"), - '"' if fancy => match prev.is_whitespace() { - true => output.push('“'), - false => output.push('”'), + '"' => match fancy { + true => match prev.is_whitespace() { + true => output.push('“'), + false => output.push('”'), + } + false => output.push_str("""), }, - '\'' if fancy => match prev.is_whitespace() { - true => output.push('‘'), - false => output.push('’'), + '\'' => match fancy { + true => match prev.is_whitespace() { + true => output.push('‘'), + false => output.push('’'), + } + false => output.push_str("'"), }, '-' if fancy => match prev.is_whitespace() && next.is_whitespace() { - true => output.push('—'), - false => output.push('-'), + true => match i > 0 { + true => output.push('—'), // em-dash, for mid-sentence + false => output.push('–'), // en-dash, for start of line + } + false => output.push('-'), // regular dash, for mid-word } _ => output.push(*c), } |