docs refactor
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 52s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 1m1s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 5m50s

This commit is contained in:
2025-10-09 18:21:23 -04:00
parent c9a3a21f07
commit ad81d7f3db
39 changed files with 12551 additions and 1037 deletions

View 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>&copy; 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!