diff --git a/CHANGELOG.md b/CHANGELOG.md index c265733f..9171203d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## 0.16.0 (unreleased) +### Breaking + +- Switch to pulldown-cmark anchor rather than ours, some (very niche) edge cases are not supported anymore, you can +also specify classes on headers now + +### Other +- Fix markup for fenced code with linenos +- Make `ignored_content` work with nested paths and directories +- `zola serve/build` can now run from anywhere in a zola directory ## 0.15.3 (2022-01-23) diff --git a/Cargo.lock b/Cargo.lock index 10c9eb40..1a4e9821 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2372,9 +2372,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6" dependencies = [ "bitflags", "memchr", diff --git a/components/libs/Cargo.toml b/components/libs/Cargo.toml index 12ede7ab..75a4290b 100644 --- a/components/libs/Cargo.toml +++ b/components/libs/Cargo.toml @@ -25,7 +25,7 @@ svg_metadata = "0.4" slotmap = "1" lexical-sort = "0.3" walkdir = "2" -pulldown-cmark = { version = "0.8", default-features = false } +pulldown-cmark = { version = "0.9", default-features = false, features = ["simd"] } gh-emoji = "1" elasticlunr-rs = {version = "2", default-features = false, features = ["da", "no", "de", "du", "es", "fi", "fr", "it", "pt", "ro", "ru", "sv", "tr"] } ammonia = "3" diff --git a/components/rendering/src/markdown.rs b/components/rendering/src/markdown.rs index 03442b0f..cca0932f 100644 --- a/components/rendering/src/markdown.rs +++ b/components/rendering/src/markdown.rs @@ -2,11 +2,13 @@ use libs::gh_emoji::Replacer as EmojiReplacer; use libs::once_cell::sync::Lazy; use libs::pulldown_cmark as cmark; use libs::tera; +use std::fmt::Write; use crate::context::RenderContext; use crate::table_of_contents::{make_table_of_contents, Heading}; use errors::{Error, Result}; use front_matter::InsertAnchor; +use libs::pulldown_cmark::escape::escape_html; use utils::site::resolve_internal_link; use utils::slugs::slugify_anchors; use utils::vec::InsertMany; @@ -37,11 +39,39 @@ struct HeadingRef { end_idx: usize, level: u32, id: Option, + classes: Vec, } impl HeadingRef { - fn new(start: usize, level: u32) -> HeadingRef { - HeadingRef { start_idx: start, end_idx: 0, level, id: None } + fn new(start: usize, level: u32, anchor: Option, classes: &[String]) -> HeadingRef { + HeadingRef { start_idx: start, end_idx: 0, level, id: anchor, classes: classes.to_vec() } + } + + fn to_html(&self, id: &str) -> String { + let mut buffer = String::with_capacity(100); + buffer.write_str("").unwrap(); + buffer } } @@ -131,10 +161,15 @@ fn get_heading_refs(events: &[Event]) -> Vec { for (i, event) in events.iter().enumerate() { match event { - Event::Start(Tag::Heading(level)) => { - heading_refs.push(HeadingRef::new(i, *level)); + Event::Start(Tag::Heading(level, anchor, classes)) => { + heading_refs.push(HeadingRef::new( + i, + *level as u32, + anchor.map(|a| a.to_owned()), + &classes.iter().map(|x| x.to_string()).collect::>(), + )); } - Event::End(Tag::Heading(_)) => { + Event::End(Tag::Heading(_, _, _)) => { heading_refs.last_mut().expect("Heading end before start?").end_idx = i; } _ => (), @@ -161,7 +196,6 @@ pub fn markdown_to_html( let mut code_block: Option = None; - let mut inserted_anchors: Vec = vec![]; let mut headings: Vec = vec![]; let mut internal_links = Vec::new(); let mut external_links = Vec::new(); @@ -174,6 +208,7 @@ pub fn markdown_to_html( opts.insert(Options::ENABLE_FOOTNOTES); opts.insert(Options::ENABLE_STRIKETHROUGH); opts.insert(Options::ENABLE_TASKLISTS); + opts.insert(Options::ENABLE_HEADING_ATTRIBUTES); if context.config.markdown.smart_punctuation { opts.insert(Options::ENABLE_SMART_PUNCTUATION); @@ -389,45 +424,34 @@ pub fn markdown_to_html( }) .collect(); - let mut heading_refs = get_heading_refs(&events); + let heading_refs = get_heading_refs(&events); let mut anchors_to_insert = vec![]; - - // First heading pass: look for a manually-specified IDs, e.g. `# Heading text {#hash}` - // (This is a separate first pass so that auto IDs can avoid collisions with manual IDs.) - for heading_ref in heading_refs.iter_mut() { - let end_idx = heading_ref.end_idx; - if let Event::Text(ref mut text) = events[end_idx - 1] { - if text.as_bytes().last() == Some(&b'}') { - if let Some(mut i) = text.find("{#") { - let id = text[i + 2..text.len() - 1].to_owned(); - inserted_anchors.push(id.clone()); - while i > 0 && text.as_bytes()[i - 1] == b' ' { - i -= 1; - } - heading_ref.id = Some(id); - *text = text[..i].to_owned().into(); - } - } + let mut inserted_anchors = vec![]; + for heading in &heading_refs { + if let Some(s) = &heading.id { + inserted_anchors.push(s.to_owned()); } } // Second heading pass: auto-generate remaining IDs, and emit HTML - for heading_ref in heading_refs { + for mut heading_ref in heading_refs { let start_idx = heading_ref.start_idx; let end_idx = heading_ref.end_idx; let title = get_text(&events[start_idx + 1..end_idx]); - let id = heading_ref.id.unwrap_or_else(|| { - find_anchor( + + if heading_ref.id.is_none() { + heading_ref.id = Some(find_anchor( &inserted_anchors, slugify_anchors(&title, context.config.slugify.anchors), 0, - ) - }); - inserted_anchors.push(id.clone()); + )); + } - // insert `id` to the tag - let html = format!("", lvl = heading_ref.level, id = id); + inserted_anchors.push(heading_ref.id.clone().unwrap()); + let id = inserted_anchors.last().unwrap(); + + let html = heading_ref.to_html(&id); events[start_idx] = Event::Html(html.into()); // generate anchors and places to insert them @@ -454,8 +478,13 @@ pub fn markdown_to_html( // record heading to make table of contents let permalink = format!("{}#{}", context.current_page_permalink, id); - let h = - Heading { level: heading_ref.level, id, permalink, title, children: Vec::new() }; + let h = Heading { + level: heading_ref.level, + id: id.to_owned(), + permalink, + title, + children: Vec::new(), + }; headings.push(h); } diff --git a/components/rendering/tests/markdown.rs b/components/rendering/tests/markdown.rs index 818e90d5..be3cbd14 100644 --- a/components/rendering/tests/markdown.rs +++ b/components/rendering/tests/markdown.rs @@ -1,12 +1,10 @@ use std::collections::HashMap; -use std::path::PathBuf; use libs::tera::Tera; use config::Config; -use errors::Result; use front_matter::InsertAnchor; -use rendering::{render_content, RenderContext, Rendered}; +use rendering::{render_content, RenderContext}; use templates::ZOLA_TERA; use utils::slugs::SlugifyStrategy; @@ -47,11 +45,6 @@ fn can_make_zola_internal_links() { fn can_handle_heading_ids() { let mut config = Config::default_for_test(); - // Tested things: manual IDs; whitespace flexibility; that automatic IDs avoid collision with - // manual IDs; that duplicates are in fact permitted among manual IDs; that any non-plain-text - // in the middle of `{#…}` will disrupt it from being acknowledged as a manual ID (that last - // one could reasonably be considered a bug rather than a feature, but test it either way); one - // workaround for the improbable case where you actually want `{#…}` at the end of a heading. let cases = vec![ // Basic "# Hello", @@ -79,6 +72,8 @@ fn can_handle_heading_ids() { "# **hi**", // See https://github.com/getzola/zola/issues/569 "# text [^1] there\n[^1]: footnote", + // Chosen slug that already exists with space + "# Classes {#classes .bold .another}", ]; let body = common::render_with_config(&cases.join("\n"), config.clone()).unwrap().body; insta::assert_snapshot!(body); diff --git a/components/rendering/tests/snapshots/markdown__all_markdown_features_integration.snap b/components/rendering/tests/snapshots/markdown__all_markdown_features_integration.snap index df227b92..72aedc4c 100644 --- a/components/rendering/tests/snapshots/markdown__all_markdown_features_integration.snap +++ b/components/rendering/tests/snapshots/markdown__all_markdown_features_integration.snap @@ -101,10 +101,10 @@ console.log(foo(5)); extextension to be used for dest files.

