Add docs
This commit is contained in:
parent
b97b2f5503
commit
ad516600bb
14 changed files with 6093 additions and 0 deletions
400
docs/reference/configuration.md
Normal file
400
docs/reference/configuration.md
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
# Configuration Reference
|
||||
|
||||
Complete reference for FolderWeb configuration options.
|
||||
|
||||
## Configuration File
|
||||
|
||||
**Location**: `/custom/config.ini` (or `/app/config.ini` for defaults)
|
||||
|
||||
**Format**: INI format with sections
|
||||
|
||||
**Override Behavior**: Custom config values override defaults
|
||||
|
||||
## Configuration Sections
|
||||
|
||||
### [languages]
|
||||
|
||||
Controls language support and defaults.
|
||||
|
||||
```ini
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "en,no,fr"
|
||||
```
|
||||
|
||||
#### default
|
||||
|
||||
**Type**: String
|
||||
**Required**: Yes
|
||||
**Default**: `"en"`
|
||||
|
||||
The primary language of your site. This language:
|
||||
- Has no URL prefix
|
||||
- Is used as fallback for missing translations
|
||||
- Should match a translation file in `/custom/languages/` or `/app/default/languages/`
|
||||
|
||||
**Examples**:
|
||||
```ini
|
||||
default = "en" ; English
|
||||
default = "no" ; Norwegian
|
||||
default = "fr" ; French
|
||||
default = "de" ; German
|
||||
```
|
||||
|
||||
#### available
|
||||
|
||||
**Type**: Comma-separated string
|
||||
**Required**: Yes
|
||||
**Default**: `"en,no"`
|
||||
|
||||
List of all languages supported by your site. Must include the default language.
|
||||
|
||||
**Examples**:
|
||||
```ini
|
||||
; English only
|
||||
available = "en"
|
||||
|
||||
; English and Norwegian
|
||||
available = "en,no"
|
||||
|
||||
; Multiple languages
|
||||
available = "en,no,fr,de,es"
|
||||
```
|
||||
|
||||
**URL Structure**:
|
||||
- Default language: `yoursite.com/page/`
|
||||
- Other languages: `yoursite.com/fr/page/`, `yoursite.com/de/page/`
|
||||
|
||||
## File Structure
|
||||
|
||||
### Default Configuration
|
||||
|
||||
**Location**: `/app/config.ini`
|
||||
|
||||
```ini
|
||||
[languages]
|
||||
default = "no"
|
||||
available = "no,en"
|
||||
```
|
||||
|
||||
**Note**: Never modify `/app/config.ini` directly.
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
**Location**: `/custom/config.ini`
|
||||
|
||||
Create this file to override defaults:
|
||||
|
||||
```ini
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "en,fr,de"
|
||||
```
|
||||
|
||||
Only include settings you want to override.
|
||||
|
||||
## Configuration Loading
|
||||
|
||||
Configuration is loaded in this order:
|
||||
|
||||
1. Load `/app/config.ini` (defaults)
|
||||
2. Load `/custom/config.ini` if exists
|
||||
3. Merge, with custom values overriding defaults
|
||||
|
||||
Example:
|
||||
|
||||
**app/config.ini**:
|
||||
```ini
|
||||
[languages]
|
||||
default = "no"
|
||||
available = "no,en"
|
||||
```
|
||||
|
||||
**custom/config.ini**:
|
||||
```ini
|
||||
[languages]
|
||||
default = "en"
|
||||
```
|
||||
|
||||
**Result**:
|
||||
```ini
|
||||
[languages]
|
||||
default = "en" ; From custom
|
||||
available = "no,en" ; From default (not overridden)
|
||||
```
|
||||
|
||||
## Complete Configuration Examples
|
||||
|
||||
### Single Language Site
|
||||
|
||||
```ini
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "en"
|
||||
```
|
||||
|
||||
URLs: All at root level (`/page/`, `/blog/`, etc.)
|
||||
|
||||
### Bilingual Site (English/Norwegian)
|
||||
|
||||
```ini
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "en,no"
|
||||
```
|
||||
|
||||
URLs:
|
||||
- English: `/page/`, `/blog/`
|
||||
- Norwegian: `/no/page/`, `/no/blog/`
|
||||
|
||||
### Multilingual Site
|
||||
|
||||
```ini
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "en,no,fr,de,es"
|
||||
```
|
||||
|
||||
URLs:
|
||||
- English (default): `/page/`
|
||||
- Norwegian: `/no/page/`
|
||||
- French: `/fr/page/`
|
||||
- German: `/de/page/`
|
||||
- Spanish: `/es/page/`
|
||||
|
||||
## Language Codes
|
||||
|
||||
Use ISO 639-1 two-letter codes:
|
||||
|
||||
| Code | Language |
|
||||
|------|----------|
|
||||
| `en` | English |
|
||||
| `no` | Norwegian |
|
||||
| `fr` | French |
|
||||
| `de` | German |
|
||||
| `es` | Spanish |
|
||||
| `it` | Italian |
|
||||
| `pt` | Portuguese |
|
||||
| `nl` | Dutch |
|
||||
| `sv` | Swedish |
|
||||
| `da` | Danish |
|
||||
| `fi` | Finnish |
|
||||
| `pl` | Polish |
|
||||
| `ru` | Russian |
|
||||
| `ja` | Japanese |
|
||||
| `zh` | Chinese |
|
||||
| `ko` | Korean |
|
||||
| `ar` | Arabic |
|
||||
|
||||
Full list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
|
||||
|
||||
## Related Configuration
|
||||
|
||||
### Translation Files
|
||||
|
||||
For each language in `available`, create a translation file:
|
||||
|
||||
**Pattern**: `/custom/languages/{lang}.ini`
|
||||
|
||||
**Example** (with `available = "en,no,fr"`):
|
||||
```
|
||||
custom/languages/
|
||||
├── en.ini
|
||||
├── no.ini
|
||||
└── fr.ini
|
||||
```
|
||||
|
||||
See [Translation Reference](translations.md) for details.
|
||||
|
||||
### Content Files
|
||||
|
||||
Language-specific content uses the same codes:
|
||||
|
||||
**Pattern**: `filename.{lang}.ext`
|
||||
|
||||
**Examples**:
|
||||
- `index.md` - Default language
|
||||
- `index.no.md` - Norwegian
|
||||
- `index.fr.md` - French
|
||||
|
||||
See [Multi-Language Guide](../how-to/multi-language.md) for details.
|
||||
|
||||
## Validation
|
||||
|
||||
### Check Configuration
|
||||
|
||||
Verify configuration is loaded correctly:
|
||||
|
||||
**test-config.php**:
|
||||
```php
|
||||
<?php
|
||||
require 'app/config.php';
|
||||
|
||||
$ctx = createContext();
|
||||
|
||||
echo "Default language: " . $ctx->defaultLang . "\n";
|
||||
echo "Available languages: " . implode(', ', $ctx->availableLangs) . "\n";
|
||||
echo "Current language: " . $ctx->currentLang . "\n";
|
||||
echo "Language prefix: " . $ctx->langPrefix . "\n";
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
php test-config.php
|
||||
```
|
||||
|
||||
### Common Errors
|
||||
|
||||
**Missing default in available**:
|
||||
```ini
|
||||
; Wrong - default must be in available
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "no,fr"
|
||||
|
||||
; Correct
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "en,no,fr"
|
||||
```
|
||||
|
||||
**Invalid language codes**:
|
||||
```ini
|
||||
; Avoid - use ISO codes
|
||||
available = "english,norwegian"
|
||||
|
||||
; Correct - ISO 639-1 codes
|
||||
available = "en,no"
|
||||
```
|
||||
|
||||
**Typos in section names**:
|
||||
```ini
|
||||
; Wrong
|
||||
[language]
|
||||
default = "en"
|
||||
|
||||
; Correct
|
||||
[languages]
|
||||
default = "en"
|
||||
```
|
||||
|
||||
## Future Configuration Options
|
||||
|
||||
FolderWeb is minimal by design. Currently, only language settings are configurable.
|
||||
|
||||
Possible future additions:
|
||||
- Date format preferences
|
||||
- Timezone settings
|
||||
- Content directory override
|
||||
- Cache settings
|
||||
|
||||
For now, these are handled through code or conventions.
|
||||
|
||||
## Environment-Specific Configuration
|
||||
|
||||
To use different configs per environment:
|
||||
|
||||
**Option 1: Conditional Loading**
|
||||
|
||||
**custom/config.ini**:
|
||||
```ini
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "en,no"
|
||||
```
|
||||
|
||||
**custom/config.dev.ini**:
|
||||
```ini
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "en"
|
||||
```
|
||||
|
||||
Modify `app/config.php` to load based on environment.
|
||||
|
||||
**Option 2: Separate Deployments**
|
||||
|
||||
Use different `custom/config.ini` files per deployment:
|
||||
- Development: `/custom/config.ini` with dev settings
|
||||
- Production: Different `/custom/config.ini` with prod settings
|
||||
|
||||
## Configuration in Templates
|
||||
|
||||
Access configuration through context object:
|
||||
|
||||
```php
|
||||
<!-- Current language -->
|
||||
<html lang="<?= $ctx->currentLang ?>">
|
||||
|
||||
<!-- Available languages -->
|
||||
<nav class="language-switcher">
|
||||
<?php foreach ($ctx->availableLangs as $lang): ?>
|
||||
<a href="/<?= $lang ?>/"><?= strtoupper($lang) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
|
||||
<!-- Default language check -->
|
||||
<?php if ($ctx->currentLang === $ctx->defaultLang): ?>
|
||||
<p>Viewing in default language</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Language prefix for URLs -->
|
||||
<a href="<?= $ctx->langPrefix ?>/about/">About</a>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Keep It Simple
|
||||
|
||||
Only configure what's necessary. FolderWeb embraces sensible defaults.
|
||||
|
||||
### Match Translation Files
|
||||
|
||||
Ensure translation files exist for all languages:
|
||||
|
||||
```ini
|
||||
[languages]
|
||||
available = "en,no,fr"
|
||||
```
|
||||
|
||||
Requires:
|
||||
- `custom/languages/en.ini`
|
||||
- `custom/languages/no.ini`
|
||||
- `custom/languages/fr.ini`
|
||||
|
||||
### Choose Appropriate Default
|
||||
|
||||
Your default language should be:
|
||||
- Your primary audience's language
|
||||
- The language with most content
|
||||
- The language you'll maintain long-term
|
||||
|
||||
### Document Your Choices
|
||||
|
||||
Add comments to explain configuration:
|
||||
|
||||
```ini
|
||||
; Site uses English as primary language (most content)
|
||||
; Norwegian and French are secondary translations
|
||||
[languages]
|
||||
default = "en"
|
||||
available = "en,no,fr"
|
||||
```
|
||||
|
||||
## Testing Configuration Changes
|
||||
|
||||
After changing configuration:
|
||||
|
||||
1. **Clear browser cache** (Ctrl+Shift+R or Cmd+Shift+R)
|
||||
2. **Test default language**: Visit `/`
|
||||
3. **Test other languages**: Visit `/no/`, `/fr/`, etc.
|
||||
4. **Check navigation**: Ensure menu links include language prefix
|
||||
5. **Verify translations**: Check UI strings change per language
|
||||
6. **Test language switcher**: Confirm switching works
|
||||
|
||||
## Related
|
||||
|
||||
- [Multi-Language Guide](../how-to/multi-language.md)
|
||||
- [Translation Reference](translations.md)
|
||||
- [Metadata Reference](metadata.md)
|
||||
- [Context Object Reference](templates.md#context-object)
|
||||
538
docs/reference/css-variables.md
Normal file
538
docs/reference/css-variables.md
Normal file
|
|
@ -0,0 +1,538 @@
|
|||
# CSS Variables Reference
|
||||
|
||||
Complete reference for all CSS custom properties available in FolderWeb.
|
||||
|
||||
## Overview
|
||||
|
||||
FolderWeb uses CSS custom properties (variables) for theming. Override these in `/custom/styles/base.css` to customize your site's appearance.
|
||||
|
||||
## Color Variables
|
||||
|
||||
### Primary Colors
|
||||
|
||||
```css
|
||||
:root {
|
||||
--color-primary: oklch(0.65 0.15 250);
|
||||
--color-secondary: oklch(0.50 0.12 250);
|
||||
--color-light: oklch(0.97 0.01 250);
|
||||
--color-grey: oklch(0.37 0 0);
|
||||
}
|
||||
```
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `--color-primary` | Blue (OKLCH) | Primary brand color, links, buttons |
|
||||
| `--color-secondary` | Dark blue (OKLCH) | Secondary accents, hover states |
|
||||
| `--color-light` | Off-white (OKLCH) | Background, light sections |
|
||||
| `--color-grey` | Dark grey | Body text, headings |
|
||||
|
||||
### OKLCH Color Space
|
||||
|
||||
FolderWeb uses OKLCH for perceptually uniform colors:
|
||||
|
||||
```css
|
||||
oklch(lightness chroma hue)
|
||||
```
|
||||
|
||||
- **Lightness**: 0 (black) to 1 (white)
|
||||
- **Chroma**: 0 (grey) to ~0.4 (vibrant)
|
||||
- **Hue**: 0-360 degrees
|
||||
|
||||
**Examples**:
|
||||
```css
|
||||
/* Blue hues (250°) */
|
||||
--color-primary: oklch(0.65 0.15 250);
|
||||
|
||||
/* Orange hues (30°) */
|
||||
--color-primary: oklch(0.65 0.20 30);
|
||||
|
||||
/* Green hues (150°) */
|
||||
--color-primary: oklch(0.60 0.15 150);
|
||||
|
||||
/* Red hues (0°) */
|
||||
--color-primary: oklch(0.60 0.20 0);
|
||||
|
||||
/* Purple hues (300°) */
|
||||
--color-primary: oklch(0.60 0.18 300);
|
||||
```
|
||||
|
||||
### Alternative Color Formats
|
||||
|
||||
You can use hex, rgb, or hsl instead:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Hex */
|
||||
--color-primary: #4169E1;
|
||||
--color-secondary: #1E3A8A;
|
||||
|
||||
/* RGB */
|
||||
--color-primary: rgb(65, 105, 225);
|
||||
--color-secondary: rgb(30, 58, 138);
|
||||
|
||||
/* HSL */
|
||||
--color-primary: hsl(225, 73%, 57%);
|
||||
--color-secondary: hsl(225, 64%, 33%);
|
||||
}
|
||||
```
|
||||
|
||||
## Typography Variables
|
||||
|
||||
### Font Families
|
||||
|
||||
```css
|
||||
:root {
|
||||
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
--font-heading: Georgia, "Times New Roman", serif;
|
||||
}
|
||||
```
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `--font-body` | System sans-serif stack | Body text, paragraphs |
|
||||
| `--font-heading` | Serif stack | Headings (h1-h6) |
|
||||
|
||||
**Custom Fonts**:
|
||||
```css
|
||||
@font-face {
|
||||
font-family: 'MyFont';
|
||||
src: url('/custom/fonts/MyFont.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-body: 'MyFont', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
### Font Sizes
|
||||
|
||||
```css
|
||||
:root {
|
||||
--font-size-base: 1.125rem; /* 18px */
|
||||
--font-size-small: 0.875rem; /* 14px */
|
||||
}
|
||||
```
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `--font-size-base` | 1.125rem (18px) | Body text size |
|
||||
| `--font-size-small` | 0.875rem (14px) | Small text, metadata |
|
||||
|
||||
**Responsive Sizing**:
|
||||
```css
|
||||
:root {
|
||||
/* Fluid typography */
|
||||
--font-size-base: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2rem, 1.5rem + 2vw, 3.5rem);
|
||||
}
|
||||
```
|
||||
|
||||
### Line Heights
|
||||
|
||||
```css
|
||||
:root {
|
||||
--line-height-base: 1.6;
|
||||
--line-height-heading: 1.2;
|
||||
}
|
||||
```
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `--line-height-base` | 1.6 | Body text line height |
|
||||
| `--line-height-heading` | 1.2 | Heading line height |
|
||||
|
||||
## Spacing Variables
|
||||
|
||||
```css
|
||||
:root {
|
||||
--spacing-unit: 1.5rem; /* 24px */
|
||||
--spacing-small: 0.75rem; /* 12px */
|
||||
--spacing-large: 3rem; /* 48px */
|
||||
}
|
||||
```
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `--spacing-unit` | 1.5rem (24px) | Base spacing unit |
|
||||
| `--spacing-small` | 0.75rem (12px) | Small gaps |
|
||||
| `--spacing-large` | 3rem (48px) | Large gaps, section spacing |
|
||||
|
||||
**Usage**:
|
||||
```css
|
||||
.card {
|
||||
padding: var(--spacing-unit);
|
||||
margin-block-end: var(--spacing-large);
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: var(--spacing-small);
|
||||
}
|
||||
```
|
||||
|
||||
**Responsive Spacing**:
|
||||
```css
|
||||
:root {
|
||||
--spacing-unit: clamp(1rem, 0.8rem + 1vw, 2rem);
|
||||
}
|
||||
```
|
||||
|
||||
## Layout Variables
|
||||
|
||||
```css
|
||||
:root {
|
||||
--max-width: 70rem; /* 1120px */
|
||||
--border-radius: 4px;
|
||||
}
|
||||
```
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `--max-width` | 70rem (1120px) | Content max-width |
|
||||
| `--border-radius` | 4px | Corner rounding |
|
||||
|
||||
**Usage**:
|
||||
```css
|
||||
.contain {
|
||||
max-inline-size: var(--max-width);
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Variable List
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: oklch(0.65 0.15 250);
|
||||
--color-secondary: oklch(0.50 0.12 250);
|
||||
--color-light: oklch(0.97 0.01 250);
|
||||
--color-grey: oklch(0.37 0 0);
|
||||
|
||||
/* Typography */
|
||||
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
--font-heading: Georgia, "Times New Roman", serif;
|
||||
--font-size-base: 1.125rem;
|
||||
--font-size-small: 0.875rem;
|
||||
--line-height-base: 1.6;
|
||||
--line-height-heading: 1.2;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-unit: 1.5rem;
|
||||
--spacing-small: 0.75rem;
|
||||
--spacing-large: 3rem;
|
||||
|
||||
/* Layout */
|
||||
--max-width: 70rem;
|
||||
--border-radius: 4px;
|
||||
}
|
||||
```
|
||||
|
||||
## Customization Examples
|
||||
|
||||
### Orange Theme
|
||||
|
||||
```css
|
||||
:root {
|
||||
--color-primary: oklch(0.65 0.20 30);
|
||||
--color-secondary: oklch(0.50 0.18 30);
|
||||
--color-light: oklch(0.97 0.01 30);
|
||||
}
|
||||
```
|
||||
|
||||
### Dark Mode
|
||||
|
||||
```css
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-primary: oklch(0.70 0.15 250);
|
||||
--color-secondary: oklch(0.80 0.12 250);
|
||||
--color-light: oklch(0.25 0 0);
|
||||
--color-grey: oklch(0.90 0 0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Large Text
|
||||
|
||||
```css
|
||||
:root {
|
||||
--font-size-base: 1.25rem; /* 20px */
|
||||
--line-height-base: 1.7;
|
||||
--spacing-unit: 2rem;
|
||||
}
|
||||
```
|
||||
|
||||
### Tight Layout
|
||||
|
||||
```css
|
||||
:root {
|
||||
--max-width: 50rem; /* 800px */
|
||||
--spacing-unit: 1rem; /* 16px */
|
||||
--spacing-large: 2rem; /* 32px */
|
||||
}
|
||||
```
|
||||
|
||||
### Rounded Design
|
||||
|
||||
```css
|
||||
:root {
|
||||
--border-radius: 12px;
|
||||
}
|
||||
```
|
||||
|
||||
## Using Variables
|
||||
|
||||
### In Your Styles
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: var(--color-light);
|
||||
color: var(--color-grey);
|
||||
padding: var(--spacing-unit);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.button {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
padding: var(--spacing-small) var(--spacing-unit);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: var(--color-secondary);
|
||||
}
|
||||
```
|
||||
|
||||
### With Fallbacks
|
||||
|
||||
Provide fallbacks for older browsers:
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: #F5F5F5; /* Fallback */
|
||||
background: var(--color-light); /* Variable */
|
||||
}
|
||||
```
|
||||
|
||||
### With calc()
|
||||
|
||||
Combine with calculations:
|
||||
|
||||
```css
|
||||
.card {
|
||||
padding: calc(var(--spacing-unit) * 2);
|
||||
margin-block-end: calc(var(--spacing-large) - 1rem);
|
||||
}
|
||||
```
|
||||
|
||||
### With color-mix()
|
||||
|
||||
Create variations:
|
||||
|
||||
```css
|
||||
.button {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: color-mix(in oklch, var(--color-primary), black 10%);
|
||||
}
|
||||
|
||||
.button-light {
|
||||
background: color-mix(in oklch, var(--color-primary), white 80%);
|
||||
}
|
||||
```
|
||||
|
||||
## Adding Custom Variables
|
||||
|
||||
Define your own variables:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Custom color palette */
|
||||
--color-accent: oklch(0.70 0.15 180);
|
||||
--color-warning: oklch(0.70 0.20 60);
|
||||
--color-danger: oklch(0.60 0.20 10);
|
||||
--color-success: oklch(0.65 0.15 140);
|
||||
|
||||
/* Custom spacing */
|
||||
--spacing-xs: 0.25rem;
|
||||
--spacing-xl: 4rem;
|
||||
--spacing-2xl: 6rem;
|
||||
|
||||
/* Custom typography */
|
||||
--font-mono: 'Monaco', 'Courier New', monospace;
|
||||
--font-size-large: 1.5rem;
|
||||
--font-size-xlarge: 2rem;
|
||||
|
||||
/* Custom layout */
|
||||
--sidebar-width: 20rem;
|
||||
--header-height: 4rem;
|
||||
--content-gap: 2rem;
|
||||
}
|
||||
```
|
||||
|
||||
Use them:
|
||||
|
||||
```css
|
||||
.sidebar {
|
||||
width: var(--sidebar-width);
|
||||
background: var(--color-light);
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--font-mono);
|
||||
background: var(--color-accent);
|
||||
padding: var(--spacing-xs);
|
||||
}
|
||||
```
|
||||
|
||||
## Scoped Variables
|
||||
|
||||
Override variables for specific sections:
|
||||
|
||||
```css
|
||||
/* Global defaults */
|
||||
:root {
|
||||
--color-primary: oklch(0.65 0.15 250);
|
||||
}
|
||||
|
||||
/* Blog section uses green */
|
||||
.section-blog {
|
||||
--color-primary: oklch(0.60 0.15 150);
|
||||
}
|
||||
|
||||
/* About page uses orange */
|
||||
.page-about {
|
||||
--color-primary: oklch(0.65 0.20 30);
|
||||
}
|
||||
|
||||
/* Variables cascade to children */
|
||||
.section-blog .button {
|
||||
background: var(--color-primary); /* Green in blog */
|
||||
}
|
||||
```
|
||||
|
||||
## Responsive Variables
|
||||
|
||||
Change variables at breakpoints:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--spacing-unit: 1rem;
|
||||
--font-size-base: 1rem;
|
||||
--max-width: 60rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
:root {
|
||||
--spacing-unit: 1.5rem;
|
||||
--font-size-base: 1.125rem;
|
||||
--max-width: 70rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
:root {
|
||||
--spacing-unit: 2rem;
|
||||
--font-size-base: 1.25rem;
|
||||
--max-width: 80rem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Browser Support
|
||||
|
||||
CSS custom properties are supported in all modern browsers:
|
||||
- Chrome 49+
|
||||
- Firefox 31+
|
||||
- Safari 9.1+
|
||||
- Edge 15+
|
||||
|
||||
For older browsers, provide fallbacks or use PostCSS with custom properties plugin.
|
||||
|
||||
## Debugging Variables
|
||||
|
||||
Inspect variables in browser DevTools:
|
||||
|
||||
1. Right-click element → Inspect
|
||||
2. Check "Computed" tab
|
||||
3. Scroll to custom properties section
|
||||
4. See resolved values
|
||||
|
||||
Or log in console:
|
||||
|
||||
```javascript
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--color-primary')
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Use Semantic Names
|
||||
|
||||
```css
|
||||
/* Good - semantic */
|
||||
--color-primary
|
||||
--color-text
|
||||
--color-background
|
||||
|
||||
/* Avoid - non-semantic */
|
||||
--color-blue
|
||||
--color-444
|
||||
```
|
||||
|
||||
### Group Related Variables
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: ...;
|
||||
--color-secondary: ...;
|
||||
|
||||
/* Typography */
|
||||
--font-body: ...;
|
||||
--font-heading: ...;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-unit: ...;
|
||||
}
|
||||
```
|
||||
|
||||
### Document Your Variables
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Brand colors from design system */
|
||||
--color-primary: oklch(0.65 0.15 250); /* Blue - primary CTA */
|
||||
--color-secondary: oklch(0.50 0.12 250); /* Dark blue - accents */
|
||||
|
||||
/* Layout constraints */
|
||||
--max-width: 70rem; /* 1120px - content max width */
|
||||
}
|
||||
```
|
||||
|
||||
### Provide Fallbacks
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: #F5F5F5;
|
||||
background: var(--color-light);
|
||||
}
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Custom Styles Guide](../how-to/custom-styles.md)
|
||||
- [Template Reference](templates.md)
|
||||
- [File Structure Reference](file-structure.md)
|
||||
394
docs/reference/file-structure.md
Normal file
394
docs/reference/file-structure.md
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
# File Structure Reference
|
||||
|
||||
Complete reference for FolderWeb's file and directory structure.
|
||||
|
||||
## Root Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── app/ # Framework core (never modify)
|
||||
├── content/ # Your website content
|
||||
├── custom/ # Your customizations
|
||||
└── .htaccess # Web server configuration (optional)
|
||||
```
|
||||
|
||||
## App Directory (Framework Core)
|
||||
|
||||
```
|
||||
app/
|
||||
├── router.php # Main entry point and request router
|
||||
├── content.php # Content discovery and parsing functions
|
||||
├── rendering.php # Template rendering engine
|
||||
├── context.php # Context object (readonly class)
|
||||
├── config.php # Configuration loader
|
||||
├── helpers.php # Utility functions
|
||||
├── constants.php # File extension constants
|
||||
├── static.php # Static file server
|
||||
├── config.ini # Default configuration
|
||||
├── default/ # Default files (fallback)
|
||||
│ ├── templates/ # Default templates
|
||||
│ │ ├── base.php # HTML wrapper
|
||||
│ │ ├── page.php # Page wrapper
|
||||
│ │ ├── list.php # Simple list
|
||||
│ │ ├── list-grid.php # Grid layout
|
||||
│ │ ├── list-card-grid.php # Card grid
|
||||
│ │ └── list-faq.php # FAQ layout
|
||||
│ ├── styles/ # Default CSS
|
||||
│ │ └── base.css # Main stylesheet
|
||||
│ ├── languages/ # Default translations
|
||||
│ │ ├── en.ini # English
|
||||
│ │ └── no.ini # Norwegian
|
||||
│ └── content/ # Demo content (fallback)
|
||||
└── vendor/ # Third-party libraries
|
||||
└── Parsedown.php # Markdown parser
|
||||
```
|
||||
|
||||
**Important**: Never modify files in `/app/`. All customization goes in `/custom/`.
|
||||
|
||||
## Custom Directory
|
||||
|
||||
```
|
||||
custom/
|
||||
├── templates/ # Override templates
|
||||
│ ├── base.php # Custom base template
|
||||
│ ├── page.php # Custom page template
|
||||
│ ├── list-*.php # Custom list templates
|
||||
│ └── [custom].php # Your custom templates
|
||||
├── styles/ # Override styles
|
||||
│ └── base.css # Custom stylesheet
|
||||
├── languages/ # Override translations
|
||||
│ ├── en.ini # English translations
|
||||
│ ├── no.ini # Norwegian translations
|
||||
│ └── [lang].ini # Other languages
|
||||
├── fonts/ # Custom web fonts
|
||||
│ └── *.woff2 # Font files
|
||||
├── assets/ # Root-level assets
|
||||
│ ├── favicon.ico # Site favicon
|
||||
│ ├── robots.txt # Robots file
|
||||
│ ├── logo.svg # Logo
|
||||
│ └── [any file] # Served at root level
|
||||
└── config.ini # Configuration overrides
|
||||
```
|
||||
|
||||
## Content Directory
|
||||
|
||||
Your content directory contains all your website pages and assets.
|
||||
|
||||
### Basic Structure
|
||||
|
||||
```
|
||||
content/
|
||||
├── index.md # Home page
|
||||
├── about/ # About page
|
||||
│ ├── index.md # Page content
|
||||
│ ├── metadata.ini # Page metadata
|
||||
│ └── team-photo.jpg # Page asset
|
||||
├── blog/ # Blog (list view)
|
||||
│ ├── metadata.ini # Blog configuration
|
||||
│ ├── 2025-11-01-first-post/
|
||||
│ │ ├── index.md # Post content
|
||||
│ │ ├── cover.jpg # Cover image
|
||||
│ │ └── metadata.ini # Post metadata
|
||||
│ └── 2025-11-02-second-post/
|
||||
│ ├── index.md
|
||||
│ ├── cover.webp
|
||||
│ └── metadata.ini
|
||||
└── docs/ # Multi-file page
|
||||
├── 00-intro.md # Section 1
|
||||
├── 01-setup.md # Section 2
|
||||
├── 02-usage.md # Section 3
|
||||
└── metadata.ini # Page metadata
|
||||
```
|
||||
|
||||
### Content Types
|
||||
|
||||
#### Single-File Page
|
||||
|
||||
```
|
||||
content/about/
|
||||
└── index.md
|
||||
```
|
||||
|
||||
URL: `/about/`
|
||||
|
||||
#### Multi-File Page
|
||||
|
||||
```
|
||||
content/docs/
|
||||
├── 00-intro.md
|
||||
├── 01-setup.md
|
||||
└── 02-usage.md
|
||||
```
|
||||
|
||||
URL: `/docs/` (all files render as one page)
|
||||
|
||||
#### List View (Directory with Subdirectories)
|
||||
|
||||
```
|
||||
content/blog/
|
||||
├── metadata.ini
|
||||
├── 2025-11-01-post/
|
||||
│ └── index.md
|
||||
└── 2025-11-02-post/
|
||||
└── index.md
|
||||
```
|
||||
|
||||
URL: `/blog/` (shows list of posts)
|
||||
|
||||
## File Naming Conventions
|
||||
|
||||
### Content Files
|
||||
|
||||
Supported extensions:
|
||||
- `.md` - Markdown (parsed with Parsedown)
|
||||
- `.html` - HTML (included as-is)
|
||||
- `.php` - PHP (executed with access to `$ctx`)
|
||||
|
||||
### Language-Specific Files
|
||||
|
||||
Format: `filename.{lang}.ext`
|
||||
|
||||
Examples:
|
||||
- `index.md` - Default language
|
||||
- `index.no.md` - Norwegian
|
||||
- `index.fr.md` - French
|
||||
- `about.en.md` - English
|
||||
|
||||
### Date Prefixes
|
||||
|
||||
Format: `YYYY-MM-DD-slug`
|
||||
|
||||
Examples:
|
||||
- `2025-11-01-my-post`
|
||||
- `2025-11-02-another-post`
|
||||
|
||||
Dates are automatically extracted and formatted.
|
||||
|
||||
### Cover Images
|
||||
|
||||
Filename: `cover.{ext}`
|
||||
|
||||
Supported formats:
|
||||
- `cover.jpg`
|
||||
- `cover.jpeg`
|
||||
- `cover.png`
|
||||
- `cover.webp`
|
||||
- `cover.gif`
|
||||
|
||||
Automatically detected in list views.
|
||||
|
||||
### PDF Files
|
||||
|
||||
Any `.pdf` file in a directory is automatically linked in grid layouts.
|
||||
|
||||
### Metadata Files
|
||||
|
||||
Filename: `metadata.ini`
|
||||
|
||||
Format: INI with optional language sections.
|
||||
|
||||
## File Discovery Order
|
||||
|
||||
### Content File Priority
|
||||
|
||||
For multi-file pages, files are rendered in alphanumerical order:
|
||||
|
||||
```
|
||||
content/docs/
|
||||
├── 00-intro.md # First
|
||||
├── 01-setup.md # Second
|
||||
├── 02-usage.md # Third
|
||||
└── 99-appendix.md # Last
|
||||
```
|
||||
|
||||
Use numerical prefixes to control order.
|
||||
|
||||
### Template Resolution
|
||||
|
||||
Templates are resolved with custom fallback:
|
||||
|
||||
1. `/custom/templates/{name}.php`
|
||||
2. `/app/default/templates/{name}.php`
|
||||
|
||||
### CSS Resolution
|
||||
|
||||
Stylesheets are resolved with custom fallback:
|
||||
|
||||
1. `/custom/styles/base.css`
|
||||
2. `/app/default/styles/base.css`
|
||||
|
||||
### Translation Resolution
|
||||
|
||||
Translations are resolved with custom fallback:
|
||||
|
||||
1. `/custom/languages/{lang}.ini`
|
||||
2. `/app/default/languages/{lang}.ini`
|
||||
|
||||
### Configuration Resolution
|
||||
|
||||
Configuration is merged:
|
||||
|
||||
1. Load `/app/config.ini`
|
||||
2. Merge with `/custom/config.ini` if exists
|
||||
|
||||
Custom values override defaults.
|
||||
|
||||
## URL Mapping
|
||||
|
||||
### Basic Mapping
|
||||
|
||||
```
|
||||
/content/about/index.md → /about/
|
||||
/content/blog/ → /blog/
|
||||
/content/docs/ → /docs/
|
||||
```
|
||||
|
||||
### Language Prefixes
|
||||
|
||||
Default language (no prefix):
|
||||
```
|
||||
/content/about/index.md → /about/
|
||||
```
|
||||
|
||||
Other languages (with prefix):
|
||||
```
|
||||
/content/about/index.no.md → /no/about/
|
||||
/content/about/index.fr.md → /fr/about/
|
||||
```
|
||||
|
||||
### Translated Slugs
|
||||
|
||||
With metadata slug overrides:
|
||||
```
|
||||
content/about/metadata.ini:
|
||||
[no]
|
||||
slug = "om-oss"
|
||||
|
||||
[fr]
|
||||
slug = "a-propos"
|
||||
```
|
||||
|
||||
URLs become:
|
||||
- `/about/` (English)
|
||||
- `/no/om-oss/` (Norwegian)
|
||||
- `/fr/a-propos/` (French)
|
||||
|
||||
### Trailing Slashes
|
||||
|
||||
FolderWeb requires trailing slashes for directories. Missing slashes trigger 301 redirects:
|
||||
|
||||
```
|
||||
/blog → 301 redirect to → /blog/
|
||||
```
|
||||
|
||||
## Special Files and Directories
|
||||
|
||||
### System Files (Ignored)
|
||||
|
||||
These files are automatically ignored:
|
||||
- `.htaccess`
|
||||
- `.git/`
|
||||
- `.DS_Store`
|
||||
- `node_modules/`
|
||||
- Hidden files/directories (starting with `.`)
|
||||
|
||||
### Index Files
|
||||
|
||||
`index.md`, `index.html`, `index.php` are treated as directory content, not separate routes.
|
||||
|
||||
### Metadata Files
|
||||
|
||||
`metadata.ini` files are configuration, never rendered as content.
|
||||
|
||||
## Asset Serving
|
||||
|
||||
### Root-Level Assets
|
||||
|
||||
Files in `/custom/assets/` are served at site root:
|
||||
|
||||
```
|
||||
/custom/assets/robots.txt → yoursite.com/robots.txt
|
||||
/custom/assets/favicon.ico → yoursite.com/favicon.ico
|
||||
/custom/assets/logo.svg → yoursite.com/logo.svg
|
||||
```
|
||||
|
||||
### Content Assets
|
||||
|
||||
Files in content directories are accessible at their directory URL:
|
||||
|
||||
```
|
||||
/content/blog/2025-11-01-post/cover.jpg
|
||||
→ yoursite.com/blog/2025-11-01-post/cover.jpg
|
||||
|
||||
/content/about/team-photo.jpg
|
||||
→ yoursite.com/about/team-photo.jpg
|
||||
```
|
||||
|
||||
### CSS Files
|
||||
|
||||
CSS is served with version hashing:
|
||||
|
||||
```
|
||||
/custom/styles/base.css
|
||||
→ yoursite.com/app/styles/base.css?v=abc123def456
|
||||
```
|
||||
|
||||
### Font Files
|
||||
|
||||
Fonts in `/custom/fonts/` are accessible:
|
||||
|
||||
```
|
||||
/custom/fonts/MyFont.woff2
|
||||
→ yoursite.com/custom/fonts/MyFont.woff2
|
||||
```
|
||||
|
||||
## File Permissions
|
||||
|
||||
### Readable Files
|
||||
|
||||
The web server must have read access to:
|
||||
- All files in `/app/`
|
||||
- All files in `/content/`
|
||||
- All files in `/custom/`
|
||||
|
||||
### Writable Files
|
||||
|
||||
FolderWeb is read-only. No files require write access.
|
||||
|
||||
### Security
|
||||
|
||||
- Path validation prevents directory traversal
|
||||
- Files must be within document root
|
||||
- Realpath checks ensure proper resolution
|
||||
|
||||
## Size Limits
|
||||
|
||||
- **Read Tool**: Files larger than 50KB are truncated
|
||||
- **No upload limits**: FolderWeb doesn't handle uploads
|
||||
- **No execution limits**: Standard PHP limits apply
|
||||
|
||||
## Caching
|
||||
|
||||
### CSS Versioning
|
||||
|
||||
CSS files are versioned with MD5 hash:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/app/styles/base.css?v=abc123def456">
|
||||
```
|
||||
|
||||
Hash updates when file content changes.
|
||||
|
||||
### No Built-in Cache
|
||||
|
||||
FolderWeb doesn't implement content caching. Use:
|
||||
- Web server caching (Apache, Nginx)
|
||||
- Reverse proxy (Varnish, Cloudflare)
|
||||
- PHP OPcache for code
|
||||
|
||||
## Related
|
||||
|
||||
- [Metadata Reference](metadata.md)
|
||||
- [Configuration Reference](configuration.md)
|
||||
- [How to Customize Templates](../how-to/custom-templates.md)
|
||||
- [How to Customize Styles](../how-to/custom-styles.md)
|
||||
610
docs/reference/metadata.md
Normal file
610
docs/reference/metadata.md
Normal file
|
|
@ -0,0 +1,610 @@
|
|||
# Metadata Reference
|
||||
|
||||
Complete reference for all metadata fields and their usage in `metadata.ini` files.
|
||||
|
||||
## File Format
|
||||
|
||||
Metadata files use INI format:
|
||||
|
||||
```ini
|
||||
; Comments start with semicolon
|
||||
key = "value"
|
||||
array[] = "value1"
|
||||
array[] = "value2"
|
||||
|
||||
[section]
|
||||
key = "section value"
|
||||
```
|
||||
|
||||
## Standard Fields
|
||||
|
||||
### title
|
||||
|
||||
**Type**: String
|
||||
**Used in**: All content types
|
||||
**Purpose**: Override automatic title extraction
|
||||
|
||||
```ini
|
||||
title = "Custom Page Title"
|
||||
```
|
||||
|
||||
If not provided, FolderWeb extracts title from:
|
||||
1. First H1 heading (`# Title` in Markdown)
|
||||
2. Folder name (as fallback)
|
||||
|
||||
**Multi-language**:
|
||||
```ini
|
||||
title = "English Title"
|
||||
|
||||
[no]
|
||||
title = "Norsk Tittel"
|
||||
|
||||
[fr]
|
||||
title = "Titre Français"
|
||||
```
|
||||
|
||||
### date
|
||||
|
||||
**Type**: Date string (YYYY-MM-DD)
|
||||
**Used in**: Blog posts, articles
|
||||
**Purpose**: Override automatic date extraction
|
||||
|
||||
```ini
|
||||
date = "2025-11-02"
|
||||
```
|
||||
|
||||
If not provided, FolderWeb extracts date from folder names like `2025-11-02-post-title`.
|
||||
|
||||
Dates are automatically formatted in Norwegian style: "2. november 2025"
|
||||
|
||||
### summary
|
||||
|
||||
**Type**: String
|
||||
**Used in**: List views
|
||||
**Purpose**: Brief description for cards and listings
|
||||
|
||||
```ini
|
||||
summary = "A concise description that appears in blog listings."
|
||||
```
|
||||
|
||||
**Multi-language**:
|
||||
```ini
|
||||
summary = "English summary"
|
||||
|
||||
[no]
|
||||
summary = "Norsk sammendrag"
|
||||
```
|
||||
|
||||
### menu
|
||||
|
||||
**Type**: Boolean
|
||||
**Used in**: Top-level directories
|
||||
**Purpose**: Include in site navigation
|
||||
|
||||
```ini
|
||||
menu = true
|
||||
```
|
||||
|
||||
Accepted values: `true`, `false`, `1`, `0`, `yes`, `no`, `on`, `off`
|
||||
|
||||
### menu_order
|
||||
|
||||
**Type**: Integer
|
||||
**Used in**: Navigation items
|
||||
**Purpose**: Control navigation order (lower numbers first)
|
||||
|
||||
```ini
|
||||
menu = true
|
||||
menu_order = 1
|
||||
```
|
||||
|
||||
### page_template
|
||||
|
||||
**Type**: String
|
||||
**Used in**: Directories with subdirectories
|
||||
**Purpose**: Choose list template
|
||||
|
||||
```ini
|
||||
page_template = "list-grid"
|
||||
```
|
||||
|
||||
Available values:
|
||||
- `list` - Simple list (default)
|
||||
- `list-grid` - Grid with cover images
|
||||
- `list-card-grid` - Card-style grid
|
||||
- `list-faq` - Expandable FAQ format
|
||||
- Any custom template name (without `.php` extension)
|
||||
|
||||
### slug
|
||||
|
||||
**Type**: String
|
||||
**Used in**: Language sections
|
||||
**Purpose**: Translate URL segments
|
||||
|
||||
```ini
|
||||
[no]
|
||||
slug = "om-oss"
|
||||
|
||||
[fr]
|
||||
slug = "a-propos"
|
||||
```
|
||||
|
||||
The actual folder is `about/`, but URLs become:
|
||||
- `/about/` (English)
|
||||
- `/no/om-oss/` (Norwegian)
|
||||
- `/fr/a-propos/` (French)
|
||||
|
||||
### redirect
|
||||
|
||||
**Type**: URL string
|
||||
**Used in**: List items (with `list-card-grid` template)
|
||||
**Purpose**: Link to external site instead of internal page
|
||||
|
||||
```ini
|
||||
redirect = "https://example.com"
|
||||
```
|
||||
|
||||
Creates an external link card in card grid layouts.
|
||||
|
||||
## Custom Fields
|
||||
|
||||
You can add any custom fields for use in your templates:
|
||||
|
||||
### Common Custom Fields
|
||||
|
||||
```ini
|
||||
; Author information
|
||||
author = "Jane Doe"
|
||||
author_email = "jane@example.com"
|
||||
author_url = "https://janedoe.com"
|
||||
|
||||
; Content metadata
|
||||
reading_time = "5 min"
|
||||
difficulty = "intermediate"
|
||||
featured = true
|
||||
|
||||
; Categorization
|
||||
tags[] = "PHP"
|
||||
tags[] = "Tutorial"
|
||||
tags[] = "Web Development"
|
||||
|
||||
categories[] = "Programming"
|
||||
categories[] = "Backend"
|
||||
|
||||
; SEO
|
||||
meta_description = "Complete guide to FolderWeb metadata"
|
||||
meta_keywords = "metadata, ini, folderweb"
|
||||
|
||||
; Social sharing
|
||||
og_image = "/blog/post/social-card.jpg"
|
||||
twitter_card = "summary_large_image"
|
||||
|
||||
; Version tracking
|
||||
version = "1.2.0"
|
||||
last_updated = "2025-11-02"
|
||||
|
||||
; Display options
|
||||
hide_date = true
|
||||
hide_author = false
|
||||
show_toc = true
|
||||
|
||||
; External references
|
||||
github_url = "https://github.com/user/repo"
|
||||
demo_url = "https://demo.example.com"
|
||||
download_url = "/files/document.pdf"
|
||||
```
|
||||
|
||||
## Array Fields
|
||||
|
||||
Use `[]` syntax for array values:
|
||||
|
||||
```ini
|
||||
tags[] = "PHP"
|
||||
tags[] = "Web Development"
|
||||
tags[] = "Tutorial"
|
||||
|
||||
authors[] = "Jane Doe"
|
||||
authors[] = "John Smith"
|
||||
|
||||
related_posts[] = "/blog/post-1/"
|
||||
related_posts[] = "/blog/post-2/"
|
||||
```
|
||||
|
||||
Access in templates:
|
||||
|
||||
```php
|
||||
<?php foreach ($metadata['tags'] as $tag): ?>
|
||||
<span><?= htmlspecialchars($tag) ?></span>
|
||||
<?php endforeach; ?>
|
||||
```
|
||||
|
||||
## Boolean Values
|
||||
|
||||
Accepted boolean formats:
|
||||
|
||||
```ini
|
||||
; True values
|
||||
featured = true
|
||||
featured = 1
|
||||
featured = yes
|
||||
featured = on
|
||||
|
||||
; False values
|
||||
draft = false
|
||||
draft = 0
|
||||
draft = no
|
||||
draft = off
|
||||
```
|
||||
|
||||
## Language Sections
|
||||
|
||||
Use `[lang]` sections for multi-language overrides:
|
||||
|
||||
```ini
|
||||
; Base values (default language)
|
||||
title = "About Us"
|
||||
summary = "Learn about our company"
|
||||
slug = "about"
|
||||
|
||||
; Norwegian overrides
|
||||
[no]
|
||||
title = "Om Oss"
|
||||
summary = "Lær om vårt selskap"
|
||||
slug = "om-oss"
|
||||
|
||||
; French overrides
|
||||
[fr]
|
||||
title = "À Propos"
|
||||
summary = "Découvrez notre entreprise"
|
||||
slug = "a-propos"
|
||||
|
||||
; Fields not overridden inherit base values
|
||||
```
|
||||
|
||||
Language-specific fields override base fields for that language.
|
||||
|
||||
## Comments
|
||||
|
||||
Use `;` or `#` for comments:
|
||||
|
||||
```ini
|
||||
; This is a comment
|
||||
title = "My Page"
|
||||
|
||||
# This is also a comment
|
||||
date = "2025-11-02"
|
||||
|
||||
; Comments can be on same line as values
|
||||
menu = true ; Include in navigation
|
||||
```
|
||||
|
||||
## Special Characters
|
||||
|
||||
### Quotes
|
||||
|
||||
Use quotes for values with special characters:
|
||||
|
||||
```ini
|
||||
; Optional for simple values
|
||||
title = Simple Title
|
||||
title = "Simple Title"
|
||||
|
||||
; Required for values with spaces at start/end
|
||||
title = " Padded Title "
|
||||
|
||||
; Required for values with special characters
|
||||
summary = "Use \"quotes\" for nested quotes"
|
||||
summary = 'Single quotes work too'
|
||||
```
|
||||
|
||||
### Escape Sequences
|
||||
|
||||
Standard INI escape sequences:
|
||||
|
||||
```ini
|
||||
; Newline
|
||||
text = "First line\nSecond line"
|
||||
|
||||
; Tab
|
||||
text = "Indented\ttext"
|
||||
|
||||
; Quote
|
||||
text = "He said \"Hello\""
|
||||
```
|
||||
|
||||
## Metadata Location
|
||||
|
||||
### Directory Metadata
|
||||
|
||||
Place `metadata.ini` in the directory it describes:
|
||||
|
||||
```
|
||||
content/blog/metadata.ini # Blog configuration
|
||||
content/about/metadata.ini # About page metadata
|
||||
```
|
||||
|
||||
### Item Metadata
|
||||
|
||||
Place `metadata.ini` in each subdirectory:
|
||||
|
||||
```
|
||||
content/blog/2025-11-01-post/metadata.ini # Post metadata
|
||||
content/blog/2025-11-02-post/metadata.ini # Post metadata
|
||||
```
|
||||
|
||||
## Metadata Scope
|
||||
|
||||
Metadata applies only to its directory. **No inheritance** from parent directories.
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Blog Configuration
|
||||
|
||||
**content/blog/metadata.ini**:
|
||||
```ini
|
||||
; Display settings
|
||||
title = "Blog"
|
||||
page_template = "list-grid"
|
||||
|
||||
; Navigation
|
||||
menu = true
|
||||
menu_order = 1
|
||||
|
||||
; Multi-language
|
||||
[no]
|
||||
title = "Blogg"
|
||||
slug = "blogg"
|
||||
|
||||
[fr]
|
||||
title = "Blog"
|
||||
slug = "blog"
|
||||
```
|
||||
|
||||
### Blog Post
|
||||
|
||||
**content/blog/2025-11-02-web-performance/metadata.ini**:
|
||||
```ini
|
||||
; Basic information
|
||||
title = "Optimizing Web Performance"
|
||||
date = "2025-11-02"
|
||||
summary = "Learn techniques to make your website faster."
|
||||
|
||||
; Author information
|
||||
author = "Jane Developer"
|
||||
author_url = "https://jane.dev"
|
||||
|
||||
; Content metadata
|
||||
reading_time = "8 min"
|
||||
difficulty = "intermediate"
|
||||
featured = true
|
||||
|
||||
; Categorization
|
||||
tags[] = "Performance"
|
||||
tags[] = "Web Development"
|
||||
tags[] = "Optimization"
|
||||
|
||||
categories[] = "Technical"
|
||||
categories[] = "Tutorial"
|
||||
|
||||
; SEO
|
||||
meta_description = "Complete guide to web performance optimization"
|
||||
|
||||
; Multi-language versions
|
||||
[no]
|
||||
title = "Optimalisering av Nettsideytelse"
|
||||
summary = "Lær teknikker for å gjøre nettsiden din raskere."
|
||||
|
||||
[fr]
|
||||
title = "Optimisation des Performances Web"
|
||||
summary = "Apprenez à accélérer votre site web."
|
||||
```
|
||||
|
||||
### Documentation Page
|
||||
|
||||
**content/docs/metadata.ini**:
|
||||
```ini
|
||||
title = "Documentation"
|
||||
menu = true
|
||||
menu_order = 2
|
||||
page_template = "list"
|
||||
|
||||
; Custom fields
|
||||
show_toc = true
|
||||
github_url = "https://github.com/user/repo"
|
||||
|
||||
[no]
|
||||
title = "Dokumentasjon"
|
||||
slug = "dokumentasjon"
|
||||
```
|
||||
|
||||
### Portfolio Project
|
||||
|
||||
**content/portfolio/project-name/metadata.ini**:
|
||||
```ini
|
||||
title = "Project Name"
|
||||
date = "2025-11-02"
|
||||
summary = "Brief project description"
|
||||
|
||||
; External links
|
||||
redirect = "https://project-demo.com"
|
||||
github_url = "https://github.com/user/project"
|
||||
|
||||
; Project details
|
||||
client = "Company Name"
|
||||
role = "Lead Developer"
|
||||
technologies[] = "PHP"
|
||||
technologies[] = "HTML"
|
||||
technologies[] = "CSS"
|
||||
|
||||
[no]
|
||||
title = "Prosjektnavn"
|
||||
summary = "Kort prosjektbeskrivelse"
|
||||
```
|
||||
|
||||
### FAQ Section
|
||||
|
||||
**content/faq/metadata.ini**:
|
||||
```ini
|
||||
title = "Frequently Asked Questions"
|
||||
menu = true
|
||||
menu_order = 4
|
||||
page_template = "list-faq"
|
||||
|
||||
[no]
|
||||
title = "Ofte Stilte Spørsmål"
|
||||
slug = "oss"
|
||||
|
||||
[fr]
|
||||
title = "Questions Fréquemment Posées"
|
||||
slug = "faq"
|
||||
```
|
||||
|
||||
## Accessing Metadata in Templates
|
||||
|
||||
### In Page Templates
|
||||
|
||||
Variable: `$pageMetadata`
|
||||
|
||||
```php
|
||||
<?php
|
||||
$title = $pageMetadata['title'] ?? 'Untitled';
|
||||
$author = $pageMetadata['author'] ?? null;
|
||||
$tags = $pageMetadata['tags'] ?? [];
|
||||
?>
|
||||
|
||||
<article>
|
||||
<h1><?= htmlspecialchars($title) ?></h1>
|
||||
|
||||
<?php if ($author): ?>
|
||||
<p class="author">By <?= htmlspecialchars($author) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= $content ?>
|
||||
|
||||
<?php if (!empty($tags)): ?>
|
||||
<div class="tags">
|
||||
<?php foreach ($tags as $tag): ?>
|
||||
<span class="tag"><?= htmlspecialchars($tag) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
```
|
||||
|
||||
### In List Templates
|
||||
|
||||
Variable: `$metadata` (directory metadata), `$items` (item metadata)
|
||||
|
||||
```php
|
||||
<!-- Directory metadata -->
|
||||
<h1><?= htmlspecialchars($metadata['title'] ?? 'Untitled') ?></h1>
|
||||
|
||||
<!-- Items with their metadata -->
|
||||
<?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; ?>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
### Check Syntax
|
||||
|
||||
Test INI file parsing:
|
||||
|
||||
```bash
|
||||
php -r "print_r(parse_ini_file('content/blog/metadata.ini', true));"
|
||||
```
|
||||
|
||||
### Common Errors
|
||||
|
||||
**Unquoted special characters**:
|
||||
```ini
|
||||
; Wrong
|
||||
title = Title with: special characters
|
||||
|
||||
; Correct
|
||||
title = "Title with: special characters"
|
||||
```
|
||||
|
||||
**Missing array brackets**:
|
||||
```ini
|
||||
; Wrong (only last value kept)
|
||||
tags = "PHP"
|
||||
tags = "Tutorial"
|
||||
|
||||
; Correct (array created)
|
||||
tags[] = "PHP"
|
||||
tags[] = "Tutorial"
|
||||
```
|
||||
|
||||
**Invalid section names**:
|
||||
```ini
|
||||
; Wrong
|
||||
[language.no]
|
||||
|
||||
; Correct
|
||||
[no]
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Always Escape Output
|
||||
|
||||
```php
|
||||
<?= htmlspecialchars($metadata['title']) ?>
|
||||
```
|
||||
|
||||
### Provide Defaults
|
||||
|
||||
```php
|
||||
$author = $metadata['author'] ?? 'Anonymous';
|
||||
$tags = $metadata['tags'] ?? [];
|
||||
```
|
||||
|
||||
### Check Before Using
|
||||
|
||||
```php
|
||||
<?php if (isset($metadata['author'])): ?>
|
||||
<p>By <?= htmlspecialchars($metadata['author']) ?></p>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
### Use Consistent Field Names
|
||||
|
||||
Stick to standard names across your site:
|
||||
- `author` not `writer` or `by`
|
||||
- `tags` not `keywords` or `topics`
|
||||
- `summary` not `description` or `excerpt`
|
||||
|
||||
### Document Custom Fields
|
||||
|
||||
Add comments explaining non-obvious fields:
|
||||
|
||||
```ini
|
||||
; Featured articles appear at top of homepage
|
||||
featured = true
|
||||
|
||||
; External demo link (overrides internal page)
|
||||
demo_url = "https://demo.example.com"
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Multi-Language Guide](../how-to/multi-language.md)
|
||||
- [Working with Metadata](../how-to/working-with-metadata.md)
|
||||
- [Template Variables Reference](templates.md)
|
||||
- [Configuration Reference](configuration.md)
|
||||
608
docs/reference/templates.md
Normal file
608
docs/reference/templates.md
Normal file
|
|
@ -0,0 +1,608 @@
|
|||
# Template Reference
|
||||
|
||||
Complete reference for all templates and available variables in FolderWeb.
|
||||
|
||||
## Template System Overview
|
||||
|
||||
FolderWeb uses a fallback template system:
|
||||
1. Check `/custom/templates/{name}.php`
|
||||
2. Fall back to `/app/default/templates/{name}.php`
|
||||
|
||||
Templates are plain PHP files with access to specific variables and the context object.
|
||||
|
||||
## Core Templates
|
||||
|
||||
### base.php
|
||||
|
||||
**Purpose**: HTML wrapper for all pages (header, navigation, footer)
|
||||
**Used**: On every page render
|
||||
**Customizable**: Yes
|
||||
|
||||
**Available Variables**:
|
||||
|
||||
| Variable | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `$content` | string | Rendered page content (HTML) |
|
||||
| `$ctx` | Context | Full context object |
|
||||
| `$currentLang` | string | Current language code (e.g., "en", "no") |
|
||||
| `$navigation` | array | Navigation menu items |
|
||||
| `$homeLabel` | string | Site title |
|
||||
| `$translations` | array | UI translation strings |
|
||||
| `$pageTitle` | string | Current page title |
|
||||
| `$dirName` | string | Parent directory name |
|
||||
| `$pageName` | string | Current page filename |
|
||||
|
||||
**Example**:
|
||||
```php
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= $ctx->currentLang ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($pageTitle) ?> | <?= htmlspecialchars($homeLabel) ?></title>
|
||||
<link rel="stylesheet" href="/app/styles/base.css?v=<?= md5_file(resolveTemplate('base.css', 'styles')) ?>">
|
||||
</head>
|
||||
<body class="section-<?= $dirName ?> page-<?= $pageName ?>">
|
||||
<header>
|
||||
<nav>
|
||||
<a href="<?= $ctx->langPrefix ?>/"><?= $homeLabel ?></a>
|
||||
<?php foreach ($navigation as $item): ?>
|
||||
<a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<?= $content ?>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p><?= $translations['footer_text'] ?></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### page.php
|
||||
|
||||
**Purpose**: Wrapper for single pages and articles
|
||||
**Used**: For file and multi-file pages
|
||||
**Customizable**: Yes
|
||||
|
||||
**Available Variables**:
|
||||
|
||||
| Variable | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `$content` | string | Rendered content (HTML) |
|
||||
| `$pageMetadata` | array | Page metadata from metadata.ini |
|
||||
| `$translations` | array | UI translation strings |
|
||||
|
||||
**Example**:
|
||||
```php
|
||||
<article>
|
||||
<?= $content ?>
|
||||
|
||||
<?php if (!empty($pageMetadata['tags'])): ?>
|
||||
<footer class="tags">
|
||||
<strong><?= $translations['tags'] ?>:</strong>
|
||||
<?php foreach ($pageMetadata['tags'] as $tag): ?>
|
||||
<span class="tag"><?= htmlspecialchars($tag) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</footer>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
```
|
||||
|
||||
## List Templates
|
||||
|
||||
### list.php
|
||||
|
||||
**Purpose**: Simple list view (default)
|
||||
**Used**: Directories with subdirectories
|
||||
**Customizable**: Yes
|
||||
|
||||
**Available Variables**:
|
||||
|
||||
| Variable | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `$items` | array | Array of subdirectory items |
|
||||
| `$metadata` | array | Directory metadata |
|
||||
| `$pageContent` | string | Optional intro content (HTML) |
|
||||
| `$translations` | array | UI translation strings |
|
||||
|
||||
**Item Structure**:
|
||||
|
||||
Each item in `$items` has:
|
||||
|
||||
```php
|
||||
[
|
||||
'title' => 'Item Title', // From metadata or H1
|
||||
'date' => '2. november 2025', // Formatted date
|
||||
'url' => '/blog/post-slug/', // Full URL with language prefix
|
||||
'cover' => '/path/to/cover.jpg', // Cover image path or null
|
||||
'summary' => 'Brief description', // From metadata or null
|
||||
'pdf' => '/path/to/file.pdf', // PDF file path or null
|
||||
'redirect' => 'https://...', // External URL or null
|
||||
]
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```php
|
||||
<?php if (!empty($pageContent)): ?>
|
||||
<div class="page-intro">
|
||||
<?= $pageContent ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="list">
|
||||
<?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; ?>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
```
|
||||
|
||||
### list-grid.php
|
||||
|
||||
**Purpose**: Grid layout with cover images
|
||||
**Used**: Visual blog/portfolio listings
|
||||
**Customizable**: Yes
|
||||
|
||||
**Same variables as list.php**
|
||||
|
||||
Features:
|
||||
- Grid layout
|
||||
- Cover images
|
||||
- PDF download links
|
||||
- "Read more" buttons
|
||||
|
||||
**Example**:
|
||||
```php
|
||||
<div class="list-grid">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<article>
|
||||
<?php if ($item['cover']): ?>
|
||||
<img src="<?= $item['cover'] ?>" alt="">
|
||||
<?php endif; ?>
|
||||
|
||||
<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; ?>
|
||||
|
||||
<div class="actions">
|
||||
<a href="<?= $item['url'] ?>" class="button">
|
||||
<?= $translations['read_more'] ?>
|
||||
</a>
|
||||
|
||||
<?php if ($item['pdf']): ?>
|
||||
<a href="<?= $item['pdf'] ?>" download class="button secondary">
|
||||
Download PDF
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
```
|
||||
|
||||
### list-card-grid.php
|
||||
|
||||
**Purpose**: Card-style grid with external link support
|
||||
**Used**: Portfolios, resource lists
|
||||
**Customizable**: Yes
|
||||
|
||||
**Same variables as list.php**
|
||||
|
||||
Features:
|
||||
- Card-style layout
|
||||
- PDF download support
|
||||
- External redirect support
|
||||
- Cover images
|
||||
|
||||
**Example**:
|
||||
```php
|
||||
<div class="card-grid">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<article class="card">
|
||||
<?php if ($item['cover']): ?>
|
||||
<img src="<?= $item['cover'] ?>" alt="">
|
||||
<?php endif; ?>
|
||||
|
||||
<h2><?= htmlspecialchars($item['title']) ?></h2>
|
||||
|
||||
<?php if ($item['summary']): ?>
|
||||
<p><?= htmlspecialchars($item['summary']) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item['redirect']): ?>
|
||||
<a href="<?= $item['redirect'] ?>"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="button">
|
||||
Visit Site
|
||||
</a>
|
||||
<?php elseif ($item['pdf']): ?>
|
||||
<a href="<?= $item['pdf'] ?>" download class="button">
|
||||
Download PDF
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a href="<?= $item['url'] ?>" class="button">
|
||||
View Details
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
```
|
||||
|
||||
### list-faq.php
|
||||
|
||||
**Purpose**: Expandable FAQ/Q&A format
|
||||
**Used**: FAQ sections, documentation
|
||||
**Customizable**: Yes
|
||||
|
||||
**Same variables as list.php**
|
||||
|
||||
Features:
|
||||
- Collapsible `<details>` elements
|
||||
- Semantic HTML
|
||||
- Keyboard accessible
|
||||
|
||||
**Example**:
|
||||
```php
|
||||
<?php if (!empty($pageContent)): ?>
|
||||
<div class="page-intro">
|
||||
<?= $pageContent ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="faq">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<details>
|
||||
<summary><?= htmlspecialchars($item['title']) ?></summary>
|
||||
|
||||
<?php if ($item['summary']): ?>
|
||||
<p><?= htmlspecialchars($item['summary']) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="<?= $item['url'] ?>">
|
||||
<?= $translations['read_more'] ?>
|
||||
</a>
|
||||
</details>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Context Object
|
||||
|
||||
All templates have access to `$ctx` (Context object):
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `$ctx->contentDir` | string | Path to content directory |
|
||||
| `$ctx->currentLang` | string | Current language code |
|
||||
| `$ctx->defaultLang` | string | Default language code |
|
||||
| `$ctx->availableLangs` | array | Available language codes |
|
||||
| `$ctx->langPrefix` | string | URL language prefix (e.g., "/en" or "") |
|
||||
| `$ctx->requestPath` | string | Current request path |
|
||||
| `$ctx->hasTrailingSlash` | bool | Whether path has trailing slash |
|
||||
| `$ctx->navigation` | array | Navigation menu items (computed) |
|
||||
| `$ctx->homeLabel` | string | Site title (computed) |
|
||||
| `$ctx->translations` | array | UI translations (computed) |
|
||||
|
||||
### Example Usage
|
||||
|
||||
```php
|
||||
<!-- Language switcher -->
|
||||
<?php foreach ($ctx->availableLangs as $lang): ?>
|
||||
<?php
|
||||
$url = $lang === $ctx->defaultLang
|
||||
? '/' . trim($ctx->requestPath, '/')
|
||||
: '/' . $lang . '/' . trim($ctx->requestPath, '/');
|
||||
?>
|
||||
<a href="<?= $url ?>" <?= $lang === $ctx->currentLang ? 'aria-current="true"' : '' ?>>
|
||||
<?= strtoupper($lang) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<nav aria-label="Breadcrumb">
|
||||
<ol>
|
||||
<li><a href="<?= $ctx->langPrefix ?>/"><?= $ctx->homeLabel ?></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>
|
||||
```
|
||||
|
||||
## Navigation Array
|
||||
|
||||
Structure of `$navigation` items:
|
||||
|
||||
```php
|
||||
[
|
||||
[
|
||||
'title' => 'Blog',
|
||||
'url' => '/blog/',
|
||||
'order' => 1
|
||||
],
|
||||
[
|
||||
'title' => 'About',
|
||||
'url' => '/about/',
|
||||
'order' => 2
|
||||
],
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
Already sorted by `menu_order` field.
|
||||
|
||||
## Translation Array
|
||||
|
||||
Structure of `$translations`:
|
||||
|
||||
```php
|
||||
[
|
||||
'home' => 'Home',
|
||||
'read_more' => 'Read more',
|
||||
'categories' => 'Categories',
|
||||
'tags' => 'Tags',
|
||||
'footer_text' => 'Made with FolderWeb',
|
||||
'footer_handcoded' => 'Generated in',
|
||||
'footer_page_time' => 'ms',
|
||||
// ... custom translations
|
||||
]
|
||||
```
|
||||
|
||||
## Helper Functions Available in Templates
|
||||
|
||||
### resolveTemplate()
|
||||
|
||||
Find custom or default template:
|
||||
|
||||
```php
|
||||
$templatePath = resolveTemplate('base', 'templates');
|
||||
$cssPath = resolveTemplate('base.css', 'styles');
|
||||
```
|
||||
|
||||
### htmlspecialchars()
|
||||
|
||||
Escape output (always use for user content):
|
||||
|
||||
```php
|
||||
<?= htmlspecialchars($variable) ?>
|
||||
```
|
||||
|
||||
### Other PHP Functions
|
||||
|
||||
All standard PHP functions are available:
|
||||
- `isset()`, `empty()`
|
||||
- `count()`, `array_filter()`
|
||||
- `date()`, `time()`
|
||||
- String functions
|
||||
- etc.
|
||||
|
||||
## Creating Custom Templates
|
||||
|
||||
### Step 1: Create Template File
|
||||
|
||||
```bash
|
||||
touch custom/templates/my-custom-list.php
|
||||
```
|
||||
|
||||
### Step 2: Use Standard Variables
|
||||
|
||||
Custom list templates receive `$items`, `$metadata`, `$pageContent`, `$translations`.
|
||||
|
||||
### Step 3: Apply in Metadata
|
||||
|
||||
**content/my-section/metadata.ini**:
|
||||
```ini
|
||||
page_template = "my-custom-list"
|
||||
```
|
||||
|
||||
Note: Omit `.php` extension.
|
||||
|
||||
## Template Best Practices
|
||||
|
||||
### Always Escape Output
|
||||
|
||||
```php
|
||||
<!-- Good -->
|
||||
<?= htmlspecialchars($item['title']) ?>
|
||||
|
||||
<!-- Bad (XSS vulnerability) -->
|
||||
<?= $item['title'] ?>
|
||||
```
|
||||
|
||||
### Check Variables Before Use
|
||||
|
||||
```php
|
||||
<?php if (isset($item['cover']) && $item['cover']): ?>
|
||||
<img src="<?= $item['cover'] ?>" alt="">
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
### Use Null Coalescing
|
||||
|
||||
```php
|
||||
$author = $metadata['author'] ?? 'Anonymous';
|
||||
```
|
||||
|
||||
### Semantic HTML
|
||||
|
||||
```php
|
||||
<!-- Good -->
|
||||
<article>
|
||||
<h2><a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a></h2>
|
||||
<time datetime="2025-11-02"><?= $item['date'] ?></time>
|
||||
</article>
|
||||
|
||||
<!-- Avoid -->
|
||||
<div>
|
||||
<span><a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a></span>
|
||||
<span><?= $item['date'] ?></span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Accessibility
|
||||
|
||||
```php
|
||||
<!-- Proper alt text -->
|
||||
<img src="<?= $item['cover'] ?>" alt="<?= htmlspecialchars($item['title']) ?>">
|
||||
|
||||
<!-- ARIA labels -->
|
||||
<nav aria-label="Main navigation">
|
||||
|
||||
<!-- Semantic elements -->
|
||||
<main>
|
||||
<header>
|
||||
<footer>
|
||||
<article>
|
||||
<aside>
|
||||
```
|
||||
|
||||
### Short Echo Tags
|
||||
|
||||
```php
|
||||
<!-- Good (modern PHP) -->
|
||||
<?= $variable ?>
|
||||
|
||||
<!-- Verbose -->
|
||||
<?php echo $variable; ?>
|
||||
```
|
||||
|
||||
### Keep Logic Minimal
|
||||
|
||||
Prepare data in functions, not templates:
|
||||
|
||||
```php
|
||||
<!-- Avoid complex logic in templates -->
|
||||
<?php
|
||||
// Don't do heavy processing here
|
||||
$processedData = someComplexFunction($items);
|
||||
?>
|
||||
|
||||
<!-- Keep templates simple -->
|
||||
<?php foreach ($items as $item): ?>
|
||||
<?= htmlspecialchars($item['title']) ?>
|
||||
<?php endforeach; ?>
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Card with Fallback Content
|
||||
|
||||
```php
|
||||
<article class="card">
|
||||
<?php if ($item['cover']): ?>
|
||||
<img src="<?= $item['cover'] ?>" alt="">
|
||||
<?php else: ?>
|
||||
<div class="placeholder">No image</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h2><?= htmlspecialchars($item['title'] ?? 'Untitled') ?></h2>
|
||||
<p><?= htmlspecialchars($item['summary'] ?? 'No description available.') ?></p>
|
||||
</article>
|
||||
```
|
||||
|
||||
### Conditional Links
|
||||
|
||||
```php
|
||||
<?php if ($item['redirect']): ?>
|
||||
<a href="<?= $item['redirect'] ?>" target="_blank" rel="noopener">
|
||||
External Link
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a href="<?= $item['url'] ?>">
|
||||
Read More
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
### Date Formatting
|
||||
|
||||
```php
|
||||
<!-- Use provided formatted date -->
|
||||
<?php if ($item['date']): ?>
|
||||
<time><?= $item['date'] ?></time>
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
Date is already formatted in Norwegian style by FolderWeb.
|
||||
|
||||
## Debugging Templates
|
||||
|
||||
### Dump Variables
|
||||
|
||||
```php
|
||||
<pre><?php var_dump($items); ?></pre>
|
||||
<pre><?php var_dump($metadata); ?></pre>
|
||||
<pre><?php var_dump($ctx); ?></pre>
|
||||
```
|
||||
|
||||
### Check Template Resolution
|
||||
|
||||
Verify which template is used:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$templatePath = resolveTemplate('base', 'templates');
|
||||
echo "Using template: $templatePath";
|
||||
?>
|
||||
```
|
||||
|
||||
### PHP Error Reporting
|
||||
|
||||
Enable in development:
|
||||
|
||||
```php
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
?>
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Custom Templates Guide](../how-to/custom-templates.md)
|
||||
- [Metadata Reference](metadata.md)
|
||||
- [File Structure Reference](file-structure.md)
|
||||
- [CSS Variables Reference](css-variables.md)
|
||||
Loading…
Add table
Add a link
Reference in a new issue