Merge pull request #124 from Keats/site

Working on site
This commit is contained in:
Vincent Prouillet 2017-10-02 19:14:36 +09:00 committed by GitHub
commit f2a641abea
45 changed files with 1326 additions and 72 deletions

View file

@ -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)

View file

@ -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),

View file

@ -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()
} }

View file

@ -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());

View file

@ -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,
} }

View file

@ -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

View file

@ -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. /// Used by the parent section to order its subsections.
/// Higher values means it ends at the end. /// 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 /// 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` template and CSS will need to be /// The default template can be overridden by creating a `anchor-link.html` in the `templates` directory
/// written if you turn that on. pub insert_anchor_links: Option<InsertAnchor>,
pub insert_anchor: 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() { if f.insert_anchor_links.is_none() {
f.insert_anchor = Some(InsertAnchor::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,
} }
} }

View file

@ -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())?;

View file

@ -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"
+++ +++

View file

@ -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);

View file

@ -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 { pub fn new(name: &str, kind: TaxonomyKind, config: &Config, pages: Vec<Page>) -> TaxonomyItem {
// We shouldn't have any pages without dates there // Taxonomy are almost always used for blogs so we filter by dates
let (sorted_pages, _) = sort_pages(pages, SortBy::Date); // 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), permalink,
pages: sorted_pages, 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(config: &Config, all_pages: &[Page]) -> (Taxonomy, Taxonomy) {
pub fn find_tags_and_categories(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 tags_taxonomy = Taxonomy::new(TaxonomyKind::Tags, config, tags);
let categories_taxonomy = Taxonomy::new(TaxonomyKind::Categories, categories); 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);
}
}

View file

@ -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>

View file

@ -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"

View file

@ -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

View file

@ -0,0 +1,7 @@
+++
title = "Content"
weight = 2
sort_by = "weight"
redirect_to = "documentation/content/overview"
insert_anchor_links = "left"
+++

View file

@ -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.

View file

@ -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
```

View file

@ -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.

View file

@ -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.

View file

@ -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") }}

View file

@ -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).

View file

@ -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.

View file

@ -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"
+++ +++

View file

@ -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.

View file

@ -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).

View file

@ -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).

View file

@ -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.

View file

@ -0,0 +1,6 @@
+++
title = "Templates"
weight = 3
sort_by = "weight"
insert_anchor_links = "left"
+++

View file

@ -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.

View file

@ -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>;
```

View file

@ -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;
```

View file

@ -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: *
```

View file

@ -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.

View file

@ -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?;
```

View file

@ -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.

View file

@ -0,0 +1,5 @@
+++
title = "Themes"
weight = 4
sort_by = "weight"
+++

View file

@ -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).

View file

@ -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.

View file

@ -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.

View file

@ -0,0 +1,8 @@
+++
title = "List of themes"
weight = 40
+++
The following themes are available for Gutenberg:
- [Hyde](https://github.com/Keats/gutenberg-hyde)

View file

@ -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;
}
} }

View file

@ -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 %} {% for page in subsection.pages %}
<li>{{page.title}}</li> <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 %}

View file

@ -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>

7
docs/templates/page.html vendored Normal file
View file

@ -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 %}

View file

@ -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(());
}, },