folderweb/docs/04-development/06-templates.md
Ruben 069ce389ea Add Atom feed support to list pages
Introduce `feed` metadata option to enable Atom feeds
Update list item structure with standardized fields
Add `$feedUrl` template variable for autodiscovery
Improve date handling with raw/processed date separation
Document feed generation in architecture and rendering docs
Update template examples to use new item structure
2026-02-06 18:24:39 +01:00

147 lines
5 KiB
Markdown

# Templates
## Template Hierarchy
Three levels, rendered inside-out:
```
Content (Markdown/HTML/PHP rendered to HTML string)
→ page.php or list-*.php (wraps content in semantic markup)
→ base.php (HTML scaffold: doctype, head, header, nav, main, footer)
```
## Template Resolution (canonical reference)
**`resolveTemplate(string $name): string`** — in `helpers.php`. This is the single resolution function used throughout the framework.
1. Check `custom/templates/{name}.php`
2. Fall back to `app/default/templates/{name}.php`
The three core templates (`base`, `page`, `list`) are resolved at context creation and stored in `$ctx->templates`. Other docs reference this section for resolution behavior.
### List Template Override
In `metadata.ini`:
```ini
[settings]
page_template = "list-grid"
```
Resolution for list template override (in `router.php`, at render time):
1. `custom/templates/{page_template}.php`
2. `app/default/templates/{page_template}.php`
3. Fall back to `$ctx->templates->list` (the default resolved at context creation)
### Default Templates Provided
| Template | Purpose |
|---|---|
| `base.php` | HTML document scaffold |
| `page.php` | Single page wrapper (`<article><?= $content ?></article>`) |
| `list.php` | Default list with cover images, dates, summaries |
| `list-compact.php` | Minimal list variant |
| `list-grid.php` | Card grid layout |
## Template Variables
Variables are injected via `extract()` — each array key becomes a local variable.
### base.php Variables
| Variable | Type | Always Set | Description |
|---|---|---|---|
| `$content` | string (HTML) | yes | Rendered output from page.php or list template |
| `$pageTitle` | ?string | yes | Page title (null if unset) |
| `$metaDescription` | ?string | no | SEO description |
| `$socialImageUrl` | ?string | no | Cover image URL for og:image |
| `$navigation` | array | yes | Menu items: `['title','url','order']` |
| `$homeLabel` | string | yes | Home link text |
| `$pageCssUrl` | ?string | no | Page-specific CSS URL |
| `$pageCssHash` | ?string | no | CSS cache-bust hash |
| `$feedUrl` | ?string | no | Atom feed URL (only on lists with `feed = true`) |
| `$currentLang` | string | plugin | Language code (from languages plugin) |
| `$langPrefix` | string | plugin | URL language prefix |
| `$languageUrls` | array | plugin | `[lang => url]` for language switcher |
| `$translations` | array | plugin | UI strings for current language |
### page.php Variables
All base.php variables plus:
| Variable | Type | Description |
|---|---|---|
| `$content` | string (HTML) | Rendered page content |
| `$metadata` | ?array | Page metadata from `metadata.ini` |
### list-*.php Variables
All base.php variables plus:
| Variable | Type | Description |
|---|---|---|
| `$items` | array | List items (see schema below) |
| `$pageContent` | ?string (HTML) | Rendered content from the list directory itself |
| `$metadata` | ?array | List directory metadata |
## List Item Schema
Each entry in `$items`:
| Key | Type | Always | Description |
|---|---|---|---|
| `title` | string | yes | From metadata, first heading, or folder name |
| `url` | string | yes | Full URL path with trailing slash and lang prefix |
| `date` | ?string | no | Formatted date string (plugin-processed) |
| `rawDate` | ?string | no | ISO `YYYY-MM-DD` date (for feeds, `<time>` elements) |
| `summary` | ?string | no | From metadata |
| `cover` | ?string | no | URL to cover image |
| `pdf` | ?string | no | URL to first PDF file |
| `redirect` | ?string | no | External redirect URL |
| `dirPath` | string | yes | Filesystem path to item directory (internal use) |
Items sorted by date — direction controlled by `order` metadata on parent (`descending` default, `ascending` available).
**Asset URLs vs page URLs:** Item `url` uses the translated slug. Asset paths (`cover`, `pdf`) use the actual folder name, ensuring assets resolve regardless of language.
## Adding Custom Template Variables
Via `Hook::TEMPLATE_VARS`:
```php
Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx): array {
$vars['myVar'] = 'value';
return $vars;
});
```
Then in any template: `<?= $myVar ?>`.
## Template Conventions
- **Escape all user-derived output:** `<?= htmlspecialchars($var) ?>`
- **Exception:** `$content` is pre-rendered HTML — output raw: `<?= $content ?>`
- **Check optional vars:** `<?php if (!empty($var)): ?>`
- **Use null coalescing for defaults:** `<?= $var ?? 'fallback' ?>`
- **Use short echo tags:** `<?= $expr ?>`
- **Semantic HTML5:** `<article>`, `<nav>`, `<header>`, `<footer>`, `<time>`, `<main>`
- **ARIA labels** on navigation elements
- **Classless defaults** — the default theme uses semantic HTML without classes where possible
## Partials
Not a framework feature — just PHP includes. Convention:
```
custom/templates/partials/post-card.php
```
Include from templates:
```php
<?php foreach ($items as $post): ?>
<?php include __DIR__ . '/partials/post-card.php'; ?>
<?php endforeach; ?>
```
Variables from the parent scope are available in the included file.