Skip to content

Commit

Permalink
Add some external link markdown tweaking options
Browse files Browse the repository at this point in the history
Closes #681, #695
  • Loading branch information
Keats committed Dec 14, 2020
1 parent 047ce32 commit 2c681f3
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 12 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
- Support `output_dir` in `config.toml`
- Allow sections to be drafted
- 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
- Add language as class to the `<code>` block
- Add bibtex to `load_data`
- Add a `[markdown]` section to `config.toml` to configure rendering
- 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)

Expand Down
43 changes: 43 additions & 0 deletions components/config/src/config/markup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,46 @@ pub struct Markdown {
pub highlight_theme: String,
/// Whether to render emoji aliases (e.g.: :smile: => 😄) in the markdown files
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 {
Expand All @@ -20,6 +60,9 @@ impl Default for Markdown {
highlight_code: false,
highlight_theme: DEFAULT_HIGHLIGHT_THEME.to_owned(),
render_emoji: false,
external_links_target_blank: false,
external_links_no_follow: false,
external_links_no_referrer: false,
}
}
}
30 changes: 19 additions & 11 deletions components/rendering/src/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use utils::slugs::slugify_anchors;
use utils::vec::InsertMany;

use self::cmark::{Event, LinkType, Options, Parser, Tag};
use pulldown_cmark::CodeBlockKind;

mod codeblock;
mod fence;
Expand Down Expand Up @@ -101,11 +100,6 @@ fn fix_link(
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:
// - it could be a relative link (starting with `@/`)
// - it could be a link to a co-located asset
Expand Down Expand Up @@ -211,7 +205,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
}
Event::Start(Tag::CodeBlock(ref kind)) => {
let language = match kind {
CodeBlockKind::Fenced(fence_info) => {
cmark::CodeBlockKind::Fenced(fence_info) => {
let fence_info = fence::FenceSettings::new(fence_info);
fence_info.language
}
Expand All @@ -228,8 +222,8 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render

let theme = &THEME_SET.themes[context.config.highlight_theme()];
match kind {
CodeBlockKind::Indented => (),
CodeBlockKind::Fenced(fence_info) => {
cmark::CodeBlockKind::Indented => (),
cmark::CodeBlockKind::Fenced(fence_info) => {
// This selects the background color the same way that
// start_coloured_html_snippet does
let color = theme
Expand Down Expand Up @@ -289,8 +283,22 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
return Event::Html("".into());
}
};

Event::Start(Tag::Link(link_type, fixed_link.into(), title))
if is_external_link(&link)
&& 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) => {
if markup.contains("<!-- more -->") {
Expand Down
58 changes: 58 additions & 0 deletions components/rendering/tests/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1058,3 +1058,61 @@ fn emoji_aliases_are_ignored_when_disabled_in_config() {
let res = render_content("Hello, World! :smile:", &context).unwrap();
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");
}
11 changes: 11 additions & 0 deletions docs/content/documentation/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ extra_syntaxes = []
# You can override the default output directory `public` by setting an another value.
# output_dir = "docs"

# Configuration of the Markdown rendering
[markdown]
# When set to "true", all code blocks are highlighted.
highlight_code = false
Expand All @@ -107,6 +108,16 @@ highlight_theme = "base16-ocean-dark"
# Unicode emoji equivalent in the rendered Markdown files. (e.g.: :smile: => 😄)
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.
[link_checker]
# Skip link checking for external URLs that start with these prefixes
Expand Down

0 comments on commit 2c681f3

Please sign in to comment.