Files
foldsite/docs/content/recipes/index.md
Tanishq Dubey ad81d7f3db
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
docs refactor
2025-10-09 18:21:23 -04:00

20 KiB
Raw Blame History

version, date, author, title, description, summary, quick_tips
version date author title description summary quick_tips
1.0 2025-01-15 DWS Foldsite Team Template Recipes Ready-to-use examples for common Foldsite patterns Complete, working examples for building blogs, photo galleries, documentation sites, and more with Foldsite.
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.

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

---
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

<!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)

<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)

<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)

<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>

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

<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

.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)

<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

<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:

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

Copy these recipes, make them your own, and build something amazing!