docs refactor
All checks were successful
All checks were successful
This commit is contained in:
578
docs/content/templates/index.md
Normal file
578
docs/content/templates/index.md
Normal file
@ -0,0 +1,578 @@
|
||||
---
|
||||
version: "1.0"
|
||||
date: "2025-01-15"
|
||||
author: "DWS Foldsite Team"
|
||||
title: "Template System"
|
||||
description: "Master Foldsite's powerful cascading template system"
|
||||
summary: "Learn how Foldsite's hierarchical template discovery works - from simple defaults to sophisticated, context-aware layouts."
|
||||
quick_tips:
|
||||
- "base.html is required and wraps every page on your site"
|
||||
- "Templates cascade from specific to general - Foldsite uses the first match"
|
||||
- "Use Jinja2 syntax for dynamic content and logic"
|
||||
---
|
||||
|
||||
# Template System
|
||||
|
||||
Foldsite's template system is both powerful and intuitive. Templates are HTML files with **Jinja2** syntax that define how your content is displayed.
|
||||
|
||||
## The Template Hierarchy
|
||||
|
||||
Foldsite uses a **cascading template discovery system**. When rendering a page, it searches for templates from **most specific** to **most general**, using the first match found.
|
||||
|
||||
###Understanding Template Priority
|
||||
|
||||
Think of it like CSS specificity - more specific selectors override general ones:
|
||||
|
||||
```
|
||||
Specific file > Type + Extension > Type + Category > Generic type
|
||||
```
|
||||
|
||||
### Example: Rendering `content/blog/my-post.md`
|
||||
|
||||
Foldsite searches in this order:
|
||||
|
||||
1. `templates/blog/my-post.html` ← Exact file match
|
||||
2. `templates/blog/__file.md.html` ← Markdown files in blog/
|
||||
3. `templates/blog/__file.document.html` ← Document files in blog/
|
||||
4. `templates/__file.md.html` ← All markdown files
|
||||
5. `templates/__file.document.html` ← All document files
|
||||
6. `templates/__file.html` ← Any file
|
||||
7. **First match wins!**
|
||||
|
||||
## Required Template: base.html
|
||||
|
||||
**Every Foldsite project MUST have a `base.html`** in the templates root. This wraps every page on your site.
|
||||
|
||||
### Minimal base.html
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Site</title>
|
||||
{% for style in styles %}
|
||||
<link rel="stylesheet" href="/styles{{ style }}">
|
||||
{% endfor %}
|
||||
</head>
|
||||
<body>
|
||||
{{ content|safe }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Available Variables in base.html
|
||||
|
||||
- `{{ content|safe }}` - Rendered page content (required)
|
||||
- `{{ styles }}` - List of CSS files to load
|
||||
- `{{ currentPath }}` - Current page path
|
||||
- `{{ metadata }}` - Frontmatter from markdown files
|
||||
- All template helper functions
|
||||
|
||||
### Complete base.html Example
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% if metadata and metadata.title %}{{ metadata.title }} - {% endif %}My Site</title>
|
||||
|
||||
{% for style in styles %}
|
||||
<link rel="stylesheet" href="/styles{{ style }}">
|
||||
{% endfor %}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
{% for item in get_navigation_items() %}
|
||||
<a href="{{ item.url }}">{{ item.title }}</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{{ content|safe }}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 My Site</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Template Naming Patterns
|
||||
|
||||
### File Templates
|
||||
|
||||
Templates for individual files use this pattern:
|
||||
|
||||
**Pattern:** `__file.{extension}.html` or `__file.{category}.html`
|
||||
|
||||
**Examples:**
|
||||
- `__file.md.html` - All markdown files
|
||||
- `__file.document.html` - All document types (md, txt, html)
|
||||
- `__file.image.html` - Individual image pages (rare)
|
||||
- `__file.jpg.html` - Specific to JPG files
|
||||
|
||||
### Folder Templates
|
||||
|
||||
Templates for directory views:
|
||||
|
||||
**Pattern:** `__folder.{category}.html`
|
||||
|
||||
**Examples:**
|
||||
- `__folder.md.html` - Folders containing mostly markdown
|
||||
- `__folder.image.html` - Photo gallery folders
|
||||
- `__folder.html` - Generic folder view
|
||||
|
||||
### Specific Page Templates
|
||||
|
||||
Override for specific pages:
|
||||
|
||||
**Pattern:** `{filename}.html`
|
||||
|
||||
**Examples:**
|
||||
- `index.html` - Only for index.md
|
||||
- `about.html` - Only for about.md
|
||||
- `contact.html` - Only for contact.md
|
||||
|
||||
## File Categories
|
||||
|
||||
Foldsite automatically categorizes files by extension:
|
||||
|
||||
| Category | Extensions | Template | Use Case |
|
||||
|----------|-----------|----------|----------|
|
||||
| **document** | `.md`, `.txt` | `__file.document.html` | Text content |
|
||||
| **image** | `.jpg`, `.png`, `.gif` | `__file.image.html` | Photos |
|
||||
| **multimedia** | `.mp4`, `.mp3` | `__file.multimedia.html` | Video/audio |
|
||||
| **other** | Everything else | `__file.other.html` | Downloads |
|
||||
|
||||
Files can have **multiple categories**. For example, `.md` files are both `md` and `document`.
|
||||
|
||||
## Template Variables
|
||||
|
||||
Every template receives these variables:
|
||||
|
||||
### Always Available
|
||||
|
||||
- `content` - Rendered HTML content
|
||||
- `styles` - List of CSS file paths
|
||||
- `currentPath` - Path relative to content root
|
||||
- `metadata` - Frontmatter dict (for markdown files)
|
||||
|
||||
### Markdown Files Only
|
||||
|
||||
For `.md` files, `metadata` contains frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "My Blog Post"
|
||||
date: "2025-01-15"
|
||||
author: "Your Name"
|
||||
tags: ["python", "web"]
|
||||
---
|
||||
|
||||
# Content here...
|
||||
```
|
||||
|
||||
Access in templates:
|
||||
|
||||
```jinja
|
||||
<h1>{{ metadata.title }}</h1>
|
||||
<time>{{ metadata.date }}</time>
|
||||
<p>By {{ metadata.author }}</p>
|
||||
|
||||
{% for tag in metadata.tags %}
|
||||
<span class="tag">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
## Template Helpers
|
||||
|
||||
Foldsite provides powerful helper functions accessible in all templates:
|
||||
|
||||
### Content Discovery
|
||||
|
||||
```jinja
|
||||
{# Get folder contents #}
|
||||
{% for file in get_folder_contents(currentPath) %}
|
||||
<a href="/{{ file.path }}">{{ file.name }}</a>
|
||||
{% endfor %}
|
||||
|
||||
{# Get sibling files #}
|
||||
{% for sibling in get_sibling_content_files(currentPath) %}
|
||||
<a href="/{{ sibling[1] }}">{{ sibling[0] }}</a>
|
||||
{% endfor %}
|
||||
|
||||
{# Get sibling folders #}
|
||||
{% for folder in get_sibling_content_folders(currentPath) %}
|
||||
<a href="/{{ folder[1] }}">{{ folder[0] }}</a>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
### Blog Functions
|
||||
|
||||
```jinja
|
||||
{# Recent blog posts #}
|
||||
{% for post in get_recent_posts(limit=5, folder='blog') %}
|
||||
<article>
|
||||
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
||||
<time>{{ post.date }}</time>
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
||||
{# Posts by tag #}
|
||||
{% for post in get_posts_by_tag('python', limit=10) %}
|
||||
<a href="{{ post.url }}">{{ post.title }}</a>
|
||||
{% endfor %}
|
||||
|
||||
{# All tags #}
|
||||
{% for tag in get_all_tags() %}
|
||||
<a href="/tags/{{ tag.name }}">{{ tag.name }} ({{ tag.count }})</a>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
```jinja
|
||||
{# Breadcrumbs #}
|
||||
{% for crumb in generate_breadcrumbs(currentPath) %}
|
||||
{% if not crumb.is_current %}
|
||||
<a href="{{ crumb.url }}">{{ crumb.title }}</a>
|
||||
{% else %}
|
||||
<span>{{ crumb.title }}</span>
|
||||
{% endif %}
|
||||
{% if not loop.last %} / {% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{# Navigation menu #}
|
||||
{% for item in get_navigation_items(max_items=10) %}
|
||||
<a href="{{ item.url }}">{{ item.title }}</a>
|
||||
{% endfor %}
|
||||
|
||||
{# Related posts #}
|
||||
{% for post in get_related_posts(currentPath, limit=3) %}
|
||||
<a href="{{ post.url }}">{{ post.title }}</a>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
See [Template Helpers Reference](template-helpers.md) for complete documentation.
|
||||
|
||||
## Cascading Through Directories
|
||||
|
||||
Templates cascade down through your directory structure. Place templates in subdirectories to override for specific sections.
|
||||
|
||||
### Example Structure
|
||||
|
||||
```
|
||||
templates/
|
||||
├── base.html # Site-wide wrapper
|
||||
├── __file.md.html # Default for all markdown
|
||||
├── __folder.image.html # Default for galleries
|
||||
├── blog/
|
||||
│ ├── __file.md.html # Override for blog posts
|
||||
│ └── __folder.md.html # Override for blog index
|
||||
└── photos/
|
||||
└── __folder.image.html # Override for photo galleries
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
**Rendering `content/blog/my-post.md`:**
|
||||
1. Looks in `templates/blog/` first
|
||||
2. Finds `blog/__file.md.html` ← **Uses this**
|
||||
3. Never checks root `__file.md.html`
|
||||
|
||||
**Rendering `content/projects/project.md`:**
|
||||
1. Looks in `templates/projects/` first
|
||||
2. Doesn't find specific template
|
||||
3. Falls back to `templates/__file.md.html` ← **Uses this**
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### Simple Blog Post Template
|
||||
|
||||
`templates/__file.md.html`:
|
||||
|
||||
```html
|
||||
<article>
|
||||
{% if metadata %}
|
||||
<header>
|
||||
<h1>{{ metadata.title }}</h1>
|
||||
<time datetime="{{ metadata.date }}">{{ metadata.date }}</time>
|
||||
{% if metadata.author %}
|
||||
<p class="author">By {{ metadata.author }}</p>
|
||||
{% endif %}
|
||||
</header>
|
||||
{% endif %}
|
||||
|
||||
<div class="content">
|
||||
{{ content|safe }}
|
||||
</div>
|
||||
|
||||
{% if metadata and metadata.tags %}
|
||||
<footer>
|
||||
<p>Tags:
|
||||
{% for tag in metadata.tags %}
|
||||
<a href="/tags/{{ tag|lower }}">#{{ tag }}</a>
|
||||
{% endfor %}
|
||||
</p>
|
||||
</footer>
|
||||
{% endif %}
|
||||
</article>
|
||||
```
|
||||
|
||||
### Blog Index Template
|
||||
|
||||
`templates/__folder.md.html`:
|
||||
|
||||
```html
|
||||
<div class="blog-index">
|
||||
<h1>Recent Posts</h1>
|
||||
|
||||
{% for post in get_recent_posts(limit=10, folder=currentPath) %}
|
||||
<article class="post-preview">
|
||||
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
||||
<time>{{ post.date }}</time>
|
||||
{% if post.metadata.description %}
|
||||
<p>{{ post.metadata.description }}</p>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Photo Gallery Template
|
||||
|
||||
`templates/__folder.image.html`:
|
||||
|
||||
```html
|
||||
<div class="gallery">
|
||||
{% set breadcrumbs = currentPath.split('/') %}
|
||||
<nav class="breadcrumbs">
|
||||
<a href="/">Home</a>
|
||||
{% for i in range(breadcrumbs|length) %}
|
||||
{% if i+1 == breadcrumbs|length %}
|
||||
/ <span>{{ breadcrumbs[i] }}</span>
|
||||
{% else %}
|
||||
/ <a href="/{{ '/'.join(breadcrumbs[:i+1]) }}">{{ breadcrumbs[i] }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</nav>
|
||||
|
||||
<div class="photos">
|
||||
{% for photo in get_folder_contents(currentPath)|sort(attribute='date_created', reverse=True) %}
|
||||
{% if 'image' in photo.categories %}
|
||||
<a href="/download/{{ photo.path }}" class="photo">
|
||||
<img src="/download/{{ photo.path }}?max_width=400"
|
||||
alt="{{ photo.name }}"
|
||||
loading="lazy">
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Error Pages
|
||||
|
||||
Custom error template: `templates/__error.html`
|
||||
|
||||
```html
|
||||
<div class="error-page">
|
||||
<h1>Error {{ error_code }}</h1>
|
||||
<p>{{ error_message }}</p>
|
||||
<p>{{ error_description }}</p>
|
||||
<a href="/">Return Home</a>
|
||||
</div>
|
||||
```
|
||||
|
||||
Variables available:
|
||||
- `error_code` - HTTP status code (404, 500, etc.)
|
||||
- `error_message` - Short message ("Not Found")
|
||||
- `error_description` - Detailed description
|
||||
|
||||
## Jinja2 Syntax Quick Reference
|
||||
|
||||
### Variables
|
||||
|
||||
```jinja
|
||||
{{ variable }}
|
||||
{{ metadata.title }}
|
||||
{{ post.url }}
|
||||
```
|
||||
|
||||
### Filters
|
||||
|
||||
```jinja
|
||||
{{ content|safe }} {# Don't escape HTML #}
|
||||
{{ title|upper }} {# Uppercase #}
|
||||
{{ date|default('Unknown') }} {# Default value #}
|
||||
{{ items|length }} {# Count items #}
|
||||
```
|
||||
|
||||
### Conditionals
|
||||
|
||||
```jinja
|
||||
{% if metadata %}
|
||||
<h1>{{ metadata.title }}</h1>
|
||||
{% endif %}
|
||||
|
||||
{% if metadata and metadata.title %}
|
||||
...
|
||||
{% elif metadata %}
|
||||
...
|
||||
{% else %}
|
||||
...
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### Loops
|
||||
|
||||
```jinja
|
||||
{% for item in items %}
|
||||
<p>{{ item }}</p>
|
||||
{% endfor %}
|
||||
|
||||
{% for key, value in metadata.items() %}
|
||||
<p>{{ key }}: {{ value }}</p>
|
||||
{% endfor %}
|
||||
|
||||
{# Loop variables #}
|
||||
{% for item in items %}
|
||||
{{ loop.index }} {# 1-indexed #}
|
||||
{{ loop.index0 }} {# 0-indexed #}
|
||||
{{ loop.first }} {# True on first iteration #}
|
||||
{{ loop.last }} {# True on last iteration #}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
### Filters and Functions
|
||||
|
||||
```jinja
|
||||
{% for file in get_folder_contents()|sort(attribute='date') %}
|
||||
...
|
||||
{% endfor %}
|
||||
|
||||
{% for post in get_recent_posts(limit=5)|reverse %}
|
||||
...
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Start Simple, Add Complexity as Needed
|
||||
|
||||
Begin with basic templates:
|
||||
|
||||
```
|
||||
templates/
|
||||
├── base.html
|
||||
├── __file.md.html
|
||||
└── __folder.md.html
|
||||
```
|
||||
|
||||
Add specific overrides only when you need different styling or layout.
|
||||
|
||||
### 2. Keep base.html Minimal
|
||||
|
||||
Your base template should handle:
|
||||
- HTML document structure
|
||||
- CSS loading
|
||||
- Site-wide navigation
|
||||
- Footer
|
||||
|
||||
Leave content-specific layouts to page templates.
|
||||
|
||||
### 3. Use Template Helpers
|
||||
|
||||
Don't manually read files or iterate directories. Use helpers:
|
||||
|
||||
```jinja
|
||||
✓ Good:
|
||||
{% for post in get_recent_posts(limit=5) %}
|
||||
|
||||
✗ Bad:
|
||||
{# Trying to manually list files - won't work #}
|
||||
```
|
||||
|
||||
### 4. Leverage the Cascade
|
||||
|
||||
Put general templates at the root, specific ones in subdirectories:
|
||||
|
||||
```
|
||||
templates/
|
||||
├── __file.md.html # Default for all markdown
|
||||
└── blog/
|
||||
└── __file.md.html # Special layout for blog
|
||||
```
|
||||
|
||||
### 5. Test with Debug Mode
|
||||
|
||||
Enable debug mode in `config.toml` to see template discovery:
|
||||
|
||||
```toml
|
||||
[server]
|
||||
debug = true
|
||||
```
|
||||
|
||||
This shows which templates Foldsite considered and why it chose the one it did.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern: Site Navigation
|
||||
|
||||
In `base.html`:
|
||||
|
||||
```html
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
{% for item in get_navigation_items() %}
|
||||
<a href="{{ item.url }}"
|
||||
{% if currentPath == item.path %}class="active"{% endif %}>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
```
|
||||
|
||||
### Pattern: Sidebar with Recent Posts
|
||||
|
||||
```html
|
||||
<aside>
|
||||
<h3>Recent Posts</h3>
|
||||
<ul>
|
||||
{% for post in get_recent_posts(limit=5) %}
|
||||
<li>
|
||||
<a href="{{ post.url }}">{{ post.title }}</a>
|
||||
<small>{{ post.date }}</small>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</aside>
|
||||
```
|
||||
|
||||
### Pattern: Tag Cloud
|
||||
|
||||
```html
|
||||
<div class="tag-cloud">
|
||||
{% for tag in get_all_tags() %}
|
||||
<a href="/tags/{{ tag.name|lower }}"
|
||||
style="font-size: {{ 0.8 + (tag.count * 0.1) }}em;">
|
||||
{{ tag.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Template Discovery](templates/template-discovery.md)** - Deep dive into how templates are found
|
||||
- **[Template Helpers Reference](templates/template-helpers.md)** - Complete API documentation
|
||||
- **[Template Recipes](../recipes/)** - Ready-to-use template examples
|
||||
- **[Styles Guide](../styles/)** - Styling your templates
|
||||
|
||||
Master the template system, and you can build any type of site with Foldsite!
|
Reference in New Issue
Block a user