nibula 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eleventy.js +68 -8
- package/CHANGELOG.md +79 -0
- package/bin/create.js +2 -25
- package/docs/Components.md +80 -42
- package/docs/Creating pages.md +3 -1
- package/docs/Deploy.md +185 -127
- package/docs/Head and SEO.md +98 -56
- package/docs/Project structure.md +3 -2
- package/package.json +3 -1
- package/src/frontend/layouts/base.njk +1 -1
- package/src/frontend/routes/404.njk +4 -3
- package/src/frontend/routes/index.njk +4 -3
- package/tools/assistant.js +21 -2
- package/tools/cli/prompt.js +99 -13
- package/tools/config/messages.json +1 -0
- package/tools/config/settings.json +10 -4
- package/tools/lib/files.js +5 -0
- package/tools/lib/layouts.js +15 -0
- package/tools/lib/pageActions.js +13 -5
- package/tools/lib/paths.js +1 -0
- package/tools/res/templates/template.njk +4 -3
package/.eleventy.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
1
|
+
const { RenderPlugin } = require("@11ty/eleventy");
|
|
2
|
+
const markdownItAnchor = require("markdown-it-anchor");
|
|
3
|
+
const markdownItAttrs = require("markdown-it-attrs");
|
|
3
4
|
const Image = require("@11ty/eleventy-img");
|
|
4
5
|
const fs = require("fs");
|
|
5
6
|
const path = require("path");
|
|
6
7
|
|
|
7
8
|
const OUTPUT_DIR = "out";
|
|
9
|
+
const TEMPLATE_ENGINE = "njk";
|
|
10
|
+
const ANCHOR_LEVELS = [2, 3, 4];
|
|
11
|
+
const ANCHOR_PERMALINK_SYMBOL = "#";
|
|
12
|
+
const ANCHOR_PERMALINK_PLACEMENT = "after";
|
|
13
|
+
|
|
14
|
+
const MARKDOWN_BASE_PATH = "src/frontend/components/";
|
|
8
15
|
|
|
9
16
|
module.exports = function (eleventyConfig) {
|
|
10
17
|
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Utilities
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
11
22
|
function copyRecursiveSync(src, dest) {
|
|
12
23
|
if (!fs.existsSync(src)) return;
|
|
13
|
-
if (src.includes(
|
|
24
|
+
if (src.includes(".git")) return;
|
|
25
|
+
|
|
14
26
|
const stat = fs.statSync(src);
|
|
27
|
+
|
|
15
28
|
if (stat.isDirectory()) {
|
|
16
29
|
fs.mkdirSync(dest, { recursive: true });
|
|
17
30
|
for (const child of fs.readdirSync(src)) {
|
|
@@ -23,18 +36,47 @@ module.exports = function (eleventyConfig) {
|
|
|
23
36
|
}
|
|
24
37
|
}
|
|
25
38
|
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Plugins & markdown
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
eleventyConfig.addPlugin(RenderPlugin);
|
|
44
|
+
|
|
45
|
+
eleventyConfig.amendLibrary("md", (markdownLibrary) => {
|
|
46
|
+
markdownLibrary
|
|
47
|
+
.use(markdownItAnchor, {
|
|
48
|
+
level: ANCHOR_LEVELS,
|
|
49
|
+
permalink: markdownItAnchor.permalink.linkInsideHeader({
|
|
50
|
+
symbol: ANCHOR_PERMALINK_SYMBOL,
|
|
51
|
+
placement: ANCHOR_PERMALINK_PLACEMENT,
|
|
52
|
+
}),
|
|
53
|
+
})
|
|
54
|
+
.use(markdownItAttrs);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Events
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
26
61
|
eleventyConfig.on("eleventy.before", () => {
|
|
27
62
|
copyRecursiveSync("src/backend", `${OUTPUT_DIR}/backend`);
|
|
28
63
|
});
|
|
29
64
|
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Passthrough copy
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
// Project assets
|
|
70
|
+
eleventyConfig.addPassthroughCopy("src/frontend/assets");
|
|
71
|
+
eleventyConfig.addPassthroughCopy("src/frontend/robots.txt");
|
|
72
|
+
|
|
73
|
+
// Hosting files
|
|
30
74
|
eleventyConfig.addPassthroughCopy({
|
|
31
75
|
"src/frontend/hosting/.htaccess": ".htaccess",
|
|
32
76
|
"src/frontend/hosting/web.config": "web.config",
|
|
33
77
|
});
|
|
34
|
-
|
|
35
|
-
eleventyConfig.addPassthroughCopy("src/frontend/assets");
|
|
36
|
-
eleventyConfig.addPassthroughCopy("src/frontend/robots.txt");
|
|
37
78
|
|
|
79
|
+
// node_modules dependencies
|
|
38
80
|
eleventyConfig.addPassthroughCopy({
|
|
39
81
|
// Bootstrap
|
|
40
82
|
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js": "js/bootstrap.bundle.min.js",
|
|
@@ -50,8 +92,14 @@ module.exports = function (eleventyConfig) {
|
|
|
50
92
|
// Bulma — CSS only, no JS passthrough needed
|
|
51
93
|
});
|
|
52
94
|
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Shortcodes
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
eleventyConfig.addFilter("markdownPath", (name) => `${MARKDOWN_BASE_PATH}${name}`);
|
|
100
|
+
|
|
53
101
|
eleventyConfig.addShortcode("image", async function (src, alt) {
|
|
54
|
-
|
|
102
|
+
const metadata = await Image(src, {
|
|
55
103
|
widths: [320, 480, 720, 1280, 1920, 2048, 2560, 3840, 4096, 7680],
|
|
56
104
|
formats: ["webp", "jpeg"],
|
|
57
105
|
outputDir: `${OUTPUT_DIR}/assets/images/`,
|
|
@@ -66,15 +114,27 @@ module.exports = function (eleventyConfig) {
|
|
|
66
114
|
});
|
|
67
115
|
});
|
|
68
116
|
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Watch targets & dev server
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
69
121
|
eleventyConfig.addWatchTarget("./src/frontend/scss");
|
|
70
122
|
eleventyConfig.addWatchTarget("./src/frontend/routes");
|
|
71
123
|
eleventyConfig.addWatchTarget("./src/frontend/data");
|
|
124
|
+
eleventyConfig.addWatchTarget("./src/frontend/components");
|
|
72
125
|
|
|
73
126
|
eleventyConfig.setServerOptions({
|
|
74
|
-
|
|
127
|
+
watch: [`${OUTPUT_DIR}/js/**/*.js`],
|
|
75
128
|
});
|
|
76
129
|
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Directory configuration
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
77
134
|
return {
|
|
135
|
+
markdownTemplateEngine: TEMPLATE_ENGINE,
|
|
136
|
+
htmlTemplateEngine: TEMPLATE_ENGINE,
|
|
137
|
+
|
|
78
138
|
dir: {
|
|
79
139
|
input: "src/frontend",
|
|
80
140
|
output: OUTPUT_DIR,
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,85 @@ All notable changes to Nibula are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.1.0] - 2026-08-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **The assistant asks which layout a new page should use.** Any `.njk` file in
|
|
12
|
+
`src/frontend/layouts/` is offered in an arrow-key menu, and the chosen one is
|
|
13
|
+
written into the route's front matter. Before, every page was created with
|
|
14
|
+
`base.njk` and the documentation had to remind you to change it by hand — the
|
|
15
|
+
assistant wrote a value the reader was then expected to correct. With a single
|
|
16
|
+
layout in the folder the menu is skipped, so nothing changes for a project that
|
|
17
|
+
has not added one; with none, the front matter is left untouched.
|
|
18
|
+
- `askChoice` in `tools/cli/prompt.js`: the arrow-key menu the scaffolder already
|
|
19
|
+
used, available to the assistant as well.
|
|
20
|
+
- **Markdown files can be rendered inside a page.** `markdown-it-anchor` and
|
|
21
|
+
`markdown-it-attrs` are installed and preconfigured, and Eleventy's
|
|
22
|
+
`RenderPlugin` is registered, so a `.md` file in `src/frontend/components/`
|
|
23
|
+
can be pulled into a route with `renderFile`. Headings from level 2 down get
|
|
24
|
+
an `id` and a `#` link, and `{.class}` applies a CSS class inline — markdown
|
|
25
|
+
produces plain HTML, so without a way to set classes the output carries none of
|
|
26
|
+
the project's framework styling.
|
|
27
|
+
- A `markdownPath` filter that prefixes the components folder, so a route writes
|
|
28
|
+
`"your-file.md" | markdownPath` and not the full path from the project root.
|
|
29
|
+
`renderFile` resolves paths from the working directory, unlike `include`, which
|
|
30
|
+
resolves them from the components folder.
|
|
31
|
+
- The template new pages are created from carries a commented `renderFile`
|
|
32
|
+
example next to the component one, with the data argument included:
|
|
33
|
+
`renderFile` does not inherit the page context, so a `.md` that reads `site.*`
|
|
34
|
+
renders those values empty unless the data is passed explicitly.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- **`tools/cli/prompt.js` creates a readline interface per question** instead of
|
|
38
|
+
holding one open for the whole session. The session-wide interface kept its own
|
|
39
|
+
keypress listener on stdin, which would have fought with the raw-mode listener
|
|
40
|
+
the arrow-key menu needs: both would have received every key, so arrows moved
|
|
41
|
+
the readline cursor and Enter fired its `line` event.
|
|
42
|
+
- **`markdownTemplateEngine` and `htmlTemplateEngine` are set to Nunjucks.**
|
|
43
|
+
Eleventy defaults `.md` files to Liquid, so a markdown file containing
|
|
44
|
+
`{{ site.title }}` was processed by a different template engine than every
|
|
45
|
+
other file in the project — working for the simple cases and silently not for
|
|
46
|
+
the rest.
|
|
47
|
+
- `src/frontend/components` is now a watch target. Eleventy tracks `.njk`
|
|
48
|
+
includes as template dependencies, but a `.md` read through `renderFile` is
|
|
49
|
+
invisible to it, so editing one changed nothing until the route itself was
|
|
50
|
+
saved.
|
|
51
|
+
- `.eleventy.js` is grouped into labelled sections, with plugins and markdown
|
|
52
|
+
configuration ahead of the build event and the passthrough copies.
|
|
53
|
+
- **Documentation is written for readers who are new to this.** The docs assumed
|
|
54
|
+
vocabulary a beginner does not have yet. `Deploy.md` opens with a question that
|
|
55
|
+
routes the reader by backend, and explains what shared hosting, a VPS, systemd,
|
|
56
|
+
an HTTPS certificate and `screen` actually are; the Node section now separates
|
|
57
|
+
trying the backend out from setting it up to survive a reboot, which read as
|
|
58
|
+
five equal steps before. `Head and SEO.md` starts from what the `<head>` is and
|
|
59
|
+
why there are two data files, and drops the unexplained jargon — canonical,
|
|
60
|
+
JSON-LD, noindex. `Components.md` documents markdown from what Markdown is,
|
|
61
|
+
with each part of the `renderFile` line explained rather than assumed.
|
|
62
|
+
- `Project structure.md` lists `components/` as the home of `.md` content, and
|
|
63
|
+
`Creating pages.md` describes both commented examples a new route carries.
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
- `Deploy.md` told the reader to open the Nginx site file with `nano` and how to
|
|
67
|
+
save it, without ever saying what to paste into it. The step now names the
|
|
68
|
+
edited `nginx.conf` as the source.
|
|
69
|
+
|
|
70
|
+
### Removed
|
|
71
|
+
- **The dead language switch in the scaffolder.** `bin/create.js` commented and
|
|
72
|
+
uncommented a `glob.sync` line for the JavaScript and TypeScript entry points,
|
|
73
|
+
but that line has not existed in `.eleventy.js` since bundling moved to
|
|
74
|
+
`tools/buildJs.js`, so the two markers matched nothing and the function ran as
|
|
75
|
+
a no-op. Entry points are collected from both language folders and resolved
|
|
76
|
+
against the filesystem, which makes the switch unnecessary: the folder that
|
|
77
|
+
does not exist contributes nothing.
|
|
78
|
+
- Unused `esbuild` and `glob` imports from `.eleventy.js`. Both are still real
|
|
79
|
+
dependencies of `tools/buildJs.js`, which is where they are used.
|
|
80
|
+
|
|
81
|
+
### Notes
|
|
82
|
+
- The markdown setup lands in newly created projects only. To adopt it in an
|
|
83
|
+
existing one, install `markdown-it-anchor` and `markdown-it-attrs` and copy the
|
|
84
|
+
plugin block, the `markdownPath` filter and the two template engine settings
|
|
85
|
+
from the current `.eleventy.js`.
|
|
86
|
+
|
|
8
87
|
## [2.0.2] - 2026-08-08
|
|
9
88
|
|
|
10
89
|
### Fixed
|
package/bin/create.js
CHANGED
|
@@ -126,13 +126,6 @@ const FRAMEWORKS = {
|
|
|
126
126
|
},
|
|
127
127
|
};
|
|
128
128
|
|
|
129
|
-
// ── LANGUAGE CONFIG ───────────────────────────────────────────────────────────
|
|
130
|
-
|
|
131
|
-
const LANGUAGE_ELEVENTY = Object.freeze({
|
|
132
|
-
jsEntry: 'const entryPoints = glob.sync("src/frontend/js/pages/*.js");',
|
|
133
|
-
tsEntry: 'const entryPoints = glob.sync("src/frontend/ts/pages/*.ts");',
|
|
134
|
-
});
|
|
135
|
-
|
|
136
129
|
// ── GENERATED FILE CONTENTS ───────────────────────────────────────────────────
|
|
137
130
|
|
|
138
131
|
const GITIGNORE_CONTENT = `
|
|
@@ -170,6 +163,8 @@ const PROJECT_PACKAGE = {
|
|
|
170
163
|
'bulma': '^1.0.4',
|
|
171
164
|
'foundation-sites': '^6.9.0',
|
|
172
165
|
'glob': '^13.0.6',
|
|
166
|
+
'markdown-it-anchor': '^9.2.1',
|
|
167
|
+
'markdown-it-attrs': '^5.0.1',
|
|
173
168
|
'uikit': '^3.25.13',
|
|
174
169
|
},
|
|
175
170
|
devDependencies: {
|
|
@@ -391,23 +386,6 @@ function applyFramework(framework) {
|
|
|
391
386
|
}
|
|
392
387
|
}
|
|
393
388
|
|
|
394
|
-
function applyLanguage(language) {
|
|
395
|
-
const eleventyPath = path.join(targetDir, '.eleventy.js');
|
|
396
|
-
if (!fs.existsSync(eleventyPath)) return;
|
|
397
|
-
|
|
398
|
-
let content = fs.readFileSync(eleventyPath, 'utf8');
|
|
399
|
-
|
|
400
|
-
if (language === LANGUAGE.TYPESCRIPT) {
|
|
401
|
-
content = slashComment(content, LANGUAGE_ELEVENTY.jsEntry);
|
|
402
|
-
content = slashUncomment(content, LANGUAGE_ELEVENTY.tsEntry);
|
|
403
|
-
} else {
|
|
404
|
-
content = slashUncomment(content, LANGUAGE_ELEVENTY.jsEntry);
|
|
405
|
-
content = slashComment(content, LANGUAGE_ELEVENTY.tsEntry);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
fs.writeFileSync(eleventyPath, content);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
389
|
// ── UI ────────────────────────────────────────────────────────────────────────
|
|
412
390
|
|
|
413
391
|
function askChoice(question, choices) {
|
|
@@ -527,7 +505,6 @@ async function init() {
|
|
|
527
505
|
}
|
|
528
506
|
|
|
529
507
|
applyFramework(framework);
|
|
530
|
-
applyLanguage(language);
|
|
531
508
|
|
|
532
509
|
installDependencies(backend);
|
|
533
510
|
|
package/docs/Components.md
CHANGED
|
@@ -58,6 +58,82 @@ A component can include other components. This is useful for breaking complex se
|
|
|
58
58
|
|
|
59
59
|
> The same path rules apply: if the included component is in a subfolder, specify the full relative path.
|
|
60
60
|
|
|
61
|
+
## Markdown files
|
|
62
|
+
|
|
63
|
+
### What is Markdown
|
|
64
|
+
|
|
65
|
+
Markdown (`.md`) is a way to write formatted text without HTML tags. You write
|
|
66
|
+
`**bold**` instead of `<strong>bold</strong>`, and a line starting with `-`
|
|
67
|
+
becomes a list item. For a page that is mostly text — an article, a privacy
|
|
68
|
+
policy, a changelog — it is much faster than writing the markup by hand.
|
|
69
|
+
|
|
70
|
+
### Write the file
|
|
71
|
+
|
|
72
|
+
Create a `.md` file in `src/frontend/components/`, next to your `.njk` ones.
|
|
73
|
+
|
|
74
|
+
#### article.md <small>(`src/frontend/components/`)</small>
|
|
75
|
+
|
|
76
|
+
```markdown
|
|
77
|
+
## Section title
|
|
78
|
+
|
|
79
|
+
Regular paragraph text. **Bold** and *italic* work as usual.
|
|
80
|
+
|
|
81
|
+
- First item
|
|
82
|
+
- Second item
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Show it in a page
|
|
86
|
+
|
|
87
|
+
Open the page's file in `src/frontend/routes/` and add this line to its body:
|
|
88
|
+
|
|
89
|
+
```njk
|
|
90
|
+
{% renderFile "article.md" | markdownPath, { site: site } %}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Three parts to it:
|
|
94
|
+
|
|
95
|
+
| Part | What it does |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `renderFile` | turns the Markdown into HTML and puts it in the page |
|
|
98
|
+
| `markdownPath` | fills in the `src/frontend/components/` part of the path for you |
|
|
99
|
+
| `{ site: site }` | hands the file your site settings — see below |
|
|
100
|
+
|
|
101
|
+
Subfolders work the same way as with components:
|
|
102
|
+
`"legal/privacy.md" | markdownPath`
|
|
103
|
+
|
|
104
|
+
### Why `{ site: site }`
|
|
105
|
+
|
|
106
|
+
A `.md` file can use site values, exactly like a component:
|
|
107
|
+
|
|
108
|
+
```markdown
|
|
109
|
+
Welcome to {{ site.title }}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
But a Markdown file does not automatically see them. Anything the file needs has
|
|
113
|
+
to be listed in the curly braces:
|
|
114
|
+
|
|
115
|
+
```njk
|
|
116
|
+
{% renderFile "article.md" | markdownPath, { site: site, pages: pages } %}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If you forget, nothing breaks — the value simply comes out empty. So if a
|
|
120
|
+
`{{ site.something }}` disappears from your page, this is why.
|
|
121
|
+
|
|
122
|
+
### Extras you get for free
|
|
123
|
+
|
|
124
|
+
**Links to a section.** Every `##` and `###` heading gets an anchor, so you can
|
|
125
|
+
link straight to it: `yoursite.com/my-page/#section-title`.
|
|
126
|
+
|
|
127
|
+
**CSS classes.** Markdown produces plain HTML with no classes, so your framework's
|
|
128
|
+
styling doesn't reach it. Add a class in curly braces after the element:
|
|
129
|
+
|
|
130
|
+
```markdown
|
|
131
|
+
## Section title {.text-center}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
> ⚠️ If the `.md` file is missing or the name is misspelled, the build stops with
|
|
135
|
+
> an error. That is on purpose: silently skipping content you asked for is worse.
|
|
136
|
+
|
|
61
137
|
## Global components
|
|
62
138
|
|
|
63
139
|
Header and footer live in `src/frontend/components/global/` and are automatically included in every page via `base.njk`. Edit them to change the site-wide layout
|
|
@@ -148,49 +224,11 @@ All values defined in `src/frontend/data/site.json` are globally available in ev
|
|
|
148
224
|
<img src="{{ site.logo }}" alt="{{ site.title }}">
|
|
149
225
|
```
|
|
150
226
|
|
|
227
|
+
> In a `.md` file rendered with `renderFile`, these values only work if you passed
|
|
228
|
+
> `site` in the curly braces — see *Why `{ site: site }`* above.
|
|
229
|
+
|
|
151
230
|
## Your own data files
|
|
152
231
|
|
|
153
232
|
`site.json` isn't special: **any** `.json` file you drop in `src/frontend/data/` becomes a global variable, and the variable takes the name of the file.
|
|
154
233
|
|
|
155
|
-
Create `test.json`, put anything you want inside it, and every key is reachable as `test.yourKey` — no import, no configuration, no restart.
|
|
156
|
-
|
|
157
|
-
### test.json <small>(`src/frontend/data/`)</small>
|
|
158
|
-
```json
|
|
159
|
-
{
|
|
160
|
-
"testMessage": "This is a test"
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Usage in any `.njk` file
|
|
165
|
-
```njk
|
|
166
|
-
<p>{{ test.testMessage }}</p>
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Renders as:
|
|
170
|
-
```html
|
|
171
|
-
<p>This is a test</p>
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
That's the whole rule: **file name = variable name, keys inside = what you write after the dot.** `site.json` gives you `site.title` for exactly the same reason.
|
|
175
|
-
|
|
176
|
-
Values can be anything JSON allows, so a list lets you loop instead of copying markup:
|
|
177
|
-
|
|
178
|
-
```json
|
|
179
|
-
{
|
|
180
|
-
"testMessage": "This is a test",
|
|
181
|
-
"links": [
|
|
182
|
-
{ "label": "Home", "url": "/" },
|
|
183
|
-
{ "label": "Contact", "url": "/contact/" }
|
|
184
|
-
]
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
```njk
|
|
189
|
-
{% for link in test.links %}
|
|
190
|
-
<a href="{{ link.url }}">{{ link.label }}</a>
|
|
191
|
-
{% endfor %}
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
Subfolders create nested names: `data/shop/products.json` becomes `{{ shop.products }}`.
|
|
195
|
-
|
|
196
|
-
> ⚠️ Don't reuse a name that already exists. Another file named `site.json` in a subfolder overwrites the original one silently, with no error to point you at the cause.
|
|
234
|
+
Create `test.json`, put anything you want inside it, and every key is reachable as `test.yourKey` — no import, no configuration, no restart.
|
package/docs/Creating pages.md
CHANGED
|
@@ -17,7 +17,9 @@ It also adds a stub entry in `src/frontend/data/pages.json` for the page's SEO a
|
|
|
17
17
|
|
|
18
18
|
## Adding content
|
|
19
19
|
|
|
20
|
-
The route is created empty
|
|
20
|
+
The route is created empty, with two commented examples: one for including a
|
|
21
|
+
`.njk` component, one for rendering a `.md` file. Uncomment the one you need or
|
|
22
|
+
write your HTML directly in the body — see the **Components** DOC file.
|
|
21
23
|
|
|
22
24
|
## URL and title
|
|
23
25
|
|