Update documentation for new JS variables and rendering pipeline Add script.js file support to content directories Implement cache-busting for JavaScript files Update static file serving to include JavaScript files Document page-specific script loading in base.php
145 lines
6.9 KiB
Markdown
145 lines
6.9 KiB
Markdown
# Content System
|
|
|
|
## Content Discovery
|
|
|
|
**`findAllContentFiles(string $dir): array`** — Returns sorted file paths from `$dir`.
|
|
|
|
- Scans for files with extensions in `CONTENT_EXTENSIONS` (`md`, `html`, `php`)
|
|
- Skips `index.php` (reserved for server entry points)
|
|
- Fires `Hook::PROCESS_CONTENT($files, $dir)` — plugins filter files (e.g., language variants)
|
|
- Sorts by filename via `strnatcmp` (natural sort: `00-hero`, `01-intro`, `02-body`)
|
|
- Returns flat array of absolute paths
|
|
|
|
**File ordering convention:** Prefix filenames with `NN-` for explicit ordering. Files without prefix sort after numbered files.
|
|
|
|
## URL Routing
|
|
|
|
**Trailing slash enforcement:** All page and list URLs are canonicalized with a trailing slash. Requests without one receive a 301 redirect (e.g., `/about` → `/about/`). The frontpage (`/`) is exempt.
|
|
|
|
**Frontpage handling:** Empty request path is handled before `parseRequestPath()` — it renders the content root directly via `renderMultipleFiles()`. The `"frontpage"` type from `parseRequestPath()` is never reached in the router's switch statement.
|
|
|
|
**`parseRequestPath(Context $ctx): array`** — Returns `['type' => string, 'path' => string]`.
|
|
|
|
Types:
|
|
- `"frontpage"` — empty request path (handled before this function is called)
|
|
- `"page"` — resolved directory with no subdirectories
|
|
- `"list"` — resolved directory with subdirectories
|
|
- `"not_found"` — slug resolution failed
|
|
|
|
**`resolveSlugToFolder(string $parentDir, string $slug): ?string`** — Matches URL slug to directory name.
|
|
|
|
Resolution order:
|
|
1. Exact folder name match (`$slug === $item`)
|
|
2. Metadata slug match (`metadata.ini` `slug` field)
|
|
|
|
Each URL segment is resolved independently, walking the directory tree. This enables language-specific slugs (e.g., `/no/om/` → `content/about/` via `[no] slug = "om"`).
|
|
|
|
## Metadata
|
|
|
|
**`loadMetadata(string $dirPath): ?array`** — Parses `metadata.ini` if present.
|
|
|
|
Returns flat key-value array with a special `_raw` key containing the full parsed INI structure (including sections). The `_raw` key is an internal contract — the language plugin reads `$data['_raw'][$lang]` to merge language-specific overrides. Plugins processing metadata **must preserve `_raw`** if present. Fires `Hook::PROCESS_CONTENT($metadata, $dirPath, 'metadata')`.
|
|
|
|
### Core Fields
|
|
|
|
| Field | Type | Default | Purpose |
|
|
|---|---|---|---|
|
|
| `title` | string | First `# heading` or folder name | Page title, list item title, `<title>` tag |
|
|
| `summary` | string | — | List item description, fallback meta description |
|
|
| `date` | string (YYYY-MM-DD) | Folder date prefix or file mtime | Sorting, display |
|
|
| `search_description` | string | Falls back to `summary` | `<meta name="description">` |
|
|
| `slug` | string | Folder name | URL override |
|
|
| `menu` | bool/int | 0 | Include in navigation |
|
|
| `menu_order` | int | 999 | Navigation sort order (ascending) |
|
|
| `order` | string | `"descending"` | List sort direction (`ascending`\|`descending`) |
|
|
| `redirect` | string | — | External URL (list items can redirect) |
|
|
| `feed` | bool | `false` | Enable Atom feed on list pages (`feed.xml`) |
|
|
| `plugins` | string | — | Comma-separated page-level plugin names |
|
|
|
|
### Settings Section
|
|
|
|
```ini
|
|
[settings]
|
|
page_template = "list-grid" # List template override (without .php)
|
|
show_date = true # Show date in list items (default: true)
|
|
hide_list = false # Force page rendering even with subdirectories
|
|
```
|
|
|
|
### Language Sections
|
|
|
|
```ini
|
|
title = "About"
|
|
slug = "about"
|
|
|
|
[no]
|
|
title = "Om oss"
|
|
slug = "om"
|
|
```
|
|
|
|
Supported language-overridable fields: `title`, `summary`, `search_description`, `slug`.
|
|
|
|
### Custom Fields
|
|
|
|
Any key not listed above is passed through to templates/plugins unchanged. Add whatever fields your templates need.
|
|
|
|
## Date Extraction
|
|
|
|
**`extractRawDateFromFolder(string $folderName): ?string`** — Extracts raw `YYYY-MM-DD` string from folder name prefix. Returns null if no date prefix. No hook processing.
|
|
|
|
**`extractDateFromFolder(string $folderName): ?string`** — Calls `extractRawDateFromFolder()` then passes the result through `Hook::PROCESS_CONTENT($date, 'date_format')` for plugin formatting (e.g., `"1. January 2025"`).
|
|
|
|
If no date prefix exists and no `date` metadata is set, falls back to file modification time (`filemtime`). All dates pass through `Hook::PROCESS_CONTENT($date, 'date_format')` for plugin formatting.
|
|
|
|
**Note:** Date extraction uses regex only — `2025-13-45-slug` would extract `2025-13-45` without validation. Invalid dates pass through to templates as-is.
|
|
|
|
**Sorting with null dates:** Items without any date are sorted as empty strings via `strcmp`. Their relative order among other dateless items is undefined.
|
|
|
|
## List Item Building
|
|
|
|
**`buildListItems(string $dir, Context $ctx, ?array $parentMetadata): array`** — Builds and sorts the items array for list views. Defined in `helpers.php`.
|
|
|
|
For each subdirectory: loads metadata, extracts title/date/cover/PDF, builds URL with lang prefix and slug. Returns sorted array — direction controlled by `order` metadata on parent (`descending` default).
|
|
|
|
Each item contains both a formatted `date` (hook-processed for display) and a `rawDate` (ISO `YYYY-MM-DD` for Atom feeds and `<time>` elements). Also includes `dirPath` (filesystem path) used by the feed generator to render full content.
|
|
|
|
Used by both the list case in `router.php` and the Atom feed generator.
|
|
|
|
## Navigation
|
|
|
|
**`buildNavigation(Context $ctx): array`** — Scans top-level content directories.
|
|
|
|
Returns items with `menu = 1` metadata, sorted by `menu_order`. Each item: `['title' => string, 'url' => string, 'order' => int]`. URLs include language prefix if applicable.
|
|
|
|
## Cover Images
|
|
|
|
**`findCoverImage(string $dirPath): ?string`** — Finds `cover.*` file.
|
|
|
|
Checks extensions in `COVER_IMAGE_EXTENSIONS` order: `jpg`, `jpeg`, `png`, `webp`, `gif`. Returns filename (not full path) or null.
|
|
|
|
## PDF Discovery
|
|
|
|
**`findPdfFile(string $dirPath): ?string`** — Returns basename of first `*.pdf` found, or null.
|
|
|
|
## Page-Specific CSS
|
|
|
|
**`findPageCss(string $dirPath, string $contentDir): ?array`** — Checks for `styles.css` in content directory.
|
|
|
|
Returns `['url' => string, 'hash' => string]` or null. Hash is MD5 of file content for cache busting.
|
|
|
|
## Page-Specific JavaScript
|
|
|
|
**`findPageJs(string $dirPath, string $contentDir): ?array`** — Checks for `script.js` in content directory.
|
|
|
|
Returns `['url' => string, 'hash' => string]` or null. Hash is MD5 of file content for cache busting. The script is loaded with the `defer` attribute in `base.php`, placed before `</body>` for non-blocking progressive enhancement.
|
|
|
|
## Meta Description Extraction
|
|
|
|
**`extractMetaDescription(string $dirPath, ?array $metadata): ?string`**
|
|
|
|
Priority: `search_description` → `summary` → first paragraph from content files (>20 chars).
|
|
|
|
## Title Extraction
|
|
|
|
**`extractTitle(string $filePath): ?string`** — Reads first content file in directory.
|
|
|
|
Markdown: first `# heading`. HTML/PHP: first `<h1>` tag. Returns null if no heading found.
|