Make shortcodes work in markdown
filter (#1358)
* Move `load_tera` to `templates` I don't know if this is a good place for it, conceptually. I'm moving it there because I need to use it from `templates`, and `templates` can't depend on `site`, because there's already a dependency in the opposite direction. * Load templates in `markdown` filter This enables the `markdown` filter to handle shortcodes, as long as those shortcodes don't access any context variables. Addresses #1350 * Update documentation of `markdown` filter * Only load templates for `markdown` filter once * Clarify `markdown` filter documentation This is a lightly edited version of what @southerntofu suggested.
This commit is contained in:
parent
3ba2d33564
commit
6d6df45f23
|
@ -20,7 +20,7 @@ use front_matter::InsertAnchor;
|
|||
use library::{find_taxonomies, Library, Page, Paginator, Section, Taxonomy};
|
||||
use relative_path::RelativePathBuf;
|
||||
use std::time::Instant;
|
||||
use templates::render_redirect_template;
|
||||
use templates::{load_tera, render_redirect_template};
|
||||
use utils::fs::{
|
||||
copy_directory, copy_file_if_needed, create_directory, create_file, ensure_directory_exists,
|
||||
};
|
||||
|
@ -80,7 +80,7 @@ impl Site {
|
|||
config.merge_with_theme(&path.join("themes").join(&theme).join("theme.toml"), &theme)?;
|
||||
}
|
||||
|
||||
let tera = tpls::load_tera(path, &config)?;
|
||||
let tera = load_tera(path, &config)?;
|
||||
|
||||
let content_path = path.join("content");
|
||||
let static_path = path.join("static");
|
||||
|
@ -295,7 +295,7 @@ impl Site {
|
|||
// taxonomy Tera fns are loaded in `register_early_global_fns`
|
||||
// so we do need to populate it first.
|
||||
self.populate_taxonomies()?;
|
||||
tpls::register_early_global_fns(self);
|
||||
tpls::register_early_global_fns(self)?;
|
||||
self.populate_sections();
|
||||
self.render_markdown()?;
|
||||
tpls::register_tera_global_fns(self);
|
||||
|
|
|
@ -1,58 +1,16 @@
|
|||
use std::path::Path;
|
||||
|
||||
use tera::Tera;
|
||||
|
||||
use crate::Site;
|
||||
use config::Config;
|
||||
use errors::{bail, Error, Result};
|
||||
use templates::{filters, global_fns, ZOLA_TERA};
|
||||
use utils::templates::rewrite_theme_paths;
|
||||
|
||||
pub fn load_tera(path: &Path, config: &Config) -> Result<Tera> {
|
||||
let tpl_glob =
|
||||
format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*.{*ml,md}");
|
||||
|
||||
// Only parsing as we might be extending templates from themes and that would error
|
||||
// as we haven't loaded them yet
|
||||
let mut tera =
|
||||
Tera::parse(&tpl_glob).map_err(|e| Error::chain("Error parsing templates", e))?;
|
||||
|
||||
if let Some(ref theme) = config.theme {
|
||||
// Test that the templates folder exist for that theme
|
||||
let theme_path = path.join("themes").join(&theme);
|
||||
if !theme_path.join("templates").exists() {
|
||||
bail!("Theme `{}` is missing a templates folder", theme);
|
||||
}
|
||||
|
||||
let theme_tpl_glob = format!(
|
||||
"{}/{}",
|
||||
path.to_string_lossy().replace("\\", "/"),
|
||||
format!("themes/{}/templates/**/*.{{*ml,md}}", theme)
|
||||
);
|
||||
let mut tera_theme = Tera::parse(&theme_tpl_glob)
|
||||
.map_err(|e| Error::chain("Error parsing templates from themes", e))?;
|
||||
rewrite_theme_paths(&mut tera_theme, &theme);
|
||||
|
||||
if theme_path.join("templates").join("robots.txt").exists() {
|
||||
tera_theme.add_template_file(theme_path.join("templates").join("robots.txt"), None)?;
|
||||
}
|
||||
tera.extend(&tera_theme)?;
|
||||
}
|
||||
tera.extend(&ZOLA_TERA)?;
|
||||
tera.build_inheritance_chains()?;
|
||||
|
||||
if path.join("templates").join("robots.txt").exists() {
|
||||
tera.add_template_file(path.join("templates").join("robots.txt"), Some("robots.txt"))?;
|
||||
}
|
||||
|
||||
Ok(tera)
|
||||
}
|
||||
use templates::{filters, global_fns};
|
||||
use tera::Result as TeraResult;
|
||||
|
||||
/// Adds global fns that are to be available to shortcodes while rendering markdown
|
||||
pub fn register_early_global_fns(site: &mut Site) {
|
||||
pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> {
|
||||
site.tera.register_filter(
|
||||
"markdown",
|
||||
filters::MarkdownFilter::new(site.config.clone(), site.permalinks.clone()),
|
||||
filters::MarkdownFilter::new(
|
||||
site.base_path.clone(),
|
||||
site.config.clone(),
|
||||
site.permalinks.clone(),
|
||||
)?,
|
||||
);
|
||||
|
||||
site.tera.register_function(
|
||||
|
@ -87,6 +45,8 @@ pub fn register_early_global_fns(site: &mut Site) {
|
|||
site.content_path.clone(),
|
||||
]),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Functions filled once we have parsed all the pages/sections only, so not available in shortcodes
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasher;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use base64::{decode, encode};
|
||||
use config::Config;
|
||||
use rendering::{render_content, RenderContext};
|
||||
use tera::{to_value, try_get_value, Filter as TeraFilter, Result as TeraResult, Value};
|
||||
use tera::{Filter as TeraFilter, Result as TeraResult, Tera, Value, to_value, try_get_value};
|
||||
|
||||
use crate::load_tera;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MarkdownFilter {
|
||||
config: Config,
|
||||
permalinks: HashMap<String, String>,
|
||||
tera: Tera,
|
||||
}
|
||||
|
||||
impl MarkdownFilter {
|
||||
pub fn new(config: Config, permalinks: HashMap<String, String>) -> Self {
|
||||
Self { config, permalinks }
|
||||
pub fn new(path: PathBuf, config: Config, permalinks: HashMap<String, String>) -> TeraResult<Self> {
|
||||
let tera = load_tera(&path, &config)
|
||||
.map_err(|err| tera::Error::msg(err))?;
|
||||
Ok(Self { config, permalinks, tera })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +29,8 @@ impl TeraFilter for MarkdownFilter {
|
|||
fn filter(&self, value: &Value, args: &HashMap<String, Value>) -> TeraResult<Value> {
|
||||
let mut context = RenderContext::from_config(&self.config);
|
||||
context.permalinks = Cow::Borrowed(&self.permalinks);
|
||||
context.tera = Cow::Borrowed(&self.tera);
|
||||
|
||||
let s = try_get_value!("markdown", "value", String, value);
|
||||
let inline = match args.get("inline") {
|
||||
Some(val) => try_get_value!("markdown", "inline", bool, val),
|
||||
|
@ -63,7 +71,7 @@ pub fn base64_decode<S: BuildHasher>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use tera::{to_value, Filter};
|
||||
|
||||
|
@ -72,7 +80,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn markdown_filter() {
|
||||
let result = MarkdownFilter::new(Config::default(), HashMap::new())
|
||||
let result = MarkdownFilter::new(PathBuf::new(), Config::default(), HashMap::new())
|
||||
.unwrap()
|
||||
.filter(&to_value(&"# Hey").unwrap(), &HashMap::new());
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), to_value(&"<h1 id=\"hey\">Hey</h1>\n").unwrap());
|
||||
|
@ -82,7 +91,8 @@ mod tests {
|
|||
fn markdown_filter_inline() {
|
||||
let mut args = HashMap::new();
|
||||
args.insert("inline".to_string(), to_value(true).unwrap());
|
||||
let result = MarkdownFilter::new(Config::default(), HashMap::new()).filter(
|
||||
let result =
|
||||
MarkdownFilter::new(PathBuf::new(), Config::default(), HashMap::new()).unwrap().filter(
|
||||
&to_value(&"Using `map`, `filter`, and `fold` instead of `for`").unwrap(),
|
||||
&args,
|
||||
);
|
||||
|
@ -95,7 +105,8 @@ mod tests {
|
|||
fn markdown_filter_inline_tables() {
|
||||
let mut args = HashMap::new();
|
||||
args.insert("inline".to_string(), to_value(true).unwrap());
|
||||
let result = MarkdownFilter::new(Config::default(), HashMap::new()).filter(
|
||||
let result =
|
||||
MarkdownFilter::new(PathBuf::new(), Config::default(), HashMap::new()).unwrap().filter(
|
||||
&to_value(
|
||||
&r#"
|
||||
|id|author_id| timestamp_created|title |content |
|
||||
|
@ -120,13 +131,15 @@ mod tests {
|
|||
config.markdown.external_links_target_blank = true;
|
||||
|
||||
let md = "Hello <https://google.com> :smile: ...";
|
||||
let result = MarkdownFilter::new(config.clone(), HashMap::new())
|
||||
let result = MarkdownFilter::new(PathBuf::new(), config.clone(), HashMap::new())
|
||||
.unwrap()
|
||||
.filter(&to_value(&md).unwrap(), &HashMap::new());
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), to_value(&"<p>Hello <a rel=\"noopener\" target=\"_blank\" href=\"https://google.com\">https://google.com</a> 😄 …</p>\n").unwrap());
|
||||
|
||||
let md = "```py\ni=0\n```";
|
||||
let result = MarkdownFilter::new(config, HashMap::new())
|
||||
let result = MarkdownFilter::new(PathBuf::new(), config, HashMap::new())
|
||||
.unwrap()
|
||||
.filter(&to_value(&md).unwrap(), &HashMap::new());
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().as_str().unwrap().contains("<pre style"));
|
||||
|
@ -137,7 +150,8 @@ mod tests {
|
|||
let mut permalinks = HashMap::new();
|
||||
permalinks.insert("blog/_index.md".to_string(), "/foo/blog".to_string());
|
||||
let md = "Hello. Check out [my blog](@/blog/_index.md)!";
|
||||
let result = MarkdownFilter::new(Config::default(), permalinks)
|
||||
let result = MarkdownFilter::new(PathBuf::new(), Config::default(), permalinks)
|
||||
.unwrap()
|
||||
.filter(&to_value(&md).unwrap(), &HashMap::new());
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
pub mod filters;
|
||||
pub mod global_fns;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use config::Config;
|
||||
use lazy_static::lazy_static;
|
||||
use tera::{Context, Tera};
|
||||
|
||||
use errors::{Error, Result};
|
||||
use errors::{Error, Result, bail};
|
||||
use utils::templates::rewrite_theme_paths;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ZOLA_TERA: Tera = {
|
||||
|
@ -51,3 +55,43 @@ pub fn render_redirect_template(url: &str, tera: &Tera) -> Result<String> {
|
|||
tera.render("internal/alias.html", &context)
|
||||
.map_err(|e| Error::chain(format!("Failed to render alias for '{}'", url), e))
|
||||
}
|
||||
|
||||
pub fn load_tera(path: &Path, config: &Config) -> Result<Tera> {
|
||||
let tpl_glob =
|
||||
format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*.{*ml,md}");
|
||||
|
||||
// Only parsing as we might be extending templates from themes and that would error
|
||||
// as we haven't loaded them yet
|
||||
let mut tera =
|
||||
Tera::parse(&tpl_glob).map_err(|e| Error::chain("Error parsing templates", e))?;
|
||||
|
||||
if let Some(ref theme) = config.theme {
|
||||
// Test that the templates folder exist for that theme
|
||||
let theme_path = path.join("themes").join(&theme);
|
||||
if !theme_path.join("templates").exists() {
|
||||
bail!("Theme `{}` is missing a templates folder", theme);
|
||||
}
|
||||
|
||||
let theme_tpl_glob = format!(
|
||||
"{}/{}",
|
||||
path.to_string_lossy().replace("\\", "/"),
|
||||
format!("themes/{}/templates/**/*.{{*ml,md}}", theme)
|
||||
);
|
||||
let mut tera_theme = Tera::parse(&theme_tpl_glob)
|
||||
.map_err(|e| Error::chain("Error parsing templates from themes", e))?;
|
||||
rewrite_theme_paths(&mut tera_theme, &theme);
|
||||
|
||||
if theme_path.join("templates").join("robots.txt").exists() {
|
||||
tera_theme.add_template_file(theme_path.join("templates").join("robots.txt"), None)?;
|
||||
}
|
||||
tera.extend(&tera_theme)?;
|
||||
}
|
||||
tera.extend(&ZOLA_TERA)?;
|
||||
tera.build_inheritance_chains()?;
|
||||
|
||||
if path.join("templates").join("robots.txt").exists() {
|
||||
tera.add_template_file(path.join("templates").join("robots.txt"), Some("robots.txt"))?;
|
||||
}
|
||||
|
||||
Ok(tera)
|
||||
}
|
||||
|
|
|
@ -64,7 +64,8 @@ Zola adds a few filters in addition to [those](https://tera.netlify.com/docs/#fi
|
|||
in Tera.
|
||||
|
||||
### markdown
|
||||
Converts the given variable to HTML using Markdown. Shortcodes won't work within this filter.
|
||||
Converts the given variable to HTML using Markdown. Please note that shortcodes evaluated by this filter cannot access the current rendering context. `config` will be available, but accessing `section` or `page` (among others) from a shortcode called within the `markdown` filter will prevent your site from building. See [this discussion](https://github.com/getzola/zola/pull/1358).
|
||||
|
||||
By default, the filter will wrap all text in a paragraph. To disable this behaviour, you can
|
||||
pass `true` to the inline argument:
|
||||
|
||||
|
|
Loading…
Reference in a new issue