Add shortcode 'invocation' variable to allow a shortcode to track how… (#1236)
* add shortcode 'invocation' variable to allow a shortcode to track how many times it has been invoked in a given Markdown file * use closure (implicit struct) instead of explicit struct for invocation tracking * update variable name to "nth"
This commit is contained in:
parent
a93063ba4a
commit
a210abc5a3
|
@ -3,6 +3,7 @@ use pest::iterators::Pair;
|
||||||
use pest::Parser;
|
use pest::Parser;
|
||||||
use pest_derive::Parser;
|
use pest_derive::Parser;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use std::collections::HashMap;
|
||||||
use tera::{to_value, Context, Map, Value};
|
use tera::{to_value, Context, Map, Value};
|
||||||
|
|
||||||
use crate::context::RenderContext;
|
use crate::context::RenderContext;
|
||||||
|
@ -102,6 +103,7 @@ fn render_shortcode(
|
||||||
name: &str,
|
name: &str,
|
||||||
args: &Map<String, Value>,
|
args: &Map<String, Value>,
|
||||||
context: &RenderContext,
|
context: &RenderContext,
|
||||||
|
invocation_count: u32,
|
||||||
body: Option<&str>,
|
body: Option<&str>,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let mut tera_context = Context::new();
|
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
|
// 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("body", b.trim_end());
|
||||||
}
|
}
|
||||||
|
tera_context.insert("nth", &invocation_count);
|
||||||
tera_context.extend(context.tera_context.clone());
|
tera_context.extend(context.tera_context.clone());
|
||||||
|
|
||||||
let mut template_name = format!("shortcodes/{}.md", name);
|
let mut template_name = format!("shortcodes/{}.md", name);
|
||||||
|
@ -139,6 +142,12 @@ fn render_shortcode(
|
||||||
|
|
||||||
pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<String> {
|
pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<String> {
|
||||||
let mut res = String::with_capacity(content.len());
|
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) {
|
let mut pairs = match ContentParser::parse(Rule::page, content) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
|
@ -184,7 +193,13 @@ pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<Strin
|
||||||
Rule::text => res.push_str(p.as_span().as_str()),
|
Rule::text => res.push_str(p.as_span().as_str()),
|
||||||
Rule::inline_shortcode => {
|
Rule::inline_shortcode => {
|
||||||
let (name, args) = parse_shortcode_call(p);
|
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 => {
|
Rule::shortcode_with_body => {
|
||||||
let mut inner = p.into_inner();
|
let mut inner = p.into_inner();
|
||||||
|
@ -192,7 +207,13 @@ pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<Strin
|
||||||
// we don't care about the closing tag
|
// we don't care about the closing tag
|
||||||
let (name, args) = parse_shortcode_call(inner.next().unwrap());
|
let (name, args) = parse_shortcode_call(inner.next().unwrap());
|
||||||
let body = inner.next().unwrap().as_span().as_str();
|
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 => {
|
Rule::ignored_inline_shortcode => {
|
||||||
res.push_str(
|
res.push_str(
|
||||||
|
|
|
@ -1059,6 +1059,36 @@ fn emoji_aliases_are_ignored_when_disabled_in_config() {
|
||||||
assert_eq!(res.body, "<p>Hello, World! :smile:</p>\n");
|
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: {{ nth }}</p>"#;
|
||||||
|
let shortcode_template_b = r#"<p>b: {{ nth }}</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]
|
#[test]
|
||||||
fn basic_external_links_unchanged() {
|
fn basic_external_links_unchanged() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
|
|
|
@ -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
|
you will need to escape it by using `{%/*` and `*/%}` instead of `{%` and `%}`. You won't need to escape
|
||||||
anything else until the closing tag.
|
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
|
||||||
|
<p id="number{{ nth }}">{{ value }} is equal to {{ nth }}.</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
|
## Built-in shortcodes
|
||||||
|
|
||||||
Zola comes with a few built-in shortcodes. If you want to override a default shortcode template,
|
Zola comes with a few built-in shortcodes. If you want to override a default shortcode template,
|
||||||
|
|
Loading…
Reference in a new issue