@@ -10,7 +10,13 @@ | |||||
- All Tera global fns are now rebuilt on changes | - All Tera global fns are now rebuilt on changes | ||||
- Use flags for port/interface in `gutenberg serve` | - Use flags for port/interface in `gutenberg serve` | ||||
- Fix various issues with headers markdown rendering | - Fix various issues with headers markdown rendering | ||||
- Rename `insert_anchor` in section front-matter to `insert_anchor_links` | |||||
- Remove `insert_anchor_links` from the config: it wasn't used | |||||
- Add `class` variable to `gist` shortcode | |||||
- Add reading analytics to sections content | |||||
- Add config to sitemap template | |||||
- Add `permalink` to all taxonomy items (tags & categories) | |||||
- Tags in the tags page are now sorting alphabetically instead of by number of pages in them | |||||
## 0.1.3 (2017-08-31) | ## 0.1.3 (2017-08-31) | ||||
@@ -47,10 +47,6 @@ pub struct Config { | |||||
pub generate_tags_pages: Option<bool>, | pub generate_tags_pages: Option<bool>, | ||||
/// Whether to generate categories and individual tag categories if some pages have them. Defaults to true | /// Whether to generate categories and individual tag categories if some pages have them. Defaults to true | ||||
pub generate_categories_pages: Option<bool>, | pub generate_categories_pages: Option<bool>, | ||||
/// Whether to insert a link for each header like in Github READMEs. Defaults to false | |||||
/// The default template can be overridden by creating a `anchor-link.html` template and CSS will need to be | |||||
/// written if you turn that on. | |||||
pub insert_anchor_links: Option<bool>, | |||||
/// Whether to compile the `sass` directory and output the css files into the static folder | /// Whether to compile the `sass` directory and output the css files into the static folder | ||||
pub compile_sass: Option<bool>, | pub compile_sass: Option<bool>, | ||||
@@ -84,7 +80,6 @@ impl Config { | |||||
set_default!(config.rss_limit, 20); | set_default!(config.rss_limit, 20); | ||||
set_default!(config.generate_tags_pages, false); | set_default!(config.generate_tags_pages, false); | ||||
set_default!(config.generate_categories_pages, false); | set_default!(config.generate_categories_pages, false); | ||||
set_default!(config.insert_anchor_links, false); | |||||
set_default!(config.compile_sass, false); | set_default!(config.compile_sass, false); | ||||
set_default!(config.extra, HashMap::new()); | set_default!(config.extra, HashMap::new()); | ||||
@@ -174,7 +169,6 @@ impl Default for Config { | |||||
rss_limit: Some(10_000), | rss_limit: Some(10_000), | ||||
generate_tags_pages: Some(true), | generate_tags_pages: Some(true), | ||||
generate_categories_pages: Some(true), | generate_categories_pages: Some(true), | ||||
insert_anchor_links: Some(false), | |||||
compile_sass: Some(false), | compile_sass: Some(false), | ||||
extra: None, | extra: None, | ||||
build_timestamp: Some(1), | build_timestamp: Some(1), | ||||
@@ -10,6 +10,7 @@ use front_matter::{SectionFrontMatter, split_section_content}; | |||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use utils::fs::read_file; | use utils::fs::read_file; | ||||
use utils::templates::render_template; | use utils::templates::render_template; | ||||
use utils::site::get_reading_analytics; | |||||
use rendering::{Context, Header, markdown_to_html}; | use rendering::{Context, Header, markdown_to_html}; | ||||
use page::Page; | use page::Page; | ||||
@@ -96,7 +97,7 @@ impl Section { | |||||
config.highlight_theme.clone().unwrap(), | config.highlight_theme.clone().unwrap(), | ||||
&self.permalink, | &self.permalink, | ||||
permalinks, | permalinks, | ||||
self.meta.insert_anchor.unwrap() | |||||
self.meta.insert_anchor_links.unwrap() | |||||
); | ); | ||||
let res = markdown_to_html(&self.raw_content, &context)?; | let res = markdown_to_html(&self.raw_content, &context)?; | ||||
self.content = res.0; | self.content = res.0; | ||||
@@ -139,7 +140,7 @@ impl Section { | |||||
impl ser::Serialize for Section { | impl ser::Serialize for Section { | ||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer { | fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer { | ||||
let mut state = serializer.serialize_struct("section", 10)?; | |||||
let mut state = serializer.serialize_struct("section", 12)?; | |||||
state.serialize_field("content", &self.content)?; | state.serialize_field("content", &self.content)?; | ||||
state.serialize_field("permalink", &self.permalink)?; | state.serialize_field("permalink", &self.permalink)?; | ||||
state.serialize_field("title", &self.meta.title)?; | state.serialize_field("title", &self.meta.title)?; | ||||
@@ -149,6 +150,9 @@ impl ser::Serialize for Section { | |||||
state.serialize_field("permalink", &self.permalink)?; | state.serialize_field("permalink", &self.permalink)?; | ||||
state.serialize_field("pages", &self.pages)?; | state.serialize_field("pages", &self.pages)?; | ||||
state.serialize_field("subsections", &self.subsections)?; | state.serialize_field("subsections", &self.subsections)?; | ||||
let (word_count, reading_time) = get_reading_analytics(&self.raw_content); | |||||
state.serialize_field("word_count", &word_count)?; | |||||
state.serialize_field("reading_time", &reading_time)?; | |||||
state.serialize_field("toc", &self.toc)?; | state.serialize_field("toc", &self.toc)?; | ||||
state.end() | state.end() | ||||
} | } | ||||
@@ -33,7 +33,7 @@ pub fn sort_pages(pages: Vec<Page>, sort_by: SortBy) -> (Vec<Page>, Vec<Page>) { | |||||
(can_be_sorted, cannot_be_sorted) | (can_be_sorted, cannot_be_sorted) | ||||
} | } | ||||
/// Horribly inefficient way to set previous and next on each pages | |||||
/// Horribly inefficient way to set previous and next on each pages that skips drafts | |||||
/// So many clones | /// So many clones | ||||
pub fn populate_previous_and_next_pages(input: &[Page]) -> Vec<Page> { | pub fn populate_previous_and_next_pages(input: &[Page]) -> Vec<Page> { | ||||
let mut res = Vec::with_capacity(input.len()); | let mut res = Vec::with_capacity(input.len()); | ||||
@@ -28,9 +28,13 @@ lazy_static! { | |||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] | ||||
#[serde(rename_all = "lowercase")] | #[serde(rename_all = "lowercase")] | ||||
pub enum SortBy { | pub enum SortBy { | ||||
/// Most recent to oldest | |||||
Date, | Date, | ||||
/// Lower order comes last | |||||
Order, | Order, | ||||
/// Lower weight comes first | |||||
Weight, | Weight, | ||||
/// No sorting | |||||
None, | None, | ||||
} | } | ||||
@@ -33,6 +33,7 @@ pub struct PageFrontMatter { | |||||
/// Integer to use to order content. Highest is at the bottom, lowest first | /// Integer to use to order content. Highest is at the bottom, lowest first | ||||
pub weight: Option<usize>, | pub weight: Option<usize>, | ||||
/// All aliases for that page. Gutenberg will create HTML templates that will | /// All aliases for that page. Gutenberg will create HTML templates that will | ||||
/// redirect to this | |||||
#[serde(skip_serializing)] | #[serde(skip_serializing)] | ||||
pub aliases: Option<Vec<String>>, | pub aliases: Option<Vec<String>>, | ||||
/// Specify a template different from `page.html` to use for that page | /// Specify a template different from `page.html` to use for that page | ||||
@@ -20,8 +20,8 @@ pub struct SectionFrontMatter { | |||||
/// Whether to sort by "date", "order", "weight" or "none". Defaults to `none`. | /// Whether to sort by "date", "order", "weight" or "none". Defaults to `none`. | ||||
#[serde(skip_serializing)] | #[serde(skip_serializing)] | ||||
pub sort_by: Option<SortBy>, | pub sort_by: Option<SortBy>, | ||||
/// The weight for this section. This is used by the parent section to order its subsections. | |||||
/// Higher values means it ends at the end. | |||||
/// Used by the parent section to order its subsections. | |||||
/// Higher values means it will be at the end. | |||||
#[serde(skip_serializing)] | #[serde(skip_serializing)] | ||||
pub weight: Option<usize>, | pub weight: Option<usize>, | ||||
/// Optional template, if we want to specify which template to render for that page | /// Optional template, if we want to specify which template to render for that page | ||||
@@ -33,10 +33,9 @@ pub struct SectionFrontMatter { | |||||
/// Path to be used by pagination: the page number will be appended after it. Defaults to `page`. | /// Path to be used by pagination: the page number will be appended after it. Defaults to `page`. | ||||
#[serde(skip_serializing)] | #[serde(skip_serializing)] | ||||
pub paginate_path: Option<String>, | pub paginate_path: Option<String>, | ||||
/// Whether to insert a link for each header like in Github READMEs. Defaults to false | |||||
/// The default template can be overridden by creating a `anchor-link.html` template and CSS will need to be | |||||
/// written if you turn that on. | |||||
pub insert_anchor: Option<InsertAnchor>, | |||||
/// Whether to insert a link for each header like the ones you can see in this site if you hover one | |||||
/// The default template can be overridden by creating a `anchor-link.html` in the `templates` directory | |||||
pub insert_anchor_links: Option<InsertAnchor>, | |||||
/// Whether to render that section or not. Defaults to `true`. | /// Whether to render that section or not. Defaults to `true`. | ||||
/// Useful when the section is only there to organize things but is not meant | /// Useful when the section is only there to organize things but is not meant | ||||
/// to be used directly, like a posts section in a personal site | /// to be used directly, like a posts section in a personal site | ||||
@@ -70,8 +69,8 @@ impl SectionFrontMatter { | |||||
f.sort_by = Some(SortBy::None); | f.sort_by = Some(SortBy::None); | ||||
} | } | ||||
if f.insert_anchor.is_none() { | |||||
f.insert_anchor = Some(InsertAnchor::None); | |||||
if f.insert_anchor_links.is_none() { | |||||
f.insert_anchor_links = Some(InsertAnchor::None); | |||||
} | } | ||||
if f.weight.is_none() { | if f.weight.is_none() { | ||||
@@ -111,7 +110,7 @@ impl Default for SectionFrontMatter { | |||||
paginate_path: Some(DEFAULT_PAGINATE_PATH.to_string()), | paginate_path: Some(DEFAULT_PAGINATE_PATH.to_string()), | ||||
render: Some(true), | render: Some(true), | ||||
redirect_to: None, | redirect_to: None, | ||||
insert_anchor: Some(InsertAnchor::None), | |||||
insert_anchor_links: Some(InsertAnchor::None), | |||||
extra: None, | extra: None, | ||||
} | } | ||||
} | } | ||||
@@ -177,7 +177,8 @@ impl Site { | |||||
.map(|entry| { | .map(|entry| { | ||||
let path = entry.as_path(); | let path = entry.as_path(); | ||||
Section::from_file(path, config) | Section::from_file(path, config) | ||||
}).collect::<Vec<_>>() | |||||
}) | |||||
.collect::<Vec<_>>() | |||||
}; | }; | ||||
let pages = { | let pages = { | ||||
@@ -189,7 +190,8 @@ impl Site { | |||||
.map(|entry| { | .map(|entry| { | ||||
let path = entry.as_path(); | let path = entry.as_path(); | ||||
Page::from_file(path, config) | Page::from_file(path, config) | ||||
}).collect::<Vec<_>>() | |||||
}) | |||||
.collect::<Vec<_>>() | |||||
}; | }; | ||||
// Kinda duplicated code for add_section/add_page but necessary to do it that | // Kinda duplicated code for add_section/add_page but necessary to do it that | ||||
@@ -224,8 +226,7 @@ impl Site { | |||||
let config = &self.config; | let config = &self.config; | ||||
self.pages.par_iter_mut() | self.pages.par_iter_mut() | ||||
.map(|(_, page)| page) | |||||
.map(|page| { | |||||
.map(|(_, page)| { | |||||
let insert_anchor = pages_insert_anchors[&page.file.path]; | let insert_anchor = pages_insert_anchors[&page.file.path]; | ||||
page.render_markdown(permalinks, tera, config, insert_anchor) | page.render_markdown(permalinks, tera, config, insert_anchor) | ||||
}) | }) | ||||
@@ -233,8 +234,7 @@ impl Site { | |||||
.reduce(|| Ok(()), Result::and)?; | .reduce(|| Ok(()), Result::and)?; | ||||
self.sections.par_iter_mut() | self.sections.par_iter_mut() | ||||
.map(|(_, section)| section) | |||||
.map(|section| section.render_markdown(permalinks, tera, config)) | |||||
.map(|(_, section)| section.render_markdown(permalinks, tera, config)) | |||||
.fold(|| Ok(()), Result::and) | .fold(|| Ok(()), Result::and) | ||||
.reduce(|| Ok(()), Result::and)?; | .reduce(|| Ok(()), Result::and)?; | ||||
} | } | ||||
@@ -295,7 +295,7 @@ impl Site { | |||||
/// Defaults to `AnchorInsert::None` if no parent section found | /// Defaults to `AnchorInsert::None` if no parent section found | ||||
pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf) -> InsertAnchor { | pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf) -> InsertAnchor { | ||||
match self.sections.get(&parent_path.join("_index.md")) { | match self.sections.get(&parent_path.join("_index.md")) { | ||||
Some(s) => s.meta.insert_anchor.unwrap(), | |||||
Some(s) => s.meta.insert_anchor_links.unwrap(), | |||||
None => InsertAnchor::None | None => InsertAnchor::None | ||||
} | } | ||||
} | } | ||||
@@ -366,6 +366,7 @@ impl Site { | |||||
// TODO: can we pass a reference? | // TODO: can we pass a reference? | ||||
let (tags, categories) = Taxonomy::find_tags_and_categories( | let (tags, categories) = Taxonomy::find_tags_and_categories( | ||||
&self.config, | |||||
self.pages | self.pages | ||||
.values() | .values() | ||||
.filter(|p| !p.is_draft()) | .filter(|p| !p.is_draft()) | ||||
@@ -647,6 +648,7 @@ impl Site { | |||||
} | } | ||||
} | } | ||||
context.add("tags", &tags); | context.add("tags", &tags); | ||||
context.add("config", &self.config); | |||||
let sitemap = &render_template("sitemap.xml", &self.tera, &context, self.config.theme.clone())?; | let sitemap = &render_template("sitemap.xml", &self.tera, &context, self.config.theme.clone())?; | ||||
@@ -2,5 +2,5 @@ | |||||
title = "Posts" | title = "Posts" | ||||
paginate_by = 2 | paginate_by = 2 | ||||
template = "section_paginated.html" | template = "section_paginated.html" | ||||
insert_anchor = "left" | |||||
insert_anchor_links = "left" | |||||
+++ | +++ |
@@ -1,4 +1,5 @@ | |||||
extern crate site; | extern crate site; | ||||
extern crate front_matter; | |||||
extern crate tempdir; | extern crate tempdir; | ||||
use std::env; | use std::env; | ||||
@@ -8,6 +9,7 @@ use std::io::prelude::*; | |||||
use tempdir::TempDir; | use tempdir::TempDir; | ||||
use site::Site; | use site::Site; | ||||
use front_matter::InsertAnchor; | |||||
#[test] | #[test] | ||||
@@ -296,6 +298,7 @@ fn can_build_site_and_insert_anchor_links() { | |||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.load().unwrap(); | site.load().unwrap(); | ||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | let tmp_dir = TempDir::new("example").expect("create temp dir"); | ||||
let public = &tmp_dir.path().join("public"); | let public = &tmp_dir.path().join("public"); | ||||
site.set_output_path(&public); | site.set_output_path(&public); | ||||
@@ -32,18 +32,30 @@ pub enum TaxonomyKind { | |||||
pub struct TaxonomyItem { | pub struct TaxonomyItem { | ||||
pub name: String, | pub name: String, | ||||
pub slug: String, | pub slug: String, | ||||
pub permalink: String, | |||||
pub pages: Vec<Page>, | pub pages: Vec<Page>, | ||||
} | } | ||||
impl TaxonomyItem { | impl TaxonomyItem { | ||||
pub fn new(name: &str, pages: Vec<Page>) -> TaxonomyItem { | |||||
// We shouldn't have any pages without dates there | |||||
let (sorted_pages, _) = sort_pages(pages, SortBy::Date); | |||||
pub fn new(name: &str, kind: TaxonomyKind, config: &Config, pages: Vec<Page>) -> TaxonomyItem { | |||||
// Taxonomy are almost always used for blogs so we filter by dates | |||||
// and it's not like we can sort things across sections by anything other | |||||
// than dates | |||||
let (mut pages, ignored_pages) = sort_pages(pages, SortBy::Date); | |||||
let slug = slugify(name); | |||||
let permalink = { | |||||
let kind_path = if kind == TaxonomyKind::Tags { "tag" } else { "category" }; | |||||
config.make_permalink(&format!("/{}/{}", kind_path, slug)) | |||||
}; | |||||
// We still append pages without dates at the end | |||||
pages.extend(ignored_pages); | |||||
TaxonomyItem { | TaxonomyItem { | ||||
name: name.to_string(), | name: name.to_string(), | ||||
slug: slugify(name), | |||||
pages: sorted_pages, | |||||
permalink, | |||||
slug, | |||||
pages, | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -57,21 +69,12 @@ pub struct Taxonomy { | |||||
} | } | ||||
impl Taxonomy { | impl Taxonomy { | ||||
// TODO: take a Vec<&'a Page> if it makes a difference in terms of perf for actual sites | |||||
pub fn find_tags_and_categories(all_pages: &[Page]) -> (Taxonomy, Taxonomy) { | |||||
pub fn find_tags_and_categories(config: &Config, all_pages: &[Page]) -> (Taxonomy, Taxonomy) { | |||||
let mut tags = HashMap::new(); | let mut tags = HashMap::new(); | ||||
let mut categories = HashMap::new(); | let mut categories = HashMap::new(); | ||||
// Find all the tags/categories first | // Find all the tags/categories first | ||||
for page in all_pages { | for page in all_pages { | ||||
// Don't consider pages without pages for tags/categories as that's the only thing | |||||
// we can sort pages with across sections | |||||
// If anyone sees that comment and wonder wtf, please open an issue as I can't think of | |||||
// usecases other than blog posts for built-in taxonomies | |||||
if page.meta.date.is_none() { | |||||
continue; | |||||
} | |||||
if let Some(ref category) = page.meta.category { | if let Some(ref category) = page.meta.category { | ||||
categories | categories | ||||
.entry(category.to_string()) | .entry(category.to_string()) | ||||
@@ -90,20 +93,20 @@ impl Taxonomy { | |||||
} | } | ||||
// Then make TaxonomyItem out of them, after sorting it | // Then make TaxonomyItem out of them, after sorting it | ||||
let tags_taxonomy = Taxonomy::new(TaxonomyKind::Tags, tags); | |||||
let categories_taxonomy = Taxonomy::new(TaxonomyKind::Categories, categories); | |||||
let tags_taxonomy = Taxonomy::new(TaxonomyKind::Tags, config, tags); | |||||
let categories_taxonomy = Taxonomy::new(TaxonomyKind::Categories, config, categories); | |||||
(tags_taxonomy, categories_taxonomy) | (tags_taxonomy, categories_taxonomy) | ||||
} | } | ||||
fn new(kind: TaxonomyKind, items: HashMap<String, Vec<Page>>) -> Taxonomy { | |||||
fn new(kind: TaxonomyKind, config: &Config, items: HashMap<String, Vec<Page>>) -> Taxonomy { | |||||
let mut sorted_items = vec![]; | let mut sorted_items = vec![]; | ||||
for (name, pages) in &items { | for (name, pages) in &items { | ||||
sorted_items.push( | sorted_items.push( | ||||
TaxonomyItem::new(name, pages.clone()) | |||||
TaxonomyItem::new(name, kind, config, pages.clone()) | |||||
); | ); | ||||
} | } | ||||
sorted_items.sort_by(|a, b| b.pages.len().cmp(&a.pages.len())); | |||||
sorted_items.sort_by(|a, b| a.name.cmp(&b.name)); | |||||
Taxonomy { | Taxonomy { | ||||
kind, | kind, | ||||
@@ -157,3 +160,55 @@ impl Taxonomy { | |||||
.chain_err(|| format!("Failed to render {} page.", name)) | .chain_err(|| format!("Failed to render {} page.", name)) | ||||
} | } | ||||
} | } | ||||
#[cfg(test)] | |||||
mod tests { | |||||
use super::*; | |||||
use config::Config; | |||||
use content::Page; | |||||
#[test] | |||||
fn can_make_taxonomies() { | |||||
let config = Config::default(); | |||||
let mut page1 = Page::default(); | |||||
page1.meta.tags = Some(vec!["rust".to_string(), "db".to_string()]); | |||||
page1.meta.category = Some("Programming tutorials".to_string()); | |||||
let mut page2 = Page::default(); | |||||
page2.meta.tags = Some(vec!["rust".to_string(), "js".to_string()]); | |||||
page2.meta.category = Some("Other".to_string()); | |||||
let mut page3 = Page::default(); | |||||
page3.meta.tags = Some(vec!["js".to_string()]); | |||||
let pages = vec![page1, page2, page3]; | |||||
let (tags, categories) = Taxonomy::find_tags_and_categories(&config, &pages); | |||||
assert_eq!(tags.items.len(), 3); | |||||
assert_eq!(categories.items.len(), 2); | |||||
assert_eq!(tags.items[0].name, "db"); | |||||
assert_eq!(tags.items[0].slug, "db"); | |||||
assert_eq!(tags.items[0].permalink, "http://a-website.com/tag/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/tag/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/tag/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/category/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/category/programming-tutorials/"); | |||||
assert_eq!(categories.items[1].pages.len(), 1); | |||||
} | |||||
} |
@@ -1,3 +1,3 @@ | |||||
<div> | |||||
<div {% if class %}class="{{class}}"{% endif %}> | |||||
<script src="{{ url }}.js{% if file %}?file={{file}}{% endif %}"></script> | <script src="{{ url }}.js{% if file %}?file={{file}}{% endif %}"></script> | ||||
</div> | </div> |
@@ -1,9 +1,10 @@ | |||||
base_url = "https://example.com" | base_url = "https://example.com" | ||||
title = "Gutenberg - your one-stop static site engine" | |||||
title = "Gutenberg" | |||||
description = "Everything you need to make a static site engine in one binary. And it's fast" | description = "Everything you need to make a static site engine in one binary. And it's fast" | ||||
compile_sass = true | compile_sass = true | ||||
highlight_code = true | highlight_code = true | ||||
insert_anchor_links = true | |||||
[extra] | [extra] | ||||
author = "Vincent Prouillet" | author = "Vincent Prouillet" |
@@ -10,24 +10,18 @@ Getting started | |||||
Content | Content | ||||
- Organisation | - Organisation | ||||
- Pages | |||||
- Sections | - Sections | ||||
- Pages | |||||
- Shortcodes | - Shortcodes | ||||
- Internal links | |||||
- Internal links & deep linking | |||||
- Table of contents | - Table of contents | ||||
- Deep linking (# links) | |||||
- Syntax highlighting | - Syntax highlighting | ||||
- Pagination | |||||
- Tag & categories | |||||
- RSS | |||||
- Sitemap | |||||
Templates | Templates | ||||
- Intro | - Intro | ||||
- Each kind of page and the variables available | - Each kind of page and the variables available | ||||
- Built-in global functions | - Built-in global functions | ||||
- Built-in filters | - Built-in filters | ||||
- Debugging | |||||
Theme | Theme | ||||
- Installing & customising a theme | - Installing & customising a theme | ||||
@@ -0,0 +1,7 @@ | |||||
+++ | |||||
title = "Content" | |||||
weight = 2 | |||||
sort_by = "weight" | |||||
redirect_to = "documentation/content/overview" | |||||
insert_anchor_links = "left" | |||||
+++ |
@@ -0,0 +1,37 @@ | |||||
+++ | |||||
title = "Internal links & deep linking" | |||||
weight = 50 | |||||
+++ | |||||
## Header id and anchor insertion | |||||
While rendering the markdown content, a unique id will automatically be assigned to each header. This id is created | |||||
by converting the header text to a [slug](https://en.wikipedia.org/wiki/Semantic_URL#Slug), appending numbers at the end | |||||
if the slug already exists for that article. For example: | |||||
```md | |||||
# Something exciting! <- something-exciting | |||||
## Example code <- example-code | |||||
# Something else <- something-else | |||||
## Example code <- example-code-1 | |||||
``` | |||||
## Anchor insertion | |||||
It is possible to have Gutenberg automatically insert anchor links next to the header, as you can see on the site you are currently | |||||
reading if you hover a title. | |||||
This option is set at the section level, look up the `insert_anchor_links` variable on the | |||||
[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. | |||||
If you want to change the anchor template, it can easily be overwritten by | |||||
creating a `anchor-link.html` file in the `templates` directory. | |||||
## Internal links | |||||
Linking to other pages and their headers is so common that Gutenberg adds a | |||||
special syntax to Markdown links to handle them: start the link with `./` and point to the `.md` file you want | |||||
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)`. | |||||
You can still link to a header directly: `[my link](./pages/about.md#example)` would work as expected, granted | |||||
the `example` id exists on the header. |
@@ -0,0 +1,48 @@ | |||||
+++ | |||||
title = "Overview" | |||||
weight = 10 | |||||
+++ | |||||
Gutenberg uses the folder structure to determine the site structure. | |||||
Each folder in the `content` directory represents a [section](./documentation/content/section.md) | |||||
that contains [pages](./documentation/content/page.md) : your `.md` files. | |||||
```bash | |||||
. | |||||
└── content | |||||
├── content | |||||
│  └── something.md // -> https://mywebsite.com/content/something/ | |||||
├── blog | |||||
│  ├── cli-usage.md // -> https://mywebsite.com/blog/cli-usage/ | |||||
│  ├── configuration.md // -> https://mywebsite.com/blog/configuration/ | |||||
│  ├── directory-structure.md // -> https://mywebsite.com/blog/directory-structure/ | |||||
│  ├── _index.md // -> https://mywebsite.com/blog/ | |||||
│  └── installation.md // -> https://mywebsite.com/blog/installation/ | |||||
└── landing | |||||
└── _index.md // -> https://mywebsite.com/landing/ | |||||
``` | |||||
Obviously, each page path (the part after the `base_url`, for example `blog/`) can be customised by setting the wanted value | |||||
in the [page front-matter](./documentation/content/page.md#front-matter). | |||||
You might have noticed a file named `_index.md` in the example above. | |||||
This file will be used for 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. | |||||
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` | |||||
and `installation.md`. | |||||
While not shown in the example, sections can be nested indefinitely. | |||||
The `content` directory is not limited to markup files though: it's natural to want to co-locate a page and some related | |||||
assets. Gutenberg supports that pattern out of the box: create a folder, add a `index.md` file and as many non-markdown as you want. | |||||
Those assets will be copied in the same folder when building so you can just use a relative path to access them. | |||||
```bash | |||||
└── with-assets | |||||
├── index.md | |||||
└── yavascript.js | |||||
``` |
@@ -0,0 +1,71 @@ | |||||
+++ | |||||
title = "Page" | |||||
weight = 30 | |||||
+++ | |||||
A page is any file ending with `.md` in the `content` directory, except files | |||||
named `_index/md`. | |||||
## Front-matter | |||||
The front-matter is a set of metadata embedded in a file. In Gutenberg, | |||||
it is at the beginning of the file, surrounded by `+++` and uses TOML. | |||||
None of the front-matter variables are mandatory. However the opening and closing `+++` are required even if there are | |||||
no variables in it. | |||||
Here is an example page with all the variables available: | |||||
```md | |||||
+++ | |||||
title = "" | |||||
description = "" | |||||
# The date of the post. | |||||
# 2 formats are allowed: YYYY-MM-DD (2012-10-02) and RFC3339 (2002-10-02T15:00:00Z) | |||||
date = "" | |||||
# A draft page will not be present in prev/next pagination | |||||
draft = false | |||||
# If filled, it will use that slug instead of the filename to make up the URL | |||||
# It will still use the section path though | |||||
slug = "" | |||||
# The URL the content will appear at | |||||
# If set, it cannot be an empty string and will override both `slug` and the filename | |||||
# and the sections' path won't be used | |||||
url = "" | |||||
# An array of strings allowing you to group pages with them | |||||
tags = [] | |||||
# An overarching category name for that page, allowing you to group pages with it | |||||
category = "" | |||||
# The order as defined in the Section page | |||||
order = 0 | |||||
# The weight as defined in the Section page | |||||
weight = 0 | |||||
# 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. | |||||
aliases = [] | |||||
# Template to use to render this page | |||||
template = "page.html" | |||||
# Your own data | |||||
[extra] | |||||
+++ | |||||
Some content | |||||
``` | |||||
## Summary | |||||
You can ask Gutenberg to create a summary if you only want to show the first | |||||
paragraph of each page in a list for example. | |||||
To do so, add `<!-- more -->` in your content at the point where you want the | |||||
summary to end and the content up to that point will be also available separately | |||||
in the template. |
@@ -0,0 +1,88 @@ | |||||
+++ | |||||
title = "Section" | |||||
weight = 20 | |||||
+++ | |||||
A section is automatically created implicitly when a folder is found | |||||
in the `content` section. | |||||
You can add `_index.md` file to a folder to augment a section and give it | |||||
some metadata and/or content. | |||||
## Front-matter | |||||
The front-matter is a set of metadata embedded in a file. In Gutenberg, | |||||
it is at the beginning of the file, surrounded by `+++` and uses TOML. | |||||
As the file itself is optional, none of the front-matter variables are | |||||
mandatory. However the opening and closing `+++` are required even if there are | |||||
no variables in it. | |||||
Here is an example `_index.md` with all the variables available: | |||||
```md | |||||
+++ | |||||
title = "" | |||||
description = "" | |||||
# Whether to sort by "date", "order", "weight" or "none". More on that below | |||||
sort_by = "none" | |||||
# Used by the parent section to order its subsections. | |||||
# Higher values means it will be at the end. | |||||
weight = 0 | |||||
# Template to use to render this section page | |||||
template = "section.html" | |||||
# How many pages to be displayed per paginated page. | |||||
# No pagination will happen if this isn't set or if the value is 0 | |||||
paginate_by = 0 | |||||
# If set, will be the path used by paginated page and the page number will be appended after it. | |||||
# For example the default would be page/1 | |||||
paginate_by = "page" | |||||
# Whether to insert a link for each header like the ones you can see in this site if you hover one | |||||
# The default template can be overridden by creating a `anchor-link.html` in the `templates` directory | |||||
# Options are "left", "right" and "none" | |||||
insert_anchor_links = "none" | |||||
# Whether to render that section or not. | |||||
# Useful when the section is only there to organize things but is not meant | |||||
# to be used directly | |||||
render = true | |||||
# Whether to redirect when landing on that section. Defaults to `None`. | |||||
# Useful for the same reason as `render` but when you don't want a 404 when | |||||
# landing on the root section page | |||||
redirect_to = "" | |||||
# Your own data | |||||
[extra] | |||||
+++ | |||||
Some content | |||||
``` | |||||
Keep in mind that the variables only apply to the direct pages, not to the subsections' pages. This means | |||||
you can only paginate the pages directly in the section folder for example. | |||||
## Sorting | |||||
Sections' pages can be sorted three different ways, not counting the unsorted default. | |||||
Sorting is enabled by setting the `sort_by` front-matter variable. | |||||
Any page that cannot be sorted, for example if missing the date variable while sorting by `date`, will be ignored and | |||||
won't be rendered. The terminal will warn you if this is happening. | |||||
### `date` | |||||
This will sort all pages by their `date` field, from the most recent to the oldest. | |||||
### `weight` | |||||
This will be sort all pages by their `weight` field. Heavier weights fall at the bottom: 5 would be before 10. | |||||
### `order` | |||||
This will be sort all pages by their `order` field. Order is the opposite of weight, think of it as enumerating | |||||
the content: this is my first post, my second, etc. A page with `order: 5` will appear after a page with `order: 10` in the sorted list. |
@@ -0,0 +1,166 @@ | |||||
+++ | |||||
title = "Shortcodes" | |||||
weight = 40 | |||||
+++ | |||||
While Markdown is good at writing, it isn't great when you need write inline | |||||
HTML to add some styling for example. | |||||
To solve this, Gutenberg borrows the concept of [shortcodes](https://codex.wordpress.org/Shortcode_API) | |||||
from WordPress. | |||||
In our case, the shortcode corresponds to a template that is defined in the `templates/shortcodes` directory or a built-in one. | |||||
## Writing a shortcode | |||||
Let's write a shortcode to embed YouTube videos as an example. | |||||
In a file called `youtube.html` in the `templates/shortcodes` directory, paste the | |||||
following: | |||||
```jinja2 | |||||
<div {% if class %}class="{{class}}"{% endif %}> | |||||
<iframe | |||||
src="https://www.youtube.com/embed/{{id}}{% if autoplay %}?autoplay=1{% endif %}" | |||||
webkitallowfullscreen | |||||
mozallowfullscreen | |||||
allowfullscreen> | |||||
</iframe> | |||||
</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 | |||||
are in a `if` statement, we can assume they are optional. | |||||
That's it, Gutenberg will now recognise this template as a shortcode named `youtube` (the filename minus the `.html` extension). | |||||
## Using shortcodes | |||||
There are two kinds of shortcodes: ones that do no take a body like the YouTube example above and ones that do, a quote for example. | |||||
In both cases, their arguments must be named and they will all be passed to the template. | |||||
Do note that shortcodes in code blocks will be ignored. | |||||
### Shortcodes without body | |||||
Those look like rendering a variable in Tera. | |||||
On a new line, call the shortcode as if it was a function in a variable block. All the examples below are valid | |||||
calls of the YouTube shortcode. | |||||
```md | |||||
{{ youtube(id="w7Ft2ymGmfc") }} | |||||
{{ youtube(id="w7Ft2ymGmfc", autoplay=true) }} | |||||
{{ youtube(id="w7Ft2ymGmfc", autoplay=true, class="youtube") }} | |||||
``` | |||||
### Shortcodes with body | |||||
Those look like a block in Tera. | |||||
For example, let's imagine we have the following shortcode `quote.html` template: | |||||
```jinja2 | |||||
<blockquote> | |||||
{{ body }} <br> | |||||
-- {{ author}} | |||||
</blockquote> | |||||
``` | |||||
We could use it in our markup file like so: | |||||
```md | |||||
{% quote(author="Vincent") %} | |||||
A quote | |||||
{% end %} | |||||
``` | |||||
The `body` variable used in the shortcode template will be implicitly passed down to the rendering | |||||
context automatically. | |||||
## Built-in shortcodes | |||||
Gutenberg comes with a few built-in shortcodes. If you want to override a default shortcode template, | |||||
simply place a `{shortcode_name}.html` file in the `templates/shortcodes` directory and Gutenberg will | |||||
use that instead. | |||||
### YouTube | |||||
Embed a responsive player for a YouTube video. | |||||
The arguments are: | |||||
- `id`: the video id (mandatory) | |||||
- `class`: a class to add the `div` surrounding the iframe | |||||
- `autoplay`: whether to autoplay the video on load | |||||
Usage example: | |||||
```md | |||||
{{ youtube(id="w7Ft2ymGmfc") }} | |||||
{{ youtube(id="w7Ft2ymGmfc", autoplay=true) }} | |||||
{{ youtube(id="w7Ft2ymGmfc", autoplay=true, class="youtube") }} | |||||
``` | |||||
Result example: | |||||
{{ youtube(id="w7Ft2ymGmfc") }} | |||||
### Vimeo | |||||
Embed a player for a Vimeo video. | |||||
The arguments are: | |||||
- `id`: the video id (mandatory) | |||||
- `class`: a class to add the `div` surrounding the iframe | |||||
Usage example: | |||||
```md | |||||
{{ vimeo(id="124313553") }} | |||||
{{ vimeo(id="124313553", class="vimeo") }} | |||||
``` | |||||
Result example: | |||||
{{ vimeo(id="124313553") }} | |||||
### Streamable | |||||
Embed a player for a Streamable video. | |||||
The arguments are: | |||||
- `id`: the video id (mandatory) | |||||
- `class`: a class to add the `div` surrounding the iframe | |||||
Usage example: | |||||
```md | |||||
{{ streamable(id="2zt0") }} | |||||
{{ streamable(id="2zt0", class="streamble") }} | |||||
``` | |||||
Result example: | |||||
{{ streamable(id="2zt0") }} | |||||
### Gist | |||||
Embed a [Github gist](). | |||||
The arguments are: | |||||
- `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 | |||||
- `class`: a class to add the `div` surrounding the iframe | |||||
Usage example: | |||||
```md | |||||
{{ gist(id="https://gist.github.com/Keats/e5fb6aad409f28721c0ba14161644c57") }} | |||||
{{ gist(id="https://gist.github.com/Keats/e5fb6aad409f28721c0ba14161644c57", class="gist") }} | |||||
``` | |||||
Result example: | |||||
{{ gist(url="https://gist.github.com/Keats/e5fb6aad409f28721c0ba14161644c57") }} |
@@ -0,0 +1,111 @@ | |||||
+++ | |||||
title = "Syntax Highlighting" | |||||
weight = 80 | |||||
+++ | |||||
Gutenberg comes with built-in syntax highlighting but you first | |||||
need to enable it in the [configuration](./documentation/getting-started/configuration.md). | |||||
Once this is done, Gutenberg will automatically highlight all code blocks | |||||
in your content. A code block in Markdown looks like the following: | |||||
````md | |||||
```rust | |||||
let highlight = true; | |||||
``` | |||||
```` | |||||
You can replace the `rust` by the language you want to highlight. | |||||
Here is a full list of the supported languages and the short name you can use: | |||||
``` | |||||
- Plain Text -> ["txt"] | |||||
- Assembly x86 (NASM) -> ["asm", "inc", "nasm"] | |||||
- Elm -> ["elm"] | |||||
- Handlebars -> ["handlebars", "handlebars.html", "hbr", "hbrs", "hbs", "hdbs", "hjs", "mu", "mustache", "rac", "stache", "template", "tmpl"] | |||||
- Jinja2 -> ["j2", "jinja2"] | |||||
- Julia -> ["jl"] | |||||
- LESS -> ["less"] | |||||
- ASP -> ["asa"] | |||||
- HTML (ASP) -> ["asp"] | |||||
- ActionScript -> ["as"] | |||||
- AppleScript -> ["applescript", "script editor"] | |||||
- Batch File -> ["bat", "cmd"] | |||||
- NAnt Build File -> ["build"] | |||||
- C# -> ["cs", "csx"] | |||||
- C++ -> ["cpp", "cc", "cp", "cxx", "c++", "C", "h", "hh", "hpp", "hxx", "h++", "inl", "ipp"] | |||||
- C -> ["c", "h"] | |||||
- CSS -> ["css", "css.erb", "css.liquid"] | |||||
- Clojure -> ["clj"] | |||||
- D -> ["d", "di"] | |||||
- Diff -> ["diff", "patch"] | |||||
- Erlang -> ["erl", "hrl", "Emakefile", "emakefile"] | |||||
- HTML (Erlang) -> ["yaws"] | |||||
- Go -> ["go"] | |||||
- Graphviz (DOT) -> ["dot", "DOT"] | |||||
- Groovy -> ["groovy", "gvy", "gradle"] | |||||
- HTML -> ["html", "htm", "shtml", "xhtml", "inc", "tmpl", "tpl"] | |||||
- Haskell -> ["hs"] | |||||
- Literate Haskell -> ["lhs"] | |||||
- Java Server Page (JSP) -> ["jsp"] | |||||
- Java -> ["java", "bsh"] | |||||
- JavaDoc -> [] | |||||
- 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"] | |||||
- JavaScript -> ["js", "htc"] | |||||
- Regular Expressions (Javascript) -> [] | |||||
- BibTeX -> ["bib"] | |||||
- LaTeX Log -> [] | |||||
- LaTeX -> ["tex", "ltx"] | |||||
- TeX -> ["sty", "cls"] | |||||
- Lisp -> ["lisp", "cl", "l", "mud", "el", "scm", "ss", "lsp", "fasl"] | |||||
- Lua -> ["lua"] | |||||
- Make Output -> [] | |||||
- Makefile -> ["make", "GNUmakefile", "makefile", "Makefile", "OCamlMakefile", "mak", "mk"] | |||||
- Markdown -> ["md", "mdown", "markdown", "markdn"] | |||||
- MultiMarkdown -> [] | |||||
- MATLAB -> ["matlab"] | |||||
- OCaml -> ["ml", "mli"] | |||||
- OCamllex -> ["mll"] | |||||
- OCamlyacc -> ["mly"] | |||||
- camlp4 -> [] | |||||
- Objective-C++ -> ["mm", "M", "h"] | |||||
- Objective-C -> ["m", "h"] | |||||
- PHP Source -> [] | |||||
- PHP -> ["php", "php3", "php4", "php5", "php7", "phps", "phpt", "phtml"] | |||||
- Pascal -> ["pas", "p", "dpr"] | |||||
- Perl -> ["pl", "pm", "pod", "t", "PL"] | |||||
- Python -> ["py", "py3", "pyw", "pyi", "rpy", "cpy", "SConstruct", "Sconstruct", "sconstruct", "SConscript", "gyp", "gypi", "Snakefile", "wscript"] | |||||
- Regular Expressions (Python) -> [] | |||||
- R Console -> [] | |||||
- R -> ["R", "r", "s", "S", "Rprofile"] | |||||
- 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"] | |||||
- Cargo Build Results -> [] | |||||
- Rust -> ["rs"] | |||||
- SQL -> ["sql", "ddl", "dml"] | |||||
- Scala -> ["scala", "sbt"] | |||||
- Shell Script (Bash) -> ["sh", "bash", "zsh", ".bash_aliases", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".profile", ".textmate_init"] | |||||
- HTML (Tcl) -> ["adp"] | |||||
- Tcl -> ["tcl"] | |||||
- Textile -> ["textile"] | |||||
- XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"] | |||||
- YAML -> ["yaml", "yml", "sublime-syntax"] | |||||
- Generic Config -> ["cfg", "conf", "config", "ini", "pro"] | |||||
- Linker Script -> ["ld"] | |||||
- TOML -> ["toml", "tml"] | |||||
- TypeScript -> ["ts"] | |||||
- TypeScriptReact -> ["tsx"] | |||||
- VimL -> ["vim"] | |||||
``` | |||||
If you want to highlight a language not on that list, please open an issue or a pull request on the [Gutenberg repo](https://github.com/Keats/gutenberg). |
@@ -0,0 +1,33 @@ | |||||
+++ | |||||
title = "Table of Contents" | |||||
weight = 60 | |||||
+++ | |||||
Each page/section will automatically generate a table of content for itself based on the headers present. | |||||
TODO: add link for template variables | |||||
It is available in the template through `section.toc` and `page.toc`. You can view the [template variables]() | |||||
documentation for information on its structure. | |||||
Here is an example of using that field to render a 2-level table of content: | |||||
```jinja2 | |||||
<ul> | |||||
{% for h1 in page.toc %} | |||||
<li> | |||||
<a href="{{h1.permalink | safe}}">{{ h1.title }}</a> | |||||
{% if h1.children %} | |||||
<ul> | |||||
{% for h2 in h1.children %} | |||||
<li> | |||||
<a href="{{h2.permalink | safe}}">{{ h2.title }}</a> | |||||
</li> | |||||
{% endfor %} | |||||
</ul> | |||||
{% endif %} | |||||
</li> | |||||
{% endfor %} | |||||
</ul> | |||||
``` | |||||
While headers are neatly ordered in that example, it will work just as well with disjoint headers. |
@@ -1,4 +1,7 @@ | |||||
+++ | +++ | ||||
title = "Getting Started" | title = "Getting Started" | ||||
sort_by = "order" | |||||
weight = 1 | |||||
sort_by = "weight" | |||||
redirect_to = "documentation/getting-started/installation" | |||||
insert_anchor_links = "left" | |||||
+++ | +++ |
@@ -1,6 +1,43 @@ | |||||
+++ | +++ | ||||
title = "CLI usage" | title = "CLI usage" | ||||
order = 2 | |||||
weight = 2 | |||||
+++ | +++ | ||||
Hey | |||||
Gutenberg only has 3 commands: init, build and serve. | |||||
You can view the help of the whole program by running `gutenberg --help` and | |||||
the command help by running `gutenberg <cmd> --help`. | |||||
## init | |||||
Creates the directory structure used by Gutenberg at the given directory. | |||||
```bash | |||||
$ gutenberg init <my_site> | |||||
``` | |||||
will create a new folder named `my_site` and the files/folders needed by | |||||
Gutenberg. | |||||
## build | |||||
This will build the whole site in the `public` directory. | |||||
```bash | |||||
$ gutenberg build | |||||
``` | |||||
## serve | |||||
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`). | |||||
```bash | |||||
$ gutenberg serve | |||||
$ gutenberg serve --port 2000 | |||||
$ gutenberg serve --interface 0.0.0.0 | |||||
$ gutenberg serve --interface 0.0.0.0 --port 2000 | |||||
``` | |||||
The serve command will watch all your content and will provide live reload, without | |||||
hard refresh if possible. |
@@ -1,6 +1,71 @@ | |||||
+++ | +++ | ||||
title = "Configuration" | title = "Configuration" | ||||
order = 4 | |||||
weight = 4 | |||||
+++ | +++ | ||||
Hey | |||||
The default configuration will be enough to get Gutenberg 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. | |||||
To change the config, 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) | |||||
to learn about it. | |||||
Only one variable - `base_url` - is mandatory, everything else is optional. You can find all variables | |||||
used by Gutenberg config as well as their default values below: | |||||
```toml | |||||
# Base URL of the site, the only required config argument | |||||
base_url = "mywebsite.com" | |||||
# Used in RSS by default | |||||
title = "" | |||||
description = "" | |||||
language_code = "en" | |||||
# Theme name to use | |||||
theme = "" | |||||
# Highlight all code blocks found | |||||
highlight_code = false | |||||
# Which theme to use for the code highlighting. See below for list of accepted values | |||||
highlight_theme = "base16-ocean-dark" | |||||
# Whether to generate a RSS feed automatically | |||||
generate_rss = false | |||||
# The number of articles to include in the RSS feed | |||||
rss_limit = 20 | |||||
# Whether to generate a tags page and individual tag pages for pages with tags | |||||
generate_tags_pages = false | |||||
# Whether to generate a categories page and individual category pages for pages with a category | |||||
generate_categories_pages = false | |||||
# Whether to compile the Sass files found in the `sass` directory | |||||
compile_sass = false | |||||
# You can put any kind of data in there and it will be accessible in all templates | |||||
[extra] | |||||
``` | |||||
## Syntax highlighting | |||||
Gutenberg currently has the following highlight themes available: | |||||
- base16-ocean-dark | |||||
- base16-ocean-light | |||||
- gruvbox-dark | |||||
- gruvbox-light | |||||
- inspired-github | |||||
- kronuz | |||||
- material-dark | |||||
- material-light | |||||
- monokai | |||||
- solarized-dark | |||||
- solarized-light | |||||
Gutenberg 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 [Gutenberg repo](https://github.com/Keats/gutenberg). |
@@ -1,6 +1,47 @@ | |||||
+++ | +++ | ||||
title = "Directory structure" | title = "Directory structure" | ||||
order = 3 | |||||
weight = 3 | |||||
+++ | +++ | ||||
Hey | |||||
After running `gutenberg init`, you should see the following structure in your folder: | |||||
```bash | |||||
. | |||||
├── config.toml | |||||
├── content | |||||
├── sass | |||||
├── static | |||||
├── templates | |||||
└── themes | |||||
5 directories, 1 file | |||||
``` | |||||
Here's a high level overview of each of these folders and `config.toml`. | |||||
## `config.toml` | |||||
A mandatory configuration file of Gutenberg in TOML format. | |||||
It is explained in details in the [Configuration page](./documentation/getting-started/configuration.md). | |||||
## `content` | |||||
Where all your markup content lies: this will most likely be mostly `.md` files. | |||||
Each folder in the `content` directory represents a [section](./documentation/content/section.md) | |||||
that contains [pages](./documentation/content/page.md) : your `.md` files. | |||||
To learn more, read [the content overview](./documentation/content/overview.md). | |||||
## `sass` | |||||
Contains the [Sass](http://sass-lang.com) files to be compiled. Non-Sass files will be ignored. | |||||
## `static` | |||||
Contains any kind of files. All the files/folders in the `static` folder will be copied as-is in the output directory. | |||||
## `templates` | |||||
Contains all the [Tera](tera.netlify.com) templates that will be used to render this site. | |||||
Have a look at the [Templates](./documentation/templates/_index.md) to learn more on the default templates | |||||
and the variables available. | |||||
## `themes` | |||||
Contains themes that can be used for that site. If you are not planning to use themes, you can safely ignore | |||||
this folder and let it be. If you want to learn about themes, head to the [themes documentation](./documentation/themes/_index.md). |
@@ -1,6 +1,31 @@ | |||||
+++ | +++ | ||||
title = "Installation" | title = "Installation" | ||||
order = 1 | |||||
weight = 1 | |||||
+++ | +++ | ||||
Hey | |||||
Gutenberg provides pre-built binaries for Mac OS, Linux and Windows on the | |||||
[Github release page](https://github.com/Keats/gutenberg/releases). | |||||
## Using brew on Mac OS | |||||
TODO: it's not on brew right now | |||||
## Windows | |||||
TODO: i have no clue whatsoever about packages in Windows | |||||
## Archlinux | |||||
TODO: add a `gutenberg-bin` in AUR and explain how to install it | |||||
## From source | |||||
To build it from source, you will need to have Git, [Rust and Cargo](https://www.rust-lang.org/en-US/) | |||||
installed. | |||||
From a terminal, you can now run the following command: | |||||
```bash | |||||
$ cargo build --release | |||||
``` | |||||
The binary will be available in the `target/release` folder. |
@@ -0,0 +1,6 @@ | |||||
+++ | |||||
title = "Templates" | |||||
weight = 3 | |||||
sort_by = "weight" | |||||
insert_anchor_links = "left" | |||||
+++ |
@@ -0,0 +1,73 @@ | |||||
+++ | |||||
title = "Overview" | |||||
weight = 10 | |||||
+++ | |||||
Gutenberg uses the [Tera](tera.netlify.com) template engine. | |||||
This documentation will only touch how templates work in Gutenberg, please read | |||||
the [Tera template documentation](https://tera.netlify.com/docs/templates/) if you want | |||||
to know how write them. If you are familiar with Jinja2, Liquid or Twig, this should be | |||||
a breeze. | |||||
All templates live in the `templates` directory and built-in or themes templates can | |||||
be overriden by creating a template with same name in the correct path. For example, | |||||
you can override the RSS template by creating a `templates/rss.xml` file. | |||||
If you are not sure what is available in a template, you can just stick `{{ __tera_context }}` in it | |||||
to print the whole context. | |||||
A few variables are available on all templates except for RSS/Sitemap: | |||||
- `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 | |||||
- `current_url`: the full URL for that page | |||||
## Built-in filters | |||||
Gutenberg adds a few filters, in addition of the ones already present in Tera. | |||||
### markdown | |||||
Converts the given variable to HTML using Markdown. This doesn't apply any of the | |||||
features that Gutenberg adds to Markdown: internal links, shortcodes etc won't work. | |||||
### base64_encode | |||||
Encode the variable to base64. | |||||
### base64_decode | |||||
Decode the variable from base64. | |||||
## Built-in global functions | |||||
Gutenberg adds a few global functions to Tera in order to make it easier to develop complex sites. | |||||
### `get_page` | |||||
Takes a path to a `.md` file and returns the associated page | |||||
```jinja2 | |||||
{% set page = get_page(path="blog/page2.md") %} | |||||
``` | |||||
### `get_section` | |||||
Takes a path to a `_index.md` file and returns the associated section | |||||
```jinja2 | |||||
{% set section = get_page(path="blog/_index.md") %} | |||||
``` | |||||
###` get_url` | |||||
Gets the permalink for the given path. | |||||
If the path starts with `./`, it will be understood as an internal | |||||
link like the ones used in markdown. | |||||
```jinja2 | |||||
{% set url = get_url(path="./blog/_index.md") %} | |||||
``` | |||||
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`: | |||||
```jinja2 | |||||
{{ get_url(path="css/app.css") }} | |||||
``` | |||||
In the case of non-internal links, you can also add a cachebust of the format `?t=1290192` at the end of a URL | |||||
by passing `cachebust=true` to the `get_url` function. |
@@ -0,0 +1,88 @@ | |||||
+++ | |||||
title = "Index, Sections and Pages" | |||||
weight = 20 | |||||
+++ | |||||
First off, it is important to know that in Gutenberg the index | |||||
page is actually a section like any other: you can add metadata | |||||
and content by adding `_index.md` at the root of the `content` folder. | |||||
Pages and sections are actually very similar. | |||||
## Page variables | |||||
By default, Gutenberg will try to load `templates/page.html`. If there isn't | |||||
one, it will render the built-in template: a blank page. | |||||
Whichever template you decide to render, you will get a `page` variable in your template | |||||
with the following fields: | |||||
```ts | |||||
content: String; | |||||
title: String?; | |||||
description: String?; | |||||
date: String?; | |||||
slug: String; | |||||
path: String; | |||||
permalink: String; | |||||
summary: String?; | |||||
tags: Array<String>; | |||||
category: String?; | |||||
extra: HashMap<String, Any>; | |||||
// Naive word count, will not work for languages without whitespace | |||||
word_count: Number; | |||||
// Based on https://help.medium.com/hc/en-us/articles/214991667-Read-time | |||||
reading_time: Number; | |||||
// `previous` and `next` are only filled if the content can be sorted | |||||
previous: Page?; | |||||
next: Page?; | |||||
// See the Table of contents section below for more details | |||||
toc: Array<Header>; | |||||
``` | |||||
## Section variables | |||||
By default, Gutenberg will try to load `templates/section.html`. If there isn't | |||||
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 | |||||
with the following fields: | |||||
```ts | |||||
content: String; | |||||
title: String?; | |||||
description: String?; | |||||
date: String?; | |||||
slug: String; | |||||
path: String; | |||||
permalink: String; | |||||
extra: HashMap<String, Any>; | |||||
// Pages directly in this section, sorted if asked | |||||
pages: Array<Pages>; | |||||
// Direct subsections to this section, sorted by subsections weight | |||||
subsections: Array<Section>; | |||||
// Naive word count, will not work for languages without whitespace | |||||
word_count: Number; | |||||
// Based on https://help.medium.com/hc/en-us/articles/214991667-Read-time | |||||
reading_time: Number; | |||||
// See the Table of contents section below for more details | |||||
toc: Array<Header>; | |||||
``` | |||||
## Table of contents | |||||
Both page and section have a `toc` field which corresponds to an array of `Header`. | |||||
A `Header` has the following fields: | |||||
```ts | |||||
// The hX level | |||||
level: 1 | 2 | 3 | 4 | 5 | 6; | |||||
// The generated slug id | |||||
id: String; | |||||
// The text of the header | |||||
title: String; | |||||
// A link pointing directly to the header, using the inserted anchor | |||||
permalink: String; | |||||
// All lower level headers below this header | |||||
children: Array<Header>; | |||||
``` |
@@ -0,0 +1,28 @@ | |||||
+++ | |||||
title = "Pagination" | |||||
weight = 30 | |||||
+++ | |||||
A paginated section gets the same `section` variable as a normal | |||||
[section page](./documentation/templates/pages-sections.md#section-variables) and will use | |||||
the template mentioned in the section front-matter or the default one. | |||||
In addition, a paginated section gets a `paginator` one, which has a `Pager` type: | |||||
```ts | |||||
// How many items per page | |||||
paginate_by: Number; | |||||
// Permalink to the first page | |||||
first: String; | |||||
// Permalink to the last page | |||||
last: String; | |||||
// Permalink to the previous page, if there is one | |||||
previous: String?; | |||||
// Permalink to the next page, if there is one | |||||
next: String?; | |||||
// All pages for the current page | |||||
pages: Array<Page>; | |||||
// All pagers for this section, but with their `pages` attribute set to an empty array | |||||
pagers: Array<Pagers>; | |||||
// Which page are we on | |||||
current_index: Number; | |||||
``` |
@@ -0,0 +1,14 @@ | |||||
+++ | |||||
title = "Robots.txt" | |||||
weight = 70 | |||||
+++ | |||||
Gutenberg will look for a `robots.txt` file in the `templates` directory or | |||||
use the built-in one. | |||||
Robots.txt is the simplest of all templates: it doesn't take any variables | |||||
and the default is what most site want. | |||||
```jinja2 | |||||
User-agent: * | |||||
``` |
@@ -0,0 +1,16 @@ | |||||
+++ | |||||
title = "RSS" | |||||
weight = 50 | |||||
+++ | |||||
Gutenberg will look for a `rss.xml` file in the `templates` directory or | |||||
use the built-in one. Currently it is only possible to have one RSS feed for the whole | |||||
site, you cannot create a RSS feed per section or taxonomy. | |||||
**Only pages with a date and that are not draft will be available.** | |||||
The RSS template gets two variables in addition of the config: | |||||
- `last_build_date`: the date of the latest post | |||||
- `pages`: see [the page variables](./documentation/templates/pages-sections.md#page-variables) for | |||||
a detailed description of this variable. |
@@ -0,0 +1,23 @@ | |||||
+++ | |||||
title = "Sitemap" | |||||
weight = 60 | |||||
+++ | |||||
Gutenberg will look for a `sitemap.xml` file in the `templates` directory or | |||||
use the built-in one. | |||||
The sitemap template gets four variables in addition of the config: | |||||
- `pages`: all pages of the site | |||||
- `sections`: all sections of the site, including an index section | |||||
- `tags`: links the tags page and individual tag page, empty if no tags | |||||
- `categories`: links the categories page and individual category page, empty if no categories | |||||
As the sitemap only requires a link and an optional date for the `lastmod` field, | |||||
all the variables above are arrays of `SitemapEntry` with the following type: | |||||
```ts | |||||
permalink: String; | |||||
date: String?; | |||||
``` |
@@ -0,0 +1,30 @@ | |||||
+++ | |||||
title = "Tags & Categories" | |||||
weight = 40 | |||||
+++ | |||||
Tags and categories actually get the same data but with different variable names. | |||||
The default templates for those pages are the following: | |||||
- `tags.html`: list of tags, gets variable `tags` | |||||
- `tag.html`: individual tag, gets variable `tag` | |||||
- `categories.html`: list of categories, gets variable `categories` | |||||
- `category.html`: individual category, gets variable `category` | |||||
You can override any of those templates by putting one with the same name in the `templates` directory. | |||||
`tags` and `categories` both are an array of `TaxonomyItem` sorted alphabetically, while `tag` and `category` | |||||
are a `TaxonomyItem`. | |||||
A `TaxonomyItem` has the following fields: | |||||
```ts | |||||
name: String; | |||||
slug: String; | |||||
// Permalink to the generated page | |||||
permalink: String; | |||||
pages: Array<Page>; | |||||
``` | |||||
Currently, there is no way to define different taxonomy templates per section, change | |||||
the path used for them or paginate them. | |||||
@@ -0,0 +1,5 @@ | |||||
+++ | |||||
title = "Themes" | |||||
weight = 4 | |||||
sort_by = "weight" | |||||
+++ |
@@ -0,0 +1,53 @@ | |||||
+++ | |||||
title = "Creating a theme" | |||||
weight = 30 | |||||
+++ | |||||
Creating is exactly like creating a normal site with Gutenberg, except you | |||||
will want to use many [Tera blocks](https://tera.netlify.com/docs/templates/#inheritance) to | |||||
allow users to easily modify it. | |||||
A theme also need to have a `theme.toml` configuration file with the | |||||
following fields, here's the one from a [real template](https://github.com/Keats/hyde): | |||||
```toml | |||||
name = "hyde" | |||||
description = "A classic blog theme" | |||||
license = "MIT" | |||||
homepage = "https://github.com/Keats/gutenberg-hyde" | |||||
# The minimum version of Gutenberg required | |||||
min_version = "0.1" | |||||
# Any variable there can be overriden in the end user `config.toml` | |||||
# You don't need to prefix variables by the theme name but as this will | |||||
# be merged with user data, some kind of prefix or nesting is preferable | |||||
# Use snake_casing to be consistent with the rest of Gutenberg | |||||
[extra] | |||||
hyde_sticky = true | |||||
hyde_reverse = false | |||||
hyde_theme = "" | |||||
hyde_links = [ | |||||
{url = "https://google.com", name = "Google.com"}, | |||||
{url = "https://google.fr", name = "Google.fr"}, | |||||
] | |||||
# The theme author info: you! | |||||
[author] | |||||
name = "Vincent Prouillet" | |||||
homepage = "https://vincent.is" | |||||
# If this is porting a theme from another static site engine, provide | |||||
# the info of the original author here | |||||
[original] | |||||
author = "mdo" | |||||
homepage = "http://markdotto.com/" | |||||
repo = "https://www.github.com/mdo/hyde" | |||||
``` | |||||
A theme will also need three directories to work: | |||||
- `static`: any static files used in this theme | |||||
- `templates`: all templates used in this theme | |||||
- `sass`: Sass stylesheets for this theme, can be empty | |||||
A simple theme you can use as example is [Hyde](https://github.com/Keats/hyde). |
@@ -0,0 +1,52 @@ | |||||
+++ | |||||
title = "Installing & using themes" | |||||
weight = 20 | |||||
+++ | |||||
## Installing a theme | |||||
The easiest way to install to theme is to clone its repository in the `themes` | |||||
directory. | |||||
```bash | |||||
$ cd themes | |||||
$ git clone THEME_REPO_URL | |||||
``` | |||||
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 | |||||
them in a folder. | |||||
## Using a theme | |||||
Now that you have the theme in your `themes` directory, you only need to tell | |||||
Gutenberg to use it to get started by setting the `theme` variable of the | |||||
[configuration file](./documentation/getting-started/configuration.md). The theme | |||||
name has to be name of the directory you cloned the theme in. | |||||
For example, if you cloned a theme in `templates/simple-blog`, the theme name to use | |||||
in the configuration file is `simple-blog`. | |||||
## 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` | |||||
directory. Here are a few examples of that, assuming the theme name is `simple-blog`: | |||||
```plain | |||||
templates/pages/post.html -> will override themes/simple-blog/pages/post.html | |||||
templates/macros.html -> will override themes/simple-blog/macros.html | |||||
static/js/site.js -> will override themes/simple-blog/static/js/site.jss | |||||
``` | |||||
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). | |||||
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: | |||||
```toml | |||||
[extra] | |||||
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 | |||||
files. |
@@ -0,0 +1,10 @@ | |||||
+++ | |||||
title = "Overview" | |||||
weight = 10 | |||||
+++ | |||||
Gutenberg has built-in support for themes in a way that are easy to customise | |||||
but still easy to update if needed. | |||||
All themes can use the full power of Gutenberg, from shortcodes to Sass compilation. | |||||
@@ -0,0 +1,8 @@ | |||||
+++ | |||||
title = "List of themes" | |||||
weight = 40 | |||||
+++ | |||||
The following themes are available for Gutenberg: | |||||
- [Hyde](https://github.com/Keats/gutenberg-hyde) |
@@ -1,3 +1,44 @@ | |||||
.documentation { | .documentation { | ||||
padding: 3rem; | padding: 3rem; | ||||
display: flex; | |||||
&__sidebar { | |||||
margin-right: 2rem; | |||||
ul { | |||||
padding-left: 0; | |||||
list-style: none; | |||||
ul { | |||||
padding-left: 1rem; | |||||
li.active a { | |||||
color: red; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
&__content { | |||||
flex: 1; | |||||
} | |||||
a { | |||||
color: $background; | |||||
padding-bottom: 2px; | |||||
border-bottom: 1px solid $background; | |||||
&:hover { | |||||
text-decoration: none; | |||||
} | |||||
&:visited { | |||||
color: $background; | |||||
} | |||||
} | |||||
iframe { | |||||
width: 100%; | |||||
min-height: 400px; | |||||
} | |||||
} | } |
@@ -8,19 +8,24 @@ | |||||
{% set section = get_section(path="documentation/_index.md") %} | {% set section = get_section(path="documentation/_index.md") %} | ||||
<div class="documentation container"> | <div class="documentation container"> | ||||
<aside class="documentation__sidebar"> | <aside class="documentation__sidebar"> | ||||
<ul> | |||||
{% for subsection in section.subsections %} | {% for subsection in section.subsections %} | ||||
<li> | <li> | ||||
{{ subsection.title }} | {{ subsection.title }} | ||||
<ul> | <ul> | ||||
{% for page in subsection.pages | reverse %} | |||||
<li>{{page.title}}</li> | |||||
{% for page in subsection.pages %} | |||||
<li class="{% if current_path == page.path %}active{% endif %}"> | |||||
<a href="{{page.permalink}}">{{page.title}}</a> | |||||
</li> | |||||
{% endfor %} | {% endfor %} | ||||
</ul> | </ul> | ||||
</li> | </li> | ||||
{% endfor %} | {% endfor %} | ||||
</ul> | |||||
</aside> | </aside> | ||||
<div class="documentation__content"> | <div class="documentation__content"> | ||||
hey | |||||
{% block doc_content %} | |||||
{% endblock doc_content %} | |||||
</div> | </div> | ||||
</div> | </div> | ||||
{% endblock content %} | {% endblock content %} |
@@ -37,7 +37,7 @@ | |||||
<h2>Everything built-in</h2> | <h2>Everything built-in</h2> | ||||
<p> | <p> | ||||
Gutenberg comes with Sass compilation, syntax highlighting and | Gutenberg comes with Sass compilation, syntax highlighting and | ||||
a other features that usually require using additional tools | |||||
other features that usually require using additional tools | |||||
or use JavaScript libraries on your site. | or use JavaScript libraries on your site. | ||||
</p> | </p> | ||||
</div> | </div> | ||||
@@ -0,0 +1,7 @@ | |||||
{% extends "documentation.html" %} | |||||
{% block title %}{{ super() }} - {{ page.title }} {% endblock title %} | |||||
{% block doc_content %} | |||||
<h1>{{page.title}}</h1> | |||||
{{page.content | safe}} | |||||
{% endblock doc_content %} |
@@ -60,7 +60,7 @@ fn find_section_front_matter_changes(current: &SectionFrontMatter, other: &Secti | |||||
if current.paginate_by != other.paginate_by | if current.paginate_by != other.paginate_by | ||||
|| current.paginate_path != other.paginate_path | || current.paginate_path != other.paginate_path | ||||
|| current.insert_anchor != other.insert_anchor { | |||||
|| current.insert_anchor_links != other.insert_anchor_links { | |||||
changes_needed.push(SectionChangesNeeded::RenderWithPages); | changes_needed.push(SectionChangesNeeded::RenderWithPages); | ||||
// Nothing else we can do | // Nothing else we can do | ||||
return changes_needed; | return changes_needed; | ||||
@@ -177,7 +177,6 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
let page = Page::from_file(path, &site.config)?; | let page = Page::from_file(path, &site.config)?; | ||||
match site.add_page(page, true)? { | match site.add_page(page, true)? { | ||||
Some(prev) => { | Some(prev) => { | ||||
site.register_tera_global_fns(); | |||||
// Updating a page | // Updating a page | ||||
let current = site.pages[path].clone(); | let current = site.pages[path].clone(); | ||||
// Front matter didn't change, only content did | // Front matter didn't change, only content did | ||||
@@ -212,6 +211,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
}, | }, | ||||
}; | }; | ||||
} | } | ||||
site.register_tera_global_fns(); | |||||
return Ok(()); | return Ok(()); | ||||
}, | }, | ||||