Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shortcode 'invocation' variable to allow a shortcode to track how… #1236

Merged
merged 8 commits into from
Dec 14, 2020
25 changes: 23 additions & 2 deletions components/rendering/src/shortcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -102,6 +103,7 @@ fn render_shortcode(
name: &str,
args: &Map<String, Value>,
context: &RenderContext,
invocation_count: u32,
body: Option<&str>,
) -> Result<String> {
let mut tera_context = Context::new();
Expand All @@ -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("invocation", &invocation_count);
tera_context.extend(context.tera_context.clone());

let mut template_name = format!("shortcodes/{}.md", name);
Expand Down Expand Up @@ -139,6 +142,12 @@ fn render_shortcode(

pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<String> {
let mut res = String::with_capacity(content.len());
let mut invocation_map: HashMap<String, u32> = 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,
Expand Down Expand Up @@ -184,15 +193,27 @@ pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<Strin
Rule::text => 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();
// 3 items in inner: call, body, end
// we don't care about the closing tag
let (name, args) = parse_shortcode_call(inner.next().unwrap());
let body = inner.next().unwrap().as_span().as_str();
res.push_str(&render_shortcode(&name, &args, context, Some(body))?);
res.push_str(&render_shortcode(
&name,
&args,
context,
get_invocation_count(&name),
Some(body),
)?);
}
Rule::ignored_inline_shortcode => {
res.push_str(
Expand Down
30 changes: 30 additions & 0 deletions components/rendering/tests/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,36 @@ fn emoji_aliases_are_ignored_when_disabled_in_config() {
assert_eq!(res.body, "<p>Hello, World! :smile:</p>\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#"<p>a: {{ invocation }}</p>"#;
let shortcode_template_b = r#"<p>b: {{ invocation }}</p>"#;

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

let expected = r#"<p>a: 1</p>
<p>b: 1</p>
<p>a: 2</p>
<p>b: 2</p>
"#;

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();
Expand Down
18 changes: 18 additions & 0 deletions docs/content/documentation/content/shortcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `invocation` that tracks how many times a particular shortcode has
NathanaelLane marked this conversation as resolved.
Show resolved Hide resolved
been invoked in a Markdown file. Given a shortcode `true_statement.html` template:

```jinja2
<p id="number{{ invocation }}">{{ value }} is equal to {{ invocation }}.</p>
```

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,
Expand Down