code.pablomurad.com

mdssg

Single-file static site generator for a folder of linked markdown notes

example/content/reference/templates.md raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
---
title: Templates
aliases: [Theme, Theming]
---

# Templates

mdssg ships one theme. Drop a file with the same name into your site's
`templates/` directory and yours is used instead — for that file only, so
replacing the page layout does not mean re-implementing the rest.

```
templates/
  base.html         the html shell
  index.html        the home page
  page.html         a note
  seed.html         a seed page
  _backlinks.html   the "mentioned in" panel
  _related.html     the "related" panel
  style.css         the stylesheet
```

## The language

```
{{ page.title }}                 a value, HTML-escaped
{{ page.html | raw }}            trusted HTML, not escaped again
{{ page.date | date:"%d %B %Y" }}
{{ page.tags | join:" / " }}

{% if page.date %}...{% elif x %}...{% else %}...{% endif %}
{% if not backlinks %}...{% endif %}

{% for note in related %}
  <a href="{{ note.url }}">{{ note.title }}</a>
  {{ loop.index }} {{ loop.first }} {{ loop.last }}
{% endfor %}

{% include "_related.html" %}
{% extends "base.html" %}
{% block content %}...{% endblock %}
```

Filters: `raw`, `escape`, `date`, `slug`, `truncate`, `length`, `default`,
`join`, `upper`, `lower`.

## What it cannot do, on purpose

There is no arithmetic, no comparison, no method call, no indexing, and no way
to register a filter of your own. An expression is a dotted path plus filters
from a fixed table, and anything else is a parse error:

```
error: 'a == b' is not a value path. The template grammar has no arithmetic,
comparisons or calls - put the computation in the page context.
```

This is the whole design, not an unfinished part of it. A template language
that can compute grows until the interesting logic lives in HTML, where it
cannot be tested and cannot be read. If a template seems to need logic, the
value belongs in the context.

Paths also cannot begin with an underscore. Values are looked up with `getattr`
as well as by key, and allowing `_` would put `{{ a.__class__.__init__ }}` one
step away from a template.

## Escaping

Escaping is on by default. `{{ }}` escapes unless the value is already marked
as HTML, which only the markdown renderer and the `raw` filter produce. Getting
this backwards is the standard way a generator grows an injection bug, so it is
the default rather than something to remember.

## What a template receives

Every page:

| name | contents |
|------|----------|
| `site` | `title`, `url`, `description`, `author`, `language` |
| `page` | `title`, `url`, `slug`, `date`, `tags`, `html`, `description`, `is_post`, `is_seed`, `noindex` |
| `related` | list of `{title, url, seed}` |
| `asset_url`, `feed_url` | paths to the stylesheet and feed |

Notes also get `backlinks`, a list of `{title, url, excerpts}` where each
excerpt is HTML with the mention wrapped in `<mark>`. Seed pages get `mentions`
in the same shape. The home page gets `posts` and `notes`.

## Styling the graph

The theme leaves these hooks for you:

```css
.wikilink            /* any resolved wikilink        */
.wikilink-seed       /* points at an unwritten note  */
.wikilink-missing    /* seeds disabled, nothing there */
.backlinks mark      /* the mention inside a quote   */
.seed-notice         /* the banner on a seed page    */
.embed               /* transcluded content          */
.embed-error         /* a cycle or a depth limit     */
```

The bundled stylesheet is about 400 lines, uses system fonts, adapts to
`prefers-color-scheme`, and ships no JavaScript at all.