Add docs
This commit is contained in:
parent
b97b2f5503
commit
ad516600bb
14 changed files with 6093 additions and 0 deletions
398
docs/how-to/custom-styles.md
Normal file
398
docs/how-to/custom-styles.md
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
# How to Customize Styles
|
||||
|
||||
This guide shows you how to override the default styles with your own CSS.
|
||||
|
||||
## Overview
|
||||
|
||||
FolderWeb uses a fallback system for styles:
|
||||
1. Check `/custom/styles/base.css`
|
||||
2. Fall back to `/app/default/styles/base.css`
|
||||
|
||||
The framework automatically versions CSS files with MD5 hashes for cache busting.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Step 1: Create Custom Stylesheet
|
||||
|
||||
```bash
|
||||
mkdir -p custom/styles
|
||||
touch custom/styles/base.css
|
||||
```
|
||||
|
||||
### Step 2: Override CSS Variables
|
||||
|
||||
The easiest way to customize is to override CSS custom properties:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--color-primary: oklch(0.65 0.20 30); /* Orange */
|
||||
--color-secondary: oklch(0.50 0.18 30); /* Dark orange */
|
||||
--color-light: oklch(0.98 0.01 30); /* Warm white */
|
||||
--color-grey: oklch(0.40 0 0); /* Grey */
|
||||
|
||||
--font-body: "Helvetica Neue", Arial, sans-serif;
|
||||
--font-heading: "Georgia", serif;
|
||||
|
||||
--spacing-unit: 1.5rem;
|
||||
--border-radius: 8px;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Test Your Changes
|
||||
|
||||
Refresh your browser. If changes don't appear, do a hard refresh (Ctrl+Shift+R or Cmd+Shift+R).
|
||||
|
||||
## Available CSS Variables
|
||||
|
||||
### Colors
|
||||
|
||||
```css
|
||||
--color-primary: oklch(0.65 0.15 250); /* Primary blue */
|
||||
--color-secondary: oklch(0.50 0.12 250); /* Dark blue */
|
||||
--color-light: oklch(0.97 0.01 250); /* Off-white */
|
||||
--color-grey: oklch(0.37 0 0); /* Dark grey */
|
||||
```
|
||||
|
||||
**Note**: FolderWeb uses OKLCH colors for perceptually uniform color spaces. You can also use hex, rgb, or hsl if preferred.
|
||||
|
||||
### Typography
|
||||
|
||||
```css
|
||||
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
--font-heading: Georgia, "Times New Roman", serif;
|
||||
|
||||
--font-size-base: 1.125rem; /* 18px */
|
||||
--font-size-small: 0.875rem; /* 14px */
|
||||
--line-height-base: 1.6;
|
||||
--line-height-heading: 1.2;
|
||||
```
|
||||
|
||||
### Spacing
|
||||
|
||||
```css
|
||||
--spacing-unit: 1.5rem; /* Base spacing (24px) */
|
||||
--spacing-small: 0.75rem; /* 12px */
|
||||
--spacing-large: 3rem; /* 48px */
|
||||
```
|
||||
|
||||
### Layout
|
||||
|
||||
```css
|
||||
--max-width: 70rem; /* Content max-width */
|
||||
--border-radius: 4px; /* Corner rounding */
|
||||
```
|
||||
|
||||
## Adding Custom Fonts
|
||||
|
||||
### Step 1: Add Font Files
|
||||
|
||||
```bash
|
||||
mkdir -p custom/fonts
|
||||
# Copy your .woff2 files here
|
||||
cp ~/Downloads/MyFont-Regular.woff2 custom/fonts/
|
||||
cp ~/Downloads/MyFont-Bold.woff2 custom/fonts/
|
||||
```
|
||||
|
||||
### Step 2: Declare Font Faces
|
||||
|
||||
In `custom/styles/base.css`:
|
||||
|
||||
```css
|
||||
@font-face {
|
||||
font-family: 'MyFont';
|
||||
src: url('/custom/fonts/MyFont-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'MyFont';
|
||||
src: url('/custom/fonts/MyFont-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-body: 'MyFont', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Font files are automatically served by FolderWeb's static file handler.
|
||||
|
||||
## Page-Specific Styling
|
||||
|
||||
FolderWeb adds dynamic CSS classes to the `<body>` element:
|
||||
|
||||
```html
|
||||
<body class="section-blog page-2025-11-02-my-post">
|
||||
```
|
||||
|
||||
Use these for targeted styling:
|
||||
|
||||
```css
|
||||
/* Style all blog pages */
|
||||
.section-blog {
|
||||
--color-primary: oklch(0.60 0.15 150); /* Green for blog */
|
||||
}
|
||||
|
||||
/* Style a specific page */
|
||||
.page-about {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
/* Combine for precision */
|
||||
.section-docs.page-installation {
|
||||
background: var(--color-light);
|
||||
}
|
||||
```
|
||||
|
||||
## Responsive Design
|
||||
|
||||
FolderWeb uses modern CSS features for responsiveness. Use `clamp()` for fluid typography:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--font-size-base: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);
|
||||
--spacing-unit: clamp(1rem, 0.8rem + 1vw, 2rem);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2rem, 1.5rem + 2vw, 3.5rem);
|
||||
}
|
||||
```
|
||||
|
||||
Use container queries for component responsiveness:
|
||||
|
||||
```css
|
||||
.card-grid {
|
||||
container-type: inline-size;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: var(--spacing-unit);
|
||||
}
|
||||
|
||||
@container (min-width: 600px) {
|
||||
.card {
|
||||
padding: var(--spacing-large);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dark Mode
|
||||
|
||||
Add a dark mode using CSS custom properties and media queries:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--color-bg: oklch(0.97 0.01 250);
|
||||
--color-text: oklch(0.20 0 0);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-bg: oklch(0.20 0 0);
|
||||
--color-text: oklch(0.95 0 0);
|
||||
--color-light: oklch(0.25 0 0);
|
||||
--color-primary: oklch(0.70 0.15 250);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
```
|
||||
|
||||
## List Template Styling
|
||||
|
||||
Style the different list templates:
|
||||
|
||||
### Grid Layout
|
||||
|
||||
```css
|
||||
.list-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: var(--spacing-unit);
|
||||
}
|
||||
|
||||
.list-grid article {
|
||||
border: 1px solid var(--color-light);
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-grid img {
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
### Card Grid
|
||||
|
||||
```css
|
||||
.card-grid .card {
|
||||
background: var(--color-light);
|
||||
padding: var(--spacing-unit);
|
||||
border-radius: var(--border-radius);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.card-grid .card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
```
|
||||
|
||||
### FAQ Layout
|
||||
|
||||
```css
|
||||
.faq details {
|
||||
border: 1px solid var(--color-light);
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--spacing-unit);
|
||||
margin-block-end: var(--spacing-small);
|
||||
}
|
||||
|
||||
.faq summary {
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.faq summary:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
```
|
||||
|
||||
## Modern CSS Features
|
||||
|
||||
FolderWeb encourages use of modern CSS:
|
||||
|
||||
### CSS Nesting
|
||||
|
||||
```css
|
||||
.article {
|
||||
padding: var(--spacing-unit);
|
||||
|
||||
& h2 {
|
||||
color: var(--color-primary);
|
||||
margin-block-start: var(--spacing-large);
|
||||
}
|
||||
|
||||
& a {
|
||||
color: var(--color-secondary);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Logical Properties
|
||||
|
||||
Use logical properties for internationalization:
|
||||
|
||||
```css
|
||||
/* Instead of: margin-left, margin-right */
|
||||
article {
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--spacing-unit);
|
||||
padding-block: var(--spacing-large);
|
||||
}
|
||||
|
||||
/* Instead of: text-align: left */
|
||||
.content {
|
||||
text-align: start;
|
||||
}
|
||||
```
|
||||
|
||||
### Modern Color Functions
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* OKLCH: lightness, chroma, hue */
|
||||
--primary: oklch(0.65 0.15 250);
|
||||
|
||||
/* Adjust lightness for hover */
|
||||
--primary-hover: oklch(0.55 0.15 250);
|
||||
|
||||
/* Or use color-mix */
|
||||
--primary-light: color-mix(in oklch, var(--primary), white 20%);
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### Minimize Custom Styles
|
||||
|
||||
Override only what's necessary. The default stylesheet is already optimized.
|
||||
|
||||
### Use CSS Variables
|
||||
|
||||
Variables reduce repetition and improve maintainability:
|
||||
|
||||
```css
|
||||
/* Good */
|
||||
:root {
|
||||
--card-padding: var(--spacing-unit);
|
||||
}
|
||||
|
||||
.card { padding: var(--card-padding); }
|
||||
.box { padding: var(--card-padding); }
|
||||
|
||||
/* Less maintainable */
|
||||
.card { padding: 1.5rem; }
|
||||
.box { padding: 1.5rem; }
|
||||
```
|
||||
|
||||
### Avoid `!important`
|
||||
|
||||
FolderWeb uses low-specificity selectors, so you shouldn't need `!important`.
|
||||
|
||||
## Debugging Styles
|
||||
|
||||
### Check Which Stylesheet is Loaded
|
||||
|
||||
View source and look for:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/app/styles/base.css?v=abc123...">
|
||||
```
|
||||
|
||||
If you see `/app/styles/`, your custom stylesheet is being used.
|
||||
If you see `/app/default-styles/`, the default is being used.
|
||||
|
||||
### Browser DevTools
|
||||
|
||||
1. Right-click element → Inspect
|
||||
2. Check "Computed" tab to see which properties are applied
|
||||
3. Check "Sources" tab to verify your CSS file is loaded
|
||||
4. Use "Network" tab to ensure CSS isn't cached with old version
|
||||
|
||||
### Hard Refresh
|
||||
|
||||
Always do a hard refresh after CSS changes:
|
||||
- **Chrome/Firefox**: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac)
|
||||
- **Safari**: Cmd+Option+R
|
||||
|
||||
## Complete Override
|
||||
|
||||
If you want complete control, you can replace the entire stylesheet. Copy the default:
|
||||
|
||||
```bash
|
||||
cp app/default/styles/base.css custom/styles/base.css
|
||||
```
|
||||
|
||||
Then edit freely. Remember: you're responsible for all styles when you do this.
|
||||
|
||||
## Related
|
||||
|
||||
- [Custom Templates](custom-templates.md)
|
||||
- [CSS Reference](../reference/css-variables.md)
|
||||
- [File Structure Reference](../reference/file-structure.md)
|
||||
288
docs/how-to/custom-templates.md
Normal file
288
docs/how-to/custom-templates.md
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
# How to Create Custom Templates
|
||||
|
||||
This guide shows you how to override default templates with your own custom designs.
|
||||
|
||||
## Overview
|
||||
|
||||
FolderWeb uses a template fallback system:
|
||||
1. Check `/custom/templates/` for custom version
|
||||
2. Fall back to `/app/default/templates/` if not found
|
||||
|
||||
**Important**: Never modify files in `/app/default/` — always create custom versions in `/custom/`.
|
||||
|
||||
## Available Templates
|
||||
|
||||
- **base.php** - HTML wrapper (header, navigation, footer)
|
||||
- **page.php** - Single page/article wrapper
|
||||
- **list.php** - Simple list view (default)
|
||||
- **list-grid.php** - Grid layout with images
|
||||
- **list-card-grid.php** - Card grid (supports PDFs, external links)
|
||||
- **list-faq.php** - Expandable FAQ/Q&A format
|
||||
|
||||
## Customizing the Base Template
|
||||
|
||||
The base template controls your entire site layout.
|
||||
|
||||
### Step 1: Copy the Default
|
||||
|
||||
```bash
|
||||
mkdir -p custom/templates
|
||||
cp app/default/templates/base.php custom/templates/base.php
|
||||
```
|
||||
|
||||
### Step 2: Edit Your Copy
|
||||
|
||||
The base template has access to these variables:
|
||||
|
||||
```php
|
||||
$content // The rendered page content (HTML)
|
||||
$currentLang // Current language code (e.g., "en", "no")
|
||||
$navigation // Array of navigation items
|
||||
$homeLabel // Site title
|
||||
$translations // Translation strings
|
||||
$pageTitle // Current page title
|
||||
$dirName // Parent directory name (for CSS classes)
|
||||
$pageName // Current page name (for CSS classes)
|
||||
```
|
||||
|
||||
### Example: Add a Custom Header
|
||||
|
||||
```php
|
||||
<header class="site-header">
|
||||
<div class="contain">
|
||||
<a href="<?= $ctx->langPrefix ?>/" class="logo">
|
||||
<img src="/custom/assets/logo.svg" alt="<?= $homeLabel ?>">
|
||||
</a>
|
||||
<nav>
|
||||
<ul>
|
||||
<?php foreach ($navigation as $item): ?>
|
||||
<li>
|
||||
<a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
```
|
||||
|
||||
## Customizing the Page Template
|
||||
|
||||
The page template wraps individual articles and pages.
|
||||
|
||||
### Step 1: Copy the Default
|
||||
|
||||
```bash
|
||||
cp app/default/templates/page.php custom/templates/page.php
|
||||
```
|
||||
|
||||
### Step 2: Customize
|
||||
|
||||
Available variables:
|
||||
|
||||
```php
|
||||
$content // Main content HTML
|
||||
$pageMetadata // Array of metadata (tags, categories, etc.)
|
||||
$translations // Translation strings
|
||||
```
|
||||
|
||||
### Example: Add Author Information
|
||||
|
||||
```php
|
||||
<article>
|
||||
<?php if (isset($pageMetadata['author'])): ?>
|
||||
<div class="author-info">
|
||||
<p>Written by <?= htmlspecialchars($pageMetadata['author']) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= $content ?>
|
||||
|
||||
<?php if (isset($pageMetadata['tags'])): ?>
|
||||
<div class="tags">
|
||||
<strong><?= $translations['tags'] ?>:</strong>
|
||||
<?php foreach ($pageMetadata['tags'] as $tag): ?>
|
||||
<span class="tag"><?= htmlspecialchars($tag) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
```
|
||||
|
||||
## Creating a Custom List Template
|
||||
|
||||
List templates control how directories with subdirectories are displayed.
|
||||
|
||||
### Step 1: Create Your Template
|
||||
|
||||
```bash
|
||||
touch custom/templates/list-custom.php
|
||||
```
|
||||
|
||||
### Step 2: Use List Template Variables
|
||||
|
||||
All list templates receive:
|
||||
|
||||
```php
|
||||
$items // Array of subdirectories
|
||||
$metadata // Directory metadata
|
||||
$pageContent // Optional intro content
|
||||
$translations // Translation strings
|
||||
```
|
||||
|
||||
Each item in `$items` has:
|
||||
|
||||
```php
|
||||
[
|
||||
'title' => 'Post Title',
|
||||
'date' => '2. november 2025',
|
||||
'url' => '/blog/2025-11-02-post/',
|
||||
'cover' => '/blog/2025-11-02-post/cover.jpg',
|
||||
'summary' => 'Brief description',
|
||||
'pdf' => '/blog/2025-11-02-post/document.pdf',
|
||||
'redirect' => 'https://external-site.com'
|
||||
]
|
||||
```
|
||||
|
||||
### Example: Timeline Template
|
||||
|
||||
```php
|
||||
<?php if (!empty($pageContent)): ?>
|
||||
<div class="page-intro">
|
||||
<?= $pageContent ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="timeline">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<article class="timeline-item">
|
||||
<time><?= $item['date'] ?></time>
|
||||
<h2>
|
||||
<a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a>
|
||||
</h2>
|
||||
<?php if ($item['summary']): ?>
|
||||
<p><?= htmlspecialchars($item['summary']) ?></p>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Step 3: Apply Your Template
|
||||
|
||||
Create a `metadata.ini` in the directory:
|
||||
|
||||
```ini
|
||||
page_template = "list-custom"
|
||||
```
|
||||
|
||||
## Template Best Practices
|
||||
|
||||
### Always Escape Output
|
||||
|
||||
Prevent XSS attacks by escaping user-generated content:
|
||||
|
||||
```php
|
||||
<?= htmlspecialchars($item['title']) ?>
|
||||
```
|
||||
|
||||
### Use Short Echo Tags
|
||||
|
||||
FolderWeb uses modern PHP, so short tags are always available:
|
||||
|
||||
```php
|
||||
<?= $variable ?> // Good
|
||||
<?php echo $variable; ?> // Also works, but verbose
|
||||
```
|
||||
|
||||
### Check Before Using
|
||||
|
||||
Always check if variables exist:
|
||||
|
||||
```php
|
||||
<?php if (isset($item['cover']) && $item['cover']): ?>
|
||||
<img src="<?= $item['cover'] ?>" alt="">
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
### Leverage CSS Classes
|
||||
|
||||
The base template adds dynamic classes to `<body>`:
|
||||
|
||||
```php
|
||||
<body class="section-<?= $dirName ?> page-<?= $pageName ?>">
|
||||
```
|
||||
|
||||
Use these for page-specific styling without JavaScript.
|
||||
|
||||
## Advanced: Accessing the Context Object
|
||||
|
||||
Templates can access the full context object `$ctx`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Available properties:
|
||||
$ctx->contentDir // Path to content directory
|
||||
$ctx->currentLang // Current language
|
||||
$ctx->defaultLang // Default language
|
||||
$ctx->availableLangs // Array of available languages
|
||||
$ctx->langPrefix // URL prefix (e.g., "/en" or "")
|
||||
$ctx->requestPath // Current request path
|
||||
$ctx->hasTrailingSlash // Boolean
|
||||
$ctx->navigation // Navigation array (computed property)
|
||||
$ctx->homeLabel // Site title (computed property)
|
||||
$ctx->translations // Translation array (computed property)
|
||||
?>
|
||||
```
|
||||
|
||||
## Example: Breadcrumb Navigation
|
||||
|
||||
Add breadcrumbs to your page template:
|
||||
|
||||
```php
|
||||
<nav class="breadcrumbs" aria-label="Breadcrumb">
|
||||
<ol>
|
||||
<li><a href="<?= $ctx->langPrefix ?>/">Home</a></li>
|
||||
<?php
|
||||
$parts = array_filter(explode('/', trim($ctx->requestPath, '/')));
|
||||
$path = '';
|
||||
foreach ($parts as $i => $part):
|
||||
$path .= '/' . $part;
|
||||
$isLast = ($i === count($parts) - 1);
|
||||
?>
|
||||
<li<?= $isLast ? ' aria-current="page"' : '' ?>>
|
||||
<?php if ($isLast): ?>
|
||||
<?= htmlspecialchars($part) ?>
|
||||
<?php else: ?>
|
||||
<a href="<?= $ctx->langPrefix . $path ?>/">
|
||||
<?= htmlspecialchars($part) ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
</nav>
|
||||
```
|
||||
|
||||
## Testing Your Templates
|
||||
|
||||
1. Clear your browser cache
|
||||
2. Reload the page
|
||||
3. Check browser console for errors
|
||||
4. Validate HTML with W3C validator
|
||||
|
||||
## Reverting Changes
|
||||
|
||||
To revert to default templates, simply delete your custom version:
|
||||
|
||||
```bash
|
||||
rm custom/templates/base.php
|
||||
```
|
||||
|
||||
FolderWeb will automatically fall back to the default.
|
||||
|
||||
## Related
|
||||
|
||||
- [Customizing Styles](custom-styles.md)
|
||||
- [Template Reference](../reference/templates.md)
|
||||
- [Metadata Reference](../reference/metadata.md)
|
||||
425
docs/how-to/multi-language.md
Normal file
425
docs/how-to/multi-language.md
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
# How to Create a Multi-Language Site
|
||||
|
||||
This guide shows you how to set up and manage a multi-language website with FolderWeb.
|
||||
|
||||
## Overview
|
||||
|
||||
FolderWeb supports multiple languages through:
|
||||
- Language prefixes in URLs
|
||||
- Language-specific content files
|
||||
- Translated slugs and metadata
|
||||
- Translation files for UI strings
|
||||
|
||||
## Configuration
|
||||
|
||||
### Step 1: Configure Available Languages
|
||||
|
||||
Create or edit `custom/config.ini`:
|
||||
|
||||
```ini
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "en,no,fr"
|
||||
```
|
||||
|
||||
- **default**: The primary language (no URL prefix)
|
||||
- **available**: Comma-separated list of all supported languages
|
||||
|
||||
### Step 2: Create Translation Files
|
||||
|
||||
Create translation files for each language in `custom/languages/`:
|
||||
|
||||
```bash
|
||||
mkdir -p custom/languages
|
||||
```
|
||||
|
||||
**English** (`custom/languages/en.ini`):
|
||||
```ini
|
||||
home = "Home"
|
||||
read_more = "Read more"
|
||||
categories = "Categories"
|
||||
tags = "Tags"
|
||||
footer_text = "Made with FolderWeb"
|
||||
footer_handcoded = "Generated in"
|
||||
footer_page_time = "ms"
|
||||
```
|
||||
|
||||
**Norwegian** (`custom/languages/no.ini`):
|
||||
```ini
|
||||
home = "Hjem"
|
||||
read_more = "Les mer"
|
||||
categories = "Kategorier"
|
||||
tags = "Stikkord"
|
||||
footer_text = "Laget med FolderWeb"
|
||||
footer_handcoded = "Generert på"
|
||||
footer_page_time = "ms"
|
||||
```
|
||||
|
||||
**French** (`custom/languages/fr.ini`):
|
||||
```ini
|
||||
home = "Accueil"
|
||||
read_more = "Lire la suite"
|
||||
categories = "Catégories"
|
||||
tags = "Étiquettes"
|
||||
footer_text = "Créé avec FolderWeb"
|
||||
footer_handcoded = "Généré en"
|
||||
footer_page_time = "ms"
|
||||
```
|
||||
|
||||
## URL Structure
|
||||
|
||||
With the configuration above:
|
||||
|
||||
- **English** (default): `yoursite.com/about/`
|
||||
- **Norwegian**: `yoursite.com/no/about/`
|
||||
- **French**: `yoursite.com/fr/about/`
|
||||
|
||||
The default language never has a URL prefix.
|
||||
|
||||
## Creating Language-Specific Content
|
||||
|
||||
### Method 1: Separate Files Per Language
|
||||
|
||||
Use language suffixes in filenames: `filename.{lang}.ext`
|
||||
|
||||
**Example structure**:
|
||||
```
|
||||
content/about/
|
||||
├── index.md # Default language (English)
|
||||
├── index.no.md # Norwegian version
|
||||
└── index.fr.md # French version
|
||||
```
|
||||
|
||||
**Rules**:
|
||||
- Language-specific files (`.lang.ext`) show only for that language
|
||||
- Default files (no language suffix) show only if no language variant exists
|
||||
- Files are automatically filtered based on current language
|
||||
|
||||
### Example Content
|
||||
|
||||
**content/about/index.md** (English):
|
||||
```markdown
|
||||
# About Us
|
||||
|
||||
We are a company dedicated to simplicity.
|
||||
```
|
||||
|
||||
**content/about/index.no.md** (Norwegian):
|
||||
```markdown
|
||||
# Om Oss
|
||||
|
||||
Vi er et selskap dedikert til enkelhet.
|
||||
```
|
||||
|
||||
**content/about/index.fr.md** (French):
|
||||
```markdown
|
||||
# À Propos
|
||||
|
||||
Nous sommes une entreprise dédiée à la simplicité.
|
||||
```
|
||||
|
||||
Now when users visit:
|
||||
- `/about/` → Shows English (index.md)
|
||||
- `/no/about/` → Shows Norwegian (index.no.md)
|
||||
- `/fr/about/` → Shows French (index.fr.md)
|
||||
|
||||
### Method 2: Language-Specific Folders
|
||||
|
||||
For blog posts and articles, you can create separate folders:
|
||||
|
||||
```
|
||||
content/blog/
|
||||
├── 2025-11-01-english-post/
|
||||
│ └── index.md
|
||||
├── 2025-11-01-norsk-innlegg/
|
||||
│ └── index.no.md
|
||||
└── 2025-11-01-article-francais/
|
||||
└── index.fr.md
|
||||
```
|
||||
|
||||
## Translated Slugs and Titles
|
||||
|
||||
Use `metadata.ini` to provide translated slugs and metadata:
|
||||
|
||||
**content/about/metadata.ini**:
|
||||
```ini
|
||||
; Default (English)
|
||||
title = "About Us"
|
||||
slug = "about"
|
||||
|
||||
[no]
|
||||
title = "Om Oss"
|
||||
slug = "om-oss"
|
||||
|
||||
[fr]
|
||||
title = "À Propos"
|
||||
slug = "a-propos"
|
||||
```
|
||||
|
||||
Now URLs become:
|
||||
- English: `/about/`
|
||||
- Norwegian: `/no/om-oss/`
|
||||
- French: `/fr/a-propos/`
|
||||
|
||||
The actual folder is still named `about/`, but FolderWeb maps the translated slug to the real folder.
|
||||
|
||||
## Blog Posts with Translations
|
||||
|
||||
**Structure**:
|
||||
```
|
||||
content/blog/
|
||||
└── 2025-11-02-my-post/
|
||||
├── index.md
|
||||
├── index.no.md
|
||||
├── index.fr.md
|
||||
├── cover.jpg
|
||||
└── metadata.ini
|
||||
```
|
||||
|
||||
**metadata.ini**:
|
||||
```ini
|
||||
; Default language
|
||||
title = "My First Post"
|
||||
summary = "An introduction to multilingual blogging."
|
||||
date = "2025-11-02"
|
||||
|
||||
[no]
|
||||
title = "Mitt Første Innlegg"
|
||||
summary = "En introduksjon til flerspråklig blogging."
|
||||
|
||||
[fr]
|
||||
title = "Mon Premier Article"
|
||||
summary = "Une introduction au blogging multilingue."
|
||||
```
|
||||
|
||||
**Important**: Date is global, cover image is shared across languages.
|
||||
|
||||
## Navigation and Menus
|
||||
|
||||
Navigation is automatically built with translations. In `metadata.ini` for each top-level directory:
|
||||
|
||||
**content/blog/metadata.ini**:
|
||||
```ini
|
||||
menu = true
|
||||
menu_order = 1
|
||||
|
||||
title = "Blog"
|
||||
[no]
|
||||
title = "Blogg"
|
||||
[fr]
|
||||
title = "Blog"
|
||||
```
|
||||
|
||||
**content/about/metadata.ini**:
|
||||
```ini
|
||||
menu = true
|
||||
menu_order = 2
|
||||
|
||||
title = "About"
|
||||
[no]
|
||||
title = "Om"
|
||||
[fr]
|
||||
title = "À Propos"
|
||||
```
|
||||
|
||||
Navigation automatically includes language prefix in URLs.
|
||||
|
||||
## Using Translations in Templates
|
||||
|
||||
### In Default Templates
|
||||
|
||||
Translations are automatically available as `$translations` array:
|
||||
|
||||
```php
|
||||
<a href="<?= $ctx->langPrefix ?>/">
|
||||
<?= $translations['home'] ?>
|
||||
</a>
|
||||
|
||||
<p><?= $translations['footer_text'] ?></p>
|
||||
```
|
||||
|
||||
### In Custom Templates
|
||||
|
||||
Access translations the same way:
|
||||
|
||||
```php
|
||||
<button><?= $translations['read_more'] ?></button>
|
||||
```
|
||||
|
||||
### Access Current Language
|
||||
|
||||
```php
|
||||
<?php if ($ctx->currentLang === 'no'): ?>
|
||||
<p>Dette er norsk innhold.</p>
|
||||
<?php else: ?>
|
||||
<p>This is English content.</p>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
## Language Switcher
|
||||
|
||||
Create a language switcher in your custom base template:
|
||||
|
||||
```php
|
||||
<nav class="language-switcher">
|
||||
<?php foreach ($ctx->availableLangs as $lang): ?>
|
||||
<?php
|
||||
$url = $lang === $ctx->defaultLang
|
||||
? '/' . trim($ctx->requestPath, '/')
|
||||
: '/' . $lang . '/' . trim($ctx->requestPath, '/');
|
||||
$current = $lang === $ctx->currentLang;
|
||||
?>
|
||||
<a href="<?= $url ?>"
|
||||
<?= $current ? 'aria-current="true"' : '' ?>
|
||||
class="<?= $current ? 'active' : '' ?>">
|
||||
<?= strtoupper($lang) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
```
|
||||
|
||||
Style it:
|
||||
|
||||
```css
|
||||
.language-switcher {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.language-switcher a {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: var(--border-radius);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.language-switcher a.active {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
## List Views with Multiple Languages
|
||||
|
||||
When displaying blog listings, FolderWeb automatically filters items by language:
|
||||
|
||||
```
|
||||
content/blog/
|
||||
├── 2025-11-01-english-article/
|
||||
│ └── index.md # Shows in English
|
||||
├── 2025-11-02-norsk-artikkel/
|
||||
│ └── index.no.md # Shows only in Norwegian
|
||||
└── 2025-11-03-universal/
|
||||
├── index.md # Shows in English
|
||||
├── index.no.md # Shows in Norwegian
|
||||
└── index.fr.md # Shows in French
|
||||
```
|
||||
|
||||
When viewing `/blog/`:
|
||||
- Shows "english-article" and "universal"
|
||||
|
||||
When viewing `/no/blog/`:
|
||||
- Shows "norsk-artikkel" and "universal"
|
||||
|
||||
When viewing `/fr/blog/`:
|
||||
- Shows only "universal"
|
||||
|
||||
## Handling Missing Translations
|
||||
|
||||
### Default Fallback
|
||||
|
||||
If a translation is missing, FolderWeb uses the default language automatically.
|
||||
|
||||
### Show Different Content
|
||||
|
||||
You can use PHP in your content files:
|
||||
|
||||
```php
|
||||
<?php if ($ctx->currentLang === 'en'): ?>
|
||||
# Welcome
|
||||
This page is only in English.
|
||||
<?php else: ?>
|
||||
# Under Construction
|
||||
This page is not yet translated.
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
## SEO Considerations
|
||||
|
||||
### Add hreflang Tags
|
||||
|
||||
In your custom base template:
|
||||
|
||||
```php
|
||||
<head>
|
||||
<!-- ... other head content ... -->
|
||||
|
||||
<?php foreach ($ctx->availableLangs as $lang): ?>
|
||||
<?php
|
||||
$url = $lang === $ctx->defaultLang
|
||||
? 'https://yoursite.com/' . trim($ctx->requestPath, '/')
|
||||
: 'https://yoursite.com/' . $lang . '/' . trim($ctx->requestPath, '/');
|
||||
?>
|
||||
<link rel="alternate" hreflang="<?= $lang ?>" href="<?= $url ?>">
|
||||
<?php endforeach; ?>
|
||||
|
||||
<link rel="alternate" hreflang="x-default"
|
||||
href="https://yoursite.com/<?= trim($ctx->requestPath, '/') ?>">
|
||||
</head>
|
||||
```
|
||||
|
||||
### Language-Specific Metadata
|
||||
|
||||
Add language attributes:
|
||||
|
||||
```php
|
||||
<html lang="<?= $ctx->currentLang ?>">
|
||||
```
|
||||
|
||||
## Testing Your Multi-Language Site
|
||||
|
||||
1. **Visit default language**: `http://localhost:8000/about/`
|
||||
2. **Visit Norwegian**: `http://localhost:8000/no/about/`
|
||||
3. **Visit French**: `http://localhost:8000/fr/about/`
|
||||
4. **Check navigation**: Ensure links include language prefix
|
||||
5. **Test translation strings**: Verify UI text changes per language
|
||||
6. **Check blog listings**: Confirm language-specific posts appear correctly
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Blog in Multiple Languages
|
||||
|
||||
Structure:
|
||||
```
|
||||
content/blog/
|
||||
├── metadata.ini # List template config
|
||||
└── [date]-[slug]/
|
||||
├── index.{lang}.md # One file per language
|
||||
├── cover.jpg # Shared assets
|
||||
└── metadata.ini # Translated metadata
|
||||
```
|
||||
|
||||
### Documentation in Multiple Languages
|
||||
|
||||
Structure:
|
||||
```
|
||||
content/docs/
|
||||
├── metadata.ini # Template config
|
||||
├── 00-intro.md # Default language
|
||||
├── 00-intro.no.md # Norwegian
|
||||
├── 01-setup.md
|
||||
├── 01-setup.no.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Mixed Content Strategy
|
||||
|
||||
Not everything needs translation. You can have:
|
||||
- English-only blog posts (no language suffix)
|
||||
- Multi-language main pages (with language suffixes)
|
||||
- Shared images and assets
|
||||
|
||||
## Related
|
||||
|
||||
- [Metadata Reference](../reference/metadata.md)
|
||||
- [Configuration Reference](../reference/configuration.md)
|
||||
- [Template Variables Reference](../reference/templates.md)
|
||||
481
docs/how-to/working-with-metadata.md
Normal file
481
docs/how-to/working-with-metadata.md
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
# How to Work with Metadata
|
||||
|
||||
This guide shows you how to use `metadata.ini` files to control page behavior, appearance, and content.
|
||||
|
||||
## What is Metadata?
|
||||
|
||||
Metadata provides structured information about your content directories without cluttering your content files. It's stored in `metadata.ini` files using the INI format.
|
||||
|
||||
## Basic Metadata File
|
||||
|
||||
Create `metadata.ini` in any content directory:
|
||||
|
||||
```ini
|
||||
title = "My Page Title"
|
||||
date = "2025-11-02"
|
||||
summary = "A brief description of this page."
|
||||
```
|
||||
|
||||
## Common Metadata Fields
|
||||
|
||||
### Title
|
||||
|
||||
Controls the displayed title (overrides automatic title extraction):
|
||||
|
||||
```ini
|
||||
title = "Custom Page Title"
|
||||
```
|
||||
|
||||
If not provided, FolderWeb extracts the title from:
|
||||
1. First H1 heading in content (`# Title` in Markdown)
|
||||
2. Folder name (as fallback)
|
||||
|
||||
### Date
|
||||
|
||||
Set an explicit date (overrides folder name date extraction):
|
||||
|
||||
```ini
|
||||
date = "2025-11-02"
|
||||
```
|
||||
|
||||
Format: `YYYY-MM-DD`
|
||||
|
||||
FolderWeb automatically formats this in Norwegian style: "2. november 2025"
|
||||
|
||||
### Summary
|
||||
|
||||
Add a summary for list views:
|
||||
|
||||
```ini
|
||||
summary = "This appears in blog listings and card grids."
|
||||
```
|
||||
|
||||
Summaries are displayed in:
|
||||
- List views
|
||||
- Grid layouts
|
||||
- Card grids
|
||||
|
||||
## Navigation Control
|
||||
|
||||
### Adding to Menu
|
||||
|
||||
```ini
|
||||
menu = true
|
||||
menu_order = 1
|
||||
```
|
||||
|
||||
- **menu**: Set to `true` to include in site navigation
|
||||
- **menu_order**: Controls order (lower numbers appear first)
|
||||
|
||||
**Example** - Setting up main navigation:
|
||||
|
||||
**content/blog/metadata.ini**:
|
||||
```ini
|
||||
menu = true
|
||||
menu_order = 1
|
||||
title = "Blog"
|
||||
```
|
||||
|
||||
**content/about/metadata.ini**:
|
||||
```ini
|
||||
menu = true
|
||||
menu_order = 2
|
||||
title = "About"
|
||||
```
|
||||
|
||||
**content/contact/metadata.ini**:
|
||||
```ini
|
||||
menu = true
|
||||
menu_order = 3
|
||||
title = "Contact"
|
||||
```
|
||||
|
||||
Result: Navigation shows "Blog", "About", "Contact" in that order.
|
||||
|
||||
## Template Control
|
||||
|
||||
### Choosing List Template
|
||||
|
||||
For directories with subdirectories, control which list template is used:
|
||||
|
||||
```ini
|
||||
page_template = "list-grid"
|
||||
```
|
||||
|
||||
Available templates:
|
||||
- `list` - Simple list (default)
|
||||
- `list-grid` - Grid with cover images
|
||||
- `list-card-grid` - Card-style grid (supports PDFs, external links)
|
||||
- `list-faq` - Expandable FAQ format
|
||||
|
||||
**Example** - Blog with grid layout:
|
||||
|
||||
**content/blog/metadata.ini**:
|
||||
```ini
|
||||
title = "Blog"
|
||||
page_template = "list-grid"
|
||||
```
|
||||
|
||||
## External Redirects
|
||||
|
||||
Make a directory item link externally (used with `list-card-grid`):
|
||||
|
||||
```ini
|
||||
redirect = "https://example.com"
|
||||
```
|
||||
|
||||
**Example** - Portfolio with external links:
|
||||
|
||||
**content/portfolio/project-live-site/metadata.ini**:
|
||||
```ini
|
||||
title = "Visit Live Site"
|
||||
summary = "Check out the deployed project."
|
||||
redirect = "https://myproject.com"
|
||||
```
|
||||
|
||||
When using the `list-card-grid` template, this creates a card that links to the external URL instead of an internal page.
|
||||
|
||||
## Multi-Language Metadata
|
||||
|
||||
Use sections for language-specific overrides:
|
||||
|
||||
```ini
|
||||
; Default language values
|
||||
title = "About Us"
|
||||
summary = "Learn more about our company."
|
||||
|
||||
[no]
|
||||
title = "Om Oss"
|
||||
summary = "Lær mer om vårt selskap."
|
||||
slug = "om-oss"
|
||||
|
||||
[fr]
|
||||
title = "À Propos"
|
||||
summary = "Découvrez notre entreprise."
|
||||
slug = "a-propos"
|
||||
```
|
||||
|
||||
Language sections override base values for that language.
|
||||
|
||||
### Translated Slugs
|
||||
|
||||
The `slug` field in language sections changes the URL:
|
||||
|
||||
```ini
|
||||
[no]
|
||||
slug = "om-oss"
|
||||
```
|
||||
|
||||
Now the Norwegian version is accessible at `/no/om-oss/` instead of `/no/about/`.
|
||||
|
||||
## Custom Metadata Fields
|
||||
|
||||
You can add any custom fields you need:
|
||||
|
||||
```ini
|
||||
title = "Article Title"
|
||||
author = "Jane Doe"
|
||||
reading_time = "5 min"
|
||||
difficulty = "intermediate"
|
||||
featured = true
|
||||
```
|
||||
|
||||
Access these in custom templates:
|
||||
|
||||
```php
|
||||
<?php if (isset($metadata['author'])): ?>
|
||||
<p class="author">By <?= htmlspecialchars($metadata['author']) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($metadata['reading_time'])): ?>
|
||||
<span class="reading-time"><?= htmlspecialchars($metadata['reading_time']) ?></span>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
## Arrays in Metadata
|
||||
|
||||
INI format supports arrays using repeated keys:
|
||||
|
||||
```ini
|
||||
tags[] = "PHP"
|
||||
tags[] = "Web Development"
|
||||
tags[] = "Tutorial"
|
||||
|
||||
categories[] = "Programming"
|
||||
categories[] = "Backend"
|
||||
```
|
||||
|
||||
Access in templates:
|
||||
|
||||
```php
|
||||
<?php if (!empty($metadata['tags'])): ?>
|
||||
<div class="tags">
|
||||
<?php foreach ($metadata['tags'] as $tag): ?>
|
||||
<span class="tag"><?= htmlspecialchars($tag) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
## Boolean Values
|
||||
|
||||
Use `true`, `false`, `1`, `0`, `yes`, `no`, `on`, `off`:
|
||||
|
||||
```ini
|
||||
menu = true
|
||||
featured = yes
|
||||
draft = false
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Add comments with `;` or `#`:
|
||||
|
||||
```ini
|
||||
; This is a comment
|
||||
title = "My Page"
|
||||
|
||||
# This is also a comment
|
||||
date = "2025-11-02"
|
||||
```
|
||||
|
||||
## Metadata Inheritance
|
||||
|
||||
Metadata does **not** inherit from parent directories. Each directory needs its own `metadata.ini`.
|
||||
|
||||
## Metadata for List Items
|
||||
|
||||
When a directory is displayed in a list view, FolderWeb loads its metadata:
|
||||
|
||||
**content/blog/2025-11-01-first-post/metadata.ini**:
|
||||
```ini
|
||||
title = "My First Blog Post"
|
||||
date = "2025-11-01"
|
||||
summary = "An introduction to blogging with FolderWeb."
|
||||
```
|
||||
|
||||
This metadata appears in the blog listing at `/blog/`.
|
||||
|
||||
## Complete Example: Blog Setup
|
||||
|
||||
### Blog Directory Metadata
|
||||
|
||||
**content/blog/metadata.ini**:
|
||||
```ini
|
||||
title = "Blog"
|
||||
menu = true
|
||||
menu_order = 1
|
||||
page_template = "list-grid"
|
||||
|
||||
[no]
|
||||
title = "Blogg"
|
||||
slug = "blogg"
|
||||
```
|
||||
|
||||
### Individual Post Metadata
|
||||
|
||||
**content/blog/2025-11-02-web-performance/metadata.ini**:
|
||||
```ini
|
||||
title = "Optimizing Web Performance"
|
||||
date = "2025-11-02"
|
||||
summary = "Learn techniques to make your website faster."
|
||||
author = "Jane Developer"
|
||||
reading_time = "8 min"
|
||||
|
||||
tags[] = "Performance"
|
||||
tags[] = "Web Development"
|
||||
tags[] = "Optimization"
|
||||
|
||||
categories[] = "Technical"
|
||||
categories[] = "Tutorial"
|
||||
|
||||
[no]
|
||||
title = "Optimalisering av Nettsideytelse"
|
||||
summary = "Lær teknikker for å gjøre nettsiden din raskere."
|
||||
```
|
||||
|
||||
## Accessing Metadata in Templates
|
||||
|
||||
### In Page Templates
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Page-specific metadata
|
||||
$metadata = $pageMetadata;
|
||||
|
||||
// Access fields
|
||||
$author = $metadata['author'] ?? 'Unknown';
|
||||
$tags = $metadata['tags'] ?? [];
|
||||
?>
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<h1><?= htmlspecialchars($metadata['title'] ?? '') ?></h1>
|
||||
<p class="author">By <?= htmlspecialchars($author) ?></p>
|
||||
</header>
|
||||
|
||||
<?= $content ?>
|
||||
|
||||
<?php if (!empty($tags)): ?>
|
||||
<footer class="tags">
|
||||
<?php foreach ($tags as $tag): ?>
|
||||
<span><?= htmlspecialchars($tag) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</footer>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
```
|
||||
|
||||
### In List Templates
|
||||
|
||||
Each item in `$items` includes its metadata:
|
||||
|
||||
```php
|
||||
<?php foreach ($items as $item): ?>
|
||||
<article>
|
||||
<h2>
|
||||
<a href="<?= $item['url'] ?>">
|
||||
<?= htmlspecialchars($item['title']) ?>
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<?php if ($item['date']): ?>
|
||||
<time><?= $item['date'] ?></time>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item['summary']): ?>
|
||||
<p><?= htmlspecialchars($item['summary']) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item['cover']): ?>
|
||||
<img src="<?= $item['cover'] ?>" alt="">
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item['pdf']): ?>
|
||||
<a href="<?= $item['pdf'] ?>" download>Download PDF</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item['redirect']): ?>
|
||||
<a href="<?= $item['redirect'] ?>" target="_blank" rel="noopener">
|
||||
Visit External Link
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
```
|
||||
|
||||
## Debugging Metadata
|
||||
|
||||
### Check if Metadata is Loaded
|
||||
|
||||
In your template, dump the metadata:
|
||||
|
||||
```php
|
||||
<pre><?php var_dump($metadata); ?></pre>
|
||||
```
|
||||
|
||||
Or for list items:
|
||||
|
||||
```php
|
||||
<pre><?php var_dump($items); ?></pre>
|
||||
```
|
||||
|
||||
### Verify INI Syntax
|
||||
|
||||
Use PHP to test your INI file:
|
||||
|
||||
```bash
|
||||
php -r "print_r(parse_ini_file('content/blog/metadata.ini', true));"
|
||||
```
|
||||
|
||||
This shows parsed values and helps identify syntax errors.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Use Consistent Field Names
|
||||
|
||||
Stick to standard fields for common data:
|
||||
- `title` for titles
|
||||
- `date` for dates
|
||||
- `summary` for summaries
|
||||
- `author` for authors
|
||||
|
||||
### Escape Output
|
||||
|
||||
Always escape metadata in templates:
|
||||
|
||||
```php
|
||||
<?= htmlspecialchars($metadata['title']) ?>
|
||||
```
|
||||
|
||||
### Provide Defaults
|
||||
|
||||
Use null coalescing for missing fields:
|
||||
|
||||
```php
|
||||
$author = $metadata['author'] ?? 'Anonymous';
|
||||
$date = $metadata['date'] ?? 'Unknown date';
|
||||
```
|
||||
|
||||
### Keep It Simple
|
||||
|
||||
Only add metadata fields you actually use. Don't over-engineer.
|
||||
|
||||
### Use Comments
|
||||
|
||||
Document non-obvious metadata:
|
||||
|
||||
```ini
|
||||
; Featured articles appear at the top of the homepage
|
||||
featured = true
|
||||
|
||||
; Legacy field kept for backwards compatibility
|
||||
old_url = "/blog/old-slug/"
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Blog Post
|
||||
|
||||
```ini
|
||||
title = "Post Title"
|
||||
date = "2025-11-02"
|
||||
summary = "Brief description"
|
||||
author = "Author Name"
|
||||
tags[] = "tag1"
|
||||
tags[] = "tag2"
|
||||
```
|
||||
|
||||
### Documentation Page
|
||||
|
||||
```ini
|
||||
title = "API Reference"
|
||||
menu = true
|
||||
menu_order = 3
|
||||
page_template = "list"
|
||||
```
|
||||
|
||||
### Portfolio Item
|
||||
|
||||
```ini
|
||||
title = "Project Name"
|
||||
date = "2025-11-02"
|
||||
summary = "Project description"
|
||||
redirect = "https://live-demo.com"
|
||||
```
|
||||
|
||||
### FAQ Section
|
||||
|
||||
```ini
|
||||
title = "Frequently Asked Questions"
|
||||
menu = true
|
||||
menu_order = 4
|
||||
page_template = "list-faq"
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Multi-Language Guide](multi-language.md)
|
||||
- [Custom Templates](custom-templates.md)
|
||||
- [Metadata Reference](../reference/metadata.md)
|
||||
- [Template Variables Reference](../reference/templates.md)
|
||||
Loading…
Add table
Add a link
Reference in a new issue