Add tutorial on adding content Add tutorial on styling Add tutorial on templates Add configuration reference Add metadata reference Add template variables reference Add internationalization reference Add plugin system documentation Add creating templates documentation Add index page
461 lines
11 KiB
Markdown
461 lines
11 KiB
Markdown
# Working with Templates
|
|
|
|
Templates control how your content is presented. FolderWeb uses a simple PHP-based template system—no complex templating languages, just HTML with a sprinkle of PHP.
|
|
|
|
## Template Types
|
|
|
|
FolderWeb has three template levels:
|
|
|
|
### 1. Base Template (`base.php`)
|
|
|
|
The HTML scaffold wrapping every page:
|
|
|
|
```
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>Page Title</title>
|
|
<link rel="stylesheet" href="...">
|
|
</head>
|
|
<body>
|
|
<header><!-- Navigation --></header>
|
|
<main><!-- Page content here --></main>
|
|
<footer><!-- Footer --></footer>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**You typically customize this once** to set up your site structure.
|
|
|
|
### 2. Page Template (`page.php`)
|
|
|
|
Wraps single-page content:
|
|
|
|
```php
|
|
<article>
|
|
<?= $content ?>
|
|
</article>
|
|
```
|
|
|
|
**Customize this** to control how individual pages look.
|
|
|
|
### 3. List Template (`list.php`, `list-grid.php`, `list-compact.php`)
|
|
|
|
Displays multiple items from subdirectories:
|
|
|
|
```php
|
|
<?= $pageContent ?>
|
|
|
|
<div class="item-list">
|
|
<?php foreach ($items as $item): ?>
|
|
<article>
|
|
<h2><a href="<?= $item['url'] ?>"><?= $item['title'] ?></a></h2>
|
|
<p><?= $item['summary'] ?></p>
|
|
</article>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
```
|
|
|
|
**Customize this** to control how lists of content (blogs, portfolios, etc.) appear.
|
|
|
|
## Template Location
|
|
|
|
Templates live in your `custom/` directory:
|
|
|
|
```
|
|
custom/
|
|
└── templates/
|
|
├── base.php # HTML scaffold
|
|
├── page.php # Single page wrapper
|
|
├── list.php # Default list layout
|
|
├── list-grid.php # Grid card layout
|
|
└── list-compact.php # Compact list layout
|
|
```
|
|
|
|
**FolderWeb falls back** to `app/default/templates/` if a custom template doesn't exist.
|
|
|
|
## Customizing the Base Template
|
|
|
|
Let's modify `base.php` to add your site name and custom navigation:
|
|
|
|
**custom/templates/base.php:**
|
|
|
|
```php
|
|
<!DOCTYPE html>
|
|
<html lang="<?= $currentLang ?? 'en' ?>">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><?= htmlspecialchars($pageTitle) ?></title>
|
|
|
|
<?php if (isset($metaDescription)): ?>
|
|
<meta name="description" content="<?= htmlspecialchars($metaDescription) ?>">
|
|
<?php endif; ?>
|
|
|
|
<?php if (isset($socialImageUrl)): ?>
|
|
<meta property="og:image" content="<?= $socialImageUrl ?>">
|
|
<?php endif; ?>
|
|
|
|
<link rel="stylesheet" href="/custom/styles/base.css?v=<?= $cssHash ?? '' ?>">
|
|
|
|
<?php if (isset($pageCssUrl)): ?>
|
|
<link rel="stylesheet" href="<?= $pageCssUrl ?>?v=<?= $pageCssHash ?? '' ?>">
|
|
<?php endif; ?>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<nav>
|
|
<a href="/" class="logo">My Site</a>
|
|
<ul>
|
|
<?php foreach ($navigation as $item): ?>
|
|
<li>
|
|
<a href="<?= htmlspecialchars($item['url']) ?>">
|
|
<?= htmlspecialchars($item['title']) ?>
|
|
</a>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</nav>
|
|
</header>
|
|
|
|
<main>
|
|
<?= $content ?>
|
|
</main>
|
|
|
|
<footer>
|
|
<p>© <?= date('Y') ?> My Site</p>
|
|
<p>
|
|
<small>Generated in <?= number_format($pageLoadTime, 4) ?>s</small>
|
|
</p>
|
|
</footer>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**Key points:**
|
|
- Always escape user content: `htmlspecialchars($var)`
|
|
- Use short echo tags: `<?= $var ?>`
|
|
- Check if variables exist: `isset($var)`
|
|
- The `$content` variable contains the rendered page/list content
|
|
|
|
## Customizing Page Templates
|
|
|
|
The page template wraps your single-page content. Let's add a reading time estimate:
|
|
|
|
**custom/templates/page.php:**
|
|
|
|
```php
|
|
<article>
|
|
<?php if (isset($metadata['title'])): ?>
|
|
<header>
|
|
<h1><?= htmlspecialchars($metadata['title']) ?></h1>
|
|
|
|
<?php if (isset($metadata['date']) && ($metadata['show_date'] ?? true)): ?>
|
|
<time datetime="<?= $metadata['date'] ?>">
|
|
<?= $metadata['formatted_date'] ?>
|
|
</time>
|
|
<?php endif; ?>
|
|
|
|
<?php
|
|
// Estimate reading time (avg 200 words/min)
|
|
$wordCount = str_word_count(strip_tags($content));
|
|
$readingTime = max(1, round($wordCount / 200));
|
|
?>
|
|
<p class="reading-time"><?= $readingTime ?> min read</p>
|
|
</header>
|
|
<?php endif; ?>
|
|
|
|
<div class="content">
|
|
<?= $content ?>
|
|
</div>
|
|
</article>
|
|
```
|
|
|
|
## Customizing List Templates
|
|
|
|
List templates display collections of content. Let's create a custom blog list:
|
|
|
|
**custom/templates/list.php:**
|
|
|
|
```php
|
|
<?php if ($pageContent): ?>
|
|
<div class="page-intro">
|
|
<?= $pageContent ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="blog-list">
|
|
<?php foreach ($items as $item): ?>
|
|
<article class="blog-item">
|
|
<?php if (isset($item['cover_image'])): ?>
|
|
<a href="<?= $item['url'] ?>">
|
|
<img
|
|
src="<?= $item['cover_image'] ?>"
|
|
alt="<?= htmlspecialchars($item['title']) ?>"
|
|
loading="lazy"
|
|
>
|
|
</a>
|
|
<?php endif; ?>
|
|
|
|
<header>
|
|
<h2>
|
|
<a href="<?= $item['url'] ?>">
|
|
<?= htmlspecialchars($item['title']) ?>
|
|
</a>
|
|
</h2>
|
|
|
|
<?php if (isset($item['date'])): ?>
|
|
<time datetime="<?= $item['date'] ?>">
|
|
<?= $item['formatted_date'] ?? $item['date'] ?>
|
|
</time>
|
|
<?php endif; ?>
|
|
</header>
|
|
|
|
<?php if (isset($item['summary'])): ?>
|
|
<p><?= htmlspecialchars($item['summary']) ?></p>
|
|
<?php endif; ?>
|
|
|
|
<a href="<?= $item['url'] ?>">Read more →</a>
|
|
</article>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
```
|
|
|
|
## Choosing List Templates
|
|
|
|
You can create multiple list templates and select them per directory:
|
|
|
|
**Available by default:**
|
|
- `list.php` — Simple vertical list
|
|
- `list-grid.php` — Card grid layout
|
|
- `list-compact.php` — Minimal compact list
|
|
|
|
**Select in metadata.ini:**
|
|
|
|
```ini
|
|
title = "Projects"
|
|
|
|
[settings]
|
|
page_template = "list-grid"
|
|
```
|
|
|
|
Now the `projects/` directory uses the grid layout.
|
|
|
|
## Creating Custom List Templates
|
|
|
|
Let's create a timeline template for a blog:
|
|
|
|
**custom/templates/list-timeline.php:**
|
|
|
|
```php
|
|
<?= $pageContent ?>
|
|
|
|
<div class="timeline">
|
|
<?php
|
|
$currentYear = null;
|
|
foreach ($items as $item):
|
|
$year = isset($item['date']) ? date('Y', strtotime($item['date'])) : null;
|
|
|
|
// Print year marker if it changed
|
|
if ($year && $year !== $currentYear):
|
|
$currentYear = $year;
|
|
?>
|
|
<div class="year-marker">
|
|
<h3><?= $year ?></h3>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<article class="timeline-item">
|
|
<time><?= $item['formatted_date'] ?? '' ?></time>
|
|
<div class="timeline-content">
|
|
<h4><a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a></h4>
|
|
<?php if (isset($item['summary'])): ?>
|
|
<p><?= htmlspecialchars($item['summary']) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</article>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
```
|
|
|
|
**Use in metadata.ini:**
|
|
|
|
```ini
|
|
[settings]
|
|
page_template = "list-timeline"
|
|
```
|
|
|
|
## Available Template Variables
|
|
|
|
Templates have access to these variables (see [Reference: Template Variables](#) for complete list):
|
|
|
|
**Base template:**
|
|
```php
|
|
$content // Rendered page/list HTML
|
|
$pageTitle // Page title for <title> tag
|
|
$metaDescription // SEO description
|
|
$navigation // Array of menu items
|
|
$homeLabel // "Home" link text (translated)
|
|
$currentLang // Current language code
|
|
$languageUrls // Links to other language versions
|
|
$translations // Translated UI strings
|
|
$cssHash // Cache-busting hash for CSS
|
|
$pageCssUrl // Page-specific CSS URL (if exists)
|
|
$pageLoadTime // Page generation time
|
|
```
|
|
|
|
**Page template:**
|
|
```php
|
|
$content // Rendered HTML
|
|
$metadata // Metadata array (title, date, etc.)
|
|
```
|
|
|
|
**List template:**
|
|
```php
|
|
$items // Array of items to display
|
|
$pageContent // Optional intro content from page
|
|
$metadata // Directory metadata
|
|
|
|
// Each $item has:
|
|
$item['url'] // Full URL to item
|
|
$item['title'] // Item title
|
|
$item['summary'] // Short description
|
|
$item['date'] // ISO date (YYYY-MM-DD)
|
|
$item['formatted_date'] // Localized date string
|
|
$item['cover_image'] // Cover image URL (if exists)
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Always Escape Output
|
|
|
|
```php
|
|
<!-- Bad -->
|
|
<h1><?= $title ?></h1>
|
|
|
|
<!-- Good -->
|
|
<h1><?= htmlspecialchars($title) ?></h1>
|
|
```
|
|
|
|
**Exception:** Already-sanitized HTML like `$content` (rendered from Markdown).
|
|
|
|
### 2. Check Variables Exist
|
|
|
|
```php
|
|
<!-- Bad -->
|
|
<p><?= $metadata['summary'] ?></p>
|
|
|
|
<!-- Good -->
|
|
<?php if (isset($metadata['summary'])): ?>
|
|
<p><?= htmlspecialchars($metadata['summary']) ?></p>
|
|
<?php endif; ?>
|
|
```
|
|
|
|
### 3. Use Short Echo Tags
|
|
|
|
```php
|
|
<!-- Verbose -->
|
|
<?php echo htmlspecialchars($title); ?>
|
|
|
|
<!-- Concise -->
|
|
<?= htmlspecialchars($title) ?>
|
|
```
|
|
|
|
### 4. Keep Logic Minimal
|
|
|
|
Templates should display data, not process it. Complex logic belongs in plugins.
|
|
|
|
```php
|
|
<!-- Bad: complex logic in template -->
|
|
<?php
|
|
$posts = array_filter($items, function($item) {
|
|
return strtotime($item['date']) > strtotime('-30 days');
|
|
});
|
|
usort($posts, function($a, $b) {
|
|
return strcmp($b['date'], $a['date']);
|
|
});
|
|
?>
|
|
|
|
<!-- Good: prepare data in a plugin, display in template -->
|
|
<?php foreach ($recentPosts as $post): ?>
|
|
...
|
|
<?php endforeach; ?>
|
|
```
|
|
|
|
### 5. Use Semantic HTML
|
|
|
|
```php
|
|
<!-- Bad -->
|
|
<div class="title">Title</div>
|
|
<div class="content">Content</div>
|
|
|
|
<!-- Good -->
|
|
<article>
|
|
<h1>Title</h1>
|
|
<div class="content">Content</div>
|
|
</article>
|
|
```
|
|
|
|
## Practical Examples
|
|
|
|
### Simple Portfolio Page Template
|
|
|
|
```php
|
|
<article class="portfolio-item">
|
|
<header>
|
|
<?php if (isset($metadata['cover_image'])): ?>
|
|
<img src="<?= $metadata['cover_image'] ?>" alt="">
|
|
<?php endif; ?>
|
|
|
|
<h1><?= htmlspecialchars($metadata['title'] ?? 'Untitled') ?></h1>
|
|
</header>
|
|
|
|
<div class="content">
|
|
<?= $content ?>
|
|
</div>
|
|
|
|
<?php if (isset($metadata['project_url'])): ?>
|
|
<footer>
|
|
<a href="<?= htmlspecialchars($metadata['project_url']) ?>"
|
|
class="button">View Project →</a>
|
|
</footer>
|
|
<?php endif; ?>
|
|
</article>
|
|
```
|
|
|
|
### Card Grid List Template
|
|
|
|
```php
|
|
<?= $pageContent ?>
|
|
|
|
<div class="card-grid">
|
|
<?php foreach ($items as $item): ?>
|
|
<article class="card">
|
|
<?php if (isset($item['cover_image'])): ?>
|
|
<img src="<?= $item['cover_image'] ?>" alt="" loading="lazy">
|
|
<?php endif; ?>
|
|
|
|
<div class="card-content">
|
|
<h3>
|
|
<a href="<?= $item['url'] ?>">
|
|
<?= htmlspecialchars($item['title']) ?>
|
|
</a>
|
|
</h3>
|
|
|
|
<?php if (isset($item['summary'])): ?>
|
|
<p><?= htmlspecialchars($item['summary']) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</article>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
```
|
|
|
|
## What's Next?
|
|
|
|
You now know how to customize templates. Next, learn about:
|
|
- **[Template Variables Reference](#)** — Complete list of available variables
|
|
- **[Creating Plugins](#)** — Extend functionality and add custom data to templates
|
|
- **[Internationalization](#)** — Build multilingual sites
|
|
|
|
Or explore the examples in `app/default/content/examples/templates-demo/` to see templates in action.
|