Handle naked anchor link for internal links

Closes #1676
This commit is contained in:
Vincent Prouillet 2021-12-03 23:30:29 +01:00
parent 769264e3e5
commit 51784aa60f
8 changed files with 99 additions and 84 deletions

View File

@ -10,6 +10,7 @@
- Shorcodes and `anchor-link.html` can now access the `lang` context
- Add prompt before replacing the output directory with `zola build` if the `output-dir` flag is given
- Shortcode handling has been completely rewritten, solving many issues
- Also add internal links starting with `#` without any internal Zola link
## 0.14.1 (2021-08-24)

View File

@ -236,7 +236,7 @@ impl Page {
anchor_insert,
);
context.set_shortcode_definitions(shortcode_definitions);
context.set_current_page_path(&self.file.relative);
context.tera_context.insert("page", &SerializingPage::from_page_basic(self, None));
let res = render_content(&self.raw_content, &context).map_err(|e| {

View File

@ -10,7 +10,7 @@ use front_matter::{split_section_content, SectionFrontMatter};
use rendering::{render_content, Heading, RenderContext};
use utils::fs::read_file;
use utils::site::get_reading_analytics;
use utils::templates::render_template;
use utils::templates::{render_template, ShortcodeDefinition};
use crate::content::file_info::FileInfo;
use crate::content::ser::SerializingSection;
@ -147,6 +147,7 @@ impl Section {
permalinks: &HashMap<String, String>,
tera: &Tera,
config: &Config,
shortcode_definitions: &HashMap<String, ShortcodeDefinition>,
) -> Result<()> {
let mut context = RenderContext::new(
tera,
@ -156,7 +157,8 @@ impl Section {
permalinks,
self.meta.insert_anchor_links,
);
context.set_shortcode_definitions(shortcode_definitions);
context.set_current_page_path(&self.file.relative);
context.tera_context.insert("section", &SerializingSection::from_section_basic(self, None));
let res = render_content(&self.raw_content, &context).map_err(|e| {

View File

@ -12,6 +12,7 @@ pub struct RenderContext<'a> {
pub tera: Cow<'a, Tera>,
pub config: &'a Config,
pub tera_context: Context,
pub current_page_path: Option<&'a str>,
pub current_page_permalink: &'a str,
pub permalinks: Cow<'a, HashMap<String, String>>,
pub insert_anchor: InsertAnchor,
@ -35,6 +36,7 @@ impl<'a> RenderContext<'a> {
Self {
tera: Cow::Borrowed(tera),
tera_context,
current_page_path: None,
current_page_permalink,
permalinks: Cow::Borrowed(permalinks),
insert_anchor,
@ -50,6 +52,11 @@ impl<'a> RenderContext<'a> {
self.shortcode_definitions = Cow::Borrowed(def);
}
/// Same as above
pub fn set_current_page_path(&mut self, path: &'a str) {
self.current_page_path = Some(path);
}
// In use in the markdown filter
// NOTE: This RenderContext is not i18n-aware, see MarkdownFilter::filter for details
// If this function is ever used outside of MarkdownFilter, take this into consideration
@ -57,6 +64,7 @@ impl<'a> RenderContext<'a> {
Self {
tera: Cow::Owned(Tera::default()),
tera_context: Context::new(),
current_page_path: None,
current_page_permalink: "",
permalinks: Cow::Owned(HashMap::new()),
insert_anchor: InsertAnchor::None,

View File

@ -10,6 +10,7 @@ use errors::Result;
pub use context::RenderContext;
use markdown::markdown_to_html;
pub use markdown::Rendered;
pub use table_of_contents::Heading;
pub fn render_content(content: &str, context: &RenderContext) -> Result<markdown::Rendered> {

View File

@ -92,9 +92,20 @@ fn fix_link(
} else {
if is_external_link(link) {
external_links.push(link.to_owned());
link.to_owned()
} else if link.starts_with("#") {
// local anchor without the internal zola path
if let Some(current_path) = context.current_page_path {
internal_links.push((current_path.to_owned(), Some(link[1..].to_owned())));
format!("{}{}", context.current_page_permalink, &link)
} else {
link.to_string()
}
} else {
link.to_string()
}
link.to_string()
};
Ok(result)
}
@ -121,8 +132,7 @@ fn get_heading_refs(events: &[Event]) -> Vec<HeadingRef> {
heading_refs.push(HeadingRef::new(i, *level));
}
Event::End(Tag::Heading(_)) => {
let msg = "Heading end before start?";
heading_refs.last_mut().expect(msg).end_idx = i;
heading_refs.last_mut().expect("Heading end before start?").end_idx = i;
}
_ => (),
}

View File

@ -1,82 +1,62 @@
use std::collections::HashMap;
use errors::Result;
use rendering::Rendered;
mod common;
macro_rules! test_links {
(
$in_str:literal,
[$($id:literal => $perma_link:literal),*],
[$($abs_path:literal),*],
[$($rel_path:literal:$opt_anchor:expr),*],
[$($shortcodes:ident),*]
) => {
let config = config::Config::default_for_test();
#[allow(unused_mut)]
let mut tera = tera::Tera::default();
// Add all shortcodes
$(
tera.add_raw_template(
&format!("shortcodes/{}", $shortcodes.filename()),
$shortcodes.output
).expect("Failed to add raw template");
)*
#[allow(unused_mut)]
let mut permalinks = std::collections::HashMap::new();
$(
permalinks.insert($id.to_string(), $perma_link.to_string());
)*
let context = rendering::RenderContext::new(
&tera,
&config,
&config.default_language,
"",
&permalinks,
front_matter::InsertAnchor::None,
);
let rendered = rendering::render_content($in_str, &context);
assert!(rendered.is_ok(), "Rendering failed");
let rendered = rendered.unwrap();
let asserted_int_links = vec![
$(
($rel_path.to_string(), $opt_anchor.map(|x| x.to_string()))
),*
];
let asserted_ext_links: Vec<&str> = vec![$($abs_path),*];
assert_eq!(rendered.internal_links, asserted_int_links, "Internal links unequal");
assert_eq!(rendered.external_links, asserted_ext_links, "External links unequal");
}
}
#[test]
fn basic_internal() {
test_links!("Hello World!", [], [], [], []);
}
#[test]
fn absolute_links() {
test_links!("[abc](https://google.com/)", [], ["https://google.com/"], [], []);
}
#[test]
fn relative_links() {
test_links!(
"[abc](@/def/123.md)",
["def/123.md" => "https://xyz.com/def/123"],
[],
["def/123.md":<Option<&str>>::None],
[]
fn render_content(content: &str, permalinks: HashMap<String, String>) -> Result<Rendered> {
let config = config::Config::default_for_test();
let tera = tera::Tera::default();
let mut context = rendering::RenderContext::new(
&tera,
&config,
&config.default_language,
"http://mypage.com",
&permalinks,
front_matter::InsertAnchor::None,
);
context.set_current_page_path("mine.md");
rendering::render_content(content, &context)
}
#[test]
#[should_panic]
fn relative_links_no_perma() {
test_links!("[abc](@/def/123.md)", [], [], ["def/123.md": <Option<&str>>::None], []);
fn can_detect_links() {
// no links
let rendered = render_content("Hello World!", HashMap::new()).unwrap();
assert_eq!(rendered.internal_links.len(), 0);
assert_eq!(rendered.external_links.len(), 0);
// external
let rendered = render_content("[abc](https://google.com/)", HashMap::new()).unwrap();
assert_eq!(rendered.internal_links.len(), 0);
assert_eq!(rendered.external_links.len(), 1);
assert_eq!(rendered.external_links[0], "https://google.com/");
// internal
let mut permalinks = HashMap::new();
permalinks.insert("def/123.md".to_owned(), "https://xyz.com/def/123".to_owned());
let rendered = render_content("[abc](@/def/123.md)", permalinks).unwrap();
assert_eq!(rendered.internal_links.len(), 1);
assert_eq!(rendered.internal_links[0], ("def/123.md".to_owned(), None));
assert_eq!(rendered.external_links.len(), 0);
// internal with anchors
let mut permalinks = HashMap::new();
permalinks.insert("def/123.md".to_owned(), "https://xyz.com/def/123".to_owned());
let rendered = render_content("[abc](@/def/123.md#hello)", permalinks).unwrap();
assert_eq!(rendered.internal_links.len(), 1);
assert_eq!(rendered.internal_links[0], ("def/123.md".to_owned(), Some("hello".to_owned())));
assert_eq!(rendered.external_links.len(), 0);
// internal link referring to self
let rendered = render_content("[abc](#hello)", HashMap::new()).unwrap();
assert_eq!(rendered.internal_links.len(), 1);
assert_eq!(rendered.internal_links[0], ("mine.md".to_owned(), Some("hello".to_owned())));
assert_eq!(rendered.external_links.len(), 0);
// Not pointing to anything so that's an error
let res = render_content("[abc](@/def/123.md)", HashMap::new());
assert!(res.is_err());
}

View File

@ -380,7 +380,9 @@ impl Site {
.values_mut()
.collect::<Vec<_>>()
.par_iter_mut()
.map(|section| section.render_markdown(permalinks, tera, config))
.map(|section| {
section.render_markdown(permalinks, tera, config, &self.shortcode_definitions)
})
.collect::<Result<()>>()?;
Ok(())
@ -426,7 +428,12 @@ impl Site {
pub fn add_section(&mut self, mut section: Section, render_md: bool) -> Result<()> {
self.permalinks.insert(section.file.relative.clone(), section.permalink.clone());
if render_md {
section.render_markdown(&self.permalinks, &self.tera, &self.config)?;
section.render_markdown(
&self.permalinks,
&self.tera,
&self.config,
&self.shortcode_definitions,
)?;
}
let mut library = self.library.write().expect("Get lock for add_section");
library.remove_section(&section.file.path);
@ -847,7 +854,13 @@ impl Site {
if taxonomy.kind.is_paginated() {
self.render_paginated(
comp.clone(),
&Paginator::from_taxonomy(taxonomy, item, &library, &self.tera, &self.config.theme),
&Paginator::from_taxonomy(
taxonomy,
item,
&library,
&self.tera,
&self.config.theme,
),
)?;
} else {
let single_output =