683 lines
20 KiB
Markdown
683 lines
20 KiB
Markdown
---
|
||
version: "1.0"
|
||
date: "2025-01-15"
|
||
author: "DWS Foldsite Team"
|
||
title: "Template Recipes"
|
||
description: "Ready-to-use examples for common Foldsite patterns"
|
||
summary: "Complete, working examples for building blogs, photo galleries, documentation sites, and more with Foldsite."
|
||
quick_tips:
|
||
- "All recipes are complete and ready to use - just copy and customize"
|
||
- "Recipes demonstrate real-world patterns used in production Foldsites"
|
||
- "Mix and match components from different recipes"
|
||
---
|
||
|
||
# Template Recipes
|
||
|
||
Ready-to-use templates and patterns for common Foldsite use cases. Copy these examples and customize them for your needs.
|
||
|
||
## Recipe Collection
|
||
|
||
### Blog Site
|
||
Complete blog setup with recent posts, tag filtering, and related content.
|
||
|
||
### Photo Gallery
|
||
Beautiful image galleries with EXIF data, breadcrumbs, and responsive layout.
|
||
|
||
### Documentation Site
|
||
Hierarchical documentation with navigation, breadcrumbs, and sibling pages.
|
||
|
||
### Portfolio Site
|
||
Project showcase with custom layouts per project.
|
||
|
||
---
|
||
|
||
## Recipe 1: Personal Blog
|
||
|
||
A complete blog with post listings, tags, and related posts.
|
||
|
||
### Directory Structure
|
||
|
||
```
|
||
my-blog/
|
||
├── content/
|
||
│ ├── index.md
|
||
│ ├── about.md
|
||
│ └── posts/
|
||
│ ├── 2024-01-15-first-post.md
|
||
│ ├── 2024-02-20-python-tips.md
|
||
│ └── 2024-03-10-web-development.md
|
||
├── templates/
|
||
│ ├── base.html
|
||
│ ├── index.html
|
||
│ ├── __file.md.html
|
||
│ └── posts/
|
||
│ ├── __file.md.html
|
||
│ └── __folder.md.html
|
||
└── styles/
|
||
├── base.css
|
||
└── posts/
|
||
└── __file.md.css
|
||
```
|
||
|
||
### Post Frontmatter Format
|
||
|
||
```markdown
|
||
---
|
||
title: "My First Blog Post"
|
||
date: "2024-01-15"
|
||
author: "Your Name"
|
||
tags: ["python", "tutorial", "beginners"]
|
||
description: "A brief introduction to Python for beginners"
|
||
---
|
||
|
||
# Your content here...
|
||
```
|
||
|
||
### Template: `base.html`
|
||
|
||
```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 Blog</title>
|
||
|
||
{% for style in styles %}
|
||
<link rel="stylesheet" href="/styles{{ style }}">
|
||
{% endfor %}
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<nav class="main-nav">
|
||
<a href="/" class="logo">My Blog</a>
|
||
<div class="nav-links">
|
||
<a href="/">Home</a>
|
||
<a href="/posts">Posts</a>
|
||
<a href="/about.md">About</a>
|
||
</div>
|
||
</nav>
|
||
</header>
|
||
|
||
<main class="container">
|
||
{{ content|safe }}
|
||
</main>
|
||
|
||
<footer>
|
||
<p>© 2025 My Blog. Built with <a href="https://github.com/DWSresearch/foldsite">Foldsite</a>.</p>
|
||
</footer>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
### Template: `index.html` (Homepage)
|
||
|
||
```html
|
||
<div class="homepage">
|
||
<section class="hero">
|
||
<h1>Welcome to My Blog</h1>
|
||
<p>Thoughts on coding, life, and everything in between.</p>
|
||
</section>
|
||
|
||
<section class="recent-posts">
|
||
<h2>Recent Posts</h2>
|
||
<div class="post-grid">
|
||
{% for post in get_recent_posts(limit=6, folder='posts') %}
|
||
<article class="post-card">
|
||
<h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
|
||
<time datetime="{{ post.date }}">{{ post.date }}</time>
|
||
|
||
{% if post.metadata.description %}
|
||
<p class="excerpt">{{ post.metadata.description }}</p>
|
||
{% endif %}
|
||
|
||
{% if post.metadata.tags %}
|
||
<div class="tags">
|
||
{% for tag in post.metadata.tags[:3] %}
|
||
<a href="/tags/{{ tag|lower }}.md" class="tag">{{ tag }}</a>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
<a href="/posts" class="view-all">View All Posts →</a>
|
||
</section>
|
||
|
||
<aside class="sidebar">
|
||
<section class="popular-tags">
|
||
<h3>Popular Tags</h3>
|
||
<div class="tag-cloud">
|
||
{% for tag in get_all_tags()|sort(attribute='count', reverse=True)[:15] %}
|
||
<a href="/tags/{{ tag.name|lower }}.md"
|
||
class="tag"
|
||
style="font-size: {{ 0.9 + (tag.count * 0.15) }}rem;">
|
||
{{ tag.name }}
|
||
</a>
|
||
{% endfor %}
|
||
</div>
|
||
</section>
|
||
</aside>
|
||
</div>
|
||
```
|
||
|
||
### Template: `posts/__file.md.html` (Blog Post)
|
||
|
||
```html
|
||
<article class="blog-post">
|
||
<header class="post-header">
|
||
{% if metadata %}
|
||
<h1>{{ metadata.title }}</h1>
|
||
|
||
<div class="post-meta">
|
||
<time datetime="{{ metadata.date }}">{{ metadata.date }}</time>
|
||
{% if metadata.author %}
|
||
<span class="author">by {{ metadata.author }}</span>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if metadata.tags %}
|
||
<div class="post-tags">
|
||
{% for tag in metadata.tags %}
|
||
<a href="/tags/{{ tag|lower }}.md" class="tag">{{ tag }}</a>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
{% endif %}
|
||
</header>
|
||
|
||
<div class="post-content">
|
||
{{ content|safe }}
|
||
</div>
|
||
|
||
<footer class="post-footer">
|
||
{% set related = get_related_posts(currentPath, limit=3) %}
|
||
{% if related %}
|
||
<section class="related-posts">
|
||
<h3>Related Posts</h3>
|
||
<div class="related-grid">
|
||
{% for post in related %}
|
||
<a href="{{ post.url }}" class="related-card">
|
||
<h4>{{ post.title }}</h4>
|
||
<time>{{ post.date }}</time>
|
||
</a>
|
||
{% endfor %}
|
||
</div>
|
||
</section>
|
||
{% endif %}
|
||
|
||
<nav class="post-navigation">
|
||
{% set siblings = get_sibling_content_files(currentPath) %}
|
||
{% if siblings|length > 1 %}
|
||
{% set current_index = namespace(value=-1) %}
|
||
{% for idx, (name, path) in enumerate(siblings) %}
|
||
{% if path == currentPath %}
|
||
{% set current_index.value = idx %}
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
{% if current_index.value > 0 %}
|
||
{% set prev = siblings[current_index.value - 1] %}
|
||
<a href="/{{ prev[1] }}" class="nav-prev">← {{ prev[0].replace('.md', '') }}</a>
|
||
{% endif %}
|
||
|
||
{% if current_index.value < siblings|length - 1 %}
|
||
{% set next = siblings[current_index.value + 1] %}
|
||
<a href="/{{ next[1] }}" class="nav-next">{{ next[0].replace('.md', '') }} →</a>
|
||
{% endif %}
|
||
{% endif %}
|
||
</nav>
|
||
</footer>
|
||
</article>
|
||
```
|
||
|
||
### Template: `posts/__folder.md.html` (Post Index)
|
||
|
||
```html
|
||
<div class="post-index">
|
||
<header class="index-header">
|
||
<h1>All Posts</h1>
|
||
<p>{{ get_recent_posts(limit=1000, folder='posts')|length }} posts and counting</p>
|
||
</header>
|
||
|
||
<div class="post-list">
|
||
{% for post in get_recent_posts(limit=100, folder='posts') %}
|
||
<article class="post-item">
|
||
<time datetime="{{ post.date }}">{{ post.date }}</time>
|
||
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
||
|
||
{% if post.metadata.description %}
|
||
<p class="description">{{ post.metadata.description }}</p>
|
||
{% endif %}
|
||
|
||
{% if post.metadata.tags %}
|
||
<div class="tags">
|
||
{% for tag in post.metadata.tags %}
|
||
<a href="/tags/{{ tag|lower }}.md" class="tag">{{ tag }}</a>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
## Recipe 2: Photo Gallery
|
||
|
||
Beautiful, responsive photo galleries with EXIF data extraction.
|
||
|
||
### Directory Structure
|
||
|
||
```
|
||
photo-site/
|
||
├── content/
|
||
│ ├── index.md
|
||
│ └── galleries/
|
||
│ ├── vacation-2024/
|
||
│ │ ├── IMG_001.jpg
|
||
│ │ ├── IMG_002.jpg
|
||
│ │ └── IMG_003.jpg
|
||
│ └── family/
|
||
│ └── photos...
|
||
├── templates/
|
||
│ ├── base.html
|
||
│ └── galleries/
|
||
│ └── __folder.image.html
|
||
└── styles/
|
||
├── base.css
|
||
└── galleries/
|
||
└── __folder.image.css
|
||
```
|
||
|
||
### Template: `galleries/__folder.image.html`
|
||
|
||
```html
|
||
<div class="photo-gallery">
|
||
<header class="gallery-header">
|
||
<nav class="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 %}
|
||
</nav>
|
||
|
||
<h1>{{ currentPath.split('/')[-1]|replace('-', ' ')|title }}</h1>
|
||
|
||
{% set photos = get_folder_contents(currentPath)|selectattr('categories', 'contains', 'image') %}
|
||
<p class="photo-count">{{ photos|list|length }} photos</p>
|
||
|
||
{# Show sibling galleries #}
|
||
{% set sibling_folders = get_sibling_content_folders(currentPath) %}
|
||
{% if sibling_folders %}
|
||
<nav class="gallery-nav">
|
||
<h3>Other Galleries:</h3>
|
||
{% for name, path in sibling_folders %}
|
||
<a href="/{{ path }}">{{ name|replace('-', ' ')|title }}</a>
|
||
{% endfor %}
|
||
</nav>
|
||
{% endif %}
|
||
</header>
|
||
|
||
<div class="photo-grid">
|
||
{% for photo in photos|list|sort(attribute='date_created', reverse=True) %}
|
||
<figure class="photo-item">
|
||
<a href="/download/{{ photo.path }}" class="photo-link">
|
||
<img src="/download/{{ photo.path }}?max_width=800"
|
||
alt="{{ photo.name }}"
|
||
loading="lazy"
|
||
width="{{ photo.metadata.width if photo.metadata else 800 }}"
|
||
height="{{ photo.metadata.height if photo.metadata else 600 }}">
|
||
</a>
|
||
|
||
{% if photo.metadata and photo.metadata.exif %}
|
||
<figcaption class="photo-caption">
|
||
{% if photo.metadata.exif.DateTimeOriginal %}
|
||
<time>{{ photo.metadata.exif.DateTimeOriginal }}</time>
|
||
{% endif %}
|
||
{% if photo.metadata.exif.Model %}
|
||
<span class="camera">{{ photo.metadata.exif.Model }}</span>
|
||
{% endif %}
|
||
{% if photo.metadata.exif.LensModel %}
|
||
<span class="lens">{{ photo.metadata.exif.LensModel }}</span>
|
||
{% endif %}
|
||
</figcaption>
|
||
{% endif %}
|
||
</figure>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
{# Optional: Include a lightbox library #}
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.7/viewer.min.css">
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.7/viewer.min.js"></script>
|
||
<script>
|
||
const gallery = new Viewer(document.querySelector('.photo-grid'), {
|
||
toolbar: true,
|
||
title: true,
|
||
navbar: true,
|
||
});
|
||
</script>
|
||
```
|
||
|
||
### Styles: `galleries/__folder.image.css`
|
||
|
||
```css
|
||
.photo-gallery {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
padding: 2rem;
|
||
}
|
||
|
||
.gallery-header {
|
||
margin-bottom: 3rem;
|
||
text-align: center;
|
||
}
|
||
|
||
.breadcrumbs {
|
||
font-size: 0.9rem;
|
||
color: #666;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.breadcrumbs a {
|
||
color: #0066cc;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.photo-count {
|
||
color: #888;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.photo-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 1.5rem;
|
||
margin-top: 2rem;
|
||
}
|
||
|
||
.photo-item {
|
||
position: relative;
|
||
margin: 0;
|
||
overflow: hidden;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
transition: transform 0.2s, box-shadow 0.2s;
|
||
}
|
||
|
||
.photo-item:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
|
||
}
|
||
|
||
.photo-link {
|
||
display: block;
|
||
line-height: 0;
|
||
}
|
||
|
||
.photo-item img {
|
||
width: 100%;
|
||
height: auto;
|
||
display: block;
|
||
}
|
||
|
||
.photo-caption {
|
||
padding: 0.75rem;
|
||
background: white;
|
||
font-size: 0.85rem;
|
||
color: #666;
|
||
}
|
||
|
||
.photo-caption time {
|
||
display: block;
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.gallery-nav {
|
||
margin-top: 2rem;
|
||
padding-top: 2rem;
|
||
border-top: 1px solid #eee;
|
||
}
|
||
|
||
.gallery-nav a {
|
||
display: inline-block;
|
||
margin: 0.5rem;
|
||
padding: 0.5rem 1rem;
|
||
background: #f5f5f5;
|
||
border-radius: 4px;
|
||
text-decoration: none;
|
||
color: #333;
|
||
}
|
||
|
||
.gallery-nav a:hover {
|
||
background: #e5e5e5;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.photo-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: 1rem;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Recipe 3: Documentation Site
|
||
|
||
Hierarchical documentation with navigation and search.
|
||
|
||
### Directory Structure
|
||
|
||
```
|
||
docs-site/
|
||
├── content/
|
||
│ ├── index.md
|
||
│ ├── getting-started/
|
||
│ │ ├── installation.md
|
||
│ │ ├── configuration.md
|
||
│ │ └── quickstart.md
|
||
│ ├── guides/
|
||
│ │ ├── basics.md
|
||
│ │ └── advanced.md
|
||
│ └── api/
|
||
│ ├── overview.md
|
||
│ └── reference.md
|
||
├── templates/
|
||
│ ├── base.html
|
||
│ ├── index.html
|
||
│ └── __file.md.html
|
||
└── styles/
|
||
└── base.css
|
||
```
|
||
|
||
### Template: `__file.md.html` (Documentation Page)
|
||
|
||
```html
|
||
<div class="docs-layout">
|
||
<aside class="docs-sidebar">
|
||
<nav class="sidebar-nav">
|
||
<h3>Documentation</h3>
|
||
|
||
<section class="nav-section">
|
||
<h4>Getting Started</h4>
|
||
<ul>
|
||
<li><a href="/getting-started/installation.md">Installation</a></li>
|
||
<li><a href="/getting-started/configuration.md">Configuration</a></li>
|
||
<li><a href="/getting-started/quickstart.md">Quick Start</a></li>
|
||
</ul>
|
||
</section>
|
||
|
||
<section class="nav-section">
|
||
<h4>Guides</h4>
|
||
<ul>
|
||
<li><a href="/guides/basics.md">Basics</a></li>
|
||
<li><a href="/guides/advanced.md">Advanced</a></li>
|
||
</ul>
|
||
</section>
|
||
|
||
<section class="nav-section">
|
||
<h4>API Reference</h4>
|
||
<ul>
|
||
<li><a href="/api/overview.md">Overview</a></li>
|
||
<li><a href="/api/reference.md">Reference</a></li>
|
||
</ul>
|
||
</section>
|
||
</nav>
|
||
|
||
{# Show sibling pages automatically #}
|
||
{% set siblings = get_sibling_content_files(currentPath) %}
|
||
{% if siblings|length > 1 %}
|
||
<nav class="page-nav">
|
||
<h4>On This Page:</h4>
|
||
<ul>
|
||
{% for name, path in siblings %}
|
||
<li>
|
||
<a href="/{{ path }}"
|
||
{% if path == currentPath %}class="active"{% endif %}>
|
||
{{ name.replace('.md', '')|replace('-', ' ')|title }}
|
||
</a>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</nav>
|
||
{% endif %}
|
||
</aside>
|
||
|
||
<main class="docs-content">
|
||
<nav class="breadcrumbs">
|
||
{% for crumb in generate_breadcrumbs(currentPath) %}
|
||
<a href="{{ crumb.url }}">{{ crumb.title }}</a>
|
||
{% if not loop.last %} › {% endif %}
|
||
{% endfor %}
|
||
</nav>
|
||
|
||
<article class="documentation">
|
||
{% if metadata and metadata.title %}
|
||
<h1>{{ metadata.title }}</h1>
|
||
{% endif %}
|
||
|
||
{{ content|safe }}
|
||
</article>
|
||
|
||
<footer class="docs-footer">
|
||
{# Previous/Next navigation #}
|
||
{% set siblings = get_sibling_content_files(currentPath) %}
|
||
{% if siblings|length > 1 %}
|
||
<nav class="pagination">
|
||
{% set current_index = namespace(value=-1) %}
|
||
{% for idx in range(siblings|length) %}
|
||
{% if siblings[idx][1] == currentPath %}
|
||
{% set current_index.value = idx %}
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
{% if current_index.value > 0 %}
|
||
{% set prev = siblings[current_index.value - 1] %}
|
||
<a href="/{{ prev[1] }}" class="prev">
|
||
← {{ prev[0].replace('.md', '')|replace('-', ' ')|title }}
|
||
</a>
|
||
{% endif %}
|
||
|
||
{% if current_index.value >= 0 and current_index.value < siblings|length - 1 %}
|
||
{% set next = siblings[current_index.value + 1] %}
|
||
<a href="/{{ next[1] }}" class="next">
|
||
{{ next[0].replace('.md', '')|replace('-', ' ')|title }} →
|
||
</a>
|
||
{% endif %}
|
||
</nav>
|
||
{% endif %}
|
||
|
||
{# Related pages based on tags #}
|
||
{% set related = get_related_posts(currentPath, limit=3) %}
|
||
{% if related %}
|
||
<section class="related-docs">
|
||
<h4>Related Documentation:</h4>
|
||
<ul>
|
||
{% for doc in related %}
|
||
<li><a href="{{ doc.url }}">{{ doc.title }}</a></li>
|
||
{% endfor %}
|
||
</ul>
|
||
</section>
|
||
{% endif %}
|
||
</footer>
|
||
</main>
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
## Recipe 4: Portfolio Site
|
||
|
||
Showcase projects with custom layouts.
|
||
|
||
### Template: Portfolio Homepage
|
||
|
||
```html
|
||
<div class="portfolio">
|
||
<section class="hero">
|
||
<h1>{{ metadata.title if metadata else "My Portfolio" }}</h1>
|
||
<p class="tagline">{{ metadata.description if metadata else "Designer & Developer" }}</p>
|
||
</section>
|
||
|
||
<section class="projects">
|
||
<h2>Featured Projects</h2>
|
||
<div class="project-grid">
|
||
{% for folder_name, folder_path in get_sibling_content_folders('projects') %}
|
||
{% set project_files = get_folder_contents(folder_path) %}
|
||
{% set cover_image = project_files|selectattr('categories', 'contains', 'image')|first %}
|
||
|
||
<a href="/{{ folder_path }}" class="project-card">
|
||
{% if cover_image %}
|
||
<img src="/download/{{ cover_image.path }}?max_width=600"
|
||
alt="{{ folder_name }}">
|
||
{% endif %}
|
||
<h3>{{ folder_name|replace('-', ' ')|title }}</h3>
|
||
</a>
|
||
{% endfor %}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
## Mixing Recipes
|
||
|
||
You can combine elements from different recipes:
|
||
|
||
### Blog + Gallery
|
||
|
||
Add photo galleries to a blog site by including the gallery template in your blog's template directory.
|
||
|
||
### Documentation + Blog
|
||
|
||
Add a `/blog` section to documentation by including blog templates alongside docs templates.
|
||
|
||
### Portfolio + Blog
|
||
|
||
Showcase projects and share thoughts by combining portfolio and blog patterns.
|
||
|
||
## Customization Tips
|
||
|
||
1. **Start with one recipe** - Get it working before adding complexity
|
||
2. **Modify styles first** - Change colors, fonts, spacing to match your brand
|
||
3. **Adjust layouts gradually** - Start with structure, refine as you go
|
||
4. **Add features incrementally** - Don't try to implement everything at once
|
||
|
||
## Next Steps
|
||
|
||
- **[Template System](../templates/)** - Understanding how templates work
|
||
- **[Template Helpers](../templates/template-helpers.md)** - Complete API reference
|
||
- **[Styles Guide](../styles/)** - Styling your site
|
||
- **[Explore Foldsites](../explore.md)** - See real examples
|
||
|
||
Copy these recipes, make them your own, and build something amazing!
|