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,682 @@
---
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>&copy; 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!