diff --git a/components/rendering/src/shortcode.rs b/components/rendering/src/shortcode.rs index 497a28f4..aece80ee 100644 --- a/components/rendering/src/shortcode.rs +++ b/components/rendering/src/shortcode.rs @@ -3,6 +3,7 @@ use pest::iterators::Pair; use pest::Parser; use pest_derive::Parser; use regex::Regex; +use std::collections::HashMap; use tera::{to_value, Context, Map, Value}; use crate::context::RenderContext; @@ -102,6 +103,7 @@ fn render_shortcode( name: &str, args: &Map, context: &RenderContext, + invocation_count: u32, body: Option<&str>, ) -> Result { let mut tera_context = Context::new(); @@ -112,6 +114,7 @@ fn render_shortcode( // Trimming right to avoid most shortcodes with bodies ending up with a HTML new line tera_context.insert("body", b.trim_end()); } + tera_context.insert("nth", &invocation_count); tera_context.extend(context.tera_context.clone()); let mut template_name = format!("shortcodes/{}.md", name); @@ -139,6 +142,12 @@ fn render_shortcode( pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result { let mut res = String::with_capacity(content.len()); + let mut invocation_map: HashMap = HashMap::new(); + let mut get_invocation_count = |name: &str| { + let invocation_number = invocation_map.entry(String::from(name)).or_insert(0); + *invocation_number += 1; + *invocation_number + }; let mut pairs = match ContentParser::parse(Rule::page, content) { Ok(p) => p, @@ -184,7 +193,13 @@ pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result res.push_str(p.as_span().as_str()), Rule::inline_shortcode => { let (name, args) = parse_shortcode_call(p); - res.push_str(&render_shortcode(&name, &args, context, None)?); + res.push_str(&render_shortcode( + &name, + &args, + context, + get_invocation_count(&name), + None, + )?); } Rule::shortcode_with_body => { let mut inner = p.into_inner(); @@ -192,7 +207,13 @@ pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result { res.push_str( diff --git a/components/rendering/tests/markdown.rs b/components/rendering/tests/markdown.rs index 7d46e11b..835e0fa4 100644 --- a/components/rendering/tests/markdown.rs +++ b/components/rendering/tests/markdown.rs @@ -1059,6 +1059,36 @@ fn emoji_aliases_are_ignored_when_disabled_in_config() { assert_eq!(res.body, "

Hello, World! :smile:

\n"); } +#[test] +fn invocation_count_increments_in_shortcode() { + let permalinks_ctx = HashMap::new(); + let mut tera = Tera::default(); + tera.extend(&ZOLA_TERA).unwrap(); + + let shortcode_template_a = r#"

a: {{ nth }}

"#; + let shortcode_template_b = r#"

b: {{ nth }}

"#; + + let markdown_string = r#"{{ a() }} +{{ b() }} +{{ a() }} +{{ b() }} +"#; + + let expected = r#"

a: 1

+

b: 1

+

a: 2

+

b: 2

+"#; + + tera.add_raw_template("shortcodes/a.html", shortcode_template_a).unwrap(); + tera.add_raw_template("shortcodes/b.html", shortcode_template_b).unwrap(); + let config = Config::default(); + let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None); + + let res = render_content(markdown_string, &context).unwrap(); + assert_eq!(res.body, expected); +} + #[test] fn basic_external_links_unchanged() { let permalinks_ctx = HashMap::new(); diff --git a/docs/content/documentation/content/shortcodes.md b/docs/content/documentation/content/shortcodes.md index b0fba7f9..8deb23c3 100644 --- a/docs/content/documentation/content/shortcodes.md +++ b/docs/content/documentation/content/shortcodes.md @@ -134,6 +134,24 @@ If you want to have some content that looks like a shortcode but not have Zola t you will need to escape it by using `{%/*` and `*/%}` instead of `{%` and `%}`. You won't need to escape anything else until the closing tag. +### Invocation Count + +Every shortcode context is passed in a variable named `nth` that tracks how many times a particular shortcode has +been invoked in a Markdown file. Given a shortcode `true_statement.html` template: + +```jinja2 +

{{ value }} is equal to {{ nth }}.

+``` + +It could be used in our Markdown as follows: + +```md +{{/* true_statement(value=1) */}} +{{/* true_statement(value=2) */}} +``` + +This is useful when implementing custom markup for features such as sidenotes or end notes. + ## Built-in shortcodes Zola comes with a few built-in shortcodes. If you want to override a default shortcode template,