Right aligned columns

- - - - +
OptionDescription
datapath to data files to supply the data that will be passed into templates.
engineengine to be used for processing templates. Handlebars is the default.
extextension to be used for dest files.
+ + +
OptionDescription
datapath to data files to supply the data that will be passed into templates.
engineengine to be used for processing templates. Handlebars is the default.
extextension to be used for dest files.

link text

diff --git a/components/rendering/tests/snapshots/markdown__can_handle_heading_ids-2.snap b/components/rendering/tests/snapshots/markdown__can_handle_heading_ids-2.snap index 4ad76c7f..d2847a8e 100644 --- a/components/rendering/tests/snapshots/markdown__can_handle_heading_ids-2.snap +++ b/components/rendering/tests/snapshots/markdown__can_handle_heading_ids-2.snap @@ -1,6 +1,6 @@ --- source: components/rendering/tests/markdown.rs -assertion_line: 89 +assertion_line: 84 expression: body --- @@ -11,7 +11,7 @@ expression: body

Hello

Hello

Workaround for literal {#…}

-

Auto {#matic}

+

Auto

About

@@ -22,5 +22,6 @@ expression: body

text 1 there

1

footnote

+

Classes

diff --git a/components/rendering/tests/snapshots/markdown__can_handle_heading_ids.snap b/components/rendering/tests/snapshots/markdown__can_handle_heading_ids.snap index 74b5169b..87cce483 100644 --- a/components/rendering/tests/snapshots/markdown__can_handle_heading_ids.snap +++ b/components/rendering/tests/snapshots/markdown__can_handle_heading_ids.snap @@ -1,6 +1,6 @@ --- source: components/rendering/tests/markdown.rs -assertion_line: 84 +assertion_line: 79 expression: body --- @@ -11,7 +11,7 @@ expression: body

Hello

Hello

Workaround for literal {#…}

-

Auto {#matic}

+

Auto

About

@@ -22,5 +22,6 @@ expression: body

text 1 there

1

footnote

+

Classes

diff --git a/docs/content/documentation/content/linking.md b/docs/content/documentation/content/linking.md index 4eefd629..a48857e0 100644 --- a/docs/content/documentation/content/linking.md +++ b/docs/content/documentation/content/linking.md @@ -19,10 +19,10 @@ For example: ## Example code <- example-code-1 ``` -You can also manually specify an id with a `{#…}` suffix on the heading line: +You can also manually specify an id with a `{#…}` suffix on the heading line as well as CSS classes: ```md -# Something manual! {#manual} +# Something manual! {#manual .header .bold} ``` This is useful for making deep links robust, either proactively (so that you can later change the text of a heading