Skip to content

Commit f96a936

Browse files
committed
Indent shortcodes as much as their starting line
1 parent e613aa8 commit f96a936

File tree

3 files changed

+80
-3
lines changed

3 files changed

+80
-3
lines changed

Diff for: components/markdown/src/shortcode/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ mod tests {
7575
args: to_value(&HashMap::<u8, u8>::new()).unwrap(),
7676
span: 0..SHORTCODE_PLACEHOLDER.len(),
7777
body: None,
78+
indent: String::new(),
7879
nth: 1,
7980
inner: Vec::new(),
8081
tera_name: "shortcodes/a.md".to_owned(),
@@ -84,6 +85,7 @@ mod tests {
8485
args: to_value(&HashMap::<u8, u8>::new()).unwrap(),
8586
span: SHORTCODE_PLACEHOLDER.len()..(2 * SHORTCODE_PLACEHOLDER.len()),
8687
body: None,
88+
indent: String::new(),
8789
nth: 2,
8890
inner: Vec::new(),
8991
tera_name: "shortcodes/a.md".to_owned(),
@@ -105,6 +107,7 @@ mod tests {
105107
args: to_value(&HashMap::<u8, u8>::new()).unwrap(),
106108
span: 9..(9 + SHORTCODE_PLACEHOLDER.len()),
107109
body: Some("Content of the body".to_owned()),
110+
indent: String::new(),
108111
nth: 1,
109112
inner: Vec::new(),
110113
tera_name: "shortcodes/bodied.md".to_owned(),
@@ -125,12 +128,14 @@ mod tests {
125128
args: to_value(&HashMap::<u8, u8>::new()).unwrap(),
126129
span: 9..(9 + SHORTCODE_PLACEHOLDER.len()),
127130
body: Some(format!("Content of {SHORTCODE_PLACEHOLDER}")),
131+
indent: String::new(),
128132
nth: 1,
129133
inner: vec![Shortcode {
130134
name: "bodied".to_string(),
131135
args: to_value(&HashMap::<u8, u8>::new()).unwrap(),
132136
span: 11..(11 + SHORTCODE_PLACEHOLDER.len()),
133137
body: Some("the body".to_owned()),
138+
indent: String::new(),
134139
nth: 1,
135140
inner: Vec::new(),
136141
tera_name: "shortcodes/bodied.md".to_owned(),

Diff for: components/markdown/src/shortcode/parser.rs

+31-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub struct Shortcode {
1616
// In practice, span.len() is always equal to SHORTCODE_PLACEHOLDER.len()
1717
pub(crate) span: Range<usize>,
1818
pub(crate) body: Option<String>,
19+
pub(crate) indent: String,
1920
pub(crate) nth: usize,
2021
pub(crate) inner: Vec<Shortcode>,
2122
// set later down the line, for quick access without needing the definitions
@@ -88,9 +89,18 @@ impl Shortcode {
8889
new_context.insert("nth", &self.nth);
8990
new_context.extend(context.clone());
9091

91-
let res = utils::templates::render_template(&tpl_name, tera, new_context, &None)
92-
.with_context(|| format!("Failed to render {} shortcode", name))?
93-
.replace("\r\n", "\n");
92+
let rendered = utils::templates::render_template(&tpl_name, tera, new_context, &None)
93+
.with_context(|| format!("Failed to render {} shortcode", name))?;
94+
// Append the rendered text, but indenting each line after the first one as much as the line in which the shortcode was called.
95+
let mut res = String::with_capacity(rendered.len());
96+
let mut lines = rendered.split_terminator('\n');
97+
if let Some(first_line) = lines.next() {
98+
res.push_str(first_line.trim_end_matches('\r'));
99+
for line in lines {
100+
res.push_str(&self.indent);
101+
res.push_str(line.trim_end_matches('\r'));
102+
}
103+
}
94104

95105
Ok(res)
96106
}
@@ -246,17 +256,31 @@ pub fn parse_for_shortcodes(
246256

247257
// We have at least a `page` pair
248258
for p in pairs.next().unwrap().into_inner() {
259+
fn current_indent(text: &str) -> &str {
260+
let current_line = match text.rsplit_once('\n') {
261+
Some((_, line)) => line,
262+
None => text,
263+
};
264+
// Stop at the first character that is not considered indentation by the CommonMark spec.
265+
match current_line.split_once(|ch| ch != ' ' && ch != '\t') {
266+
Some((whitespace, _)) => whitespace,
267+
None => current_line,
268+
}
269+
}
270+
249271
match p.as_rule() {
250272
Rule::text => output.push_str(p.as_span().as_str()),
251273
Rule::inline_shortcode => {
252274
let start = output.len();
275+
let indent = current_indent(&output).into();
253276
let (name, args) = parse_shortcode_call(p);
254277
let nth = invocation_counter.get(&name);
255278
shortcodes.push(Shortcode {
256279
name,
257280
args,
258281
span: start..(start + SHORTCODE_PLACEHOLDER.len()),
259282
body: None,
283+
indent,
260284
nth,
261285
inner: Vec::new(),
262286
tera_name: String::new(),
@@ -265,6 +289,7 @@ pub fn parse_for_shortcodes(
265289
}
266290
Rule::shortcode_with_body => {
267291
let start = output.len();
292+
let indent = current_indent(&output).into();
268293
let mut inner = p.into_inner();
269294
// 3 items in inner: call, body, end
270295
// we don't care about the closing tag
@@ -279,6 +304,7 @@ pub fn parse_for_shortcodes(
279304
args,
280305
span: start..(start + SHORTCODE_PLACEHOLDER.len()),
281306
body: Some(body),
307+
indent,
282308
nth,
283309
inner,
284310
tera_name: String::new(),
@@ -422,6 +448,7 @@ mod tests {
422448
args: Value::Null,
423449
span: 10..20,
424450
body: None,
451+
indent: String::new(),
425452
nth: 0,
426453
inner: Vec::new(),
427454
tera_name: String::new(),
@@ -442,6 +469,7 @@ mod tests {
442469
args: Value::Null,
443470
span: 42..65,
444471
body: None,
472+
indent: String::new(),
445473
nth: 0,
446474
inner: Vec::new(),
447475
tera_name: String::new(),

Diff for: docs/content/documentation/content/shortcodes.md

+44
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,50 @@ If you want to have some content that looks like a shortcode but not have Zola t
167167
you will need to escape it by using `{%/*` and `*/%}` instead of `{%` and `%}`. You won't need to escape
168168
anything else until the closing tag.
169169

170+
### Indentation
171+
172+
If the shortcode expands to multiple lines, the lines after the first one will be indented as much as the first one:
173+
174+
```jinja2
175+
- Second, heed this ancient wisdom:
176+
{%/* quote(author=Vincent) */%}A quote{%/* end */%}
177+
```
178+
179+
becomes...
180+
181+
```md
182+
- Second, heed this ancient wisdom:
183+
> A quote
184+
> -- *Vincent*
185+
```
186+
187+
But sometimes, the first line is not indented, and this breaks rendering:
188+
189+
```jinja2
190+
- {%/* quote(author=Vincent) */%}A quote{%/* end */%}
191+
192+
...are some wise words
193+
```
194+
195+
becomes...
196+
197+
```md
198+
- > A quote
199+
> -- *Vincent*
200+
201+
...are some wise words
202+
```
203+
204+
...which renders improperly (the second line of the quote onwards are not part of the list item).
205+
206+
Since the first line of a list item can be empty, the fix is simply to add a new line:
207+
208+
```jinja2
209+
-
210+
{%/* quote(author=Vincent) */%}A quote{%/* end */%}
211+
...are some wise words
212+
```
213+
170214
## Shortcode context
171215

172216
Every shortcode can access some variables, beyond what you explicitly passed as parameter. These variables are explained in the following subsections:

0 commit comments

Comments
 (0)