commit
1972e58823
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -25,3 +25,5 @@ stage
|
||||||
|
|
||||||
# nixos dependencies snippet
|
# nixos dependencies snippet
|
||||||
shell.nix
|
shell.nix
|
||||||
|
# vim temporary files
|
||||||
|
**/.*.sw*
|
||||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -1,5 +1,27 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.10.0 (unreleased)
|
||||||
|
|
||||||
|
### Breaking
|
||||||
|
- Remove `toc` variable in section/page context and pass it to `page.toc` and `section.toc` instead so they are
|
||||||
|
accessible everywhere
|
||||||
|
- [Slugification](https://en.wikipedia.org/wiki/Slug_(web_publishing)#Slug) of paths, taxonomies and anchors is now configurable. By default, everything will still be slugified like in previous versions.
|
||||||
|
See documentation for information on how to disable it.
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- Add zenburn syntax highlighting theme
|
||||||
|
- Fix `zola init .`
|
||||||
|
- Add `total_pages` to paginator
|
||||||
|
- Do not prepend URL prefix to links that start with a scheme
|
||||||
|
- Allow skipping anchor checking in `zola check` for some URL prefixes
|
||||||
|
- Allow skipping prefixes in `zola check`
|
||||||
|
- Check for path collisions when building the site
|
||||||
|
- Fix bug in template extension with themes
|
||||||
|
- Use Rustls instead of openssl
|
||||||
|
- The continue reading HTML element is now a <span> instead of a <p>
|
||||||
|
- Update livereload.js
|
||||||
|
- Add --root global argument
|
||||||
|
|
||||||
## 0.9.0 (2019-09-28)
|
## 0.9.0 (2019-09-28)
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
2474
Cargo.lock
generated
2474
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "zola"
|
name = "zola"
|
||||||
version = "0.9.0"
|
version = "0.10.0"
|
||||||
authors = ["Vincent Prouillet <hello@vincentprouillet.com>"]
|
authors = ["Vincent Prouillet <hello@vincentprouillet.com>"]
|
||||||
|
edition = "2018"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
description = "A fast static site generator with everything built-in"
|
description = "A fast static site generator with everything built-in"
|
||||||
|
@ -25,8 +26,9 @@ termcolor = "1.0.4"
|
||||||
# Used in init to ensure the url given as base_url is a valid one
|
# Used in init to ensure the url given as base_url is a valid one
|
||||||
url = "2"
|
url = "2"
|
||||||
# Below is for the serve cmd
|
# Below is for the serve cmd
|
||||||
actix-files = "0.1"
|
hyper = { version = "0.13", default-features = false, features = ["runtime"] }
|
||||||
actix-web = { version = "1.0", default-features = false, features = [] }
|
hyper-staticfile = "0.5"
|
||||||
|
tokio = { version = "0.2", default-features = false, features = [] }
|
||||||
notify = "4"
|
notify = "4"
|
||||||
ws = "0.9"
|
ws = "0.9"
|
||||||
ctrlc = "3"
|
ctrlc = "3"
|
||||||
|
|
|
@ -34,4 +34,3 @@
|
||||||
| [peterlyons.com](https://peterlyons.com) | https://github.com/focusaurus/peterlyons.com-zola |
|
| [peterlyons.com](https://peterlyons.com) | https://github.com/focusaurus/peterlyons.com-zola |
|
||||||
| [blog.turbo.fish](https://blog.turbo.fish) | https://git.sr.ht/~jplatte/blog.turbo.fish |
|
| [blog.turbo.fish](https://blog.turbo.fish) | https://git.sr.ht/~jplatte/blog.turbo.fish |
|
||||||
| [guerinpe.com](https://guerinpe.com) | https://github.com/Grelot/blog |
|
| [guerinpe.com](https://guerinpe.com) | https://github.com/Grelot/blog |
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,9 @@ stages:
|
||||||
linux-stable:
|
linux-stable:
|
||||||
imageName: 'ubuntu-16.04'
|
imageName: 'ubuntu-16.04'
|
||||||
rustup_toolchain: stable
|
rustup_toolchain: stable
|
||||||
linux-1.35:
|
linux-1.39:
|
||||||
imageName: 'ubuntu-16.04'
|
imageName: 'ubuntu-16.04'
|
||||||
rustup_toolchain: 1.35.0
|
rustup_toolchain: 1.39.0
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
steps:
|
steps:
|
||||||
|
@ -32,7 +32,7 @@ stages:
|
||||||
condition: ne( variables['Agent.OS'], 'Windows_NT' )
|
condition: ne( variables['Agent.OS'], 'Windows_NT' )
|
||||||
- script: |
|
- script: |
|
||||||
curl -sSf -o rustup-init.exe https://win.rustup.rs
|
curl -sSf -o rustup-init.exe https://win.rustup.rs
|
||||||
rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN%
|
rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN% --default-host x86_64-pc-windows-msvc
|
||||||
echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
|
echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
|
||||||
displayName: Windows install rust
|
displayName: Windows install rust
|
||||||
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
||||||
|
@ -70,8 +70,10 @@ stages:
|
||||||
displayName: Install rust
|
displayName: Install rust
|
||||||
condition: ne( variables['Agent.OS'], 'Windows_NT' )
|
condition: ne( variables['Agent.OS'], 'Windows_NT' )
|
||||||
- script: |
|
- script: |
|
||||||
|
set CARGO_HOME=%USERPROFILE%\.cargo
|
||||||
curl -sSf -o rustup-init.exe https://win.rustup.rs
|
curl -sSf -o rustup-init.exe https://win.rustup.rs
|
||||||
rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN%
|
rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN% --default-host x86_64-pc-windows-msvc
|
||||||
|
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||||
echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
|
echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
|
||||||
displayName: Windows install rust
|
displayName: Windows install rust
|
||||||
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
||||||
|
|
3
build.rs
3
build.rs
|
@ -1,6 +1,3 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
// use clap::Shell;
|
// use clap::Shell;
|
||||||
|
|
||||||
include!("src/cli.rs");
|
include!("src/cli.rs");
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
name = "config"
|
name = "config"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
//! syntect, not as a helpful example for beginners.
|
//! syntect, not as a helpful example for beginners.
|
||||||
//! Although it is a valid example for serializing syntaxes, you probably won't need
|
//! Although it is a valid example for serializing syntaxes, you probably won't need
|
||||||
//! to do this yourself unless you want to cache your own compiled grammars.
|
//! to do this yourself unless you want to cache your own compiled grammars.
|
||||||
extern crate syntect;
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::iter::FromIterator;
|
||||||
use syntect::dumps::*;
|
use syntect::dumps::*;
|
||||||
use syntect::highlighting::ThemeSet;
|
use syntect::highlighting::ThemeSet;
|
||||||
use syntect::parsing::SyntaxSetBuilder;
|
use syntect::parsing::SyntaxSetBuilder;
|
||||||
|
@ -26,10 +29,25 @@ fn main() {
|
||||||
builder.add_from_folder(package_dir, true).unwrap();
|
builder.add_from_folder(package_dir, true).unwrap();
|
||||||
let ss = builder.build();
|
let ss = builder.build();
|
||||||
dump_to_file(&ss, packpath_newlines).unwrap();
|
dump_to_file(&ss, packpath_newlines).unwrap();
|
||||||
|
let mut syntaxes: HashMap<String, HashSet<String>> = HashMap::new();
|
||||||
|
|
||||||
for s in ss.syntaxes() {
|
for s in ss.syntaxes().iter() {
|
||||||
if !s.file_extensions.is_empty() {
|
syntaxes
|
||||||
println!("- {} -> {:?}", s.name, s.file_extensions);
|
.entry(s.name.clone())
|
||||||
|
.and_modify(|e| {
|
||||||
|
for ext in &s.file_extensions {
|
||||||
|
e.insert(ext.clone());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.or_insert_with(|| HashSet::from_iter(s.file_extensions.iter().cloned()));
|
||||||
|
}
|
||||||
|
let mut keys = syntaxes.keys().collect::<Vec<_>>();
|
||||||
|
keys.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
|
||||||
|
for k in keys {
|
||||||
|
if !syntaxes[k].is_empty() {
|
||||||
|
let mut extensions_sorted = syntaxes[k].iter().cloned().collect::<Vec<_>>();
|
||||||
|
extensions_sorted.sort();
|
||||||
|
println!("- {} -> {:?}", k, extensions_sorted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,16 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use syntect::parsing::{SyntaxSet, SyntaxSetBuilder};
|
use syntect::parsing::{SyntaxSet, SyntaxSetBuilder};
|
||||||
use toml;
|
use toml;
|
||||||
use toml::Value as Toml;
|
use toml::Value as Toml;
|
||||||
|
|
||||||
use errors::Result;
|
use crate::highlighting::THEME_SET;
|
||||||
use errors::Error;
|
use crate::theme::Theme;
|
||||||
use highlighting::THEME_SET;
|
use errors::{bail, Error, Result};
|
||||||
use theme::Theme;
|
|
||||||
use utils::fs::read_file_with_error;
|
use utils::fs::read_file_with_error;
|
||||||
|
use utils::slugs::SlugifyStrategy;
|
||||||
|
|
||||||
// We want a default base url for tests
|
// We want a default base url for tests
|
||||||
static DEFAULT_BASE_URL: &str = "http://a-website.com";
|
static DEFAULT_BASE_URL: &str = "http://a-website.com";
|
||||||
|
@ -23,6 +24,24 @@ pub enum Mode {
|
||||||
Check,
|
Check,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct Slugify {
|
||||||
|
pub paths: SlugifyStrategy,
|
||||||
|
pub taxonomies: SlugifyStrategy,
|
||||||
|
pub anchors: SlugifyStrategy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Slugify {
|
||||||
|
fn default() -> Self {
|
||||||
|
Slugify {
|
||||||
|
paths: SlugifyStrategy::On,
|
||||||
|
taxonomies: SlugifyStrategy::On,
|
||||||
|
anchors: SlugifyStrategy::On,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Language {
|
pub struct Language {
|
||||||
|
@ -35,7 +54,7 @@ pub struct Language {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Language {
|
impl Default for Language {
|
||||||
fn default() -> Language {
|
fn default() -> Self {
|
||||||
Language { code: String::new(), rss: false, search: false }
|
Language { code: String::new(), rss: false, search: false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +94,7 @@ impl Taxonomy {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Taxonomy {
|
impl Default for Taxonomy {
|
||||||
fn default() -> Taxonomy {
|
fn default() -> Self {
|
||||||
Taxonomy {
|
Taxonomy {
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
paginate_by: None,
|
paginate_by: None,
|
||||||
|
@ -88,6 +107,21 @@ impl Default for Taxonomy {
|
||||||
|
|
||||||
type TranslateTerm = HashMap<String, String>;
|
type TranslateTerm = HashMap<String, String>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct LinkChecker {
|
||||||
|
/// Skip link checking for these URL prefixes
|
||||||
|
pub skip_prefixes: Vec<String>,
|
||||||
|
/// Skip anchor checking for these URL prefixes
|
||||||
|
pub skip_anchor_prefixes: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LinkChecker {
|
||||||
|
fn default() -> LinkChecker {
|
||||||
|
LinkChecker { skip_prefixes: Vec::new(), skip_anchor_prefixes: Vec::new() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -152,6 +186,11 @@ pub struct Config {
|
||||||
#[serde(skip_serializing, skip_deserializing)] // not a typo, 2 are need
|
#[serde(skip_serializing, skip_deserializing)] // not a typo, 2 are need
|
||||||
pub extra_syntax_set: Option<SyntaxSet>,
|
pub extra_syntax_set: Option<SyntaxSet>,
|
||||||
|
|
||||||
|
pub link_checker: LinkChecker,
|
||||||
|
|
||||||
|
/// The setup for which slugification strategies to use for paths, taxonomies and anchors
|
||||||
|
pub slugify: Slugify,
|
||||||
|
|
||||||
/// All user params set in [extra] in the config
|
/// All user params set in [extra] in the config
|
||||||
pub extra: HashMap<String, Toml>,
|
pub extra: HashMap<String, Toml>,
|
||||||
|
|
||||||
|
@ -317,9 +356,16 @@ impl Config {
|
||||||
Error::msg(format!("Translation for language '{}' is missing", lang.as_ref()))
|
Error::msg(format!("Translation for language '{}' is missing", lang.as_ref()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
terms.get(key.as_ref()).ok_or_else(|| {
|
terms
|
||||||
Error::msg(format!("Translation key '{}' for language '{}' is missing", key.as_ref(), lang.as_ref()))
|
.get(key.as_ref())
|
||||||
}).map(|term| term.to_string())
|
.ok_or_else(|| {
|
||||||
|
Error::msg(format!(
|
||||||
|
"Translation key '{}' for language '{}' is missing",
|
||||||
|
key.as_ref(),
|
||||||
|
lang.as_ref()
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.map(|term| term.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,6 +392,8 @@ impl Default for Config {
|
||||||
translations: HashMap::new(),
|
translations: HashMap::new(),
|
||||||
extra_syntaxes: Vec::new(),
|
extra_syntaxes: Vec::new(),
|
||||||
extra_syntax_set: None,
|
extra_syntax_set: None,
|
||||||
|
link_checker: LinkChecker::default(),
|
||||||
|
slugify: Slugify::default(),
|
||||||
extra: HashMap::new(),
|
extra: HashMap::new(),
|
||||||
build_timestamp: Some(1),
|
build_timestamp: Some(1),
|
||||||
}
|
}
|
||||||
|
@ -354,7 +402,7 @@ impl Default for Config {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Config, Theme};
|
use super::{Config, SlugifyStrategy, Theme};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_import_valid_config() {
|
fn can_import_valid_config() {
|
||||||
|
@ -551,4 +599,62 @@ ignored_content = ["*.{graphml,iso}", "*.py?"]
|
||||||
assert!(g.is_match("foo.py3"));
|
assert!(g.is_match("foo.py3"));
|
||||||
assert!(!g.is_match("foo.py"));
|
assert!(!g.is_match("foo.py"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn link_checker_skip_anchor_prefixes() {
|
||||||
|
let config_str = r#"
|
||||||
|
title = "My site"
|
||||||
|
base_url = "example.com"
|
||||||
|
|
||||||
|
[link_checker]
|
||||||
|
skip_anchor_prefixes = [
|
||||||
|
"https://caniuse.com/#feat=",
|
||||||
|
"https://github.com/rust-lang/rust/blob/",
|
||||||
|
]
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config = Config::parse(config_str).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
config.link_checker.skip_anchor_prefixes,
|
||||||
|
vec!["https://caniuse.com/#feat=", "https://github.com/rust-lang/rust/blob/"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn link_checker_skip_prefixes() {
|
||||||
|
let config_str = r#"
|
||||||
|
title = "My site"
|
||||||
|
base_url = "example.com"
|
||||||
|
|
||||||
|
[link_checker]
|
||||||
|
skip_prefixes = [
|
||||||
|
"http://[2001:db8::]/",
|
||||||
|
"https://www.example.com/path",
|
||||||
|
]
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config = Config::parse(config_str).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
config.link_checker.skip_prefixes,
|
||||||
|
vec!["http://[2001:db8::]/", "https://www.example.com/path",]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn slugify_strategies() {
|
||||||
|
let config_str = r#"
|
||||||
|
title = "My site"
|
||||||
|
base_url = "example.com"
|
||||||
|
|
||||||
|
[slugify]
|
||||||
|
paths = "on"
|
||||||
|
taxonomies = "safe"
|
||||||
|
anchors = "off"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config = Config::parse(config_str).unwrap();
|
||||||
|
assert_eq!(config.slugify.paths, SlugifyStrategy::On);
|
||||||
|
assert_eq!(config.slugify.taxonomies, SlugifyStrategy::Safe);
|
||||||
|
assert_eq!(config.slugify.anchors, SlugifyStrategy::Off);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use syntect::dumps::from_binary;
|
use syntect::dumps::from_binary;
|
||||||
use syntect::easy::HighlightLines;
|
use syntect::easy::HighlightLines;
|
||||||
use syntect::highlighting::ThemeSet;
|
use syntect::highlighting::ThemeSet;
|
||||||
use syntect::parsing::SyntaxSet;
|
use syntect::parsing::SyntaxSet;
|
||||||
|
|
||||||
use Config;
|
use crate::config::Config;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref SYNTAX_SET: SyntaxSet = {
|
pub static ref SYNTAX_SET: SyntaxSet = {
|
||||||
|
|
|
@ -1,20 +1,7 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate chrono;
|
|
||||||
extern crate globset;
|
|
||||||
extern crate toml;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
extern crate syntect;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate errors;
|
|
||||||
extern crate utils;
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub mod highlighting;
|
pub mod highlighting;
|
||||||
mod theme;
|
mod theme;
|
||||||
pub use config::{Config, Language, Taxonomy};
|
pub use crate::config::{Config, Language, LinkChecker, Taxonomy};
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use toml::Value as Toml;
|
use toml::Value as Toml;
|
||||||
|
|
||||||
use errors::Result;
|
use errors::{bail, Result};
|
||||||
use utils::fs::read_file_with_error;
|
use utils::fs::read_file_with_error;
|
||||||
|
|
||||||
/// Holds the data from a `theme.toml` file.
|
/// Holds the data from a `theme.toml` file.
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
name = "errors"
|
name = "errors"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tera = "1.0.0-beta.10"
|
tera = "1"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
image = "0.22"
|
image = "0.23"
|
||||||
syntect = "=3.2.0"
|
syntect = "=3.2.0"
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
extern crate image;
|
|
||||||
extern crate syntect;
|
|
||||||
extern crate tera;
|
|
||||||
extern crate toml;
|
|
||||||
|
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -63,6 +58,18 @@ impl Error {
|
||||||
pub fn chain(value: impl ToString, source: impl Into<Box<dyn StdError>>) -> Self {
|
pub fn chain(value: impl ToString, source: impl Into<Box<dyn StdError>>) -> Self {
|
||||||
Self { kind: ErrorKind::Msg(value.to_string()), source: Some(source.into()) }
|
Self { kind: ErrorKind::Msg(value.to_string()), source: Some(source.into()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an error from a list of path collisions, formatting the output
|
||||||
|
pub fn from_collisions(collisions: Vec<(&str, Vec<String>)>) -> Self {
|
||||||
|
let mut msg = String::from("Found path collisions:\n");
|
||||||
|
|
||||||
|
for (path, filepaths) in collisions {
|
||||||
|
let row = format!("- `{}` from files {:?}\n", path, filepaths);
|
||||||
|
msg.push_str(&row);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { kind: ErrorKind::Msg(msg), source: None }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Error {
|
impl From<&str> for Error {
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
name = "front_matter"
|
name = "front_matter"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tera = "1.0.0-beta.10"
|
tera = "1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
|
|
|
@ -1,18 +1,7 @@
|
||||||
#[macro_use]
|
use lazy_static::lazy_static;
|
||||||
extern crate lazy_static;
|
use serde_derive::{Deserialize, Serialize};
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate chrono;
|
|
||||||
extern crate regex;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate tera;
|
|
||||||
extern crate toml;
|
|
||||||
|
|
||||||
#[macro_use]
|
use errors::{bail, Error, Result};
|
||||||
extern crate errors;
|
|
||||||
extern crate utils;
|
|
||||||
|
|
||||||
use errors::{Error, Result};
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
|
use serde_derive::Deserialize;
|
||||||
use tera::{Map, Value};
|
use tera::{Map, Value};
|
||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
use errors::Result;
|
use errors::{bail, Result};
|
||||||
use utils::de::{fix_toml_dates, from_toml_datetime};
|
use utils::de::{fix_toml_dates, from_toml_datetime};
|
||||||
|
|
||||||
/// The front matter of every page
|
/// The front matter of every page
|
||||||
|
@ -87,11 +88,9 @@ impl PageFrontMatter {
|
||||||
pub fn date_to_datetime(&mut self) {
|
pub fn date_to_datetime(&mut self) {
|
||||||
self.datetime = if let Some(ref d) = self.date {
|
self.datetime = if let Some(ref d) = self.date {
|
||||||
if d.contains('T') {
|
if d.contains('T') {
|
||||||
DateTime::parse_from_rfc3339(&d).ok().and_then(|s| Some(s.naive_local()))
|
DateTime::parse_from_rfc3339(&d).ok().map(|s| s.naive_local())
|
||||||
} else {
|
} else {
|
||||||
NaiveDate::parse_from_str(&d, "%Y-%m-%d")
|
NaiveDate::parse_from_str(&d, "%Y-%m-%d").ok().map(|s| s.and_hms(0, 0, 0))
|
||||||
.ok()
|
|
||||||
.and_then(|s| Some(s.and_hms(0, 0, 0)))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use tera::{Map, Value};
|
||||||
use tera::Value;
|
|
||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
use errors::Result;
|
|
||||||
|
|
||||||
use super::{InsertAnchor, SortBy};
|
use super::{InsertAnchor, SortBy};
|
||||||
|
use errors::{bail, Result};
|
||||||
|
use utils::de::fix_toml_dates;
|
||||||
|
|
||||||
static DEFAULT_PAGINATE_PATH: &str = "page";
|
static DEFAULT_PAGINATE_PATH: &str = "page";
|
||||||
|
|
||||||
|
@ -63,16 +62,21 @@ pub struct SectionFrontMatter {
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub aliases: Vec<String>,
|
pub aliases: Vec<String>,
|
||||||
/// Any extra parameter present in the front matter
|
/// Any extra parameter present in the front matter
|
||||||
pub extra: HashMap<String, Value>,
|
pub extra: Map<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectionFrontMatter {
|
impl SectionFrontMatter {
|
||||||
pub fn parse(toml: &str) -> Result<SectionFrontMatter> {
|
pub fn parse(toml: &str) -> Result<SectionFrontMatter> {
|
||||||
let f: SectionFrontMatter = match toml::from_str(toml) {
|
let mut f: SectionFrontMatter = match toml::from_str(toml) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(e) => bail!(e),
|
Err(e) => bail!(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
f.extra = match fix_toml_dates(f.extra) {
|
||||||
|
Value::Object(o) => o,
|
||||||
|
_ => unreachable!("Got something other than a table in section extra"),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(f)
|
Ok(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +106,7 @@ impl Default for SectionFrontMatter {
|
||||||
transparent: false,
|
transparent: false,
|
||||||
page_template: None,
|
page_template: None,
|
||||||
aliases: Vec::new(),
|
aliases: Vec::new(),
|
||||||
extra: HashMap::new(),
|
extra: Map::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
name = "imageproc"
|
name = "imageproc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vojtěch Král <vojtech@kral.hk>"]
|
authors = ["Vojtěch Král <vojtech@kral.hk>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
regex = "1.0"
|
regex = "1.0"
|
||||||
tera = "1.0.0-beta.10"
|
tera = "1"
|
||||||
image = "0.22"
|
image = "0.23"
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
|
|
||||||
errors = { path = "../errors" }
|
errors = { path = "../errors" }
|
||||||
|
|
|
@ -1,12 +1,3 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
extern crate image;
|
|
||||||
extern crate rayon;
|
|
||||||
extern crate regex;
|
|
||||||
|
|
||||||
extern crate errors;
|
|
||||||
extern crate utils;
|
|
||||||
|
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::collections::hash_map::Entry as HEntry;
|
use std::collections::hash_map::Entry as HEntry;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -14,9 +5,9 @@ use std::fs::{self, File};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use image::jpeg::JPEGEncoder;
|
use image::imageops::FilterType;
|
||||||
use image::png::PNGEncoder;
|
use image::{GenericImageView, ImageOutputFormat};
|
||||||
use image::{FilterType, GenericImageView};
|
use lazy_static::lazy_static;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
@ -272,7 +263,7 @@ impl ImageOp {
|
||||||
} else {
|
} else {
|
||||||
img
|
img
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Fill(w, h) => {
|
Fill(w, h) => {
|
||||||
let factor_w = img_w as f32 / w as f32;
|
let factor_w = img_w as f32 / w as f32;
|
||||||
let factor_h = img_h as f32 / h as f32;
|
let factor_h = img_h as f32 / h as f32;
|
||||||
|
@ -304,16 +295,13 @@ impl ImageOp {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut f = File::create(target_path)?;
|
let mut f = File::create(target_path)?;
|
||||||
let (img_w, img_h) = img.dimensions();
|
|
||||||
|
|
||||||
match self.format {
|
match self.format {
|
||||||
Format::Png => {
|
Format::Png => {
|
||||||
let enc = PNGEncoder::new(&mut f);
|
img.write_to(&mut f, ImageOutputFormat::Png)?;
|
||||||
enc.encode(&img.raw_pixels(), img_w, img_h, img.color())?;
|
|
||||||
}
|
}
|
||||||
Format::Jpeg(q) => {
|
Format::Jpeg(q) => {
|
||||||
let mut enc = JPEGEncoder::new_with_quality(&mut f, q);
|
img.write_to(&mut f, ImageOutputFormat::Jpeg(q))?;
|
||||||
enc.encode(&img.raw_pixels(), img_w, img_h, img.color())?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
name = "library"
|
name = "library"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
slotmap = "0.4"
|
slotmap = "0.4"
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
tera = "1.0.0-beta.10"
|
tera = "1"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
slug = "0.1"
|
|
||||||
regex = "1"
|
regex = "1"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use errors::Result;
|
use errors::{bail, Result};
|
||||||
|
|
||||||
/// Takes a full path to a file and returns only the components after the first `content` directory
|
/// Takes a full path to a file and returns only the components after the first `content` directory
|
||||||
/// Will not return the filename as last component
|
/// Will not return the filename as last component
|
||||||
|
@ -56,6 +56,7 @@ impl FileInfo {
|
||||||
let file_path = path.to_path_buf();
|
let file_path = path.to_path_buf();
|
||||||
let mut parent = file_path.parent().expect("Get parent of page").to_path_buf();
|
let mut parent = file_path.parent().expect("Get parent of page").to_path_buf();
|
||||||
let name = path.file_stem().unwrap().to_string_lossy().to_string();
|
let name = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||||
|
let canonical = parent.join(&name);
|
||||||
let mut components =
|
let mut components =
|
||||||
find_content_components(&file_path.strip_prefix(base_path).unwrap_or(&file_path));
|
find_content_components(&file_path.strip_prefix(base_path).unwrap_or(&file_path));
|
||||||
let relative = if !components.is_empty() {
|
let relative = if !components.is_empty() {
|
||||||
|
@ -78,7 +79,7 @@ impl FileInfo {
|
||||||
path: file_path,
|
path: file_path,
|
||||||
// We don't care about grand parent for pages
|
// We don't care about grand parent for pages
|
||||||
grand_parent: None,
|
grand_parent: None,
|
||||||
canonical: parent.join(&name),
|
canonical,
|
||||||
parent,
|
parent,
|
||||||
name,
|
name,
|
||||||
components,
|
components,
|
||||||
|
@ -135,7 +136,7 @@ impl FileInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.name = parts.swap_remove(0);
|
self.name = parts.swap_remove(0);
|
||||||
self.canonical = self.parent.join(&self.name);
|
self.canonical = self.path.parent().expect("Get parent of page path").join(&self.name);
|
||||||
let lang = parts.swap_remove(0);
|
let lang = parts.swap_remove(0);
|
||||||
|
|
||||||
Ok(lang)
|
Ok(lang)
|
||||||
|
@ -254,4 +255,34 @@ mod tests {
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
assert_eq!(res.unwrap(), "fr");
|
assert_eq!(res.unwrap(), "fr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Regression test for https://github.com/getzola/zola/issues/854
|
||||||
|
#[test]
|
||||||
|
fn correct_canonical_for_index() {
|
||||||
|
let file = FileInfo::new_page(
|
||||||
|
&Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.md"),
|
||||||
|
&PathBuf::new(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
file.canonical,
|
||||||
|
Path::new("/home/vincent/code/site/content/posts/tutorials/python/index")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Regression test for https://github.com/getzola/zola/issues/854
|
||||||
|
#[test]
|
||||||
|
fn correct_canonical_after_find_language() {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
|
||||||
|
let mut file = FileInfo::new_page(
|
||||||
|
&Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.fr.md"),
|
||||||
|
&PathBuf::new(),
|
||||||
|
);
|
||||||
|
let res = file.find_language(&config);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
file.canonical,
|
||||||
|
Path::new("/home/vincent/code/site/content/posts/tutorials/python/index")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,23 +2,24 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use slotmap::DefaultKey;
|
use slotmap::DefaultKey;
|
||||||
use slug::slugify;
|
|
||||||
use tera::{Context as TeraContext, Tera};
|
use tera::{Context as TeraContext, Tera};
|
||||||
|
|
||||||
|
use crate::library::Library;
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use errors::{Error, Result};
|
use errors::{Error, Result};
|
||||||
use front_matter::{split_page_content, InsertAnchor, PageFrontMatter};
|
use front_matter::{split_page_content, InsertAnchor, PageFrontMatter};
|
||||||
use library::Library;
|
|
||||||
use rendering::{render_content, Heading, RenderContext};
|
use rendering::{render_content, Heading, RenderContext};
|
||||||
use utils::fs::{find_related_assets, read_file};
|
use utils::fs::{find_related_assets, read_file};
|
||||||
use utils::site::get_reading_analytics;
|
use utils::site::get_reading_analytics;
|
||||||
use utils::templates::render_template;
|
use utils::templates::render_template;
|
||||||
|
|
||||||
use content::file_info::FileInfo;
|
use crate::content::file_info::FileInfo;
|
||||||
use content::has_anchor;
|
use crate::content::has_anchor;
|
||||||
use content::ser::SerializingPage;
|
use crate::content::ser::SerializingPage;
|
||||||
|
use utils::slugs::slugify_paths;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
// Based on https://regex101.com/r/H2n38Z/1/tests
|
// Based on https://regex101.com/r/H2n38Z/1/tests
|
||||||
|
@ -160,21 +161,24 @@ impl Page {
|
||||||
|
|
||||||
page.slug = {
|
page.slug = {
|
||||||
if let Some(ref slug) = page.meta.slug {
|
if let Some(ref slug) = page.meta.slug {
|
||||||
slugify(&slug.trim())
|
slugify_paths(slug, config.slugify.paths)
|
||||||
} else if page.file.name == "index" {
|
} else if page.file.name == "index" {
|
||||||
if let Some(parent) = page.file.path.parent() {
|
if let Some(parent) = page.file.path.parent() {
|
||||||
if let Some(slug) = slug_from_dated_filename {
|
if let Some(slug) = slug_from_dated_filename {
|
||||||
slugify(&slug)
|
slugify_paths(&slug, config.slugify.paths)
|
||||||
} else {
|
} else {
|
||||||
slugify(parent.file_name().unwrap().to_str().unwrap())
|
slugify_paths(
|
||||||
|
parent.file_name().unwrap().to_str().unwrap(),
|
||||||
|
config.slugify.paths,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
slugify(&page.file.name)
|
slugify_paths(&page.file.name, config.slugify.paths)
|
||||||
}
|
}
|
||||||
} else if let Some(slug) = slug_from_dated_filename {
|
} else if let Some(slug) = slug_from_dated_filename {
|
||||||
slugify(&slug)
|
slugify_paths(&slug, config.slugify.paths)
|
||||||
} else {
|
} else {
|
||||||
slugify(&page.file.name)
|
slugify_paths(&page.file.name, config.slugify.paths)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -290,7 +294,6 @@ impl Page {
|
||||||
context.insert("current_path", &self.path);
|
context.insert("current_path", &self.path);
|
||||||
context.insert("page", &self.to_serialized(library));
|
context.insert("page", &self.to_serialized(library));
|
||||||
context.insert("lang", &self.lang);
|
context.insert("lang", &self.lang);
|
||||||
context.insert("toc", &self.toc);
|
|
||||||
|
|
||||||
render_template(&tpl_name, tera, context, &config.theme).map_err(|e| {
|
render_template(&tpl_name, tera, context, &config.theme).map_err(|e| {
|
||||||
Error::chain(format!("Failed to render page '{}'", self.file.path.display()), e)
|
Error::chain(format!("Failed to render page '{}'", self.file.path.display()), e)
|
||||||
|
@ -376,6 +379,7 @@ mod tests {
|
||||||
use super::Page;
|
use super::Page;
|
||||||
use config::{Config, Language};
|
use config::{Config, Language};
|
||||||
use front_matter::InsertAnchor;
|
use front_matter::InsertAnchor;
|
||||||
|
use utils::slugs::SlugifyStrategy;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_can_parse_a_valid_page() {
|
fn test_can_parse_a_valid_page() {
|
||||||
|
@ -444,7 +448,8 @@ Hello world"#;
|
||||||
slug = "hello-&-world"
|
slug = "hello-&-world"
|
||||||
+++
|
+++
|
||||||
Hello world"#;
|
Hello world"#;
|
||||||
let config = Config::default();
|
let mut config = Config::default();
|
||||||
|
config.slugify.paths = SlugifyStrategy::On;
|
||||||
let res = Page::parse(Path::new("start.md"), content, &config, &PathBuf::new());
|
let res = Page::parse(Path::new("start.md"), content, &config, &PathBuf::new());
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
let page = res.unwrap();
|
let page = res.unwrap();
|
||||||
|
@ -453,6 +458,23 @@ Hello world"#;
|
||||||
assert_eq!(page.permalink, config.make_permalink("hello-world"));
|
assert_eq!(page.permalink, config.make_permalink("hello-world"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_make_url_from_utf8_slug_frontmatter() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
slug = "日本"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.slugify.paths = SlugifyStrategy::Safe;
|
||||||
|
let res = Page::parse(Path::new("start.md"), content, &config, &PathBuf::new());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.path, "日本/");
|
||||||
|
assert_eq!(page.components, vec!["日本"]);
|
||||||
|
assert_eq!(page.permalink, config.make_permalink("日本"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_make_url_from_path() {
|
fn can_make_url_from_path() {
|
||||||
let content = r#"
|
let content = r#"
|
||||||
|
@ -509,7 +531,8 @@ Hello world"#;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_make_slug_from_non_slug_filename() {
|
fn can_make_slug_from_non_slug_filename() {
|
||||||
let config = Config::default();
|
let mut config = Config::default();
|
||||||
|
config.slugify.paths = SlugifyStrategy::On;
|
||||||
let res =
|
let res =
|
||||||
Page::parse(Path::new(" file with space.md"), "+++\n+++", &config, &PathBuf::new());
|
Page::parse(Path::new(" file with space.md"), "+++\n+++", &config, &PathBuf::new());
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
@ -518,6 +541,17 @@ Hello world"#;
|
||||||
assert_eq!(page.permalink, config.make_permalink(&page.slug));
|
assert_eq!(page.permalink, config.make_permalink(&page.slug));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_make_path_from_utf8_filename() {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.slugify.paths = SlugifyStrategy::Safe;
|
||||||
|
let res = Page::parse(Path::new("日本.md"), "+++\n++++", &config, &PathBuf::new());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.slug, "日本");
|
||||||
|
assert_eq!(page.permalink, config.make_permalink(&page.slug));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_specify_summary() {
|
fn can_specify_summary() {
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
|
|
|
@ -12,10 +12,10 @@ use utils::fs::{find_related_assets, read_file};
|
||||||
use utils::site::get_reading_analytics;
|
use utils::site::get_reading_analytics;
|
||||||
use utils::templates::render_template;
|
use utils::templates::render_template;
|
||||||
|
|
||||||
use content::file_info::FileInfo;
|
use crate::content::file_info::FileInfo;
|
||||||
use content::has_anchor;
|
use crate::content::has_anchor;
|
||||||
use content::ser::SerializingSection;
|
use crate::content::ser::SerializingSection;
|
||||||
use library::Library;
|
use crate::library::Library;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Section {
|
pub struct Section {
|
||||||
|
@ -121,6 +121,7 @@ impl Section {
|
||||||
} else {
|
} else {
|
||||||
section.path = format!("{}/", path);
|
section.path = format!("{}/", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
section.components = section
|
section.components = section
|
||||||
.path
|
.path
|
||||||
.split('/')
|
.split('/')
|
||||||
|
@ -131,7 +132,7 @@ impl Section {
|
||||||
Ok(section)
|
Ok(section)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read and parse a .md file into a Page struct
|
/// Read and parse a .md file into a Section struct
|
||||||
pub fn from_file<P: AsRef<Path>>(
|
pub fn from_file<P: AsRef<Path>>(
|
||||||
path: P,
|
path: P,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
|
@ -219,7 +220,6 @@ impl Section {
|
||||||
context.insert("current_path", &self.path);
|
context.insert("current_path", &self.path);
|
||||||
context.insert("section", &self.to_serialized(library));
|
context.insert("section", &self.to_serialized(library));
|
||||||
context.insert("lang", &self.lang);
|
context.insert("lang", &self.lang);
|
||||||
context.insert("toc", &self.toc);
|
|
||||||
|
|
||||||
render_template(tpl_name, tera, context, &config.theme).map_err(|e| {
|
render_template(tpl_name, tera, context, &config.theme).map_err(|e| {
|
||||||
Error::chain(format!("Failed to render section '{}'", self.file.path.display()), e)
|
Error::chain(format!("Failed to render section '{}'", self.file.path.display()), e)
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
//! What we are sending to the templates when rendering them
|
//! What we are sending to the templates when rendering them
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use serde_derive::Serialize;
|
||||||
use tera::{Map, Value};
|
use tera::{Map, Value};
|
||||||
|
|
||||||
use content::{Page, Section};
|
use crate::content::{Page, Section};
|
||||||
use library::Library;
|
use crate::library::Library;
|
||||||
|
use rendering::Heading;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||||
pub struct TranslatedContent<'a> {
|
pub struct TranslatedContent<'a> {
|
||||||
lang: &'a str,
|
lang: &'a str,
|
||||||
permalink: &'a str,
|
permalink: &'a str,
|
||||||
title: &'a Option<String>,
|
title: &'a Option<String>,
|
||||||
|
/// The path to the markdown file; useful for retrieving the full page through
|
||||||
|
/// the `get_page` function.
|
||||||
|
path: &'a Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TranslatedContent<'a> {
|
impl<'a> TranslatedContent<'a> {
|
||||||
|
@ -24,6 +30,7 @@ impl<'a> TranslatedContent<'a> {
|
||||||
lang: &other.lang,
|
lang: &other.lang,
|
||||||
permalink: &other.permalink,
|
permalink: &other.permalink,
|
||||||
title: &other.meta.title,
|
title: &other.meta.title,
|
||||||
|
path: &other.file.path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +46,7 @@ impl<'a> TranslatedContent<'a> {
|
||||||
lang: &other.lang,
|
lang: &other.lang,
|
||||||
permalink: &other.permalink,
|
permalink: &other.permalink,
|
||||||
title: &other.meta.title,
|
title: &other.meta.title,
|
||||||
|
path: &other.file.path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +72,7 @@ pub struct SerializingPage<'a> {
|
||||||
path: &'a str,
|
path: &'a str,
|
||||||
components: &'a [String],
|
components: &'a [String],
|
||||||
summary: &'a Option<String>,
|
summary: &'a Option<String>,
|
||||||
|
toc: &'a [Heading],
|
||||||
word_count: Option<usize>,
|
word_count: Option<usize>,
|
||||||
reading_time: Option<usize>,
|
reading_time: Option<usize>,
|
||||||
assets: &'a [String],
|
assets: &'a [String],
|
||||||
|
@ -125,6 +134,7 @@ impl<'a> SerializingPage<'a> {
|
||||||
path: &page.path,
|
path: &page.path,
|
||||||
components: &page.components,
|
components: &page.components,
|
||||||
summary: &page.summary,
|
summary: &page.summary,
|
||||||
|
toc: &page.toc,
|
||||||
word_count: page.word_count,
|
word_count: page.word_count,
|
||||||
reading_time: page.reading_time,
|
reading_time: page.reading_time,
|
||||||
assets: &page.serialized_assets,
|
assets: &page.serialized_assets,
|
||||||
|
@ -180,6 +190,7 @@ impl<'a> SerializingPage<'a> {
|
||||||
path: &page.path,
|
path: &page.path,
|
||||||
components: &page.components,
|
components: &page.components,
|
||||||
summary: &page.summary,
|
summary: &page.summary,
|
||||||
|
toc: &page.toc,
|
||||||
word_count: page.word_count,
|
word_count: page.word_count,
|
||||||
reading_time: page.reading_time,
|
reading_time: page.reading_time,
|
||||||
assets: &page.serialized_assets,
|
assets: &page.serialized_assets,
|
||||||
|
@ -202,9 +213,10 @@ pub struct SerializingSection<'a> {
|
||||||
ancestors: Vec<String>,
|
ancestors: Vec<String>,
|
||||||
title: &'a Option<String>,
|
title: &'a Option<String>,
|
||||||
description: &'a Option<String>,
|
description: &'a Option<String>,
|
||||||
extra: &'a HashMap<String, Value>,
|
extra: &'a Map<String, Value>,
|
||||||
path: &'a str,
|
path: &'a str,
|
||||||
components: &'a [String],
|
components: &'a [String],
|
||||||
|
toc: &'a [Heading],
|
||||||
word_count: Option<usize>,
|
word_count: Option<usize>,
|
||||||
reading_time: Option<usize>,
|
reading_time: Option<usize>,
|
||||||
lang: &'a str,
|
lang: &'a str,
|
||||||
|
@ -244,6 +256,7 @@ impl<'a> SerializingSection<'a> {
|
||||||
extra: §ion.meta.extra,
|
extra: §ion.meta.extra,
|
||||||
path: §ion.path,
|
path: §ion.path,
|
||||||
components: §ion.components,
|
components: §ion.components,
|
||||||
|
toc: §ion.toc,
|
||||||
word_count: section.word_count,
|
word_count: section.word_count,
|
||||||
reading_time: section.reading_time,
|
reading_time: section.reading_time,
|
||||||
assets: §ion.serialized_assets,
|
assets: §ion.serialized_assets,
|
||||||
|
@ -280,6 +293,7 @@ impl<'a> SerializingSection<'a> {
|
||||||
extra: §ion.meta.extra,
|
extra: §ion.meta.extra,
|
||||||
path: §ion.path,
|
path: §ion.path,
|
||||||
components: §ion.components,
|
components: §ion.components,
|
||||||
|
toc: §ion.toc,
|
||||||
word_count: section.word_count,
|
word_count: section.word_count,
|
||||||
reading_time: section.reading_time,
|
reading_time: section.reading_time,
|
||||||
assets: §ion.serialized_assets,
|
assets: §ion.serialized_assets,
|
||||||
|
|
|
@ -1,29 +1,3 @@
|
||||||
extern crate serde;
|
|
||||||
extern crate slug;
|
|
||||||
extern crate tera;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate chrono;
|
|
||||||
extern crate rayon;
|
|
||||||
extern crate slotmap;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
extern crate regex;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate globset;
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate tempfile;
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate toml;
|
|
||||||
|
|
||||||
extern crate config;
|
|
||||||
extern crate front_matter;
|
|
||||||
extern crate rendering;
|
|
||||||
extern crate utils;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate errors;
|
|
||||||
|
|
||||||
mod content;
|
mod content;
|
||||||
mod library;
|
mod library;
|
||||||
mod pagination;
|
mod pagination;
|
||||||
|
@ -32,8 +6,8 @@ mod taxonomies;
|
||||||
|
|
||||||
pub use slotmap::{DenseSlotMap, Key};
|
pub use slotmap::{DenseSlotMap, Key};
|
||||||
|
|
||||||
|
pub use crate::library::Library;
|
||||||
pub use content::{Page, Section, SerializingPage, SerializingSection};
|
pub use content::{Page, Section, SerializingPage, SerializingSection};
|
||||||
pub use library::Library;
|
|
||||||
pub use pagination::Paginator;
|
pub use pagination::Paginator;
|
||||||
pub use sorting::sort_actual_pages_by_date;
|
pub use sorting::sort_actual_pages_by_date;
|
||||||
pub use taxonomies::{find_taxonomies, Taxonomy, TaxonomyItem};
|
pub use taxonomies::{find_taxonomies, Taxonomy, TaxonomyItem};
|
||||||
|
|
|
@ -1,13 +1,26 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use slotmap::{DenseSlotMap, DefaultKey};
|
use slotmap::{DefaultKey, DenseSlotMap};
|
||||||
|
|
||||||
use front_matter::SortBy;
|
use front_matter::SortBy;
|
||||||
|
|
||||||
|
use crate::content::{Page, Section};
|
||||||
|
use crate::sorting::{find_siblings, sort_pages_by_date, sort_pages_by_weight};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use content::{Page, Section};
|
|
||||||
use sorting::{find_siblings, sort_pages_by_date, sort_pages_by_weight};
|
// Like vec! but for HashSet
|
||||||
|
macro_rules! set {
|
||||||
|
( $( $x:expr ),* ) => {
|
||||||
|
{
|
||||||
|
let mut s = HashSet::new();
|
||||||
|
$(
|
||||||
|
s.insert($x);
|
||||||
|
)*
|
||||||
|
s
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Houses everything about pages and sections
|
/// Houses everything about pages and sections
|
||||||
/// Think of it as a database where each page and section has an id (Key here)
|
/// Think of it as a database where each page and section has an id (Key here)
|
||||||
|
@ -398,4 +411,128 @@ impl Library {
|
||||||
pub fn contains_page<P: AsRef<Path>>(&self, path: P) -> bool {
|
pub fn contains_page<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||||
self.paths_to_pages.contains_key(path.as_ref())
|
self.paths_to_pages.contains_key(path.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This will check every section/page paths + the aliases and ensure none of them
|
||||||
|
/// are colliding.
|
||||||
|
/// Returns (path colliding, [list of files causing that collision])
|
||||||
|
pub fn check_for_path_collisions(&self) -> Vec<(&str, Vec<String>)> {
|
||||||
|
let mut paths: HashMap<&str, HashSet<DefaultKey>> = HashMap::new();
|
||||||
|
|
||||||
|
for (key, page) in &self.pages {
|
||||||
|
paths
|
||||||
|
.entry(&page.path)
|
||||||
|
.and_modify(|s| {
|
||||||
|
s.insert(key);
|
||||||
|
})
|
||||||
|
.or_insert_with(|| set!(key));
|
||||||
|
|
||||||
|
for alias in &page.meta.aliases {
|
||||||
|
paths
|
||||||
|
.entry(&alias)
|
||||||
|
.and_modify(|s| {
|
||||||
|
s.insert(key);
|
||||||
|
})
|
||||||
|
.or_insert_with(|| set!(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (key, section) in &self.sections {
|
||||||
|
if !section.meta.render {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
paths
|
||||||
|
.entry(§ion.path)
|
||||||
|
.and_modify(|s| {
|
||||||
|
s.insert(key);
|
||||||
|
})
|
||||||
|
.or_insert_with(|| set!(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut collisions = vec![];
|
||||||
|
for (p, keys) in paths {
|
||||||
|
if keys.len() > 1 {
|
||||||
|
let file_paths: Vec<String> = keys
|
||||||
|
.iter()
|
||||||
|
.map(|k| {
|
||||||
|
self.pages.get(*k).map(|p| p.file.relative.clone()).unwrap_or_else(|| {
|
||||||
|
self.sections.get(*k).map(|s| s.file.relative.clone()).unwrap()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
collisions.push((p, file_paths));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collisions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_find_no_collisions() {
|
||||||
|
let mut library = Library::new(10, 10, false);
|
||||||
|
let mut page = Page::default();
|
||||||
|
page.path = "hello".to_string();
|
||||||
|
let mut page2 = Page::default();
|
||||||
|
page2.path = "hello-world".to_string();
|
||||||
|
let mut section = Section::default();
|
||||||
|
section.path = "blog".to_string();
|
||||||
|
library.insert_page(page);
|
||||||
|
library.insert_page(page2);
|
||||||
|
library.insert_section(section);
|
||||||
|
|
||||||
|
let collisions = library.check_for_path_collisions();
|
||||||
|
assert_eq!(collisions.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_find_collisions_between_pages() {
|
||||||
|
let mut library = Library::new(10, 10, false);
|
||||||
|
let mut page = Page::default();
|
||||||
|
page.path = "hello".to_string();
|
||||||
|
page.file.relative = "hello".to_string();
|
||||||
|
let mut page2 = Page::default();
|
||||||
|
page2.path = "hello".to_string();
|
||||||
|
page2.file.relative = "hello-world".to_string();
|
||||||
|
let mut section = Section::default();
|
||||||
|
section.path = "blog".to_string();
|
||||||
|
section.file.relative = "hello-world".to_string();
|
||||||
|
library.insert_page(page.clone());
|
||||||
|
library.insert_page(page2.clone());
|
||||||
|
library.insert_section(section);
|
||||||
|
|
||||||
|
let collisions = library.check_for_path_collisions();
|
||||||
|
assert_eq!(collisions.len(), 1);
|
||||||
|
assert_eq!(collisions[0].0, page.path);
|
||||||
|
assert!(collisions[0].1.contains(&page.file.relative));
|
||||||
|
assert!(collisions[0].1.contains(&page2.file.relative));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_find_collisions_with_an_alias() {
|
||||||
|
let mut library = Library::new(10, 10, false);
|
||||||
|
let mut page = Page::default();
|
||||||
|
page.path = "hello".to_string();
|
||||||
|
page.file.relative = "hello".to_string();
|
||||||
|
let mut page2 = Page::default();
|
||||||
|
page2.path = "hello-world".to_string();
|
||||||
|
page2.file.relative = "hello-world".to_string();
|
||||||
|
page2.meta.aliases = vec!["hello".to_string()];
|
||||||
|
let mut section = Section::default();
|
||||||
|
section.path = "blog".to_string();
|
||||||
|
section.file.relative = "hello-world".to_string();
|
||||||
|
library.insert_page(page.clone());
|
||||||
|
library.insert_page(page2.clone());
|
||||||
|
library.insert_section(section);
|
||||||
|
|
||||||
|
let collisions = library.check_for_path_collisions();
|
||||||
|
assert_eq!(collisions.len(), 1);
|
||||||
|
assert_eq!(collisions[0].0, page.path);
|
||||||
|
assert!(collisions[0].1.contains(&page.file.relative));
|
||||||
|
assert!(collisions[0].1.contains(&page2.file.relative));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde_derive::Serialize;
|
||||||
use slotmap::DefaultKey;
|
use slotmap::DefaultKey;
|
||||||
use tera::{to_value, Context, Tera, Value};
|
use tera::{to_value, Context, Tera, Value};
|
||||||
|
|
||||||
|
@ -7,9 +8,9 @@ use config::Config;
|
||||||
use errors::{Error, Result};
|
use errors::{Error, Result};
|
||||||
use utils::templates::render_template;
|
use utils::templates::render_template;
|
||||||
|
|
||||||
use content::{Section, SerializingPage, SerializingSection};
|
use crate::content::{Section, SerializingPage, SerializingSection};
|
||||||
use library::Library;
|
use crate::library::Library;
|
||||||
use taxonomies::{Taxonomy, TaxonomyItem};
|
use crate::taxonomies::{Taxonomy, TaxonomyItem};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
enum PaginationRoot<'a> {
|
enum PaginationRoot<'a> {
|
||||||
|
@ -137,7 +138,11 @@ impl<'a> Paginator<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let page_path = format!("{}/{}/", self.paginate_path, index + 1);
|
let page_path = if self.paginate_path.is_empty() {
|
||||||
|
format!("{}/", index + 1)
|
||||||
|
} else {
|
||||||
|
format!("{}/{}/", self.paginate_path, index + 1)
|
||||||
|
};
|
||||||
let permalink = format!("{}{}", self.permalink, page_path);
|
let permalink = format!("{}{}", self.permalink, page_path);
|
||||||
|
|
||||||
let pager_path = if self.is_index {
|
let pager_path = if self.is_index {
|
||||||
|
@ -185,12 +190,15 @@ impl<'a> Paginator<'a> {
|
||||||
paginator.insert("next", Value::Null);
|
paginator.insert("next", Value::Null);
|
||||||
}
|
}
|
||||||
paginator.insert("number_pagers", to_value(&self.pagers.len()).unwrap());
|
paginator.insert("number_pagers", to_value(&self.pagers.len()).unwrap());
|
||||||
paginator.insert(
|
let base_url = if self.paginate_path.is_empty() {
|
||||||
"base_url",
|
self.permalink.to_string()
|
||||||
to_value(&format!("{}{}/", self.permalink, self.paginate_path)).unwrap(),
|
} else {
|
||||||
);
|
format!("{}{}/", self.permalink, self.paginate_path)
|
||||||
|
};
|
||||||
|
paginator.insert("base_url", to_value(&base_url).unwrap());
|
||||||
paginator.insert("pages", to_value(¤t_pager.pages).unwrap());
|
paginator.insert("pages", to_value(¤t_pager.pages).unwrap());
|
||||||
paginator.insert("current_index", to_value(current_pager.index).unwrap());
|
paginator.insert("current_index", to_value(current_pager.index).unwrap());
|
||||||
|
paginator.insert("total_pages", to_value(self.all_pages.len()).unwrap());
|
||||||
|
|
||||||
paginator
|
paginator
|
||||||
}
|
}
|
||||||
|
@ -230,11 +238,11 @@ mod tests {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tera::to_value;
|
use tera::to_value;
|
||||||
|
|
||||||
|
use crate::content::{Page, Section};
|
||||||
|
use crate::library::Library;
|
||||||
|
use crate::taxonomies::{Taxonomy, TaxonomyItem};
|
||||||
use config::Taxonomy as TaxonomyConfig;
|
use config::Taxonomy as TaxonomyConfig;
|
||||||
use content::{Page, Section};
|
|
||||||
use front_matter::SectionFrontMatter;
|
use front_matter::SectionFrontMatter;
|
||||||
use library::Library;
|
|
||||||
use taxonomies::{Taxonomy, TaxonomyItem};
|
|
||||||
|
|
||||||
use super::Paginator;
|
use super::Paginator;
|
||||||
|
|
||||||
|
@ -323,6 +331,7 @@ mod tests {
|
||||||
assert_eq!(context["next"], to_value::<Option<()>>(None).unwrap());
|
assert_eq!(context["next"], to_value::<Option<()>>(None).unwrap());
|
||||||
assert_eq!(context["previous"], to_value("https://vincent.is/posts/").unwrap());
|
assert_eq!(context["previous"], to_value("https://vincent.is/posts/").unwrap());
|
||||||
assert_eq!(context["current_index"], to_value(2).unwrap());
|
assert_eq!(context["current_index"], to_value(2).unwrap());
|
||||||
|
assert_eq!(context["total_pages"], to_value(4).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -353,4 +362,26 @@ mod tests {
|
||||||
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/tags/something/page/2/");
|
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/tags/something/page/2/");
|
||||||
assert_eq!(paginator.pagers[1].path, "tags/something/page/2/");
|
assert_eq!(paginator.pagers[1].path, "tags/something/page/2/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/getzola/zola/issues/866
|
||||||
|
#[test]
|
||||||
|
fn works_with_empty_paginate_path() {
|
||||||
|
let (mut section, library) = create_library(false);
|
||||||
|
section.meta.paginate_path = String::new();
|
||||||
|
let paginator = Paginator::from_section(§ion, &library);
|
||||||
|
assert_eq!(paginator.pagers.len(), 2);
|
||||||
|
|
||||||
|
assert_eq!(paginator.pagers[0].index, 1);
|
||||||
|
assert_eq!(paginator.pagers[0].pages.len(), 2);
|
||||||
|
assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/posts/");
|
||||||
|
assert_eq!(paginator.pagers[0].path, "posts/");
|
||||||
|
|
||||||
|
assert_eq!(paginator.pagers[1].index, 2);
|
||||||
|
assert_eq!(paginator.pagers[1].pages.len(), 2);
|
||||||
|
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/posts/2/");
|
||||||
|
assert_eq!(paginator.pagers[1].path, "posts/2/");
|
||||||
|
|
||||||
|
let context = paginator.build_paginator_context(&paginator.pagers[0]);
|
||||||
|
assert_eq!(context["base_url"], to_value("https://vincent.is/posts/").unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use chrono::NaiveDateTime;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use slotmap::DefaultKey;
|
use slotmap::DefaultKey;
|
||||||
|
|
||||||
use content::Page;
|
use crate::content::Page;
|
||||||
|
|
||||||
/// Used by the RSS feed
|
/// Used by the RSS feed
|
||||||
/// There to not have to import sorting stuff in the site crate
|
/// There to not have to import sorting stuff in the site crate
|
||||||
|
@ -21,7 +21,9 @@ pub fn sort_actual_pages_by_date(a: &&Page, b: &&Page) -> Ordering {
|
||||||
/// Takes a list of (page key, date, permalink) and sort them by dates if possible
|
/// Takes a list of (page key, date, permalink) and sort them by dates if possible
|
||||||
/// Pages without date will be put in the unsortable bucket
|
/// Pages without date will be put in the unsortable bucket
|
||||||
/// The permalink is used to break ties
|
/// The permalink is used to break ties
|
||||||
pub fn sort_pages_by_date(pages: Vec<(&DefaultKey, Option<NaiveDateTime>, &str)>) -> (Vec<DefaultKey>, Vec<DefaultKey>) {
|
pub fn sort_pages_by_date(
|
||||||
|
pages: Vec<(&DefaultKey, Option<NaiveDateTime>, &str)>,
|
||||||
|
) -> (Vec<DefaultKey>, Vec<DefaultKey>) {
|
||||||
let (mut can_be_sorted, cannot_be_sorted): (Vec<_>, Vec<_>) =
|
let (mut can_be_sorted, cannot_be_sorted): (Vec<_>, Vec<_>) =
|
||||||
pages.into_par_iter().partition(|page| page.1.is_some());
|
pages.into_par_iter().partition(|page| page.1.is_some());
|
||||||
|
|
||||||
|
@ -40,7 +42,9 @@ pub fn sort_pages_by_date(pages: Vec<(&DefaultKey, Option<NaiveDateTime>, &str)>
|
||||||
/// Takes a list of (page key, weight, permalink) and sort them by weight if possible
|
/// Takes a list of (page key, weight, permalink) and sort them by weight if possible
|
||||||
/// Pages without weight will be put in the unsortable bucket
|
/// Pages without weight will be put in the unsortable bucket
|
||||||
/// The permalink is used to break ties
|
/// The permalink is used to break ties
|
||||||
pub fn sort_pages_by_weight(pages: Vec<(&DefaultKey, Option<usize>, &str)>) -> (Vec<DefaultKey>, Vec<DefaultKey>) {
|
pub fn sort_pages_by_weight(
|
||||||
|
pages: Vec<(&DefaultKey, Option<usize>, &str)>,
|
||||||
|
) -> (Vec<DefaultKey>, Vec<DefaultKey>) {
|
||||||
let (mut can_be_sorted, cannot_be_sorted): (Vec<_>, Vec<_>) =
|
let (mut can_be_sorted, cannot_be_sorted): (Vec<_>, Vec<_>) =
|
||||||
pages.into_par_iter().partition(|page| page.1.is_some());
|
pages.into_par_iter().partition(|page| page.1.is_some());
|
||||||
|
|
||||||
|
@ -57,7 +61,9 @@ pub fn sort_pages_by_weight(pages: Vec<(&DefaultKey, Option<usize>, &str)>) -> (
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the lighter/heavier and earlier/later pages for all pages having a date/weight
|
/// Find the lighter/heavier and earlier/later pages for all pages having a date/weight
|
||||||
pub fn find_siblings(sorted: &[DefaultKey]) -> Vec<(DefaultKey, Option<DefaultKey>, Option<DefaultKey>)> {
|
pub fn find_siblings(
|
||||||
|
sorted: &[DefaultKey],
|
||||||
|
) -> Vec<(DefaultKey, Option<DefaultKey>, Option<DefaultKey>)> {
|
||||||
let mut res = Vec::with_capacity(sorted.len());
|
let mut res = Vec::with_capacity(sorted.len());
|
||||||
let length = sorted.len();
|
let length = sorted.len();
|
||||||
|
|
||||||
|
@ -85,7 +91,7 @@ mod tests {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::{find_siblings, sort_pages_by_date, sort_pages_by_weight};
|
use super::{find_siblings, sort_pages_by_date, sort_pages_by_weight};
|
||||||
use content::Page;
|
use crate::content::Page;
|
||||||
use front_matter::PageFrontMatter;
|
use front_matter::PageFrontMatter;
|
||||||
|
|
||||||
fn create_page_with_date(date: &str) -> Page {
|
fn create_page_with_date(date: &str) -> Page {
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde_derive::Serialize;
|
||||||
use slotmap::DefaultKey;
|
use slotmap::DefaultKey;
|
||||||
use slug::slugify;
|
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
|
|
||||||
use config::{Config, Taxonomy as TaxonomyConfig};
|
use config::{Config, Taxonomy as TaxonomyConfig};
|
||||||
use errors::{Error, Result};
|
use errors::{bail, Error, Result};
|
||||||
use utils::templates::render_template;
|
use utils::templates::render_template;
|
||||||
|
|
||||||
use content::SerializingPage;
|
use crate::content::SerializingPage;
|
||||||
use library::Library;
|
use crate::library::Library;
|
||||||
use sorting::sort_pages_by_date;
|
use crate::sorting::sort_pages_by_date;
|
||||||
|
use utils::slugs::slugify_paths;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
pub struct SerializedTaxonomyItem<'a> {
|
pub struct SerializedTaxonomyItem<'a> {
|
||||||
|
@ -69,7 +70,7 @@ impl TaxonomyItem {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let (mut pages, ignored_pages) = sort_pages_by_date(data);
|
let (mut pages, ignored_pages) = sort_pages_by_date(data);
|
||||||
let slug = slugify(name);
|
let slug = slugify_paths(name, config.slugify.taxonomies);
|
||||||
let permalink = if taxonomy.lang != config.default_language {
|
let permalink = if taxonomy.lang != config.default_language {
|
||||||
config.make_permalink(&format!("/{}/{}/{}", taxonomy.lang, taxonomy.name, slug))
|
config.make_permalink(&format!("/{}/{}/{}", taxonomy.lang, taxonomy.name, slug))
|
||||||
} else {
|
} else {
|
||||||
|
@ -169,7 +170,6 @@ impl Taxonomy {
|
||||||
self.items.iter().map(|i| SerializedTaxonomyItem::from_item(i, library)).collect();
|
self.items.iter().map(|i| SerializedTaxonomyItem::from_item(i, library)).collect();
|
||||||
context.insert("terms", &terms);
|
context.insert("terms", &terms);
|
||||||
context.insert("taxonomy", &self.kind);
|
context.insert("taxonomy", &self.kind);
|
||||||
context.insert("lang", &self.kind.lang);
|
|
||||||
context.insert("current_url", &config.make_permalink(&self.kind.name));
|
context.insert("current_url", &config.make_permalink(&self.kind.name));
|
||||||
context.insert("current_path", &self.kind.name);
|
context.insert("current_path", &self.kind.name);
|
||||||
|
|
||||||
|
@ -232,9 +232,10 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::content::Page;
|
||||||
|
use crate::library::Library;
|
||||||
use config::{Config, Language, Taxonomy as TaxonomyConfig};
|
use config::{Config, Language, Taxonomy as TaxonomyConfig};
|
||||||
use content::Page;
|
use utils::slugs::SlugifyStrategy;
|
||||||
use library::Library;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_make_taxonomies() {
|
fn can_make_taxonomies() {
|
||||||
|
@ -331,6 +332,101 @@ mod tests {
|
||||||
assert_eq!(categories.items[1].pages.len(), 1);
|
assert_eq!(categories.items[1].pages.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_make_slugified_taxonomies() {
|
||||||
|
let mut config = Config::default();
|
||||||
|
let mut library = Library::new(2, 0, false);
|
||||||
|
|
||||||
|
config.taxonomies = vec![
|
||||||
|
TaxonomyConfig {
|
||||||
|
name: "categories".to_string(),
|
||||||
|
lang: config.default_language.clone(),
|
||||||
|
..TaxonomyConfig::default()
|
||||||
|
},
|
||||||
|
TaxonomyConfig {
|
||||||
|
name: "tags".to_string(),
|
||||||
|
lang: config.default_language.clone(),
|
||||||
|
..TaxonomyConfig::default()
|
||||||
|
},
|
||||||
|
TaxonomyConfig {
|
||||||
|
name: "authors".to_string(),
|
||||||
|
lang: config.default_language.clone(),
|
||||||
|
..TaxonomyConfig::default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut page1 = Page::default();
|
||||||
|
let mut taxo_page1 = HashMap::new();
|
||||||
|
taxo_page1.insert("tags".to_string(), vec!["rust".to_string(), "db".to_string()]);
|
||||||
|
taxo_page1.insert("categories".to_string(), vec!["Programming tutorials".to_string()]);
|
||||||
|
page1.meta.taxonomies = taxo_page1;
|
||||||
|
page1.lang = config.default_language.clone();
|
||||||
|
library.insert_page(page1);
|
||||||
|
|
||||||
|
let mut page2 = Page::default();
|
||||||
|
let mut taxo_page2 = HashMap::new();
|
||||||
|
taxo_page2.insert("tags".to_string(), vec!["rust".to_string(), "js".to_string()]);
|
||||||
|
taxo_page2.insert("categories".to_string(), vec!["Other".to_string()]);
|
||||||
|
page2.meta.taxonomies = taxo_page2;
|
||||||
|
page2.lang = config.default_language.clone();
|
||||||
|
library.insert_page(page2);
|
||||||
|
|
||||||
|
let mut page3 = Page::default();
|
||||||
|
let mut taxo_page3 = HashMap::new();
|
||||||
|
taxo_page3.insert("tags".to_string(), vec!["js".to_string()]);
|
||||||
|
taxo_page3.insert("authors".to_string(), vec!["Vincent Prouillet".to_string()]);
|
||||||
|
page3.meta.taxonomies = taxo_page3;
|
||||||
|
page3.lang = config.default_language.clone();
|
||||||
|
library.insert_page(page3);
|
||||||
|
|
||||||
|
let taxonomies = find_taxonomies(&config, &library).unwrap();
|
||||||
|
let (tags, categories, authors) = {
|
||||||
|
let mut t = None;
|
||||||
|
let mut c = None;
|
||||||
|
let mut a = None;
|
||||||
|
for x in taxonomies {
|
||||||
|
match x.kind.name.as_ref() {
|
||||||
|
"tags" => t = Some(x),
|
||||||
|
"categories" => c = Some(x),
|
||||||
|
"authors" => a = Some(x),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(t.unwrap(), c.unwrap(), a.unwrap())
|
||||||
|
};
|
||||||
|
assert_eq!(tags.items.len(), 3);
|
||||||
|
assert_eq!(categories.items.len(), 2);
|
||||||
|
assert_eq!(authors.items.len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(tags.items[0].name, "db");
|
||||||
|
assert_eq!(tags.items[0].slug, "db");
|
||||||
|
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/db/");
|
||||||
|
assert_eq!(tags.items[0].pages.len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(tags.items[1].name, "js");
|
||||||
|
assert_eq!(tags.items[1].slug, "js");
|
||||||
|
assert_eq!(tags.items[1].permalink, "http://a-website.com/tags/js/");
|
||||||
|
assert_eq!(tags.items[1].pages.len(), 2);
|
||||||
|
|
||||||
|
assert_eq!(tags.items[2].name, "rust");
|
||||||
|
assert_eq!(tags.items[2].slug, "rust");
|
||||||
|
assert_eq!(tags.items[2].permalink, "http://a-website.com/tags/rust/");
|
||||||
|
assert_eq!(tags.items[2].pages.len(), 2);
|
||||||
|
|
||||||
|
assert_eq!(categories.items[0].name, "Other");
|
||||||
|
assert_eq!(categories.items[0].slug, "other");
|
||||||
|
assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/other/");
|
||||||
|
assert_eq!(categories.items[0].pages.len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(categories.items[1].name, "Programming tutorials");
|
||||||
|
assert_eq!(categories.items[1].slug, "programming-tutorials");
|
||||||
|
assert_eq!(
|
||||||
|
categories.items[1].permalink,
|
||||||
|
"http://a-website.com/categories/programming-tutorials/"
|
||||||
|
);
|
||||||
|
assert_eq!(categories.items[1].pages.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn errors_on_unknown_taxonomy() {
|
fn errors_on_unknown_taxonomy() {
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
|
@ -466,4 +562,151 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(categories.items[1].pages.len(), 1);
|
assert_eq!(categories.items[1].pages.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_make_utf8_taxonomies() {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.slugify.taxonomies = SlugifyStrategy::Safe;
|
||||||
|
config.languages.push(Language {
|
||||||
|
rss: false,
|
||||||
|
code: "fr".to_string(),
|
||||||
|
..Language::default()
|
||||||
|
});
|
||||||
|
let mut library = Library::new(2, 0, true);
|
||||||
|
|
||||||
|
config.taxonomies = vec![TaxonomyConfig {
|
||||||
|
name: "catégories".to_string(),
|
||||||
|
lang: "fr".to_string(),
|
||||||
|
..TaxonomyConfig::default()
|
||||||
|
}];
|
||||||
|
|
||||||
|
let mut page = Page::default();
|
||||||
|
page.lang = "fr".to_string();
|
||||||
|
let mut taxo_page = HashMap::new();
|
||||||
|
taxo_page.insert("catégories".to_string(), vec!["Écologie".to_string()]);
|
||||||
|
page.meta.taxonomies = taxo_page;
|
||||||
|
library.insert_page(page);
|
||||||
|
|
||||||
|
let taxonomies = find_taxonomies(&config, &library).unwrap();
|
||||||
|
let categories = &taxonomies[0];
|
||||||
|
|
||||||
|
assert_eq!(categories.items.len(), 1);
|
||||||
|
assert_eq!(categories.items[0].name, "Écologie");
|
||||||
|
assert_eq!(categories.items[0].permalink, "http://a-website.com/fr/catégories/Écologie/");
|
||||||
|
assert_eq!(categories.items[0].pages.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_make_slugified_taxonomies_in_multiple_languages() {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.slugify.taxonomies = SlugifyStrategy::On;
|
||||||
|
config.languages.push(Language {
|
||||||
|
rss: false,
|
||||||
|
code: "fr".to_string(),
|
||||||
|
..Language::default()
|
||||||
|
});
|
||||||
|
let mut library = Library::new(2, 0, true);
|
||||||
|
|
||||||
|
config.taxonomies = vec![
|
||||||
|
TaxonomyConfig {
|
||||||
|
name: "categories".to_string(),
|
||||||
|
lang: config.default_language.clone(),
|
||||||
|
..TaxonomyConfig::default()
|
||||||
|
},
|
||||||
|
TaxonomyConfig {
|
||||||
|
name: "tags".to_string(),
|
||||||
|
lang: config.default_language.clone(),
|
||||||
|
..TaxonomyConfig::default()
|
||||||
|
},
|
||||||
|
TaxonomyConfig {
|
||||||
|
name: "auteurs".to_string(),
|
||||||
|
lang: "fr".to_string(),
|
||||||
|
..TaxonomyConfig::default()
|
||||||
|
},
|
||||||
|
TaxonomyConfig {
|
||||||
|
name: "tags".to_string(),
|
||||||
|
lang: "fr".to_string(),
|
||||||
|
..TaxonomyConfig::default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut page1 = Page::default();
|
||||||
|
let mut taxo_page1 = HashMap::new();
|
||||||
|
taxo_page1.insert("tags".to_string(), vec!["rust".to_string(), "db".to_string()]);
|
||||||
|
taxo_page1.insert("categories".to_string(), vec!["Programming tutorials".to_string()]);
|
||||||
|
page1.meta.taxonomies = taxo_page1;
|
||||||
|
page1.lang = config.default_language.clone();
|
||||||
|
library.insert_page(page1);
|
||||||
|
|
||||||
|
let mut page2 = Page::default();
|
||||||
|
let mut taxo_page2 = HashMap::new();
|
||||||
|
taxo_page2.insert("tags".to_string(), vec!["rust".to_string()]);
|
||||||
|
taxo_page2.insert("categories".to_string(), vec!["Other".to_string()]);
|
||||||
|
page2.meta.taxonomies = taxo_page2;
|
||||||
|
page2.lang = config.default_language.clone();
|
||||||
|
library.insert_page(page2);
|
||||||
|
|
||||||
|
let mut page3 = Page::default();
|
||||||
|
page3.lang = "fr".to_string();
|
||||||
|
let mut taxo_page3 = HashMap::new();
|
||||||
|
taxo_page3.insert("tags".to_string(), vec!["rust".to_string()]);
|
||||||
|
taxo_page3.insert("auteurs".to_string(), vec!["Vincent Prouillet".to_string()]);
|
||||||
|
page3.meta.taxonomies = taxo_page3;
|
||||||
|
library.insert_page(page3);
|
||||||
|
|
||||||
|
let taxonomies = find_taxonomies(&config, &library).unwrap();
|
||||||
|
let (tags, categories, authors) = {
|
||||||
|
let mut t = None;
|
||||||
|
let mut c = None;
|
||||||
|
let mut a = None;
|
||||||
|
for x in taxonomies {
|
||||||
|
match x.kind.name.as_ref() {
|
||||||
|
"tags" => {
|
||||||
|
if x.kind.lang == "en" {
|
||||||
|
t = Some(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"categories" => c = Some(x),
|
||||||
|
"auteurs" => a = Some(x),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(t.unwrap(), c.unwrap(), a.unwrap())
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(tags.items.len(), 2);
|
||||||
|
assert_eq!(categories.items.len(), 2);
|
||||||
|
assert_eq!(authors.items.len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(tags.items[0].name, "db");
|
||||||
|
assert_eq!(tags.items[0].slug, "db");
|
||||||
|
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/db/");
|
||||||
|
assert_eq!(tags.items[0].pages.len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(tags.items[1].name, "rust");
|
||||||
|
assert_eq!(tags.items[1].slug, "rust");
|
||||||
|
assert_eq!(tags.items[1].permalink, "http://a-website.com/tags/rust/");
|
||||||
|
assert_eq!(tags.items[1].pages.len(), 2);
|
||||||
|
|
||||||
|
assert_eq!(authors.items[0].name, "Vincent Prouillet");
|
||||||
|
assert_eq!(authors.items[0].slug, "vincent-prouillet");
|
||||||
|
assert_eq!(
|
||||||
|
authors.items[0].permalink,
|
||||||
|
"http://a-website.com/fr/auteurs/vincent-prouillet/"
|
||||||
|
);
|
||||||
|
assert_eq!(authors.items[0].pages.len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(categories.items[0].name, "Other");
|
||||||
|
assert_eq!(categories.items[0].slug, "other");
|
||||||
|
assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/other/");
|
||||||
|
assert_eq!(categories.items[0].pages.len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(categories.items[1].name, "Programming tutorials");
|
||||||
|
assert_eq!(categories.items[1].slug, "programming-tutorials");
|
||||||
|
assert_eq!(
|
||||||
|
categories.items[1].permalink,
|
||||||
|
"http://a-website.com/categories/programming-tutorials/"
|
||||||
|
);
|
||||||
|
assert_eq!(categories.items[1].pages.len(), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,14 @@
|
||||||
name = "link_checker"
|
name = "link_checker"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
reqwest = "0.9"
|
reqwest = { version = "0.10", features = ["blocking", "rustls-tls"] }
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
|
||||||
|
config = { path = "../config" }
|
||||||
errors = { path = "../errors" }
|
errors = { path = "../errors" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
mockito = "0.23"
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
extern crate reqwest;
|
use lazy_static::lazy_static;
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
extern crate errors;
|
|
||||||
|
|
||||||
use reqwest::header::{HeaderMap, ACCEPT};
|
use reqwest::header::{HeaderMap, ACCEPT};
|
||||||
use reqwest::StatusCode;
|
use reqwest::{blocking::Client, StatusCode};
|
||||||
|
|
||||||
|
use config::LinkChecker;
|
||||||
use errors::Result;
|
use errors::Result;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -27,7 +22,7 @@ impl LinkResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(c) = self.code {
|
if let Some(c) = self.code {
|
||||||
return c.is_success();
|
return c.is_success() || c == StatusCode::NOT_MODIFIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
@ -51,7 +46,7 @@ lazy_static! {
|
||||||
static ref LINKS: Arc<RwLock<HashMap<String, LinkResult>>> = Arc::new(RwLock::new(HashMap::new()));
|
static ref LINKS: Arc<RwLock<HashMap<String, LinkResult>>> = Arc::new(RwLock::new(HashMap::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_url(url: &str) -> LinkResult {
|
pub fn check_url(url: &str, config: &LinkChecker) -> LinkResult {
|
||||||
{
|
{
|
||||||
let guard = LINKS.read().unwrap();
|
let guard = LINKS.read().unwrap();
|
||||||
if let Some(res) = guard.get(url) {
|
if let Some(res) = guard.get(url) {
|
||||||
|
@ -63,18 +58,44 @@ pub fn check_url(url: &str) -> LinkResult {
|
||||||
headers.insert(ACCEPT, "text/html".parse().unwrap());
|
headers.insert(ACCEPT, "text/html".parse().unwrap());
|
||||||
headers.append(ACCEPT, "*/*".parse().unwrap());
|
headers.append(ACCEPT, "*/*".parse().unwrap());
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = Client::new();
|
||||||
|
|
||||||
|
let check_anchor = !config.skip_anchor_prefixes.iter().any(|prefix| url.starts_with(prefix));
|
||||||
|
|
||||||
// Need to actually do the link checking
|
// Need to actually do the link checking
|
||||||
let res = match client.get(url).headers(headers).send() {
|
let res = match client.get(url).headers(headers).send() {
|
||||||
Ok(ref mut response) if has_anchor(url) => {
|
Ok(ref mut response) if check_anchor && has_anchor(url) => {
|
||||||
match check_page_for_anchor(url, response.text()) {
|
let body = {
|
||||||
|
let mut buf: Vec<u8> = vec![];
|
||||||
|
response.copy_to(&mut buf).unwrap();
|
||||||
|
String::from_utf8(buf).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
match check_page_for_anchor(url, body) {
|
||||||
Ok(_) => LinkResult { code: Some(response.status()), error: None },
|
Ok(_) => LinkResult { code: Some(response.status()), error: None },
|
||||||
Err(e) => LinkResult { code: None, error: Some(e.to_string()) },
|
Err(e) => LinkResult { code: None, error: Some(e.to_string()) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(response) => LinkResult { code: Some(response.status()), error: None },
|
Ok(response) => {
|
||||||
Err(e) => LinkResult { code: None, error: Some(e.description().to_string()) },
|
if response.status().is_success() || response.status() == StatusCode::NOT_MODIFIED {
|
||||||
|
LinkResult { code: Some(response.status()), error: None }
|
||||||
|
} else {
|
||||||
|
let error_string = if response.status().is_informational() {
|
||||||
|
format!("Informational status code ({}) received", response.status())
|
||||||
|
} else if response.status().is_redirection() {
|
||||||
|
format!("Redirection status code ({}) received", response.status())
|
||||||
|
} else if response.status().is_client_error() {
|
||||||
|
format!("Client error status code ({}) received", response.status())
|
||||||
|
} else if response.status().is_server_error() {
|
||||||
|
format!("Server error status code ({}) received", response.status())
|
||||||
|
} else {
|
||||||
|
format!("Non-success status code ({}) received", response.status())
|
||||||
|
};
|
||||||
|
|
||||||
|
LinkResult { code: None, error: Some(error_string) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => LinkResult { code: None, error: Some(e.to_string()) },
|
||||||
};
|
};
|
||||||
|
|
||||||
LINKS.write().unwrap().insert(url.to_string(), res.clone());
|
LINKS.write().unwrap().insert(url.to_string(), res.clone());
|
||||||
|
@ -91,8 +112,7 @@ fn has_anchor(url: &str) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_page_for_anchor(url: &str, body: reqwest::Result<String>) -> Result<()> {
|
fn check_page_for_anchor(url: &str, body: String) -> Result<()> {
|
||||||
let body = body.unwrap();
|
|
||||||
let index = url.find('#').unwrap();
|
let index = url.find('#').unwrap();
|
||||||
let anchor = url.get(index + 1..).unwrap();
|
let anchor = url.get(index + 1..).unwrap();
|
||||||
let checks: [String; 4] = [
|
let checks: [String; 4] = [
|
||||||
|
@ -111,21 +131,115 @@ fn check_page_for_anchor(url: &str, body: reqwest::Result<String>) -> Result<()>
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{check_page_for_anchor, check_url, has_anchor, LINKS};
|
use super::{check_page_for_anchor, check_url, has_anchor, LinkChecker, LINKS};
|
||||||
|
use mockito::mock;
|
||||||
|
|
||||||
|
// NOTE: HTTP mock paths below are randomly generated to avoid name
|
||||||
|
// collisions. Mocks with the same path can sometimes bleed between tests
|
||||||
|
// and cause them to randomly pass/fail. Please make sure to use unique
|
||||||
|
// paths when adding or modifying tests that use Mockito.
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_validate_ok_links() {
|
fn can_validate_ok_links() {
|
||||||
let url = "https://google.com";
|
let url = format!("{}{}", mockito::server_url(), "/ekbtwxfhjw");
|
||||||
let res = check_url(url);
|
let _m = mock("GET", "/ekbtwxfhjw")
|
||||||
|
.with_header("Content-Type", "text/html")
|
||||||
|
.with_body(format!(
|
||||||
|
r#"<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="{}">Mock URL</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#,
|
||||||
|
url
|
||||||
|
))
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let res = check_url(&url, &LinkChecker::default());
|
||||||
assert!(res.is_valid());
|
assert!(res.is_valid());
|
||||||
assert!(LINKS.read().unwrap().get(url).is_some());
|
assert!(LINKS.read().unwrap().get(&url).is_some());
|
||||||
let res = check_url(url);
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_follow_301_links() {
|
||||||
|
let _m1 = mock("GET", "/c7qrtrv3zz")
|
||||||
|
.with_status(301)
|
||||||
|
.with_header("Content-Type", "text/plain")
|
||||||
|
.with_header("Location", format!("{}/rbs5avjs8e", mockito::server_url()).as_str())
|
||||||
|
.with_body("Redirecting...")
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let _m2 = mock("GET", "/rbs5avjs8e")
|
||||||
|
.with_header("Content-Type", "text/plain")
|
||||||
|
.with_body("Test")
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let url = format!("{}{}", mockito::server_url(), "/c7qrtrv3zz");
|
||||||
|
let res = check_url(&url, &LinkChecker::default());
|
||||||
assert!(res.is_valid());
|
assert!(res.is_valid());
|
||||||
|
assert!(res.code.is_some());
|
||||||
|
assert!(res.error.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_fail_301_to_404_links() {
|
||||||
|
let _m1 = mock("GET", "/cav9vibhsc")
|
||||||
|
.with_status(301)
|
||||||
|
.with_header("Content-Type", "text/plain")
|
||||||
|
.with_header("Location", format!("{}/72zmfg4smd", mockito::server_url()).as_str())
|
||||||
|
.with_body("Redirecting...")
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let _m2 = mock("GET", "/72zmfg4smd")
|
||||||
|
.with_status(404)
|
||||||
|
.with_header("Content-Type", "text/plain")
|
||||||
|
.with_body("Not Found")
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let url = format!("{}{}", mockito::server_url(), "/cav9vibhsc");
|
||||||
|
let res = check_url(&url, &LinkChecker::default());
|
||||||
|
assert_eq!(res.is_valid(), false);
|
||||||
|
assert!(res.code.is_none());
|
||||||
|
assert!(res.error.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_fail_404_links() {
|
fn can_fail_404_links() {
|
||||||
let res = check_url("https://google.comys");
|
let _m = mock("GET", "/nlhab9c1vc")
|
||||||
|
.with_status(404)
|
||||||
|
.with_header("Content-Type", "text/plain")
|
||||||
|
.with_body("Not Found")
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let url = format!("{}{}", mockito::server_url(), "/nlhab9c1vc");
|
||||||
|
let res = check_url(&url, &LinkChecker::default());
|
||||||
|
assert_eq!(res.is_valid(), false);
|
||||||
|
assert!(res.code.is_none());
|
||||||
|
assert!(res.error.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_fail_500_links() {
|
||||||
|
let _m = mock("GET", "/qdbrssazes")
|
||||||
|
.with_status(500)
|
||||||
|
.with_header("Content-Type", "text/plain")
|
||||||
|
.with_body("Internal Server Error")
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let url = format!("{}{}", mockito::server_url(), "/qdbrssazes");
|
||||||
|
let res = check_url(&url, &LinkChecker::default());
|
||||||
|
assert_eq!(res.is_valid(), false);
|
||||||
|
assert!(res.code.is_none());
|
||||||
|
assert!(res.error.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_fail_unresolved_links() {
|
||||||
|
let res = check_url("https://t6l5cn9lpm.lxizfnzckd", &LinkChecker::default());
|
||||||
assert_eq!(res.is_valid(), false);
|
assert_eq!(res.is_valid(), false);
|
||||||
assert!(res.code.is_none());
|
assert!(res.code.is_none());
|
||||||
assert!(res.error.is_some());
|
assert!(res.error.is_some());
|
||||||
|
@ -134,8 +248,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn can_validate_anchors() {
|
fn can_validate_anchors() {
|
||||||
let url = "https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect";
|
let url = "https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect";
|
||||||
let body = "<body><h3 id='method.collect'>collect</h3></body>".to_string();
|
let body = r#"<body><h3 id="method.collect">collect</h3></body>"#.to_string();
|
||||||
let res = check_page_for_anchor(url, Ok(body));
|
let res = check_page_for_anchor(url, body);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +257,7 @@ mod tests {
|
||||||
fn can_validate_anchors_with_other_quotes() {
|
fn can_validate_anchors_with_other_quotes() {
|
||||||
let url = "https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect";
|
let url = "https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect";
|
||||||
let body = r#"<body><h3 id="method.collect">collect</h3></body>"#.to_string();
|
let body = r#"<body><h3 id="method.collect">collect</h3></body>"#.to_string();
|
||||||
let res = check_page_for_anchor(url, Ok(body));
|
let res = check_page_for_anchor(url, body);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,15 +265,15 @@ mod tests {
|
||||||
fn can_validate_anchors_with_name_attr() {
|
fn can_validate_anchors_with_name_attr() {
|
||||||
let url = "https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect";
|
let url = "https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect";
|
||||||
let body = r#"<body><h3 name="method.collect">collect</h3></body>"#.to_string();
|
let body = r#"<body><h3 name="method.collect">collect</h3></body>"#.to_string();
|
||||||
let res = check_page_for_anchor(url, Ok(body));
|
let res = check_page_for_anchor(url, body);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_fail_when_anchor_not_found() {
|
fn can_fail_when_anchor_not_found() {
|
||||||
let url = "https://doc.rust-lang.org/std/iter/trait.Iterator.html#me";
|
let url = "https://doc.rust-lang.org/std/iter/trait.Iterator.html#me";
|
||||||
let body = "<body><h3 id='method.collect'>collect</h3></body>".to_string();
|
let body = r#"<body><h3 id="method.collect">collect</h3></body>"#.to_string();
|
||||||
let res = check_page_for_anchor(url, Ok(body));
|
let res = check_page_for_anchor(url, body);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,4 +304,53 @@ mod tests {
|
||||||
let res = has_anchor(url);
|
let res = has_anchor(url);
|
||||||
assert_eq!(res, false);
|
assert_eq!(res, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn skip_anchor_prefixes() {
|
||||||
|
let ignore_url = format!("{}{}", mockito::server_url(), "/ignore/");
|
||||||
|
let config = LinkChecker { skip_prefixes: vec![], skip_anchor_prefixes: vec![ignore_url] };
|
||||||
|
|
||||||
|
let _m1 = mock("GET", "/ignore/i30hobj1cy")
|
||||||
|
.with_header("Content-Type", "text/html")
|
||||||
|
.with_body(
|
||||||
|
r#"<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Ignore</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p id="existent"></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
// anchor check is ignored because the url matches the prefix
|
||||||
|
let ignore = format!("{}{}", mockito::server_url(), "/ignore/i30hobj1cy#nonexistent");
|
||||||
|
assert!(check_url(&ignore, &config).is_valid());
|
||||||
|
|
||||||
|
let _m2 = mock("GET", "/guvqcqwmth")
|
||||||
|
.with_header("Content-Type", "text/html")
|
||||||
|
.with_body(
|
||||||
|
r#"<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p id="existent"></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
// other anchors are checked
|
||||||
|
let existent = format!("{}{}", mockito::server_url(), "/guvqcqwmth#existent");
|
||||||
|
assert!(check_url(&existent, &config).is_valid());
|
||||||
|
|
||||||
|
let nonexistent = format!("{}{}", mockito::server_url(), "/guvqcqwmth#nonexistent");
|
||||||
|
assert_eq!(check_url(&nonexistent, &config).is_valid(), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
name = "rebuild"
|
name = "rebuild"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
errors = { path = "../errors" }
|
errors = { path = "../errors" }
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
extern crate site;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate errors;
|
|
||||||
extern crate front_matter;
|
|
||||||
extern crate library;
|
|
||||||
|
|
||||||
use std::path::{Component, Path};
|
use std::path::{Component, Path};
|
||||||
|
|
||||||
use errors::Result;
|
use errors::{bail, Result};
|
||||||
use front_matter::{PageFrontMatter, SectionFrontMatter};
|
use front_matter::{PageFrontMatter, SectionFrontMatter};
|
||||||
use library::{Page, Section};
|
use library::{Page, Section};
|
||||||
use site::Site;
|
use site::Site;
|
||||||
|
@ -335,7 +329,7 @@ fn is_section(path: &str, languages_codes: &[&str]) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What happens when a section or a page is created/edited
|
/// What happens when a section or a page is created/edited
|
||||||
|
@ -423,7 +417,6 @@ pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
|
||||||
if filename == "anchor-link.html"
|
if filename == "anchor-link.html"
|
||||||
|| path.components().any(|x| x == Component::Normal("shortcodes".as_ref()))
|
|| path.components().any(|x| x == Component::Normal("shortcodes".as_ref()))
|
||||||
{
|
{
|
||||||
println!("Rendering markdown");
|
|
||||||
site.render_markdown()?;
|
site.render_markdown()?;
|
||||||
}
|
}
|
||||||
site.populate_sections();
|
site.populate_sections();
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
extern crate fs_extra;
|
|
||||||
extern crate rebuild;
|
|
||||||
extern crate site;
|
|
||||||
extern crate tempfile;
|
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
name = "rendering"
|
name = "rendering"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tera = { version = "1.0.0-beta.10", features = ["preserve_order"] }
|
tera = { version = "1", features = ["preserve_order"] }
|
||||||
syntect = "=3.2.0"
|
syntect = "=3.2.0"
|
||||||
pulldown-cmark = "0.6"
|
pulldown-cmark = "0.7"
|
||||||
slug = "0.1"
|
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
pest = "2"
|
pest = "2"
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
extern crate tera;
|
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
extern crate config;
|
|
||||||
extern crate front_matter;
|
|
||||||
extern crate rendering;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use front_matter::InsertAnchor;
|
use front_matter::InsertAnchor;
|
||||||
|
@ -92,8 +86,7 @@ fn bench_render_content_with_highlighting(b: &mut test::Bencher) {
|
||||||
tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
|
tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let context =
|
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new(""), InsertAnchor::None);
|
|
||||||
b.iter(|| render_content(CONTENT, &context).unwrap());
|
b.iter(|| render_content(CONTENT, &context).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,8 +97,7 @@ fn bench_render_content_without_highlighting(b: &mut test::Bencher) {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.highlight_code = false;
|
config.highlight_code = false;
|
||||||
let context =
|
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new(""), InsertAnchor::None);
|
|
||||||
b.iter(|| render_content(CONTENT, &context).unwrap());
|
b.iter(|| render_content(CONTENT, &context).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,8 +108,7 @@ fn bench_render_content_no_shortcode(b: &mut test::Bencher) {
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.highlight_code = false;
|
config.highlight_code = false;
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let context =
|
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new(""), InsertAnchor::None);
|
|
||||||
|
|
||||||
b.iter(|| render_content(&content2, &context).unwrap());
|
b.iter(|| render_content(&content2, &context).unwrap());
|
||||||
}
|
}
|
||||||
|
@ -128,8 +119,7 @@ fn bench_render_shortcodes_one_present(b: &mut test::Bencher) {
|
||||||
tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
|
tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let context =
|
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new(""), InsertAnchor::None);
|
|
||||||
|
|
||||||
b.iter(|| render_shortcodes(CONTENT, &context));
|
b.iter(|| render_shortcodes(CONTENT, &context));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,3 @@
|
||||||
extern crate pulldown_cmark;
|
|
||||||
extern crate slug;
|
|
||||||
extern crate syntect;
|
|
||||||
extern crate tera;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate pest;
|
|
||||||
extern crate serde;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate pest_derive;
|
|
||||||
extern crate regex;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate errors;
|
|
||||||
extern crate config;
|
|
||||||
extern crate front_matter;
|
|
||||||
extern crate link_checker;
|
|
||||||
extern crate utils;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate templates;
|
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
mod markdown;
|
mod markdown;
|
||||||
mod shortcode;
|
mod shortcode;
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use pulldown_cmark as cmark;
|
use pulldown_cmark as cmark;
|
||||||
use slug::slugify;
|
use regex::Regex;
|
||||||
use syntect::easy::HighlightLines;
|
use syntect::easy::HighlightLines;
|
||||||
use syntect::html::{
|
use syntect::html::{
|
||||||
start_highlighted_html_snippet, styled_line_to_highlighted_html, IncludeBackground,
|
start_highlighted_html_snippet, styled_line_to_highlighted_html, IncludeBackground,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::context::RenderContext;
|
||||||
|
use crate::table_of_contents::{make_table_of_contents, Heading};
|
||||||
use config::highlighting::{get_highlighter, SYNTAX_SET, THEME_SET};
|
use config::highlighting::{get_highlighter, SYNTAX_SET, THEME_SET};
|
||||||
use context::RenderContext;
|
|
||||||
use errors::{Error, Result};
|
use errors::{Error, Result};
|
||||||
use front_matter::InsertAnchor;
|
use front_matter::InsertAnchor;
|
||||||
use table_of_contents::{make_table_of_contents, Heading};
|
|
||||||
use utils::site::resolve_internal_link;
|
use utils::site::resolve_internal_link;
|
||||||
|
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;
|
||||||
|
|
||||||
const CONTINUE_READING: &str =
|
const CONTINUE_READING: &str = "<span id=\"continue-reading\"></span>";
|
||||||
"<p id=\"zola-continue-reading\"><a name=\"continue-reading\"></a></p>\n";
|
|
||||||
const ANCHOR_LINK_TEMPLATE: &str = "anchor-link.html";
|
const ANCHOR_LINK_TEMPLATE: &str = "anchor-link.html";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -60,11 +62,31 @@ fn find_anchor(anchors: &[String], name: String, level: u8) -> String {
|
||||||
find_anchor(anchors, name, level + 1)
|
find_anchor(anchors, name, level + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns whether the given string starts with a schema.
|
||||||
|
//
|
||||||
|
// Although there exists [a list of registered URI schemes][uri-schemes], a link may use arbitrary,
|
||||||
|
// private schemes. This function checks if the given string starts with something that just looks
|
||||||
|
// like a scheme, i.e., a case-insensitive identifier followed by a colon.
|
||||||
|
//
|
||||||
|
// [uri-schemes]: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
|
||||||
|
fn starts_with_schema(s: &str) -> bool {
|
||||||
|
lazy_static! {
|
||||||
|
static ref PATTERN: Regex = Regex::new(r"^[0-9A-Za-z\-]+:").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
PATTERN.is_match(s)
|
||||||
|
}
|
||||||
|
|
||||||
// Colocated asset links refers to the files in the same directory,
|
// Colocated asset links refers to the files in the same directory,
|
||||||
// there it should be a filename only
|
// there it should be a filename only
|
||||||
fn is_colocated_asset_link(link: &str) -> bool {
|
fn is_colocated_asset_link(link: &str) -> bool {
|
||||||
!link.contains('/') // http://, ftp://, ../ etc
|
!link.contains('/') // http://, ftp://, ../ etc
|
||||||
&& !link.starts_with("mailto:")
|
&& !starts_with_schema(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns whether a link starts with an HTTP(s) scheme.
|
||||||
|
fn is_external_link(link: &str) -> bool {
|
||||||
|
link.starts_with("http:") || link.starts_with("https:")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fix_link(
|
fn fix_link(
|
||||||
|
@ -103,7 +125,7 @@ fn fix_link(
|
||||||
} else if is_colocated_asset_link(&link) {
|
} else if is_colocated_asset_link(&link) {
|
||||||
format!("{}{}", context.current_page_permalink, link)
|
format!("{}{}", context.current_page_permalink, link)
|
||||||
} else {
|
} else {
|
||||||
if !link.starts_with('#') && !link.starts_with("mailto:") {
|
if is_external_link(link) {
|
||||||
external_links.push(link.to_owned());
|
external_links.push(link.to_owned());
|
||||||
}
|
}
|
||||||
link.to_string()
|
link.to_string()
|
||||||
|
@ -162,6 +184,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
|
||||||
let mut has_summary = false;
|
let mut has_summary = false;
|
||||||
opts.insert(Options::ENABLE_TABLES);
|
opts.insert(Options::ENABLE_TABLES);
|
||||||
opts.insert(Options::ENABLE_FOOTNOTES);
|
opts.insert(Options::ENABLE_FOOTNOTES);
|
||||||
|
opts.insert(Options::ENABLE_STRIKETHROUGH);
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut events = Parser::new_ext(content, opts)
|
let mut events = Parser::new_ext(content, opts)
|
||||||
|
@ -189,13 +212,18 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
|
||||||
// Business as usual
|
// Business as usual
|
||||||
Event::Text(text)
|
Event::Text(text)
|
||||||
}
|
}
|
||||||
Event::Start(Tag::CodeBlock(ref info)) => {
|
Event::Start(Tag::CodeBlock(ref kind)) => {
|
||||||
if !context.config.highlight_code {
|
if !context.config.highlight_code {
|
||||||
return Event::Html("<pre><code>".into());
|
return Event::Html("<pre><code>".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let theme = &THEME_SET.themes[&context.config.highlight_theme];
|
let theme = &THEME_SET.themes[&context.config.highlight_theme];
|
||||||
|
match kind {
|
||||||
|
CodeBlockKind::Indented => (),
|
||||||
|
CodeBlockKind::Fenced(info) => {
|
||||||
highlighter = Some(get_highlighter(info, &context.config));
|
highlighter = Some(get_highlighter(info, &context.config));
|
||||||
|
}
|
||||||
|
};
|
||||||
// This selects the background color the same way that start_coloured_html_snippet does
|
// This selects the background color the same way that start_coloured_html_snippet does
|
||||||
let color = theme
|
let color = theme
|
||||||
.settings
|
.settings
|
||||||
|
@ -221,6 +249,10 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
|
||||||
|
|
||||||
Event::Start(Tag::Image(link_type, src, title))
|
Event::Start(Tag::Image(link_type, src, title))
|
||||||
}
|
}
|
||||||
|
Event::Start(Tag::Link(link_type, link, title)) if link.is_empty() => {
|
||||||
|
error = Some(Error::msg("There is a link that is missing a URL"));
|
||||||
|
Event::Start(Tag::Link(link_type, "#".into(), title))
|
||||||
|
}
|
||||||
Event::Start(Tag::Link(link_type, link, title)) => {
|
Event::Start(Tag::Link(link_type, link, title)) => {
|
||||||
let fixed_link = match fix_link(
|
let fixed_link = match fix_link(
|
||||||
link_type,
|
link_type,
|
||||||
|
@ -275,8 +307,13 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
|
||||||
let start_idx = heading_ref.start_idx;
|
let start_idx = heading_ref.start_idx;
|
||||||
let end_idx = heading_ref.end_idx;
|
let end_idx = heading_ref.end_idx;
|
||||||
let title = get_text(&events[start_idx + 1..end_idx]);
|
let title = get_text(&events[start_idx + 1..end_idx]);
|
||||||
let id =
|
let id = heading_ref.id.unwrap_or_else(|| {
|
||||||
heading_ref.id.unwrap_or_else(|| find_anchor(&inserted_anchors, slugify(&title), 0));
|
find_anchor(
|
||||||
|
&inserted_anchors,
|
||||||
|
slugify_anchors(&title, context.config.slugify.anchors),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
});
|
||||||
inserted_anchors.push(id.clone());
|
inserted_anchors.push(id.clone());
|
||||||
|
|
||||||
// insert `id` to the tag
|
// insert `id` to the tag
|
||||||
|
@ -305,7 +342,8 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
|
||||||
|
|
||||||
// record heading to make table of contents
|
// record heading to make table of contents
|
||||||
let permalink = format!("{}#{}", context.current_page_permalink, id);
|
let permalink = format!("{}#{}", context.current_page_permalink, id);
|
||||||
let h = Heading { level: heading_ref.level, id, permalink, title, children: Vec::new() };
|
let h =
|
||||||
|
Heading { level: heading_ref.level, id, permalink, title, children: Vec::new() };
|
||||||
headings.push(h);
|
headings.push(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,3 +366,41 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_starts_with_schema() {
|
||||||
|
// registered
|
||||||
|
assert!(starts_with_schema("https://example.com/"));
|
||||||
|
assert!(starts_with_schema("ftp://example.com/"));
|
||||||
|
assert!(starts_with_schema("mailto:user@example.com"));
|
||||||
|
assert!(starts_with_schema("xmpp:node@example.com"));
|
||||||
|
assert!(starts_with_schema("tel:18008675309"));
|
||||||
|
assert!(starts_with_schema("sms:18008675309"));
|
||||||
|
assert!(starts_with_schema("h323:user@example.com"));
|
||||||
|
|
||||||
|
// arbitrary
|
||||||
|
assert!(starts_with_schema("zola:post?content=hi"));
|
||||||
|
|
||||||
|
// case-insensitive
|
||||||
|
assert!(starts_with_schema("MailTo:user@example.com"));
|
||||||
|
assert!(starts_with_schema("MAILTO:user@example.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_external_link() {
|
||||||
|
assert!(is_external_link("http://example.com/"));
|
||||||
|
assert!(is_external_link("https://example.com/"));
|
||||||
|
assert!(is_external_link("https://example.com/index.html#introduction"));
|
||||||
|
|
||||||
|
assert!(!is_external_link("mailto:user@example.com"));
|
||||||
|
assert!(!is_external_link("tel:18008675309"));
|
||||||
|
|
||||||
|
assert!(!is_external_link("#introduction"));
|
||||||
|
|
||||||
|
assert!(!is_external_link("http.jpg"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use pest::iterators::Pair;
|
use pest::iterators::Pair;
|
||||||
use pest::Parser;
|
use pest::Parser;
|
||||||
|
use pest_derive::Parser;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use tera::{to_value, Context, Map, Value};
|
use tera::{to_value, Context, Map, Value};
|
||||||
|
|
||||||
use context::RenderContext;
|
use crate::context::RenderContext;
|
||||||
use errors::{Error, Result};
|
use errors::{bail, Error, Result};
|
||||||
|
|
||||||
// This include forces recompiling this source file if the grammar file changes.
|
// This include forces recompiling this source file if the grammar file changes.
|
||||||
// Uncomment it when doing changes to the .pest file
|
// Uncomment it when doing changes to the .pest file
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
use serde_derive::Serialize;
|
||||||
|
|
||||||
/// Populated while receiving events from the markdown parser
|
/// Populated while receiving events from the markdown parser
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||||
pub struct Heading {
|
pub struct Heading {
|
||||||
#[serde(skip_serializing)]
|
|
||||||
pub level: u32,
|
pub level: u32,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub permalink: String,
|
pub permalink: String,
|
||||||
|
@ -113,6 +114,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
let toc = make_table_of_contents(input);
|
let toc = make_table_of_contents(input);
|
||||||
assert_eq!(toc.len(), 1);
|
assert_eq!(toc.len(), 1);
|
||||||
|
assert_eq!(toc[0].level, 1);
|
||||||
assert_eq!(toc[0].children.len(), 1);
|
assert_eq!(toc[0].children.len(), 1);
|
||||||
assert_eq!(toc[0].children[0].children.len(), 1);
|
assert_eq!(toc[0].children[0].children.len(), 1);
|
||||||
assert_eq!(toc[0].children[0].children[0].children.len(), 2);
|
assert_eq!(toc[0].children[0].children[0].children.len(), 2);
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
extern crate config;
|
|
||||||
extern crate front_matter;
|
|
||||||
extern crate rendering;
|
|
||||||
extern crate templates;
|
|
||||||
extern crate tera;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
|
@ -12,6 +6,7 @@ use config::Config;
|
||||||
use front_matter::InsertAnchor;
|
use front_matter::InsertAnchor;
|
||||||
use rendering::{render_content, RenderContext};
|
use rendering::{render_content, RenderContext};
|
||||||
use templates::ZOLA_TERA;
|
use templates::ZOLA_TERA;
|
||||||
|
use utils::slugs::SlugifyStrategy;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_do_render_content_simple() {
|
fn can_do_render_content_simple() {
|
||||||
|
@ -351,6 +346,17 @@ fn can_add_id_to_headings_same_slug() {
|
||||||
assert_eq!(res.body, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n");
|
assert_eq!(res.body, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_add_non_slug_id_to_headings() {
|
||||||
|
let tera_ctx = Tera::default();
|
||||||
|
let permalinks_ctx = HashMap::new();
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.slugify.anchors = SlugifyStrategy::Safe;
|
||||||
|
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
|
let res = render_content(r#"# L'écologie et vous"#, &context).unwrap();
|
||||||
|
assert_eq!(res.body, "<h1 id=\"L'écologie_et_vous\">L'écologie et vous</h1>\n");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_handle_manual_ids_on_headings() {
|
fn can_handle_manual_ids_on_headings() {
|
||||||
let tera_ctx = Tera::default();
|
let tera_ctx = Tera::default();
|
||||||
|
@ -619,11 +625,14 @@ fn can_understand_footnote_in_heading() {
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let context = RenderContext::new(&ZOLA_TERA, &config, "", &permalinks_ctx, InsertAnchor::None);
|
let context = RenderContext::new(&ZOLA_TERA, &config, "", &permalinks_ctx, InsertAnchor::None);
|
||||||
let res = render_content("# text [^1] there\n[^1]: footnote", &context).unwrap();
|
let res = render_content("# text [^1] there\n[^1]: footnote", &context).unwrap();
|
||||||
assert_eq!(res.body, r##"<h1 id="text-there">text <sup class="footnote-reference"><a href="#1">1</a></sup> there</h1>
|
assert_eq!(
|
||||||
|
res.body,
|
||||||
|
r##"<h1 id="text-there">text <sup class="footnote-reference"><a href="#1">1</a></sup> there</h1>
|
||||||
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
|
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
|
||||||
<p>footnote</p>
|
<p>footnote</p>
|
||||||
</div>
|
</div>
|
||||||
"##);
|
"##
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -751,7 +760,7 @@ Bla bla
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.body,
|
res.body,
|
||||||
"<p>Hello <a href=\"https://vincentprouillet.com\">My site</a></p>\n<p id=\"zola-continue-reading\"><a name=\"continue-reading\"></a></p>\n<p>Bla bla</p>\n"
|
"<p>Hello <a href=\"https://vincentprouillet.com\">My site</a></p>\n<span id=\"continue-reading\"></span>\n<p>Bla bla</p>\n"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.summary_len,
|
res.summary_len,
|
||||||
|
@ -821,12 +830,58 @@ fn doesnt_try_to_highlight_content_from_shortcode() {
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// https://github.com/getzola/zola/issues/747
|
// https://github.com/getzola/zola/issues/747
|
||||||
|
// https://github.com/getzola/zola/issues/816
|
||||||
#[test]
|
#[test]
|
||||||
fn leaves_custom_url_scheme_untouched() {
|
fn leaves_custom_url_scheme_untouched() {
|
||||||
|
let content = r#"[foo@bar.tld](xmpp:foo@bar.tld)
|
||||||
|
|
||||||
|
[(123) 456-7890](tel:+11234567890)
|
||||||
|
|
||||||
|
[blank page](about:blank)
|
||||||
|
"#;
|
||||||
|
|
||||||
let tera_ctx = Tera::default();
|
let tera_ctx = Tera::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None);
|
let permalinks_ctx = HashMap::new();
|
||||||
let res = render_content("[foo@bar.tld](xmpp:foo@bar.tld)", &context).unwrap();
|
|
||||||
assert_eq!(res.body, "<p><a href=\"xmpp:foo@bar.tld\">foo@bar.tld</a></p>\n");
|
let context = RenderContext::new(
|
||||||
|
&tera_ctx,
|
||||||
|
&config,
|
||||||
|
"https://vincent.is/",
|
||||||
|
&permalinks_ctx,
|
||||||
|
InsertAnchor::None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = render_content(content, &context).unwrap();
|
||||||
|
|
||||||
|
let expected = r#"<p><a href="xmpp:foo@bar.tld">foo@bar.tld</a></p>
|
||||||
|
<p><a href="tel:+11234567890">(123) 456-7890</a></p>
|
||||||
|
<p><a href="about:blank">blank page</a></p>
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(res.body, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stops_with_an_error_on_an_empty_link() {
|
||||||
|
let content = r#"[some link]()"#;
|
||||||
|
|
||||||
|
let tera_ctx = Tera::default();
|
||||||
|
let config = Config::default();
|
||||||
|
let permalinks_ctx = HashMap::new();
|
||||||
|
|
||||||
|
let context = RenderContext::new(
|
||||||
|
&tera_ctx,
|
||||||
|
&config,
|
||||||
|
"https://vincent.is/",
|
||||||
|
&permalinks_ctx,
|
||||||
|
InsertAnchor::None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = render_content(content, &context);
|
||||||
|
|
||||||
|
let expected = "There is a link that is missing a URL";
|
||||||
|
|
||||||
|
assert!(res.is_err());
|
||||||
|
assert_eq!(res.unwrap_err().to_string(), expected);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
name = "search"
|
name = "search"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
elasticlunr-rs = "2"
|
elasticlunr-rs = "2"
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
extern crate elasticlunr;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
extern crate ammonia;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate errors;
|
|
||||||
extern crate library;
|
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use elasticlunr::{Index, Language};
|
use elasticlunr::{Index, Language};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use errors::Result;
|
use errors::{bail, Result};
|
||||||
use library::{Library, Section};
|
use library::{Library, Section};
|
||||||
|
|
||||||
pub const ELASTICLUNR_JS: &str = include_str!("elasticlunr.min.js");
|
pub const ELASTICLUNR_JS: &str = include_str!("elasticlunr.min.js");
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
name = "site"
|
name = "site"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tera = "1.0.0-beta.10"
|
tera = "1"
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
//! Benchmarking loading/markdown rendering of generated sites of various sizes
|
//! Benchmarking loading/markdown rendering of generated sites of various sizes
|
||||||
|
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
extern crate site;
|
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
extern crate library;
|
|
||||||
extern crate site;
|
|
||||||
extern crate tempfile;
|
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
|
@ -1,26 +1,4 @@
|
||||||
extern crate glob;
|
pub mod sitemap;
|
||||||
extern crate rayon;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate tera;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate sass_rs;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate errors;
|
|
||||||
extern crate config;
|
|
||||||
extern crate front_matter;
|
|
||||||
extern crate imageproc;
|
|
||||||
extern crate library;
|
|
||||||
extern crate link_checker;
|
|
||||||
extern crate search;
|
|
||||||
extern crate templates;
|
|
||||||
extern crate utils;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate tempfile;
|
|
||||||
|
|
||||||
mod sitemap;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::{copy, create_dir_all, remove_dir_all};
|
use std::fs::{copy, create_dir_all, remove_dir_all};
|
||||||
|
@ -33,7 +11,7 @@ use sass_rs::{compile_file, Options as SassOptions, OutputStyle};
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
|
|
||||||
use config::{get_config, Config};
|
use config::{get_config, Config};
|
||||||
use errors::{Error, ErrorKind, Result};
|
use errors::{bail, Error, ErrorKind, Result};
|
||||||
use front_matter::InsertAnchor;
|
use front_matter::InsertAnchor;
|
||||||
use library::{
|
use library::{
|
||||||
find_taxonomies, sort_actual_pages_by_date, Library, Page, Paginator, Section, Taxonomy,
|
find_taxonomies, sort_actual_pages_by_date, Library, Page, Paginator, Section, Taxonomy,
|
||||||
|
@ -69,7 +47,7 @@ pub struct Site {
|
||||||
|
|
||||||
impl Site {
|
impl Site {
|
||||||
/// Parse a site at the given path. Defaults to the current dir
|
/// Parse a site at the given path. Defaults to the current dir
|
||||||
/// Passing in a path is only used in tests
|
/// Passing in a path is used in tests and when --root argument is passed
|
||||||
pub fn new<P: AsRef<Path>>(path: P, config_file: &str) -> Result<Site> {
|
pub fn new<P: AsRef<Path>>(path: P, config_file: &str) -> Result<Site> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let mut config = get_config(path, config_file);
|
let mut config = get_config(path, config_file);
|
||||||
|
@ -98,17 +76,19 @@ impl Site {
|
||||||
);
|
);
|
||||||
let mut tera_theme = Tera::parse(&theme_tpl_glob)
|
let mut tera_theme = Tera::parse(&theme_tpl_glob)
|
||||||
.map_err(|e| Error::chain("Error parsing templates from themes", e))?;
|
.map_err(|e| Error::chain("Error parsing templates from themes", e))?;
|
||||||
rewrite_theme_paths(&mut tera_theme, &theme);
|
rewrite_theme_paths(
|
||||||
|
&mut tera_theme,
|
||||||
|
tera.templates.values().map(|v| v.name.as_ref()).collect(),
|
||||||
|
&theme,
|
||||||
|
);
|
||||||
// TODO: we do that twice, make it dry?
|
// TODO: we do that twice, make it dry?
|
||||||
if theme_path.join("templates").join("robots.txt").exists() {
|
if theme_path.join("templates").join("robots.txt").exists() {
|
||||||
tera_theme
|
tera_theme
|
||||||
.add_template_file(theme_path.join("templates").join("robots.txt"), None)?;
|
.add_template_file(theme_path.join("templates").join("robots.txt"), None)?;
|
||||||
}
|
}
|
||||||
tera_theme.build_inheritance_chains()?;
|
|
||||||
tera.extend(&tera_theme)?;
|
tera.extend(&tera_theme)?;
|
||||||
}
|
}
|
||||||
tera.extend(&ZOLA_TERA)?;
|
tera.extend(&ZOLA_TERA)?;
|
||||||
// the `extend` above already does it but hey
|
|
||||||
tera.build_inheritance_chains()?;
|
tera.build_inheritance_chains()?;
|
||||||
|
|
||||||
// TODO: Tera doesn't use globset right now so we can load the robots.txt as part
|
// TODO: Tera doesn't use globset right now so we can load the robots.txt as part
|
||||||
|
@ -253,6 +233,14 @@ impl Site {
|
||||||
self.add_page(p, false)?;
|
self.add_page(p, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let library = self.library.read().unwrap();
|
||||||
|
let collisions = library.check_for_path_collisions();
|
||||||
|
if !collisions.is_empty() {
|
||||||
|
return Err(Error::from_collisions(collisions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// taxonomy Tera fns are loaded in `register_early_global_fns`
|
// taxonomy Tera fns are loaded in `register_early_global_fns`
|
||||||
// so we do need to populate it first.
|
// so we do need to populate it first.
|
||||||
self.populate_taxonomies()?;
|
self.populate_taxonomies()?;
|
||||||
|
@ -399,7 +387,16 @@ impl Site {
|
||||||
all_links
|
all_links
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.filter_map(|(page_path, link)| {
|
.filter_map(|(page_path, link)| {
|
||||||
let res = check_url(&link);
|
if self
|
||||||
|
.config
|
||||||
|
.link_checker
|
||||||
|
.skip_prefixes
|
||||||
|
.iter()
|
||||||
|
.any(|prefix| link.starts_with(prefix))
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let res = check_url(&link, &self.config.link_checker);
|
||||||
if res.is_valid() {
|
if res.is_valid() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -456,6 +453,7 @@ impl Site {
|
||||||
index_path.file_name().unwrap().to_string_lossy().to_string();
|
index_path.file_name().unwrap().to_string_lossy().to_string();
|
||||||
if let Some(ref l) = lang {
|
if let Some(ref l) = lang {
|
||||||
index_section.file.name = format!("_index.{}", l);
|
index_section.file.name = format!("_index.{}", l);
|
||||||
|
index_section.path = format!("{}/", l);
|
||||||
index_section.permalink = self.config.make_permalink(l);
|
index_section.permalink = self.config.make_permalink(l);
|
||||||
let filename = format!("_index.{}.md", l);
|
let filename = format!("_index.{}.md", l);
|
||||||
index_section.file.path = self.content_path.join(&filename);
|
index_section.file.path = self.content_path.join(&filename);
|
||||||
|
@ -633,7 +631,7 @@ impl Site {
|
||||||
return html.replace(
|
return html.replace(
|
||||||
"</body>",
|
"</body>",
|
||||||
&format!(
|
&format!(
|
||||||
r#"<script src="/livereload.js?port={}&mindelay=10"></script></body>"#,
|
r#"<script src="/livereload.js?port={}&mindelay=10"></script></body>"#,
|
||||||
port
|
port
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,6 +2,8 @@ use std::borrow::Cow;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
use serde_derive::Serialize;
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use library::{Library, Taxonomy};
|
use library::{Library, Taxonomy};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
@ -11,9 +13,9 @@ use tera::{Map, Value};
|
||||||
/// for examples so we trim down all entries to only that
|
/// for examples so we trim down all entries to only that
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct SitemapEntry<'a> {
|
pub struct SitemapEntry<'a> {
|
||||||
permalink: Cow<'a, str>,
|
pub permalink: Cow<'a, str>,
|
||||||
date: Option<String>,
|
pub date: Option<String>,
|
||||||
extra: Option<&'a Map<String, Value>>,
|
pub extra: Option<&'a Map<String, Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash/Eq is not implemented for tera::Map but in our case we only care about the permalink
|
// Hash/Eq is not implemented for tera::Map but in our case we only care about the permalink
|
||||||
|
@ -77,7 +79,11 @@ pub fn find_entries<'a>(
|
||||||
.sections_values()
|
.sections_values()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|s| s.meta.render)
|
.filter(|s| s.meta.render)
|
||||||
.map(|s| SitemapEntry::new(Cow::Borrowed(&s.permalink), None))
|
.map(|s| {
|
||||||
|
let mut entry = SitemapEntry::new(Cow::Borrowed(&s.permalink), None);
|
||||||
|
entry.add_extra(&s.meta.extra);
|
||||||
|
entry
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for section in library.sections_values().iter().filter(|s| s.meta.paginate_by.is_some()) {
|
for section in library.sections_values().iter().filter(|s| s.meta.paginate_by.is_some()) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
extern crate site;
|
#![allow(dead_code)]
|
||||||
extern crate tempfile;
|
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use self::site::Site;
|
use site::Site;
|
||||||
use self::tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
// 2 helper macros to make all the build testing more bearable
|
// 2 helper macros to make all the build testing more bearable
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
extern crate config;
|
|
||||||
extern crate site;
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -8,6 +6,7 @@ use std::path::Path;
|
||||||
|
|
||||||
use common::{build_site, build_site_with_setup};
|
use common::{build_site, build_site_with_setup};
|
||||||
use config::Taxonomy;
|
use config::Taxonomy;
|
||||||
|
use site::sitemap;
|
||||||
use site::Site;
|
use site::Site;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -87,6 +86,19 @@ fn can_parse_site() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(prog_section.subsections.len(), 0);
|
assert_eq!(prog_section.subsections.len(), 0);
|
||||||
assert_eq!(prog_section.pages.len(), 2);
|
assert_eq!(prog_section.pages.len(), 2);
|
||||||
|
|
||||||
|
// Testing extra variables in sections & sitemaps
|
||||||
|
// Regression test for #https://github.com/getzola/zola/issues/842
|
||||||
|
assert_eq!(
|
||||||
|
prog_section.meta.extra.get("we_have_extra").and_then(|s| s.as_str()),
|
||||||
|
Some("variables")
|
||||||
|
);
|
||||||
|
let sitemap_entries = sitemap::find_entries(&library, &site.taxonomies[..], &site.config);
|
||||||
|
let sitemap_entry = sitemap_entries
|
||||||
|
.iter()
|
||||||
|
.find(|e| e.permalink.ends_with("tutorials/programming/"))
|
||||||
|
.expect("expected to find programming section in sitemap");
|
||||||
|
assert_eq!(Some(&prog_section.meta.extra), sitemap_entry.extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -161,7 +173,10 @@ fn can_build_site_without_live_reload() {
|
||||||
assert!(file_exists!(public, "nested_sass/scss.css"));
|
assert!(file_exists!(public, "nested_sass/scss.css"));
|
||||||
|
|
||||||
// no live reload code
|
// no live reload code
|
||||||
assert_eq!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"), false);
|
assert_eq!(
|
||||||
|
file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
// Both pages and sections are in the sitemap
|
// Both pages and sections are in the sitemap
|
||||||
assert!(file_contains!(
|
assert!(file_contains!(
|
||||||
|
@ -224,11 +239,11 @@ fn can_build_site_with_live_reload_and_drafts() {
|
||||||
// no live reload code
|
// no live reload code
|
||||||
assert!(file_contains!(public, "index.html", "/livereload.js"));
|
assert!(file_contains!(public, "index.html", "/livereload.js"));
|
||||||
|
|
||||||
// the summary anchor link has been created
|
// the summary target has been created
|
||||||
assert!(file_contains!(
|
assert!(file_contains!(
|
||||||
public,
|
public,
|
||||||
"posts/python/index.html",
|
"posts/python/index.html",
|
||||||
r#"<a name="continue-reading"></a>"#
|
r#"<span id="continue-reading"></span>"#
|
||||||
));
|
));
|
||||||
|
|
||||||
// Drafts are included
|
// Drafts are included
|
||||||
|
@ -470,6 +485,12 @@ fn can_build_site_with_pagination_for_index() {
|
||||||
"page/1/index.html",
|
"page/1/index.html",
|
||||||
"http-equiv=\"refresh\" content=\"0;url=https://replace-this-with-your-url.com/\""
|
"http-equiv=\"refresh\" content=\"0;url=https://replace-this-with-your-url.com/\""
|
||||||
));
|
));
|
||||||
|
assert!(file_contains!(public, "page/1/index.html", "<title>Redirect</title>"));
|
||||||
|
assert!(file_contains!(
|
||||||
|
public,
|
||||||
|
"page/1/index.html",
|
||||||
|
"<a href=\"https://replace-this-with-your-url.com/\">Click here</a>"
|
||||||
|
));
|
||||||
assert!(file_contains!(public, "index.html", "Num pages: 1"));
|
assert!(file_contains!(public, "index.html", "Num pages: 1"));
|
||||||
assert!(file_contains!(public, "index.html", "Current index: 1"));
|
assert!(file_contains!(public, "index.html", "Current index: 1"));
|
||||||
assert!(file_contains!(public, "index.html", "First: https://replace-this-with-your-url.com/"));
|
assert!(file_contains!(public, "index.html", "First: https://replace-this-with-your-url.com/"));
|
||||||
|
@ -662,3 +683,17 @@ fn can_ignore_markdown_content() {
|
||||||
let (_, _tmp_dir, public) = build_site("test_site");
|
let (_, _tmp_dir, public) = build_site("test_site");
|
||||||
assert!(!file_exists!(public, "posts/ignored/index.html"));
|
assert!(!file_exists!(public, "posts/ignored/index.html"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_site() {
|
||||||
|
let (mut site, _tmp_dir, _public) = build_site("test_site");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
site.config.link_checker.skip_anchor_prefixes,
|
||||||
|
vec!["https://github.com/rust-lang/rust/blob/"]
|
||||||
|
);
|
||||||
|
assert_eq!(site.config.link_checker.skip_prefixes, vec!["http://[2001:db8::]/"]);
|
||||||
|
|
||||||
|
site.config.enable_check_mode();
|
||||||
|
site.load().expect("link check test_site");
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
extern crate site;
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
|
@ -2,17 +2,18 @@
|
||||||
name = "templates"
|
name = "templates"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tera = "1.0.0-beta.10"
|
tera = "1"
|
||||||
base64 = "0.10"
|
base64 = "0.11"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
pulldown-cmark = "0.6"
|
pulldown-cmark = "0.7"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
csv = "1"
|
csv = "1"
|
||||||
image = "0.22"
|
image = "0.23"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
reqwest = "0.9"
|
reqwest = { version = "0.10", features = ["blocking"] }
|
||||||
url = "2"
|
url = "2"
|
||||||
|
|
||||||
errors = { path = "../errors" }
|
errors = { path = "../errors" }
|
||||||
|
@ -20,3 +21,6 @@ utils = { path = "../utils" }
|
||||||
library = { path = "../library" }
|
library = { path = "../library" }
|
||||||
config = { path = "../config" }
|
config = { path = "../config" }
|
||||||
imageproc = { path = "../imageproc" }
|
imageproc = { path = "../imageproc" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
mockito = "0.23"
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<title>Redirect</title>
|
||||||
<link rel="canonical" href="{{ url | safe }}" />
|
<link rel="canonical" href="{{ url | safe }}" />
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
<meta http-equiv="refresh" content="0;url={{ url | safe }}" />
|
<meta http-equiv="refresh" content="0;url={{ url | safe }}" />
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
|
<p><a href="{{ url | safe }}">Click here</a> to be redirected.</p>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::hash::BuildHasher;
|
||||||
|
|
||||||
use base64::{decode, encode};
|
use base64::{decode, encode};
|
||||||
use pulldown_cmark as cmark;
|
use pulldown_cmark as cmark;
|
||||||
use tera::{to_value, Result as TeraResult, Value};
|
use tera::{to_value, try_get_value, Result as TeraResult, Value};
|
||||||
|
|
||||||
pub fn markdown<S: BuildHasher>(
|
pub fn markdown<S: BuildHasher>(
|
||||||
value: &Value,
|
value: &Value,
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
extern crate serde_json;
|
|
||||||
extern crate toml;
|
|
||||||
|
|
||||||
use utils::de::fix_toml_dates;
|
use utils::de::fix_toml_dates;
|
||||||
use utils::fs::{get_file_time, is_path_in_directory, read_file};
|
use utils::fs::{get_file_time, is_path_in_directory, read_file};
|
||||||
|
|
||||||
use reqwest::{header, Client};
|
use reqwest::{blocking::Client, header};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
@ -202,7 +199,7 @@ impl TeraFn for LoadData {
|
||||||
let data = match data_source {
|
let data = match data_source {
|
||||||
DataSource::Path(path) => read_data_file(&self.base_path, path),
|
DataSource::Path(path) => read_data_file(&self.base_path, path),
|
||||||
DataSource::Url(url) => {
|
DataSource::Url(url) => {
|
||||||
let mut response = response_client
|
let response = response_client
|
||||||
.get(url.as_str())
|
.get(url.as_str())
|
||||||
.header(header::ACCEPT, file_format.as_accept_header())
|
.header(header::ACCEPT, file_format.as_accept_header())
|
||||||
.send()
|
.send()
|
||||||
|
@ -324,8 +321,15 @@ mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use mockito::mock;
|
||||||
|
use serde_json::json;
|
||||||
use tera::{to_value, Function};
|
use tera::{to_value, Function};
|
||||||
|
|
||||||
|
// NOTE: HTTP mock paths below are randomly generated to avoid name
|
||||||
|
// collisions. Mocks with the same path can sometimes bleed between tests
|
||||||
|
// and cause them to randomly pass/fail. Please make sure to use unique
|
||||||
|
// paths when adding or modifying tests that use Mockito.
|
||||||
|
|
||||||
fn get_test_file(filename: &str) -> PathBuf {
|
fn get_test_file(filename: &str) -> PathBuf {
|
||||||
let test_files = PathBuf::from("../utils/test-files").canonicalize().unwrap();
|
let test_files = PathBuf::from("../utils/test-files").canonicalize().unwrap();
|
||||||
return test_files.join(filename);
|
return test_files.join(filename);
|
||||||
|
@ -367,10 +371,14 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn calculates_cache_key_for_url() {
|
fn calculates_cache_key_for_url() {
|
||||||
let cache_key =
|
let _m = mock("GET", "/kr1zdgbm4y")
|
||||||
DataSource::Url("https://api.github.com/repos/getzola/zola".parse().unwrap())
|
.with_header("content-type", "text/plain")
|
||||||
.get_cache_key(&OutputFormat::Plain);
|
.with_body("Test")
|
||||||
assert_eq!(cache_key, 8916756616423791754);
|
.create();
|
||||||
|
|
||||||
|
let url = format!("{}{}", mockito::server_url(), "/kr1zdgbm4y");
|
||||||
|
let cache_key = DataSource::Url(url.parse().unwrap()).get_cache_key(&OutputFormat::Plain);
|
||||||
|
assert_eq!(cache_key, 425638486551656875);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -393,28 +401,45 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_load_remote_data() {
|
fn can_load_remote_data() {
|
||||||
|
let _m = mock("GET", "/zpydpkjj67")
|
||||||
|
.with_header("content-type", "application/json")
|
||||||
|
.with_body(
|
||||||
|
r#"{
|
||||||
|
"test": {
|
||||||
|
"foo": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let url = format!("{}{}", mockito::server_url(), "/zpydpkjj67");
|
||||||
let static_fn = LoadData::new(PathBuf::new());
|
let static_fn = LoadData::new(PathBuf::new());
|
||||||
let mut args = HashMap::new();
|
let mut args = HashMap::new();
|
||||||
args.insert("url".to_string(), to_value("https://httpbin.org/json").unwrap());
|
args.insert("url".to_string(), to_value(&url).unwrap());
|
||||||
args.insert("format".to_string(), to_value("json").unwrap());
|
args.insert("format".to_string(), to_value("json").unwrap());
|
||||||
let result = static_fn.call(&args).unwrap();
|
let result = static_fn.call(&args).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(result.get("test").unwrap().get("foo").unwrap(), &to_value("bar").unwrap());
|
||||||
result.get("slideshow").unwrap().get("title").unwrap(),
|
|
||||||
&to_value("Sample Slide Show").unwrap()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_when_request_404s() {
|
fn fails_when_request_404s() {
|
||||||
|
let _m = mock("GET", "/aazeow0kog")
|
||||||
|
.with_status(404)
|
||||||
|
.with_header("content-type", "text/plain")
|
||||||
|
.with_body("Not Found")
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let url = format!("{}{}", mockito::server_url(), "/aazeow0kog");
|
||||||
let static_fn = LoadData::new(PathBuf::new());
|
let static_fn = LoadData::new(PathBuf::new());
|
||||||
let mut args = HashMap::new();
|
let mut args = HashMap::new();
|
||||||
args.insert("url".to_string(), to_value("https://httpbin.org/status/404/").unwrap());
|
args.insert("url".to_string(), to_value(&url).unwrap());
|
||||||
args.insert("format".to_string(), to_value("json").unwrap());
|
args.insert("format".to_string(), to_value("json").unwrap());
|
||||||
let result = static_fn.call(&args);
|
let result = static_fn.call(&args);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().to_string(),
|
result.unwrap_err().to_string(),
|
||||||
"Failed to request https://httpbin.org/status/404/: 404 Not Found"
|
format!("Failed to request {}: 404 Not Found", url)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,10 @@ impl TeraFn for Trans {
|
||||||
let lang = optional_arg!(String, args.get("lang"), "`trans`: `lang` must be a string.")
|
let lang = optional_arg!(String, args.get("lang"), "`trans`: `lang` must be a string.")
|
||||||
.unwrap_or_else(|| self.config.default_language.clone());
|
.unwrap_or_else(|| self.config.default_language.clone());
|
||||||
|
|
||||||
let term = self.config.get_translation(lang, key).map_err(|e| {
|
let term = self
|
||||||
Error::chain("Failed to retreive term translation", e)
|
.config
|
||||||
})?;
|
.get_translation(lang, key)
|
||||||
|
.map_err(|e| Error::chain("Failed to retreive term translation", e))?;
|
||||||
|
|
||||||
Ok(to_value(term).unwrap())
|
Ok(to_value(term).unwrap())
|
||||||
}
|
}
|
||||||
|
@ -162,11 +163,11 @@ impl TeraFn for GetImageMeta {
|
||||||
let path = required_arg!(
|
let path = required_arg!(
|
||||||
String,
|
String,
|
||||||
args.get("path"),
|
args.get("path"),
|
||||||
"`get_image_meta` requires a `path` argument with a string value"
|
"`get_image_metadata` requires a `path` argument with a string value"
|
||||||
);
|
);
|
||||||
let src_path = self.content_path.join(&path);
|
let src_path = self.content_path.join(&path);
|
||||||
if !src_path.exists() {
|
if !src_path.exists() {
|
||||||
return Err(format!("`get_image_meta`: Cannot find path: {}", path).into());
|
return Err(format!("`get_image_metadata`: Cannot find path: {}", path).into());
|
||||||
}
|
}
|
||||||
let img = image::open(&src_path)
|
let img = image::open(&src_path)
|
||||||
.map_err(|e| Error::chain(format!("Failed to process image: {}", path), e))?;
|
.map_err(|e| Error::chain(format!("Failed to process image: {}", path), e))?;
|
||||||
|
@ -345,6 +346,7 @@ mod tests {
|
||||||
|
|
||||||
use config::{Config, Taxonomy as TaxonomyConfig};
|
use config::{Config, Taxonomy as TaxonomyConfig};
|
||||||
use library::{Library, Taxonomy, TaxonomyItem};
|
use library::{Library, Taxonomy, TaxonomyItem};
|
||||||
|
use utils::slugs::SlugifyStrategy;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_add_cachebust_to_url() {
|
fn can_add_cachebust_to_url() {
|
||||||
|
@ -388,7 +390,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_get_taxonomy() {
|
fn can_get_taxonomy() {
|
||||||
let config = Config::default();
|
let mut config = Config::default();
|
||||||
|
config.slugify.taxonomies = SlugifyStrategy::On;
|
||||||
let taxo_config = TaxonomyConfig {
|
let taxo_config = TaxonomyConfig {
|
||||||
name: "tags".to_string(),
|
name: "tags".to_string(),
|
||||||
lang: config.default_language.clone(),
|
lang: config.default_language.clone(),
|
||||||
|
@ -465,7 +468,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_get_taxonomy_url() {
|
fn can_get_taxonomy_url() {
|
||||||
let config = Config::default();
|
let mut config = Config::default();
|
||||||
|
config.slugify.taxonomies = SlugifyStrategy::On;
|
||||||
let taxo_config = TaxonomyConfig {
|
let taxo_config = TaxonomyConfig {
|
||||||
name: "tags".to_string(),
|
name: "tags".to_string(),
|
||||||
lang: config.default_language.clone(),
|
lang: config.default_language.clone(),
|
||||||
|
@ -509,7 +513,6 @@ mod tests {
|
||||||
assert!(static_fn.call(&args).is_err());
|
assert!(static_fn.call(&args).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const TRANS_CONFIG: &str = r#"
|
const TRANS_CONFIG: &str = r#"
|
||||||
base_url = "https://remplace-par-ton-url.fr"
|
base_url = "https://remplace-par-ton-url.fr"
|
||||||
default_language = "fr"
|
default_language = "fr"
|
||||||
|
|
|
@ -1,28 +1,7 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate tera;
|
|
||||||
extern crate base64;
|
|
||||||
extern crate csv;
|
|
||||||
extern crate image;
|
|
||||||
extern crate pulldown_cmark;
|
|
||||||
extern crate reqwest;
|
|
||||||
extern crate url;
|
|
||||||
#[cfg(test)]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_json;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
extern crate config;
|
|
||||||
extern crate errors;
|
|
||||||
extern crate imageproc;
|
|
||||||
extern crate library;
|
|
||||||
extern crate utils;
|
|
||||||
|
|
||||||
pub mod filters;
|
pub mod filters;
|
||||||
pub mod global_fns;
|
pub mod global_fns;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
|
|
||||||
use errors::{Error, Result};
|
use errors::{Error, Result};
|
||||||
|
@ -69,6 +48,6 @@ pub fn render_redirect_template(url: &str, tera: &Tera) -> Result<String> {
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.insert("url", &url);
|
context.insert("url", &url);
|
||||||
|
|
||||||
tera.render("internal/alias.html", context)
|
tera.render("internal/alias.html", &context)
|
||||||
.map_err(|e| Error::chain(format!("Failed to render alias for '{}'", url), e))
|
.map_err(|e| Error::chain(format!("Failed to render alias for '{}'", url), e))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,19 @@
|
||||||
name = "utils"
|
name = "utils"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
errors = { path = "../errors" }
|
tera = "1"
|
||||||
tera = "1.0.0-beta.10"
|
|
||||||
unicode-segmentation = "1.2"
|
unicode-segmentation = "1.2"
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
|
serde_derive = "1"
|
||||||
|
slug = "0.1"
|
||||||
|
percent-encoding = "2"
|
||||||
|
|
||||||
|
errors = { path = "../errors" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
|
@ -1,17 +1,7 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate errors;
|
|
||||||
|
|
||||||
extern crate serde;
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate tempfile;
|
|
||||||
extern crate tera;
|
|
||||||
extern crate toml;
|
|
||||||
extern crate unicode_segmentation;
|
|
||||||
extern crate walkdir;
|
|
||||||
|
|
||||||
pub mod de;
|
pub mod de;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
pub mod slugs;
|
||||||
pub mod templates;
|
pub mod templates;
|
||||||
pub mod vec;
|
pub mod vec;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
|
|
||||||
pub fn get_available_port(avoid: u16) -> Option<u16> {
|
pub fn get_available_port(avoid: u16) -> Option<u16> {
|
||||||
(1000..9000).find(|port| *port != avoid && port_is_available(*port))
|
// Start after "well-known" ports (0–1023) as they require superuser
|
||||||
|
// privileges on UNIX-like operating systems.
|
||||||
|
(1024..9000).find(|port| *port != avoid && port_is_available(*port))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn port_is_available(port: u16) -> bool {
|
pub fn port_is_available(port: u16) -> bool {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
use percent_encoding::percent_decode;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::BuildHasher;
|
use std::hash::BuildHasher;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use errors::Result;
|
use errors::{bail, Result};
|
||||||
|
|
||||||
/// Get word count and estimated reading time
|
/// Get word count and estimated reading time
|
||||||
pub fn get_reading_analytics(content: &str) -> (usize, usize) {
|
pub fn get_reading_analytics(content: &str) -> (usize, usize) {
|
||||||
|
@ -33,12 +34,15 @@ pub fn resolve_internal_link<S: BuildHasher>(
|
||||||
// Then we remove any potential anchor
|
// Then we remove any potential anchor
|
||||||
// parts[0] will be the file path and parts[1] the anchor if present
|
// parts[0] will be the file path and parts[1] the anchor if present
|
||||||
let parts = clean_link.split('#').collect::<Vec<_>>();
|
let parts = clean_link.split('#').collect::<Vec<_>>();
|
||||||
match permalinks.get(parts[0]) {
|
// If we have slugification turned off, we might end up with some escaped characters so we need
|
||||||
|
// to decode them first
|
||||||
|
let decoded = &*percent_decode(parts[0].as_bytes()).decode_utf8_lossy();
|
||||||
|
match permalinks.get(decoded) {
|
||||||
Some(p) => {
|
Some(p) => {
|
||||||
if parts.len() > 1 {
|
if parts.len() > 1 {
|
||||||
Ok(ResolvedInternalLink {
|
Ok(ResolvedInternalLink {
|
||||||
permalink: format!("{}#{}", p, parts[1]),
|
permalink: format!("{}#{}", p, parts[1]),
|
||||||
md_path: Some(parts[0].to_string()),
|
md_path: Some(decoded.to_string()),
|
||||||
anchor: Some(parts[1].to_string()),
|
anchor: Some(parts[1].to_string()),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -81,6 +85,19 @@ mod tests {
|
||||||
assert_eq!(res.anchor, Some("hello".to_string()));
|
assert_eq!(res.anchor, Some("hello".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_resolve_escaped_internal_links() {
|
||||||
|
let mut permalinks = HashMap::new();
|
||||||
|
permalinks.insert(
|
||||||
|
"pages/about space.md".to_string(),
|
||||||
|
"https://vincent.is/about%20space/".to_string(),
|
||||||
|
);
|
||||||
|
let res = resolve_internal_link("@/pages/about%20space.md#hello", &permalinks).unwrap();
|
||||||
|
assert_eq!(res.permalink, "https://vincent.is/about%20space/#hello");
|
||||||
|
assert_eq!(res.md_path, Some("pages/about space.md".to_string()));
|
||||||
|
assert_eq!(res.anchor, Some("hello".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn errors_resolve_inexistant_internal_link() {
|
fn errors_resolve_inexistant_internal_link() {
|
||||||
let res = resolve_internal_link("@/pages/about.md#hello", &HashMap::new());
|
let res = resolve_internal_link("@/pages/about.md#hello", &HashMap::new());
|
||||||
|
|
89
components/utils/src/slugs.rs
Normal file
89
components/utils/src/slugs.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum SlugifyStrategy {
|
||||||
|
/// Classic slugification, the default
|
||||||
|
On,
|
||||||
|
/// No slugification, only remove unsafe characters for filepaths/urls
|
||||||
|
Safe,
|
||||||
|
/// Nothing is changed, hope for the best!
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_chars(s: &str, chars: &str) -> String {
|
||||||
|
let mut sanitized_string = s.to_string();
|
||||||
|
sanitized_string.retain(|c| !chars.contains(c));
|
||||||
|
sanitized_string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_invalid_paths_chars(s: &str) -> String {
|
||||||
|
// NTFS forbidden characters : https://gist.github.com/doctaphred/d01d05291546186941e1b7ddc02034d3
|
||||||
|
// Also we need to trim whitespaces and `.` from the end of filename
|
||||||
|
let trimmed = s.trim_end_matches(|c| c == ' ' || c == '.');
|
||||||
|
strip_chars(&trimmed, r#"<>:"/\|?*"#)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slugify_paths(s: &str, strategy: SlugifyStrategy) -> String {
|
||||||
|
match strategy {
|
||||||
|
SlugifyStrategy::On => slug::slugify(s),
|
||||||
|
SlugifyStrategy::Safe => strip_invalid_paths_chars(s),
|
||||||
|
SlugifyStrategy::Off => s.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slugify_anchors(s: &str, strategy: SlugifyStrategy) -> String {
|
||||||
|
match strategy {
|
||||||
|
SlugifyStrategy::On => slug::slugify(s),
|
||||||
|
SlugifyStrategy::Safe | SlugifyStrategy::Off => {
|
||||||
|
s.replace(|c: char| c.is_ascii_whitespace(), "_")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_slugify_paths() {
|
||||||
|
let tests = vec![
|
||||||
|
// input, (on, safe, off)
|
||||||
|
("input", ("input", "input", "input")),
|
||||||
|
("test ", ("test", "test", "test ")),
|
||||||
|
("tes t", ("tes-t", "tes t", "tes t")),
|
||||||
|
// Invalid NTFS
|
||||||
|
("dot. ", ("dot", "dot", "dot. ")),
|
||||||
|
("日本", ("ri-ben", "日本", "日本")),
|
||||||
|
("héhé", ("hehe", "héhé", "héhé")),
|
||||||
|
("test (hey)", ("test-hey", "test (hey)", "test (hey)")),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, (on, safe, off)) in tests {
|
||||||
|
assert_eq!(on, slugify_paths(input, SlugifyStrategy::On));
|
||||||
|
assert_eq!(safe, slugify_paths(input, SlugifyStrategy::Safe));
|
||||||
|
assert_eq!(off, slugify_paths(input, SlugifyStrategy::Off));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_slugify_anchors() {
|
||||||
|
let tests = vec![
|
||||||
|
// input, (on, safe, off)
|
||||||
|
("input", ("input", "input", "input")),
|
||||||
|
("test ", ("test", "test_", "test_")),
|
||||||
|
("tes t", ("tes-t", "tes_t", "tes_t")),
|
||||||
|
// Invalid NTFS
|
||||||
|
("dot. ", ("dot", "dot._", "dot._")),
|
||||||
|
("日本", ("ri-ben", "日本", "日本")),
|
||||||
|
("héhé", ("hehe", "héhé", "héhé")),
|
||||||
|
("test (hey)", ("test-hey", "test_(hey)", "test_(hey)")),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, (on, safe, off)) in tests {
|
||||||
|
assert_eq!(on, slugify_anchors(input, SlugifyStrategy::On));
|
||||||
|
assert_eq!(safe, slugify_anchors(input, SlugifyStrategy::Safe));
|
||||||
|
assert_eq!(off, slugify_anchors(input, SlugifyStrategy::Off));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
|
|
||||||
use errors::Result;
|
use errors::{bail, Result};
|
||||||
|
|
||||||
static DEFAULT_TPL: &str = include_str!("default_tpl.html");
|
static DEFAULT_TPL: &str = include_str!("default_tpl.html");
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ macro_rules! render_default_tpl {
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.insert("filename", $filename);
|
context.insert("filename", $filename);
|
||||||
context.insert("url", $url);
|
context.insert("url", $url);
|
||||||
Tera::one_off(DEFAULT_TPL, context, true).map_err(std::convert::Into::into)
|
Tera::one_off(DEFAULT_TPL, &context, true).map_err(std::convert::Into::into)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,21 +27,21 @@ pub fn render_template(
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
// check if it is in the templates
|
// check if it is in the templates
|
||||||
if tera.templates.contains_key(name) {
|
if tera.templates.contains_key(name) {
|
||||||
return tera.render(name, context).map_err(std::convert::Into::into);
|
return tera.render(name, &context).map_err(std::convert::Into::into);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if it is part of a theme
|
// check if it is part of a theme
|
||||||
if let Some(ref t) = *theme {
|
if let Some(ref t) = *theme {
|
||||||
let theme_template_name = format!("{}/templates/{}", t, name);
|
let theme_template_name = format!("{}/templates/{}", t, name);
|
||||||
if tera.templates.contains_key(&theme_template_name) {
|
if tera.templates.contains_key(&theme_template_name) {
|
||||||
return tera.render(&theme_template_name, context).map_err(std::convert::Into::into);
|
return tera.render(&theme_template_name, &context).map_err(std::convert::Into::into);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if it is part of ZOLA_TERA defaults
|
// check if it is part of ZOLA_TERA defaults
|
||||||
let default_name = format!("__zola_builtins/{}", name);
|
let default_name = format!("__zola_builtins/{}", name);
|
||||||
if tera.templates.contains_key(&default_name) {
|
if tera.templates.contains_key(&default_name) {
|
||||||
return tera.render(&default_name, context).map_err(std::convert::Into::into);
|
return tera.render(&default_name, &context).map_err(std::convert::Into::into);
|
||||||
}
|
}
|
||||||
|
|
||||||
// maybe it's a default one?
|
// maybe it's a default one?
|
||||||
|
@ -67,18 +67,22 @@ pub fn render_template(
|
||||||
/// or macros is always better anyway for themes
|
/// or macros is always better anyway for themes
|
||||||
/// This will also rename the shortcodes to NOT have the themes in the path
|
/// This will also rename the shortcodes to NOT have the themes in the path
|
||||||
/// so themes shortcodes can be used.
|
/// so themes shortcodes can be used.
|
||||||
pub fn rewrite_theme_paths(tera: &mut Tera, theme: &str) {
|
pub fn rewrite_theme_paths(tera_theme: &mut Tera, site_templates: Vec<&str>, theme: &str) {
|
||||||
let mut shortcodes_to_move = vec![];
|
let mut shortcodes_to_move = vec![];
|
||||||
let mut templates = HashMap::new();
|
let mut templates = HashMap::new();
|
||||||
let old_templates = ::std::mem::replace(&mut tera.templates, HashMap::new());
|
let old_templates = ::std::mem::replace(&mut tera_theme.templates, HashMap::new());
|
||||||
|
|
||||||
// We want to match the paths in the templates to the new names
|
// We want to match the paths in the templates to the new names
|
||||||
for (key, mut tpl) in old_templates {
|
for (key, mut tpl) in old_templates {
|
||||||
tpl.name = format!("{}/templates/{}", theme, tpl.name);
|
tpl.name = format!("{}/templates/{}", theme, tpl.name);
|
||||||
// First the parent if there is none
|
// First the parent if there is one
|
||||||
|
// If a template with the same name is also in site, assumes it overrides the theme one
|
||||||
|
// and do not change anything
|
||||||
if let Some(ref p) = tpl.parent.clone() {
|
if let Some(ref p) = tpl.parent.clone() {
|
||||||
|
if !site_templates.contains(&p.as_ref()) {
|
||||||
tpl.parent = Some(format!("{}/templates/{}", theme, p));
|
tpl.parent = Some(format!("{}/templates/{}", theme, p));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Next the macros import
|
// Next the macros import
|
||||||
let mut updated = vec![];
|
let mut updated = vec![];
|
||||||
|
@ -96,12 +100,12 @@ pub fn rewrite_theme_paths(tera: &mut Tera, theme: &str) {
|
||||||
templates.insert(tpl.name.clone(), tpl);
|
templates.insert(tpl.name.clone(), tpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
tera.templates = templates;
|
tera_theme.templates = templates;
|
||||||
|
|
||||||
// and then replace shortcodes in the Tera instance using the new names
|
// and then replace shortcodes in the Tera instance using the new names
|
||||||
for (old_name, new_name) in shortcodes_to_move {
|
for (old_name, new_name) in shortcodes_to_move {
|
||||||
let tpl = tera.templates.remove(&old_name).unwrap();
|
let tpl = tera_theme.templates.remove(&old_name).unwrap();
|
||||||
tera.templates.insert(new_name, tpl);
|
tera_theme.templates.insert(new_name, tpl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,12 +117,23 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn can_rewrite_all_paths_of_theme() {
|
fn can_rewrite_all_paths_of_theme() {
|
||||||
let mut tera = Tera::parse("test-templates/*.html").unwrap();
|
let mut tera = Tera::parse("test-templates/*.html").unwrap();
|
||||||
rewrite_theme_paths(&mut tera, "hyde");
|
rewrite_theme_paths(&mut tera, vec!["base.html"], "hyde");
|
||||||
// special case to make the test work: we also rename the files to
|
// special case to make the test work: we also rename the files to
|
||||||
// match the imports
|
// match the imports
|
||||||
for (key, val) in tera.templates.clone() {
|
for (key, val) in &tera.templates.clone() {
|
||||||
tera.templates.insert(format!("hyde/templates/{}", key), val.clone());
|
tera.templates.insert(format!("hyde/templates/{}", key), val.clone());
|
||||||
}
|
}
|
||||||
|
// Adding our fake base
|
||||||
|
tera.add_raw_template("base.html", "Hello").unwrap();
|
||||||
tera.build_inheritance_chains().unwrap();
|
tera.build_inheritance_chains().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
tera.templates["hyde/templates/index.html"].parent,
|
||||||
|
Some("base.html".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tera.templates["hyde/templates/child.html"].parent,
|
||||||
|
Some("hyde/templates/index.html".to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
components/utils/test-templates/base.html
Normal file
10
components/utils/test-templates/base.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% block body %}
|
||||||
|
{% endblock body %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1 +1,5 @@
|
||||||
Some base template, used in tests to check whether path rewriting works.
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
The default text
|
||||||
|
{% endblock body %}
|
||||||
|
|
|
@ -29,9 +29,9 @@ resize_image(path, width, height, op, format, quality)
|
||||||
- `"jpg"`
|
- `"jpg"`
|
||||||
- `"png"`
|
- `"png"`
|
||||||
|
|
||||||
The default is `"auto"`, this means the format is chosen based on input image format.
|
The default is `"auto"`, this means that the format is chosen based on input image format.
|
||||||
JPEG is chosen for JPEGs and other lossy formats, while PNG is chosen for PNGs and other lossless formats.
|
JPEG is chosen for JPEGs and other lossy formats, and PNG is chosen for PNGs and other lossless formats.
|
||||||
- `quality` (_optional_): JPEG quality of the resized image, in percents. Only used when encoding JPEGs, default value is `75`.
|
- `quality` (_optional_): JPEG quality of the resized image, in percent. Only used when encoding JPEGs; default value is `75`.
|
||||||
|
|
||||||
### Image processing and return value
|
### Image processing and return value
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ Zola performs image processing during the build process and places the resized i
|
||||||
static/processed_images/
|
static/processed_images/
|
||||||
```
|
```
|
||||||
|
|
||||||
Filename of each resized image is a hash of the function arguments,
|
The filename of each resized image is a hash of the function arguments,
|
||||||
which means that once an image is resized in a certain way, it will be stored in the above directory and will not
|
which means that once an image is resized in a certain way, it will be stored in the above directory and will not
|
||||||
need to be resized again during subsequent builds (unless the image itself, the dimensions, or other arguments are changed).
|
need to be resized again during subsequent builds (unless the image itself, the dimensions, or other arguments are changed).
|
||||||
Therefore, if you have a large number of images, they will only need to be resized once.
|
Therefore, if you have a large number of images, they will only need to be resized once.
|
||||||
|
@ -50,7 +50,7 @@ The function returns a full URL to the resized image.
|
||||||
|
|
||||||
## Resize operations
|
## Resize operations
|
||||||
|
|
||||||
The source for all examples is this 300 × 380 pixels image:
|
The source for all examples is this 300 pixel × 380 pixel image:
|
||||||
|
|
||||||
![zola](01-zola.png)
|
![zola](01-zola.png)
|
||||||
|
|
||||||
|
@ -79,9 +79,11 @@ The source for all examples is this 300 × 380 pixels image:
|
||||||
|
|
||||||
### **`"fit"`**
|
### **`"fit"`**
|
||||||
Like `"fit_width"` and `"fit_height"` combined, but only resize if the image is bigger than any of the specified dimensions.
|
Like `"fit_width"` and `"fit_height"` combined, but only resize if the image is bigger than any of the specified dimensions.
|
||||||
This mode is handy, if e.g. images are automatically shrinked to certain sizes in a shortcode for mobile optimization.
|
This mode is handy, if for example images are automatically shrunk to certain sizes in a shortcode for
|
||||||
Resizes the image such that the result fits within `width` and `height` preserving aspect ratio. This means that both width or height
|
mobile optimization.
|
||||||
will be at max `width` and `height`, respectively, but possibly one of them smaller so as to preserve the aspect ratio.
|
Resizes the image such that the result fits within `width` and `height` while preserving the aspect ratio. This
|
||||||
|
means that both width or height will be at max `width` and `height`, respectively, but possibly one of them
|
||||||
|
smaller so as to preserve the aspect ratio.
|
||||||
|
|
||||||
|
|
||||||
`resize_image(..., width=5000, height=5000, op="fit")`
|
`resize_image(..., width=5000, height=5000, op="fit")`
|
||||||
|
@ -93,8 +95,9 @@ The source for all examples is this 300 × 380 pixels image:
|
||||||
{{ resize_image(path="documentation/content/image-processing/01-zola.png", width=150, height=150, op="fit") }}
|
{{ resize_image(path="documentation/content/image-processing/01-zola.png", width=150, height=150, op="fit") }}
|
||||||
|
|
||||||
### **`"fill"`**
|
### **`"fill"`**
|
||||||
This is the default operation. It takes the image's center part with the same aspect ratio as the `width` & `height` given and resizes that
|
This is the default operation. It takes the image's center part with the same aspect ratio as the `width` and
|
||||||
to `width` & `height`. This means that parts of the image that are outsize of the resized aspect ratio are cropped away.
|
`height` given and resizes that to `width` and `height`. This means that parts of the image that are outside
|
||||||
|
of the resized aspect ratio are cropped away.
|
||||||
|
|
||||||
`resize_image(..., width=150, height=150, op="fill")`
|
`resize_image(..., width=150, height=150, op="fill")`
|
||||||
|
|
||||||
|
@ -103,8 +106,8 @@ The source for all examples is this 300 × 380 pixels image:
|
||||||
|
|
||||||
## Using `resize_image` in markdown via shortcodes
|
## Using `resize_image` in markdown via shortcodes
|
||||||
|
|
||||||
`resize_image` is a built-in Tera global function (see the [Templates](@/documentation/templates/_index.md) chapter),
|
`resize_image` is a built-in Tera global function (see the [templates](@/documentation/templates/_index.md) chapter),
|
||||||
but it can be used in markdown, too, using [Shortcodes](@/documentation/content/shortcodes.md).
|
but it can be used in Markdown using [shortcodes](@/documentation/content/shortcodes.md).
|
||||||
|
|
||||||
The examples above were generated using a shortcode file named `resize_image.html` with this content:
|
The examples above were generated using a shortcode file named `resize_image.html` with this content:
|
||||||
|
|
||||||
|
@ -118,7 +121,7 @@ The `resize_image()` can be used multiple times and/or in loops. It is designed
|
||||||
|
|
||||||
This can be used along with `assets` [page metadata](@/documentation/templates/pages-sections.md) to create picture galleries.
|
This can be used along with `assets` [page metadata](@/documentation/templates/pages-sections.md) to create picture galleries.
|
||||||
The `assets` variable holds paths to all assets in the directory of a page with resources
|
The `assets` variable holds paths to all assets in the directory of a page with resources
|
||||||
(see [assets colocation](@/documentation/content/overview.md#assets-colocation)): if you have files other than images you
|
(see [asset colocation](@/documentation/content/overview.md#asset-colocation)); if you have files other than images you
|
||||||
will need to filter them out in the loop first like in the example below.
|
will need to filter them out in the loop first like in the example below.
|
||||||
|
|
||||||
This can be used in shortcodes. For example, we can create a very simple html-only clickable
|
This can be used in shortcodes. For example, we can create a very simple html-only clickable
|
||||||
|
@ -135,10 +138,10 @@ picture gallery with the following shortcode named `gallery.html`:
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
```
|
```
|
||||||
|
|
||||||
As you can notice, we didn't specify an `op` argument, which means it'll default to `"fill"`. Similarly, the format will default to
|
As you can notice, we didn't specify an `op` argument, which means that it'll default to `"fill"`. Similarly,
|
||||||
`"auto"` (choosing PNG or JPEG as appropriate) and the JPEG quality will default to `75`.
|
the format will default to `"auto"` (choosing PNG or JPEG as appropriate) and the JPEG quality will default to `75`.
|
||||||
|
|
||||||
To call it from a markdown file, simply do:
|
To call it from a Markdown file, simply do:
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{{/* gallery() */}}
|
{{/* gallery() */}}
|
||||||
|
@ -156,4 +159,4 @@ Here is the result:
|
||||||
## Get image size
|
## Get image size
|
||||||
|
|
||||||
Sometimes when building a gallery it is useful to know the dimensions of each asset. You can get this information with
|
Sometimes when building a gallery it is useful to know the dimensions of each asset. You can get this information with
|
||||||
[get_image_metadata](@/documentation/templates/overview.md#get-image-metadata)
|
[get_image_metadata](@/documentation/templates/overview.md#get-image-metadata).
|
||||||
|
|
|
@ -4,9 +4,11 @@ weight = 50
|
||||||
+++
|
+++
|
||||||
|
|
||||||
## Heading id and anchor insertion
|
## Heading id and anchor insertion
|
||||||
While rendering the markdown content, a unique id will automatically be assigned to each heading. This id is created
|
While rendering the Markdown content, a unique id will automatically be assigned to each heading.
|
||||||
by converting the heading text to a [slug](https://en.wikipedia.org/wiki/Semantic_URL#Slug), appending numbers at the end
|
This id is created by converting the heading text to a [slug](https://en.wikipedia.org/wiki/Semantic_URL#Slug) if `slugify_paths` is enabled.
|
||||||
if the slug already exists for that article. For example:
|
if `slugify_paths` is disabled, whitespaces are replaced by `_` and the following characters are stripped: `#`, `%`, `<`, `>`, `[`, `]`, `(`, `)`, \`, `^`, `{`, `|`, `}`.
|
||||||
|
A number is appended at the end if the slug already exists for that article
|
||||||
|
For example:
|
||||||
|
|
||||||
```md
|
```md
|
||||||
# Something exciting! <- something-exciting
|
# Something exciting! <- something-exciting
|
||||||
|
@ -22,18 +24,21 @@ You can also manually specify an id with a `{#…}` suffix on the heading line:
|
||||||
# Something manual! {#manual}
|
# Something manual! {#manual}
|
||||||
```
|
```
|
||||||
|
|
||||||
This is useful for making deep links robust, either proactively (so that you can later change the text of a heading without breaking links to it) or retroactively (keeping the slug of the old header text, when changing the text). It can also be useful for migration of existing sites with different header id schemes, so that you can keep deep links working.
|
This is useful for making deep links robust, either proactively (so that you can later change the text of a heading
|
||||||
|
without breaking links to it) or retroactively (keeping the slug of the old header text when changing the text). It
|
||||||
|
can also be useful for migration of existing sites with different header id schemes, so that you can keep deep
|
||||||
|
links working.
|
||||||
|
|
||||||
## Anchor insertion
|
## Anchor insertion
|
||||||
It is possible to have Zola automatically insert anchor links next to the heading, as you can see on the site you are currently
|
It is possible to have Zola automatically insert anchor links next to the heading, as you can see on this documentation
|
||||||
reading if you hover a title.
|
if you hover a title.
|
||||||
|
|
||||||
This option is set at the section level: the `insert_anchor_links` variable on the
|
This option is set at the section level: the `insert_anchor_links` variable on the
|
||||||
[Section front-matter page](@/documentation/content/section.md#front-matter).
|
[section front matter page](@/documentation/content/section.md#front-matter).
|
||||||
|
|
||||||
The default template is very basic and will need CSS tweaks in your project to look decent.
|
The default template is very basic and will need CSS tweaks in your project to look decent.
|
||||||
If you want to change the anchor template, it can easily be overwritten by
|
If you want to change the anchor template, it can be easily overwritten by
|
||||||
creating a `anchor-link.html` file in the `templates` directory which gets an `id` variable.
|
creating an `anchor-link.html` file in the `templates` directory, which gets an `id` variable.
|
||||||
|
|
||||||
## Internal links
|
## Internal links
|
||||||
Linking to other pages and their headings is so common that Zola adds a
|
Linking to other pages and their headings is so common that Zola adds a
|
||||||
|
@ -41,4 +46,4 @@ special syntax to Markdown links to handle them: start the link with `@/` and po
|
||||||
to link to. The path to the file starts from the `content` directory.
|
to link to. The path to the file starts from the `content` directory.
|
||||||
|
|
||||||
For example, linking to a file located at `content/pages/about.md` would be `[my link](@/pages/about.md)`.
|
For example, linking to a file located at `content/pages/about.md` would be `[my link](@/pages/about.md)`.
|
||||||
You can still link to an anchor directly: `[my link](@/pages/about.md#example)` will work as expected.
|
You can still link to an anchor directly; `[my link](@/pages/about.md#example)` will work as expected.
|
||||||
|
|
|
@ -21,7 +21,7 @@ If you want to use per-language taxonomies, ensure you set the `lang` field in t
|
||||||
configuration.
|
configuration.
|
||||||
|
|
||||||
## Content
|
## Content
|
||||||
Once the languages are added in, you can start to translate your content. Zola
|
Once the languages have been added, you can start to translate your content. Zola
|
||||||
uses the filename to detect the language:
|
uses the filename to detect the language:
|
||||||
|
|
||||||
- `content/an-article.md`: this will be the default language
|
- `content/an-article.md`: this will be the default language
|
||||||
|
@ -30,9 +30,9 @@ uses the filename to detect the language:
|
||||||
If the language code in the filename does not correspond to one of the languages configured,
|
If the language code in the filename does not correspond to one of the languages configured,
|
||||||
an error will be shown.
|
an error will be shown.
|
||||||
|
|
||||||
If your default language has an `_index.md` in a directory, you will need to add a `_index.{code}.md`
|
If your default language has an `_index.md` in a directory, you will need to add an `_index.{code}.md`
|
||||||
file with the desired front-matter options as there is no language fallback.
|
file with the desired front-matter options as there is no language fallback.
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
Zola outputs the translated content with a base URL of `{base_url}/{code}/`.
|
Zola outputs the translated content with a base URL of `{base_url}/{code}/`.
|
||||||
The only exception to that is if you are setting a translated page `path` directly in the front-matter.
|
The only exception to this is if you are setting a translated page `path` directly in the front matter.
|
||||||
|
|
|
@ -4,9 +4,9 @@ weight = 10
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
|
||||||
Zola uses the folder structure to determine the site structure.
|
Zola uses the directory structure to determine the site structure.
|
||||||
Each folder in the `content` directory represents a [section](@/documentation/content/section.md)
|
Each child directory in the `content` directory represents a [section](@/documentation/content/section.md)
|
||||||
that contains [pages](@/documentation/content/page.md): your `.md` files.
|
that contains [pages](@/documentation/content/page.md) (your `.md` files).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.
|
.
|
||||||
|
@ -23,30 +23,30 @@ that contains [pages](@/documentation/content/page.md): your `.md` files.
|
||||||
└── _index.md // -> https://mywebsite.com/landing/
|
└── _index.md // -> https://mywebsite.com/landing/
|
||||||
```
|
```
|
||||||
|
|
||||||
Each page path (the part after the `base_url`, for example `blog/cli-usage/`) can be customised by changing the `path` or `slug`
|
Each page path (the part after `base_url`, for example `blog/cli-usage/`) can be customised by changing the `path` or
|
||||||
attribute of the [page front-matter](@/documentation/content/page.md#front-matter).
|
`slug` attribute of the [page front-matter](@/documentation/content/page.md#front-matter).
|
||||||
|
|
||||||
You might have noticed a file named `_index.md` in the example above.
|
You might have noticed a file named `_index.md` in the example above.
|
||||||
This file is used to store both metadata and content of the section itself and is not considered a page.
|
This file is used to store both the metadata and content of the section itself and is not considered a page.
|
||||||
|
|
||||||
To make sure the terminology used in the rest of the documentation is understood, let's go over the example above.
|
To ensure that the terminology used in the rest of the documentation is understood, let's go over the example above.
|
||||||
|
|
||||||
The `content` directory in this case has three `sections`: `content`, `blog` and `landing`. The `content` section has only
|
The `content` directory in this case has three `sections`: `content`, `blog` and `landing`. The `content` section has only
|
||||||
one page, `something.md`, the `landing` section has no page and the `blog` section has 4 pages: `cli-usage.md`, `configuration.md`, `directory-structure.md`
|
one page (`something.md`), the `landing` section has no pages and the `blog` section has 4 pages (`cli-usage.md`,
|
||||||
and `installation.md`.
|
`configuration.md`, `directory-structure.md` and `installation.md`).
|
||||||
|
|
||||||
While not shown in the example, sections can be nested indefinitely.
|
Sections can be nested indefinitely.
|
||||||
|
|
||||||
## Assets colocation
|
## Asset colocation
|
||||||
|
|
||||||
The `content` directory is not limited to markup files though: it's natural to want to co-locate a page and some related
|
The `content` directory is not limited to markup files. It's natural to want to co-locate a page and some related
|
||||||
assets, for instance images or spreadsheets. Zola supports that pattern out of the box for both sections and pages.
|
assets, such as images or spreadsheets. Zola supports this pattern out of the box for both sections and pages.
|
||||||
|
|
||||||
Any non-markdown file you add in the page/section folder will be copied alongside the generated page when building the site,
|
All non-Markdown files you add in a page/section directory will be copied alongside the generated page when the site is
|
||||||
which allows us to use a relative path to access them.
|
built, which allows us to use a relative path to access them.
|
||||||
|
|
||||||
For pages to use assets colocation, they should not be placed directly in their section folder (such as `latest-experiment.md`), but as an `index.md` file
|
Pages with co-located assets should not be placed directly in their section directory (such as `latest-experiment.md`), but
|
||||||
in a dedicated folder (`latest-experiment/index.md`), like so:
|
as an `index.md` file in a dedicated directory (`latest-experiment/index.md`), like so:
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -58,23 +58,23 @@ in a dedicated folder (`latest-experiment/index.md`), like so:
|
||||||
└── research.jpg
|
└── research.jpg
|
||||||
```
|
```
|
||||||
|
|
||||||
In this setup, you may access `research.jpg` from your 'research' section,
|
With this setup, you may access `research.jpg` from your 'research' section
|
||||||
and `yavascript.js` from your 'latest-experiment' directly within the Markdown:
|
and `yavascript.js` from your 'latest-experiment' page directly within the Markdown:
|
||||||
|
|
||||||
```markdown
|
```Markdown
|
||||||
Check out the complete program [here](yavascript.js). It's **really cool free-software**!
|
Check out the complete program [here](yavascript.js). It's **really cool free-software**!
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, this page will get the folder name as its slug. So its permalink would be in the form of `https://example.com/research/latest-experiment/`
|
By default, this page's slug will be the directory name and thus its permalink will be `https://example.com/research/latest-experiment/`.
|
||||||
|
|
||||||
### Excluding files from assets
|
### Excluding files from assets
|
||||||
|
|
||||||
It is possible to ignore selected asset files using the
|
It is possible to ignore selected asset files using the
|
||||||
[ignored_content](@/documentation/getting-started/configuration.md) setting in the config file.
|
[ignored_content](@/documentation/getting-started/configuration.md) setting in the config file.
|
||||||
For example, say you have an Excel spreadsheet from which you are taking several screenshots and
|
For example, say that you have an Excel spreadsheet from which you are taking several screenshots and
|
||||||
then linking to those image files on your website. For maintainability purposes, you want to keep
|
then linking to these image files on your website. For maintainability, you want to keep
|
||||||
the spreadsheet in the same folder as the markdown, but you don't want to copy the spreadsheet to
|
the spreadsheet in the same directory as the Markdown file, but you don't want to copy the spreadsheet to
|
||||||
the public web site. You can achieve this by simply setting `ignored_content` in the config file:
|
the public web site. You can achieve this by setting `ignored_content` in the config file:
|
||||||
|
|
||||||
```
|
```
|
||||||
ignored_content = ["*.xlsx"]
|
ignored_content = ["*.xlsx"]
|
||||||
|
@ -83,15 +83,15 @@ ignored_content = ["*.xlsx"]
|
||||||
## Static assets
|
## Static assets
|
||||||
|
|
||||||
In addition to placing content files in the `content` directory, you may also place content
|
In addition to placing content files in the `content` directory, you may also place content
|
||||||
files in the `static` directory. Any files/folders that you place in the `static` directory
|
files in the `static` directory. Any files/directories that you place in the `static` directory
|
||||||
will be copied, without modification, to the public directory.
|
will be copied, without modification, to the `public` directory.
|
||||||
|
|
||||||
Typically, you might put site-wide assets (such as the site favicon, site logos or site-wide
|
Typically, you might put site-wide assets (such as the site favicon, site logos or site-wide
|
||||||
JavaScript) in the root of the static directory. You can also place any HTML or other files that
|
JavaScript) in the root of the static directory. You can also place any HTML or other files that
|
||||||
you wish to be included without modification (that is, without being parsed as Markdown files)
|
you wish to be included without modification (that is, without being parsed as Markdown files)
|
||||||
into the static directory.
|
into the static directory.
|
||||||
|
|
||||||
Note that the static folder provides an _alternative_ to colocation. For example, imagine that you
|
Note that the static directory provides an _alternative_ to co-location. For example, imagine that you
|
||||||
had the following directory structure (a simplified version of the structure presented above):
|
had the following directory structure (a simplified version of the structure presented above):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -103,18 +103,16 @@ had the following directory structure (a simplified version of the structure pre
|
||||||
└── _index.md // -> https://mywebsite.com/blog/
|
└── _index.md // -> https://mywebsite.com/blog/
|
||||||
```
|
```
|
||||||
|
|
||||||
If you wanted to add an image to the `https://mywebsite.com/blog/configuration` page, you would
|
To add an image to the `https://mywebsite.com/blog/configuration` page, you have three options:
|
||||||
have three options:
|
* You could save the image to the `content/blog/configuration` directory and then link to it with a
|
||||||
* You could save the image to the `content/blog/configuration` folder and then link it with a
|
relative path from the `index.md` page. This is the approach described under **co-location**
|
||||||
relative path from the `index.md` page. This is the approach described under **colocation**,
|
|
||||||
above.
|
above.
|
||||||
* You could save the image to a `static/blog/configuration` folder and link it in exactly the
|
* You could save the image to a `static/blog/configuration` directory and link to it in exactly the
|
||||||
same way as if you had colocated it. If you do this, the generated files will be identical to
|
same way as if you had co-located it. If you do this, the generated files will be identical to those
|
||||||
if you had colocated; the only difference will be that all static files will be saved in the
|
obtained if you had co-located the image; the only difference will be that all static files will be saved in the
|
||||||
static folder rather than in the content folder. Depending on your organizational needs, this
|
static directory rather than in the content directory. The choice depends on your organizational needs.
|
||||||
may be better or worse.
|
* Or you could save the image to some arbitrary directory within the static directory. For example,
|
||||||
* Or you could save the image to some arbitrary folder within the static folder. For example,
|
you could save all images to `static/images`. Using this approach, you can no longer use relative links. Instead,
|
||||||
you could save all images to `static/images`. Using this approach, you would no longer be able
|
you must use an absolute link to `images/[filename]` to access your
|
||||||
to use relative links, but could use an absolute link to `images/[filename]` to access your
|
|
||||||
image. This might be preferable for small sites or for sites that associate images with
|
image. This might be preferable for small sites or for sites that associate images with
|
||||||
multiple pages (e.g., logo images that appear on every page).
|
multiple pages (e.g., logo images that appear on every page).
|
||||||
|
|
|
@ -6,98 +6,140 @@ weight = 30
|
||||||
A page is any file ending with `.md` in the `content` directory, except files
|
A page is any file ending with `.md` in the `content` directory, except files
|
||||||
named `_index.md`.
|
named `_index.md`.
|
||||||
|
|
||||||
If a file ending with `.md` is named `index.md`, then it will generate a page
|
If a file ending with `.md` is named `index.md`, it will generate a page
|
||||||
with the name of the containing folder (for example, `/content/about/index.md` would
|
with the name of its directory (for example, `/content/about/index.md` would
|
||||||
create a page at `[base_url]/about`). (Note the lack of an underscore; if the file
|
create a page at `[base_url]/about`). (Note the lack of an underscore; if the file
|
||||||
were named `_index.md`, then it would create a **section** at `[base_url]/about`, as
|
were named `_index.md`, then it would create a **section** at `[base_url]/about`, as
|
||||||
discussed in the prior part of this documentation. But naming the file `index.md` will
|
discussed in a previous part of this documentation. In contrast, naming the file `index.md` will
|
||||||
create a **page** at `[base_url]/about`).
|
create a **page** at `[base_url]/about`).
|
||||||
|
|
||||||
If the file is given any name *other* than `index.md` or `_index.md`, then it will
|
If the file is given any name *other* than `index.md` or `_index.md`, then it will
|
||||||
create a page with that name (without the `.md`). So naming a file in the root of your
|
create a page with that name (without the `.md`). For example, naming a file in the root of your
|
||||||
content directory `about.md` would also create a page at `[base_url]/about`.
|
content directory `about.md` would create a page at `[base_url]/about`.
|
||||||
Another exception to that rule is that a filename starting with a datetime (YYYY-mm-dd or [a RFC3339 datetime](https://www.ietf.org/rfc/rfc3339.txt)) followed by
|
Another exception to this rule is that a filename starting with a datetime (YYYY-mm-dd or [an RFC3339 datetime](https://www.ietf.org/rfc/rfc3339.txt)) followed by
|
||||||
an underscore (`_`) or a dash (`-`) will use that date as the page date, unless already set
|
an underscore (`_`) or a dash (`-`) will use that date as the page date, unless already set
|
||||||
in the front-matter. The page name will be anything after `_`/`-` so a filename like `2018-10-10-hello-world.md` will
|
in the front matter. The page name will be anything after `_`/`-`, so the file `2018-10-10-hello-world.md` will
|
||||||
be available at `[base_url]/hello-world`. Note that the full RFC3339 datetime contains colons, which is not a valid
|
be available at `[base_url]/hello-world`. Note that the full RFC3339 datetime contains colons, which is not a valid
|
||||||
character in a filename on Windows.
|
character in a filename on Windows.
|
||||||
|
|
||||||
As you can see, creating an `about.md` file is exactly equivalent to creating an
|
As you can see, creating an `about.md` file is equivalent to creating an
|
||||||
`about/index.md` file. The only difference between the two methods is that creating
|
`about/index.md` file. The only difference between the two methods is that creating
|
||||||
the `about` folder allows you to use asset colocation, as discussed in the
|
the `about` directory allows you to use asset co-location, as discussed in the
|
||||||
[Overview](@/documentation/content/overview.md#assets-colocation) section of this documentation.
|
[overview](@/documentation/content/overview.md#asset-colocation) section.
|
||||||
|
|
||||||
## Front-matter
|
## Output paths
|
||||||
|
|
||||||
The front-matter is a set of metadata embedded in a file. In Zola,
|
For any page within your content folder, its output path will be defined by either:
|
||||||
it is at the beginning of the file, surrounded by `+++` and uses TOML.
|
|
||||||
|
|
||||||
While none of the front-matter variables are mandatory, the opening and closing `+++` are required.
|
- its `slug` frontmatter key
|
||||||
|
- its filename
|
||||||
|
|
||||||
Here is an example page with all the variables available. The values provided below are the default
|
Either way, these proposed path will be sanitized before being used.
|
||||||
values.
|
If `slugify_paths` is enabled in the site's config - the default - paths are [slugified](https://en.wikipedia.org/wiki/Clean_URL#Slug).
|
||||||
|
Otherwise, a simpler sanitation is performed, outputting only valid NTFS paths.
|
||||||
|
The following characters are removed: `<`, `>`, `:`, `/`, `|`, `?`, `*`, `#`, `\\`, `(`, `)`, `[`, `]` as well as newlines and tabulations.
|
||||||
|
Additionally, trailing whitespace and dots are removed and whitespaces are replaced by `_`.
|
||||||
|
|
||||||
|
**NOTE:** To produce URLs containing non-English characters (UTF8), `slugify_paths` needs to be set to `false`.
|
||||||
|
|
||||||
|
### Path from frontmatter
|
||||||
|
|
||||||
|
The output path for the page will first be read from the `slug` key in the page's frontmatter.
|
||||||
|
|
||||||
|
**Example:** (file `content/zines/mlf-kurdistan.md`)
|
||||||
|
|
||||||
|
```
|
||||||
|
+++
|
||||||
|
title = "Le mouvement des Femmes Libres, à la tête de la libération kurde"
|
||||||
|
slug = "femmes-libres-libération-kurde"
|
||||||
|
+++
|
||||||
|
This is my article.
|
||||||
|
```
|
||||||
|
|
||||||
|
This frontmatter will output the article to `[base_url]/zines/femmes-libres-libération-kurde` with `slugify_paths` disabled, and to `[base_url]/zines/femmes-libres-liberation-kurde` with `slugify_enabled` enabled.
|
||||||
|
|
||||||
|
### Path from filename
|
||||||
|
|
||||||
|
When the article's output path is not specified in the frontmatter, it is extracted from the file's path in the content folder. Consider a file `content/foo/bar/thing.md`. The output path is constructed:
|
||||||
|
- if the filename is `index.md`, its parent folder name (`bar`) is used as output path
|
||||||
|
- otherwise, the output path is extracted from `thing` (the filename without the `.md` extension)
|
||||||
|
|
||||||
|
If the path found starts with a datetime string (`YYYY-mm-dd` or [a RFC3339 datetime](https://www.ietf.org/rfc/rfc3339.txt)) followed by an underscore (`_`) or a dash (`-`), this date is removed from the output path and will be used as the page date (unless already set in the front-matter). Note that the full RFC3339 datetime contains colons, which is not a valid character in a filename on Windows.
|
||||||
|
|
||||||
|
The output path extracted from the file path is then slugified or not depending on the `slugify_paths` config, as explained previously.
|
||||||
|
|
||||||
|
**Example:** The file `content/blog/2018-10-10-hello-world.md` will generated a page available at will be available at `[base_url]/hello-world`.
|
||||||
|
|
||||||
|
## Front matter
|
||||||
|
|
||||||
|
The TOML front matter is a set of metadata embedded in a file at the beginning of the file enclosed
|
||||||
|
by triple pluses (`+++`).
|
||||||
|
|
||||||
|
Although none of the front matter variables are mandatory, the opening and closing `+++` are required.
|
||||||
|
|
||||||
|
Here is an example page with all the available variables. The values provided below are the
|
||||||
|
default values.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
title = ""
|
title = ""
|
||||||
description = ""
|
description = ""
|
||||||
|
|
||||||
# The date of the post.
|
# The date of the post.
|
||||||
# 2 formats are allowed: YYYY-MM-DD (2012-10-02) and RFC3339 (2002-10-02T15:00:00Z)
|
# Two formats are allowed: YYYY-MM-DD (2012-10-02) and RFC3339 (2002-10-02T15:00:00Z).
|
||||||
# Do not wrap dates in quotes, the line below only indicates that there is no default date.
|
# Do not wrap dates in quotes; the line below only indicates that there is no default date.
|
||||||
# If the section variable `sort_by` is set to `date`, then any page that lacks a `date`
|
# If the section variable `sort_by` is set to `date`, then any page that lacks a `date`
|
||||||
# will not be rendered.
|
# will not be rendered.
|
||||||
# Setting this overrides a date set in the filename.
|
# Setting this overrides a date set in the filename.
|
||||||
date =
|
date =
|
||||||
|
|
||||||
# The weight as defined in the Section page
|
# The weight as defined on the Section page of the documentation.
|
||||||
# If the section variable `sort_by` is set to `weight`, then any page that lacks a `weight`
|
# If the section variable `sort_by` is set to `weight`, then any page that lacks a `weight`
|
||||||
# will not be rendered.
|
# will not be rendered.
|
||||||
weight = 0
|
weight = 0
|
||||||
|
|
||||||
# A draft page is only loaded if the `--drafts` flag is passed to `zola build`, `zola serve` or `zola check`
|
# A draft page is only loaded if the `--drafts` flag is passed to `zola build`, `zola serve` or `zola check`.
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
# If filled, it will use that slug instead of the filename to make up the URL
|
# If set, this slug will be instead of the filename to make the URL.
|
||||||
# It will still use the section path though
|
# The section path will still be used.
|
||||||
slug = ""
|
slug = ""
|
||||||
|
|
||||||
# The path the content will appear at
|
# The path the content will appear at.
|
||||||
# If set, it cannot be an empty string and will override both `slug` and the filename.
|
# If set, it cannot be an empty string and will override both `slug` and the filename.
|
||||||
# The sections' path won't be used.
|
# The sections' path won't be used.
|
||||||
# It should not start with a `/` and the slash will be removed if it does
|
# It should not start with a `/` and the slash will be removed if it does.
|
||||||
path = ""
|
path = ""
|
||||||
|
|
||||||
# Use aliases if you are moving content but want to redirect previous URLs to the
|
# Use aliases if you are moving content but want to redirect previous URLs to the
|
||||||
# current one. This takes an array of path, not URLs.
|
# current one. This takes an array of paths, not URLs.
|
||||||
aliases = []
|
aliases = []
|
||||||
|
|
||||||
# Whether the page should be in the search index. This is only used if
|
# When set to "true", the page will be in the search index. This is only used if
|
||||||
# `build_search_index` is set to true in the config and the parent section
|
# `build_search_index` is set to "true" in the Zola configuration and the parent section
|
||||||
# hasn't set `in_search_index` to false in its front-matter
|
# hasn't set `in_search_index` to "false" in its front matter.
|
||||||
in_search_index = true
|
in_search_index = true
|
||||||
|
|
||||||
# Template to use to render this page
|
# Template to use to render this page.
|
||||||
template = "page.html"
|
template = "page.html"
|
||||||
|
|
||||||
# The taxonomies for that page. The keys need to be the same as the taxonomies
|
# The taxonomies for this page. The keys need to be the same as the taxonomy
|
||||||
# name configured in `config.toml` and the values an array of String like
|
# names configured in `config.toml` and the values are an array of String objects. For example,
|
||||||
# tags = ["rust", "web"]
|
# tags = ["rust", "web"].
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
|
|
||||||
# Your own data
|
# Your own data.
|
||||||
[extra]
|
[extra]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
You can ask Zola to create a summary if you only want to show the first
|
You can ask Zola to create a summary if, for example, you only want to show the first
|
||||||
paragraph of each page in a list for example.
|
paragraph of the page content in a list.
|
||||||
|
|
||||||
To do so, add <code><!-- more --></code> in your content at the point
|
To do so, add <code><!-- more --></code> in your content at the point
|
||||||
where you want the summary to end and the content up to that point will be also
|
where you want the summary to end. The content up to that point will be
|
||||||
available separately in the
|
available separately in the
|
||||||
[template](@/documentation/templates/pages-sections.md#page-variables).
|
[template](@/documentation/templates/pages-sections.md#page-variables).
|
||||||
|
|
||||||
An anchor link to this position named `continue-reading` is created, wrapped in a paragraph
|
A span element in this position with a `continue-reading` id is created, so you can link directly to it if needed. For example:
|
||||||
with a `zola-continue-reading` id, so you can link directly to it if needed for example:
|
`<a href="{{ page.permalink }}#continue-reading">Continue Reading</a>`.
|
||||||
`<a href="{{ page.permalink }}#continue-reading">Continue Reading</a>`
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ title = "Sass"
|
||||||
weight = 110
|
weight = 110
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Sass is a popular CSS extension language that approaches some of the harder
|
Sass is a popular CSS preprocessor that adds special features (e.g., variables, nested rules) to facilate the
|
||||||
parts of maintaining large sets of CSS rules. If you're curious about what Sass
|
maintenance of large sets of CSS rules. If you're curious about what Sass
|
||||||
is and why it might be useful for styling your static site, the following links
|
is and why it might be useful for styling your static site, the following links
|
||||||
may be of interest:
|
may be of interest:
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ may be of interest:
|
||||||
|
|
||||||
## Using Sass in Zola
|
## Using Sass in Zola
|
||||||
|
|
||||||
Zola processes any files with the `sass` or `scss` extensions in the `sass`
|
Zola processes any files with the `sass` or `scss` extension in the `sass`
|
||||||
folder, and places the processed output into a `css` file with the same folder
|
folder, and places the processed output into a `css` file with the same folder
|
||||||
structure and base name into the `public` folder:
|
structure and base name into the `public` folder:
|
||||||
|
|
||||||
|
@ -34,9 +34,9 @@ structure and base name into the `public` folder:
|
||||||
|
|
||||||
Files with a leading underscore in the name are not placed into the `public`
|
Files with a leading underscore in the name are not placed into the `public`
|
||||||
folder, but can still be used as `@import` dependencies. For more information, see the "Partials" section of
|
folder, but can still be used as `@import` dependencies. For more information, see the "Partials" section of
|
||||||
[Sass Basics](https://sass-lang.com/guide#partials).
|
[Sass Basics](https://sass-lang.com/guide).
|
||||||
|
|
||||||
Files with the `scss` extension use ["Sassy CSS" syntax](http://sass-lang.com/documentation/#Formatting),
|
Files with the `scss` extension use "Sassy CSS" syntax,
|
||||||
while files with the `sass` extension use the ["indented" syntax](http://sass-lang.com/documentation/file.INDENTED_SYNTAX.html).
|
while files with the `sass` extension use the "indented" syntax: <https://sass-lang.com/documentation/syntax>.
|
||||||
Zola will return an error if a `scss` and `sass` file exist with the same
|
Zola will return an error if `scss` and `sass` files with the same
|
||||||
base name in the same folder to avoid confusion -- see the example above.
|
base name exist in the same folder to avoid confusion -- see the example above.
|
||||||
|
|
|
@ -4,19 +4,19 @@ weight = 100
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Zola can build a search index from the sections and pages content to
|
Zola can build a search index from the sections and pages content to
|
||||||
be used by a JavaScript library: [elasticlunr](http://elasticlunr.com/).
|
be used by a JavaScript library such as [elasticlunr](http://elasticlunr.com/).
|
||||||
|
|
||||||
To enable it, you only need to set `build_search_index = true` in your `config.toml` and Zola will
|
To enable it, you only need to set `build_search_index = true` in your `config.toml` and Zola will
|
||||||
generate an index for the `default_language` set for all pages not excluded from the search index.
|
generate an index for the `default_language` set for all pages not excluded from the search index.
|
||||||
|
|
||||||
It is very important to set the `default_language` in your `config.toml` if you are writing a site not in
|
It is very important to set the `default_language` in your `config.toml` if you are writing a site not in
|
||||||
English: the index building pipelines are very different depending on the language.
|
English; the index building pipelines are very different depending on the language.
|
||||||
|
|
||||||
After `zola build` or `zola serve`, you should see two files in your static directory:
|
After `zola build` or `zola serve`, you should see two files in your static directory:
|
||||||
|
|
||||||
- `search_index.${default_language}.js`: so `search_index.en.js` for a default setup
|
- `search_index.${default_language}.js`: so `search_index.en.js` for a default setup
|
||||||
- `elasticlunr.min.js`
|
- `elasticlunr.min.js`
|
||||||
|
|
||||||
As each site will be different, Zola makes no assumptions about how your search and doesn't provide
|
As each site will be different, Zola makes no assumptions about your search function and doesn't provide
|
||||||
the JavaScript/CSS code to do an actual search and display results. You can however look at how this very site
|
the JavaScript/CSS code to do an actual search and display results. You can look at how this site
|
||||||
is implementing it to have an idea: [search.js](https://github.com/getzola/zola/tree/master/docs/static/search.js).
|
implements it to get an idea: [search.js](https://github.com/getzola/zola/tree/master/docs/static/search.js).
|
||||||
|
|
|
@ -3,33 +3,34 @@ title = "Section"
|
||||||
weight = 20
|
weight = 20
|
||||||
+++
|
+++
|
||||||
|
|
||||||
A section is created whenever a folder (or subfolder) in the `content` section contains an
|
A section is created whenever a directory (or subdirectory) in the `content` section contains an
|
||||||
`_index.md` file. If a folder does not contain an `_index.md` file, no section will be
|
`_index.md` file. If a directory does not contain an `_index.md` file, no section will be
|
||||||
created, but markdown files within that folder will still create pages (known as orphan pages).
|
created, but Markdown files within that directory will still create pages (known as orphan pages).
|
||||||
|
|
||||||
The index page (i.e., the page displayed when a user browses to your `base_url`) is a section,
|
The index page (i.e., the page displayed when a user browses to your `base_url`) is a section,
|
||||||
which is created whether or not you add an `_index.md` file at the root of your `content` folder.
|
which is created whether or not you add an `_index.md` file at the root of your `content` directory.
|
||||||
If you do not create an `_index.md` file in your content directory, this main content section will
|
If you do not create an `_index.md` file in your content directory, this main content section will
|
||||||
not have any content or metadata. If you would like to add content or metadata, you can add an
|
not have any content or metadata. If you would like to add content or metadata, you can add an
|
||||||
`_index.md` file at the root of the `content` folder and edit it just as you would edit any other
|
`_index.md` file at the root of the `content` directory and edit it just as you would edit any other
|
||||||
`_index.md` file; your `index.html` template will then have access to that content and metadata.
|
`_index.md` file; your `index.html` template will then have access to that content and metadata.
|
||||||
|
|
||||||
Any non-Markdown file in the section folder is added to the `assets` collection of the section, as explained in the [Content Overview](@/documentation/content/overview.md#assets-colocation). These files are then available from the Markdown using relative links.
|
Any non-Markdown file in a section directory is added to the `assets` collection of the section, as explained in the
|
||||||
|
[content overview](@/documentation/content/overview.md#asset-colocation). These files are then available in the
|
||||||
|
Markdown file using relative links.
|
||||||
|
|
||||||
## Front-matter
|
## Front matter
|
||||||
|
|
||||||
The `_index.md` file within a folder defines the content and metadata for that section. To set
|
The `_index.md` file within a directory defines the content and metadata for that section. To set
|
||||||
the metadata, add front matter to the file.
|
the metadata, add front matter to the file.
|
||||||
|
|
||||||
The front-matter is a set of metadata embedded in a file. In Zola,
|
The TOML front matter is a set of metadata embedded in a file at the beginning of the file enclosed by triple pluses (`+++`).
|
||||||
it is at the beginning of the file, surrounded by `+++` and uses TOML.
|
|
||||||
|
|
||||||
After the closing `+++`, you can add content that will be parsed as markdown and will be available
|
After the closing `+++`, you can add content, which will be parsed as Markdown and made available
|
||||||
to your templates through the `section.content` variable.
|
to your templates through the `section.content` variable.
|
||||||
|
|
||||||
While none of the front-matter variables are mandatory, the opening and closing `+++` are required.
|
Although none of the front matter variables are mandatory, the opening and closing `+++` are required.
|
||||||
|
|
||||||
Here is an example `_index.md` with all the variables available. The values provided below are the
|
Here is an example `_index.md` with all the available variables. The values provided below are the
|
||||||
default values.
|
default values.
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,80 +39,80 @@ title = ""
|
||||||
|
|
||||||
description = ""
|
description = ""
|
||||||
|
|
||||||
# Whether to sort pages by "date", "weight", or "none". More on that below
|
# Used to sort pages by "date", "weight" or "none". See below for more information.
|
||||||
sort_by = "none"
|
sort_by = "none"
|
||||||
|
|
||||||
# Used by the parent section to order its subsections.
|
# Used by the parent section to order its subsections.
|
||||||
# Lower values have priority.
|
# Lower values have higher priority.
|
||||||
weight = 0
|
weight = 0
|
||||||
|
|
||||||
# Template to use to render this section page
|
# Template to use to render this section page.
|
||||||
template = "section.html"
|
template = "section.html"
|
||||||
|
|
||||||
# Apply the given template to ALL pages below the section, recursively.
|
# The given template is applied to ALL pages below the section, recursively.
|
||||||
# If you have several nested sections each with a page_template set, the page
|
# If you have several nested sections, each with a page_template set, the page
|
||||||
# will always use the closest to itself.
|
# will always use the closest to itself.
|
||||||
# However, a page own `template` variable will always have priority.
|
# However, a page's own `template` variable will always have priority.
|
||||||
# Not set by default
|
# Not set by default.
|
||||||
page_template =
|
page_template =
|
||||||
|
|
||||||
# How many pages to be displayed per paginated page.
|
# This sets the number of pages to be displayed per paginated page.
|
||||||
# No pagination will happen if this isn't set or if the value is 0
|
# No pagination will happen if this isn't set or if the value is 0.
|
||||||
paginate_by = 0
|
paginate_by = 0
|
||||||
|
|
||||||
# If set, will be the path used by paginated page and the page number will be appended after it.
|
# If set, this will be the path used by the paginated page. The page number will be appended after this path.
|
||||||
# For example the default would be page/1
|
# The default is page/1.
|
||||||
paginate_path = "page"
|
paginate_path = "page"
|
||||||
|
|
||||||
# Whether to insert a link for each header like the ones you can see in this site if you hover one
|
# This determines whether to insert a link for each header like the ones you can see on this site if you hover over
|
||||||
# The default template can be overridden by creating a `anchor-link.html` in the `templates` directory
|
# a header.
|
||||||
# Options are "left", "right" and "none"
|
# The default template can be overridden by creating an `anchor-link.html` file in the `templates` directory.
|
||||||
|
# This value can be "left", "right" or "none".
|
||||||
insert_anchor_links = "none"
|
insert_anchor_links = "none"
|
||||||
|
|
||||||
# Whether the section pages should be in the search index. This is only used if
|
# If set to "true", the section pages will be in the search index. This is only used if
|
||||||
# `build_search_index` is set to true in the config
|
# `build_search_index` is set to "true" in the Zola configuration file.
|
||||||
in_search_index = true
|
in_search_index = true
|
||||||
|
|
||||||
# Whether to render that section homepage or not.
|
# If set to "true", the section homepage is rendered.
|
||||||
# Useful when the section is only there to organize things but is not meant
|
# Useful when the section is used to organize pages (not used directly).
|
||||||
# to be used directly
|
|
||||||
render = true
|
render = true
|
||||||
|
|
||||||
# Whether to redirect when landing on that section. Defaults to not being set.
|
# This determines whether to redirect when a user lands on the section. Defaults to not being set.
|
||||||
# Useful for the same reason as `render` but when you don't want a 404 when
|
# Useful for the same reason as `render` but when you don't want a 404 when
|
||||||
# landing on the root section page.
|
# landing on the root section page.
|
||||||
# Example: redirect_to = "documentation/content/overview"
|
# Example: redirect_to = "documentation/content/overview"
|
||||||
redirect_to = ""
|
redirect_to = ""
|
||||||
|
|
||||||
# Whether the section should pass its pages on to the parent section. Defaults to `false`.
|
# If set to "true", the section will pass its pages on to the parent section. Defaults to `false`.
|
||||||
# Useful when the section shouldn't split up the parent section, like
|
# Useful when the section shouldn't split up the parent section, like
|
||||||
# sections for each year under a posts section.
|
# sections for each year under a posts section.
|
||||||
transparent = false
|
transparent = false
|
||||||
|
|
||||||
# Use aliases if you are moving content but want to redirect previous URLs to the
|
# Use aliases if you are moving content but want to redirect previous URLs to the
|
||||||
# current one. This takes an array of path, not URLs.
|
# current one. This takes an array of paths, not URLs.
|
||||||
aliases = []
|
aliases = []
|
||||||
|
|
||||||
# Your own data
|
# Your own data.
|
||||||
[extra]
|
[extra]
|
||||||
```
|
```
|
||||||
|
|
||||||
Keep in mind that any configuration apply only to the direct pages, not to the subsections' pages.
|
Keep in mind that any configuration options apply only to the direct pages, not to the subsections' pages.
|
||||||
|
|
||||||
## Pagination
|
## Pagination
|
||||||
|
|
||||||
To enable pagination for a section's pages, simply set `paginate_by` to a positive number and it will automatically
|
To enable pagination for a section's pages, set `paginate_by` to a positive number. See
|
||||||
paginate by this much. See [pagination template documentation](@/documentation/templates/pagination.md) for more information
|
[pagination template documentation](@/documentation/templates/pagination.md) for more information
|
||||||
on what will be available in the template.
|
on what variables are available in the template.
|
||||||
|
|
||||||
You can also change the pagination path (the word displayed while paginated in the URL, like `page/1`)
|
You can also change the pagination path (the word displayed while paginated in the URL, like `page/1`)
|
||||||
by setting the `paginate_path` variable, which defaults to `page`.
|
by setting the `paginate_path` variable, which defaults to `page`.
|
||||||
|
|
||||||
## Sorting
|
## Sorting
|
||||||
It is very common for Zola templates to iterate over pages or sections
|
It is very common for Zola templates to iterate over pages or sections
|
||||||
to display all pages/sections a given directory. Consider a very simple
|
to display all pages/sections in a given directory. Consider a very simple
|
||||||
example: a `blog` directory with three files: `blog/Post_1.md`,
|
example: a `blog` directory with three files: `blog/Post_1.md`,
|
||||||
`blog/Post_2.md`, and `blog/Post_3.md`. To iterate over these posts and
|
`blog/Post_2.md` and `blog/Post_3.md`. To iterate over these posts and
|
||||||
create a list of links to the posts, a simple template might look like this:
|
create a list of links to the posts, a simple template might look like this:
|
||||||
|
|
||||||
```j2
|
```j2
|
||||||
|
@ -120,21 +121,21 @@ create a list of links to the posts, a simple template might look like this:
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
```
|
```
|
||||||
|
|
||||||
This would iterate over the posts, and would do so in a specific order
|
This would iterate over the posts in the order specified
|
||||||
based on the `sort_by` variable set in the `_index.md` page for the
|
by the `sort_by` variable set in the `_index.md` page for the corresponding
|
||||||
containing section. The `sort_by` variable can be given three values: `date`,
|
section. The `sort_by` variable can be given one of three values: `date`,
|
||||||
`weight`, and `none`. If no `sort_by` method is set, the pages will be
|
`weight` or `none`. If `sort_by` is not set, the pages will be
|
||||||
sorted in the `none` order, which is not intended to be used for sorted content.
|
sorted in the `none` order, which is not intended for sorted content.
|
||||||
|
|
||||||
Any page that is missing the data it needs to be sorted will be ignored and
|
Any page that is missing the data it needs to be sorted will be ignored and
|
||||||
won't be rendered. For example, if a page is missing the date variable the
|
won't be rendered. For example, if a page is missing the date variable and its
|
||||||
containing section sets `sort_by = "date"`, then that page will be ignored.
|
section sets `sort_by = "date"`, then that page will be ignored.
|
||||||
The terminal will warn you if this is happening.
|
The terminal will warn you if this occurs.
|
||||||
|
|
||||||
If several pages have the same date/weight/order, their permalink will be used
|
If several pages have the same date/weight/order, their permalink will be used
|
||||||
to break the tie following an alphabetical order.
|
to break the tie based on alphabetical order.
|
||||||
|
|
||||||
## Sorting Pages
|
## Sorting pages
|
||||||
The `sort_by` front-matter variable can have the following values:
|
The `sort_by` front-matter variable can have the following values:
|
||||||
|
|
||||||
### `date`
|
### `date`
|
||||||
|
@ -150,18 +151,18 @@ page gets `page.lighter` and `page.heavier` variables that contain the
|
||||||
pages with lighter and heavier weights, respectively.
|
pages with lighter and heavier weights, respectively.
|
||||||
|
|
||||||
When iterating through pages, you may wish to use the Tera `reverse` filter,
|
When iterating through pages, you may wish to use the Tera `reverse` filter,
|
||||||
which reverses the order of the pages. Thus, after using the `reverse` filter,
|
which reverses the order of the pages. For example, after using the `reverse` filter,
|
||||||
pages sorted by weight will be sorted from lightest (at the top) to heaviest
|
pages sorted by weight will be sorted from lightest (at the top) to heaviest
|
||||||
(at the bottom); pages sorted by date will be sorted from oldest (at the top)
|
(at the bottom); pages sorted by date will be sorted from oldest (at the top)
|
||||||
to newest (at the bottom).
|
to newest (at the bottom).
|
||||||
|
|
||||||
`reverse` has no effect on `page.later`/`page.earlier`/`page.heavier`/`page.lighter`.
|
`reverse` has no effect on `page.later`/`page.earlier` or `page.heavier`/`page.lighter`.
|
||||||
|
|
||||||
## Sorting Subsections
|
## Sorting subsections
|
||||||
Sorting sections is a bit less flexible: sections are always sorted by `weight`,
|
Sorting sections is a bit less flexible: sections are always sorted by `weight`,
|
||||||
and do not have any variables that point to the next heavier/lighter sections.
|
and do not have variables that point to the heavier/lighter sections.
|
||||||
|
|
||||||
Based on this, by default the lightest (lowest `weight`) subsections will be at
|
By default, the lightest (lowest `weight`) subsections will be at
|
||||||
the top of the list and the heaviest (highest `weight`) will be at the bottom;
|
the top of the list and the heaviest (highest `weight`) will be at the bottom;
|
||||||
the `reverse` filter reverses this order.
|
the `reverse` filter reverses this order.
|
||||||
|
|
||||||
|
@ -170,4 +171,4 @@ equally weighted sections. Thus, if the `weight` variable for your section is n
|
||||||
is set in a way that produces ties), then your sections will be sorted in
|
is set in a way that produces ties), then your sections will be sorted in
|
||||||
**random** order. Moreover, that order is determined at build time and will
|
**random** order. Moreover, that order is determined at build time and will
|
||||||
change with each site rebuild. Thus, if there is any chance that you will
|
change with each site rebuild. Thus, if there is any chance that you will
|
||||||
iterate over your sections, you should always assign them weight.
|
iterate over your sections, you should always assign them a weight.
|
||||||
|
|
|
@ -3,13 +3,14 @@ title = "Shortcodes"
|
||||||
weight = 40
|
weight = 40
|
||||||
+++
|
+++
|
||||||
|
|
||||||
While Markdown is good at writing, it isn't great when you need write inline
|
Although Markdown is good for writing, it isn't great when you need write inline
|
||||||
HTML to add some styling for example.
|
HTML to add some styling for example.
|
||||||
|
|
||||||
To solve this, Zola borrows the concept of [shortcodes](https://codex.wordpress.org/Shortcode_API)
|
To solve this, Zola borrows the concept of [shortcodes](https://codex.wordpress.org/Shortcode_API)
|
||||||
from WordPress.
|
from WordPress.
|
||||||
In our case, the shortcode corresponds to a template that is defined in the `templates/shortcodes` directory or a built-in one that can
|
In our case, a shortcode corresponds to a template defined in the `templates/shortcodes` directory or
|
||||||
be used in a Markdown file. If you want to use something similar to shortcodes in your templates, try [Tera macros](https://tera.netlify.com/docs#macros).
|
a built-in one that can be used in a Markdown file. If you want to use something similar to shortcodes in your templates, try [Tera macros](https://tera.netlify.com/docs/templates/#macros).
|
||||||
|
|
||||||
|
|
||||||
## Writing a shortcode
|
## Writing a shortcode
|
||||||
Let's write a shortcode to embed YouTube videos as an example.
|
Let's write a shortcode to embed YouTube videos as an example.
|
||||||
|
@ -28,44 +29,44 @@ following:
|
||||||
```
|
```
|
||||||
|
|
||||||
This template is very straightforward: an iframe pointing to the YouTube embed URL wrapped in a `<div>`.
|
This template is very straightforward: an iframe pointing to the YouTube embed URL wrapped in a `<div>`.
|
||||||
In terms of input, it expects at least one variable: `id`. Since the other variables
|
In terms of input, this shortcode expects at least one variable: `id`. Because the other variables
|
||||||
are in a `if` statement, we can assume they are optional.
|
are in an `if` statement, they are optional.
|
||||||
|
|
||||||
That's it, Zola will now recognise this template as a shortcode named `youtube` (the filename minus the `.html` extension).
|
That's it. Zola will now recognise this template as a shortcode named `youtube` (the filename minus the `.html` extension).
|
||||||
|
|
||||||
The markdown renderer will wrap an inline HTML node like `<a>` or `<span>` into a paragraph. If you want to disable that,
|
The Markdown renderer will wrap an inline HTML node such as `<a>` or `<span>` into a paragraph.
|
||||||
simply wrap your shortcode in a `div`.
|
If you want to disable this behaviour, wrap your shortcode in a `<div>`.
|
||||||
|
|
||||||
Shortcodes are rendered before parsing the markdown so it doesn't have access to the table of contents. Because of that,
|
Shortcodes are rendered before the Markdown is parsed so they don't have access to the table of contents. Because of that,
|
||||||
you also cannot use the `get_page`/`get_section`/`get_taxonomy` global function. It might work while running `zola serve` because
|
you also cannot use the `get_page`/`get_section`/`get_taxonomy` global functions. It might work while running
|
||||||
it has been loaded but it will fail during `zola build`.
|
`zola serve` because it has been loaded but it will fail during `zola build`.
|
||||||
|
|
||||||
## Using shortcodes
|
## Using shortcodes
|
||||||
|
|
||||||
There are two kinds of shortcodes:
|
There are two kinds of shortcodes:
|
||||||
|
|
||||||
- ones that do not take a body like the YouTube example above
|
- ones that do not take a body, such as the YouTube example above
|
||||||
- ones that do, a quote for example
|
- ones that do, such as one that styles a quote
|
||||||
|
|
||||||
In both cases, their arguments must be named and they will all be passed to the template.
|
In both cases, the arguments must be named and they will all be passed to the template.
|
||||||
|
|
||||||
Lastly, a shortcode name (and thus the corresponding `.html` file) as well as the arguments name
|
Lastly, a shortcode name (and thus the corresponding `.html` file) as well as the argument names
|
||||||
can only contain numbers, letters and underscores, or in Regex terms the following: `[0-9A-Za-z_]`.
|
can only contain numbers, letters and underscores, or in Regex terms `[0-9A-Za-z_]`.
|
||||||
While theoretically an argument name could be a number, it will not be possible to use it in the template in that case.
|
Although theoretically an argument name could be a number, it will not be possible to use such an argument in the template.
|
||||||
|
|
||||||
Argument values can be of 5 types:
|
Argument values can be of one of five types:
|
||||||
|
|
||||||
- string: surrounded by double quotes, single quotes or backticks
|
- string: surrounded by double quotes, single quotes or backticks
|
||||||
- bool: `true` or `false`
|
- bool: `true` or `false`
|
||||||
- float: a number with a `.` in it
|
- float: a number with a decimal point (e.g., 1.2)
|
||||||
- integer: a number without a `.` in it
|
- integer: a whole number or its negative counterpart (e.g., 3)
|
||||||
- array: an array of any kind of values, except arrays
|
- array: an array of any kind of value, except arrays
|
||||||
|
|
||||||
Malformed values will be silently ignored.
|
Malformed values will be silently ignored.
|
||||||
|
|
||||||
Both type of shortcodes will also get either a `page` or `section` variable depending on where they were used and a `config`
|
Both types of shortcode will also get either a `page` or `section` variable depending on where they were used
|
||||||
one. Those values will overwrite any arguments passed to a shortcode so shortcodes should not use arguments called like one
|
and a `config` variable. These values will overwrite any arguments passed to a shortcode so these variable names
|
||||||
of these.
|
should not be used as argument names in shortcodes.
|
||||||
|
|
||||||
### Shortcodes without body
|
### Shortcodes without body
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ Note that if you want to have some content that looks like a shortcode but not h
|
||||||
you will need to escape it by using `{{/*` and `*/}}` instead of `{{` and `}}`.
|
you will need to escape it by using `{{/*` and `*/}}` instead of `{{` and `}}`.
|
||||||
|
|
||||||
### Shortcodes with body
|
### Shortcodes with body
|
||||||
For example, let's imagine we have the following shortcode `quote.html` template:
|
Let's imagine that we have the following shortcode `quote.html` template:
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
<blockquote>
|
<blockquote>
|
||||||
|
@ -95,7 +96,7 @@ For example, let's imagine we have the following shortcode `quote.html` template
|
||||||
</blockquote>
|
</blockquote>
|
||||||
```
|
```
|
||||||
|
|
||||||
We could use it in our markup file like so:
|
We could use it in our Markdown file like so:
|
||||||
|
|
||||||
```md
|
```md
|
||||||
As someone said:
|
As someone said:
|
||||||
|
@ -106,7 +107,7 @@ A quote
|
||||||
```
|
```
|
||||||
|
|
||||||
The body of the shortcode will be automatically passed down to the rendering context as the `body` variable and needs
|
The body of the shortcode will be automatically passed down to the rendering context as the `body` variable and needs
|
||||||
to be in a newline.
|
to be on a new line.
|
||||||
|
|
||||||
If you want to have some content that looks like a shortcode but not have Zola try to render it,
|
If you want to have some content that looks like a shortcode but not have Zola try to render it,
|
||||||
you will need to escape it by using `{%/*` and `*/%}` instead of `{%` and `%}`. You won't need to escape
|
you will need to escape it by using `{%/*` and `*/%}` instead of `{%` and `%}`. You won't need to escape
|
||||||
|
@ -124,8 +125,8 @@ Embed a responsive player for a YouTube video.
|
||||||
The arguments are:
|
The arguments are:
|
||||||
|
|
||||||
- `id`: the video id (mandatory)
|
- `id`: the video id (mandatory)
|
||||||
- `class`: a class to add the `div` surrounding the iframe
|
- `class`: a class to add to the `<div>` surrounding the iframe
|
||||||
- `autoplay`: whether to autoplay the video on load
|
- `autoplay`: when set to "true", the video autoplays on load
|
||||||
|
|
||||||
Usage example:
|
Usage example:
|
||||||
|
|
||||||
|
@ -147,7 +148,7 @@ Embed a player for a Vimeo video.
|
||||||
The arguments are:
|
The arguments are:
|
||||||
|
|
||||||
- `id`: the video id (mandatory)
|
- `id`: the video id (mandatory)
|
||||||
- `class`: a class to add the `div` surrounding the iframe
|
- `class`: a class to add to the `<div>` surrounding the iframe
|
||||||
|
|
||||||
Usage example:
|
Usage example:
|
||||||
|
|
||||||
|
@ -167,7 +168,7 @@ Embed a player for a Streamable video.
|
||||||
The arguments are:
|
The arguments are:
|
||||||
|
|
||||||
- `id`: the video id (mandatory)
|
- `id`: the video id (mandatory)
|
||||||
- `class`: a class to add the `div` surrounding the iframe
|
- `class`: a class to add to the `<div>` surrounding the iframe
|
||||||
|
|
||||||
Usage example:
|
Usage example:
|
||||||
|
|
||||||
|
@ -188,7 +189,7 @@ The arguments are:
|
||||||
|
|
||||||
- `url`: the url to the gist (mandatory)
|
- `url`: the url to the gist (mandatory)
|
||||||
- `file`: by default, the shortcode will pull every file from the URL unless a specific filename is requested
|
- `file`: by default, the shortcode will pull every file from the URL unless a specific filename is requested
|
||||||
- `class`: a class to add the `div` surrounding the iframe
|
- `class`: a class to add to the `<div>` surrounding the iframe
|
||||||
|
|
||||||
Usage example:
|
Usage example:
|
||||||
|
|
||||||
|
|
|
@ -17,115 +17,114 @@ let highlight = true;
|
||||||
|
|
||||||
````
|
````
|
||||||
|
|
||||||
You can replace the `rust` by the language you want to highlight or not put anything to get it
|
You can replace `rust` with another language or not put anything to get the text
|
||||||
interpreted as plain text.
|
interpreted as plain text.
|
||||||
|
|
||||||
Here is a full list of the supported languages and the short names you can use:
|
Here is a full list of supported languages and their short names:
|
||||||
|
|
||||||
```
|
```
|
||||||
- Plain Text -> ["txt"]
|
|
||||||
- Assembly x86 (NASM) -> ["asm", "inc", "nasm"]
|
|
||||||
- Crystal -> ["cr"]
|
|
||||||
- Dart -> ["dart"]
|
|
||||||
- Elixir -> ["ex", "exs"]
|
|
||||||
- fsharp -> ["fs"]
|
|
||||||
- Handlebars -> ["handlebars", "handlebars.html", "hbr", "hbrs", "hbs", "hdbs", "hjs", "mu", "mustache", "rac", "stache", "template", "tmpl"]
|
|
||||||
- Jinja2 -> ["j2", "jinja2"]
|
|
||||||
- Julia -> ["jl"]
|
|
||||||
- Kotlin -> ["kt", "kts"]
|
|
||||||
- Less -> ["less", "css.less"]
|
|
||||||
- MiniZinc (MZN) -> ["mzn", "dzn"]
|
|
||||||
- Nim -> ["nim", "nims"]
|
|
||||||
- ASP -> ["asa"]
|
|
||||||
- HTML (ASP) -> ["asp"]
|
|
||||||
- ActionScript -> ["as"]
|
- ActionScript -> ["as"]
|
||||||
- AppleScript -> ["applescript", "script editor"]
|
- AppleScript -> ["applescript", "script editor"]
|
||||||
|
- ASP -> ["asa"]
|
||||||
|
- Assembly x86 (NASM) -> ["asm", "inc", "nasm"]
|
||||||
- Batch File -> ["bat", "cmd"]
|
- Batch File -> ["bat", "cmd"]
|
||||||
- NAnt Build File -> ["build"]
|
- BibTeX -> ["bib"]
|
||||||
- C# -> ["cs", "csx"]
|
- Bourne Again Shell (bash) -> [".bash_aliases", ".bash_completions", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".profile", ".textmate_init", ".zshrc", "bash", "fish", "sh", "zsh"]
|
||||||
- C++ -> ["cpp", "cc", "cp", "cxx", "c++", "C", "h", "hh", "hpp", "hxx", "h++", "inl", "ipp"]
|
|
||||||
- C -> ["c", "h"]
|
- C -> ["c", "h"]
|
||||||
- CSS -> ["css", "css.erb", "css.liquid"]
|
- C# -> ["cs", "csx"]
|
||||||
|
- C++ -> ["C", "c++", "cc", "cp", "cpp", "cxx", "h", "h++", "hh", "hpp", "hxx", "inl", "ipp"]
|
||||||
- Clojure -> ["clj"]
|
- Clojure -> ["clj"]
|
||||||
|
- CMake -> ["CMakeLists.txt", "cmake"]
|
||||||
|
- CMake C Header -> ["h.in"]
|
||||||
|
- CMake C++ Header -> ["h++.in", "hh.in", "hpp.in", "hxx.in"]
|
||||||
|
- CMakeCache -> ["CMakeCache.txt"]
|
||||||
|
- Crystal -> ["cr"]
|
||||||
|
- CSS -> ["css", "css.erb", "css.liquid"]
|
||||||
- D -> ["d", "di"]
|
- D -> ["d", "di"]
|
||||||
|
- Dart -> ["dart"]
|
||||||
- Diff -> ["diff", "patch"]
|
- Diff -> ["diff", "patch"]
|
||||||
- Erlang -> ["erl", "hrl", "Emakefile", "emakefile"]
|
- Elixir -> ["ex", "exs"]
|
||||||
- HTML (Erlang) -> ["yaws"]
|
- Elm -> ["elm"]
|
||||||
- Git Attributes -> ["attributes", "gitattributes", ".gitattributes"]
|
- Erlang -> ["Emakefile", "emakefile", "erl", "hrl"]
|
||||||
|
- fsharp -> ["fs"]
|
||||||
|
- Generic Config -> [".dircolors", ".gitattributes", ".gitignore", ".gitmodules", ".inputrc", "Doxyfile", "cfg", "conf", "config", "dircolors", "gitattributes", "gitignore", "gitmodules", "ini", "inputrc", "mak", "mk", "pro"]
|
||||||
|
- Git Attributes -> [".gitattributes", "attributes", "gitattributes"]
|
||||||
- Git Commit -> ["COMMIT_EDITMSG", "MERGE_MSG", "TAG_EDITMSG"]
|
- Git Commit -> ["COMMIT_EDITMSG", "MERGE_MSG", "TAG_EDITMSG"]
|
||||||
- Git Config -> ["gitconfig", ".gitconfig", ".gitmodules"]
|
- Git Config -> [".gitconfig", ".gitmodules", "gitconfig"]
|
||||||
- Git Ignore -> ["exclude", "gitignore", ".gitignore"]
|
- Git Ignore -> [".gitignore", "exclude", "gitignore"]
|
||||||
- Git Link -> [".git"]
|
- Git Link -> [".git"]
|
||||||
- Git Log -> ["gitlog"]
|
- Git Log -> ["gitlog"]
|
||||||
- Git Rebase Todo -> ["git-rebase-todo"]
|
- Git Rebase Todo -> ["git-rebase-todo"]
|
||||||
- Go -> ["go"]
|
- Go -> ["go"]
|
||||||
- Graphviz (DOT) -> ["dot", "DOT", "gv"]
|
- Graphviz (DOT) -> ["DOT", "dot", "gv"]
|
||||||
- Groovy -> ["groovy", "gvy", "gradle", "Jenkinsfile"]
|
- Groovy -> ["Jenkinsfile", "gradle", "groovy", "gvy"]
|
||||||
- HTML -> ["html", "htm", "shtml", "xhtml"]
|
- Handlebars -> ["handlebars", "handlebars.html", "hbr", "hbrs", "hbs", "hdbs", "hjs", "mu", "mustache", "rac", "stache", "template", "tmpl"]
|
||||||
- Haskell -> ["hs"]
|
- Haskell -> ["hs"]
|
||||||
- Literate Haskell -> ["lhs"]
|
- HTML -> ["htm", "html", "shtml", "xhtml"]
|
||||||
- Java Server Page (JSP) -> ["jsp"]
|
- HTML (ASP) -> ["asp"]
|
||||||
- Java -> ["java", "bsh"]
|
- HTML (Erlang) -> ["yaws"]
|
||||||
|
- HTML (Rails) -> ["erb", "html.erb", "rails", "rhtml"]
|
||||||
|
- HTML (Tcl) -> ["adp"]
|
||||||
|
- Java -> ["bsh", "java"]
|
||||||
- Java Properties -> ["properties"]
|
- Java Properties -> ["properties"]
|
||||||
- JSON -> ["json", "sublime-settings", "sublime-menu", "sublime-keymap", "sublime-mousemap", "sublime-theme", "sublime-build", "sublime-project", "sublime-completions", "sublime-commands", "sublime-macro", "sublime-color-scheme"]
|
- Java Server Page (JSP) -> ["jsp"]
|
||||||
- JavaScript -> ["js", "htc"]
|
- JavaScript -> ["htc", "js"]
|
||||||
- BibTeX -> ["bib"]
|
- JavaScript (Rails) -> ["js.erb"]
|
||||||
- LaTeX -> ["tex", "ltx"]
|
- Jinja2 -> ["j2", "jinja2"]
|
||||||
- TeX -> ["sty", "cls"]
|
- JSON -> ["json", "sublime-build", "sublime-color-scheme", "sublime-commands", "sublime-completions", "sublime-keymap", "sublime-macro", "sublime-menu", "sublime-mousemap", "sublime-project", "sublime-settings", "sublime-theme"]
|
||||||
- Lisp -> ["lisp", "cl", "clisp", "l", "mud", "el", "scm", "ss", "lsp", "fasl"]
|
- Julia -> ["jl"]
|
||||||
|
- Kotlin -> ["kt", "kts"]
|
||||||
|
- LaTeX -> ["ltx", "tex"]
|
||||||
|
- Less -> ["css.less", "less"]
|
||||||
|
- Linker Script -> ["ld"]
|
||||||
|
- Lisp -> ["cl", "clisp", "el", "fasl", "l", "lisp", "lsp", "mud", "scm", "ss"]
|
||||||
|
- Literate Haskell -> ["lhs"]
|
||||||
- Lua -> ["lua"]
|
- Lua -> ["lua"]
|
||||||
- Makefile -> ["make", "GNUmakefile", "makefile", "Makefile", "makefile.am", "Makefile.am", "makefile.in", "Makefile.in", "OCamlMakefile", "mak", "mk"]
|
- Makefile -> ["GNUmakefile", "Makefile", "Makefile.am", "Makefile.in", "OCamlMakefile", "mak", "make", "makefile", "makefile.am", "makefile.in", "mk"]
|
||||||
- Markdown -> ["md", "mdown", "markdown", "markdn"]
|
- Markdown -> ["markdn", "markdown", "md", "mdown"]
|
||||||
- MATLAB -> ["matlab"]
|
- MATLAB -> ["matlab"]
|
||||||
|
- MiniZinc (MZN) -> ["dzn", "mzn"]
|
||||||
|
- NAnt Build File -> ["build"]
|
||||||
|
- Nim -> ["nim", "nims"]
|
||||||
|
- Nix -> ["nix"]
|
||||||
|
- Objective-C -> ["h", "m"]
|
||||||
|
- Objective-C++ -> ["M", "h", "mm"]
|
||||||
- OCaml -> ["ml", "mli"]
|
- OCaml -> ["ml", "mli"]
|
||||||
- OCamllex -> ["mll"]
|
- OCamllex -> ["mll"]
|
||||||
- OCamlyacc -> ["mly"]
|
- OCamlyacc -> ["mly"]
|
||||||
- Objective-C++ -> ["mm", "M", "h"]
|
- Pascal -> ["dpr", "p", "pas"]
|
||||||
- Objective-C -> ["m", "h"]
|
- Perl -> ["PL", "pl", "pm", "pod", "t"]
|
||||||
- PHP -> ["php", "php3", "php4", "php5", "php7", "phps", "phpt", "phtml"]
|
- PHP -> ["php", "php3", "php4", "php5", "php7", "phps", "phpt", "phtml"]
|
||||||
- Pascal -> ["pas", "p", "dpr"]
|
- Plain Text -> ["txt"]
|
||||||
- Perl -> ["pl", "pm", "pod", "t", "PL"]
|
- PowerShell -> ["ps1", "psd1", "psm1"]
|
||||||
- Python -> ["py", "py3", "pyw", "pyi", "pyx", "pyx.in", "pxd", "pxd.in", "pxi", "pxi.in", "rpy", "cpy", "SConstruct", "Sconstruct", "sconstruct", "SConscript", "gyp", "gypi", "Snakefile", "wscript"]
|
- Python -> ["SConscript", "SConstruct", "Sconstruct", "Snakefile", "cpy", "gyp", "gypi", "pxd", "pxd.in", "pxi", "pxi.in", "py", "py3", "pyi", "pyw", "pyx", "pyx.in", "rpy", "sconstruct", "wscript"]
|
||||||
- R -> ["R", "r", "s", "S", "Rprofile"]
|
- R -> ["R", "Rprofile", "S", "r", "s"]
|
||||||
- Rd (R Documentation) -> ["rd"]
|
- Rd (R Documentation) -> ["rd"]
|
||||||
- HTML (Rails) -> ["rails", "rhtml", "erb", "html.erb"]
|
|
||||||
- JavaScript (Rails) -> ["js.erb"]
|
|
||||||
- Ruby Haml -> ["haml", "sass"]
|
|
||||||
- Ruby on Rails -> ["rxml", "builder"]
|
|
||||||
- SQL (Rails) -> ["erbsql", "sql.erb"]
|
|
||||||
- Regular Expression -> ["re"]
|
|
||||||
- reStructuredText -> ["rst", "rest"]
|
|
||||||
- Ruby -> ["rb", "Appfile", "Appraisals", "Berksfile", "Brewfile", "capfile", "cgi", "Cheffile", "config.ru", "Deliverfile", "Fastfile", "fcgi", "Gemfile", "gemspec", "Guardfile", "irbrc", "jbuilder", "podspec", "prawn", "rabl", "rake", "Rakefile", "Rantfile", "rbx", "rjs", "ruby.rail", "Scanfile", "simplecov", "Snapfile", "thor", "Thorfile", "Vagrantfile"]
|
|
||||||
- Rust -> ["rs"]
|
|
||||||
- SQL -> ["sql", "ddl", "dml"]
|
|
||||||
- Scala -> ["scala", "sbt"]
|
|
||||||
- Bourne Again Shell (bash) -> ["sh", "bash", "zsh", "fish", ".bash_aliases", ".bash_completions", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".profile", ".textmate_init", ".zshrc"]
|
|
||||||
- HTML (Tcl) -> ["adp"]
|
|
||||||
- Tcl -> ["tcl"]
|
|
||||||
- Textile -> ["textile"]
|
|
||||||
- XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"]
|
|
||||||
- YAML -> ["yaml", "yml", "sublime-syntax"]
|
|
||||||
- PowerShell -> ["ps1", "psm1", "psd1"]
|
|
||||||
- SWI-Prolog -> ["pro"]
|
|
||||||
- Reason -> ["re", "rei"]
|
- Reason -> ["re", "rei"]
|
||||||
- CMake C Header -> ["h.in"]
|
- Regular Expression -> ["re"]
|
||||||
- CMake C++ Header -> ["hh.in", "hpp.in", "hxx.in", "h++.in"]
|
- reStructuredText -> ["rest", "rst"]
|
||||||
- CMake -> ["CMakeLists.txt", "cmake"]
|
- Ruby -> ["Appfile", "Appraisals", "Berksfile", "Brewfile", "Cheffile", "Deliverfile", "Fastfile", "Gemfile", "Guardfile", "Rakefile", "Rantfile", "Scanfile", "Snapfile", "Thorfile", "Vagrantfile", "capfile", "cgi", "config.ru", "fcgi", "gemspec", "irbrc", "jbuilder", "podspec", "prawn", "rabl", "rake", "rb", "rbx", "rjs", "ruby.rail", "simplecov", "thor"]
|
||||||
- CMakeCache -> ["CMakeCache.txt"]
|
- Ruby Haml -> ["haml", "sass"]
|
||||||
- Generic Config -> ["cfg", "conf", "config", "ini", "pro", "mak", "mk", "Doxyfile", "inputrc", ".inputrc", "dircolors", ".dircolors", "gitmodules", ".gitmodules", "gitignore", ".gitignore", "gitattributes", ".gitattributes"]
|
- Ruby on Rails -> ["builder", "rxml"]
|
||||||
- Elm -> ["elm"]
|
- Rust -> ["rs"]
|
||||||
- Linker Script -> ["ld"]
|
- Scala -> ["sbt", "scala"]
|
||||||
|
- SQL -> ["ddl", "dml", "sql"]
|
||||||
|
- SQL (Rails) -> ["erbsql", "sql.erb"]
|
||||||
|
- SWI-Prolog -> ["pro"]
|
||||||
- Swift -> ["swift"]
|
- Swift -> ["swift"]
|
||||||
- TOML -> ["toml", "tml"]
|
- Tcl -> ["tcl"]
|
||||||
|
- TeX -> ["cls", "sty"]
|
||||||
|
- Textile -> ["textile"]
|
||||||
|
- TOML -> ["Cargo.lock", "Gopkg.lock", "Pipfile", "tml", "toml"]
|
||||||
- TypeScript -> ["ts"]
|
- TypeScript -> ["ts"]
|
||||||
- TypeScriptReact -> ["tsx"]
|
- TypeScriptReact -> ["tsx"]
|
||||||
- VimL -> ["vim"]
|
- VimL -> ["vim"]
|
||||||
- Nix -> ["nix"]
|
- XML -> ["dtml", "opml", "rss", "svg", "tld", "xml", "xsd", "xslt"]
|
||||||
- TOML -> ["toml", "tml", "Cargo.lock", "Gopkg.lock", "Pipfile"]
|
- YAML -> ["sublime-syntax", "yaml", "yml"]
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to highlight a language not on that list, please open an issue or a pull request on the [Zola repo](https://github.com/getzola/zola).
|
If you want to highlight a language not on this list, please open an issue or a pull request on the [Zola repo](https://github.com/getzola/zola).
|
||||||
Alternatively, the `extra_syntaxes` config option can be used to add additional syntax files.
|
Alternatively, the `extra_syntaxes` configuration option can be used to add additional syntax files.
|
||||||
|
|
||||||
If your site source is laid out as follows:
|
If your site source is laid out as follows:
|
||||||
|
|
||||||
|
@ -144,4 +143,4 @@ If your site source is laid out as follows:
|
||||||
└── ...
|
└── ...
|
||||||
```
|
```
|
||||||
|
|
||||||
you would set your `extra_syntaxes` to `["syntaxes", "syntaxes/Sublime-Language1"]` in order to load `lang1.sublime-syntax` and `lang2.sublime-syntax`.
|
you would set your `extra_syntaxes` to `["syntaxes", "syntaxes/Sublime-Language1"]` to load `lang1.sublime-syntax` and `lang2.sublime-syntax`.
|
||||||
|
|
|
@ -5,15 +5,15 @@ weight = 60
|
||||||
|
|
||||||
Each page/section will automatically generate a table of contents for itself based on the headers present.
|
Each page/section will automatically generate a table of contents for itself based on the headers present.
|
||||||
|
|
||||||
It is available in the template through the `toc` variable.
|
It is available in the template through the `page.toc` or `section.toc` variable.
|
||||||
You can view the [template variables](@/documentation/templates/pages-sections.md#table-of-contents)
|
You can view the [template variables](@/documentation/templates/pages-sections.md#table-of-contents)
|
||||||
documentation for information on its structure.
|
documentation for information on its structure.
|
||||||
|
|
||||||
Here is an example of using that field to render a 2-level table of contents:
|
Here is an example of using that field to render a two-level table of contents:
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
<ul>
|
<ul>
|
||||||
{% for h1 in toc %}
|
{% for h1 in page.toc %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{h1.permalink | safe}}">{{ h1.title }}</a>
|
<a href="{{h1.permalink | safe}}">{{ h1.title }}</a>
|
||||||
{% if h1.children %}
|
{% if h1.children %}
|
||||||
|
@ -30,7 +30,7 @@ Here is an example of using that field to render a 2-level table of contents:
|
||||||
</ul>
|
</ul>
|
||||||
```
|
```
|
||||||
|
|
||||||
While headers are neatly ordered in that example, it will work just as well with disjoint headers.
|
While headers are neatly ordered in this example, it will work just as well with disjoint headers.
|
||||||
|
|
||||||
Note that all existing HTML tags from the title will NOT be present in the table of contents to
|
Note that all existing HTML tags from the title will NOT be present in the table of contents to
|
||||||
avoid various issues.
|
avoid various issues.
|
||||||
|
|
|
@ -5,32 +5,59 @@ weight = 90
|
||||||
|
|
||||||
Zola has built-in support for taxonomies.
|
Zola has built-in support for taxonomies.
|
||||||
|
|
||||||
The first step is to define the taxonomies in your [config.toml](@/documentation/getting-started/configuration.md).
|
## Configuration
|
||||||
|
|
||||||
A taxonomy has 5 variables:
|
A taxonomy has five variables:
|
||||||
|
|
||||||
- `name`: a required string that will be used in the URLs, usually the plural version (i.e. tags, categories etc)
|
- `name`: a required string that will be used in the URLs, usually the plural version (i.e., tags, categories, etc.)
|
||||||
- `paginate_by`: if this is set to a number, each term page will be paginated by this much.
|
- `paginate_by`: if this is set to a number, each term page will be paginated by this much.
|
||||||
- `paginate_path`: if set, will be the path used by paginated page and the page number will be appended after it.
|
- `paginate_path`: if set, this path will be used by the paginated page and the page number will be appended after it.
|
||||||
For example the default would be page/1
|
For example the default would be page/1.
|
||||||
- `rss`: if set to `true`, a RSS feed will be generated for each individual term.
|
- `rss`: if set to `true`, an RSS feed will be generated for each term.
|
||||||
- `lang`: only set this if you are making a multilingual site and want to indicate which language this taxonomy is for
|
- `lang`: only set this if you are making a multilingual site and want to indicate which language this taxonomy is for
|
||||||
|
|
||||||
Once this is done, you can then set taxonomies in your content and Zola will pick
|
**Example 1:** (one language)
|
||||||
them up:
|
|
||||||
|
```toml
|
||||||
|
taxonomies = [ name = "categories", rss = true ]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 2:** (multilingual site)
|
||||||
|
|
||||||
|
```toml
|
||||||
|
taxonomies = [
|
||||||
|
{name = "tags", lang = "fr"},
|
||||||
|
{name = "tags", lang = "eo"},
|
||||||
|
{name = "tags", lang = "en"},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using taxonomies
|
||||||
|
|
||||||
|
Once the configuration is done, you can then set taxonomies in your content and Zola will pick them up:
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
+++
|
+++
|
||||||
...
|
title = "Writing a static-site generator in Rust"
|
||||||
|
date = 2019-08-15
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
tags = ["rust", "web"]
|
tags = ["rust", "web"]
|
||||||
categories = ["programming"]
|
categories = ["programming"]
|
||||||
+++
|
+++
|
||||||
```
|
```
|
||||||
|
|
||||||
The taxonomy pages are available at the following paths:
|
## Output paths
|
||||||
|
|
||||||
|
In a similar manner to how section and pages calculate their output path:
|
||||||
|
- the taxonomy name is never slugified
|
||||||
|
- the taxonomy entry (eg. as specific tag) is slugified when `slugify_paths` is enabled in the configuration
|
||||||
|
|
||||||
|
The taxonomy pages are then available at the following paths:
|
||||||
|
|
||||||
```plain
|
```plain
|
||||||
$BASE_URL/$NAME/
|
$BASE_URL/$NAME/ (taxonomy)
|
||||||
$BASE_URL/$NAME/$SLUG
|
$BASE_URL/$NAME/$SLUG (taxonomy entry)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -3,25 +3,31 @@ title = "GitHub Pages"
|
||||||
weight = 30
|
weight = 30
|
||||||
+++
|
+++
|
||||||
|
|
||||||
By default, GitHub Pages uses Jekyll (A ruby based static site generator),
|
By default, GitHub Pages uses Jekyll (a ruby based static site generator),
|
||||||
but you can also publish any generated files provided you have an `index.html` file in the root of a branch called `gh-pages` or `master`, in addition you can also publish from a `docs` directory in your repository. That branch name can also be manually changed in the settings of a repository. **However** this only applies to publishing in a custom domain, i.e. if you want to publish to a GitHub provided web service under the `github.io` domain, you can **only** use the `master` branch of your repository as explained [here](https://help.github.com/en/articles/configuring-a-publishing-source-for-github-pages), so we will focus on the method which will work regardless of the domain.
|
but you can also publish any generated files provided you have an `index.html` file in the root of a branch called
|
||||||
|
`gh-pages` or `master`. In addition you can publish from a `docs` directory in your repository. That branch name can
|
||||||
|
also be manually changed in the settings of a repository. **However**, this only applies to publishing in a custom domain,
|
||||||
|
i.e., if you want to publish to a GitHub-provided web service under the `github.io` domain, you can **only** use the
|
||||||
|
`master` branch of your repository, as explained
|
||||||
|
[here](https://help.github.com/en/articles/configuring-a-publishing-source-for-github-pages),
|
||||||
|
so we will focus on the method that will work regardless of the domain.
|
||||||
|
|
||||||
We can use any CI server to build and deploy our site. For example:
|
We can use any continuous integration (CI) server to build and deploy our site. For example:
|
||||||
|
|
||||||
* [Github Actions](https://github.com/shalzz/zola-deploy-action)
|
* [Github Actions](https://github.com/shalzz/zola-deploy-action)
|
||||||
* [Travis CI](#travis-ci)
|
* [Travis CI](#travis-ci)
|
||||||
|
|
||||||
## Travis CI
|
## Travis CI
|
||||||
|
|
||||||
We are going to use [TravisCI](https://travis-ci.org) to automatically publish the site. If you are not using Travis already,
|
We are going to use [Travis CI](https://travis-ci.org) to automatically publish the site. If you are not using Travis
|
||||||
you will need to login with the GitHub OAuth and activate Travis for the repository.
|
already, you will need to login with the GitHub OAuth and activate Travis for the repository.
|
||||||
Don't forget to also check if your repository allows GitHub Pages in its settings.
|
Don't forget to also check if your repository allows GitHub Pages in its settings.
|
||||||
|
|
||||||
## Ensure Travis can access your theme
|
## Ensure that Travis can access your theme
|
||||||
|
|
||||||
Depending on how you added your theme Travis may not exactly know how to access
|
Depending on how you added your theme, Travis may not know how to access
|
||||||
it. The best way to ensure it will have full access to the theme is to use git
|
it. The best way to ensure that it will have full access to the theme is to use git
|
||||||
submodules. When doing this ensure you are using the `https` version of the URL.
|
submodules. When doing this, ensure that you are using the `https` version of the URL.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ git submodule add {THEME_URL} themes/{THEME_NAME}
|
$ git submodule add {THEME_URL} themes/{THEME_NAME}
|
||||||
|
@ -29,16 +35,17 @@ $ git submodule add {THEME_URL} themes/{THEME_NAME}
|
||||||
|
|
||||||
## Allowing Travis to push to GitHub
|
## Allowing Travis to push to GitHub
|
||||||
|
|
||||||
Before pushing anything, Travis needs a Github private access key in order to make changes to your repository.
|
Before pushing anything, Travis needs a Github private access key to make changes to your repository.
|
||||||
If you're already logged in to your account, just click [here](https://github.com/settings/tokens) to go to your tokens page.
|
If you're already logged in to your account, just click [here](https://github.com/settings/tokens) to go to
|
||||||
|
your tokens page.
|
||||||
Otherwise, navigate to `Settings > Developer Settings > Personal Access Tokens`.
|
Otherwise, navigate to `Settings > Developer Settings > Personal Access Tokens`.
|
||||||
Generate a new token, and give it any description you'd like.
|
Generate a new token and give it any description you'd like.
|
||||||
Under the "Select Scopes" section, give it repo permissions. Click "Generate token" to finish up.
|
Under the "Select Scopes" section, give it repo permissions. Click "Generate token" to finish up.
|
||||||
|
|
||||||
Your token will now be visible!
|
Your token will now be visible.
|
||||||
Copy it into your clipboard and head back to Travis.
|
Copy it into your clipboard and head back to Travis.
|
||||||
Once on Travis, click on your project, and navigate to "Settings". Scroll down to "Environment Variables" and input a name of `GH_TOKEN` with a value of your access token.
|
Once on Travis, click on your project, and navigate to "Settings". Scroll down to "Environment Variables" and input a name of `GH_TOKEN` with a value of your access token.
|
||||||
Make sure "Display value in build log" is off, and then click add. Now Travis has access to your repository.
|
Make sure that "Display value in build log" is off, and then click add. Now Travis has access to your repository.
|
||||||
|
|
||||||
## Setting up Travis
|
## Setting up Travis
|
||||||
|
|
||||||
|
@ -68,7 +75,7 @@ after_success: |
|
||||||
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git master
|
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git master
|
||||||
```
|
```
|
||||||
|
|
||||||
If your site is using a custom domain, you will need to mention it in the `ghp-import` command: `ghp-import -c vaporsoft.net -n public`
|
If your site is using a custom domain, you will need to mention it in the `ghp-import` command:
|
||||||
for example.
|
`ghp-import -c vaporsoft.net -n public` for example.
|
||||||
|
|
||||||
Credits: this page is based on the article https://vaporsoft.net/publishing-gutenberg-to-github/
|
Credits: this page is based on the article https://vaporsoft.net/publishing-gutenberg-to-github/
|
||||||
|
|
|
@ -9,7 +9,7 @@ We are going to use the GitLab CI runner to automatically publish the site (this
|
||||||
|
|
||||||
Your repository needs to be set up to be a user or group website. This means the name of the repository has to be in the correct format.
|
Your repository needs to be set up to be a user or group website. This means the name of the repository has to be in the correct format.
|
||||||
|
|
||||||
For example, under your username, `john`, you have to create a project called `john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`. Once you enable GitLab Pages for your project, your website will be published under `https://john.gitlab.io`.
|
For example, assuming that the username is `john`, you have to create a project called `john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`. Once you enable GitLab Pages for your project, your website will be published under `https://john.gitlab.io`.
|
||||||
|
|
||||||
Under your group `websites`, you created a project called `websites.gitlab.io`. Your project’s URL will be `https://gitlab.com/websites/websites.gitlab.io`. Once you enable GitLab Pages for your project, your website will be published under `https://websites.gitlab.io`.
|
Under your group `websites`, you created a project called `websites.gitlab.io`. Your project’s URL will be `https://gitlab.com/websites/websites.gitlab.io`. Once you enable GitLab Pages for your project, your website will be published under `https://websites.gitlab.io`.
|
||||||
|
|
||||||
|
@ -18,21 +18,21 @@ This guide assumes that your zola project is located in the root of your reposit
|
||||||
|
|
||||||
## Ensuring that the CI runner can access your theme
|
## Ensuring that the CI runner can access your theme
|
||||||
|
|
||||||
Depending on how you added your theme your repository may not contain it. The best way to ensure the theme will be added is to use
|
Depending on how you added your theme, your repository may not contain it. The best way to ensure that the theme will
|
||||||
submodules. When doing this ensure you are using the `https` version of the URL.
|
be added is to use submodules. When doing this, ensure that you are using the `https` version of the URL.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ git submodule add {THEME_URL} themes/{THEME_NAME}
|
$ git submodule add {THEME_URL} themes/{THEME_NAME}
|
||||||
```
|
```
|
||||||
|
|
||||||
For example, this could look like
|
For example, this could look like:
|
||||||
```shell
|
```shell
|
||||||
$ git submodule add https://github.com/getzola/hyde.git themes/hyde
|
$ git submodule add https://github.com/getzola/hyde.git themes/hyde
|
||||||
```
|
```
|
||||||
|
|
||||||
## Setting up the GitLab CI/CD Runner
|
## Setting up the GitLab CI/CD Runner
|
||||||
|
|
||||||
The second step is to tell the gitlab continous integration runner how to create the gitlab page.
|
The second step is to tell the GitLab continous integration runner how to create the GitLab page.
|
||||||
|
|
||||||
To do this, create a file called `.gitlab-ci.yml` in the root directory of your repository.
|
To do this, create a file called `.gitlab-ci.yml` in the root directory of your repository.
|
||||||
|
|
||||||
|
@ -62,7 +62,8 @@ pages:
|
||||||
- master
|
- master
|
||||||
```
|
```
|
||||||
|
|
||||||
Push this new file and... Tada! You're done! If you navigate to `settings > pages` you should be able to see something like this:
|
Push this new file and ... Tada! You're done! If you navigate to `settings > pages`, you should be able to see
|
||||||
|
something like this:
|
||||||
|
|
||||||
> Congratulations! Your pages are served under:
|
> Congratulations! Your pages are served under:
|
||||||
https://john.gitlab.io
|
https://john.gitlab.io
|
||||||
|
|
|
@ -4,12 +4,12 @@ weight = 20
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Netlify provides best practices like SSL, CDN distribution, caching and continuous deployment
|
Netlify provides best practices like SSL, CDN distribution, caching and continuous deployment
|
||||||
with no effort. This very site is hosted by Netlify and automatically deployed on commits.
|
with no effort. This site is hosted by Netlify and automatically deployed on commits.
|
||||||
|
|
||||||
If you don't have an account with Netlify, you can [sign up](https://app.netlify.com) for one.
|
If you don't have an account with Netlify, you can [sign up](https://app.netlify.com) for one.
|
||||||
|
|
||||||
|
|
||||||
## Automatic Deploys
|
## Automatic deploys
|
||||||
|
|
||||||
Once you are in the admin interface, you can add a site from a Git provider (GitHub, GitLab or Bitbucket). At the end
|
Once you are in the admin interface, you can add a site from a Git provider (GitHub, GitLab or Bitbucket). At the end
|
||||||
of this process, you can select the deploy settings for the project:
|
of this process, you can select the deploy settings for the project:
|
||||||
|
@ -20,27 +20,27 @@ Once you are in the admin interface, you can add a site from a Git provider (Git
|
||||||
- Environment variables: `ZOLA_VERSION` with for example `0.8.0` as value
|
- Environment variables: `ZOLA_VERSION` with for example `0.8.0` as value
|
||||||
|
|
||||||
With this setup, your site should be automatically deployed on every commit on master. For `ZOLA_VERSION`, you may
|
With this setup, your site should be automatically deployed on every commit on master. For `ZOLA_VERSION`, you may
|
||||||
use any of the tagged `release` versions in the GitHub repository — Netlify will automatically fetch the tagged version
|
use any of the tagged `release` versions in the GitHub repository. Netlify will automatically fetch the tagged version
|
||||||
and use it to build your site.
|
and use it to build your site.
|
||||||
|
|
||||||
However, if you want to use everything that Netlify gives you, you should also publish temporary sites for pull requests.
|
However, if you want to use everything that Netlify gives you, you should also publish temporary sites for pull requests.
|
||||||
|
|
||||||
This is done by adding the following `netlify.toml` file in your repository and removing the build command/publish directory in
|
This is done by adding the following `netlify.toml` file in your repository and removing the build command/publish
|
||||||
the admin interface.
|
directory in the admin interface.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[build]
|
[build]
|
||||||
# assuming the Zola site is in a docs folder, if it isn't you don't need
|
# This assumes that the Zola site is in a docs folder. If it isn't, you don't need
|
||||||
# to have a `base` variable but you do need the `publish` and `command`
|
# to have a `base` variable but you do need the `publish` and `command` variables.
|
||||||
base = "docs"
|
base = "docs"
|
||||||
publish = "docs/public"
|
publish = "docs/public"
|
||||||
command = "zola build"
|
command = "zola build"
|
||||||
|
|
||||||
[build.environment]
|
[build.environment]
|
||||||
# Set the version name that you want to use and Netlify will automatically use it
|
# Set the version name that you want to use and Netlify will automatically use it.
|
||||||
ZOLA_VERSION = "0.9.0"
|
ZOLA_VERSION = "0.9.0"
|
||||||
|
|
||||||
# The magic for deploying previews of branches
|
# The magic for deploying previews of branches.
|
||||||
# We need to override the base url with whatever url Netlify assigns to our
|
# We need to override the base url with whatever url Netlify assigns to our
|
||||||
# preview site. We do this using the Netlify environment variable
|
# preview site. We do this using the Netlify environment variable
|
||||||
# `$DEPLOY_PRIME_URL`.
|
# `$DEPLOY_PRIME_URL`.
|
||||||
|
@ -49,17 +49,18 @@ ZOLA_VERSION = "0.9.0"
|
||||||
command = "zola build --base-url $DEPLOY_PRIME_URL"
|
command = "zola build --base-url $DEPLOY_PRIME_URL"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Manual Deploys
|
## Manual deploys
|
||||||
If you would prefer to use a version of Zola that isn't a tagged release (for example, after having built Zola from
|
If you would prefer to use a version of Zola that isn't a tagged release (for example, after having built Zola from
|
||||||
source and made modifications), then you will need to manually deploy your `public` folder to Netlify. You can do this through
|
source and made modifications), then you will need to manually deploy your `public` folder to Netlify. You can do
|
||||||
Netlify's web GUI or via the command line.
|
this through Netlify's web GUI or via the command line.
|
||||||
|
|
||||||
For a command-line manual deploy, follow these steps:
|
For a command-line manual deploy, follow these steps:
|
||||||
1. Generate a `Personal Access Token` from the settings section of your Netlify account (*not* an OAuth Application)
|
1. Generate a `Personal Access Token` from the settings section of your Netlify account (*not* an OAuth Application).
|
||||||
2. Build your site with `zola build`
|
2. Build your site with `zola build`.
|
||||||
3. Create a zip folder containing the `public` directory
|
3. Create a zip folder containing the `public` directory.
|
||||||
4. Run the `curl` command below, filling in your values for PERSONAL_ACCESS_TOKEN_FROM_STEP_1, FILE_NAME.zip and SITE_NAME
|
4. Run the `curl` command below, filling in your values for PERSONAL_ACCESS_TOKEN_FROM_STEP_1, FILE_NAME.zip
|
||||||
5. (Optional) delete the zip folder
|
and SITE_NAME.
|
||||||
|
5. (Optional) delete the zip folder.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -H "Content-Type: application/zip" \
|
curl -H "Content-Type: application/zip" \
|
||||||
|
|
|
@ -5,20 +5,20 @@ weight = 2
|
||||||
|
|
||||||
Zola only has 4 commands: `init`, `build`, `serve` and `check`.
|
Zola only has 4 commands: `init`, `build`, `serve` and `check`.
|
||||||
|
|
||||||
You can view the help of the whole program by running `zola --help` and
|
You can view the help for the whole program by running `zola --help` and
|
||||||
the command help by running `zola <cmd> --help`.
|
that for a specific command by running `zola <cmd> --help`.
|
||||||
|
|
||||||
## init
|
## init
|
||||||
|
|
||||||
Creates the directory structure used by Zola at the given directory after asking a few basic configuration questions.
|
Creates the directory structure used by Zola at the given directory after asking a few basic configuration questions.
|
||||||
Any choices made during those prompts can easily be changed by modifying the `config.toml`.
|
Any choices made during these prompts can be easily changed by modifying `config.toml`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ zola init my_site
|
$ zola init my_site
|
||||||
$ zola init
|
$ zola init
|
||||||
```
|
```
|
||||||
|
|
||||||
If the `my_site` folder already exists, Zola will only populate it if it does not contain non-hidden files (dotfiles are ignored). If no `my_site` argument is passed, Zola will try to populate the current directory.
|
If the `my_site` directory already exists, Zola will only populate it if it contains only hidden files (dotfiles are ignored). If no `my_site` argument is passed, Zola will try to populate the current directory.
|
||||||
|
|
||||||
You can initialize a git repository and a Zola site directly from within a new folder:
|
You can initialize a git repository and a Zola site directly from within a new folder:
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ $ zola init
|
||||||
|
|
||||||
## build
|
## build
|
||||||
|
|
||||||
This will build the whole site in the `public` directory after deleting it.
|
This will build the whole site in the `public` directory (if this directory already exists, it is overwritten).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ zola build
|
$ zola build
|
||||||
|
@ -44,34 +44,38 @@ $ zola build --base-url $DEPLOY_URL
|
||||||
This is useful for example when you want to deploy previews of a site to a dynamic URL, such as Netlify
|
This is useful for example when you want to deploy previews of a site to a dynamic URL, such as Netlify
|
||||||
deploy previews.
|
deploy previews.
|
||||||
|
|
||||||
You can override the default output directory 'public' by passing a other value to the `output-dir` flag.
|
You can override the default output directory `public` by passing another value to the `output-dir` flag.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ zola build --output-dir $DOCUMENT_ROOT
|
$ zola build --output-dir $DOCUMENT_ROOT
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also point to another config file than `config.toml` like so - the position of the `config` option is important:
|
You can point to a config file other than `config.toml` like so (note that the position of the `config` option is important):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ zola --config config.staging.toml build
|
$ zola --config config.staging.toml build
|
||||||
```
|
```
|
||||||
|
|
||||||
By defaults, drafts are not loaded. If you wish to include them, pass the `--drafts` flag.
|
You can also process a project from a different directory with the `root` flag. If building a project 'out-of-tree' with the `root` flag, you may want to combine it with the `output-dir` flag. (Note that like `config`, the position is important):
|
||||||
|
```bash
|
||||||
|
$ zola --root /path/to/project build
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, drafts are not loaded. If you wish to include them, pass the `--drafts` flag.
|
||||||
|
|
||||||
## serve
|
## serve
|
||||||
|
|
||||||
This will build and serve the site using a local server. You can also specify
|
This will build and serve the site using a local server. You can also specify
|
||||||
the interface/port combination to use if you want something different than the default (`127.0.0.1:1111`).
|
the interface/port combination to use if you want something different than the default (`127.0.0.1:1111`).
|
||||||
|
|
||||||
You can also specify different addresses for the interface and base_url using `-u`/`--base-url`, for example
|
You can also specify different addresses for the interface and base_url using `--interface` and `-u`/`--base-url`, respectively, if for example you are running Zola in a Docker container.
|
||||||
if you are running zola in a Docker container.
|
|
||||||
|
|
||||||
Use the `--open` flag to automatically open the locally hosted instance in your
|
Use the `--open` flag to automatically open the locally hosted instance in your
|
||||||
web browser.
|
web browser.
|
||||||
|
|
||||||
In the event you don't want zola to run a local webserver, you can use the `--watch-only` flag.
|
In the event you don't want Zola to run a local webserver, you can use the `--watch-only` flag.
|
||||||
|
|
||||||
Before starting, it will delete the public directory to ensure it starts from a clean slate.
|
Before starting, Zola will delete the `public` directory to start from a clean slate.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ zola serve
|
$ zola serve
|
||||||
|
@ -84,40 +88,40 @@ $ zola serve --watch-only
|
||||||
$ zola serve --open
|
$ zola serve --open
|
||||||
```
|
```
|
||||||
|
|
||||||
The serve command will watch all your content and will provide live reload, without
|
The serve command will watch all your content and provide live reload without
|
||||||
hard refresh if possible.
|
a hard refresh if possible.
|
||||||
|
|
||||||
Zola does a best-effort to live reload but some changes cannot be handled automatically. If you
|
Some changes cannot be handled automatically and thus live reload may not always work. If you
|
||||||
fail to see your change or get a weird error, try to restart `zola serve`.
|
fail to see your change or get an error, try restarting `zola serve`.
|
||||||
|
|
||||||
|
|
||||||
You can also point to another config file than `config.toml` like so - the position of the `config` option is important:
|
You can also point to a config file other than `config.toml` like so (note that the position of the `config` option is important):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ zola --config config.staging.toml serve
|
$ zola --config config.staging.toml serve
|
||||||
```
|
```
|
||||||
|
|
||||||
By defaults, drafts are not loaded. If you wish to include them, pass the `--drafts` flag.
|
By default, drafts are not loaded. If you wish to include them, pass the `--drafts` flag.
|
||||||
|
|
||||||
### check
|
### check
|
||||||
|
|
||||||
The check subcommand will try to build all pages just like the build command would, but without writing any of the
|
The check subcommand will try to build all pages just like the build command would, but without writing any of the
|
||||||
results to disk. Additionally, it will also check all external links present in Markdown files by trying to fetch
|
results to disk. Additionally, it will also check all external links in Markdown files by trying to fetch
|
||||||
them (links present in the template files will not be checked).
|
them (links in the template files are not checked).
|
||||||
|
|
||||||
By defaults, drafts are not loaded. If you wish to include them, pass the `--drafts` flag.
|
By default, drafts are not loaded. If you wish to include them, pass the `--drafts` flag.
|
||||||
|
|
||||||
## Colored output
|
## Colored output
|
||||||
|
|
||||||
Any of the three commands will emit colored output if your terminal supports it.
|
Colored output is used if your terminal supports it.
|
||||||
|
|
||||||
*Note*: coloring is automatically disabled when the output is redirected to a pipe or a file (ie. when the standard output is not a TTY).
|
*Note*: coloring is automatically disabled when the output is redirected to a pipe or a file (i.e., when the standard output is not a TTY).
|
||||||
|
|
||||||
You can disable this behavior by exporting one of the two following environment variables:
|
You can disable this behavior by exporting one of the following two environment variables:
|
||||||
|
|
||||||
- `NO_COLOR` (the value does not matter)
|
- `NO_COLOR` (the value does not matter)
|
||||||
- `CLICOLOR=0`
|
- `CLICOLOR=0`
|
||||||
|
|
||||||
Should you want to force the use of colors, you can set the following environment variable:
|
To force the use of colors, you can set the following environment variable:
|
||||||
|
|
||||||
- `CLICOLOR_FORCE=1`
|
- `CLICOLOR_FORCE=1`
|
||||||
|
|
|
@ -1,53 +1,53 @@
|
||||||
+++
|
+++
|
||||||
title = "Configuration"
|
title = "Configuration"
|
||||||
weight = 4
|
weight = 40
|
||||||
+++
|
+++
|
||||||
|
|
||||||
The default configuration will be enough to get Zola running locally but not more than that.
|
The default configuration is sufficient to get Zola running locally but not more than that.
|
||||||
It follows the philosophy of only paying for what you need: almost everything is turned off by default.
|
It follows the philosophy of paying for only what you need; almost everything is turned off by default.
|
||||||
|
|
||||||
To change the config, edit the `config.toml` file.
|
To change the configuration, edit the `config.toml` file.
|
||||||
If you are not familiar with TOML, have a look at [the TOML Spec](https://github.com/toml-lang/toml)
|
If you are not familiar with TOML, have a look at [the TOML spec](https://github.com/toml-lang/toml).
|
||||||
to learn about it.
|
|
||||||
|
|
||||||
Only one variable - `base_url` - is mandatory, everything else is optional. You can find all variables
|
Only the `base_url` variable is mandatory; everything else is optional. All configuration variables
|
||||||
used by Zola config as well as their default values below:
|
used by Zola as well as their default values are listed below:
|
||||||
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# Base URL of the site, the only required config argument
|
# The base URL of the site; the only required configuration variable.
|
||||||
base_url = "mywebsite.com"
|
base_url = "mywebsite.com"
|
||||||
|
|
||||||
# Used in RSS by default
|
# The site title and description; used in RSS by default.
|
||||||
title = ""
|
title = ""
|
||||||
description = ""
|
description = ""
|
||||||
# The default language, used in RSS
|
|
||||||
|
# The default language; used in RSS.
|
||||||
default_language = "en"
|
default_language = "en"
|
||||||
|
|
||||||
# Theme name to use
|
# The site theme to use.
|
||||||
theme = ""
|
theme = ""
|
||||||
|
|
||||||
# Highlight all code blocks found
|
# When set to "true", all code blocks are highlighted.
|
||||||
highlight_code = false
|
highlight_code = false
|
||||||
|
|
||||||
# Which theme to use for the code highlighting.
|
# The theme to use for code highlighting.
|
||||||
# See below for list of accepted values
|
# See below for list of allowed values.
|
||||||
highlight_theme = "base16-ocean-dark"
|
highlight_theme = "base16-ocean-dark"
|
||||||
|
|
||||||
# Whether to generate a RSS feed automatically
|
# When set to "true", an RSS feed is automatically generated.
|
||||||
generate_rss = false
|
generate_rss = false
|
||||||
|
|
||||||
# The number of articles to include in the RSS feed. Will include all items if
|
# The number of articles to include in the RSS feed. All items are included if
|
||||||
# not set (the default).
|
# this limit is not set (the default).
|
||||||
# rss_limit = 20
|
# rss_limit = 20
|
||||||
|
|
||||||
# Whether to copy or hardlink files in static/ directory. Useful for sites
|
# When set to "true", files in the `static` directory are hard-linked. Useful for large
|
||||||
# whose static files are large. Note that for this to work, both static/ and
|
# static files. Note that for this to work, both `static` and the
|
||||||
# output directory need to be on the same filesystem. Also, theme's static/
|
# output directory need to be on the same filesystem. Note that the theme's `static`
|
||||||
# files are always copies, regardles of this setting. False by default.
|
# files are always copied, regardles of this setting.
|
||||||
# hard_link_static = false
|
# hard_link_static = false
|
||||||
|
|
||||||
# The taxonomies to be rendered for that site and their configuration
|
# The taxonomies to be rendered for the site and their configuration.
|
||||||
# Example:
|
# Example:
|
||||||
# taxonomies = [
|
# taxonomies = [
|
||||||
# {name = "tags", rss = true}, # each tag will have its own RSS feed
|
# {name = "tags", rss = true}, # each tag will have its own RSS feed
|
||||||
|
@ -58,7 +58,7 @@ generate_rss = false
|
||||||
#
|
#
|
||||||
taxonomies = []
|
taxonomies = []
|
||||||
|
|
||||||
# The additional languages for that site
|
# The additional languages for the site.
|
||||||
# Example:
|
# Example:
|
||||||
# languages = [
|
# languages = [
|
||||||
# {code = "fr", rss = true}, # there will be a RSS feed for French content
|
# {code = "fr", rss = true}, # there will be a RSS feed for French content
|
||||||
|
@ -68,21 +68,21 @@ taxonomies = []
|
||||||
#
|
#
|
||||||
languages = []
|
languages = []
|
||||||
|
|
||||||
# Whether to compile the Sass files found in the `sass` directory
|
# When set to "true", the Sass files in the `sass` directory are compiled.
|
||||||
compile_sass = false
|
compile_sass = false
|
||||||
|
|
||||||
# Whether to build a search index out of the pages and section
|
# When set to "true", a search index is built from the pages and section
|
||||||
# content for the `default_language`
|
# content for `default_language`.
|
||||||
build_search_index = false
|
build_search_index = false
|
||||||
|
|
||||||
# A list of glob patterns specifying asset files to ignore when
|
# A list of glob patterns specifying asset files to ignore when the content
|
||||||
# processing the content directory.
|
# directory is processed. Defaults to none, which means that all asset files are
|
||||||
# Defaults to none, which means all asset files are copied over to the public folder.
|
# copied over to the `public` directory.
|
||||||
# Example:
|
# Example:
|
||||||
# ignored_content = ["*.{graphml,xlsx}", "temp.*"]
|
# ignored_content = ["*.{graphml,xlsx}", "temp.*"]
|
||||||
ignored_content = []
|
ignored_content = []
|
||||||
|
|
||||||
# A list of directories to search for additional `.sublime-syntax` files in.
|
# A list of directories used to search for additional `.sublime-syntax` files.
|
||||||
extra_syntaxes = []
|
extra_syntaxes = []
|
||||||
|
|
||||||
# Optional translation object. The key if present should be a language code.
|
# Optional translation object. The key if present should be a language code.
|
||||||
|
@ -95,11 +95,32 @@ extra_syntaxes = []
|
||||||
#
|
#
|
||||||
# [translations.en]
|
# [translations.en]
|
||||||
# title = "A title"
|
# title = "A title"
|
||||||
#
|
|
||||||
|
|
||||||
|
# Configuration of the link checker.
|
||||||
|
[link_checker]
|
||||||
|
# Skip link checking for external URLs that start with these prefixes
|
||||||
|
skip_prefixes = [
|
||||||
|
"http://[2001:db8::]/",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Skip anchor checking for external URLs that start with these prefixes
|
||||||
|
skip_anchor_prefixes = [
|
||||||
|
"https://caniuse.com/",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Various slugification strategies, see below for details
|
||||||
|
# Defauls to everything being a slug
|
||||||
|
[slugify]
|
||||||
|
paths = "on"
|
||||||
|
taxonomies = "on"
|
||||||
|
anchors = "on"
|
||||||
|
|
||||||
|
# Optional translation object. Keys should be language codes.
|
||||||
[translations]
|
[translations]
|
||||||
|
|
||||||
# You can put any kind of data in there and it
|
# You can put any kind of data here. The data
|
||||||
# will be accessible in all templates
|
# will be accessible in all templates.
|
||||||
[extra]
|
[extra]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -141,6 +162,23 @@ Zola currently has the following highlight themes available:
|
||||||
- [ayu-mirage](https://github.com/dempfi/ayu)
|
- [ayu-mirage](https://github.com/dempfi/ayu)
|
||||||
- [Tomorrow](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Tomorrow)
|
- [Tomorrow](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Tomorrow)
|
||||||
- [one-dark](https://github.com/andresmichel/one-dark-theme)
|
- [one-dark](https://github.com/andresmichel/one-dark-theme)
|
||||||
|
- [zenburn](https://github.com/colinta/zenburn)
|
||||||
|
|
||||||
Zola uses the Sublime Text themes, making it very easy to add more.
|
Zola uses the Sublime Text themes, making it very easy to add more.
|
||||||
If you want a theme not on that list, please open an issue or a pull request on the [Zola repo](https://github.com/getzola/zola).
|
If you want a theme not listed above, please open an issue or a pull request on the [Zola repo](https://github.com/getzola/zola).
|
||||||
|
|
||||||
|
## Slugification strategies
|
||||||
|
|
||||||
|
By default, Zola will turn every path, taxonomies and anchors to a slug, an ASCII representation with no special characters.
|
||||||
|
You can however change that strategy for each kind of item, if you want UTF-8 characters in your URLs for example. There are 3 strategies:
|
||||||
|
|
||||||
|
- `on`: the default one, everything is turned into a slug
|
||||||
|
- `safe`: characters that cannot exist in files on Windows (`<>:"/\|?*`) or Unix (`/`) are removed, everything else stays
|
||||||
|
- `off`: nothing is changed, your site might not build on some OS and/or break various URL parsers
|
||||||
|
|
||||||
|
Since there are no filename issues with anchors, the `safe` and `off` strategies are identical in their case: the only change
|
||||||
|
is space being replaced by `_` since a space is not valid in an anchor.
|
||||||
|
|
||||||
|
Note that if you are using a strategy other than the default, you will have to manually escape whitespace and Markdown
|
||||||
|
tokens to be able to link to your pages. For example an internal link to a file named `some space.md` will need to be
|
||||||
|
written like `some%20space.md` in your Markdown files.
|
|
@ -1,9 +1,9 @@
|
||||||
+++
|
+++
|
||||||
title = "Directory structure"
|
title = "Directory structure"
|
||||||
weight = 3
|
weight = 30
|
||||||
+++
|
+++
|
||||||
|
|
||||||
After running `zola init`, you should see the following structure in your folder:
|
After running `zola init`, you should see the following structure in your directory:
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -18,34 +18,34 @@ After running `zola init`, you should see the following structure in your folder
|
||||||
5 directories, 1 file
|
5 directories, 1 file
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's a high level overview of each of these folders and `config.toml`.
|
Here's a high-level overview of each of these directories and `config.toml`.
|
||||||
|
|
||||||
## `config.toml`
|
## `config.toml`
|
||||||
A mandatory configuration file of Zola in TOML format.
|
A mandatory Zola configuration file in TOML format.
|
||||||
It is explained in details in the [Configuration page](@/documentation/getting-started/configuration.md).
|
This file is explained in detail in the [configuration documentation](@/documentation/getting-started/configuration.md).
|
||||||
|
|
||||||
## `content`
|
## `content`
|
||||||
Where all your markup content lies: this will be mostly comprised of `.md` files.
|
Contains all your markup content (mostly `.md` files).
|
||||||
Each folder in the `content` directory represents a [section](@/documentation/content/section.md)
|
Each child directory of the `content` directory represents a [section](@/documentation/content/section.md)
|
||||||
that contains [pages](@/documentation/content/page.md) : your `.md` files.
|
that contains [pages](@/documentation/content/page.md) (your `.md` files).
|
||||||
|
|
||||||
To learn more, read [the content overview](@/documentation/content/overview.md).
|
To learn more, read the [content overview page](@/documentation/content/overview.md).
|
||||||
|
|
||||||
## `sass`
|
## `sass`
|
||||||
Contains the [Sass](http://sass-lang.com) files to be compiled. Non-Sass files will be ignored.
|
Contains the [Sass](http://sass-lang.com) files to be compiled. Non-Sass files will be ignored.
|
||||||
The directory structure of the `sass` folder will be preserved when copying over the compiled files: a file at
|
The directory structure of the `sass` folder will be preserved when copying over the compiled files; for example, a file at
|
||||||
`sass/something/site.scss` will be compiled to `public/something/site.css`.
|
`sass/something/site.scss` will be compiled to `public/something/site.css`.
|
||||||
|
|
||||||
## `static`
|
## `static`
|
||||||
Contains any kind of files. All the files/folders in the `static` folder will be copied as-is in the output directory.
|
Contains any kind of file. All the files/directories in the `static` directory will be copied as-is to the output directory.
|
||||||
If your static files are large you can configure Zola to [hard link](https://en.wikipedia.org/wiki/Hard_link) them
|
If your static files are large, you can configure Zola to [hard link](https://en.wikipedia.org/wiki/Hard_link) them
|
||||||
instead of copying by setting `hard_link_static = true` in the config file.
|
instead of copying them by setting `hard_link_static = true` in the config file.
|
||||||
|
|
||||||
## `templates`
|
## `templates`
|
||||||
Contains all the [Tera](https://tera.netlify.com) templates that will be used to render this site.
|
Contains all the [Tera](https://tera.netlify.com) templates that will be used to render your site.
|
||||||
Have a look at the [Templates](@/documentation/templates/_index.md) to learn more about default templates
|
Have a look at the [templates documentation](@/documentation/templates/_index.md) to learn more about default templates
|
||||||
and available variables.
|
and available variables.
|
||||||
|
|
||||||
## `themes`
|
## `themes`
|
||||||
Contains themes that can be used for that site. If you are not planning to use themes, leave this folder empty.
|
Contains themes that can be used for your site. If you are not planning to use themes, leave this directory empty.
|
||||||
If you want to learn about themes, head to the [themes documentation](@/documentation/themes/_index.md).
|
If you want to learn about themes, see the [themes documentation](@/documentation/themes/_index.md).
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
+++
|
+++
|
||||||
title = "Installation"
|
title = "Installation"
|
||||||
weight = 1
|
weight = 10
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Zola provides pre-built binaries for MacOS, Linux and Windows on the
|
Zola provides pre-built binaries for MacOS, Linux and Windows on the
|
||||||
|
@ -24,7 +24,7 @@ $ yay -S zola-bin
|
||||||
|
|
||||||
### Fedora
|
### Fedora
|
||||||
|
|
||||||
Zola is available in official repositories since Fedora 29.
|
Zola has been available in the official repositories since Fedora 29.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ sudo dnf install zola
|
$ sudo dnf install zola
|
||||||
|
@ -54,7 +54,7 @@ Zola is available on [Scoop](http://scoop.sh):
|
||||||
$ scoop install zola
|
$ scoop install zola
|
||||||
```
|
```
|
||||||
|
|
||||||
And [Chocolatey](https://chocolatey.org/):
|
and [Chocolatey](https://chocolatey.org/):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ choco install zola
|
$ choco install zola
|
||||||
|
@ -63,10 +63,10 @@ $ choco install zola
|
||||||
Zola does not work in PowerShell ISE.
|
Zola does not work in PowerShell ISE.
|
||||||
|
|
||||||
## From source
|
## From source
|
||||||
To build it from source, you will need to have Git, [Rust (at least 1.31) and Cargo](https://www.rust-lang.org/)
|
To build Zola from source, you will need to have Git, [Rust (at least 1.36) and Cargo](https://www.rust-lang.org/)
|
||||||
installed. You will also need additional dependencies to compile [libsass](https://github.com/sass/libsass):
|
installed. You will also need to meet additional dependencies to compile [libsass](https://github.com/sass/libsass):
|
||||||
|
|
||||||
- OSX, Linux and other Unix: `make` (`gmake` on BSDs), `g++`, `libssl-dev`
|
- OSX, Linux and other Unix-like operating systems: `make` (`gmake` on BSDs), `g++`, `libssl-dev`
|
||||||
- NixOS: Create a `shell.nix` file in the root of the cloned project with the following contents:
|
- NixOS: Create a `shell.nix` file in the root of the cloned project with the following contents:
|
||||||
```nix
|
```nix
|
||||||
with import <nixpkgs> {};
|
with import <nixpkgs> {};
|
||||||
|
@ -79,7 +79,7 @@ installed. You will also need additional dependencies to compile [libsass](https
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- Then invoke `nix-shell`. This opens a shell with the above dependencies. You then run `cargo build --release` to build the project.
|
- Then, invoke `nix-shell`. This opens a shell with the above dependencies. Then, run `cargo build --release` to build the project.
|
||||||
- Windows (a bit trickier): updated `MSVC` and overall updated VS installation
|
- Windows (a bit trickier): updated `MSVC` and overall updated VS installation
|
||||||
|
|
||||||
From a terminal, you can now run the following command:
|
From a terminal, you can now run the following command:
|
||||||
|
@ -88,6 +88,6 @@ From a terminal, you can now run the following command:
|
||||||
$ cargo build --release
|
$ cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
The binary will be available in the `target/release` folder. You can move it in your `$PATH` to have the
|
The binary will be available in the `target/release` directory. You can move it in your `$PATH` to have the
|
||||||
`zola` command available globally or in a directory if you want for example to have the binary in the
|
`zola` command available globally or in a directory if you want for example to have the binary in the
|
||||||
same repository as the site.
|
same repository as the site.
|
||||||
|
|
206
docs/content/documentation/getting-started/overview.md
Normal file
206
docs/content/documentation/getting-started/overview.md
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
+++
|
||||||
|
title = "Overview"
|
||||||
|
weight = 5
|
||||||
|
+++
|
||||||
|
|
||||||
|
## Zola at a Glance
|
||||||
|
|
||||||
|
Zola is a static site generator (SSG), similar to [Hugo](https://gohugo.io/), [Pelican](https://blog.getpelican.com/), and [Jekyll](https://jekyllrb.com/) (for a comprehensive list of SSGs, please see the [StaticGen](https://www.staticgen.com/) site). It is written in [Rust](https://www.rust-lang.org/) and uses the [Tera](https://tera.netlify.com/) template engine, which is similar to [Jinja2](https://jinja.palletsprojects.com/en/2.10.x/), [Django templates](https://docs.djangoproject.com/en/2.2/topics/templates/), [Liquid](https://shopify.github.io/liquid/), and [Twig](https://twig.symfony.com/). Content is written in [CommonMark](https://commonmark.org/), a strongly defined, highly compatible specification of [Markdown](https://www.markdownguide.org/).
|
||||||
|
|
||||||
|
SSGs use dynamic templates to transform content into static HTML pages. Static sites are thus very fast and require no databases, making them easy to host. A comparison between static and dynamic sites, such as WordPress, Drupal, and Django, can be found [here](https://dev.to/ashenmaster/static-vs-dynamic-sites-61f).
|
||||||
|
|
||||||
|
To get a taste of Zola, please see the quick overview below.
|
||||||
|
|
||||||
|
## First Steps with Zola
|
||||||
|
|
||||||
|
Unlike some SSGs, Zola makes no assumptions regarding the structure of your site. In this overview, we'll be making a simple blog site.
|
||||||
|
|
||||||
|
### Initialize Site
|
||||||
|
|
||||||
|
> This overview is based on Zola 0.9.
|
||||||
|
|
||||||
|
Please see the detailed [installation instructions for your platform](@/documentation/getting-started/installation.md). With Zola installed, let's initialize our site:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ zola init myblog
|
||||||
|
```
|
||||||
|
|
||||||
|
You will be asked a few questions.
|
||||||
|
|
||||||
|
```
|
||||||
|
> What is the URL of your site? (https://example.com):
|
||||||
|
> Do you want to enable Sass compilation? [Y/n]:
|
||||||
|
> Do you want to enable syntax highlighting? [y/N]:
|
||||||
|
> Do you want to build a search index of the content? [y/N]:
|
||||||
|
```
|
||||||
|
|
||||||
|
For our blog, let's accept the default values (i.e., press Enter for each question). We now have a `myblog` directory with the following structure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
├── config.toml
|
||||||
|
├── content
|
||||||
|
├── sass
|
||||||
|
├── static
|
||||||
|
├── templates
|
||||||
|
└── themes
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's start the zola development server with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ zola serve
|
||||||
|
Building site...
|
||||||
|
-> Creating 0 pages (0 orphan), 0 sections, and processing 0 images
|
||||||
|
```
|
||||||
|
|
||||||
|
> This command must be run in the base Zola directory, which contains `config.toml`.
|
||||||
|
|
||||||
|
If you point your web browser to <http://127.0.0.1:1111>, you should see a "Welcome to Zola" message.
|
||||||
|
|
||||||
|
### Home Page
|
||||||
|
|
||||||
|
Let's make a home page. To do this, let's first create a `base.html` file inside the `templates` directory. This step will make more sense as we move through this overview. We'll be using the CSS framework [Bulma](https://bulma.io/).
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>MyBlog</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
{% block content %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, let's create an `index.html` file inside the `templates` directory.
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title">
|
||||||
|
This is my blog made with Zola.
|
||||||
|
</h1>
|
||||||
|
{% endblock content %}
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells Zola that `index.html` extends our `base.html` file and replaces the block called "content" with the text between the `{% block content %}` and `{% endblock content %}` tags.
|
||||||
|
|
||||||
|
### Content Directory
|
||||||
|
|
||||||
|
Now let's add some content. We'll start by making a `blog` subdirectory in the `content` directory and creating an `_index.md` file inside it. This file tells Zola that `blog` is a [section](@/documentation/content/section.md), which is how content is categorized in Zola.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
├── content
|
||||||
|
│ └── blog
|
||||||
|
│ └── _index.md
|
||||||
|
```
|
||||||
|
|
||||||
|
In the `_index.md` file, we'll set the following variables in [TOML](https://github.com/toml-lang/toml) format:
|
||||||
|
|
||||||
|
```md
|
||||||
|
+++
|
||||||
|
title = "List of blog posts"
|
||||||
|
sort_by = "date"
|
||||||
|
template = "blog.html"
|
||||||
|
page_template = "blog-page.html"
|
||||||
|
+++
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note that although no variables are mandatory, the opening and closing `+++` are required.
|
||||||
|
|
||||||
|
* *sort_by = "date"* tells Zola to use the date to order our section pages (more on pages below).
|
||||||
|
* *template = "blog.html"* tells Zola to use `blog.html` in the `templates` directory as the template for listing the Markdown files in this section.
|
||||||
|
* *page_template = "blog-page.html"* tells Zola to use `blog-page.html` in the `templates` directory as the template for individual Markdown files.
|
||||||
|
|
||||||
|
For a full list of section variables, please see the [section](@/documentation/content/section.md) documentation. We will use *title = "List of blog posts"* in a template (see below).
|
||||||
|
|
||||||
|
### Templates
|
||||||
|
|
||||||
|
Let's now create some more templates. In the `templates` directory, create a `blog.html` file with the following contents:
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title">
|
||||||
|
{{ section.title }}
|
||||||
|
</h1>
|
||||||
|
<ul>
|
||||||
|
{% for page in section.pages %}
|
||||||
|
<li><a href="{{ page.permalink }}">{{ page.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock content %}
|
||||||
|
```
|
||||||
|
|
||||||
|
As done by `index.html`, `blog.html` extends `base.html`, but this time we want to list the blog posts. The *title* we set in the `_index.md` file above is available to us as `{{ section.title }}`. In the list below the title, we loop through all the pages in our section (`blog` directory) and output the page title and URL using `{{ page.title }}` and `{{ page.permalink }}`, respectively. If you go to <http://127.0.0.1:1111/blog/>, you will see the section page for `blog`. The list is empty because we don't have any blog posts. Let's fix that now.
|
||||||
|
|
||||||
|
### Markdown Content
|
||||||
|
|
||||||
|
In the `blog` directory, create a file called `first.md` with the following contents:
|
||||||
|
|
||||||
|
```md
|
||||||
|
+++
|
||||||
|
title = "My first post"
|
||||||
|
date = 2019-11-27
|
||||||
|
+++
|
||||||
|
|
||||||
|
This is my first blog post.
|
||||||
|
```
|
||||||
|
|
||||||
|
The *title* and *date* will be avaiable to us in the `blog-page.html` template as `{{ page.title }}` and `{{ page.date }}`, respectively. All text below the closing `+++` will be available to us as `{{ page.content }}`.
|
||||||
|
|
||||||
|
We now need to make the `blog-page.html` template. In the `templates` directory, create this file with the contents:
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title">
|
||||||
|
{{ page.title }}
|
||||||
|
</h1>
|
||||||
|
<p class="subtitle"><strong>{{ page.date }}</strong></p>
|
||||||
|
<p>{{ page.content | safe }}</p>
|
||||||
|
{% endblock content %}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note the `| safe` filter for `{{ page.content }}`.
|
||||||
|
|
||||||
|
This should start to look familiar. If you now go back to our blog list page at <http://127.0.0.1:1111/blog/>, you should see our lonely post. Let's add another. In the `content/blog` directory, let's create the file `second.md` with the contents:
|
||||||
|
|
||||||
|
```md
|
||||||
|
+++
|
||||||
|
title = "My second post"
|
||||||
|
date = 2019-11-28
|
||||||
|
+++
|
||||||
|
|
||||||
|
This is my second blog post.
|
||||||
|
```
|
||||||
|
|
||||||
|
Back at <http://127.0.0.1:1111/blog/>, our second post shows up on top of the list because it's newer than the first post and we had set *sort_by = "date"* in our `_index.md` file. As a final step, let's modify our home page to link to our blog posts.
|
||||||
|
|
||||||
|
The `index.html` file inside the `templates` directory should be:
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="title">
|
||||||
|
This is my blog made with Zola.
|
||||||
|
</h1>
|
||||||
|
<p>Click <a href="/blog/">here</a> to see my posts.</p>
|
||||||
|
{% endblock content %}
|
||||||
|
```
|
||||||
|
|
||||||
|
This has been a quick overview of Zola. You can now dive into the rest of the documentation.
|
|
@ -4,5 +4,4 @@ weight = 80
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Zola will look for a `404.html` file in the `templates` directory or
|
Zola will look for a `404.html` file in the `templates` directory or
|
||||||
use the built-in one. The default template is very basic and gets a simple
|
use the built-in one. The default template is very basic and gets `config` in its context.
|
||||||
variable in the context: the site `config`.
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ title = "Archive"
|
||||||
weight = 90
|
weight = 90
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Zola doesn't have a built-in way to display an archive page, a page showing
|
Zola doesn't have a built-in way to display an archive page (a page showing
|
||||||
all post titles ordered by year. However, this can be accomplished directly in the templates:
|
all post titles ordered by year). However, this can be accomplished directly in the templates:
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{% for year, posts in section.pages | group_by(attribute="year") %}
|
{% for year, posts in section.pages | group_by(attribute="year") %}
|
||||||
|
@ -19,5 +19,5 @@ all post titles ordered by year. However, this can be accomplished directly in t
|
||||||
```
|
```
|
||||||
|
|
||||||
This snippet assumes that posts are sorted by date and that you want to display the archive
|
This snippet assumes that posts are sorted by date and that you want to display the archive
|
||||||
in a descending order. If you want to show articles in a ascending order, simply add a `reverse` filter
|
in descending order. If you want to show articles in ascending order, add a `reverse` filter
|
||||||
after the `group_by`.
|
after `group_by`.
|
||||||
|
|
|
@ -3,49 +3,49 @@ title = "Overview"
|
||||||
weight = 10
|
weight = 10
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Zola uses the [Tera](https://tera.netlify.com) template engine and is very similar
|
Zola uses the [Tera](https://tera.netlify.com) template engine, which is very similar
|
||||||
to Jinja2, Liquid or Twig.
|
to Jinja2, Liquid and Twig.
|
||||||
|
|
||||||
As this documentation will only talk about how templates work in Zola, please read
|
As this documentation will only talk about how templates work in Zola, please read
|
||||||
the [Tera template documentation](https://tera.netlify.com/docs#templates) if you want
|
the [Tera template documentation](https://tera.netlify.com/docs#templates) if you want
|
||||||
to learn more about it first.
|
to learn more about it first.
|
||||||
|
|
||||||
All templates live in the `templates` directory. If you are not sure what variables are available in a template, you can just stick `{{ __tera_context }}` in it
|
All templates live in the `templates` directory. If you are not sure what variables are available in a template,
|
||||||
to print the whole context.
|
you can place `{{ __tera_context }}` in the template to print the whole context.
|
||||||
|
|
||||||
A few variables are available on all templates minus RSS and sitemap:
|
A few variables are available on all templates except RSS and the sitemap:
|
||||||
|
|
||||||
- `config`: the [configuration](@/documentation/getting-started/configuration.md) without any modifications
|
- `config`: the [configuration](@/documentation/getting-started/configuration.md) without any modifications
|
||||||
- `current_path`: the path (full URL without the `base_url`) of the current page, never starting with a `/`
|
- `current_path`: the path (full URL without `base_url`) of the current page, never starting with a `/`
|
||||||
- `current_url`: the full URL for that page
|
- `current_url`: the full URL for the current page
|
||||||
- `lang`: the language for that page, `null` if the page/section doesn't have a language set
|
- `lang`: the language for the current page; `null` if the page/section doesn't have a language set
|
||||||
|
|
||||||
The 404 template does not get `current_path` and `current_url` as it cannot know it.
|
The 404 template does not get `current_path` and `current_url` (this information cannot be determined).
|
||||||
|
|
||||||
## Standard Templates
|
## Standard templates
|
||||||
By default, Zola will look for three templates: `index.html`, which is applied
|
By default, Zola will look for three templates: `index.html`, which is applied
|
||||||
to the site homepage; `section.html`, which is applied to all sections (any HTML
|
to the site homepage; `section.html`, which is applied to all sections (any HTML
|
||||||
page generated by creating a directory within your `content` directory); and
|
page generated by creating a directory within your `content` directory); and
|
||||||
`page.html`, which is applied to all pages (any HTML page generated by creating a
|
`page.html`, which is applied to all pages (any HTML page generated by creating an
|
||||||
`.md` file within your `content` directory).
|
`.md` file within your `content` directory).
|
||||||
|
|
||||||
The homepage is always a section (regardless of whether it contains other pages).
|
The homepage is always a section (regardless of whether it contains other pages).
|
||||||
Thus, the `index.html` and `section.html` templates both have access to the
|
Thus, the `index.html` and `section.html` templates both have access to the
|
||||||
section variables. The `page.html` template has access to the page variables.
|
section variables. The `page.html` template has access to the page variables.
|
||||||
The page and section variables are described in more detail in the next section of this documentation.
|
The page and section variables are described in more detail in the next section.
|
||||||
|
|
||||||
## Built-in Templates
|
## Built-in templates
|
||||||
Zola comes with three built-in templates: `rss.xml`, `sitemap.xml`, and
|
Zola comes with three built-in templates: `rss.xml`, `sitemap.xml` and
|
||||||
`robots.txt` (each described in their own section of this documentation).
|
`robots.txt` (each is described in its own section of this documentation).
|
||||||
Additionally, themes can add their own templates, which will be applied if not
|
Additionally, themes can add their own templates, which will be applied if not
|
||||||
overridden. You can override built-in or theme templates by creating a template with
|
overridden. You can override built-in or theme templates by creating a template with
|
||||||
same name in the correct path. For example, you can override the RSS template by
|
the same name in the correct path. For example, you can override the RSS template by
|
||||||
creating a `templates/rss.xml` file.
|
creating a `templates/rss.xml` file.
|
||||||
|
|
||||||
## Custom Templates
|
## Custom templates
|
||||||
In addition to the standard `index.html`, `section.html`, and `page.html` templates,
|
In addition to the standard `index.html`, `section.html` and `page.html` templates,
|
||||||
you may also create custom templates by creating a `.html` file in the `templates`
|
you may also create custom templates by creating an `.html` file in the `templates`
|
||||||
directory. These custom templates will not be used by default. Instead, the custom template will _only_ be used if you apply it by setting the `template` front-matter variable to the path for that template (or if you `include` it in another template that is applied). For example, if you created a custom template for your site's About page called `about.html`, you could apply it to your `about.md` page by including the following front matter in your `about.md` page:
|
directory. These custom templates will not be used by default. Instead, a custom template will _only_ be used if you apply it by setting the `template` front-matter variable to the path for that template (or if you `include` it in another template that is applied). For example, if you created a custom template for your site's About page called `about.html`, you could apply it to your `about.md` page by including the following front matter in your `about.md` page:
|
||||||
|
|
||||||
```md
|
```md
|
||||||
+++
|
+++
|
||||||
|
@ -58,13 +58,14 @@ Custom templates are not required to live at the root of your `templates` direct
|
||||||
For example, `product_pages/with_pictures.html` is a valid template.
|
For example, `product_pages/with_pictures.html` is a valid template.
|
||||||
|
|
||||||
## Built-in filters
|
## Built-in filters
|
||||||
Zola adds a few filters, in addition of the ones [ones already present](https://tera.netlify.com/docs#built-in-filters) in Tera.
|
Zola adds a few filters in addition to [those](https://tera.netlify.com/docs/templates/#built-in-filters) already present
|
||||||
|
in Tera.
|
||||||
|
|
||||||
### markdown
|
### markdown
|
||||||
Converts the given variable to HTML using Markdown. This doesn't apply any of the
|
Converts the given variable to HTML using Markdown. This doesn't apply any of the
|
||||||
features that Zola adds to Markdown: internal links, shortcodes etc won't work.
|
features that Zola adds to Markdown; for example, internal links and shortcodes won't work.
|
||||||
|
|
||||||
By default, the filter will wrap all text into a paragraph. To disable that, you can
|
By default, the filter will wrap all text in a paragraph. To disable this behaviour, you can
|
||||||
pass `true` to the inline argument:
|
pass `true` to the inline argument:
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
|
@ -80,18 +81,19 @@ Decode the variable from base64.
|
||||||
|
|
||||||
## Built-in global functions
|
## Built-in global functions
|
||||||
|
|
||||||
Zola adds a few global functions to [those in Tera](https://tera.netlify.com/docs#built-in-functions) in order to make it easier to develop complex sites.
|
Zola adds a few global functions to [those in Tera](https://tera.netlify.com/docs/templates/#built-in-functions)
|
||||||
|
to make it easier to develop complex sites.
|
||||||
|
|
||||||
|
|
||||||
### `get_page`
|
### `get_page`
|
||||||
Takes a path to a `.md` file and returns the associated page
|
Takes a path to an `.md` file and returns the associated page.
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{% set page = get_page(path="blog/page2.md") %}
|
{% set page = get_page(path="blog/page2.md") %}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `get_section`
|
### `get_section`
|
||||||
Takes a path to a `_index.md` file and returns the associated section
|
Takes a path to an `_index.md` file and returns the associated section.
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{% set section = get_section(path="blog/_index.md") %}
|
{% set section = get_section(path="blog/_index.md") %}
|
||||||
|
@ -105,14 +107,14 @@ If you only need the metadata of the section, you can pass `metadata_only=true`
|
||||||
|
|
||||||
### ` get_url`
|
### ` get_url`
|
||||||
Gets the permalink for the given path.
|
Gets the permalink for the given path.
|
||||||
If the path starts with `@/`, it will be understood as an internal
|
If the path starts with `@/`, it will be treated as an internal
|
||||||
link like the ones used in markdown, starting from the root `content` directory.
|
link like the ones used in Markdown, starting from the root `content` directory.
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{% set url = get_url(path="@/blog/_index.md") %}
|
{% set url = get_url(path="@/blog/_index.md") %}
|
||||||
```
|
```
|
||||||
|
|
||||||
This can also be used to get the permalinks for static assets for example if
|
This can also be used to get the permalinks for static assets, for example if
|
||||||
we want to link to the file that is located at `static/css/app.css`:
|
we want to link to the file that is located at `static/css/app.css`:
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
|
@ -131,7 +133,7 @@ by passing `cachebust=true` to the `get_url` function.
|
||||||
|
|
||||||
|
|
||||||
### `get_image_metadata`
|
### `get_image_metadata`
|
||||||
Gets metadata for an image. Today the only supported keys are `width` and `height`.
|
Gets metadata for an image. Currently, the only supported keys are `width` and `height`.
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{% set meta = get_image_metadata(path="...") %}
|
{% set meta = get_image_metadata(path="...") %}
|
||||||
|
@ -145,8 +147,8 @@ Gets the permalink for the taxonomy item found.
|
||||||
{% set url = get_taxonomy_url(kind="categories", name=page.taxonomies.category) %}
|
{% set url = get_taxonomy_url(kind="categories", name=page.taxonomies.category) %}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `name` will almost come from a variable but in case you want to do it manually,
|
`name` will almost always come from a variable but in case you want to do it manually,
|
||||||
the value should be the same as the one in the front-matter, not the slugified version.
|
the value should be the same as the one in the front matter, not the slugified version.
|
||||||
|
|
||||||
### `get_taxonomy`
|
### `get_taxonomy`
|
||||||
Gets the whole taxonomy of a specific kind.
|
Gets the whole taxonomy of a specific kind.
|
||||||
|
@ -160,7 +162,7 @@ Loads data from a file or URL. Supported file types include *toml*, *json* and *
|
||||||
Any other file type will be loaded as plain text.
|
Any other file type will be loaded as plain text.
|
||||||
|
|
||||||
The `path` argument specifies the path to the data file relative to your base directory, where your `config.toml` is.
|
The `path` argument specifies the path to the data file relative to your base directory, where your `config.toml` is.
|
||||||
As a security precaution, If this file is outside of the main site directory, your site will fail to build.
|
As a security precaution, if this file is outside the main site directory, your site will fail to build.
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{% set data = load_data(path="content/blog/story/data.toml") %}
|
{% set data = load_data(path="content/blog/story/data.toml") %}
|
||||||
|
@ -168,7 +170,7 @@ As a security precaution, If this file is outside of the main site directory, yo
|
||||||
|
|
||||||
The optional `format` argument allows you to specify and override which data type is contained
|
The optional `format` argument allows you to specify and override which data type is contained
|
||||||
within the file specified in the `path` argument. Valid entries are `toml`, `json`, `csv`
|
within the file specified in the `path` argument. Valid entries are `toml`, `json`, `csv`
|
||||||
or `plain`. If the `format` argument isn't specified, then the paths extension is used.
|
or `plain`. If the `format` argument isn't specified, then the path extension is used.
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{% set data = load_data(path="content/blog/story/data.txt", format="json") %}
|
{% set data = load_data(path="content/blog/story/data.txt", format="json") %}
|
||||||
|
@ -176,8 +178,8 @@ or `plain`. If the `format` argument isn't specified, then the paths extension i
|
||||||
|
|
||||||
Use the `plain` format for when your file has a toml/json/csv extension but you want to load it as plain text.
|
Use the `plain` format for when your file has a toml/json/csv extension but you want to load it as plain text.
|
||||||
|
|
||||||
For *toml* and *json* the data is loaded into a structure matching the original data file,
|
For *toml* and *json*, the data is loaded into a structure matching the original data file;
|
||||||
however for *csv* there is no native notion of such a structure. Instead the data is separated
|
however, for *csv* there is no native notion of such a structure. Instead, the data is separated
|
||||||
into a data structure containing *headers* and *records*. See the example below to see
|
into a data structure containing *headers* and *records*. See the example below to see
|
||||||
how this works.
|
how this works.
|
||||||
|
|
||||||
|
@ -207,14 +209,16 @@ template:
|
||||||
|
|
||||||
#### Remote content
|
#### Remote content
|
||||||
|
|
||||||
Instead of using a file, you can load data from a remote URL. This can be done by specifying a `url` parameter to `load_data` rather than `path`.
|
Instead of using a file, you can load data from a remote URL. This can be done by specifying a `url` parameter
|
||||||
|
to `load_data` rather than `path`.
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{% set response = load_data(url="https://api.github.com/repos/getzola/zola") %}
|
{% set response = load_data(url="https://api.github.com/repos/getzola/zola") %}
|
||||||
{{ response }}
|
{{ response }}
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, the response body will be returned with no parsing. This can be changed by using the `format` argument as below.
|
By default, the response body will be returned with no parsing. This can be changed by using the `format` argument
|
||||||
|
as below.
|
||||||
|
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
|
@ -222,11 +226,13 @@ By default, the response body will be returned with no parsing. This can be chan
|
||||||
{{ response }}
|
{{ response }}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Data Caching
|
#### Data caching
|
||||||
|
|
||||||
Data file loading and remote requests are cached in memory during build, so multiple requests aren't made to the same endpoint.
|
Data file loading and remote requests are cached in memory during the build, so multiple requests aren't made
|
||||||
URLs are cached based on the URL, and data files are cached based on the files modified time.
|
to the same endpoint.
|
||||||
The format is also taken into account when caching, so a request will be sent twice if it's loaded with 2 different formats.
|
URLs are cached based on the URL, and data files are cached based on the file modified time.
|
||||||
|
The format is also taken into account when caching, so a request will be sent twice if it's loaded with two
|
||||||
|
different formats.
|
||||||
|
|
||||||
### `trans`
|
### `trans`
|
||||||
Gets the translation of the given `key`, for the `default_language` or the `lang`uage given
|
Gets the translation of the given `key`, for the `default_language` or the `lang`uage given
|
||||||
|
|
|
@ -3,11 +3,11 @@ title = "Sections and Pages"
|
||||||
weight = 20
|
weight = 20
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Pages and sections are actually very similar.
|
Templates for pages and sections are very similar.
|
||||||
|
|
||||||
## Page variables
|
## Page variables
|
||||||
Zola will try to load the `templates/page.html` template, the `page.html` template of the theme if one is used
|
Zola will try to load the `templates/page.html` template, the `page.html` template of the theme if one is used
|
||||||
or will render the built-in template: a blank page.
|
or render the built-in template (a blank page).
|
||||||
|
|
||||||
Whichever template you decide to render, you will get a `page` variable in your template
|
Whichever template you decide to render, you will get a `page` variable in your template
|
||||||
with the following fields:
|
with the following fields:
|
||||||
|
@ -27,6 +27,7 @@ permalink: String;
|
||||||
summary: String?;
|
summary: String?;
|
||||||
taxonomies: HashMap<String, Array<String>>;
|
taxonomies: HashMap<String, Array<String>>;
|
||||||
extra: HashMap<String, Any>;
|
extra: HashMap<String, Any>;
|
||||||
|
toc: Array<Header>,
|
||||||
// Naive word count, will not work for languages without whitespace
|
// Naive word count, will not work for languages without whitespace
|
||||||
word_count: Number;
|
word_count: Number;
|
||||||
// Based on https://help.medium.com/hc/en-us/articles/214991667-Read-time
|
// Based on https://help.medium.com/hc/en-us/articles/214991667-Read-time
|
||||||
|
@ -59,8 +60,8 @@ translations: Array<TranslatedContent>;
|
||||||
|
|
||||||
## Section variables
|
## Section variables
|
||||||
By default, Zola will try to load `templates/index.html` for `content/_index.md`
|
By default, Zola will try to load `templates/index.html` for `content/_index.md`
|
||||||
and `templates/section.html` for others `_index.md` files. If there isn't
|
and `templates/section.html` for other `_index.md` files. If there isn't
|
||||||
one, it will render the built-in template: a blank page.
|
one, it will render the built-in template (a blank page).
|
||||||
|
|
||||||
Whichever template you decide to render, you will get a `section` variable in your template
|
Whichever template you decide to render, you will get a `section` variable in your template
|
||||||
with the following fields:
|
with the following fields:
|
||||||
|
@ -81,6 +82,7 @@ pages: Array<Page>;
|
||||||
// This only contains the path to use in the `get_section` Tera function to get
|
// This only contains the path to use in the `get_section` Tera function to get
|
||||||
// the actual section object if you need it
|
// the actual section object if you need it
|
||||||
subsections: Array<String>;
|
subsections: Array<String>;
|
||||||
|
toc: Array<Header>,
|
||||||
// Unicode word count
|
// Unicode word count
|
||||||
word_count: Number;
|
word_count: Number;
|
||||||
// Based on https://help.medium.com/hc/en-us/articles/214991667-Read-time
|
// Based on https://help.medium.com/hc/en-us/articles/214991667-Read-time
|
||||||
|
@ -101,7 +103,7 @@ translations: Array<TranslatedContent>;
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
Both page and section templates have a `toc` variable which corresponds to an array of `Header`.
|
Both page and section templates have a `toc` variable that corresponds to an array of `Header`.
|
||||||
A `Header` has the following fields:
|
A `Header` has the following fields:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
@ -119,9 +121,9 @@ children: Array<Header>;
|
||||||
|
|
||||||
## Translated content
|
## Translated content
|
||||||
|
|
||||||
Both page and section have a `translations` field which corresponds to an array of `TranslatedContent`. If your site is not using multiple languages,
|
Both pages and sections have a `translations` field that corresponds to an array of `TranslatedContent`. If your
|
||||||
this will always be an empty array.
|
site is not using multiple languages, this will always be an empty array.
|
||||||
A `TranslatedContent` has the following fields:
|
`TranslatedContent` has the following fields:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// The language code for that content, empty if it is the default language
|
// The language code for that content, empty if it is the default language
|
||||||
|
@ -130,5 +132,8 @@ lang: String?;
|
||||||
title: String?;
|
title: String?;
|
||||||
// A permalink to that content
|
// A permalink to that content
|
||||||
permalink: String;
|
permalink: String;
|
||||||
|
// The path to the markdown file; useful for retrieving the full page through
|
||||||
|
// the `get_page` function.
|
||||||
|
path: String;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,12 @@ next: String?;
|
||||||
pages: Array<Page>;
|
pages: Array<Page>;
|
||||||
// Which pager are we on
|
// Which pager are we on
|
||||||
current_index: Number;
|
current_index: Number;
|
||||||
|
// Total number of pages accross all the pagers
|
||||||
|
total_pages: Number;
|
||||||
```
|
```
|
||||||
|
|
||||||
A pager is a page of the pagination: if you have 100 pages and are paginating 10 by 10, you will have 10 pagers containing
|
A pager is a page of the pagination; if you have 100 pages and paginate_by is set to 10, you will have 10 pagers each
|
||||||
each 10 pages.
|
containing 10 pages.
|
||||||
|
|
||||||
## Section
|
## Section
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ weight = 70
|
||||||
Zola will look for a `robots.txt` file in the `templates` directory or
|
Zola will look for a `robots.txt` file in the `templates` directory or
|
||||||
use the built-in one.
|
use the built-in one.
|
||||||
|
|
||||||
Robots.txt is the simplest of all templates: it only gets the config
|
Robots.txt is the simplest of all templates: it only gets `config`
|
||||||
and the default is what most site want:
|
and the default is what most sites want:
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
User-agent: *
|
User-agent: *
|
||||||
|
|
|
@ -5,14 +5,14 @@ weight = 50
|
||||||
|
|
||||||
If the site `config.toml` file sets `generate_rss = true`, then Zola will
|
If the site `config.toml` file sets `generate_rss = true`, then Zola will
|
||||||
generate an `rss.xml` page for the site, which will live at `base_url/rss.xml`. To
|
generate an `rss.xml` page for the site, which will live at `base_url/rss.xml`. To
|
||||||
generate the `rss.xml` page, Zola will look for a `rss.xml` file in the `templates`
|
generate the `rss.xml` page, Zola will look for an `rss.xml` file in the `templates`
|
||||||
directory or, if one does not exist, will use the use the built-in rss template.
|
directory or, if one does not exist, it will use the use the built-in rss template.
|
||||||
|
|
||||||
**Only pages with a date will be available.**
|
**Only pages with a date will be available.**
|
||||||
|
|
||||||
The RSS template gets three variables in addition of the config:
|
The RSS template gets three variables in addition to `config`:
|
||||||
|
|
||||||
- `feed_url`: the full url to that specific feed
|
- `feed_url`: the full url to that specific feed
|
||||||
- `last_build_date`: the date of the latest post
|
- `last_build_date`: the date of the latest post
|
||||||
- `pages`: see [the page variables](@/documentation/templates/pages-sections.md#page-variables) for
|
- `pages`: see [page variables](@/documentation/templates/pages-sections.md#page-variables) for
|
||||||
a detailed description of what this contains
|
a detailed description of what this contains
|
||||||
|
|
|
@ -7,11 +7,12 @@ Zola will look for a `sitemap.xml` file in the `templates` directory or
|
||||||
use the built-in one.
|
use the built-in one.
|
||||||
|
|
||||||
If your site has more than 30 000 pages, it will automatically split
|
If your site has more than 30 000 pages, it will automatically split
|
||||||
the links into multiple sitemaps as recommended by [Google](https://support.google.com/webmasters/answer/183668?hl=en):
|
the links into multiple sitemaps, as recommended by [Google](https://support.google.com/webmasters/answer/183668?hl=en):
|
||||||
|
|
||||||
> All formats limit a single sitemap to 50MB (uncompressed) and 50,000 URLs.
|
> All formats limit a single sitemap to 50MB (uncompressed) and 50,000 URLs.
|
||||||
> If you have a larger file or more URLs, you will have to break your list into multiple sitemaps.
|
> If you have a larger file or more URLs, you will have to break your list into multiple sitemaps.
|
||||||
> You can optionally create a sitemap index file (a file that points to a list of sitemaps) and submit that single index file to Google.
|
> You can optionally create a sitemap index file (a file that points to a list of sitemaps) and submit
|
||||||
|
> that single index file to Google.
|
||||||
|
|
||||||
In such a case, Zola will use a template called `split_sitemap_index.xml` to render the index sitemap.
|
In such a case, Zola will use a template called `split_sitemap_index.xml` to render the index sitemap.
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ Zola will look up the following files in the `templates` directory:
|
||||||
- `$TAXONOMY_NAME/single.html`
|
- `$TAXONOMY_NAME/single.html`
|
||||||
- `$TAXONOMY_NAME/list.html`
|
- `$TAXONOMY_NAME/list.html`
|
||||||
|
|
||||||
First, a `TaxonomyTerm` has the following fields:
|
First, `TaxonomyTerm` has the following fields:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
name: String;
|
name: String;
|
||||||
|
@ -17,7 +17,7 @@ permalink: String;
|
||||||
pages: Array<Page>;
|
pages: Array<Page>;
|
||||||
```
|
```
|
||||||
|
|
||||||
and a `TaxonomyConfig`:
|
and `TaxonomyConfig` has the following fields:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -30,7 +30,7 @@ rss: Bool;
|
||||||
|
|
||||||
### Taxonomy list (`list.html`)
|
### Taxonomy list (`list.html`)
|
||||||
|
|
||||||
This template is never paginated and therefore get the following variables in all cases.
|
This template is never paginated and therefore gets the following variables in all cases.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// The site config
|
// The site config
|
||||||
|
@ -64,5 +64,5 @@ term: TaxonomyTerm;
|
||||||
lang: String;
|
lang: String;
|
||||||
```
|
```
|
||||||
|
|
||||||
A paginated taxonomy term will also get a `paginator` variable, see the [pagination page](@/documentation/templates/pagination.md)
|
A paginated taxonomy term will also get a `paginator` variable; see the [pagination page]
|
||||||
for more details on that.
|
(@/documentation/templates/pagination.md) for more details.
|
||||||
|
|
|
@ -8,9 +8,9 @@ will want to use many [Tera blocks](https://tera.netlify.com/docs#inheritance) t
|
||||||
allow users to easily modify it.
|
allow users to easily modify it.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
As mentioned, a theme is just like any site: start with running `zola init MY_THEME_NAME`.
|
As mentioned, a theme is just like any site; start by running `zola init MY_THEME_NAME`.
|
||||||
|
|
||||||
The only thing needed to turn that site into a theme is to add `theme.toml` configuration file with the
|
The only thing needed to turn that site into a theme is to add a `theme.toml` configuration file with the
|
||||||
following fields:
|
following fields:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -42,11 +42,11 @@ homepage = "http://markdotto.com/"
|
||||||
repo = "https://www.github.com/mdo/hyde"
|
repo = "https://www.github.com/mdo/hyde"
|
||||||
```
|
```
|
||||||
|
|
||||||
A simple theme you can use as example is [Hyde](https://github.com/Keats/hyde).
|
A simple theme you can use as an example is [Hyde](https://github.com/Keats/hyde).
|
||||||
|
|
||||||
## Working on a theme
|
## Working on a theme
|
||||||
As a theme is just a site, you can simply use `zola serve` and make changes to your
|
As a theme is just a site, you can simply use `zola serve` and make changes to your
|
||||||
theme, with live reloading working as expected.
|
theme, with live reload working as expected.
|
||||||
|
|
||||||
Make sure to commit every directory (including `content`) in order for other people
|
Make sure to commit every directory (including `content`) in order for other people
|
||||||
to be able to build the theme from your repository.
|
to be able to build the theme from your repository.
|
||||||
|
@ -65,7 +65,7 @@ of this site, the theme will require two more things:
|
||||||
- `README.md`: a thorough README explaining how to use the theme and any other information
|
- `README.md`: a thorough README explaining how to use the theme and any other information
|
||||||
of importance
|
of importance
|
||||||
|
|
||||||
The first step is to make sure the theme is fulfilling those three requirements:
|
The first step is to make sure that the theme meets the following three requirements:
|
||||||
|
|
||||||
- have a `screenshot.png` of the theme in action with a max size of around 2000x1000
|
- have a `screenshot.png` of the theme in action with a max size of around 2000x1000
|
||||||
- have a thorough `README.md` explaining how to use the theme and any other information
|
- have a thorough `README.md` explaining how to use the theme and any other information
|
||||||
|
|
|
@ -15,24 +15,24 @@ $ git clone THEME_REPO_URL
|
||||||
```
|
```
|
||||||
|
|
||||||
Cloning the repository using Git or another VCS will allow you to easily
|
Cloning the repository using Git or another VCS will allow you to easily
|
||||||
update it but you can also simply download the files manually and paste
|
update. Alternatively, you can download the files manually and place
|
||||||
them in a folder.
|
them in a folder.
|
||||||
|
|
||||||
You can find a list of themes [on this very website](@/themes/_index.md).
|
You can find a list of themes [here](@/themes/_index.md).
|
||||||
|
|
||||||
## Using a theme
|
## Using a theme
|
||||||
|
|
||||||
Now that you have the theme in your `themes` directory, you only need to tell
|
Now that you have the theme in your `themes` directory, you need to tell
|
||||||
Zola to use it to get started by setting the `theme` variable of the
|
Zola to use it by setting the `theme` variable in the
|
||||||
[configuration file](@/documentation/getting-started/configuration.md). The theme
|
[configuration file](@/documentation/getting-started/configuration.md). The theme
|
||||||
name has to be name of the directory you cloned the theme in.
|
name has to be the name of the directory you cloned the theme in.
|
||||||
For example, if you cloned a theme in `themes/simple-blog`, the theme name to use
|
For example, if you cloned a theme in `themes/simple-blog`, the theme name to use
|
||||||
in the configuration file is `simple-blog`.
|
in the configuration file is `simple-blog`.
|
||||||
|
|
||||||
## Customizing a theme
|
## Customizing a theme
|
||||||
|
|
||||||
Any file from the theme can be overriden by creating a file with the same path and name in your `templates` or `static`
|
Any file from the theme can be overriden by creating a file with the same path and name in your `templates` or `static`
|
||||||
directory. Here are a few examples of that, assuming the theme name is `simple-blog`:
|
directory. Here are a few examples of that, assuming that the theme name is `simple-blog`:
|
||||||
|
|
||||||
```plain
|
```plain
|
||||||
templates/pages/post.html -> replace themes/simple-blog/templates/pages/post.html
|
templates/pages/post.html -> replace themes/simple-blog/templates/pages/post.html
|
||||||
|
@ -40,7 +40,7 @@ templates/macros.html -> replace themes/simple-blog/templates/macros.html
|
||||||
static/js/site.js -> replace themes/simple-blog/static/js/site.js
|
static/js/site.js -> replace themes/simple-blog/static/js/site.js
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also choose to only override parts of a page if a theme define some blocks by extending it. If we wanted
|
You can also choose to only override parts of a page if a theme defines some blocks by extending it. If we wanted
|
||||||
to only change a single block from the `post.html` page in the example above, we could do the following:
|
to only change a single block from the `post.html` page in the example above, we could do the following:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -51,7 +51,7 @@ Some custom data
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
```
|
```
|
||||||
|
|
||||||
Most themes will also provide some variables that are meant to be overriden: this happens in the `extra` section
|
Most themes will also provide some variables that are meant to be overriden. This happens in the `extra` section
|
||||||
of the [configuration file](@/documentation/getting-started/configuration.md).
|
of the [configuration file](@/documentation/getting-started/configuration.md).
|
||||||
Let's say a theme uses a `show_twitter` variable and sets it to `false` by default. If you want to set it to `true`,
|
Let's say a theme uses a `show_twitter` variable and sets it to `false` by default. If you want to set it to `true`,
|
||||||
you can update your `config.toml` like so:
|
you can update your `config.toml` like so:
|
||||||
|
@ -61,5 +61,5 @@ you can update your `config.toml` like so:
|
||||||
show_twitter = false
|
show_twitter = false
|
||||||
```
|
```
|
||||||
|
|
||||||
You can modify files directly in the `themes` directory but this will make updating the theme harder and live reload won't work with those
|
You can modify files directly in the `themes` directory but this will make updating the theme harder and live reload
|
||||||
files.
|
won't work with these files.
|
||||||
|
|
|
@ -3,10 +3,9 @@ title = "Overview"
|
||||||
weight = 10
|
weight = 10
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Zola has built-in support for themes in a way that are easy to customise
|
Zola has built-in support for themes that makes it easy to customise and update them.
|
||||||
but still easy to update if needed.
|
|
||||||
|
|
||||||
All themes can use the full power of Zola, from shortcodes to Sass compilation.
|
All themes can use the full power of Zola, from shortcodes to Sass compilation.
|
||||||
|
|
||||||
A list of themes is available [on this very website](@/themes/_index.md).
|
A list of themes is available [here](@/themes/_index.md).
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
margin-left: -2rem;
|
margin-left: -2rem;
|
||||||
|
margin-right: 0.75rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-bottom-color: transparent;
|
border-bottom-color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue