diff --git a/Cargo.lock b/Cargo.lock
index 402de6995..9f92329cd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -693,6 +693,7 @@ dependencies = [
"markdown",
"serde",
"tempfile",
+ "templates",
"test-case",
"time",
"utils",
diff --git a/components/content/Cargo.toml b/components/content/Cargo.toml
index df99263b1..8de332824 100644
--- a/components/content/Cargo.toml
+++ b/components/content/Cargo.toml
@@ -18,3 +18,4 @@ markdown = { path = "../markdown" }
[dev-dependencies]
test-case = "3" # TODO: can we solve that usecase in src/page.rs in a simpler way? A custom macro_rules! maybe
tempfile = "3.3.0"
+templates = { path = "../templates" }
diff --git a/components/content/src/page.rs b/components/content/src/page.rs
index 041ab981d..b38981119 100644
--- a/components/content/src/page.rs
+++ b/components/content/src/page.rs
@@ -302,7 +302,7 @@ mod tests {
use std::path::{Path, PathBuf};
use libs::globset::{Glob, GlobSetBuilder};
- use libs::tera::Tera;
+ use templates::ZOLA_TERA;
use tempfile::tempdir;
use crate::Page;
@@ -325,7 +325,7 @@ Hello world"#;
let mut page = res.unwrap();
page.render_markdown(
&HashMap::default(),
- &Tera::default(),
+ &ZOLA_TERA,
&config,
InsertAnchor::None,
&HashMap::new(),
@@ -353,7 +353,7 @@ Hello world"#;
let mut page = res.unwrap();
page.render_markdown(
&HashMap::default(),
- &Tera::default(),
+ &ZOLA_TERA,
&config,
InsertAnchor::None,
&HashMap::new(),
@@ -523,7 +523,7 @@ Hello world
let mut page = res.unwrap();
page.render_markdown(
&HashMap::default(),
- &Tera::default(),
+ &ZOLA_TERA,
&config,
InsertAnchor::None,
&HashMap::new(),
@@ -557,7 +557,7 @@ And here's another. [^3]
let mut page = res.unwrap();
page.render_markdown(
&HashMap::default(),
- &Tera::default(),
+ &ZOLA_TERA,
&config,
InsertAnchor::None,
&HashMap::new(),
diff --git a/components/markdown/src/markdown.rs b/components/markdown/src/markdown.rs
index 8bdc778e2..5ba1cb455 100644
--- a/components/markdown/src/markdown.rs
+++ b/components/markdown/src/markdown.rs
@@ -24,6 +24,7 @@ use crate::codeblock::{CodeBlock, FenceSettings};
use crate::shortcode::{Shortcode, SHORTCODE_PLACEHOLDER};
const CONTINUE_READING: &str = "";
+const SUMMARY_CUTOFF_TEMPLATE: &str = "summary-cutoff.html";
const ANCHOR_LINK_TEMPLATE: &str = "anchor-link.html";
static EMOJI_REPLACER: Lazy = Lazy::new(EmojiReplacer::new);
@@ -684,7 +685,9 @@ pub fn markdown_to_html(
event
});
}
- Event::Html(text) if !has_summary && MORE_DIVIDER_RE.is_match(text.as_ref()) => {
+ Event::Html(text) | Event::InlineHtml(text)
+ if !has_summary && MORE_DIVIDER_RE.is_match(text.as_ref()) =>
+ {
has_summary = true;
events.push(Event::Html(CONTINUE_READING.into()));
}
@@ -787,6 +790,19 @@ pub fn markdown_to_html(
.position(|e| matches!(e, Event::Html(CowStr::Borrowed(CONTINUE_READING))))
.unwrap_or(events.len());
+ // determine closing tags missing from summary
+ let mut tags = Vec::new();
+ for event in &events[..continue_reading] {
+ match event {
+ Event::Start(Tag::HtmlBlock) | Event::End(TagEnd::HtmlBlock) => (),
+ Event::Start(tag) => tags.push(tag.to_end()),
+ Event::End(tag) => {
+ tags.truncate(tags.iter().rposition(|t| *t == *tag).unwrap_or(0));
+ }
+ _ => (),
+ }
+ }
+
let mut events = events.into_iter();
// emit everything up to summary
@@ -794,8 +810,24 @@ pub fn markdown_to_html(
if has_summary {
// remove footnotes
- let summary_html = FOOTNOTES_RE.replace_all(&html, "").into_owned();
- summary = Some(summary_html)
+ let mut summary_html = FOOTNOTES_RE.replace_all(&html, "").into_owned();
+
+ // truncate trailing whitespace
+ summary_html.truncate(summary_html.trim_end().len());
+
+ // add cutoff template
+ let mut c = tera::Context::new();
+ c.insert("summary", &summary_html);
+ c.insert("lang", &context.lang);
+ let summary_cutoff =
+ utils::templates::render_template(SUMMARY_CUTOFF_TEMPLATE, &context.tera, c, &None)
+ .context("Failed to render summary cutoff template")?;
+ summary_html.push_str(&summary_cutoff);
+
+ // close remaining tags
+ cmark::html::push_html(&mut summary_html, tags.into_iter().rev().map(Event::End));
+
+ summary = Some(summary_html);
}
// emit everything after summary
@@ -820,6 +852,7 @@ mod tests {
use super::*;
use config::Config;
use insta::assert_snapshot;
+ use templates::ZOLA_TERA;
#[test]
fn insert_many_works() {
@@ -875,7 +908,8 @@ mod tests {
let mores =
["", "", "", "", ""];
let config = Config::default();
- let context = RenderContext::from_config(&config);
+ let mut context = RenderContext::from_config(&config);
+ context.tera.to_mut().extend(&ZOLA_TERA).unwrap();
for more in mores {
let content = format!("{top}\n\n{more}\n\n{bottom}");
let rendered = markdown_to_html(&content, &context, vec![]).unwrap();
diff --git a/components/markdown/tests/markdown.rs b/components/markdown/tests/markdown.rs
index e8cdcd42c..e286a48df 100644
--- a/components/markdown/tests/markdown.rs
+++ b/components/markdown/tests/markdown.rs
@@ -125,6 +125,25 @@ fn can_customise_anchor_template() {
insta::assert_snapshot!(body);
}
+#[test]
+fn can_customise_summary_template() {
+ let mut tera = Tera::default();
+ tera.extend(&ZOLA_TERA).unwrap();
+ tera.add_raw_template("summary-cutoff.html", " (in {{ lang }})").unwrap();
+ let permalinks_ctx = HashMap::new();
+ let config = Config::default_for_test();
+ let context = RenderContext::new(
+ &tera,
+ &config,
+ &config.default_language,
+ "",
+ &permalinks_ctx,
+ InsertAnchor::Right,
+ );
+ let summary = render_content("Hello World!", &context).unwrap().summary.unwrap();
+ insta::assert_snapshot!(summary);
+}
+
#[test]
fn can_use_smart_punctuation() {
let mut config = Config::default_for_test();
diff --git a/components/markdown/tests/snapshots/markdown__can_customise_summary_template.snap b/components/markdown/tests/snapshots/markdown__can_customise_summary_template.snap
new file mode 100644
index 000000000..491852d4a
--- /dev/null
+++ b/components/markdown/tests/snapshots/markdown__can_customise_summary_template.snap
@@ -0,0 +1,5 @@
+---
+source: components/markdown/tests/markdown.rs
+expression: summary
+---
+Hello (in en)
diff --git a/components/markdown/tests/snapshots/summary__no_truncated_summary.snap b/components/markdown/tests/snapshots/summary__no_truncated_summary.snap
deleted file mode 100644
index 70c632d63..000000000
--- a/components/markdown/tests/snapshots/summary__no_truncated_summary.snap
+++ /dev/null
@@ -1,10 +0,0 @@
----
-source: components/markdown/tests/summary.rs
-expression: rendered.body
----
-Things to do:
-
-- Program something
-- Eat
-- Sleep
-
diff --git a/components/markdown/tests/snapshots/summary__truncated_summary.snap b/components/markdown/tests/snapshots/summary__truncated_summary.snap
new file mode 100644
index 000000000..7f8574a48
--- /dev/null
+++ b/components/markdown/tests/snapshots/summary__truncated_summary.snap
@@ -0,0 +1,9 @@
+---
+source: components/markdown/tests/summary.rs
+expression: body
+---
+Things to do:
+
diff --git a/components/markdown/tests/summary.rs b/components/markdown/tests/summary.rs
index 39dae4099..eb8aa04f9 100644
--- a/components/markdown/tests/summary.rs
+++ b/components/markdown/tests/summary.rs
@@ -48,8 +48,8 @@ And some content after
}
#[test]
-fn no_truncated_summary() {
- let rendered = get_rendered(
+fn truncated_summary() {
+ let body = get_summary(
r#"
Things to do:
* Program something
@@ -57,8 +57,7 @@ Things to do:
* Sleep
"#,
);
- assert!(rendered.summary.is_none());
- insta::assert_snapshot!(rendered.body);
+ insta::assert_snapshot!(body);
}
#[test]
diff --git a/components/templates/src/builtins/summary-cutoff.html b/components/templates/src/builtins/summary-cutoff.html
new file mode 100644
index 000000000..9d36ed218
--- /dev/null
+++ b/components/templates/src/builtins/summary-cutoff.html
@@ -0,0 +1 @@
+{% if summary is matching("\PP$") and summary is not matching(">$") %}…{% endif %}
diff --git a/components/templates/src/lib.rs b/components/templates/src/lib.rs
index 59ab2abaf..04e7d1da3 100644
--- a/components/templates/src/lib.rs
+++ b/components/templates/src/lib.rs
@@ -23,6 +23,7 @@ pub static ZOLA_TERA: Lazy = Lazy::new(|| {
include_str!("builtins/split_sitemap_index.xml"),
),
("__zola_builtins/anchor-link.html", include_str!("builtins/anchor-link.html")),
+ ("__zola_builtins/summary-cutoff.html", include_str!("builtins/summary-cutoff.html")),
("internal/alias.html", include_str!("builtins/internal/alias.html")),
])
.unwrap();
diff --git a/components/utils/src/templates.rs b/components/utils/src/templates.rs
index b51206bac..5342fa0ce 100644
--- a/components/utils/src/templates.rs
+++ b/components/utils/src/templates.rs
@@ -83,6 +83,8 @@ pub fn render_template(
return tera.render(template, &context).map_err(std::convert::Into::into);
}
+ dbg!(&tera.templates);
+
// maybe it's a default one?
match name {
"index.html" | "section.html" => render_default_tpl!(
diff --git a/docs/content/documentation/content/page.md b/docs/content/documentation/content/page.md
index aed2de14b..a172d5eb1 100644
--- a/docs/content/documentation/content/page.md
+++ b/docs/content/documentation/content/page.md
@@ -162,3 +162,7 @@ available separately in the
A span element in this position with a `continue-reading` id is created, so you can link directly to it if needed. For example:
`Continue Reading`.
+
+The `` marker can also exist in the middle of a line, and it will ensure that this does not emit unclosed HTML tags.
+You can use the `summary-cutoff.html` to show an ellipsis or other text after the summary (but before these closing tags) based
+upon the summary before the cutoff. By default, it will show an ellipsis (…) if the summary does not end in any punctuation.