Update getting started documentation
Remove redundant quick start section Simplify requirements and installation Clarify local development setup Streamline first page creation Add shared hosting deployment instructions Update tutorial content structure Improve content format explanations Clarify asset handling Simplify metadata documentation Update styling documentation Improve template explanations Remove unnecessary examples Make documentation more concise
This commit is contained in:
parent
cef370ca0c
commit
8855a9b5be
4 changed files with 226 additions and 759 deletions
|
|
@ -1,137 +1,70 @@
|
|||
# Getting Started with FolderWeb
|
||||
# Getting Started
|
||||
|
||||
Welcome to FolderWeb—a delightfully minimal PHP framework that turns your folder structure into a website. No build steps, no package managers, no JavaScript frameworks. Just files and folders doing what they do best.
|
||||
## Requirements
|
||||
|
||||
## What You Need
|
||||
- PHP 8.4+
|
||||
- Apache with mod_rewrite
|
||||
- A text editor
|
||||
|
||||
- **PHP 8.4+** — Modern PHP with all the good stuff
|
||||
- **A web server** — Apache, Nginx, or just PHP's built-in server for local development
|
||||
- **A text editor** — Whatever makes you happy
|
||||
|
||||
That's it. No npm, no webpack, no node_modules folder the size of the observable universe.
|
||||
|
||||
## Quick Start (5 Minutes)
|
||||
|
||||
### 1. Get the Code
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
git clone https://github.com/yourusername/folderweb.git
|
||||
cd folderweb
|
||||
```
|
||||
|
||||
### 2. Set Up Your Custom Directory
|
||||
|
||||
FolderWeb separates framework code (`app/`) from your customizations (`custom/`). Copy the defaults to get started:
|
||||
|
||||
**Unix/Linux/macOS:**
|
||||
```bash
|
||||
cp -r app/default custom
|
||||
```
|
||||
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
Copy-Item -Recurse app\default custom
|
||||
```
|
||||
|
||||
**Or just:** Copy the `app/default` folder and rename it to `custom` using your file manager.
|
||||
|
||||
### 3. Create Your Content Directory
|
||||
|
||||
```bash
|
||||
mkdir content
|
||||
```
|
||||
|
||||
This is where your actual website content lives—separate from the framework.
|
||||
|
||||
### 4. Fire It Up Locally
|
||||
|
||||
**Using Podman (Recommended):**
|
||||
|
||||
If you have [Podman](https://podman.io/) installed, there's a ready-made setup:
|
||||
## Local Development
|
||||
|
||||
```bash
|
||||
cd devel
|
||||
podman-compose up
|
||||
podman-compose up -d
|
||||
```
|
||||
|
||||
Visit `http://localhost:8080` and you're live.
|
||||
|
||||
**Using PHP's Built-in Server:**
|
||||
Or with PHP's built-in server:
|
||||
|
||||
```bash
|
||||
php -S localhost:8080 -t .
|
||||
```
|
||||
|
||||
Simple, but you'll need to configure routing manually for production.
|
||||
Visit `http://localhost:8080`.
|
||||
|
||||
### 5. Make Your First Page
|
||||
## Your First Page
|
||||
|
||||
Create a file at `content/hello.md`:
|
||||
Create `content/hello.md`:
|
||||
|
||||
```markdown
|
||||
# Hello, World!
|
||||
|
||||
This is my first page. Look ma, no build step!
|
||||
This is my first page.
|
||||
```
|
||||
|
||||
Visit `http://localhost:8080/hello/` and there it is.
|
||||
Visit `http://localhost:8080/hello/`.
|
||||
|
||||
**Pro tip:** Notice the trailing slash? FolderWeb enforces them. Folders are folders, after all.
|
||||
## Deploying to Shared Hosting
|
||||
|
||||
## What Just Happened?
|
||||
### Recommended Structure
|
||||
|
||||
FolderWeb looked at your request (`/hello/`), found `content/hello.md`, processed the Markdown into HTML, wrapped it in a template, and served it. All in milliseconds.
|
||||
Keep the framework and content outside the web root. Use symlinks to expose them:
|
||||
|
||||
The magic is simple:
|
||||
- **Folders = URLs:** Your directory structure is your site structure
|
||||
- **Files = Content:** Drop a `.md`, `.html`, or `.php` file and it renders
|
||||
- **Templates = Presentation:** HTML wrappers that make everything pretty
|
||||
- **Metadata = Configuration:** Optional `.ini` files for titles, dates, and settings
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you're up and running, you can:
|
||||
|
||||
1. **[Add more content](#)** — Learn about Markdown files, metadata, and organizing pages
|
||||
2. **[Customize the design](#)** — Edit templates and CSS to make it yours
|
||||
3. **[Deploy to production](#)** — Get your site online with a web host
|
||||
|
||||
Or just keep making pages. It's your website—do what makes you happy.
|
||||
|
||||
---
|
||||
|
||||
## Deploying to Production
|
||||
|
||||
When you're ready to go live, you'll need a web host with PHP support. Look for:
|
||||
|
||||
- **PHP 8.4+** (or at least 8.0, but why settle?)
|
||||
- **Apache or Nginx** with mod_rewrite or equivalent
|
||||
- **SSH access** (optional, but makes life easier)
|
||||
|
||||
Most shared hosting providers offer this. Avoid anything that says "managed WordPress only"—you're too cool for that.
|
||||
|
||||
### Deployment with Symlinks (Recommended)
|
||||
|
||||
Keep the framework separate from the web root for easy upgrades:
|
||||
|
||||
```bash
|
||||
# Your server structure:
|
||||
/home/yourusername/
|
||||
```
|
||||
/home/username/
|
||||
├── folderweb/ # Git repo (not public)
|
||||
│ ├── app/
|
||||
│ └── custom/
|
||||
├── content/ # Your content (not public)
|
||||
└── public_html/ # Web root (public)
|
||||
├── app -> ../folderweb/app/ # Symlink
|
||||
├── custom -> ../folderweb/custom/ # Symlink
|
||||
└── content -> ../content/ # Symlink
|
||||
└── public_html/ # Web root
|
||||
├── app -> ../folderweb/app/
|
||||
├── custom -> ../folderweb/custom/
|
||||
└── content -> ../content/
|
||||
```
|
||||
|
||||
**Why?** When you update FolderWeb, just `git pull` and you're done. No copying files, no risk of overwriting customizations.
|
||||
To update FolderWeb, `git pull` in the repo directory.
|
||||
|
||||
### Apache Configuration
|
||||
### Apache
|
||||
|
||||
If your host lets you use `.htaccess`, FolderWeb will handle routing automatically. Otherwise, add this to your Apache config:
|
||||
FolderWeb includes an `.htaccess` file that handles routing automatically. If your host requires manual configuration:
|
||||
|
||||
```apache
|
||||
<Directory /path/to/public_html>
|
||||
|
|
@ -142,24 +75,8 @@ If your host lets you use `.htaccess`, FolderWeb will handle routing automatical
|
|||
</Directory>
|
||||
```
|
||||
|
||||
### Nginx Configuration
|
||||
## Next Steps
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
try_files $uri $uri/ /app/router.php?$query_string;
|
||||
}
|
||||
```
|
||||
|
||||
That's it. Upload your files, point your domain, and you're live.
|
||||
|
||||
---
|
||||
|
||||
## What's Next?
|
||||
|
||||
Ready to dive deeper? Head to the [Tutorial](#) to learn how to:
|
||||
- Organize content with folders and metadata
|
||||
- Customize templates and styles
|
||||
- Add multilingual support
|
||||
- Create list views and navigation menus
|
||||
|
||||
Or jump straight to the [Reference](#) if you're the "read the manual" type.
|
||||
- [Adding content](../02-tutorial/01-adding-content.md) — Pages, metadata, and content formats
|
||||
- [Styling](../02-tutorial/02-styling.md) — Customize the look
|
||||
- [Templates](../02-tutorial/03-templates.md) — Control how content is presented
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Adding Content
|
||||
|
||||
FolderWeb turns your folder structure into a website. That's not marketing speak—it's literally how it works. Let's explore what that means in practice.
|
||||
FolderWeb turns your folder structure into a website. Every file becomes a page, every folder becomes a URL path.
|
||||
|
||||
## The Basic Idea
|
||||
|
||||
|
|
@ -12,16 +12,14 @@ content/
|
|||
└── contact.html → yoursite.com/contact/
|
||||
```
|
||||
|
||||
Every file becomes a page. Every folder becomes a URL path. No configuration required.
|
||||
No configuration required.
|
||||
|
||||
## File Types
|
||||
## Content Formats
|
||||
|
||||
FolderWeb recognizes three content types:
|
||||
FolderWeb supports three content formats. You can use them individually or mix them within a single page.
|
||||
|
||||
### Markdown (`.md`)
|
||||
|
||||
The bread and butter of content creation. Write in Markdown, get HTML.
|
||||
|
||||
```markdown
|
||||
# My Page Title
|
||||
|
||||
|
|
@ -29,46 +27,30 @@ This is a paragraph with **bold** and *italic* text.
|
|||
|
||||
- Lists work
|
||||
- And so do [links](https://example.com)
|
||||
|
||||
## Subheading
|
||||
|
||||
More content here.
|
||||
```
|
||||
|
||||
FolderWeb uses [Parsedown](https://parsedown.org/) to convert Markdown to HTML, with caching for performance.
|
||||
Converted to HTML using [Parsedown](https://parsedown.org/) with caching.
|
||||
|
||||
### HTML (`.html`)
|
||||
|
||||
When you need more control or already have HTML:
|
||||
|
||||
```html
|
||||
<h1>My Page</h1>
|
||||
<p>This is plain HTML.</p>
|
||||
<p>Plain HTML, rendered as-is.</p>
|
||||
<div class="custom-widget">
|
||||
Whatever you want.
|
||||
Full control over markup.
|
||||
</div>
|
||||
```
|
||||
|
||||
### PHP (`.php`)
|
||||
|
||||
For dynamic content:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$currentYear = date('Y');
|
||||
echo "<p>Copyright {$currentYear}</p>";
|
||||
?>
|
||||
|
||||
<h2>Latest Posts</h2>
|
||||
<?php
|
||||
$posts = ['Post 1', 'Post 2', 'Post 3'];
|
||||
foreach ($posts as $post) {
|
||||
echo "<li>{$post}</li>";
|
||||
}
|
||||
?>
|
||||
```
|
||||
|
||||
**Important:** PHP files are executed, so be careful with user input and security.
|
||||
PHP files are executed server-side. Be careful with user input and security.
|
||||
|
||||
## Folder Structure as URL Structure
|
||||
|
||||
|
|
@ -90,14 +72,13 @@ content/
|
|||
└── index.md → /projects/my-project/
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Folder names become URL slugs
|
||||
- Date prefixes (`YYYY-MM-DD-`) are stripped from URLs but preserved for sorting
|
||||
- Trailing slashes are enforced (FolderWeb redirects `/about` → `/about/`)
|
||||
- Trailing slashes are enforced (`/about` redirects to `/about/`)
|
||||
|
||||
## Multiple Files in One Page
|
||||
|
||||
You can combine multiple content files in a single directory. They render as a single page, combined in filename order.
|
||||
Multiple content files in one directory render as a single page, combined in filename order.
|
||||
|
||||
**Prefix filenames with numbers to control the order:**
|
||||
|
||||
|
|
@ -111,11 +92,11 @@ content/portfolio/
|
|||
|
||||
All four files render as one page at `/portfolio/`. You can mix `.md`, `.html`, and `.php` freely — use whichever format fits each section best.
|
||||
|
||||
**Why number prefixes?** Files are sorted using natural sort (`strnatcmp`), so `10-` comes before `20-` comes before `30-`. Using increments of 10 leaves room to insert new sections later without renaming existing files. Files without a number prefix sort after numbered files.
|
||||
**Why number prefixes?** Files are sorted using natural sort, so `10-` comes before `20-` comes before `30-`. Using increments of 10 leaves room to insert new sections later without renaming existing files. Files without a number prefix sort after numbered files.
|
||||
|
||||
## Dates in Folder Names
|
||||
|
||||
FolderWeb automatically extracts dates from folder names:
|
||||
FolderWeb extracts dates from folder names automatically:
|
||||
|
||||
```
|
||||
content/blog/
|
||||
|
|
@ -124,17 +105,16 @@ content/blog/
|
|||
└── 2025-01-01-new-year-post/ # Date: January 1, 2025
|
||||
```
|
||||
|
||||
The date format is `YYYY-MM-DD-` and it's **stripped from the URL**:
|
||||
The `YYYY-MM-DD-` prefix is stripped from the URL:
|
||||
|
||||
- Folder: `2024-12-15-my-first-post`
|
||||
- URL: `/blog/my-first-post/`
|
||||
- Date: Extracted and available in templates
|
||||
|
||||
Dates are automatically formatted based on your language (e.g., "15. desember 2024" in Norwegian).
|
||||
Dates are formatted based on your configured language.
|
||||
|
||||
## Adding Assets (Images, Files)
|
||||
## Assets
|
||||
|
||||
Drop assets directly in your content folders:
|
||||
Drop images and other files directly in your content folders:
|
||||
|
||||
```
|
||||
content/blog/my-post/
|
||||
|
|
@ -144,17 +124,17 @@ content/blog/my-post/
|
|||
└── styles.css # Page-specific styles
|
||||
```
|
||||
|
||||
**Reference in Markdown:**
|
||||
Reference in Markdown:
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
**Cover images:** Name your cover image `cover.jpg`, `cover.png`, or `cover.webp`. FolderWeb automatically uses it in list views and social media meta tags.
|
||||
**Cover images:** Name your image `cover.jpg`, `cover.png`, or `cover.webp`. FolderWeb uses it automatically in list views and social media meta tags.
|
||||
|
||||
## Metadata Files
|
||||
## Metadata
|
||||
|
||||
Add a `metadata.ini` file to configure pages:
|
||||
Add a `metadata.ini` file to configure a page:
|
||||
|
||||
```
|
||||
content/blog/my-post/
|
||||
|
|
@ -162,7 +142,7 @@ content/blog/my-post/
|
|||
└── metadata.ini
|
||||
```
|
||||
|
||||
**Basic metadata:**
|
||||
Basic metadata:
|
||||
|
||||
```ini
|
||||
title = "My Awesome Post"
|
||||
|
|
@ -170,84 +150,71 @@ summary = "A short description for list views"
|
|||
date = "2024-12-15"
|
||||
```
|
||||
|
||||
**More options:**
|
||||
More options:
|
||||
|
||||
```ini
|
||||
title = "My Post"
|
||||
summary = "Short description"
|
||||
date = "2024-12-15"
|
||||
search_description = "SEO-friendly description for search engines"
|
||||
search_description = "SEO description for search engines"
|
||||
menu = 1 # Show in navigation menu
|
||||
menu_order = 10 # Menu position (lower = first)
|
||||
|
||||
[settings]
|
||||
show_date = true # Display date on page
|
||||
hide_list = false # Don't show list view even if subdirectories exist
|
||||
hide_list = false # Force page view even with subdirectories
|
||||
```
|
||||
|
||||
See the [Metadata Reference](#) for all available options.
|
||||
See the [Metadata Reference](../03-reference/02-metadata.md) for all options.
|
||||
|
||||
## List Views vs. Page Views
|
||||
|
||||
FolderWeb automatically decides whether to show a **list** or a **page**:
|
||||
|
||||
**List view** (directory has subdirectories):
|
||||
**List view** — directory has subdirectories:
|
||||
```
|
||||
content/blog/
|
||||
├── index.md # Intro content
|
||||
├── metadata.ini # List configuration
|
||||
├── index.md # Intro content (shown above list)
|
||||
├── metadata.ini
|
||||
├── 2024-12-15-first-post/
|
||||
└── 2024-12-20-second-post/
|
||||
```
|
||||
Result: `/blog/` shows a list of posts.
|
||||
|
||||
Result: `/blog/` shows a list of posts with the intro content at the top.
|
||||
|
||||
**Page view** (directory has only files):
|
||||
**Page view** — directory has only files:
|
||||
```
|
||||
content/about/
|
||||
└── index.md
|
||||
```
|
||||
Result: `/about/` shows the page content.
|
||||
|
||||
Result: `/about/` shows just the page content.
|
||||
|
||||
**Override:** Use `hide_list = true` in `metadata.ini` to force page view even with subdirectories.
|
||||
Set `hide_list = true` in `metadata.ini` to force page view even when subdirectories exist.
|
||||
|
||||
## Custom URL Slugs
|
||||
|
||||
Don't like your folder name as the URL? Override it with metadata:
|
||||
|
||||
```ini
|
||||
slug = "custom-url-path"
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
content/blog/2024-12-15-very-long-title-that-i-regret/
|
||||
└── metadata.ini
|
||||
```
|
||||
Override the folder name as URL with metadata:
|
||||
|
||||
```ini
|
||||
slug = "short-title"
|
||||
```
|
||||
|
||||
URL becomes `/blog/short-title/` instead of `/blog/very-long-title-that-i-regret/`.
|
||||
Folder `2024-12-15-very-long-title/` becomes `/blog/short-title/` instead of `/blog/very-long-title/`.
|
||||
|
||||
## Navigation Menus
|
||||
## Navigation Menu
|
||||
|
||||
Add pages to the navigation menu with metadata:
|
||||
Add pages to the navigation menu:
|
||||
|
||||
```ini
|
||||
title = "About Us"
|
||||
menu = 1 # Show in menu
|
||||
menu_order = 20 # Position (lower numbers first)
|
||||
menu_order = 20 # Position (lower = first)
|
||||
```
|
||||
|
||||
Menu items are sorted by `menu_order`, then alphabetically by title.
|
||||
Items are sorted by `menu_order`, then alphabetically by title.
|
||||
|
||||
## Practical Examples
|
||||
## Examples
|
||||
|
||||
### Simple Blog Post
|
||||
### Blog Post
|
||||
|
||||
```
|
||||
content/blog/2024-12-15-my-first-post/
|
||||
|
|
@ -260,7 +227,7 @@ content/blog/2024-12-15-my-first-post/
|
|||
```markdown
|
||||
# My First Post
|
||||
|
||||
This is my blog post content.
|
||||
Blog post content goes here.
|
||||
|
||||

|
||||
```
|
||||
|
|
@ -271,7 +238,7 @@ title = "My First Post"
|
|||
summary = "An introduction to my blog"
|
||||
```
|
||||
|
||||
### Multi-Section Page
|
||||
### Multi-Format Page
|
||||
|
||||
```
|
||||
content/services/
|
||||
|
|
@ -283,28 +250,8 @@ content/services/
|
|||
|
||||
All content files render together as one page at `/services/`, in number-prefix order.
|
||||
|
||||
### Documentation Site
|
||||
## Next Steps
|
||||
|
||||
```
|
||||
content/docs/
|
||||
├── index.md
|
||||
├── getting-started/
|
||||
│ └── index.md
|
||||
├── tutorial/
|
||||
│ ├── index.md
|
||||
│ ├── basics.md
|
||||
│ └── advanced.md
|
||||
└── reference/
|
||||
└── index.md
|
||||
```
|
||||
|
||||
Creates a hierarchical documentation structure with automatic list views.
|
||||
|
||||
## What's Next?
|
||||
|
||||
Now that you know how to add content, learn how to:
|
||||
- **[Customize styling](#)** — Make it look like your own
|
||||
- **[Create templates](#)** — Control how content is presented
|
||||
- **[Add multilingual support](#)** — Reach a global audience
|
||||
|
||||
Or jump to the [Reference](#) for detailed documentation on metadata, templates, and configuration.
|
||||
- [Styling](02-styling.md) — Customize colors, fonts, and layout
|
||||
- [Templates](03-templates.md) — Control how content is presented
|
||||
- [Internationalization](../03-reference/04-internationalization.md) — Multi-language support
|
||||
|
|
|
|||
|
|
@ -1,62 +1,51 @@
|
|||
# Styling Your Site
|
||||
# Styling
|
||||
|
||||
FolderWeb embraces modern CSS—no preprocessors, no build steps, just the good stuff browsers support today. Let's make your site look exactly how you want.
|
||||
FolderWeb uses modern CSS — no preprocessors, no build steps. Edit CSS files directly and refresh.
|
||||
|
||||
## Where Styles Live
|
||||
|
||||
FolderWeb has a simple style hierarchy:
|
||||
|
||||
```
|
||||
custom/
|
||||
├── styles/
|
||||
│ ├── base.css # Your main stylesheet
|
||||
│ └── custom-theme.css # Additional stylesheets (optional)
|
||||
└── content/
|
||||
└── my-page/
|
||||
└── styles.css # Page-specific styles (optional)
|
||||
└── styles/
|
||||
└── base.css # Your main stylesheet
|
||||
```
|
||||
|
||||
Page-specific styles can be placed alongside content:
|
||||
|
||||
```
|
||||
content/portfolio/
|
||||
├── index.md
|
||||
└── styles.css # Loaded only on this page
|
||||
```
|
||||
|
||||
**Loading order:**
|
||||
1. `custom/styles/base.css` — Your main styles (always loaded)
|
||||
2. Page-specific `styles.css` — If it exists in the content directory
|
||||
1. `custom/styles/base.css` — always loaded
|
||||
2. Page-specific `styles.css` — if present in the content directory
|
||||
|
||||
## Editing the Main Stylesheet
|
||||
|
||||
Start by editing `custom/styles/base.css`. This is where your site-wide styles live.
|
||||
|
||||
**Default structure:**
|
||||
Edit `custom/styles/base.css` for site-wide styles:
|
||||
|
||||
```css
|
||||
/* CSS Variables (Design Tokens) */
|
||||
:root {
|
||||
--color-primary: oklch(60% 0.15 250);
|
||||
--color-text: oklch(20% 0 0);
|
||||
--color-bg: oklch(98% 0 0);
|
||||
--font-base: system-ui, -apple-system, sans-serif;
|
||||
--font-mono: 'SF Mono', Monaco, monospace;
|
||||
--spacing-unit: 1rem;
|
||||
}
|
||||
|
||||
/* Base Typography */
|
||||
body {
|
||||
font-family: var(--font-base);
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* More styles... */
|
||||
```
|
||||
|
||||
Edit these values to match your brand. No build step, no compilation—just save and refresh.
|
||||
## CSS Variables
|
||||
|
||||
## Modern CSS Features
|
||||
|
||||
FolderWeb encourages using modern CSS features. Here's what you should know:
|
||||
|
||||
### CSS Variables (Custom Properties)
|
||||
|
||||
Define once, use everywhere:
|
||||
Define design tokens as variables, use them throughout:
|
||||
|
||||
```css
|
||||
:root {
|
||||
|
|
@ -72,32 +61,26 @@ Define once, use everywhere:
|
|||
}
|
||||
```
|
||||
|
||||
### OKLCH Colors
|
||||
## OKLCH Colors
|
||||
|
||||
Use modern color spaces for better color manipulation:
|
||||
FolderWeb's default styles use OKLCH for perceptually uniform colors:
|
||||
|
||||
```css
|
||||
/* Traditional RGB/HSL */
|
||||
--old: rgb(100, 150, 200);
|
||||
--old-hsl: hsl(210, 50%, 60%);
|
||||
|
||||
/* Modern OKLCH (better perceptual uniformity) */
|
||||
--new: oklch(65% 0.1 250);
|
||||
--color: oklch(65% 0.1 250);
|
||||
```
|
||||
|
||||
**Format:** `oklch(lightness chroma hue / alpha)`
|
||||
Format: `oklch(lightness chroma hue / alpha)`
|
||||
- **Lightness:** 0% (black) to 100% (white)
|
||||
- **Chroma:** 0 (gray) to ~0.4 (vivid)
|
||||
- **Hue:** 0-360 degrees (color wheel)
|
||||
- **Hue:** 0–360 degrees
|
||||
|
||||
### CSS Nesting
|
||||
## CSS Nesting
|
||||
|
||||
Nest related styles without preprocessors:
|
||||
Nest related styles without a preprocessor:
|
||||
|
||||
```css
|
||||
.card {
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
& h2 {
|
||||
margin-top: 0;
|
||||
|
|
@ -113,50 +96,17 @@ Nest related styles without preprocessors:
|
|||
}
|
||||
```
|
||||
|
||||
### Fluid Typography with `clamp()`
|
||||
## Fluid Typography
|
||||
|
||||
Responsive sizing without media queries:
|
||||
|
||||
```css
|
||||
h1 {
|
||||
/* Min 2rem, ideal 5vw, max 4rem */
|
||||
font-size: clamp(2rem, 5vw, 4rem);
|
||||
}
|
||||
|
||||
p {
|
||||
/* Min 1rem, ideal 1.125rem, max 1.25rem */
|
||||
font-size: clamp(1rem, 1.125rem, 1.25rem);
|
||||
}
|
||||
```
|
||||
|
||||
### Logical Properties
|
||||
|
||||
Use logical properties for better internationalization:
|
||||
|
||||
```css
|
||||
/* Old way (assumes left-to-right) */
|
||||
.old {
|
||||
margin-left: 1rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
/* New way (respects text direction) */
|
||||
.new {
|
||||
margin-inline-start: 1rem;
|
||||
padding-inline-end: 2rem;
|
||||
}
|
||||
```
|
||||
|
||||
**Common logical properties:**
|
||||
- `margin-inline-start` / `margin-inline-end` (left/right in LTR)
|
||||
- `margin-block-start` / `margin-block-end` (top/bottom)
|
||||
- `padding-inline` / `padding-block`
|
||||
- `inline-size` (width)
|
||||
- `block-size` (height)
|
||||
|
||||
### Grid Layout
|
||||
|
||||
Use CSS Grid for layout (not flexbox for everything):
|
||||
## Grid Layout
|
||||
|
||||
```css
|
||||
.page-layout {
|
||||
|
|
@ -174,17 +124,9 @@ Use CSS Grid for layout (not flexbox for everything):
|
|||
}
|
||||
```
|
||||
|
||||
## Classless CSS Philosophy
|
||||
## Classless CSS
|
||||
|
||||
FolderWeb defaults to **classless CSS**—styling HTML elements directly instead of adding classes everywhere.
|
||||
|
||||
**Good (classless):**
|
||||
```html
|
||||
<article>
|
||||
<h1>Page Title</h1>
|
||||
<p>Content here.</p>
|
||||
</article>
|
||||
```
|
||||
FolderWeb defaults to styling HTML elements directly rather than relying on classes. Since content comes from Markdown and HTML files, this means most content is styled without any classes:
|
||||
|
||||
```css
|
||||
article {
|
||||
|
|
@ -202,22 +144,11 @@ article {
|
|||
}
|
||||
```
|
||||
|
||||
**Less good (class-heavy):**
|
||||
```html
|
||||
<article class="article-container">
|
||||
<h1 class="article-title">Page Title</h1>
|
||||
<p class="article-text">Content here.</p>
|
||||
</article>
|
||||
```
|
||||
|
||||
**When to use classes:**
|
||||
- Component variants (`.button-primary`, `.button-secondary`)
|
||||
- JavaScript hooks (`.js-toggle`)
|
||||
- Utility overrides (`.visually-hidden`)
|
||||
Use classes when you need component variants, JavaScript hooks, or utility overrides.
|
||||
|
||||
## Page-Specific Styles
|
||||
|
||||
Add `styles.css` to a content directory for page-specific styling:
|
||||
Add a `styles.css` file to a content directory:
|
||||
|
||||
```
|
||||
content/portfolio/
|
||||
|
|
@ -225,9 +156,7 @@ content/portfolio/
|
|||
└── styles.css
|
||||
```
|
||||
|
||||
**styles.css:**
|
||||
```css
|
||||
/* Scoped to this page only */
|
||||
.portfolio-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
|
|
@ -235,42 +164,31 @@ content/portfolio/
|
|||
}
|
||||
```
|
||||
|
||||
FolderWeb automatically loads and includes page-specific styles with cache-busting:
|
||||
FolderWeb loads it automatically with cache-busting:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/portfolio/styles.css?v=abc123def">
|
||||
<link rel="stylesheet" href="/portfolio/styles.css?v=abc123">
|
||||
```
|
||||
|
||||
## Page-Specific Scripts
|
||||
|
||||
For small progressive enhancements, you can add a `script.js` file to a content directory:
|
||||
Add a `script.js` file to a content directory for progressive enhancement:
|
||||
|
||||
```
|
||||
content/portfolio/
|
||||
├── index.md
|
||||
├── styles.css
|
||||
└── script.js
|
||||
```
|
||||
|
||||
**script.js:**
|
||||
```js
|
||||
// Small enhancement for this page only
|
||||
document.querySelector('.portfolio-grid')?.addEventListener('click', (e) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
FolderWeb automatically loads the script with `defer` (non-blocking) and cache-busting:
|
||||
Loaded automatically with `defer`:
|
||||
|
||||
```html
|
||||
<script defer src="/portfolio/script.js?v=abc123def"></script>
|
||||
<script defer src="/portfolio/script.js?v=abc123"></script>
|
||||
```
|
||||
|
||||
The script tag is placed before `</body>`, so it runs after the page has been parsed. This is ideal for progressive enhancement — the page works without JavaScript, but gets enhanced when it's available.
|
||||
|
||||
## Dark Mode
|
||||
|
||||
Add dark mode with CSS variables and `prefers-color-scheme`:
|
||||
Use CSS variables with `prefers-color-scheme`:
|
||||
|
||||
```css
|
||||
:root {
|
||||
|
|
@ -286,21 +204,11 @@ Add dark mode with CSS variables and `prefers-color-scheme`:
|
|||
}
|
||||
```
|
||||
|
||||
All colors using the variables automatically adapt.
|
||||
|
||||
## Responsive Design
|
||||
|
||||
Use fluid layouts and relative units:
|
||||
Prefer fluid CSS over fixed breakpoints:
|
||||
|
||||
```css
|
||||
/* Bad: fixed breakpoints */
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
width: 750px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Good: fluid and flexible */
|
||||
.container {
|
||||
width: min(90%, 1200px);
|
||||
padding: clamp(1rem, 3vw, 3rem);
|
||||
|
|
@ -313,147 +221,19 @@ Use fluid layouts and relative units:
|
|||
}
|
||||
```
|
||||
|
||||
**Use media queries sparingly:**
|
||||
- Layout changes (sidebar position)
|
||||
- Font size adjustments
|
||||
- Complex interactions
|
||||
|
||||
**Prefer fluid CSS:**
|
||||
- Spacing (`clamp()`)
|
||||
- Typography (`clamp()`)
|
||||
- Grids (`auto-fit`, `minmax()`)
|
||||
Use media queries only when layout needs to change fundamentally (e.g., sidebar position).
|
||||
|
||||
## Cache Busting
|
||||
|
||||
FolderWeb automatically versions CSS files with MD5 hashes:
|
||||
FolderWeb versions CSS files with MD5 hashes automatically:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/custom/styles/base.css?v=a1b2c3d4">
|
||||
```
|
||||
|
||||
When you edit your CSS, the hash changes and browsers fetch the new version. No manual cache clearing needed.
|
||||
When you edit your CSS, the hash changes and browsers fetch the new version.
|
||||
|
||||
## Practical Examples
|
||||
## Next Steps
|
||||
|
||||
### Simple Blog Theme
|
||||
|
||||
```css
|
||||
:root {
|
||||
--color-accent: oklch(55% 0.15 220);
|
||||
--color-text: oklch(25% 0 0);
|
||||
--color-bg: oklch(99% 0 0);
|
||||
--max-width: 65ch;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Georgia, serif;
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
article {
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
|
||||
& h1 {
|
||||
font-size: clamp(2rem, 5vw, 3rem);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
& p {
|
||||
line-height: 1.8;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Portfolio Grid
|
||||
|
||||
```css
|
||||
.project-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
|
||||
gap: 2rem;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.project-card {
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px oklch(0% 0 0 / 0.1);
|
||||
transition: transform 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 4px 16px oklch(0% 0 0 / 0.15);
|
||||
}
|
||||
|
||||
& img {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
& h3 {
|
||||
padding: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Documentation Site
|
||||
|
||||
```css
|
||||
:root {
|
||||
--sidebar-width: 250px;
|
||||
}
|
||||
|
||||
.docs-layout {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 1fr;
|
||||
gap: 3rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
|
||||
@media (max-width: 900px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar nav {
|
||||
position: sticky;
|
||||
top: 2rem;
|
||||
|
||||
& ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
& a {
|
||||
display: block;
|
||||
padding: 0.5rem 1rem;
|
||||
text-decoration: none;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-bg-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- **Start simple:** Edit colors and fonts first, then tackle layout
|
||||
- **Use the inspector:** Browser dev tools show computed values and help debug
|
||||
- **Test in different browsers:** Modern CSS has excellent support, but always verify
|
||||
- **Keep it readable:** Future you will thank present you for clear, organized styles
|
||||
|
||||
## What's Next?
|
||||
|
||||
Now that you can style your site, learn how to:
|
||||
- **[Create custom templates](#)** — Control HTML structure and layout
|
||||
- **[Add multilingual support](#)** — Style for different languages and text directions
|
||||
|
||||
Or explore the [Reference](#) for detailed documentation on all available template variables and hooks.
|
||||
- [Templates](03-templates.md) — Control HTML structure and layout
|
||||
- [Internationalization](../03-reference/04-internationalization.md) — Multi-language support
|
||||
|
|
|
|||
|
|
@ -1,66 +1,26 @@
|
|||
# Working with Templates
|
||||
# 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.
|
||||
Templates control how content is presented. FolderWeb uses plain PHP templates — HTML with embedded PHP for dynamic output.
|
||||
|
||||
## Template Types
|
||||
|
||||
FolderWeb has three template levels:
|
||||
|
||||
### 1. Base Template (`base.php`)
|
||||
### Base Template (`base.php`)
|
||||
|
||||
The HTML scaffold wrapping every page:
|
||||
The HTML scaffold wrapping every page. Contains `<head>`, navigation, footer, and a `$content` placeholder for the inner template.
|
||||
|
||||
```
|
||||
<!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>
|
||||
```
|
||||
### Page Template (`page.php`)
|
||||
|
||||
**You typically customize this once** to set up your site structure.
|
||||
Wraps single-page content. Receives the rendered content and metadata.
|
||||
|
||||
### 2. Page Template (`page.php`)
|
||||
### List Templates (`list.php`, `list-grid.php`, `list-compact.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.
|
||||
Displays collections of items from subdirectories. Receives an `$items` array and optional intro content.
|
||||
|
||||
## Template Location
|
||||
|
||||
Templates live in your `custom/` directory:
|
||||
Templates live in `custom/templates/`:
|
||||
|
||||
```
|
||||
custom/
|
||||
|
|
@ -72,13 +32,11 @@ custom/
|
|||
└── list-compact.php # Compact list layout
|
||||
```
|
||||
|
||||
**FolderWeb falls back** to `app/default/templates/` if a custom template doesn't exist.
|
||||
FolderWeb falls back to `app/default/templates/` for any template not present in `custom/`.
|
||||
|
||||
## Customizing the Base Template
|
||||
## Base Template
|
||||
|
||||
Let's modify `base.php` to add your site name and custom navigation:
|
||||
|
||||
**custom/templates/base.php:**
|
||||
The base template wraps every page. Here is a minimal example:
|
||||
|
||||
```php
|
||||
<!DOCTYPE html>
|
||||
|
|
@ -105,7 +63,7 @@ Let's modify `base.php` to add your site name and custom navigation:
|
|||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/" class="logo">My Site</a>
|
||||
<a href="<?= htmlspecialchars($langPrefix ?? '') ?>/">My Site</a>
|
||||
<ul>
|
||||
<?php foreach ($navigation as $item): ?>
|
||||
<li>
|
||||
|
|
@ -124,9 +82,6 @@ Let's modify `base.php` to add your site name and custom navigation:
|
|||
|
||||
<footer>
|
||||
<p>© <?= date('Y') ?> My Site</p>
|
||||
<p>
|
||||
<small>Generated in <?= number_format($pageLoadTime, 4) ?>s</small>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<?php if (!empty($pageJsUrl)): ?>
|
||||
|
|
@ -136,17 +91,14 @@ Let's modify `base.php` to add your site name and custom navigation:
|
|||
</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
|
||||
Key points:
|
||||
- `$content` contains the rendered output from the page or list template
|
||||
- Always escape user-facing values with `htmlspecialchars()`
|
||||
- Check optional variables with `isset()` before using them
|
||||
|
||||
## Customizing Page Templates
|
||||
## Page Template
|
||||
|
||||
The page template wraps your single-page content. Let's add a reading time estimate:
|
||||
|
||||
**custom/templates/page.php:**
|
||||
The page template wraps individual page content:
|
||||
|
||||
```php
|
||||
<article>
|
||||
|
|
@ -159,13 +111,6 @@ The page template wraps your single-page content. Let's add a reading time estim
|
|||
<?= $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; ?>
|
||||
|
||||
|
|
@ -175,11 +120,9 @@ The page template wraps your single-page content. Let's add a reading time estim
|
|||
</article>
|
||||
```
|
||||
|
||||
## Customizing List Templates
|
||||
## List Template
|
||||
|
||||
List templates display collections of content. Let's create a custom blog list:
|
||||
|
||||
**custom/templates/list.php:**
|
||||
List templates display items from subdirectories:
|
||||
|
||||
```php
|
||||
<?php if ($pageContent): ?>
|
||||
|
|
@ -190,51 +133,38 @@ List templates display collections of content. Let's create a custom blog list:
|
|||
|
||||
<div class="blog-list">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<article class="blog-item">
|
||||
<article>
|
||||
<?php if (isset($item['cover_image'])): ?>
|
||||
<a href="<?= $item['url'] ?>">
|
||||
<img
|
||||
src="<?= $item['cover_image'] ?>"
|
||||
alt="<?= htmlspecialchars($item['title']) ?>"
|
||||
loading="lazy"
|
||||
>
|
||||
<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>
|
||||
<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['date'])): ?>
|
||||
<time datetime="<?= $item['date'] ?>">
|
||||
<?= $item['formatted_date'] ?? $item['date'] ?>
|
||||
</time>
|
||||
<?php endif; ?>
|
||||
|
||||
<?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
|
||||
## Choosing a List Template
|
||||
|
||||
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:**
|
||||
Select which list template to use per directory in `metadata.ini`:
|
||||
|
||||
```ini
|
||||
title = "Projects"
|
||||
|
|
@ -243,11 +173,14 @@ title = "Projects"
|
|||
page_template = "list-grid"
|
||||
```
|
||||
|
||||
Now the `projects/` directory uses the grid layout.
|
||||
Built-in options:
|
||||
- `list` — vertical list (default)
|
||||
- `list-grid` — card grid
|
||||
- `list-compact` — minimal compact list
|
||||
|
||||
## Creating Custom List Templates
|
||||
|
||||
Let's create a timeline template for a blog:
|
||||
Create a new file in `custom/templates/`. For example, a timeline layout:
|
||||
|
||||
**custom/templates/list-timeline.php:**
|
||||
|
||||
|
|
@ -259,8 +192,6 @@ Let's create a timeline template for a blog:
|
|||
$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;
|
||||
?>
|
||||
|
|
@ -271,196 +202,88 @@ Let's create a timeline template for a blog:
|
|||
|
||||
<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>
|
||||
<h4><a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a></h4>
|
||||
<?php if (isset($item['summary'])): ?>
|
||||
<p><?= htmlspecialchars($item['summary']) ?></p>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Use in metadata.ini:**
|
||||
Use it with:
|
||||
|
||||
```ini
|
||||
[settings]
|
||||
page_template = "list-timeline"
|
||||
```
|
||||
|
||||
## Available Template Variables
|
||||
## Template Variables
|
||||
|
||||
Templates have access to these variables (see [Reference: Template Variables](#) for complete list):
|
||||
### Base template
|
||||
|
||||
**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)
|
||||
$pageJsUrl // Page-specific JS URL (if exists)
|
||||
$pageLoadTime // Page generation time
|
||||
```
|
||||
| Variable | Type | Description |
|
||||
|---|---|---|
|
||||
| `$content` | string | Rendered page/list HTML |
|
||||
| `$pageTitle` | string | Page title for `<title>` |
|
||||
| `$metaDescription` | string | SEO description |
|
||||
| `$navigation` | array | Menu items (`title`, `url`, `order`) |
|
||||
| `$homeLabel` | string | Translated "Home" text |
|
||||
| `$currentLang` | string | Current language code |
|
||||
| `$langPrefix` | string | URL language prefix (`""` or `"/no"`) |
|
||||
| `$languageUrls` | array | Links to other language versions |
|
||||
| `$translations` | array | Translated UI strings |
|
||||
| `$cssHash` | string | Cache-busting hash for base CSS |
|
||||
| `$pageCssUrl` | string | Page-specific CSS URL (if exists) |
|
||||
| `$pageJsUrl` | string | Page-specific JS URL (if exists) |
|
||||
| `$pageLoadTime` | float | Page generation time |
|
||||
|
||||
**Page template:**
|
||||
```php
|
||||
$content // Rendered HTML
|
||||
$metadata // Metadata array (title, date, etc.)
|
||||
```
|
||||
### Page template
|
||||
|
||||
**List template:**
|
||||
```php
|
||||
$items // Array of items to display
|
||||
$pageContent // Optional intro content from page
|
||||
$metadata // Directory metadata
|
||||
| Variable | Type | Description |
|
||||
|---|---|---|
|
||||
| `$content` | string | Rendered HTML from content files |
|
||||
| `$metadata` | array | Page metadata (title, date, etc.) |
|
||||
|
||||
// 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)
|
||||
```
|
||||
### List template
|
||||
|
||||
| Variable | Type | Description |
|
||||
|---|---|---|
|
||||
| `$items` | array | Items to display |
|
||||
| `$pageContent` | string | Rendered intro content |
|
||||
| `$metadata` | array | Directory metadata |
|
||||
|
||||
Each item in `$items`:
|
||||
|
||||
| Key | Type | Description |
|
||||
|---|---|---|
|
||||
| `url` | string | Full URL to item |
|
||||
| `title` | string | Item title |
|
||||
| `summary` | string | Short description |
|
||||
| `date` | string | ISO date (YYYY-MM-DD) |
|
||||
| `formatted_date` | string | Localized date string |
|
||||
| `cover_image` | string | Cover image URL |
|
||||
|
||||
See the [Template Variables Reference](../03-reference/03-template-variables.md) for the complete list.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Escape Output
|
||||
**Escape output.** Always use `htmlspecialchars()` for user-facing values. The `$content` variable is already rendered HTML and does not need escaping.
|
||||
|
||||
**Check variables.** Use `isset()` before accessing optional values:
|
||||
|
||||
```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
|
||||
**Keep logic minimal.** Templates should display data, not process it. Complex logic belongs in plugins.
|
||||
|
||||
```php
|
||||
<!-- Verbose -->
|
||||
<?php echo htmlspecialchars($title); ?>
|
||||
**Use semantic HTML.** Prefer `<article>`, `<header>`, `<nav>`, `<time>` over generic `<div>` elements.
|
||||
|
||||
<!-- Concise -->
|
||||
<?= htmlspecialchars($title) ?>
|
||||
```
|
||||
## Next Steps
|
||||
|
||||
### 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.
|
||||
- [Template Variables Reference](../03-reference/03-template-variables.md) — Complete variable list
|
||||
- [Internationalization](../03-reference/04-internationalization.md) — Multi-language support
|
||||
- [Configuration Reference](../03-reference/01-configuration.md) — All configuration options
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue