🔗 ⭐ How HTML pages map to URLs ⤴️

HTML pages are stored inside the top level pages/ directory, and they map to URLs 1-to-1.

index.html is a special page which maps to the root URL. As a result, no other file can be named index.html other than the one for the root URL.

File path URL
pages/index.html /
pages/foo.html /foo/
pages/foo/bar.html /foo/bar/
pages/foo/bar/baz.html /foo/bar/baz/
pages/foo/bar/baz/index.html ❌ name "index.html" is not allowed

404.html is another special page. It is shown whenever a visitor visits a non-existent URL.

File path URL
pages/404.html /404/ (and any other URLs that don't exist)

🔗 ⭐ The post and post list templates ⤴️

The post.html and postlist.html files inside the posts/ directory are responsible for controlling what a post and post list page looks like.

Post category Template Post URL
default posts/postlist.html - /posts/
posts/post.html posts/hello-world.md /posts/hello-world/
posts/what-im-doing.md /posts/what-im-doing/
movie-reviews posts/movie-reviews/postlist.html - /posts/movie-reviews/
posts/movie-reviews/post.html posts/movie-reviews/morbius-2022.md /posts/movie-reviews/morbius-2022/
posts/movie-reviews/everything-everywhere-all-at-once-2022.md /posts/movie-reviews/everything-everywhere-all-at-once-2022/
posts/movie-reviews/barbie-2023.md /posts/movie-reviews/barbie-2023/

🔗 ⭐ Including static assets (css, js, images, fonts) ⤴️

Any file that you upload to or create in the themes/ directory (which is located in "output/themes/") is a static asset that is globally accessible via the "/themes/" root URL. This allows you to create or upload files that are accessible by all pages.

File path URL HTML element
output/themes/banner.jpg /themes/banner.jpg <img src='/themes/banner.jpg'>
output/themes/styles.css /themes/styles.css <link rel='stylesheet' href='/themes/styles.css'>
output/themes/jquery.slim.min.js /themes/jquery.slim.min.js <script src='/themes/jquery.slim.min.js'></script>
output/themes/bootstrap/bootstrap.min.css /themes/bootstrap/bootstrap.min.css <link rel='stylesheet' href='/themes/bootstrap/bootstrap.min.css'>
output/themes/bootstrap/bootstrap.bundle.min.js /themes/bootstrap/bootstrap.bundle.min.js <script src='/themes/bootstrap/bootstrap.bundle.min.js'></script>
output/themes/Roboto-Regular.ttf /themes/Roboto-Regular.ttf
<style>
  @font-face {
    font-family: "Roboto-Regular";
    src: url(/themes/Roboto-Regular.ttf) format("truetype");
  }
  html {
    font-family: "Roboto-Regular", Verdana, sans-serif;
  }
</style>

🔗 ⭐ Site-wide HTML boilerplate ⤴️

Notebrew adds these five lines of HTML boilerplate at the top of every page.

<!DOCTYPE html>
<html lang='{{ $.Site.LanguageCode }}'>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='icon' href='{{ $.Site.Favicon }}'>

Therefore, you should not write the stereotypical <html>, <head> and <body> tags that are present in so many HTML tutorials. Just write what you would write in <head>, followed by what you would write in <body>. Omit the <head> and <body> tags. Browsers know where the <body> begins and <head> ends.

❌ Don’t do:

<!DOCTYPE html>
<html lang='{{ $.Site.LanguageCode }}'>
  <head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <link rel='icon' href='{{ $.Site.Favicon }}'>
    <title>This is my title</title>
    <style>
      html { font-family: Georgia; }
    </style>
  </head>
  <body>
    <p>Hello World!</p>
  </body>
</html>

✅ Do:

<title>This is my title</title>
<style>
  html { font-family: Georgia; }
</style>
<p>Hello World!</p>

NOTE: If the <title> element is detected on the very first line, it will be used as the page title. Otherwise, the page title is derived from converting the page's file name to title case.

If you are comfortable with writing all your HTML by hand, you can stop here. This is all you have to know. This models the experience of writing HTML pages by hand as closely as possible. There are no other implicit lookup rules you have to learn.

🔗 Internal templates ⤴️

Within a page, you can define internal templates to be reused.

Internal templates are defined like this

{{ define "my-template" }}
... content goes here ...
{{ end }}

and they are called like this

{{ template "my-template" }}

Internal templates names must not end with the suffix ".html".

Example:

<p>Now I will greet you three times</p>
{{ template "hello" }}
{{ template "hello" }}
{{ template "hello" }}

{{ define "hello" }}
<div class='greeting'>Hello!</div>
{{ end }}

Result:

<p>Now I will greet you three times</p>
<div class='greeting'>Hello!</div>
<div class='greeting'>Hello!</div>
<div class='greeting'>Hello!</div>

Internal templates can only be used within a page. If you wish to reuse templates between pages, you need to use an external template.

🔗 External templates ⤴️

An external template is any HTML file in the themes/ directory. An external template is invoked just like an internal template, except the name must start with "/themes/" and end with ".html".

Note: although the external template's name starts with "/themes/", the actual location of the "themes/" directory is in "output/themes/". This will be reflected in the examples below.

External template name Validity
{{ template "hello.html" }} ❌ invalid external template (does not start with "/themes/")
{{ template "/themes/hello" }} ❌ invalid external template (starts with "/themes/" but does not end with ".html")
{{ template "themes/hello.html" }} ❌ invalid external template (does not start with "/themes/")
{{ template "/themes/hello.html" }} ✅ valid external template (references output/themes/hello.html)

Example:

🔗 External templates can be called from anywhere ⤴️

External templates can be called from any page, even other external templates. This makes it possible for external template calls to create a dependency cycle. In such cases, Notebrew will return an error. Try to keep calls to external templates to a minimum, it better for both performance and simplicity.

🔗 Calling an external template brings its internal templates into scope ⤴️

An external template may define its own set of internal templates. When you call an external template, you bring its internal templates into scope. This allows you to put a bunch of loosely related templates in a single file and import them by calling the external template.

🔗 If two external templates define the same internal template, the last one wins ⤴️

If a page calls two external templates that each define an internal template called "a", the last one wins. That is to say, all references to the template "a" are defined by whichever external template was loaded the last. External templates are loaded in alphabetical order based on their name (not the order in which they were called. The order in which they were called within a page does not matter).

🔗 Internal templates defined in the current template get the highest priority ⤴️

Internal templates defined in the current template always take precedence over any internal templates brought into scope from other templates. This allows template authors to export templates which provide user-overrideable blocks.

🔗 The template context variable ⤴️

Each template has access to exactly one variable, known as the context variable. It is denoted by the "$" dollar sign, and all information needed to render the template is contained within it. You can print the contents of a variable (or nested fields within a variable) by putting it between double curly braces {{ }}.

<!-- prints the entire $ variable (warning: quite unreadable) -->
{{ $ }}
<!-- prints nested fields within the $ variable -->
{{ $.Site.LanguageCode }}
{{ $.Site.Title }}
{{ $.CreationTime }}

Instead of printing the dollar "$" variable directly with {{ $ }}, it is better to use the {{ dump }} template function to print $ because it formats the output in a human-readable way.

{{ dump $ }} <!-- better than {{ $ }} -->

🔗 Passing in variables to templates ⤴️

TODO: highlight the importance of this because it is by the the most common source of errors, even as the creator I get caught by this so many times

Whenever you call an internal or external template, you must also pass in exactly one variable otherwise the template will not have access to any variables. The variable passed in will become the new "$" for that template.

{{ define "a" }}
<p>This site's language is {{ $.Site.LanguageCode }}</p>
<p>This site's title is {{ $.Site.Title }}</p>
{{ end }}

{{ $.Site.LanguageCode }}
{{ $.Site.Title }}
{{ template "a" }}   <!-- ❌ wrong! No variable passed in -->
{{ template "a" $ }} <!-- ✅ correct -->

If you don't pass in any variable to a template, the template will still work as long as it is purely HTML and never contains any references to the context variable.

{{ define "b" }}
<p>99 bottles of beer on the wall, 99 bottles of beer.</p>
<p>Take one down and pass it around, 98 bottles of beer on the wall.</p>
{{ end }}

{{ template "b" }} <!-- ✅ no variable passed in but that's okay -->

🔗 Page data ⤴️

This is the schema of the dollar "$" variable for every HTML page.

type PageData struct {
    Site struct { // Site information.
        LanguageCode          string       // Language code of the site.
        Title                 string       // Title of the site.
        Tagline               string       // Tagline of the site.
        Favicon               template.URL // Favicon of the site.
        TimezoneOffsetSeconds int          // The site's timezone offset in seconds.
        Description           string       // Description of the site.
        NavigationLinks       []struct {   // NavigationLinks of the site.
            Name string // Name of the link.
            URL  string // URL of the link.
        }
    }
    Parent     string     // Parent URL of the page.
    Name       string     // Name of the page.
    Title      string     // Title of the page.
    ChildPages []struct { // ChildPages of the page.
        Parent string // Parent URL of the child page.
        Name   string // Name of the child page.
        Title  string // Title of the child page.
    }
    ContentMap map[string]string // ContentMap of markdown file names to their file contents. Convert it to HTML using the markdownToHTML template function.
    Images     []struct {        // Images belonging to the page.
        Parent  string // Parent URL of the image.
        Name    string // Name of the image.
        AltText string // AltText of the image.
        Caption string // Caption of the image.
    }
    ModificationTime time.Time // ModificationTime of the page.
    CreationTime     time.Time // CreationTime of the page.
}

🔗 Post data ⤴️

This is the schema of the dollar "$" variable for every post template.

type PostData struct {
    Site struct { // Site information.
        LanguageCode          string       // Language code of the site.
        Title                 string       // Title of the site.
        Tagline               string       // Tagline of the site.
        Favicon               template.URL // Favicon of the site.
        TimezoneOffsetSeconds int          // The site's timezone offset in seconds.
        Description           string       // Description of the site.
        NavigationLinks       []struct {   // NavigationLinks of the site.
            Name string // Name of the link.
            URL  string // URL of the link.
        }
    }
    Category string     // Category of the post.
    Name     string     // Name of the post.
    Title    string     // Title of the post.
    Content  string     // Content of the post. Convert it to HTML using the markdownToHTML template function.
    Images   []struct { // Images belonging to the post that are not already explicitly included in the post content.
        Parent  string // Parent URL of the image.
        Name    string // Name of the image.
        AltText string // AltText of the image.
        Caption string // Caption of the image.
    }
    CreationTime     time.Time // CreationTime of the post.
    ModificationTime time.Time // ModificationTime of the post.
}

🔗 Post list data ⤴️

This is the schema of the dollar "$" variable for every post list template.

type PostListData struct {
    Site struct { // Site information.
        LanguageCode          string       // Language code of the site.
        Title                 string       // Title of the site.
        Tagline               string       // Tagline of the site.
        Favicon               template.URL // Favicon of the site.
        TimezoneOffsetSeconds int          // The site's timezone offset in seconds.
        Description           string       // Description of the site.
        NavigationLinks       []struct {   // NavigationLinks of the site.
            Name string // Name of the link.
            URL  string // URL of the link.
        }
    }
    Category   string   // Category of the post list.
    Pagination struct { // Pagination information for the post list.
        First    int   // First page.
        Previous int   // Previous page.
        Current  int   // Current page.
        Next     int   // Next page.
        Last     int   // Last page.
        Numbers  []int // Numbers is the list of page numbers to display.
    }
    Posts []struct { // The lists of posts for the current page of the post list.
        Category string     // Category of the post.
        Name     string     // Name of the post.
        Title    string     // Title of the post.
        Preview  string     // Preview of the post.
        HasMore  bool       // Whether the post has more text after the preview.
        Content  string     // Content of the post. Convert it to HTML using the markdownToHTML template function.
        Images   []struct { // Images belonging to the post that are not already explicitly included in the post content.
            Parent  string // Parent URL of the image.
            Name    string // Name of the image.
            AltText string // AltText of the image.
            Caption string // Caption of the image.
        }
        CreationTime     time.Time // CreationTime of the post.
        ModificationTime time.Time // ModificationTime of the post.
    }
}

🔗 Initializing new variables ⤴️

You can initialize new variables using the := assignment operator. Since a template only has access to the dollar "$" variable at first, all new variables will have to be derived from "$". You can use variables to make typing certain nested field accesses on the dollar "$" variable less tedious.

NOTE: All template variables must be prefixed with a dollar $ sign. That's how they can be differentiated from template functions, which do not begin with a $ sign.

{{ $languageCode := $.Site.LanguageCode }}
{{ $title := $.Site.Title }}

<p>This site's language is {{ $languageCode }}</p>
<p>This site's title is {{ $title }}</p>

To assign another value to a variable that has already been initialized, use the = assignment operator.

{{ $text := $.Site.Title }}
{{ if $.Site.Description }}
{{ $text = $.Site.Description }}
{{ end }}
<p>Site info: {{ $text }}</p>

🔗 If-else statements ⤴️

The {{ if }} {{ else }} {{ end }} keywords will execute the first or second block depending on whether a value is truthy or falsy. A value is falsy if it's false, 0, an empty string, an empty list, an empty map or nil. Any other value is considered truthy.

Additional conditions can be attached with {{ else if }}.

{{ if $.Site.Title }} <!-- true if string is not empty -->
<div>{{ $.Site.Title }}</div>
{{ else if $.Site.Description }} <!-- true if string is not empty -->
<div>{{ $.Site.Description }}</div>
{{ else }}
<div>welcome to my site</div>
{{ end }}

The {{ eq }} template function can be used for comparing two values in an if-else statement.

{{ if eq $.Site.Title "Hello World" }}
<p>{{ $.Site.Title }}</p>
{{ end }} 

There are 6 such comparison functions: eq, ne, lt, le, gt, ge. These correspond to the ==, !=, <, <=, >, >= comparison operators.

Template function Meaning
{{ if eq $a $b }} if $a == $b
{{ if ne $a $b }} if $a != $b
{{ if lt $a $b }} if $a < $b
{{ if le $a $b }} if $a <= $b
{{ if gt $a $b }} if $a > $b
{{ if ge $a $b }} if $a >= $b

Additionally, eq supports comparing the first argument with one or more arguments to make multi-way equality tests simpler.

Template function Meaning
{{ if eq $a $b $c $d }} if $a == $b || $a == $c || $a == $d

You can combine boolean values together using the and, or and not logical functions.

Template function Meaning
{{ if and $a $b $c }} $a && $b && $b
{{ if or $a $b $c }} $a || $b || $c
{{ if not $a }} !$a

Expressions can be further nested using brackets ( ), allowing you to specify more complex predicates.

Template function Meaning
{{ if and (not $a) (not $b) (or (lt $c 5) (ge $c 10)) }} if !$a && !$b && ($c < 5 || $c >= 10)

🔗 Loop over a list ⤴️

Loop over a list using the {{ range }} keyword.

{{ range $navigationLink := $.Site.NavigationLinks }}
<a href='{{ $navigationLink.URL }}'>{{ $navigationLink.Name }}</a>
{{ end }}

You can also write range statements using a second form which includes the index variable.

{{ range $index, $navigationLink := $.Site.NavigationLinks }}
<a href='{{ $navigationLink.URL }}'>{{ $index }}: {{ $navigationLink.Name }}</a>
{{ end }}

Within a loop, you can increment or decrement the index variable using the {{ plus }} and {{ minus }} template functions.

🔗 Loop over a list in reverse ⤴️

🔗 Access a list value by index ⤴️

Access a list value by index using the {{ index }} function.

Template function Meaning
{{ index $.Site.NavigationLinks 0 }} Site.NavigationLinks[0]
{{ (index $.Site.NavigationLinks 1).Name }} Site.NavigationLinks[1].Name

🔗 Access map value by key ⤴️

Access a map value by key using the {{ index }} function.

Template function Meaning
{{ index $.ContentMap "content.md" }} ContentMap["content.md"]
{{ markdownToHTML (index $.ContentMap "about.md") }} markdownToHTML(ContentMap["about.md"])

If you access a nonexistent key, you will get back an empty value. You can test for this using an {{ if }} statement, because empty values always evaluate to false.

🔗 Convert markdown to HTML ⤴️

🔗 Get the URL of a page, post or image ⤴️

Get the URL of a page, post or image by joining its parent URL with its name using the {{ join }} template function. The {{ join }} template function joins multiple strings using into a single path delimited by forward slashes, ignoring any empty strings. It does not add any leading or trailing slashes, so you will have to add those yourself.

{{ join "a" "b" "c" }} => a/b/c
{{ join "a" "b/c" }}   => a/b/c
{{ join "a" "" "c" }}  => a/c
<!-- linking to a page -->
<a href='/{{ join $.Parent $.Name }}/'>{{ $.Name }}</a>
<a href='/{{ join $page.Parent $page.Name }}/'>{{ $page.Name }}</a>
<!-- linking to a post -->
<a href='/{{ join "posts" $.Category $.Name }}/'>{{ $.Name }}</a>
<a href='/{{ join "posts" $post.Category $post.Name }}/'>{{ $post.Name }}</a>
<!-- displaying an image -->
<img src='/{{ join $image.Parent $image.Name }}'>

🔗 Get a table of contents from markdown ⤴️

🔗 Formatting time ⤴️

🔗 Template function reference ⤴️

TODO: improve this

(notebrew.PageData) {
 Site: (notebrew.Site) {
  LanguageCode: (string) (len=2) "en",
  Title: (string) (len=8) "notebrew",
  Tagline: (string) "",
  Favicon: (template.URL) (len=139) "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 10 10%22><text y=%221em%22 font-size=%228%22>☕</text></svg>",
  TimezoneOffsetSeconds: (int) 0,
  Description: (string) (len=63) "notebrew is a self-hostable static site CMS in a single binary.",
  NavigationLinks: ([]notebrew.NavigationLink) (len=2 cap=2) {
   (notebrew.NavigationLink) {
    Name: (string) (len=12) "notebrew.com",
    URL: (string) (len=1) "/"
   },
   (notebrew.NavigationLink) {
    Name: (string) (len=5) "posts",
    URL: (string) (len=7) "/posts/"
   }
  }
 },
 Parent: (string) (len=13) "documentation",
 Name: (string) (len=53) "the-essential-guide-to-writing-html-pages-in-notebrew",
 Title: (string) (len=53) "The Essential Guide to Writing HTML Pages in Notebrew",
 ChildPages: ([]notebrew.Page) {
 },
 ContentMap: (map[string]string) (len=1) {
  (string) (len=10) "content.md": (string) (len=34020) "## [🔗](#how-html-pages-map-to-urls) ⭐ How HTML pages map to URLs [⤴️](#toc-how-html-pages-map-to-urls) {#how-html-pages-map-to-urls}\r\n\r\nHTML pages are stored inside the top level pages/ directory, and they map to URLs 1-to-1.\r\n\r\nindex.html is a special page which maps to the root URL. As a result, no other file can be named index.html other than the one for the root URL.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>File path</th>\r\n  <th>URL</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td>pages/index.html</td>\r\n  <td>/</td>\r\n</tr>\r\n<tr>\r\n  <td>pages/foo.html</td>\r\n  <td>/foo/</td>\r\n</tr>\r\n<tr>\r\n  <td>pages/foo/bar.html</td>\r\n  <td>/foo/bar/</td>\r\n</tr>\r\n<tr>\r\n  <td>pages/foo/bar/baz.html</td>\r\n  <td>/foo/bar/baz/</td>\r\n</tr>\r\n<tr>\r\n  <td>pages/foo/bar/baz/index.html</td>\r\n  <td>❌ name \"index.html\" is not allowed</td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\n404.html is another special page. It is shown whenever a visitor visits a non-existent URL.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>File path</th>\r\n  <th>URL</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td>pages/404.html</td>\r\n  <td>/404/ (and any other URLs that don't exist)</td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\n## [🔗](#the-post-and-post-list-templates) ⭐ The post and post list templates [⤴️](#toc-the-post-and-post-list-templates) {#the-post-and-post-list-templates}\r\n\r\nThe post.html and postlist.html files inside the posts/ directory are responsible for controlling what a post and post list page looks like. \r\n\r\n- Each template has access to the [post data](#post-data) or [post list data](#post-list-data) via [the dollar \"`$`\" variable](#the-template-context-variable). \r\n\r\n- Each post category has their own copy of post.html and postlist.html, allowing you to have different post and post list appearances per post category.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>Post category</th>\r\n  <th>Template</th>\r\n  <th>Post</th>\r\n  <th>URL</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td rowspan='3'><em>default</em></td>\r\n  <td>posts/postlist.html</td>\r\n  <td>-</td>\r\n  <td>/posts/</td>\r\n</tr>\r\n<tr>\r\n  <td rowspan='2'>posts/post.html</td>\r\n  <td>posts/hello-world.md</td>\r\n  <td>/posts/hello-world/</td>\r\n</tr>\r\n<tr>\r\n  <td>posts/what-im-doing.md</td>\r\n  <td>/posts/what-im-doing/</td>\r\n</tr>\r\n<tr>\r\n  <td rowspan='4'>movie-reviews</td>\r\n  <td>posts/movie-reviews/postlist.html</td>\r\n  <td>-</td>\r\n  <td>/posts/movie-reviews/</td>\r\n</tr>\r\n<tr>\r\n  <td rowspan='3'>posts/movie-reviews/post.html</td>\r\n  <td>posts/movie-reviews/morbius-2022.md</td>\r\n  <td>/posts/movie-reviews/morbius-2022/</td>\r\n</tr>\r\n<tr>\r\n  <td>posts/movie-reviews/everything-everywhere-all-at-once-2022.md</td>\r\n  <td>/posts/movie-reviews/everything-everywhere-all-at-once-2022/</td>\r\n</tr>\r\n<tr>\r\n  <td>posts/movie-reviews/barbie-2023.md</td>\r\n  <td>/posts/movie-reviews/barbie-2023/</td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\n## [🔗](#including-static-assets) ⭐ Including static assets (css, js, images, fonts) [⤴️](#toc-including-static-assets) {#including-static-assets}\r\n\r\nAny file that you upload to or create in the themes/ directory (which is located in \"output/themes/\") is a static asset that is globally accessible via the \"/themes/\" root URL. This allows you to create or upload files that are accessible by all pages.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>File path</th>\r\n  <th>URL</th>\r\n  <th>HTML element</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td>output/themes/banner.jpg</td>\r\n  <td>/themes/banner.jpg</td>\r\n  <td><code>&lt;img src='/themes/banner.jpg'&gt;</code></td>\r\n</tr>\r\n<tr>\r\n  <td>output/themes/styles.css</td>\r\n  <td>/themes/styles.css</td>\r\n  <td><code>&lt;link rel='stylesheet' href='/themes/styles.css'&gt;</code></td>\r\n</tr>\r\n<tr>\r\n  <td>output/themes/jquery.slim.min.js</td>\r\n  <td>/themes/jquery.slim.min.js</td>\r\n  <td><code>&lt;script src='/themes/jquery.slim.min.js'&gt;&lt;/script&gt;</code></td>\r\n</tr>\r\n<tr>\r\n  <td>output/themes/bootstrap/bootstrap.min.css</td>\r\n  <td>/themes/bootstrap/bootstrap.min.css</td>\r\n  <td><code>&lt;link rel='stylesheet' href='/themes/bootstrap/bootstrap.min.css'&gt;</code></td>\r\n</tr>\r\n<tr>\r\n  <td>output/themes/bootstrap/bootstrap.bundle.min.js</td>\r\n  <td>/themes/bootstrap/bootstrap.bundle.min.js</td>\r\n  <td><code>&lt;script src='/themes/bootstrap/bootstrap.bundle.min.js'&gt;&lt;/script&gt;</code></td>\r\n</tr>\r\n<tr>\r\n  <td>output/themes/Roboto-Regular.ttf</td>\r\n  <td>/themes/Roboto-Regular.ttf</td>\r\n  <td><pre>&lt;style&gt;\r\n  @font-face {\r\n    font-family: \"Roboto-Regular\";\r\n    src: url(/themes/Roboto-Regular.ttf) format(\"truetype\");\r\n  }\r\n  html {\r\n    font-family: \"Roboto-Regular\", Verdana, sans-serif;\r\n  }\r\n&lt;/style&gt;</pre></td>\r\n  </tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\n## [🔗](#site-wide-html-boilerplate) ⭐ Site-wide HTML boilerplate [⤴️](#toc-site-wide-html-boilerplate) {#site-wide-html-boilerplate}\r\n\r\nNotebrew adds these five lines of HTML boilerplate at the top of every page.\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang='{{ $.Site.LanguageCode }}'>\r\n<meta charset='utf-8'>\r\n<meta name='viewport' content='width=device-width, initial-scale=1'>\r\n<link rel='icon' href='{{ $.Site.Favicon }}'>\r\n```\r\n\r\nTherefore, you should *not* write the stereotypical \\<html\\>, \\<head\\> and \\<body\\> tags that are present in so many HTML tutorials. Just write what you would write in \\<head\\>, followed by what you would write in \\<body\\>. Omit the \\<head\\> and \\<body\\> tags. Browsers know where the \\<body\\> begins and \\<head\\> ends.\r\n\r\n**❌ Don’t do:**\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang='{{ $.Site.LanguageCode }}'>\r\n  <head>\r\n    <meta charset='utf-8'>\r\n    <meta name='viewport' content='width=device-width, initial-scale=1'>\r\n    <link rel='icon' href='{{ $.Site.Favicon }}'>\r\n    <title>This is my title</title>\r\n    <style>\r\n      html { font-family: Georgia; }\r\n    </style>\r\n  </head>\r\n  <body>\r\n    <p>Hello World!</p>\r\n  </body>\r\n</html>\r\n```\r\n\r\n**✅ Do:**\r\n\r\n```html\r\n<title>This is my title</title>\r\n<style>\r\n  html { font-family: Georgia; }\r\n</style>\r\n<p>Hello World!</p>\r\n```\r\n\r\n**NOTE:** If the \\<title\\> element is detected on the very first line, it will be used as the page title. Otherwise, the page title is derived from converting the page's file name to title case.\r\n\r\nIf you are comfortable with writing all your HTML by hand, you can stop here. This is all you have to know. This models the experience of writing HTML pages by hand as closely as possible. There are no other implicit lookup rules you have to learn.\r\n\r\n## [🔗](#internal-templates) Internal templates [⤴️](#toc-internal-templates) {#internal-templates}\r\n\r\nWithin a page, you can define internal templates to be reused. \r\n\r\nInternal templates are defined like this \r\n\r\n```html\r\n{{ define \"my-template\" }}\r\n... content goes here ...\r\n{{ end }}\r\n```\r\n\r\nand they are called like this\r\n\r\n```html\r\n{{ template \"my-template\" }}\r\n```\r\n\r\nInternal templates names must not end with the suffix \".html\".\r\n\r\n**Example:**\r\n\r\n```html\r\n<p>Now I will greet you three times</p>\r\n{{ template \"hello\" }}\r\n{{ template \"hello\" }}\r\n{{ template \"hello\" }}\r\n\r\n{{ define \"hello\" }}\r\n<div class='greeting'>Hello!</div>\r\n{{ end }}\r\n```\r\n\r\n**Result:**\r\n\r\n```html\r\n<p>Now I will greet you three times</p>\r\n<div class='greeting'>Hello!</div>\r\n<div class='greeting'>Hello!</div>\r\n<div class='greeting'>Hello!</div>\r\n```\r\n\r\nInternal templates can only be used within a page. If you wish to reuse templates between pages, you need to use an external template.\r\n\r\n## [🔗](#external-templates) External templates [⤴️](#toc-external-templates) {#external-templates}\r\n\r\nAn external template is any HTML file in the themes/ directory. An external template is invoked just like an internal template, except the name must start with \"/themes/\" and end with \".html\".\r\n\r\n> Note: although the external template's name starts with \"/themes/\", the actual location of the \"themes/\" directory is in \"output/themes/\". This will be reflected in the examples below.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>External template name</th>\r\n  <th>Validity</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td><code>{{ template \"hello.html\" }}</code></td>\r\n  <td>❌ invalid external template (does not start with \"/themes/\")</td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ template \"/themes/hello\" }}</code></td>\r\n  <td>❌ invalid external template (starts with \"/themes/\" but does not end with \".html\")</td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ template \"themes/hello.html\" }}</code></td>\r\n  <td>❌ invalid external template (does not start with \"/themes/\")</td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ template \"/themes/hello.html\" }}</code></td>\r\n  <td>✅ valid external template (references output/themes/hello.html)</td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\n**Example:**\r\n\r\n- **output/themes/header.html**\r\n\r\n  ```html\r\n  <header>\r\n    <h1>Welcome to my site</h1>\r\n    <a href='/about-me/'>About Me</a>\r\n    <a href='/posts/'>Posts</a>\r\n  </header>\r\n  ```\r\n\r\n- **output/themes/footer.html**\r\n\r\n  ```html\r\n  <footer>\r\n    Copyright © 2024 My Name. <span>All rights reserved.</span>\r\n  </footer>\r\n  ```\r\n\r\n- **pages/index.html**\r\n\r\n  ```html\r\n  {{ template \"/themes/header.html\" }}\r\n  <main>\r\n    Hello world!\r\n  </main>\r\n  {{ template \"/themes/footer.html\" }}\r\n  ```\r\n\r\n  **Result:**\r\n\r\n  ```html\r\n  <header>\r\n    <h1>Welcome to my site</h1>\r\n    <a href='/about-me/'>About Me</a>\r\n    <a href='/posts/'>Posts</a>\r\n  </header>\r\n  <main>\r\n    Hello world!\r\n  </main>\r\n  <footer>\r\n    Copyright © 2024 My Name. <span>All rights reserved.</span>\r\n  </footer>\r\n  ```\r\n\r\n### [🔗](#external-templates-can-be-called-from-anywhere) External templates can be called from anywhere [⤴️](#toc-external-templates-can-be-called-from-anywhere) {#external-templates-can-be-called-from-anywhere}\r\n\r\nExternal templates can be called from any page, even other external templates. This makes it possible for external template calls to create a dependency cycle. In such cases, Notebrew will return an error. Try to keep calls to external templates to a minimum, it better for both performance and simplicity.\r\n\r\n### [🔗](#calling-an-external-template-brings-its-internal-templates-into-scope) Calling an external template brings its internal templates into scope [⤴️](#toc-calling-an-external-template-brings-its-internal-templates-into-scope) {#calling-an-external-template-brings-its-internal-templates-into-scope}\r\n\r\nAn external template may define its own set of [internal templates](#internal-templates). When you call an external template, you bring its internal templates into scope. This allows you to put a bunch of loosely related templates in a single file and import them by calling the external template.\r\n\r\n- **output/themes/common.html**\r\n\r\n  ```html\r\n  {{ define \"header\" }}\r\n  <header>\r\n    <h1>Welcome to my site</h1>\r\n    <a href='/about-me/'>About Me</a>\r\n    <a href='/posts/'>Posts</a>\r\n  </header>\r\n  {{ end }}\r\n  \r\n  {{ define \"footer\" }}\r\n  <footer>\r\n    Copyright © 2024 My Name. <span>All rights reserved.</span>\r\n  </footer>\r\n  {{ end }}\r\n  ```\r\n\r\n- **pages/index.html**\r\n\r\n  ```html\r\n  <!-- call /theme/common.html for the side effect \r\n  of \"importing\" its internal templates -->\r\n  {{ template \"/themes/common.html\" }}\r\n\r\n  <!-- now you can access the \"header\" and \"footer\" templates, \r\n  which were defined inside /themes/common.html -->\r\n  {{ template \"header\" }}\r\n  <main>\r\n    Hello world!\r\n  </main>\r\n  {{ template \"footer\" }}\r\n  ```\r\n\r\n  **Result:**\r\n\r\n  ```html\r\n  <header>\r\n    <h1>Welcome to my site</h1>\r\n    <a href='/about-me/'>About Me</a>\r\n    <a href='/posts/'>Posts</a>\r\n  </header>\r\n  <main>\r\n    Hello world!\r\n  </main>\r\n  <footer>\r\n    Copyright © 2024 My Name. <span>All rights reserved.</span>\r\n  </footer>\r\n  ```\r\n\r\n### [🔗](#if-two-external-templates-define-same-internal-template-last-one-wins) If two external templates define the same internal template, the last one wins [⤴️](#toc-if-two-external-templates-define-same-internal-template-last-one-wins) {#if-two-external-templates-define-same-internal-template-last-one-wins}\r\n\r\nIf a page calls two external templates that each define an internal template called \"a\", the last one wins. That is to say, all references to the template \"a\" are defined by whichever external template was loaded the last. External templates are loaded in alphabetical order based on their name (*not* the order in which they were called. The order in which they were called within a page does not matter).\r\n\r\n- **output/themes/01.html**\r\n\r\n  ```html\r\n  {{ define \"a\" }}\r\n  <span>My favorite fruit is apple.</span>\r\n  {{ end }}\r\n\r\n  {{ define \"b\" }}\r\n  <span>My favorite color is red.</span>\r\n  {{ end }}\r\n  ```\r\n\r\n- **output/themes/02.html**\r\n\r\n  ```html\r\n  {{ define \"a\" }}\r\n  <span>My favorite fruit is banana.</span>\r\n  {{ end }}\r\n  ```\r\n\r\n- **pages/index.html**\r\n\r\n  ```html\r\n  {{ template \"/themes/01.html\" }}\r\n  {{ template \"/themes/02.html\" }}\r\n  {{ template \"a\" }} <!-- 👈 comes from /themes/02.html -->\r\n  {{ template \"b\" }} <!-- 👈 comes from /themes/01.html -->\r\n  ```\r\n\r\n  **Result:**\r\n\r\n  ```html\r\n  <span>My favorite fruit is banana.</span>\r\n  <span>My favorite color is red.</span>\r\n  ```\r\n\r\n#### [🔗](#internal-templates-defined-in-the-current-template-get-highest-priority) Internal templates defined in the current template get the highest priority [⤴️](#toc-internal-templates-defined-in-the-current-template-get-highest-priority) {#internal-templates-defined-in-the-current-template-get-highest-priority}\r\n\r\nInternal templates defined in the current template always take precedence over any internal templates brought into scope from other templates. This allows template authors to export templates which provide user-overrideable blocks.\r\n\r\n- **output/themes/my-special-theme/index.html**\r\n\r\n  ```html\r\n  <header>\r\n    <h1>Welcome to my site</h1>\r\n    <a href='/about-me/'>About Me</a>\r\n    <a href='/posts/'>Posts</a>\r\n  </header>\r\n  <main>\r\n    {{ template \"introduction\" }} <!-- 👈 template \"introduction\" can be overridden -->\r\n  </main>\r\n  <footer>\r\n                     <!-- 👇 template \"name\" can be overridden -->\r\n    Copyright © 2024 {{ template \"name\" }}. <span>All rights reserved.</span>\r\n  </footer>\r\n\r\n  {{ define \"introduction\" }}\r\n  <p>Your Introduction Goes Here.</p>\r\n  {{ end }}\r\n\r\n  {{ define \"name\" }}Your Name Goes Here{{ end }}\r\n  ```\r\n- **pages/index.html**\r\n\r\n  ```html\r\n  {{ template \"/themes/my-special-theme/index.html\" }}\r\n\r\n  {{ define \"introduction\" }}\r\n  <p>Hello World! My name is Bob.</p>\r\n  {{ end }}\r\n\r\n  {{ define \"name\" }}Bob{{ end }}\r\n  ```\r\n\r\n  **Result:**\r\n\r\n  ```html\r\n  <header>\r\n    <h1>Welcome to my site</h1>\r\n    <a href='/about-me/'>About Me</a>\r\n    <a href='/posts/'>Posts</a>\r\n  </header>\r\n  <main>\r\n    <p>Hello World! My name is Bob.</p>\r\n  </main>\r\n  <footer>\r\n    Copyright © 2024 Bob. <span>All rights reserved.</span>\r\n  </footer>\r\n  ```\r\n\r\n## [🔗](#the-template-context-variable) The template context variable [⤴️](#toc-the-template-context-variable) {#the-template-context-variable}\r\n\r\nEach template has access to exactly one variable, known as the context variable. It is denoted by the \"`$`\" dollar sign, and all information needed to render the template is contained within it. You can print the contents of a variable (or nested fields within a variable) by putting it between double curly braces `{{ }}`.\r\n\r\n```html\r\n<!-- prints the entire $ variable (warning: quite unreadable) -->\r\n{{ $ }}\r\n<!-- prints nested fields within the $ variable -->\r\n{{ $.Site.LanguageCode }}\r\n{{ $.Site.Title }}\r\n{{ $.CreationTime }}\r\n```\r\n\r\nInstead of printing the dollar \"`$`\" variable directly with `{{ $ }}`, it is better to use the `{{ dump }}` template function to print `$` because it formats the output in a human-readable way.\r\n\r\n```html\r\n{{ dump $ }} <!-- better than {{ $ }} -->\r\n```\r\n\r\n### [🔗](#passing-in-variables-to-templates) Passing in variables to templates [⤴️](#toc-passing-in-variables-to-templates) {#passing-in-variables-to-templates}\r\n\r\nTODO: highlight the importance of this because it is by the the most common source of errors, even as the creator I get caught by this *so* many times\r\n\r\nWhenever you call an [internal](#internal-templates) or [external](#external-templates) template, you must also pass in exactly one variable otherwise the template will not have access to any variables. The variable passed in will become the new \"`$`\" for that template.\r\n\r\n```html\r\n{{ define \"a\" }}\r\n<p>This site's language is {{ $.Site.LanguageCode }}</p>\r\n<p>This site's title is {{ $.Site.Title }}</p>\r\n{{ end }}\r\n\r\n{{ $.Site.LanguageCode }}\r\n{{ $.Site.Title }}\r\n{{ template \"a\" }}   <!-- ❌ wrong! No variable passed in -->\r\n{{ template \"a\" $ }} <!-- ✅ correct -->\r\n```\r\n\r\nIf you don't pass in any variable to a template, the template will still work as long as it is purely HTML and never contains any references to the context variable.\r\n\r\n```html\r\n{{ define \"b\" }}\r\n<p>99 bottles of beer on the wall, 99 bottles of beer.</p>\r\n<p>Take one down and pass it around, 98 bottles of beer on the wall.</p>\r\n{{ end }}\r\n\r\n{{ template \"b\" }} <!-- ✅ no variable passed in but that's okay -->\r\n```\r\n\r\n### [🔗](#page-data) Page data [⤴️](#toc-page-data) {#page-data}\r\n\r\nThis is the schema of [the dollar \"`$`\" variable](#the-template-context-variable) for every [HTML page](#how-html-pages-map-to-urls). \r\n\r\n```go\r\ntype PageData struct {\r\n    Site struct { // Site information.\r\n        LanguageCode          string       // Language code of the site.\r\n        Title                 string       // Title of the site.\r\n        Tagline               string       // Tagline of the site.\r\n        Favicon               template.URL // Favicon of the site.\r\n        TimezoneOffsetSeconds int          // The site's timezone offset in seconds.\r\n        Description           string       // Description of the site.\r\n        NavigationLinks       []struct {   // NavigationLinks of the site.\r\n            Name string // Name of the link.\r\n            URL  string // URL of the link.\r\n        }\r\n    }\r\n    Parent     string     // Parent URL of the page.\r\n    Name       string     // Name of the page.\r\n    Title      string     // Title of the page.\r\n    ChildPages []struct { // ChildPages of the page.\r\n        Parent string // Parent URL of the child page.\r\n        Name   string // Name of the child page.\r\n        Title  string // Title of the child page.\r\n    }\r\n    ContentMap map[string]string // ContentMap of markdown file names to their file contents. Convert it to HTML using the markdownToHTML template function.\r\n    Images     []struct {        // Images belonging to the page.\r\n        Parent  string // Parent URL of the image.\r\n        Name    string // Name of the image.\r\n        AltText string // AltText of the image.\r\n        Caption string // Caption of the image.\r\n    }\r\n    ModificationTime time.Time // ModificationTime of the page.\r\n    CreationTime     time.Time // CreationTime of the page.\r\n}\r\n```\r\n\r\n### [🔗](#post-data) Post data [⤴️](#toc-post-data) {#post-data}\r\n\r\nThis is the schema of [the dollar \"`$`\" variable](#the-template-context-variable) for every [post template](#the-post-and-post-list-templates). \r\n\r\n```go\r\ntype PostData struct {\r\n    Site struct { // Site information.\r\n        LanguageCode          string       // Language code of the site.\r\n        Title                 string       // Title of the site.\r\n        Tagline               string       // Tagline of the site.\r\n        Favicon               template.URL // Favicon of the site.\r\n        TimezoneOffsetSeconds int          // The site's timezone offset in seconds.\r\n        Description           string       // Description of the site.\r\n        NavigationLinks       []struct {   // NavigationLinks of the site.\r\n            Name string // Name of the link.\r\n            URL  string // URL of the link.\r\n        }\r\n    }\r\n    Category string     // Category of the post.\r\n    Name     string     // Name of the post.\r\n    Title    string     // Title of the post.\r\n    Content  string     // Content of the post. Convert it to HTML using the markdownToHTML template function.\r\n    Images   []struct { // Images belonging to the post that are not already explicitly included in the post content.\r\n        Parent  string // Parent URL of the image.\r\n        Name    string // Name of the image.\r\n        AltText string // AltText of the image.\r\n        Caption string // Caption of the image.\r\n    }\r\n    CreationTime     time.Time // CreationTime of the post.\r\n    ModificationTime time.Time // ModificationTime of the post.\r\n}\r\n```\r\n\r\n### [🔗](#post-list-data) Post list data [⤴️](#toc-post-list-data) {#post-list-data}\r\n\r\nThis is the schema of [the dollar \"`$`\" variable](#the-template-context-variable) for every [post list template](#the-post-and-post-list-templates). \r\n\r\n```go\r\ntype PostListData struct {\r\n    Site struct { // Site information.\r\n        LanguageCode          string       // Language code of the site.\r\n        Title                 string       // Title of the site.\r\n        Tagline               string       // Tagline of the site.\r\n        Favicon               template.URL // Favicon of the site.\r\n        TimezoneOffsetSeconds int          // The site's timezone offset in seconds.\r\n        Description           string       // Description of the site.\r\n        NavigationLinks       []struct {   // NavigationLinks of the site.\r\n            Name string // Name of the link.\r\n            URL  string // URL of the link.\r\n        }\r\n    }\r\n    Category   string   // Category of the post list.\r\n    Pagination struct { // Pagination information for the post list.\r\n        First    int   // First page.\r\n        Previous int   // Previous page.\r\n        Current  int   // Current page.\r\n        Next     int   // Next page.\r\n        Last     int   // Last page.\r\n        Numbers  []int // Numbers is the list of page numbers to display.\r\n    }\r\n    Posts []struct { // The lists of posts for the current page of the post list.\r\n        Category string     // Category of the post.\r\n        Name     string     // Name of the post.\r\n        Title    string     // Title of the post.\r\n        Preview  string     // Preview of the post.\r\n        HasMore  bool       // Whether the post has more text after the preview.\r\n        Content  string     // Content of the post. Convert it to HTML using the markdownToHTML template function.\r\n        Images   []struct { // Images belonging to the post that are not already explicitly included in the post content.\r\n            Parent  string // Parent URL of the image.\r\n            Name    string // Name of the image.\r\n            AltText string // AltText of the image.\r\n            Caption string // Caption of the image.\r\n        }\r\n        CreationTime     time.Time // CreationTime of the post.\r\n        ModificationTime time.Time // ModificationTime of the post.\r\n    }\r\n}\r\n```\r\n\r\n### [🔗](#initializing-new-variables) Initializing new variables [⤴️](#toc-initializing-new-variables) {#initializing-new-variables}\r\n\r\nYou can initialize new variables using the `:=` assignment operator. Since a template only has access to the dollar \"`$`\" variable at first, all new variables will have to be derived from \"`$`\". You can use variables to make typing certain nested field accesses on the dollar \"`$`\" variable less tedious.\r\n\r\n> NOTE: All template variables must be prefixed with a dollar `$` sign. That's how they can be differentiated from template functions, which do not begin with a `$` sign.\r\n\r\n```html\r\n{{ $languageCode := $.Site.LanguageCode }}\r\n{{ $title := $.Site.Title }}\r\n\r\n<p>This site's language is {{ $languageCode }}</p>\r\n<p>This site's title is {{ $title }}</p>\r\n```\r\n\r\nTo assign another value to a variable that has already been initialized, use the `=` assignment operator.\r\n```html\r\n{{ $text := $.Site.Title }}\r\n{{ if $.Site.Description }}\r\n{{ $text = $.Site.Description }}\r\n{{ end }}\r\n<p>Site info: {{ $text }}</p>\r\n```\r\n\r\n## [🔗](#if-else-statements) If-else statements [⤴️](#toc-if-else-statements) {#if-else-statements}\r\n\r\nThe `{{ if }} {{ else }} {{ end }}` keywords will execute the first or second block depending on whether a value is truthy or falsy. A value is falsy if it's false, 0, an empty string, an empty list, an empty map or nil. Any other value is considered truthy. \r\n\r\nAdditional conditions can be attached with `{{ else if }}`.\r\n\r\n```html\r\n{{ if $.Site.Title }} <!-- true if string is not empty -->\r\n<div>{{ $.Site.Title }}</div>\r\n{{ else if $.Site.Description }} <!-- true if string is not empty -->\r\n<div>{{ $.Site.Description }}</div>\r\n{{ else }}\r\n<div>welcome to my site</div>\r\n{{ end }}\r\n```\r\n\r\nThe `{{ eq }}` template function can be used for comparing two values in an if-else statement.\r\n\r\n```html\r\n{{ if eq $.Site.Title \"Hello World\" }}\r\n<p>{{ $.Site.Title }}</p>\r\n{{ end }} \r\n```\r\n\r\nThere are 6 such comparison functions: `eq`, `ne`, `lt`, `le`, `gt`, `ge`. These correspond to the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>Template function</th>\r\n  <th>Meaning</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td><code>{{ if eq $a $b }}</code></td>\r\n  <td><code>if $a == $b</code></td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ if ne $a $b }}</code></td>\r\n  <td><code>if $a != $b</code></td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ if lt $a $b }}</code></td>\r\n  <td><code>if $a < $b</code></td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ if le $a $b }}</code></td>\r\n  <td><code>if $a <= $b</code></td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ if gt $a $b }}</code></td>\r\n  <td><code>if $a > $b</code></td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ if ge $a $b }}</code></td>\r\n  <td><code>if $a >= $b</code></td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\nAdditionally, `eq` supports comparing the first argument with one or more arguments to make multi-way equality tests simpler.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>Template function</th>\r\n  <th>Meaning</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td><code>{{ if eq $a $b $c $d }}</code></td>\r\n  <td><code>if $a == $b || $a == $c || $a == $d</code></td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\nYou can combine boolean values together using the `and`, `or` and `not` logical functions.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>Template function</th>\r\n  <th>Meaning</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td><code>{{ if and $a $b $c }}</code></td>\r\n  <td><code>$a && $b && $b</code></td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ if or $a $b $c }}</code></td>\r\n  <td><code>$a || $b || $c</code></td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ if not $a }}</code></td>\r\n  <td><code>!$a</code></td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\nExpressions can be further nested using brackets `( )`, allowing you to specify more complex predicates.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>Template function</th>\r\n  <th>Meaning</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td><code>{{ if and (not $a) (not $b) (or (lt $c 5) (ge $c 10)) }}</code></td>\r\n  <td><code>if !$a && !$b && ($c < 5 || $c >= 10)</code></td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\n## [🔗](#loop-over-a-list) Loop over a list [⤴️](#toc-loop-over-a-list) {#loop-over-a-list}\r\n\r\nLoop over a list using the `{{ range }}` keyword.\r\n\r\n```html\r\n{{ range $navigationLink := $.Site.NavigationLinks }}\r\n<a href='{{ $navigationLink.URL }}'>{{ $navigationLink.Name }}</a>\r\n{{ end }}\r\n```\r\n\r\nYou can also write range statements using a second form which includes the index variable.\r\n\r\n```html\r\n{{ range $index, $navigationLink := $.Site.NavigationLinks }}\r\n<a href='{{ $navigationLink.URL }}'>{{ $index }}: {{ $navigationLink.Name }}</a>\r\n{{ end }}\r\n```\r\n\r\nWithin a loop, you can increment or decrement the index variable using the [`{{ plus }}`](#plus) and [`{{ minus }}`](#minus) template functions.\r\n\r\n## [🔗](#loop-over-a-list-in-reverse) Loop over a list in reverse [⤴️](#toc-loop-over-a-list-in-reverse) {#loop-over-a-list-in-reverse}\r\n\r\n## [🔗](#access-a-list-value-by-index) Access a list value by index [⤴️](#toc-access-a-list-value-by-index) {#access-a-list-value-by-index}\r\n\r\nAccess a list value by index using the `{{ index }}` function.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>Template function</th>\r\n  <th>Meaning</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td><code>{{ index $.Site.NavigationLinks 0 }}</code></td>\r\n  <td><code>Site.NavigationLinks[0]</code></td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ (index $.Site.NavigationLinks 1).Name }}</code></td>\r\n  <td><code>Site.NavigationLinks[1].Name</code></td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\n## [🔗](#access-map-value-by-key) Access map value by key [⤴️](#toc-access-a-map-by-key) {#access-a-map-by-key}\r\n\r\nAccess a map value by key using the `{{ index }}` function.\r\n\r\n<div class='table-wrapper'>\r\n<table>\r\n<thead>\r\n<tr>\r\n  <th>Template function</th>\r\n  <th>Meaning</th>\r\n</tr>\r\n</thead>\r\n<tbody>\r\n<tr>\r\n  <td><code>{{ index $.ContentMap \"content.md\" }}</code></td>\r\n  <td><code>ContentMap[\"content.md\"]</code></td>\r\n</tr>\r\n<tr>\r\n  <td><code>{{ markdownToHTML (index $.ContentMap \"about.md\") }}</code></td>\r\n  <td><code>markdownToHTML(ContentMap[\"about.md\"])</code></td>\r\n</tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n\r\nIf you access a nonexistent key, you will get back an empty value. You can test for this using an [`{{ if }}`](#if-else-statements) statement, because empty values always evaluate to false.\r\n\r\n## [🔗](#convert-markdown-to-html) Convert markdown to HTML [⤴️](#toc-convert-markdown-to-html) {#convert-markdown-to-html}\r\n\r\n## [🔗](#get-the-url-of-a-page-post-or-image) Get the URL of a page, post or image [⤴️](#toc-get-the-url-of-a-page-post-or-image) {#get-the-url-of-a-page-post-or-image}\r\n\r\nGet the URL of a page, post or image by joining its parent URL with its name using the `{{ join }}` template function. The `{{ join }}` template function joins multiple strings using into a single path delimited by forward slashes, ignoring any empty strings. It does not add any leading or trailing slashes, so you will have to add those yourself.\r\n\r\n```text\r\n{{ join \"a\" \"b\" \"c\" }} => a/b/c\r\n{{ join \"a\" \"b/c\" }}   => a/b/c\r\n{{ join \"a\" \"\" \"c\" }}  => a/c\r\n```\r\n\r\n```html\r\n<!-- linking to a page -->\r\n<a href='/{{ join $.Parent $.Name }}/'>{{ $.Name }}</a>\r\n<a href='/{{ join $page.Parent $page.Name }}/'>{{ $page.Name }}</a>\r\n<!-- linking to a post -->\r\n<a href='/{{ join \"posts\" $.Category $.Name }}/'>{{ $.Name }}</a>\r\n<a href='/{{ join \"posts\" $post.Category $post.Name }}/'>{{ $post.Name }}</a>\r\n<!-- displaying an image -->\r\n<img src='/{{ join $image.Parent $image.Name }}'>\r\n```\r\n\r\n## [🔗](#get-a-table-of-contents-from-markdown) Get a table of contents from markdown [⤴️](#toc-get-a-table-of-contents-from-markdown) {#get-a-table-of-contents-from-markdown}\r\n\r\n## [🔗](#formatting-time) Formatting time [⤴️](#toc-formatting-time) {#formatting-time}\r\n\r\n## [🔗](#template-function-reference) Template function reference [⤴️](#toc-template-function-reference) {#template-function-reference}\r\n\r\nTODO: improve this\r\n\r\n- the root dollar '$' variable\r\n  \"a template is applied to some data structure\"\r\n  you can print variables using sinker curly braces\r\n  - when calling a template, you also need to apply to some data structure\r\n    - the data structure is initially provided to pages, posts and post lists. So there are only three types of data you have to familiarize yourself with.\r\n  - inspecting the $ variable\r\n  - declare new variables from the $ variable\r\n- string manipulation\r\n- addition and subtraction\r\n- formatting time\r\n- if else conditionals\r\n  - truthy/falsy\r\n  - comparison functions eq ne gt ge lt le\r\n  - combinator and or not\r\n- lists\r\n  - get the length of a list\r\n  - loop over a list\r\n    - loop over a list in reverse\r\n  - access a list by index\r\n  - create a new list of values\r\n  - seq\r\n- maps\r\n  - access a list by index\r\n  - passing in multiple variables to a template using maps\r\n- case/casewhen\r\n- how to convert markdown text into HTML (and where do you even get markdown text in the first place)\r\n- getting the URL of the current page or current post or an image\r\n- generate a table of contents from html content\r\n\r\n- variables\r\n  - how to print a variable\r\n  - every template's variable is represented by $\r\n  - declare intermediate variables using the $var syntax\r\n  - how to inspect a variable with dump function\r\n  - index to access map and slice values\r\n  - how to loop over a variable with the range keyword (also mention loop with index)\r\n  - obtain the length of a slice of map with len\r\n  - when you call a template, you can pass a variable into it. That variable becomes the new $ for that template\r\n    - despite only accepting one argument, how to pass multiple variables to a template using the dict function, and unpack the arguments later using the index function and intermediate variables.\r\n- how to use join to assemble URLs for PageData, Page, Image and PostData.\r\n  - emphasize its special behavior with empty strings when joining a Post Category, if category is empty it seamlessly merges the forward slashes.\r\n- basic string manipulation with hasPrefix trimPrefix hasSuffix trimSuffix trimSpace\r\n- page data has ContentMap. post data has Content. render it using markdownToHTML.\r\n- how to generate a table of contents using htmlHeadings (which uses markdownToHTML).\r\n- if else conditionals\r\n  - truthy/false\r\n  - comparison functions eq ne gt ge lt le\r\n  - boolean combinator functions and or not\r\n- increment and decrement with the plus and minus functions\r\n  - useful for working with the loop index\r\n  - use seq and minus in order to loop over a slice backwards (seq requires quite a bit of explanation of its various modes)\r\n- passing in multiple variables to a template using the dict function\r\n- miscellaneous functions like case/casewhen"
 },
 Images: ([]notebrew.Image) {
 },
 ModificationTime: (time.Time) 2024-10-13 22:43:37 +0000 UTC,
 CreationTime: (time.Time) 2024-08-23 09:28:06 +0000 UTC
}