parent
047ce32efd
commit
2c681f3439
|
@ -6,12 +6,13 @@
|
||||||
- Support `output_dir` in `config.toml`
|
- Support `output_dir` in `config.toml`
|
||||||
- Allow sections to be drafted
|
- Allow sections to be drafted
|
||||||
- Allow specifying default language in filenames
|
- Allow specifying default language in filenames
|
||||||
- Render emoji in Markdown content if the option is enabled
|
- Render emoji in Markdown content if the `render_emoji` option is enabled
|
||||||
- Enable YouTube privacy mode for the YouTube shortcode
|
- Enable YouTube privacy mode for the YouTube shortcode
|
||||||
- Add language as class to the `<code>` block
|
- Add language as class to the `<code>` block
|
||||||
- Add bibtex to `load_data`
|
- Add bibtex to `load_data`
|
||||||
- Add a `[markdown]` section to `config.toml` to configure rendering
|
- Add a `[markdown]` section to `config.toml` to configure rendering
|
||||||
- Add `highlight_code` and `highlight_theme` to a `[markdown]` section in `config.toml`
|
- Add `highlight_code` and `highlight_theme` to a `[markdown]` section in `config.toml`
|
||||||
|
- Add `external_links_target_blank`, `external_links_no_follow` and `external_links_no_referrer`
|
||||||
|
|
||||||
## 0.12.2 (2020-09-28)
|
## 0.12.2 (2020-09-28)
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,46 @@ pub struct Markdown {
|
||||||
pub highlight_theme: String,
|
pub highlight_theme: String,
|
||||||
/// Whether to render emoji aliases (e.g.: :smile: => 😄) in the markdown files
|
/// Whether to render emoji aliases (e.g.: :smile: => 😄) in the markdown files
|
||||||
pub render_emoji: bool,
|
pub render_emoji: bool,
|
||||||
|
/// Whether external links are to be opened in a new tab
|
||||||
|
/// If this is true, a `rel="noopener"` will always automatically be added for security reasons
|
||||||
|
pub external_links_target_blank: bool,
|
||||||
|
/// Whether to set rel="nofollow" for all external links
|
||||||
|
pub external_links_no_follow: bool,
|
||||||
|
/// Whether to set rel="noreferrer" for all external links
|
||||||
|
pub external_links_no_referrer: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Markdown {
|
||||||
|
pub fn has_external_link_tweaks(&self) -> bool {
|
||||||
|
self.external_links_target_blank
|
||||||
|
|| self.external_links_no_follow
|
||||||
|
|| self.external_links_no_referrer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct_external_link_tag(&self, url: &str, title: &str) -> String {
|
||||||
|
let mut rel_opts = Vec::new();
|
||||||
|
let mut target = "".to_owned();
|
||||||
|
let title = if title == "" { "".to_owned() } else { format!("title=\"{}\" ", title) };
|
||||||
|
|
||||||
|
if self.external_links_target_blank {
|
||||||
|
// Security risk otherwise
|
||||||
|
rel_opts.push("noopener");
|
||||||
|
target = "target=\"_blank\" ".to_owned();
|
||||||
|
}
|
||||||
|
if self.external_links_no_follow {
|
||||||
|
rel_opts.push("nofollow");
|
||||||
|
}
|
||||||
|
if self.external_links_no_referrer {
|
||||||
|
rel_opts.push("noreferrer");
|
||||||
|
}
|
||||||
|
let rel = if rel_opts.is_empty() {
|
||||||
|
"".to_owned()
|
||||||
|
} else {
|
||||||
|
format!("rel=\"{}\" ", rel_opts.join(" "))
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("<a {}{}{}href=\"{}\">", rel, target, title, url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Markdown {
|
impl Default for Markdown {
|
||||||
|
@ -20,6 +60,9 @@ impl Default for Markdown {
|
||||||
highlight_code: false,
|
highlight_code: false,
|
||||||
highlight_theme: DEFAULT_HIGHLIGHT_THEME.to_owned(),
|
highlight_theme: DEFAULT_HIGHLIGHT_THEME.to_owned(),
|
||||||
render_emoji: false,
|
render_emoji: false,
|
||||||
|
external_links_target_blank: false,
|
||||||
|
external_links_no_follow: false,
|
||||||
|
external_links_no_referrer: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ use utils::slugs::slugify_anchors;
|
||||||
use utils::vec::InsertMany;
|
use utils::vec::InsertMany;
|
||||||
|
|
||||||
use self::cmark::{Event, LinkType, Options, Parser, Tag};
|
use self::cmark::{Event, LinkType, Options, Parser, Tag};
|
||||||
use pulldown_cmark::CodeBlockKind;
|
|
||||||
|
|
||||||
mod codeblock;
|
mod codeblock;
|
||||||
mod fence;
|
mod fence;
|
||||||
|
@ -101,11 +100,6 @@ fn fix_link(
|
||||||
return Ok(link.to_string());
|
return Ok(link.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove me in a few versions when people have upgraded
|
|
||||||
if link.starts_with("./") && link.contains(".md") {
|
|
||||||
println!("It looks like the link `{}` is using the previous syntax for internal links: start with @/ instead", link);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A few situations here:
|
// A few situations here:
|
||||||
// - it could be a relative link (starting with `@/`)
|
// - it could be a relative link (starting with `@/`)
|
||||||
// - it could be a link to a co-located asset
|
// - it could be a link to a co-located asset
|
||||||
|
@ -211,7 +205,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
|
||||||
}
|
}
|
||||||
Event::Start(Tag::CodeBlock(ref kind)) => {
|
Event::Start(Tag::CodeBlock(ref kind)) => {
|
||||||
let language = match kind {
|
let language = match kind {
|
||||||
CodeBlockKind::Fenced(fence_info) => {
|
cmark::CodeBlockKind::Fenced(fence_info) => {
|
||||||
let fence_info = fence::FenceSettings::new(fence_info);
|
let fence_info = fence::FenceSettings::new(fence_info);
|
||||||
fence_info.language
|
fence_info.language
|
||||||
}
|
}
|
||||||
|
@ -228,8 +222,8 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
|
||||||
|
|
||||||
let theme = &THEME_SET.themes[context.config.highlight_theme()];
|
let theme = &THEME_SET.themes[context.config.highlight_theme()];
|
||||||
match kind {
|
match kind {
|
||||||
CodeBlockKind::Indented => (),
|
cmark::CodeBlockKind::Indented => (),
|
||||||
CodeBlockKind::Fenced(fence_info) => {
|
cmark::CodeBlockKind::Fenced(fence_info) => {
|
||||||
// This selects the background color the same way that
|
// This selects the background color the same way that
|
||||||
// start_coloured_html_snippet does
|
// start_coloured_html_snippet does
|
||||||
let color = theme
|
let color = theme
|
||||||
|
@ -289,8 +283,22 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
|
||||||
return Event::Html("".into());
|
return Event::Html("".into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if is_external_link(&link)
|
||||||
Event::Start(Tag::Link(link_type, fixed_link.into(), title))
|
&& context.config.markdown.has_external_link_tweaks()
|
||||||
|
{
|
||||||
|
let mut escaped = String::new();
|
||||||
|
// write_str can fail but here there are no reasons it should (afaik?)
|
||||||
|
cmark::escape::escape_href(&mut escaped, &link).expect("Could not write to buffer");
|
||||||
|
Event::Html(
|
||||||
|
context
|
||||||
|
.config
|
||||||
|
.markdown
|
||||||
|
.construct_external_link_tag(&escaped, &title)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Event::Start(Tag::Link(link_type, fixed_link.into(), title))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Event::Html(ref markup) => {
|
Event::Html(ref markup) => {
|
||||||
if markup.contains("<!-- more -->") {
|
if markup.contains("<!-- more -->") {
|
||||||
|
|
|
@ -1058,3 +1058,61 @@ fn emoji_aliases_are_ignored_when_disabled_in_config() {
|
||||||
let res = render_content("Hello, World! :smile:", &context).unwrap();
|
let res = render_content("Hello, World! :smile:", &context).unwrap();
|
||||||
assert_eq!(res.body, "<p>Hello, World! :smile:</p>\n");
|
assert_eq!(res.body, "<p>Hello, World! :smile:</p>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_external_links_unchanged() {
|
||||||
|
let permalinks_ctx = HashMap::new();
|
||||||
|
let config = Config::default();
|
||||||
|
let context = RenderContext::new(&ZOLA_TERA, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
|
let res = render_content("<https://google.com>", &context).unwrap();
|
||||||
|
assert_eq!(res.body, "<p><a href=\"https://google.com\">https://google.com</a></p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_set_target_blank_for_external_link() {
|
||||||
|
let permalinks_ctx = HashMap::new();
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.markdown.external_links_target_blank = true;
|
||||||
|
let context = RenderContext::new(&ZOLA_TERA, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
|
let res = render_content("<https://google.com>", &context).unwrap();
|
||||||
|
assert_eq!(res.body, "<p><a rel=\"noopener\" target=\"_blank\" href=\"https://google.com\">https://google.com</a></p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_set_nofollow_for_external_link() {
|
||||||
|
let permalinks_ctx = HashMap::new();
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.markdown.external_links_no_follow = true;
|
||||||
|
let context = RenderContext::new(&ZOLA_TERA, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
|
// Testing href escaping while we're there
|
||||||
|
let res = render_content("<https://google.com/éllo>", &context).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res.body,
|
||||||
|
"<p><a rel=\"nofollow\" href=\"https://google.com/%C3%A9llo\">https://google.com/éllo</a></p>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_set_noreferrer_for_external_link() {
|
||||||
|
let permalinks_ctx = HashMap::new();
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.markdown.external_links_no_referrer = true;
|
||||||
|
let context = RenderContext::new(&ZOLA_TERA, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
|
let res = render_content("<https://google.com>", &context).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res.body,
|
||||||
|
"<p><a rel=\"noreferrer\" href=\"https://google.com\">https://google.com</a></p>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_set_all_options_for_external_link() {
|
||||||
|
let permalinks_ctx = HashMap::new();
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.markdown.external_links_target_blank = true;
|
||||||
|
config.markdown.external_links_no_follow = true;
|
||||||
|
config.markdown.external_links_no_referrer = true;
|
||||||
|
let context = RenderContext::new(&ZOLA_TERA, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
|
let res = render_content("<https://google.com>", &context).unwrap();
|
||||||
|
assert_eq!(res.body, "<p><a rel=\"noopener nofollow noreferrer\" target=\"_blank\" href=\"https://google.com\">https://google.com</a></p>\n");
|
||||||
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@ extra_syntaxes = []
|
||||||
# You can override the default output directory `public` by setting an another value.
|
# You can override the default output directory `public` by setting an another value.
|
||||||
# output_dir = "docs"
|
# output_dir = "docs"
|
||||||
|
|
||||||
|
# Configuration of the Markdown rendering
|
||||||
[markdown]
|
[markdown]
|
||||||
# When set to "true", all code blocks are highlighted.
|
# When set to "true", all code blocks are highlighted.
|
||||||
highlight_code = false
|
highlight_code = false
|
||||||
|
@ -107,6 +108,16 @@ highlight_theme = "base16-ocean-dark"
|
||||||
# Unicode emoji equivalent in the rendered Markdown files. (e.g.: :smile: => 😄)
|
# Unicode emoji equivalent in the rendered Markdown files. (e.g.: :smile: => 😄)
|
||||||
render_emoji = false
|
render_emoji = false
|
||||||
|
|
||||||
|
# Whether external links are to be opened in a new tab
|
||||||
|
# If this is true, a `rel="noopener"` will always automatically be added for security reasons
|
||||||
|
external_links_target_blank = false
|
||||||
|
|
||||||
|
# Whether to set rel="nofollow" for all external links
|
||||||
|
external_links_no_follow = false
|
||||||
|
|
||||||
|
# Whether to set rel="noreferrer" for all external links
|
||||||
|
external_links_no_referrer = false
|
||||||
|
|
||||||
# Configuration of the link checker.
|
# Configuration of the link checker.
|
||||||
[link_checker]
|
[link_checker]
|
||||||
# Skip link checking for external URLs that start with these prefixes
|
# Skip link checking for external URLs that start with these prefixes
|
||||||
|
|
Loading…
Reference in a new issue