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

@ -1,35 +0,0 @@
# Configuration
## Configuration file
Foldsite is configured using a TOML file (default: `config.toml`). This file specifies paths to content, templates, and styles, as well as server settings.
Example `config.toml`:
```toml
[paths]
content_dir = "/home/myuser/site/content"
templates_dir = "/home/myuser/site/themes/cobalt/templates"
styles_dir = "/home/myuser/site/themes/cobalt/styles"
[server]
listen_address = "127.0.0.1"
listen_port = 8080
debug = false
access_log = true
max_threads = 4
admin_browser = false
admin_password = "your_admin_password"
```
## Server Settings
- **`listen_address`**: The IP address the server listens on (default: `127.0.0.1`).
- **`listen_port`**: The port the server listens on (default: `8080`).
- **`debug`**: Enables or disables debug mode (default: `false`). In debug mode, the server will automatically reload when code changes are detected, and more detailed error messages will be shown.
- **`access_log`**: Enables or disables access logging (default: `true`). If enabled, access logs will be written to standard output.
- **`max_threads`**: The maximum number of threads to use for handling requests (default: `4`). This setting directly impacts the concurrency of the server.
- **`admin_browser`**: Enables or disables the built-in file manager (default: `false`).
- **`admin_password`**: Sets the password for the file manager. Required if `admin_browser` is `true`.
The `Configuration` class (`/foldsite/src/config/config.py`) is responsible for loading and parsing this configuration file. It also sets global variables (`CONTENT_DIR`, `TEMPLATES_DIR`, `STYLES_DIR`) for easy access to these directories throughout the application. Errors are raised if the config file is missing, invalid, or lacks required sections (like `paths` or `server`).

View File

@ -1,56 +0,0 @@
## Rendering Process
The `RouteManager` class (`/foldsite/src/routes/routes.py`) and `render_page` function (`/foldsite/src/rendering/renderer.py`) are central to the rendering process.
### How Foldsite Determines File Types
The `determine_type` function (in `renderer.py`) is crucial for figuring out how to handle a given file or directory. It examines file extensions and directory contents to classify files into broad categories (defined in `GENERIC_FILE_MAPPING` in `/foldsite/src/rendering/__init__.py`):
* **`document`**: Files with extensions like `.md`, `.txt`, and `.html`.
* **`image`**: Files with extensions like `.png`, `.jpg`, `.jpeg`, `.gif`, and `.svg`.
* **`directory`**: Directories. If a directory contains files, the most common file extension within that directory is used to infer the directory's "type".
* **`other`**: Files that don't match any of the above categories.
* **`multimedia`**: This is a combination that contains `image`.
### Template Search
When a request comes in, Foldsite searches for an appropriate template in the `templates` directory. The search logic is implemented in `render_page` and follows a specific order, prioritizing more specific templates:
1. **Exact Path Match:** If a template exists with the exact same path relative to the `templates` directory as the requested content file (but with a `.html` extension), it's used. For example, if the request is for `/about/team.md`, and a template exists at `templates/about/team.md.html`, that template will be used.
2. **Folder-Specific Template:** If the requested path is a directory, Foldsite looks for a `__folder.html` template within that directory. For example, if the request is for `/blog/`, and `templates/blog/__folder.html` exists, it will be used.
3. **Type and Extension-Specific Templates:** Foldsite searches for templates named `__{type}.{extension}.html` within the requested directory and its parent directories, moving upwards. For instance, if requesting `/blog/post1.md`, it would look for:
* `templates/blog/__file.md.html`
* `templates/__file.md.html`
4. **Type and Category-Specific Templates:** Similar to the above, but searches for `__{type}.{category}.html`. If requesting an image at `/images/logo.png`, it looks for:
* `templates/images/__file.image.html`
* `templates/__file.image.html`
5. **Base Template:** Finally, if no other template is found, `templates/base.html` is used as a fallback. This template *must* exist; otherwise, an exception is raised.
### Style Search
CSS styles are searched similarly to templates, prioritizing specificity:
1. **Exact Path Match:** A CSS file with the exact same path as the requested content file (relative to the `styles` directory) is used. For example, `/about/team.md` would look for `styles/about/team.md.css`.
2. **Type and Extension-Specific Styles:** Searches for `__{type}.{extension}.css` in the requested directory and its parent directories. For example, `/blog/post1.md` would look for:
* `styles/blog/__file.md.css`
* `styles/__file.md.css`
3. **Type and Category-Specific Styles:** Similar to the above, but searches for `__{type}.{category}.css`.
* `styles/images/__file.image.css`
* `styles/__file.image.css`
4. **Base Style:** `styles/base.css` is always included.
The discovered styles are added to the `styles` variable, which is passed to the Jinja2 template. The order ensures that more specific styles override general ones.
### Error Handling
The `render_error_page` function (in `renderer.py`) handles errors. If a requested resource is not found (404 error) or if an exception occurs during processing, this function is called. It looks for a template named `__error.html` in the `templates` directory. If found, it's used to render the error page; otherwise, a default error page is generated. The error code, message, and description are passed to the template.

219
docs/content/about.md Normal file
View File

@ -0,0 +1,219 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "About Foldsite"
description: "The philosophy and story behind Foldsite"
summary: "Learn why Foldsite was created and how it empowers you to reclaim your corner of the internet with simple, file-based content management."
quick_tips:
- "Foldsite is part of the DWS mission to help people own their web presence"
- "Your content stays as simple files and folders - no database lock-in"
- "Built for people who want to focus on content, not configuration"
---
# About Foldsite
## The Vision
**Foldsite** exists to make creating and hosting your own website as simple as organizing files on your computer. It's part of the broader [DWS (Dubey Web Services)](https://dws.rip) mission: **"It's your Internet. Take it back."**
In an era where social media platforms control your content and complex CMSs require constant maintenance, Foldsite offers a refreshing alternative: your website is just folders and files on your filesystem.
## Why Foldsite Was Created
### The Problem
Modern web development has become unnecessarily complex:
- **Content Management Systems** require databases, constant updates, and security patches
- **Static Site Generators** force you to learn specific frameworks and build processes
- **Social Media Platforms** own your content and can remove it at any time
- **Blog Platforms** lock you into their ecosystem with proprietary formats
### The Solution
Foldsite strips away the complexity:
1. **Your content is just files** - Markdown, images, PDFs - organize them however makes sense
2. **Your structure is your site** - Folders become URLs automatically
3. **Templates are optional** - Start with defaults, customize when you need
4. **No build step required** - Dynamic server or export to static files
## Core Principles
### 1. Content Ownership
Your content lives in simple text files using standard markdown format. No proprietary databases, no vendor lock-in. Take your files anywhere - they'll work with any markdown tool.
### 2. Convention Over Configuration
Foldsite uses sensible defaults based on file types and folder structure:
- Markdown files become pages
- Image folders become galleries
- Folder names become navigation
- No routing configuration needed
### 3. Progressive Enhancement
Start with the simplest possible setup:
```
content/
└── index.md
```
Add complexity only when you need it:
```
content/
├── index.md
├── blog/
│ └── post.md
templates/
├── base.html
└── __file.md.html
styles/
└── base.css
```
### 4. Developer Friendly
When things don't work as expected:
- Clear error messages explain what went wrong
- Debug mode shows template discovery process
- File-based structure makes troubleshooting intuitive
## Who Foldsite Is For
### Content Creators
Writers, photographers, artists who want to share their work without fighting with technology. Focus on creating, not configuring.
### Personal Website Owners
People who want a simple blog, portfolio, or personal site without the overhead of WordPress or the limitations of site builders.
### Documentation Writers
Technical writers and project maintainers who need clean, navigable documentation that mirrors their mental model.
### Privacy-Conscious Individuals
Anyone who wants to control their web presence without relying on platforms that monetize user data.
### Hobbyist Developers
Developers who appreciate simple, understandable tools and want to hack on their personal sites without complex build pipelines.
## What Foldsite Is NOT
To set clear expectations:
- **Not a CMS replacement** - No admin UI for non-technical users (though an optional file manager exists)
- **Not optimized for huge sites** - Works best with hundreds to thousands of pages, not millions
- **Not a full application framework** - It renders content, doesn't handle complex application logic
- **Not trying to replace everything** - It's a focused tool for a specific use case
## The Technology
Foldsite is built with:
- **Python & Flask** - Proven, stable web framework
- **Jinja2** - Powerful templating with familiar syntax
- **Markdown** - Universal content format
- **No JavaScript required** - Works with JS disabled (progressive enhancement)
It can run as:
- **Dynamic server** - Live rendering with Python
- **Static site** - Export to HTML files
- **Docker container** - Easy deployment anywhere
## The DWS Philosophy
DWS (Dubey Web Services) believes the internet should be:
### Decentralized
Not controlled by a handful of mega-platforms. Everyone should be able to host their own corner of the web.
### Simple
Technology should serve users, not the other way around. Complexity is often unnecessary.
### Yours
You should own your content, your presentation, and your digital presence.
### Open
Open source tools, open standards, open community. No lock-in, no secrets.
## Use Cases
### Personal Blog
Write in markdown, organize by topic or date, let Foldsite handle the rest. Built-in helpers for recent posts, tags, and related content.
### Photography Portfolio
Upload images to folders, automatic EXIF extraction, thumbnail generation, and gallery views. Organize by project, date, or theme.
### Documentation Site
Mirror your project structure in folders, automatic navigation and breadcrumbs, searchable content hierarchy.
### Digital Garden
Non-linear, interconnected notes and thoughts. Flexible organization, easy linking between pages.
### Project Showcase
Portfolio of work with custom templates per project. Mix markdown descriptions with galleries and downloads.
## The Name
**Foldsite** = **Fold**er + Web**site**
Your folders become your site. Simple as that.
## Community & Contribution
Foldsite is open source and welcomes contributions:
- **Use it** - Build your site and share what you create
- **Improve it** - Submit bug fixes and features
- **Extend it** - Create themes and templates
- **Share it** - Tell others about the project
See the [Develop Section](develop/) for contribution guidelines.
## Getting Help
- **Documentation** - You're reading it! Check other sections for specific topics
- **Examples** - See [Explore Foldsites](explore.md) for real-world sites
- **Support** - Visit [Support](support.md) for help channels
- **Source Code** - [GitHub Repository](https://github.com/DWSresearch/foldsite)
## Future Vision
Foldsite aims to remain focused and simple while adding:
- More template helpers for common use cases
- Better performance optimization
- Enhanced developer tooling
- Growing library of themes and recipes
- Strong community of Foldsite users
The goal is never to become a monolithic CMS, but to remain a focused tool that does one thing well: turns folders into websites.
## Start Building
Ready to create your own site?
- [Quick Start Guide](index.md#quick-start)
- [Directory Structure](directory-structure.md)
- [Deployment Options](deployment/)
- [Template Recipes](recipes/)
**It's your internet. Take it back.**

View File

@ -0,0 +1,735 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Docker Deployment"
description: "Running Foldsite in Docker containers"
summary: "Complete guide to deploying Foldsite with Docker for consistent, isolated, and portable deployments."
quick_tips:
- "Docker ensures consistent environments across development and production"
- "Use docker-compose for easy multi-container setup"
- "Mount content as volumes for live updates without rebuilding"
---
# Docker Deployment
Docker provides isolated, reproducible deployments of Foldsite. Perfect for testing, staging, and production environments.
## Why Docker?
**Benefits:**
- **Consistency** - Same environment everywhere
- **Isolation** - Dependencies don't conflict with system
- **Portability** - Run anywhere Docker runs
- **Easy deployment** - Single command to start
- **Version control** - Docker images are versioned
**Use cases:**
- Team development (everyone has same environment)
- Staging environments before production
- Production deployments
- CI/CD pipelines
- Cloud platform deployments
## Prerequisites
### Install Docker
**macOS:**
```bash
# Download Docker Desktop from docker.com
# Or use Homebrew
brew install --cask docker
```
**Linux (Ubuntu/Debian):**
```bash
# Update package index
sudo apt update
# Install Docker
sudo apt install docker.io docker-compose
# Add your user to docker group
sudo usermod -aG docker $USER
# Log out and back in for group changes
```
**Windows:**
```bash
# Download Docker Desktop from docker.com
# Requires WSL2
```
**Verify installation:**
```bash
docker --version
docker-compose --version
```
## Quick Start with Docker Compose
### Step 1: Create docker-compose.yml
In your Foldsite directory:
```yaml
version: '3.8'
services:
foldsite:
image: python:3.11-slim
container_name: foldsite
working_dir: /app
command: >
sh -c "pip install -r requirements.txt &&
python main.py --config config.toml"
ports:
- "8081:8081"
volumes:
- .:/app
- ./my-site/content:/app/content
- ./my-site/templates:/app/templates
- ./my-site/styles:/app/styles
environment:
- PYTHONUNBUFFERED=1
restart: unless-stopped
```
### Step 2: Create config.toml for Docker
```toml
[paths]
content_dir = "/app/content"
templates_dir = "/app/templates"
styles_dir = "/app/styles"
[server]
listen_address = "0.0.0.0" # Important: bind to all interfaces
listen_port = 8081
admin_browser = false
max_threads = 4
debug = false
access_log = true
```
### Step 3: Start Container
```bash
# Start in background
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose down
```
Visit `http://localhost:8081` to see your site!
## Building a Custom Docker Image
For production, build a dedicated Foldsite image:
### Create Dockerfile
```dockerfile
# Dockerfile
FROM python:3.11-slim
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first (for caching)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Create directories for content
RUN mkdir -p /content /templates /styles
# Expose port
EXPOSE 8081
# Run application
CMD ["python", "main.py", "--config", "/app/config.toml"]
```
### Build Image
```bash
# Build image
docker build -t foldsite:latest .
# Tag for versioning
docker tag foldsite:latest foldsite:1.0.0
```
### Run Container
```bash
docker run -d \
--name foldsite \
-p 8081:8081 \
-v $(pwd)/my-site/content:/content \
-v $(pwd)/my-site/templates:/templates \
-v $(pwd)/my-site/styles:/styles \
foldsite:latest
```
## Development with Docker
### Hot Reload Setup
Mount your code as volumes for live updates:
```yaml
# docker-compose.dev.yml
version: '3.8'
services:
foldsite:
build: .
container_name: foldsite-dev
ports:
- "8081:8081"
volumes:
# Mount everything for development
- .:/app
- ./my-site/content:/app/content
- ./my-site/templates:/app/templates
- ./my-site/styles:/app/styles
environment:
- PYTHONUNBUFFERED=1
- FLASK_ENV=development
command: >
sh -c "pip install -r requirements.txt &&
python main.py --config config.toml"
```
**Usage:**
```bash
# Start development environment
docker-compose -f docker-compose.dev.yml up
# Changes to content, templates, and styles appear immediately
# Code changes require restart
```
### Interactive Development
Run commands inside container:
```bash
# Start bash session in running container
docker exec -it foldsite bash
# Inside container, you can:
python main.py --config config.toml
pip install new-package
ls /app/content
```
## Production Docker Setup
### Multi-Stage Build
Optimize image size with multi-stage build:
```dockerfile
# Dockerfile.production
# Stage 1: Build dependencies
FROM python:3.11-slim as builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Stage 2: Runtime
FROM python:3.11-slim
WORKDIR /app
# Copy Python dependencies from builder
COPY --from=builder /root/.local /root/.local
# Copy application
COPY . .
# Create non-root user
RUN useradd -m -u 1000 foldsite && \
chown -R foldsite:foldsite /app
# Switch to non-root user
USER foldsite
# Make sure scripts in .local are usable
ENV PATH=/root/.local/bin:$PATH
EXPOSE 8081
# Use Gunicorn for production
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8081", "main:app"]
```
### Production docker-compose.yml
```yaml
version: '3.8'
services:
foldsite:
build:
context: .
dockerfile: Dockerfile.production
container_name: foldsite-prod
ports:
- "8081:8081"
volumes:
- ./content:/app/content:ro # Read-only
- ./templates:/app/templates:ro
- ./styles:/app/styles:ro
environment:
- PYTHONUNBUFFERED=1
restart: always
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8081/"]
interval: 30s
timeout: 10s
retries: 3
```
## Docker Compose Examples
### With Nginx Reverse Proxy
```yaml
version: '3.8'
services:
foldsite:
build: .
container_name: foldsite
expose:
- "8081"
volumes:
- ./content:/app/content:ro
- ./templates:/app/templates:ro
- ./styles:/app/styles:ro
restart: always
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- foldsite
restart: always
```
**nginx.conf:**
```nginx
events {
worker_connections 1024;
}
http {
upstream foldsite {
server foldsite:8081;
}
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://foldsite;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
proxy_pass http://foldsite;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
}
```
### Multiple Sites
Run multiple Foldsite instances:
```yaml
version: '3.8'
services:
blog:
build: .
ports:
- "8081:8081"
volumes:
- ./blog/content:/app/content
- ./blog/templates:/app/templates
- ./blog/styles:/app/styles
restart: always
portfolio:
build: .
ports:
- "8082:8081"
volumes:
- ./portfolio/content:/app/content
- ./portfolio/templates:/app/templates
- ./portfolio/styles:/app/styles
restart: always
```
## Volume Management
### Content Volumes
**Development** - Mount host directories:
```yaml
volumes:
- ./my-site/content:/app/content
- ./my-site/templates:/app/templates
- ./my-site/styles:/app/styles
```
**Production** - Read-only mounts:
```yaml
volumes:
- ./content:/app/content:ro
- ./templates:/app/templates:ro
- ./styles:/app/styles:ro
```
### Named Volumes
For persistent data:
```yaml
services:
foldsite:
volumes:
- content-data:/app/content
- templates-data:/app/templates
volumes:
content-data:
templates-data:
```
**Backup named volumes:**
```bash
# Backup
docker run --rm \
-v foldsite_content-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/content-backup.tar.gz -C /data .
# Restore
docker run --rm \
-v foldsite_content-data:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/content-backup.tar.gz -C /data
```
## Environment Variables
Pass configuration via environment:
```yaml
services:
foldsite:
environment:
- FOLDSITE_DEBUG=false
- FOLDSITE_PORT=8081
- FOLDSITE_MAX_THREADS=4
```
**Use in config:**
```python
# In a config loader
import os
debug = os.getenv('FOLDSITE_DEBUG', 'false').lower() == 'true'
port = int(os.getenv('FOLDSITE_PORT', '8081'))
```
## Common Docker Commands
### Container Management
```bash
# Start containers
docker-compose up -d
# Stop containers
docker-compose down
# Restart
docker-compose restart
# View logs
docker-compose logs -f
# View logs for specific service
docker-compose logs -f foldsite
# Exec into running container
docker exec -it foldsite bash
# View running containers
docker ps
# View all containers (including stopped)
docker ps -a
```
### Image Management
```bash
# Build image
docker-compose build
# Pull images
docker-compose pull
# List images
docker images
# Remove unused images
docker image prune
# Remove all unused data
docker system prune -a
```
### Debugging
```bash
# Check container logs
docker logs foldsite
# Follow logs
docker logs -f foldsite
# Inspect container
docker inspect foldsite
# View container stats
docker stats foldsite
# Check container health
docker ps --filter health=healthy
```
## Troubleshooting
### Port Already in Use
```
Error: port is already allocated
```
**Solution:** Change port mapping:
```yaml
ports:
- "8082:8081" # Map to different host port
```
### Permission Errors
```
PermissionError: [Errno 13] Permission denied
```
**Solution:** Fix volume permissions:
```bash
# Fix ownership
sudo chown -R $USER:$USER ./my-site
# Or run container as your user
docker run --user $(id -u):$(id -g) ...
```
### Container Won't Start
```bash
# Check logs
docker-compose logs
# Common issues:
# 1. Missing requirements.txt
# 2. Wrong working directory
# 3. Port conflicts
# 4. Volume mount errors
```
### Changes Not Appearing
**Content changes:**
- Should appear immediately (volumes mounted)
- Try hard refresh in browser
**Code changes:**
- Require container restart: `docker-compose restart`
**Template changes:**
- Should appear immediately
- Check volume mounts are correct
### Container Crashes
```bash
# View exit reason
docker ps -a
# Check logs
docker logs foldsite
# Try running interactively
docker run -it foldsite bash
```
## Performance Optimization
### Resource Limits
Limit container resources:
```yaml
services:
foldsite:
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
```
### Build Cache
Speed up builds:
```bash
# Use BuildKit
DOCKER_BUILDKIT=1 docker build .
# Cache from registry
docker build --cache-from myregistry/foldsite:latest .
```
### Layer Optimization
Order Dockerfile for better caching:
```dockerfile
# Dependencies first (change rarely)
COPY requirements.txt .
RUN pip install -r requirements.txt
# Code last (changes often)
COPY . .
```
## CI/CD Integration
### GitHub Actions Example
```yaml
# .github/workflows/docker.yml
name: Build Docker Image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build image
run: docker build -t foldsite:${{ github.sha }} .
- name: Test
run: |
docker run -d --name test foldsite:${{ github.sha }}
docker logs test
docker stop test
```
## Cloud Platform Deployment
### Deploy to AWS ECS
```bash
# Build and push to ECR
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
docker tag foldsite:latest 123456789.dkr.ecr.us-east-1.amazonaws.com/foldsite:latest
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/foldsite:latest
```
### Deploy to Google Cloud Run
```bash
# Build and push to GCR
gcloud builds submit --tag gcr.io/PROJECT_ID/foldsite
gcloud run deploy --image gcr.io/PROJECT_ID/foldsite --platform managed
```
### Deploy to Azure Container Instances
```bash
# Create container instance
az container create \
--resource-group myResourceGroup \
--name foldsite \
--image myregistry.azurecr.io/foldsite:latest \
--ports 8081
```
## Next Steps
- **[Production Deployment](production.md)** - Production-grade setup
- **[Local Development](local-development.md)** - Development workflow
- **[Support](../support.md)** - Get help
Docker provides a solid foundation for deploying Foldsite anywhere. From development to production, containers ensure consistency and reliability.

View File

@ -0,0 +1,380 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Deployment Overview"
description: "Getting Foldsite running - from local development to production"
summary: "Learn how to deploy Foldsite in various environments: local development, Docker containers, or production servers."
quick_tips:
- "Start with local development for fastest iteration"
- "Docker provides consistent environments across machines"
- "Production deployment supports both dynamic and static modes"
---
# Deployment Overview
Foldsite is flexible in how you run it. Choose the deployment method that fits your needs:
## Deployment Options
### [Local Development](local-development.md)
**Best for:** Content creation, theme development, testing
Run Foldsite directly with Python for the fastest development cycle. Changes to content and templates appear immediately (no rebuild needed).
```bash
python main.py --config config.toml
```
**Pros:**
- Fastest iteration - see changes instantly
- Easy debugging with Python stack traces
- Full access to logs and error messages
**Cons:**
- Requires Python environment
- Manual dependency management
- Not suitable for production
**When to use:** Always use this during development. It's the fastest way to see your changes.
### [Docker Deployment](docker.md)
**Best for:** Consistent environments, easy deployment, testing production builds
Run Foldsite in a Docker container for isolated, reproducible deployments.
```bash
docker-compose up
```
**Pros:**
- Consistent across development and production
- Isolated dependencies
- Easy to share with team members
- Simplifies deployment to cloud platforms
**Cons:**
- Slight overhead from containerization
- Requires Docker knowledge
- Extra layer to debug
**When to use:** Use Docker when you need consistency across environments, or when deploying to platforms that support containers.
### [Production Deployment](production.md)
**Best for:** Public-facing websites, high-traffic sites, static hosting
Deploy Foldsite as either a dynamic Python server or export to static files.
**Dynamic Mode:**
```bash
gunicorn -w 4 -b 0.0.0.0:8081 main:app
```
**Static Export:**
```bash
# Generate static HTML files
python export.py --output ./dist
```
**Pros:**
- Optimized for production workloads
- Can use static hosting (cheap/free)
- Supports CDN caching
- Professional-grade performance
**Cons:**
- More complex setup
- Requires understanding of web servers
- Static mode requires rebuilds for updates
**When to use:** Use this for your live website once development is complete.
## Quick Comparison
| Method | Speed | Complexity | Best For |
|--------|-------|------------|----------|
| **Local Development** | ⚡⚡⚡ | ⭐ | Creating content and themes |
| **Docker** | ⚡⚡ | ⭐⭐ | Team collaboration, staging |
| **Production (Dynamic)** | ⚡⚡ | ⭐⭐⭐ | Live sites with frequent updates |
| **Production (Static)** | ⚡⚡⚡ | ⭐⭐⭐ | Live sites, maximum performance |
## Prerequisites
### For All Deployment Methods
1. **Git** (to clone the repository)
```bash
git --version
```
2. **A text editor** (VS Code, Sublime, vim, etc.)
### For Local Development
1. **Python 3.10+**
```bash
python3 --version
```
2. **pip** (Python package manager)
```bash
pip --version
```
### For Docker Deployment
1. **Docker** and **Docker Compose**
```bash
docker --version
docker-compose --version
```
### For Production Deployment
Choose based on your hosting strategy:
- **Dynamic mode:** Python 3.10+, web server (nginx/apache)
- **Static mode:** Any web server or static host (GitHub Pages, Netlify, etc.)
## Getting the Source Code
All deployment methods start by getting Foldsite:
```bash
# Clone the repository
git clone https://github.com/DWSresearch/foldsite.git
cd foldsite
# Or download and extract the latest release
wget https://github.com/DWSresearch/foldsite/archive/main.zip
unzip main.zip
cd foldsite-main
```
## Configuration
Every deployment uses a `config.toml` file to specify paths and settings:
```toml
[paths]
content_dir = "/path/to/your/content"
templates_dir = "/path/to/your/templates"
styles_dir = "/path/to/your/styles"
[server]
listen_address = "0.0.0.0"
listen_port = 8081
admin_browser = false
admin_password = "change-me"
max_threads = 4
debug = false
access_log = true
```
**Important:** Adjust these paths before running Foldsite!
### Development Config Example
```toml
[paths]
content_dir = "./my-site/content"
templates_dir = "./my-site/templates"
styles_dir = "./my-site/styles"
[server]
listen_address = "127.0.0.1" # Local only
listen_port = 8081
debug = true # Enable debug mode
access_log = true
```
### Production Config Example
```toml
[paths]
content_dir = "/var/www/site/content"
templates_dir = "/var/www/site/templates"
styles_dir = "/var/www/site/styles"
[server]
listen_address = "0.0.0.0" # All interfaces
listen_port = 8081
debug = false # Disable debug mode
access_log = true
max_threads = 8 # More workers for production
```
## Typical Deployment Workflow
### 1. Development Phase
Use **local development** for content creation and theme building:
```bash
# Edit content
vim content/my-post.md
# Run server
python main.py --config config.toml
# Visit http://localhost:8081
# See changes immediately
```
### 2. Testing Phase
Use **Docker** to test in a production-like environment:
```bash
# Build and run
docker-compose up
# Test on http://localhost:8081
# Verify everything works in container
```
### 3. Deployment Phase
Deploy to **production** using your preferred method:
```bash
# Option A: Dynamic server
gunicorn -w 4 main:app
# Option B: Static export
python export.py --output ./dist
rsync -avz ./dist/ user@server:/var/www/site/
```
## Common Scenarios
### Scenario: Personal Blog
**Best deployment:** Local development + static export to GitHub Pages
```bash
# Develop locally
python main.py --config config.toml
# Export to static files
python export.py --output ./docs
# Push to GitHub (Pages serves from /docs)
git add docs/
git commit -m "Update site"
git push
```
### Scenario: Team Documentation
**Best deployment:** Docker for everyone, dynamic server in production
```bash
# Everyone on the team uses Docker
docker-compose up
# Production uses dynamic Python server
# for real-time updates when docs change
```
### Scenario: Photography Portfolio
**Best deployment:** Local development, Docker for staging, static export for production
High-performance static site with CDN for fast image delivery.
## Troubleshooting
### Port Already in Use
```
OSError: [Errno 48] Address already in use
```
**Solution:** Change `listen_port` in config.toml or stop the other service using that port.
### Module Not Found
```
ModuleNotFoundError: No module named 'flask'
```
**Solution:** Install dependencies:
```bash
pip install -r requirements.txt
```
### Permission Denied
```
PermissionError: [Errno 13] Permission denied
```
**Solution:** Check directory permissions:
```bash
chmod -R 755 content/ templates/ styles/
```
### Template Not Found
```
Exception: Base template not found
```
**Solution:** Ensure `base.html` exists in templates directory:
```bash
ls templates/base.html
```
## Next Steps
Choose your deployment path:
- **[Local Development Guide](deployment/local-development.md)** - Start here for development
- **[Docker Deployment Guide](deployment/docker.md)** - Containerized deployment
- **[Production Deployment Guide](deployment/production.md)** - Go live with your site
## Security Considerations
### Development
- **Only bind to localhost** (`listen_address = "127.0.0.1"`)
- **Never commit config.toml** with sensitive data to version control
### Production
- **Use HTTPS** - Always use TLS/SSL in production
- **Strong passwords** - If using admin interface, use strong passwords
- **Firewall rules** - Only expose necessary ports
- **Regular updates** - Keep Foldsite and dependencies updated
- **Content validation** - Be careful with user-uploaded content
See [Production Deployment](production.md) for detailed security guidance.
## Performance Tips
### For All Deployments
- **Use hidden files** for drafts (`___draft.md`) to avoid processing
- **Optimize images** before uploading (Foldsite generates thumbnails, but smaller source = faster)
- **Minimize template complexity** - Simple templates render faster
### For Production
- **Enable caching** at the web server level
- **Use a CDN** for static assets
- **Compress responses** with gzip/brotli
- **Monitor resource usage** and scale as needed
## Getting Help
Need assistance with deployment?
- **[Support](../support.md)** - Community help and resources
- **GitHub Issues** - Report bugs or ask questions
- **Example Configs** - See `/examples` directory in repository
Happy deploying!

View File

@ -0,0 +1,629 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Local Development"
description: "Running Foldsite on your local machine for development"
summary: "Complete guide to setting up and running Foldsite locally for the fastest development workflow."
quick_tips:
- "Local development gives instant feedback - no build step needed"
- "Use debug mode to see template discovery and errors clearly"
- "Changes to content and templates appear immediately on refresh"
---
# Local Development
Running Foldsite locally is the fastest way to develop your site. Changes to content and templates appear instantly without any build process.
## Prerequisites
### Required Software
**Python 3.10 or higher**
```bash
# Check your Python version
python3 --version
# Should output: Python 3.10.x or higher
```
If you don't have Python 3.10+:
- **macOS:** `brew install python3`
- **Ubuntu/Debian:** `sudo apt install python3.10`
- **Windows:** Download from [python.org](https://www.python.org/downloads/)
**pip (Python package manager)**
```bash
# Check pip version
pip --version
# If missing, install
python3 -m ensurepip --upgrade
```
**Git (recommended)**
```bash
git --version
```
### Optional but Recommended
**Virtual environment support**
```bash
python3 -m venv --help
```
**Text editor with syntax highlighting**
- VS Code (recommended)
- Sublime Text
- Vim/Neovim
- Any editor you prefer
## Installation
### Step 1: Get Foldsite
**Option A: Clone with Git (recommended)**
```bash
# Clone the repository
git clone https://github.com/DWSresearch/foldsite.git
cd foldsite
```
**Option B: Download ZIP**
```bash
# Download latest release
wget https://github.com/DWSresearch/foldsite/archive/main.zip
unzip main.zip
cd foldsite-main
```
### Step 2: Create Virtual Environment
Using a virtual environment keeps dependencies isolated:
```bash
# Create virtual environment
python3 -m venv venv
# Activate it
# On macOS/Linux:
source venv/bin/activate
# On Windows:
venv\Scripts\activate
# Your prompt should now show (venv)
```
**Why virtual environment?**
- Isolates project dependencies
- Prevents conflicts with system Python
- Easy to recreate or remove
- Professional Python practice
### Step 3: Install Dependencies
```bash
# Ensure pip is up to date
pip install --upgrade pip
# Install Foldsite dependencies
pip install -r requirements.txt
```
**Dependencies installed:**
- Flask - Web framework
- Jinja2 - Template engine
- mistune - Markdown parser
- python-frontmatter - YAML frontmatter parsing
- Pillow - Image processing
- Gunicorn - WSGI server
**Troubleshooting:**
If you see compilation errors:
```bash
# Install build tools
# macOS:
xcode-select --install
# Ubuntu/Debian:
sudo apt install python3-dev build-essential
# Then retry:
pip install -r requirements.txt
```
### Step 4: Set Up Your Content
Create your site directory structure:
```bash
# Create your site directories
mkdir -p my-site/content
mkdir -p my-site/templates
mkdir -p my-site/styles
# Create a basic index page
echo "# Welcome to My Site" > my-site/content/index.md
# Copy example templates to start
cp -r example_site/template/* my-site/templates/
cp -r example_site/style/* my-site/styles/
```
### Step 5: Configure Foldsite
Create a configuration file:
```bash
# Copy example config
cp config.toml my-site-config.toml
```
Edit `my-site-config.toml`:
```toml
[paths]
content_dir = "/absolute/path/to/my-site/content"
templates_dir = "/absolute/path/to/my-site/templates"
styles_dir = "/absolute/path/to/my-site/styles"
[server]
listen_address = "127.0.0.1" # Only accessible from your machine
listen_port = 8081
admin_browser = false # Disable admin interface for now
max_threads = 4
debug = true # Enable debug mode for development
access_log = true
```
**Important:** Use absolute paths in development to avoid confusion.
**Find absolute path:**
```bash
# macOS/Linux:
cd my-site && pwd
# Windows:
cd my-site && cd
```
## Running Foldsite
### Start the Server
```bash
# Make sure virtual environment is activated
source venv/bin/activate # or venv\Scripts\activate on Windows
# Run Foldsite
python main.py --config my-site-config.toml
```
**Expected output:**
```
[2025-01-15 10:30:45] [INFO] Starting Foldsite server
[2025-01-15 10:30:45] [INFO] Content directory: /path/to/my-site/content
[2025-01-15 10:30:45] [INFO] Templates directory: /path/to/my-site/templates
[2025-01-15 10:30:45] [INFO] Listening on http://127.0.0.1:8081
```
### Visit Your Site
Open your browser to:
```
http://localhost:8081
```
You should see your site!
### Stop the Server
Press `Ctrl+C` in the terminal where Foldsite is running.
## Development Workflow
### The Edit-Refresh Cycle
Foldsite has **no build step**. Changes appear immediately:
1. **Edit content** - Modify markdown files
2. **Save file** - Ctrl+S / Cmd+S
3. **Refresh browser** - F5 or Cmd+R
4. **See changes instantly**
**What updates live:**
- Content (markdown files)
- Templates (HTML files)
- Styles (CSS files)
- Configuration (requires restart)
### Example Workflow
**Scenario:** Adding a blog post
```bash
# 1. Create new post
vim my-site/content/blog/my-new-post.md
```
```markdown
---
title: "My New Blog Post"
date: "2025-01-15"
tags: ["tutorial", "foldsite"]
---
# My New Blog Post
This is my latest post!
```
```bash
# 2. Save file and switch to browser
# 3. Visit http://localhost:8081/blog/my-new-post.md
# 4. See your post rendered immediately
```
**No restart needed!**
### Working with Templates
```bash
# 1. Edit template
vim my-site/templates/__file.md.html
```
```html
<!-- Add a new section -->
<article>
<header>
<h1>{{ metadata.title }}</h1>
<time>{{ metadata.date }}</time>
</header>
{{ content|safe }}
<!-- NEW: Add related posts -->
{% set related = get_related_posts(currentPath, limit=3) %}
{% if related %}
<aside class="related">
<h3>Related Posts</h3>
{% for post in related %}
<a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}
</aside>
{% endif %}
</article>
```
```bash
# 2. Save and refresh browser
# 3. See related posts section appear
```
### Working with Styles
```bash
# 1. Edit CSS
vim my-site/styles/base.css
```
```css
/* Add new styles */
.related {
margin-top: 2rem;
padding: 1rem;
background: #f5f5f5;
border-radius: 4px;
}
.related h3 {
margin-bottom: 0.5rem;
}
```
```bash
# 2. Save and hard refresh (Cmd+Shift+R / Ctrl+Shift+R)
# 3. See styled related posts
```
## Debug Mode
Enable debug mode for helpful development information:
```toml
[server]
debug = true
```
**What debug mode shows:**
- Template discovery process
- Which templates were considered
- Which template was chosen
- Detailed error messages with stack traces
- Template variables and context
**Example debug output:**
When visiting a page, console shows:
```
[DEBUG] Template search for: blog/my-post.md
[DEBUG] Checking: templates/blog/my-post.html - Not found
[DEBUG] Checking: templates/blog/__file.md.html - Found!
[DEBUG] Using template: templates/blog/__file.md.html
[DEBUG] Available variables: content, metadata, currentPath, styles
```
**View in browser:**
With debug mode, error pages show:
- Full Python stack trace
- Template rendering context
- What went wrong and where
- Suggestions for fixes
## Common Development Tasks
### Creating New Pages
```bash
# Simple page
echo "# About Me\n\nI'm a web developer." > my-site/content/about.md
# With frontmatter
cat > my-site/content/projects.md << 'EOF'
---
title: "My Projects"
description: "Things I've built"
---
# My Projects
Here are some things I've worked on...
EOF
```
### Organizing Content
```bash
# Create a blog section
mkdir -p my-site/content/blog
# Add posts
for i in {1..5}; do
cat > my-site/content/blog/post-$i.md << EOF
---
title: "Blog Post $i"
date: "2024-01-$i"
tags: ["example"]
---
# Post $i
Content here...
EOF
done
```
### Testing Template Helpers
Create a test page to experiment:
```bash
cat > my-site/content/test.md << 'EOF'
---
title: "Template Helper Test"
---
# Testing Helpers
## Recent Posts
{% for post in get_recent_posts(limit=5) %}
- [{{ post.title }}]({{ post.url }}) - {{ post.date }}
{% endfor %}
## All Tags
{% for tag in get_all_tags() %}
- {{ tag.name }} ({{ tag.count }})
{% endfor %}
## Current Path Info
- Path: {{ currentPath }}
- Metadata: {{ metadata }}
EOF
```
Visit `/test.md` to see helper output.
### Hiding Work in Progress
Use the `___` prefix:
```bash
# Hidden draft
vim my-site/content/___draft-post.md
# Hidden development folder
mkdir my-site/content/___testing
```
These won't appear in navigation or listings.
## Editor Setup
### VS Code
Recommended extensions:
- **Python** (Microsoft)
- **Jinja** (wholroyd.jinja)
- **Markdown All in One** (yzhang.markdown-all-in-one)
**Settings:**
```json
{
"files.associations": {
"*.html": "jinja-html"
},
"editor.formatOnSave": true
}
```
### Vim/Neovim
Add to `.vimrc` or `init.vim`:
```vim
" Jinja syntax for .html files
autocmd BufNewFile,BufRead */templates/*.html set filetype=jinja
" Markdown settings
autocmd FileType markdown setlocal spell spelllang=en_us
autocmd FileType markdown setlocal textwidth=80
```
## Troubleshooting
### Port Already in Use
```
Error: [Errno 48] Address already in use
```
**Solution 1:** Change port in config:
```toml
[server]
listen_port = 8082
```
**Solution 2:** Find and kill process:
```bash
# Find process using port 8081
lsof -i :8081
# Kill it
kill -9 <PID>
```
### Module Not Found
```
ModuleNotFoundError: No module named 'flask'
```
**Solution:**
```bash
# Ensure virtual environment is activated
source venv/bin/activate
# Reinstall dependencies
pip install -r requirements.txt
```
### Template Not Found
```
Exception: Base template not found
```
**Solution:**
```bash
# Check templates directory exists and has base.html
ls my-site/templates/base.html
# If missing, copy from example
cp example_site/template/base.html my-site/templates/
```
### Changes Not Appearing
**Possible causes:**
1. **Browser cache** - Hard refresh (Cmd/Ctrl + Shift + R)
2. **Wrong file** - Check you're editing the file Foldsite is using
3. **Configuration issue** - Verify config.toml paths
4. **Syntax error** - Check console for error messages
**Debug:**
```bash
# Enable debug mode
# Edit config.toml
[server]
debug = true
# Restart Foldsite
# Check console output
```
### Permission Denied
```
PermissionError: [Errno 13] Permission denied
```
**Solution:**
```bash
# Fix permissions
chmod -R 755 my-site/
# Or run with correct user
sudo chown -R $USER:$USER my-site/
```
## Performance Tips
### Development Speed
**Fast iteration:**
- Keep browser DevTools open
- Use multiple browser windows for comparison
- Enable auto-reload browser extensions
- Use terminal multiplexer (tmux/screen)
**Organize workspace:**
```
Terminal 1: Foldsite server
Terminal 2: Content editing
Terminal 3: Git operations
Browser: Live preview
Editor: Code/templates
```
### Working with Large Sites
If your site has many files:
```toml
[server]
max_threads = 8 # Increase workers
```
**Cache considerations:**
- Template helpers are cached automatically
- Folder contents cached for 5 minutes
- Recent posts cached for 10 minutes
- Restart server to clear caches
## Next Steps
Now that you have local development running:
- **[Templates Guide](../templates/)** - Learn the template system
- **[Template Helpers](../templates/template-helpers.md)** - Explore helper functions
- **[Recipes](../recipes/)** - Copy working examples
- **[Docker Deployment](docker.md)** - Test in container
- **[Production Deployment](production.md)** - Go live
## Tips from Developers
> "Keep the server running in a dedicated terminal. Switch to it to see errors immediately." — Foldsite contributor
> "Use `___testing/` folder for experimenting. It's ignored so you can mess around without affecting the site." — Content creator
> "Debug mode is your friend. Always enable it in development." — Theme developer
> "Create test pages to try helper functions. Much faster than reading docs." — Documentation writer
Happy developing! Local development is where the magic happens. 🚀

View File

@ -0,0 +1,810 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Production Deployment"
description: "Deploying Foldsite for production use"
summary: "Complete guide to deploying Foldsite in production - from VPS servers to static hosting, with security, performance, and reliability best practices."
quick_tips:
- "Use Gunicorn with multiple workers for dynamic deployments"
- "Static export is fastest and cheapest for sites that don't change frequently"
- "Always use HTTPS in production with proper SSL certificates"
---
# Production Deployment
Deploy Foldsite to serve real traffic with reliability, security, and performance.
## Deployment Options
### Option 1: Dynamic Server (Python)
**Best for:**
- Frequently updated content
- Sites needing template helpers in real-time
- Admin file browser interface
- Dynamic content generation
**Characteristics:**
- Runs Python/Gunicorn server
- Content updates appear immediately
- Template helpers work dynamically
- Requires server with Python
**Hosting:** VPS, dedicated server, PaaS platforms
### Option 2: Static Export
**Best for:**
- Infrequently updated sites
- Maximum performance
- Minimal cost
- CDN delivery
**Characteristics:**
- Pre-rendered HTML files
- Blazing fast delivery
- Can host anywhere (GitHub Pages, Netlify, S3)
- Rebuild required for updates
**Hosting:** Static hosts, CDN, object storage
## Dynamic Server Deployment
### Prerequisites
- Linux server (Ubuntu 20.04+ recommended)
- Python 3.10+
- Domain name
- SSH access
### Step 1: Server Setup
**Update system:**
```bash
sudo apt update
sudo apt upgrade -y
```
**Install dependencies:**
```bash
# Python and pip
sudo apt install -y python3.10 python3-pip python3-venv
# Build tools
sudo apt install -y build-essential python3-dev
# Nginx
sudo apt install -y nginx
# Certbot for SSL
sudo apt install -y certbot python3-certbot-nginx
```
### Step 2: Deploy Foldsite
**Create user:**
```bash
sudo useradd -m -s /bin/bash foldsite
sudo su - foldsite
```
**Clone and setup:**
```bash
# Clone repository
git clone https://github.com/DWSresearch/foldsite.git
cd foldsite
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
pip install gunicorn # Production WSGI server
```
**Setup your content:**
```bash
# Create site structure
mkdir -p ~/site/content
mkdir -p ~/site/templates
mkdir -p ~/site/styles
# Copy your content
# (upload via SCP, rsync, or git)
```
**Create production config:**
```bash
vim ~/foldsite/production-config.toml
```
```toml
[paths]
content_dir = "/home/foldsite/site/content"
templates_dir = "/home/foldsite/site/templates"
styles_dir = "/home/foldsite/site/styles"
[server]
listen_address = "127.0.0.1" # Only accept local connections
listen_port = 8081
admin_browser = false # Disable for security
max_threads = 8 # Adjust based on server
debug = false # Never enable in production
access_log = true
```
### Step 3: Systemd Service
Create service file:
```bash
sudo vim /etc/systemd/system/foldsite.service
```
```ini
[Unit]
Description=Foldsite Web Server
After=network.target
[Service]
Type=simple
User=foldsite
Group=foldsite
WorkingDirectory=/home/foldsite/foldsite
Environment="PATH=/home/foldsite/foldsite/venv/bin"
ExecStart=/home/foldsite/foldsite/venv/bin/gunicorn \
--workers 4 \
--bind 127.0.0.1:8081 \
--access-logfile /var/log/foldsite/access.log \
--error-logfile /var/log/foldsite/error.log \
--config /home/foldsite/foldsite/production-config.toml \
main:app
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
**Create log directory:**
```bash
sudo mkdir -p /var/log/foldsite
sudo chown foldsite:foldsite /var/log/foldsite
```
**Enable and start service:**
```bash
sudo systemctl daemon-reload
sudo systemctl enable foldsite
sudo systemctl start foldsite
# Check status
sudo systemctl status foldsite
```
### Step 4: Nginx Reverse Proxy
**Create Nginx config:**
```bash
sudo vim /etc/nginx/sites-available/foldsite
```
```nginx
upstream foldsite {
server 127.0.0.1:8081;
keepalive 32;
}
server {
listen 80;
server_name your-domain.com www.your-domain.com;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Logs
access_log /var/log/nginx/foldsite-access.log;
error_log /var/log/nginx/foldsite-error.log;
# Max upload size
client_max_body_size 10M;
# Proxy to Foldsite
location / {
proxy_pass http://foldsite;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://foldsite;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Security: deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
```
**Enable site:**
```bash
sudo ln -s /etc/nginx/sites-available/foldsite /etc/nginx/sites-enabled/
sudo nginx -t # Test configuration
sudo systemctl reload nginx
```
### Step 5: SSL Certificate
**Get certificate with Certbot:**
```bash
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
```
Follow prompts. Certbot will:
- Obtain certificate from Let's Encrypt
- Modify Nginx config for HTTPS
- Setup auto-renewal
**Verify auto-renewal:**
```bash
sudo certbot renew --dry-run
```
**Final Nginx config (with SSL):**
Certbot updates your config to include:
```nginx
server {
listen 443 ssl http2;
server_name your-domain.com www.your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# ... rest of config
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name your-domain.com www.your-domain.com;
return 301 https://$server_name$request_uri;
}
```
### Step 6: Firewall
**Configure UFW:**
```bash
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
```
### Verification
Visit your domain:
```
https://your-domain.com
```
Should see your Foldsite with valid SSL!
## Static Export Deployment
### Generate Static Files
*Note: Static export functionality may need to be implemented or use a static site generator mode*
**Conceptual approach:**
```python
# export.py
import os
from pathlib import Path
from src.rendering.renderer import render_page
from src.config.config import Configuration
def export_static(config_path, output_dir):
"""Export all pages to static HTML"""
config = Configuration(config_path)
config.load_config()
output = Path(output_dir)
output.mkdir(parents=True, exist_ok=True)
# Walk content directory
for content_file in config.content_dir.rglob('*'):
if content_file.is_file() and not content_file.name.startswith('___'):
# Generate output path
rel_path = content_file.relative_to(config.content_dir)
out_path = output / rel_path.with_suffix('.html')
out_path.parent.mkdir(parents=True, exist_ok=True)
# Render and save
html = render_page(
content_file,
base_path=config.content_dir,
template_path=config.templates_dir,
style_path=config.styles_dir
)
with open(out_path, 'w') as f:
f.write(html)
# Copy styles
import shutil
shutil.copytree(config.styles_dir, output / 'styles')
if __name__ == '__main__':
import sys
export_static(sys.argv[1], sys.argv[2])
```
**Usage:**
```bash
python export.py config.toml ./dist
```
### Deploy Static Files
#### GitHub Pages
```bash
# Export to docs/
python export.py config.toml ./docs
# Commit and push
git add docs/
git commit -m "Update site"
git push
# Enable Pages in GitHub repo settings
# Source: docs/ folder
```
#### Netlify
```bash
# Export
python export.py config.toml ./dist
# Install Netlify CLI
npm install -g netlify-cli
# Deploy
netlify deploy --prod --dir=dist
```
**Or use continuous deployment:**
```yaml
# netlify.toml
[build]
command = "pip install -r requirements.txt && python export.py config.toml dist"
publish = "dist"
```
#### AWS S3 + CloudFront
```bash
# Export
python export.py config.toml ./dist
# Sync to S3
aws s3 sync ./dist s3://your-bucket-name --delete
# Invalidate CloudFront cache
aws cloudfront create-invalidation --distribution-id YOUR_DIST_ID --paths "/*"
```
#### Vercel
```bash
# Export
python export.py config.toml ./dist
# Deploy
vercel --prod ./dist
```
## Performance Optimization
### Nginx Caching
Add to Nginx config:
```nginx
# Define cache path
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=foldsite_cache:10m max_size=1g inactive=60m use_temp_path=off;
server {
# ...
location / {
proxy_cache foldsite_cache;
proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://foldsite;
}
}
```
### Gzip Compression
```nginx
# In /etc/nginx/nginx.conf
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json;
```
### Image Optimization
Foldsite automatically generates thumbnails, but optimize source images:
```bash
# Install optimization tools
sudo apt install jpegoptim optipng
# Optimize JPEGs
find content/ -name "*.jpg" -exec jpegoptim --strip-all {} \;
# Optimize PNGs
find content/ -name "*.png" -exec optipng -o2 {} \;
```
### CDN Integration
Use CDN for static assets:
```nginx
# Separate static assets
location /styles/ {
alias /home/foldsite/site/styles/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location /download/ {
# Proxy to Foldsite for thumbnails
proxy_pass http://foldsite;
expires 30d;
}
```
Then point DNS for `static.yourdomain.com` to CDN origin.
## Monitoring & Logging
### Application Logs
**View logs:**
```bash
# Systemd logs
sudo journalctl -u foldsite -f
# Application logs
tail -f /var/log/foldsite/error.log
tail -f /var/log/foldsite/access.log
```
### Nginx Logs
```bash
tail -f /var/log/nginx/foldsite-access.log
tail -f /var/log/nginx/foldsite-error.log
```
### Log Rotation
Create `/etc/logrotate.d/foldsite`:
```
/var/log/foldsite/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 foldsite foldsite
sharedscripts
postrotate
systemctl reload foldsite
endscript
}
```
### Monitoring Tools
**Install basic monitoring:**
```bash
# Netdata (system monitoring)
bash <(curl -Ss https://my-netdata.io/kickstart.sh)
# Access at http://your-server:19999
```
**Check application health:**
```bash
#!/bin/bash
# health-check.sh
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/)
if [ $response -eq 200 ]; then
echo "Foldsite is healthy"
exit 0
else
echo "Foldsite is down (HTTP $response)"
systemctl restart foldsite
exit 1
fi
```
Run via cron:
```bash
*/5 * * * * /usr/local/bin/health-check.sh >> /var/log/foldsite/health.log 2>&1
```
## Backup Strategy
### Content Backup
```bash
#!/bin/bash
# backup.sh
BACKUP_DIR="/backups/foldsite"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
# Backup content, templates, styles
tar czf $BACKUP_DIR/site-$DATE.tar.gz \
/home/foldsite/site/
# Keep only last 30 days
find $BACKUP_DIR -name "site-*.tar.gz" -mtime +30 -delete
echo "Backup completed: site-$DATE.tar.gz"
```
**Run daily via cron:**
```bash
0 2 * * * /usr/local/bin/backup.sh
```
### Remote Backup
```bash
# Sync to remote server
rsync -avz /home/foldsite/site/ user@backup-server:/backups/foldsite/
# Or sync to S3
aws s3 sync /home/foldsite/site/ s3://your-backup-bucket/foldsite/
```
## Updating Foldsite
### Update Process
```bash
# As foldsite user
cd ~/foldsite
# Pull latest code
git pull
# Activate venv
source venv/bin/activate
# Update dependencies
pip install -r requirements.txt
# Restart service
sudo systemctl restart foldsite
# Check logs
sudo journalctl -u foldsite -n 50
```
### Zero-Downtime Updates
Use multiple Gunicorn workers and graceful reloading:
```bash
# Graceful reload (workers restart one by one)
sudo systemctl reload foldsite
# Or send HUP signal to Gunicorn
sudo pkill -HUP gunicorn
```
## Security Hardening
### Disable Directory Listing
Nginx automatically prevents this, but verify:
```nginx
autoindex off;
```
### Rate Limiting
Add to Nginx config:
```nginx
limit_req_zone $binary_remote_addr zone=foldsite_limit:10m rate=10r/s;
server {
location / {
limit_req zone=foldsite_limit burst=20 nodelay;
# ... rest of config
}
}
```
### Fail2ban
Protect against brute force:
```bash
sudo apt install fail2ban
# Create /etc/fail2ban/jail.local
[nginx-foldsite]
enabled = true
port = http,https
filter = nginx-foldsite
logpath = /var/log/nginx/foldsite-access.log
maxretry = 5
bantime = 3600
```
### Security Headers
Already in Nginx config, but verify:
```nginx
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' https:; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline' https:;" always;
```
### File Permissions
Ensure proper permissions:
```bash
# Content should be readable by foldsite user
chmod -R 755 ~/site/content
chmod -R 755 ~/site/templates
chmod -R 755 ~/site/styles
# Application should not be writable
chmod -R 555 ~/foldsite/src
```
## Troubleshooting
### Service Won't Start
```bash
# Check logs
sudo journalctl -u foldsite -xe
# Common issues:
# - Wrong Python path
# - Missing dependencies
# - Port already in use
# - Permission errors
```
### 502 Bad Gateway
Nginx can't reach Foldsite:
```bash
# Check if Foldsite is running
sudo systemctl status foldsite
# Check if port is listening
sudo netstat -tulpn | grep 8081
# Check Nginx error log
sudo tail /var/log/nginx/error.log
```
### High Memory Usage
```bash
# Check process memory
ps aux | grep gunicorn
# Reduce workers or add swap
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
```
### Slow Response Times
```bash
# Check Nginx access logs for slow requests
sudo tail -f /var/log/nginx/foldsite-access.log
# Enable query logging in Foldsite
# Check for expensive template helpers
# Consider caching with Redis/Memcached
```
## Platform-Specific Guides
### DigitalOcean
```bash
# Create droplet (Ubuntu 22.04)
# Follow server setup steps above
# Use DigitalOcean firewall for security
# Enable backups in control panel
```
### AWS EC2
```bash
# Launch Ubuntu instance
# Setup security groups (ports 22, 80, 443)
# Use Elastic IP for static IP
# Consider RDS for database if needed
```
### Hetzner Cloud
```bash
# Create CX11 or larger instance
# Follow server setup
# Use Hetzner firewall
# Consider Hetzner volumes for storage
```
## Next Steps
- **[Local Development](local-development.md)** - Development workflow
- **[Docker Deployment](docker.md)** - Container deployment
- **[Support](../support.md)** - Get help
Your Foldsite is now production-ready! Monitor it regularly, keep it updated, and enjoy your self-hosted corner of the internet.

View File

@ -0,0 +1,594 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Develop Foldsite"
description: "Contributing to Foldsite development"
summary: "Guidelines for contributing code, documentation, themes, and ideas to the Foldsite project."
quick_tips:
- "Start with documentation or small bug fixes"
- "Discuss major changes in issues before implementing"
- "Follow existing code style and patterns"
---
# Develop Foldsite
Want to contribute to Foldsite? We welcome contributions of all kinds!
## Ways to Contribute
### 1. Documentation
**Impact:** High | **Difficulty:** Low
Improving documentation helps everyone:
- Fix typos and unclear explanations
- Add missing examples
- Create tutorials
- Translate to other languages
**How to start:**
1. Find documentation that confused you
2. Fork the repository
3. Edit markdown files in `docs/content/`
4. Submit pull request
No coding required!
### 2. Bug Fixes
**Impact:** High | **Difficulty:** Low to Medium
Fix issues others are experiencing:
- Browse [GitHub Issues](https://github.com/DWSresearch/foldsite/issues)
- Look for "good first issue" label
- Fix the bug
- Add test if possible
- Submit pull request
### 3. New Features
**Impact:** High | **Difficulty:** Medium to High
Add new capabilities:
- Discuss in issue first
- Get feedback on approach
- Implement feature
- Add documentation
- Add tests
- Submit pull request
### 4. Templates & Themes
**Impact:** Medium | **Difficulty:** Medium
Create reusable designs:
- Build complete theme
- Document installation
- Submit to theme gallery
- Help others customize
### 5. Testing
**Impact:** Medium | **Difficulty:** Low
Help ensure quality:
- Test new releases
- Report bugs with details
- Verify fixes work
- Test on different platforms
## Development Setup
### Prerequisites
- Python 3.10 or higher
- Git
- Text editor or IDE
### Getting Started
```bash
# Clone repository
git clone https://github.com/DWSresearch/foldsite.git
cd foldsite
# Create virtual environment
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Install development dependencies
pip install -r requirements-dev.txt # If exists
# Run tests
python -m pytest # If tests exist
# Run Foldsite
python main.py --config config.toml
```
### Development Workflow
1. **Create branch** for your changes:
```bash
git checkout -b feature/my-feature
```
2. **Make changes** following code style
3. **Test changes**:
```bash
# Manual testing
python main.py --config config.toml
# Run tests if available
python -m pytest
```
4. **Commit changes**:
```bash
git add .
git commit -m "Add feature: description"
```
5. **Push and create PR**:
```bash
git push origin feature/my-feature
```
## Code Style
### Python
Follow [PEP 8](https://pep8.org/) with these specifics:
**Formatting:**
- 4 spaces for indentation
- Max line length: 100 characters
- Use type hints where helpful
- Docstrings for public functions
**Example:**
```python
def render_page(
path: Path,
base_path: Path = Path("./"),
template_path: Path = Path("./"),
) -> str:
"""
Renders a web page based on the provided path.
Args:
path: The path to the target file or directory
base_path: The base path for relative paths
template_path: Path to directory containing templates
Returns:
Rendered HTML content of the page
"""
# Implementation...
```
**Naming:**
- `snake_case` for functions and variables
- `PascalCase` for classes
- `UPPER_CASE` for constants
### HTML Templates
- 2 spaces for indentation
- Close all tags
- Use semantic HTML
- Comment complex logic
```html
<article class="post">
{% if metadata %}
<h1>{{ metadata.title }}</h1>
{# Loop through tags #}
{% if metadata.tags %}
<div class="tags">
{% for tag in metadata.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
{% endif %}
</article>
```
### CSS
- 2 spaces for indentation
- Alphabetical property order (within reason)
- Mobile-first responsive design
- BEM naming for classes (optional but encouraged)
```css
.post {
margin: 2rem 0;
padding: 1rem;
}
.post__title {
font-size: 2rem;
font-weight: bold;
margin-bottom: 1rem;
}
@media (min-width: 768px) {
.post {
margin: 3rem 0;
padding: 2rem;
}
}
```
## Architecture Overview
### Key Components
**main.py**
- Entry point
- Initializes configuration
- Registers template helpers
- Starts server
**src/config/**
- Configuration loading
- Command-line argument parsing
**src/server/**
- Flask application setup
- Route registration
- File manager (admin interface)
**src/routes/**
- URL routing logic
- Path validation and security
- Content serving
**src/rendering/**
- Template discovery
- Markdown rendering
- Page rendering pipeline
- Helper functions
### Request Flow
```
1. HTTP Request
2. Flask routes (/src/routes/routes.py)
3. Path validation & security checks
4. Template discovery (/src/rendering/template_discovery.py)
5. Content rendering (/src/rendering/renderer.py)
6. Template rendering with Jinja2
7. HTTP Response
```
### Adding a New Template Helper
Template helpers are functions available in all templates.
**1. Add to TemplateHelpers class** (`src/rendering/helpers.py`):
```python
class TemplateHelpers:
def __init__(self, config: Configuration):
self.config = config
def get_my_helper(self, param: str) -> list:
"""
Description of what this helper does.
Args:
param: Description of parameter
Returns:
Description of return value
"""
# Implementation
result = []
# ... logic ...
return result
```
**2. Register in main.py**:
```python
server.register_template_function("get_my_helper", t.get_my_helper)
```
**3. Use in templates**:
```jinja
{% for item in get_my_helper('value') %}
{{ item }}
{% endfor %}
```
**4. Document** in `docs/content/templates/template-helpers.md`
## Testing
### Manual Testing
Create test content:
```
test-site/
├── content/
│ ├── index.md
│ ├── test-post.md
│ └── photos/
│ └── test.jpg
├── templates/
│ ├── base.html
│ └── __file.md.html
└── styles/
└── base.css
```
Run and verify:
```bash
python main.py --config test-config.toml
```
### Writing Tests
*Note: Test infrastructure may need to be set up*
Example test structure:
```python
# tests/test_renderer.py
import pytest
from pathlib import Path
from src.rendering.renderer import render_page
def test_render_simple_markdown():
"""Test rendering a basic markdown file"""
# Setup
content = Path("test-content/simple.md")
templates = Path("test-templates")
# Execute
result = render_page(content, template_path=templates)
# Assert
assert "<h1>" in result
assert "<!DOCTYPE html>" in result
```
## Pull Request Guidelines
### Before Submitting
- [ ] Code follows style guidelines
- [ ] Changes are focused (one feature/fix per PR)
- [ ] Commit messages are clear
- [ ] Documentation updated if needed
- [ ] Manual testing completed
- [ ] No merge conflicts
### PR Description Template
```markdown
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Documentation
- [ ] Refactoring
- [ ] Other (describe)
## Testing
How you tested the changes
## Screenshots
If UI changes, add screenshots
## Checklist
- [ ] Code follows project style
- [ ] Self-reviewed code
- [ ] Commented complex code
- [ ] Updated documentation
- [ ] No breaking changes (or documented)
```
### Review Process
1. **Maintainer reviews** code and design
2. **Feedback provided** if changes needed
3. **Discussion** on approach if necessary
4. **Approval** when ready
5. **Merge** by maintainer
Be patient - maintainers are volunteers.
## Issue Guidelines
### Bug Reports
Use this template:
```markdown
**Foldsite Version:** X.X.X
**Python Version:** X.X.X
**OS:** macOS/Linux/Windows
**Description:**
Clear description of the bug
**Steps to Reproduce:**
1. Step one
2. Step two
3. See error
**Expected Behavior:**
What should happen
**Actual Behavior:**
What actually happens
**Error Messages:**
```
Paste full error/traceback
```
**Additional Context:**
Any other relevant information
```
### Feature Requests
Use this template:
```markdown
**Feature Description:**
Clear description of the feature
**Use Case:**
Why you need this feature
**Proposed Implementation:**
Ideas for how it could work (optional)
**Alternatives Considered:**
Other solutions you've thought about
**Additional Context:**
Examples, mockups, etc.
```
## Communication
### GitHub Issues
- **Bugs** - Report problems
- **Features** - Request improvements
- **Questions** - Ask for help (or use Discussions)
### GitHub Discussions
- **Q&A** - Get help using Foldsite
- **Ideas** - Discuss potential features
- **Show and Tell** - Share what you built
- **General** - Everything else
### Response Times
- **Critical bugs** - Few days
- **Other issues** - 1-2 weeks
- **Feature requests** - Varies
- **PRs** - 1-2 weeks
## Security
### Reporting Vulnerabilities
**Do not** open public issues for security problems.
Email: security@dws.rip
Include:
- Description of vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if you have one)
### Security Considerations
When contributing:
- Validate all file paths
- Sanitize user inputs
- Be careful with path traversal
- Don't expose sensitive info in errors
- Follow principle of least privilege
## Project Philosophy
Keep these in mind when contributing:
### 1. Simplicity
Prefer simple solutions over complex ones. Add complexity only when necessary.
### 2. Convention Over Configuration
Sensible defaults that work for most cases. Configuration for edge cases.
### 3. User Focus
Optimize for content creators, not developers. Make common tasks easy.
### 4. Clear Over Clever
Code should be understandable. Clever tricks make maintenance harder.
### 5. Backward Compatibility
Don't break existing sites without very good reason and clear migration path.
## Resources
### Learning
- **Python** - [docs.python.org](https://docs.python.org/)
- **Flask** - [flask.palletsprojects.com](https://flask.palletsprojects.com/)
- **Jinja2** - [jinja.palletsprojects.com](https://jinja.palletsprojects.com/)
### Tools
- **VS Code** - Great Python support with extensions
- **PyCharm** - Full-featured Python IDE
- **Git** - Version control
- **GitHub CLI** - Command-line GitHub interface
## Recognition
Contributors are recognized:
- **README** - Listed in contributors section
- **Release notes** - Mentioned in changelogs
- **Commits** - Your name in git history
- **Gratitude** - Appreciation from the community!
## Getting Help
Stuck on contribution?
- **Read existing code** - Learn from examples
- **Ask in Discussions** - Community can help
- **Open draft PR** - Get early feedback
- **Reach out** - DWS team is friendly!
## Next Steps
- **Browse issues** - Find something to work on
- **Read the code** - Understand how it works
- **Make a change** - Start small
- **Submit PR** - Share your contribution!
Thank you for helping make Foldsite better!
**Remember:** Every contribution matters, from fixing a typo to adding major features. We appreciate all help!

View File

@ -0,0 +1,441 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Directory Structure Guide"
description: "Understanding how to organize your Foldsite project"
summary: "Learn how Foldsite's directory structure works - where to put content, templates, and styles for maximum flexibility and power."
quick_tips:
- "content/ is where all your pages and media live"
- "templates/ cascade down through subdirectories for hierarchical theming"
- "Hidden files (starting with ___) are ignored by Foldsite"
---
# Directory Structure Guide
Understanding Foldsite's directory structure is essential to using it effectively. The beauty of Foldsite is that **your folder structure IS your site structure**.
## The Three Core Directories
Every Foldsite project has three main directories:
```
my-site/
├── content/ # Your pages, posts, images, and files
├── templates/ # HTML templates using Jinja2
└── styles/ # CSS stylesheets
```
### content/ - Your Website Content
This is where everything your visitors see lives. Every file and folder in here becomes part of your website.
**Example structure:**
```
content/
├── index.md # Homepage (/)
├── about.md # About page (/about.md)
├── contact.md # Contact page (/contact.md)
├── blog/ # Blog section (/blog)
│ ├── 2024-01-15-first-post.md
│ ├── 2024-02-20-second-post.md
│ └── archives/
│ └── 2023-recap.md
├── photos/ # Photo galleries
│ ├── vacation/
│ │ ├── IMG_001.jpg
│ │ ├── IMG_002.jpg
│ │ └── IMG_003.jpg
│ └── family/
│ ├── portrait.jpg
│ └── gathering.jpg
└── downloads/ # Files for download
├── resume.pdf
└── presentation.pptx
```
**How URLs work:**
- `content/index.md``http://yoursite.com/`
- `content/about.md``http://yoursite.com/about.md`
- `content/blog/first-post.md``http://yoursite.com/blog/first-post.md`
- `content/photos/vacation/``http://yoursite.com/photos/vacation`
### templates/ - HTML Templates
Templates define how your content is displayed. They mirror your content structure and cascade down through subdirectories.
**Example structure:**
```
templates/
├── base.html # Required: Main page wrapper
├── __error.html # Optional: Custom error page
├── __file.md.html # Template for all markdown files
├── __folder.md.html # Template for folders with markdown
├── __folder.image.html # Template for image galleries
├── blog/
│ └── __file.md.html # Custom template for blog posts
└── photos/
└── __folder.image.html # Custom gallery template for photos
```
**Key template files:**
- `base.html` - **REQUIRED** - Wraps all pages (header, navigation, footer)
- `__file.{category}.html` - Templates for individual files
- `__folder.{category}.html` - Templates for folder views
- `__error.html` - Custom error pages (404, etc.)
### styles/ - CSS Stylesheets
Stylesheets follow the same cascading logic as templates.
**Example structure:**
```
styles/
├── base.css # Required: Base styles
├── __file.md.css # Styles for markdown files
├── __folder.image.css # Styles for image galleries
├── blog/
│ └── __file.md.css # Blog-specific styles
└── layouts/
├── document.css # Layout styles
└── gallery.css # Gallery layout styles
```
## File Naming Conventions
### Content Files
- **Regular names**: `about.md`, `contact.md`, `my-post.md`
- **Hidden files**: Prefix with `___` to hide from navigation
- `___draft-post.md` - Won't appear in listings
- `___notes.md` - Private notes not rendered
### Template Files
Templates use a special naming pattern:
- `__file.{extension}.html` - For individual files
- `__file.md.html` - All markdown files
- `__file.jpg.html` - Individual images (rare)
- `__folder.{category}.html` - For folders
- `__folder.md.html` - Folders containing mostly markdown
- `__folder.image.html` - Photo gallery folders
- `{specific-name}.html` - For specific pages
- `index.html` - Only for index.md
- `about.html` - Only for about.md
### Style Files
Styles follow the same pattern as templates:
- `base.css` - Always loaded
- `__file.md.css` - Loaded for markdown files
- `__folder.image.css` - Loaded for image galleries
- `{specific-path}.css` - Loaded for specific pages
## How File Categories Work
Foldsite automatically categorizes files by extension:
| Category | Extensions | Template Pattern | Use Case |
|----------|-----------|------------------|----------|
| **document** | `.md`, `.txt`, `.html` | `__file.document.html` | Articles, pages |
| **image** | `.jpg`, `.png`, `.gif`, `.svg` | `__file.image.html` | Photos |
| **multimedia** | `.mp4`, `.mp3`, `.webm` | `__file.multimedia.html` | Videos, audio |
| **other** | Everything else | `__file.other.html` | PDFs, downloads |
Folders are categorized by their **most common file type**:
```
photos/ # Mostly images → "image" category
IMG_001.jpg
IMG_002.jpg
notes.txt # Ignored for categorization
blog/ # Mostly markdown → "document" category
post-1.md
post-2.md
header.jpg # Ignored for categorization
```
## Template and Style Discovery
Foldsite uses a **hierarchical discovery system**. When rendering a page, it searches for templates from most specific to most general.
### Example: Rendering `content/blog/my-post.md`
**Template search order:**
1. `templates/blog/my-post.html` - Specific file template
2. `templates/blog/__file.md.html` - Blog folder markdown template
3. `templates/blog/__file.document.html` - Blog folder document template
4. `templates/__file.md.html` - Root markdown template
5. `templates/__file.document.html` - Root document template
6. `templates/__file.html` - Generic file template
**First match wins.**
### Example: Rendering `content/photos/vacation/` folder
**Template search order:**
1. `templates/photos/vacation/__folder.html` - Specific folder template
2. `templates/photos/__folder.image.html` - Photo folder image template
3. `templates/__folder.image.html` - Root image folder template
4. `templates/__folder.html` - Generic folder template
**Style discovery works the same way**, building a list of all matching styles from most specific to most general.
## Configuration File
The `config.toml` file tells Foldsite where your directories are:
```toml
[paths]
content_dir = "/path/to/content"
templates_dir = "/path/to/templates"
styles_dir = "/path/to/styles"
[server]
listen_address = "0.0.0.0"
listen_port = 8081
admin_browser = false
admin_password = "change-me"
max_threads = 4
debug = false
access_log = true
```
### Key Configuration Options
**paths:**
- `content_dir` - Absolute path to content folder
- `templates_dir` - Absolute path to templates folder
- `styles_dir` - Absolute path to styles folder
**server:**
- `listen_address` - IP to bind to (`0.0.0.0` for all interfaces)
- `listen_port` - Port number (default 8081)
- `admin_browser` - Enable file manager UI (true/false)
- `admin_password` - Password for file manager (if enabled)
- `max_threads` - Number of worker threads
- `debug` - Enable debug mode (shows template discovery)
- `access_log` - Log HTTP requests
## Special Files and Folders
### Hidden Content (`___` prefix)
Files and folders starting with three underscores are ignored:
```
content/
├── public-post.md # ✓ Visible
├── ___draft-post.md # ✗ Hidden
├── blog/ # ✓ Visible
│ └── article.md
└── ___notes/ # ✗ Hidden (entire folder)
└── ideas.md
```
Use this for:
- Draft posts
- Private notes
- Work-in-progress content
- Template testing
### Index Files
`index.md` files serve as folder landing pages:
```
content/
├── index.md # Homepage: /
└── blog/
├── index.md # Blog index: /blog or /blog/
├── post-1.md # Post: /blog/post-1.md
└── post-2.md # Post: /blog/post-2.md
```
Without an `index.md`, folders display using the `__folder.*` template.
## Practical Examples
### Minimal Setup
Simplest possible Foldsite:
```
my-site/
├── content/
│ └── index.md
├── templates/
│ ├── base.html
│ └── __file.md.html
└── styles/
└── base.css
```
### Blog Site
Typical blog structure:
```
blog-site/
├── content/
│ ├── index.md # Homepage
│ ├── about.md # About page
│ └── posts/
│ ├── 2024-01-post.md
│ ├── 2024-02-post.md
│ └── ___draft.md # Hidden draft
├── templates/
│ ├── base.html
│ ├── __file.md.html # Default post template
│ ├── __folder.md.html # Post listing
│ └── index.html # Custom homepage
└── styles/
├── base.css
├── __file.md.css
└── __folder.md.css
```
### Photo Portfolio
Photography site structure:
```
portfolio/
├── content/
│ ├── index.md
│ └── galleries/
│ ├── landscape/
│ │ ├── IMG_001.jpg
│ │ └── IMG_002.jpg
│ ├── portrait/
│ │ └── photos...
│ └── urban/
│ └── photos...
├── templates/
│ ├── base.html
│ ├── __folder.image.html # Gallery template
│ └── galleries/
│ └── __folder.image.html # Custom for galleries
└── styles/
├── base.css
└── __folder.image.css
```
### Documentation Site
Hierarchical documentation:
```
docs-site/
├── content/
│ ├── index.md
│ ├── getting-started/
│ │ ├── installation.md
│ │ ├── configuration.md
│ │ └── first-steps.md
│ ├── guides/
│ │ ├── basics.md
│ │ └── advanced.md
│ └── api/
│ ├── overview.md
│ └── reference.md
├── templates/
│ ├── base.html
│ ├── __file.md.html
│ └── __folder.md.html
└── styles/
├── base.css
└── layouts/
└── document.css
```
## Best Practices
### 1. Organize Content Logically
Your folder structure should make sense to humans:
```
✓ Good:
content/
├── blog/
├── projects/
└── about.md
✗ Confusing:
content/
├── stuff/
├── things/
└── misc/
```
### 2. Use Descriptive Names
File and folder names become URLs and navigation labels:
```
✓ Good:
getting-started.md
python-tutorial.md
2024-year-review.md
✗ Poor:
page1.md
doc.md
tmp.md
```
### 3. Keep Templates DRY
Don't duplicate templates. Use inheritance and the cascade:
```
✓ Good:
templates/
├── base.html # Shared structure
├── __file.md.html # All markdown files
└── blog/
└── __file.md.html # Only blog-specific changes
✗ Redundant:
templates/
├── base.html
├── about.html # Unnecessary if using __file.md.html
├── contact.html # Unnecessary if using __file.md.html
└── ...
```
### 4. Use Hidden Files for Work in Progress
Keep drafts and private notes out of production:
```
content/
├── published-post.md
├── ___draft-post.md # Hidden
└── ___notes/ # Hidden folder
└── ideas.md
```
## Next Steps
Now that you understand the directory structure:
- [Learn about Templates](templates/) - Master the template system
- [Explore Styles](styles/) - Understand CSS cascading
- [See Recipes](recipes/) - Ready-to-use examples
- [Deploy Your Site](deployment/) - Get it online
The directory structure is the foundation of Foldsite. Master it, and everything else becomes intuitive.

356
docs/content/explore.md Normal file
View File

@ -0,0 +1,356 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Explore Foldsites"
description: "Real-world Foldsite examples and inspiration"
summary: "Discover websites built with Foldsite - from personal blogs to photo portfolios and documentation sites."
quick_tips:
- "Real sites provide the best learning examples"
- "View source to see how they're built"
- "Steal ideas (with attribution) and make them your own"
---
# Explore Foldsites
See what people are building with Foldsite. These real-world examples demonstrate different approaches, designs, and use cases.
## Featured Sites
### Personal Sites & Blogs
#### Tanishq Dubey's Site
**URL:** [https://tanishq.page](https://tanishq.page)
**Type:** Personal blog + photo gallery
**Highlights:**
- Clean, minimal design
- Mix of blog posts and photo galleries
- Breadcrumb navigation
- Responsive layout
**What you can learn:**
- How to combine blog and gallery in one site
- Sidebar navigation pattern
- Photo organization by folders
---
### Documentation Sites
#### Foldsite Documentation
**URL:** (This site!)
**Type:** Product documentation
**Highlights:**
- Hierarchical content organization
- Sibling page navigation
- Code examples with syntax highlighting
- Comprehensive frontmatter usage
**What you can learn:**
- Documentation site structure
- Template organization
- Content hierarchy best practices
---
## Community Showcase
*This section is community-curated. Add your site below!*
### How to Add Your Site
Built something with Foldsite? Share it with the community!
**To add your site:**
1. Fork the Foldsite repository
2. Edit `docs/content/explore.md`
3. Add your site using this template:
```markdown
#### Your Site Name
**URL:** https://your-site.com
**Type:** Blog / Portfolio / Gallery / Docs / Other
**Highlights:**
- Key feature 1
- Key feature 2
- Key feature 3
**What makes it special:**
Brief description of unique aspects or interesting implementation details.
```
4. Submit a pull request
**Guidelines:**
- Your site must be publicly accessible
- Must be built with Foldsite
- Keep description concise
- No commercial promotion (personal/hobby sites only)
- Family-friendly content
---
## Inspiration Gallery
### Blog Designs
**Minimalist Blog**
- Clean typography
- Lots of whitespace
- Focus on content
- Tag-based navigation
**Magazine Style**
- Grid layout
- Featured images
- Multi-column
- Category sections
**Tech Blog**
- Syntax highlighting
- Code-focused
- Dark mode
- Technical diagrams
### Photo Galleries
**Travel Photography**
- Location-based organization
- EXIF data display
- Lightbox viewing
- Map integration (possible)
**Portfolio Site**
- Project-based galleries
- About/contact pages
- Custom landing page
- Client testimonials
**Photo Blog**
- Mix of photos and text
- Chronological posts
- Photo series
- Behind-the-scenes content
### Documentation Styles
**API Reference**
- Code examples
- Parameter tables
- Return value documentation
- Search functionality
**User Guide**
- Step-by-step tutorials
- Screenshots/diagrams
- Prerequisites sections
- Troubleshooting guides
**Knowledge Base**
- FAQ style
- Search by category
- Related articles
- Quick answers
## Design Patterns
Common patterns seen across successful Foldsites:
### Navigation
**Sidebar Navigation** (Like example_site)
```
┌─────────────┬──────────────────────────┐
│ │ │
│ Sidebar │ Main Content │
│ Nav │ │
│ │ │
│ - Home │ Page content here... │
│ - About │ │
│ - Blog │ │
│ - Photos │ │
│ │ │
└─────────────┴──────────────────────────┘
```
**Top Navigation**
```
┌────────────────────────────────────────┐
│ Logo Home About Blog Contact │
├────────────────────────────────────────┤
│ │
│ Main Content │
│ │
└────────────────────────────────────────┘
```
**Breadcrumb Navigation**
```
Home / Blog / 2024 / My Post
────────────────────────────────────────
Post Content
````
### Layouts
**Single Column**
- Best for reading
- Focus on content
- Mobile-friendly by default
**Two Column**
- Content + sidebar
- Related posts/navigation
- Additional information
**Grid**
- Photo galleries
- Post previews
- Portfolio items
## Learning from Examples
### View Source
All Foldsites are just HTML, CSS, and markdown. View source to learn:
1. **Right-click → View Page Source** - See rendered HTML
2. **Check `/styles/` URLs** - View CSS files
3. **Look at URL structure** - Understand content organization
### Clone and Experiment
For open source Foldsites:
```bash
# Clone repository
git clone https://github.com/user/their-site.git
# Copy their templates
cp -r their-site/templates my-site/templates
# Customize and make it your own
```
### Adaptation Guidelines
**Do:**
- Study patterns and techniques
- Adapt ideas to your needs
- Give credit for inspiration
- Make it your own
**Don't:**
- Copy entire sites wholesale
- Use someone else's content
- Steal unique designs exactly
- Claim others' work as yours
## Themes & Templates
### Official Examples
**example_site/** (in repository)
- Typical blog/gallery site
- Shows common patterns
- Breadcrumb navigation
- Sidebar layout
### Community Themes
*Coming soon! Community-contributed themes will be listed here.*
Want to contribute a theme? See [Theme Gallery](theme-gallery.md).
## Case Studies
### From Idea to Site
**Case Study 1: Personal Blog**
**Goal:** Simple blog to share thoughts
**Time:** 2 hours
**Approach:**
1. Started with `example_site` templates
2. Customized colors and fonts
3. Added personal branding
4. Deployed to GitHub Pages
**Lessons learned:**
- Start simple, iterate
- Templates cascade saves time
- Custom homepage makes it special
---
**Case Study 2: Photography Portfolio**
**Goal:** Showcase travel photography
**Time:** 4 hours
**Approach:**
1. Organized photos by location/trip
2. Used `__folder.image.html` template
3. Added EXIF data display
4. Integrated lightbox library
**Lessons learned:**
- Folder structure = site structure
- EXIF data adds context
- Thumbnail generation is automatic
---
**Case Study 3: Project Documentation**
**Goal:** Document open source project
**Time:** 6 hours
**Approach:**
1. Mirrored code structure in content
2. Created hierarchical docs
3. Added code examples
4. Set up sibling navigation
**Lessons learned:**
- Mirror mental model in folders
- Breadcrumbs essential for deep hierarchies
- Code blocks need good syntax highlighting
## Tips from the Community
> "Start with one template and iterate. Don't try to build everything at once."
> — Early Foldsite user
> "The folder structure IS the site structure. Once I understood that, everything clicked."
> — Documentation writer
> "Use hidden files (`___`) for drafts. Game changer for my workflow."
> — Blogger
> "Template helpers like `get_recent_posts()` saved me so much time. No database needed!"
> — Former WordPress user
## Your Site Here
Built something with Foldsite? We'd love to feature it!
**Submission criteria:**
- Publicly accessible
- Built with Foldsite
- Demonstrates interesting pattern or design
- Well-executed (doesn't have to be perfect!)
**How to submit:**
1. Open issue on GitHub with "Showcase" label
2. Include URL, description, and what makes it special
3. We'll review and add to this page
## Explore More
- **[Recipes](recipes/)** - Template code you can copy
- **[Theme Gallery](theme-gallery.md)** - Downloadable themes
- **[Templates Guide](templates/)** - Learn the system
- **[Support](support.md)** - Get help building your site
**Ready to build?** [Get Started](index.md#quick-start)
*Remember: Every great Foldsite started as an empty folder. Your site could be featured here next!*

View File

@ -1,3 +1,165 @@
# Foldsite Documentation
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Foldsite Documentation"
description: "Turn your folders into beautiful websites with zero configuration"
summary: "Welcome to Foldsite - a modern static/dynamic site generator that transforms your file structure into a navigable website. Focus on content, not configuration."
quick_tips:
- "Your folder structure IS your site structure - no complex routing needed"
- "Templates cascade automatically - create them where you need them"
- "Start with just markdown files - add templates and styles later"
---
Foldsite acts as a dynamic site generator. It takes content primarily from Markdown files, combines it with HTML templates, applies CSS styles, and serves the resulting pages. It supports features like image thumbnails, Markdown rendering with frontmatter, and a built-in file manager for administrative tasks. Foldsite is dynamic in the sense that content is rendered on demand, rather than generating static HTML up-front.
# Welcome to Foldsite
**Foldsite** is a static/dynamic site generator that lets you focus on what matters: **your content**. Drop markdown files into folders, add templates for customization, and Foldsite handles the rest.
> *"It's your Internet. Take it back."*
> — [DWS (Dubey Web Services)](https://dws.rip)
## What Makes Foldsite Different?
### Folders → Site Structure
Your directory layout becomes your website structure automatically. No routing configuration, no complex build steps. Create a folder, drop in a markdown file, and it's live.
```
content/
├── about.md → /about
├── blog/
│ ├── post-1.md → /blog/post-1.md
│ └── post-2.md → /blog/post-2.md
└── photos/
└── vacation/ → /photos/vacation
```
### Template System That Makes Sense
Templates cascade through your directory tree. Create specific templates for individual files, or general templates that apply to entire sections:
- `__file.md.html` - Template for all markdown files
- `__folder.md.html` - Template for folders containing markdown
- `__folder.image.html` - Template for photo galleries
- Custom templates for specific pages
### Powerful Helper Functions
Access content dynamically from your templates using built-in Jinja2 helpers:
```jinja
{# List recent blog posts #}
{% for post in get_recent_posts(limit=5, folder='blog') %}
<a href="/{{ post.path }}">{{ post.title }}</a>
{% endfor %}
{# Show navigation breadcrumbs #}
{% for crumb in generate_breadcrumbs(currentPath) %}
<a href="{{ crumb.url }}">{{ crumb.title }}</a>
{% endfor %}
```
## Quick Start
### 1. Install and Run
```bash
# Clone the repository
git clone https://github.com/DWSresearch/foldsite
cd foldsite
# Install dependencies
pip install -r requirements.txt
# Run the development server
python main.py --config config.toml
```
Visit `http://localhost:8081` to see your site!
### 2. Create Your First Page
```bash
# Create a content directory
mkdir -p my-site/content
# Write your first page
echo "# Hello World" > my-site/content/index.md
```
### 3. Customize with Templates
```bash
# Create a basic template structure
mkdir -p my-site/templates my-site/styles
# Add a base template (see Templates section for examples)
```
## Common Use Cases
### Personal Blog
Perfect for sharing your thoughts with automatic post discovery, tagging, and chronological sorting.
### Photo Gallery
Built-in image handling with EXIF data extraction, thumbnail generation, and gallery views.
### Documentation Site
Hierarchical content organization with automatic navigation and sibling page discovery.
### Portfolio Site
Showcase projects with flexible templates that adapt to your content type.
## Documentation Sections
### [About Foldsite](about.md)
Learn about the philosophy behind Foldsite and why it was created.
### [Directory Structure](directory-structure.md)
Understanding how to organize your content, templates, and styles.
### [Deployment](deployment/)
Get your Foldsite running locally, in Docker, or production environments.
### [Templates](templates/)
Master the template system - from basics to advanced hierarchical templates.
### [Styles](styles/)
Learn how CSS cascades through your site structure.
### [Template Recipes](recipes/)
Ready-to-use examples for blogs, galleries, documentation sites, and more.
### [Theme Gallery](theme-gallery.md)
Explore community-created themes and templates.
### [Explore Foldsites](explore.md)
See real-world examples of Foldsite in action.
### [Develop Foldsite](develop/)
Contributing to Foldsite development.
### [Support](support.md)
Get help and connect with the community.
## Why Foldsite Exists
Foldsite is part of the **DWS (Dubey Web Services)** mission to help people reclaim their corner of the internet. In an era of complex CMSs and heavy frameworks, Foldsite brings simplicity back:
- **Own your content** - Just files and folders on your filesystem
- **Control your presentation** - Templates and styles that make sense
- **Host anywhere** - Static files or dynamic Python server
- **Zero lock-in** - Your markdown works everywhere
## Philosophy
1. **Content is king** - Your folders and files are the source of truth
2. **Convention over configuration** - Sensible defaults, customize when needed
3. **Progressive enhancement** - Start simple, add complexity only where needed
4. **Developer friendly** - Clear APIs, helpful error messages, debug tools
## Next Steps
- **New users**: Start with [Directory Structure](directory-structure.md) to understand the basics
- **Building a blog**: Jump to [Blog Site Recipe](recipes/blog-site.md)
- **Creating themes**: Read the [Templates Guide](templates/)
- **Deploying**: Check [Deployment Options](deployment/)
Ready to turn your folders into a website? Let's get started!

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!

View File

@ -0,0 +1,770 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Styles Guide"
description: "Understanding Foldsite's CSS cascade system"
summary: "Learn how CSS styles cascade through your Foldsite project - from base styles to page-specific customizations."
quick_tips:
- "base.css is required and loaded on every page"
- "All matching styles are loaded (unlike templates where first match wins)"
- "Styles cascade down through directory structure like templates"
---
# Styles Guide
Foldsite's style system follows the same hierarchical logic as templates, allowing you to organize CSS in a maintainable, scalable way.
## The Style System
### Key Differences from Templates
| Templates | Styles |
|-----------|--------|
| **First match wins** | **All matches load** |
| One template per page | Multiple stylesheets per page |
| Must have at least one | base.css required |
**Why the difference?**
CSS is designed to cascade and layer. Loading multiple stylesheets allows you to:
- Share base styles
- Add section-specific styles
- Override with page-specific styles
## Required Style: base.css
Every Foldsite project **must have** `styles/base.css`.
This file is **loaded on every page**, providing:
- Typography
- Layout basics
- Color scheme
- Resets/normalizes
### Minimal base.css
```css
/* styles/base.css */
* {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.5em;
margin-bottom: 0.5em;
line-height: 1.2;
}
a {
color: #0066cc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
img {
max-width: 100%;
height: auto;
}
```
## Style Discovery
Styles follow a **hierarchical discovery pattern** similar to templates.
### For File: `blog/my-post.md`
**All matching styles load (in order):**
1. **Base style** (always)
```
/styles/base.css
```
2. **Type + extension** styles (from root to specific)
```
/styles/__file.md.css
/styles/blog/__file.md.css
```
3. **Type + category** styles
```
/styles/__file.document.css
/styles/blog/__file.document.css
```
4. **Specific file** style
```
/styles/blog/my-post.md.css
```
**All found styles are included!**
### Rendered HTML
```html
<link rel="stylesheet" href="/styles/base.css">
<link rel="stylesheet" href="/styles/__file.md.css">
<link rel="stylesheet" href="/styles/blog/__file.md.css">
```
## Style Naming Patterns
### File Styles
Pattern: `__file.{extension}.css` or `__file.{category}.css`
```
styles/
├── __file.md.css # All markdown files
├── __file.document.css # All document files
├── __file.image.css # Individual images (rare)
└── __file.other.css # Other file types
```
### Folder Styles
Pattern: `__folder.{category}.css`
```
styles/
├── __folder.md.css # Folders with markdown
├── __folder.image.css # Photo galleries
└── __folder.html # Any folder view
```
### Specific Page Styles
Pattern: `{path/to/file}.css`
```
styles/
├── index.md.css # Only homepage
├── about.md.css # Only about page
└── blog/
└── special-post.md.css # One specific post
```
## Directory Structure
### Basic Structure
```
styles/
├── base.css # Required: Base styles
├── __file.md.css # Markdown file styles
└── __folder.image.css # Gallery styles
```
### Advanced Structure
```
styles/
├── base.css # Base
├── __file.md.css # General markdown
├── __folder.image.css # General galleries
├── blog/
│ ├── __file.md.css # Blog posts
│ └── __folder.md.css # Blog index
├── docs/
│ └── __file.md.css # Documentation
├── layouts/
│ ├── document.css # Document layout
│ ├── gallery.css # Gallery layout
│ └── landing.css # Landing pages
└── components/
├── navigation.css # Navigation
├── footer.css # Footer
└── breadcrumbs.css # Breadcrumbs
```
## Cascade & Specificity
### CSS Cascade Order
**Load order matters:**
1. `base.css` - Loaded first
2. General styles (`__file.md.css`)
3. Section styles (`blog/__file.md.css`)
4. Specific styles (`blog/my-post.md.css`)
**Later styles override earlier ones** (standard CSS behavior).
### Example Cascade
**Given page:** `blog/tutorial.md`
**Loaded styles:**
```html
<link rel="stylesheet" href="/styles/base.css">
<link rel="stylesheet" href="/styles/__file.md.css">
<link rel="stylesheet" href="/styles/blog/__file.md.css">
```
**base.css:**
```css
h1 {
color: black; /* Default */
}
```
**__file.md.css:**
```css
h1 {
color: #333; /* Slightly lighter */
}
```
**blog/__file.md.css:**
```css
h1 {
color: #0066cc; /* Blue - wins! */
}
```
**Result:** Blog headings are blue.
## Common Patterns
### Pattern 1: Base + Overrides
Start with comprehensive base, override as needed:
**base.css** - Everything
```css
/* Typography */
body { font-family: sans-serif; }
h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
/* Layout */
.container { max-width: 1200px; }
/* Components */
nav { /* navigation styles */ }
footer { /* footer styles */ }
```
**blog/__file.md.css** - Blog-specific
```css
/* Override heading colors for blog */
h1 { color: #0066cc; }
/* Add blog-specific components */
.post-meta { /* metadata styles */ }
```
### Pattern 2: Modular Components
Split styles into reusable modules:
**base.css** - Minimal
```css
@import url('components/typography.css');
@import url('components/layout.css');
@import url('components/navigation.css');
```
**components/typography.css**
```css
body {
font-family: Georgia, serif;
line-height: 1.6;
}
h1 { font-size: 2.5rem; }
/* ... */
```
**components/navigation.css**
```css
nav {
display: flex;
justify-content: space-between;
}
/* ... */
```
### Pattern 3: Layout Variants
Different layouts for different sections:
**layouts/document.css**
```css
.document-layout {
display: grid;
grid-template-columns: 250px 1fr;
gap: 2rem;
}
.docs-sidebar { /* sidebar styles */ }
.docs-content { /* content styles */ }
```
**layouts/gallery.css**
```css
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
.photo-item { /* photo card styles */ }
```
**Include in templates:**
```html
<!-- __file.md.html for docs -->
<link rel="stylesheet" href="/styles/layouts/document.css">
<div class="document-layout">
<!-- content -->
</div>
```
### Pattern 4: Responsive Design
Mobile-first approach:
```css
/* base.css - Mobile first */
body {
padding: 1rem;
}
.container {
display: block;
}
/* Tablet */
@media (min-width: 768px) {
body {
padding: 2rem;
}
.container {
display: grid;
grid-template-columns: 1fr 300px;
}
}
/* Desktop */
@media (min-width: 1200px) {
body {
padding: 3rem;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
}
```
## Practical Examples
### Example 1: Blog Site
```
styles/
├── base.css # Site-wide styles
├── __file.md.css # General markdown
├── __folder.md.css # Folder listings
└── blog/
├── __file.md.css # Blog posts
└── __folder.md.css # Blog index
```
**base.css:**
```css
/* Basic layout and typography */
body {
font-family: Georgia, serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
a { color: #0066cc; }
```
**__file.md.css:**
```css
/* Default markdown styles */
article {
margin: 2rem 0;
}
code {
background: #f4f4f4;
padding: 0.2em 0.4em;
border-radius: 3px;
}
pre {
background: #f4f4f4;
padding: 1rem;
overflow-x: auto;
}
```
**blog/__file.md.css:**
```css
/* Blog post specific */
.post-header {
border-bottom: 2px solid #eee;
padding-bottom: 1rem;
margin-bottom: 2rem;
}
.post-meta {
color: #666;
font-size: 0.9rem;
}
.post-tags {
margin-top: 2rem;
}
.tag {
display: inline-block;
padding: 0.25rem 0.75rem;
background: #f0f0f0;
border-radius: 3px;
margin-right: 0.5rem;
font-size: 0.85rem;
}
```
**blog/__folder.md.css:**
```css
/* Blog index */
.post-list {
list-style: none;
padding: 0;
}
.post-item {
margin: 2rem 0;
padding: 1.5rem;
border: 1px solid #eee;
border-radius: 4px;
}
.post-item:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
```
### Example 2: Photo Gallery
```
styles/
├── base.css
└── galleries/
└── __folder.image.css
```
**galleries/__folder.image.css:**
```css
.photo-gallery {
padding: 2rem;
}
.photo-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.photo-item {
position: relative;
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-item img {
width: 100%;
height: auto;
display: block;
}
.photo-caption {
padding: 0.75rem;
background: white;
font-size: 0.85rem;
color: #666;
}
/* Responsive */
@media (max-width: 768px) {
.photo-grid {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
}
```
### Example 3: Documentation Site
```
styles/
├── base.css
├── layouts/
│ └── document.css
└── docs/
└── __file.md.css
```
**layouts/document.css:**
```css
.docs-layout {
display: grid;
grid-template-columns: 250px 1fr;
gap: 3rem;
max-width: 1400px;
margin: 0 auto;
}
.docs-sidebar {
position: sticky;
top: 2rem;
height: calc(100vh - 4rem);
overflow-y: auto;
}
.docs-content {
min-width: 0; /* Prevent grid blowout */
}
@media (max-width: 1024px) {
.docs-layout {
grid-template-columns: 1fr;
}
.docs-sidebar {
position: static;
height: auto;
}
}
```
**docs/__file.md.css:**
```css
.documentation {
max-width: 800px;
}
/* Table of contents */
.doc-toc {
background: #f8f8f8;
padding: 1rem;
border-radius: 4px;
margin: 2rem 0;
}
/* Code blocks */
.documentation pre {
background: #282c34;
color: #abb2bf;
padding: 1.5rem;
border-radius: 6px;
overflow-x: auto;
}
.documentation code {
font-family: 'Monaco', 'Courier New', monospace;
}
/* Callouts */
.note,
.warning,
.tip {
padding: 1rem 1.5rem;
margin: 1.5rem 0;
border-left: 4px solid;
border-radius: 4px;
}
.note {
background: #e3f2fd;
border-color: #2196f3;
}
.warning {
background: #fff3e0;
border-color: #ff9800;
}
.tip {
background: #e8f5e9;
border-color: #4caf50;
}
```
## CSS Variables
Use CSS custom properties for theming:
### base.css with Variables
```css
:root {
/* Colors */
--color-primary: #0066cc;
--color-secondary: #6c757d;
--color-text: #333;
--color-background: #fff;
--color-border: #dee2e6;
/* Typography */
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
--font-serif: Georgia, serif;
--font-mono: 'Monaco', 'Courier New', monospace;
/* Spacing */
--space-xs: 0.5rem;
--space-sm: 1rem;
--space-md: 2rem;
--space-lg: 3rem;
/* Breakpoints (for reference) */
/* Use in @media queries */
}
body {
color: var(--color-text);
background: var(--color-background);
font-family: var(--font-sans);
}
a {
color: var(--color-primary);
}
/* ... */
```
### Dark Mode
```css
/* base.css */
@media (prefers-color-scheme: dark) {
:root {
--color-text: #e4e4e4;
--color-background: #1a1a1a;
--color-border: #333;
}
}
/* Or toggle with class */
body.dark-mode {
--color-text: #e4e4e4;
--color-background: #1a1a1a;
--color-border: #333;
}
```
## Performance Tips
### 1. Minimize File Size
```css
/* Remove unnecessary spaces/newlines in production */
/* Use a CSS minifier */
```
### 2. Avoid @import
```css
/* Slow - additional HTTP request */
@import url('components/typography.css');
/* Better - combine files or use build tool */
/* Or let browser load multiple <link> tags in parallel */
```
### 3. Optimize Selectors
```css
/* Fast */
.class-name { }
#id-name { }
/* Slower */
div > ul > li > a { }
[data-attribute="value"] { }
/* Use classes for styling */
```
### 4. Use Will-Change Sparingly
```css
/* Only for elements that will actually animate */
.photo-item {
transition: transform 0.2s;
}
.photo-item:hover {
transform: translateY(-4px);
will-change: transform; /* Hint to browser */
}
```
## Debugging Styles
### Browser DevTools
1. **Inspect element** - Right-click → Inspect
2. **Check computed styles** - See which rules apply
3. **See cascade** - Understand override order
4. **Live edit** - Test changes instantly
### Debug Checklist
**Styles not loading:**
- [ ] File exists in `styles/` directory
- [ ] Filename matches expected pattern
- [ ] No syntax errors in CSS
- [ ] Browser cache cleared
**Styles not applying:**
- [ ] Check CSS specificity
- [ ] Check cascade order
- [ ] Look for typos in selectors
- [ ] Verify HTML classes match CSS
**Wrong styles applying:**
- [ ] Check for conflicting rules
- [ ] Verify file loading order
- [ ] Look for !important (avoid if possible)
## Next Steps
- **[Templates Guide](../templates/)** - Templates use these styles
- **[Template Discovery](../templates/template-discovery.md)** - How styles are discovered
- **[Recipes](../recipes/)** - Complete examples with CSS
Master the style system to create beautiful, maintainable Foldsites!

314
docs/content/support.md Normal file
View File

@ -0,0 +1,314 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Support & Community"
description: "Get help with Foldsite"
summary: "Find help, report issues, and connect with the Foldsite community."
quick_tips:
- "Check documentation first - most questions are answered here"
- "Search existing GitHub issues before opening new ones"
- "Share your Foldsite creations with the community"
---
# Support & Community
Need help with Foldsite? Here's how to get assistance and connect with others.
## Documentation
**Start here first!** Most questions are answered in the documentation:
- **[Home](index.md)** - Overview and quick start
- **[Directory Structure](directory-structure.md)** - Understanding the basics
- **[Templates](templates/)** - Template system guide
- **[Template Helpers](templates/template-helpers.md)** - Complete API reference
- **[Deployment](deployment/)** - Getting your site running
- **[Recipes](recipes/)** - Working examples
## Common Issues
### Template Not Found
**Error:** `Exception: Base template not found`
**Solution:** Ensure `base.html` exists in your templates directory:
```bash
ls templates/base.html
```
### Module Not Found
**Error:** `ModuleNotFoundError: No module named 'flask'`
**Solution:** Install dependencies:
```bash
pip install -r requirements.txt
```
### Port Already in Use
**Error:** `OSError: [Errno 48] Address already in use`
**Solution:** Change port in `config.toml`:
```toml
[server]
listen_port = 8082 # Use different port
```
Or stop the process using port 8081:
```bash
# Find process
lsof -i :8081
# Kill it
kill -9 <PID>
```
### Images Not Loading
**Problem:** Images show broken links
**Solution:** Use the `/download/` route for serving files:
```html
<!-- Correct -->
<img src="/download/{{ photo.path }}">
<!-- Wrong -->
<img src="/{{ photo.path }}">
```
### Template Changes Not Appearing
**Problem:** Modified templates don't show changes
**Solutions:**
1. Check you're editing the correct file
2. Clear browser cache (Cmd/Ctrl + Shift + R)
3. Restart the Foldsite server
4. Check `config.toml` points to correct templates directory
## GitHub Repository
**Repository:** [https://github.com/DWSresearch/foldsite](https://github.com/DWSresearch/foldsite)
### Reporting Bugs
Found a bug? Please report it on GitHub Issues.
**Before reporting:**
1. Search existing issues to avoid duplicates
2. Make sure you're using the latest version
3. Try to reproduce with minimal example
**Good bug report includes:**
- Foldsite version
- Python version
- Operating system
- Clear steps to reproduce
- Expected vs. actual behavior
- Error messages (full stack traces)
- Minimal example code if possible
**Example:**
```markdown
## Bug Report
**Foldsite version:** 1.0.0
**Python version:** 3.11.5
**OS:** macOS 14.0
### Steps to Reproduce
1. Create template with `{{ get_recent_posts() }}`
2. Run `python main.py --config config.toml`
3. Visit homepage
### Expected
Should show 5 recent posts
### Actual
Raises `AttributeError: 'NoneType' object has no attribute 'metadata'`
### Stack Trace
```
[paste full error here]
```
```
### Feature Requests
Have an idea for improvement? Open a feature request!
**Good feature request includes:**
- Clear description of the feature
- Use case (why you need it)
- Proposed implementation (if you have ideas)
- Examples from other tools (if applicable)
### Pull Requests
Contributions welcome! See [Develop Foldsite](develop/) for guidelines.
## Community
### Show & Tell
Share what you've built with Foldsite:
- Post in GitHub Discussions
- Tag `#foldsite` on social media
- Add your site to [Explore Foldsites](explore.md)
### Discussions
Have questions or want to chat? Use GitHub Discussions:
- **Q&A** - Ask questions
- **Show and Tell** - Share your site
- **Ideas** - Discuss potential features
- **General** - Everything else
## Getting Help
### Before Asking
1. **Read the docs** - Your answer might be here
2. **Search issues** - Someone might have asked already
3. **Check examples** - See working code in `/example_site`
4. **Enable debug mode** - See what Foldsite is doing:
```toml
[server]
debug = true
```
### How to Ask
**Good questions include:**
- What you're trying to achieve
- What you've tried
- Relevant code snippets
- Error messages
**Example:**
> I'm trying to show recent posts on my homepage, but `get_recent_posts()` returns an empty list.
>
> My content structure:
> ```
> content/
> ├── index.md
> └── blog/
> ├── post1.md
> └── post2.md
> ```
>
> My template code:
> ```jinja
> {% for post in get_recent_posts(limit=5) %}
> {{ post.title }}
> {% endfor %}
> ```
>
> Posts have frontmatter with `title` and `date` fields. What am I missing?
### Response Times
Foldsite is maintained by volunteers. Please be patient:
- **Bugs** - Usually addressed within a few days
- **Features** - May take longer depending on complexity
- **Questions** - Community often responds quickly
## Contributing
Want to help make Foldsite better?
### Ways to Contribute
- **Documentation** - Fix typos, clarify confusing sections, add examples
- **Bug fixes** - Fix issues and submit pull requests
- **Features** - Implement new functionality
- **Templates** - Create and share themes
- **Testing** - Test new releases, report issues
- **Community** - Help others in discussions
See [Develop Foldsite](develop/) for detailed contribution guidelines.
## Professional Support
Need dedicated help with Foldsite?
**[DWS (Dubey Web Services)](https://dws.rip)** may offer consulting for complex implementations. Contact through the website for availability and rates.
## Security Issues
Found a security vulnerability? **Do not open a public issue.**
Email security@dws.rip with:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if you have one)
We'll respond as quickly as possible and coordinate disclosure.
## Learning Resources
### Jinja2 (Template Engine)
Foldsite uses Jinja2 for templates:
- **[Official Docs](https://jinja.palletsprojects.com/)** - Complete Jinja2 reference
- **[Template Designer Docs](https://jinja.palletsprojects.com/en/3.1.x/templates/)** - Template syntax
### Markdown
Content is written in Markdown:
- **[CommonMark](https://commonmark.org/)** - Markdown specification
- **[Markdown Guide](https://www.markdownguide.org/)** - Beginner-friendly guide
### Python (For Development)
If you want to contribute to Foldsite:
- **[Python Tutorial](https://docs.python.org/3/tutorial/)** - Official Python tutorial
- **[Flask Docs](https://flask.palletsprojects.com/)** - Web framework used by Foldsite
## Acknowledgments
Foldsite is built with:
- **[Flask](https://flask.palletsprojects.com/)** - Web framework
- **[Jinja2](https://jinja.palletsprojects.com/)** - Template engine
- **[mistune](https://github.com/lepture/mistune)** - Markdown parser
- **[python-frontmatter](https://github.com/eyeseast/python-frontmatter)** - Frontmatter parsing
- **[Pillow](https://python-pillow.org/)** - Image processing
- **[Gunicorn](https://gunicorn.org/)** - WSGI HTTP server
Thank you to all contributors and users!
## Stay Updated
- **Watch** the GitHub repository for updates
- **Star** the repo if you find Foldsite useful
- **Fork** to experiment with your own changes
## Philosophy
Foldsite is part of the **DWS mission**: *"It's your Internet. Take it back."*
We believe in:
- **Simple tools** that solve real problems
- **User ownership** of content and presentation
- **Open source** collaboration
- **Privacy** and control
## Next Steps
- **[Explore Foldsites](explore.md)** - See what others have built
- **[Develop Foldsite](develop/)** - Contributing guidelines
- **[Recipes](recipes/)** - Working examples to learn from
**Still stuck?** Don't hesitate to ask for help. The Foldsite community is here to support you!

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!

View File

@ -0,0 +1,558 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Template Discovery System"
description: "Understanding how Foldsite finds and chooses templates"
summary: "Deep dive into Foldsite's hierarchical template discovery algorithm - learn exactly how templates are matched to content."
quick_tips:
- "Templates are searched from most specific to most general"
- "First match wins - more specific templates override general ones"
- "Templates cascade down through directory hierarchies"
---
# Template Discovery System
Understanding how Foldsite discovers and chooses templates is key to building sophisticated sites. This guide explains the complete template resolution algorithm.
## The Discovery Algorithm
When Foldsite renders a page, it follows a **hierarchical search pattern** to find the best template.
### Core Principle
**Specificity wins.** More specific templates override general ones.
Think of it like CSS specificity:
```
#specific-id (most specific)
.class-name
element (least specific)
```
In Foldsite:
```
my-post.html (most specific - exact file)
__file.md.html (category + extension)
__file.document.html (category only)
__file.html (least specific - any file)
```
## File Type Detection
Before template discovery, Foldsite determines the content type:
### For Files
**Step 1:** Extract extension
```
blog/my-post.md → extension: "md"
```
**Step 2:** Map to categories
```
"md" → categories: ["document", "md"]
```
**Category mapping:**
```python
GENERIC_FILE_MAPPING = {
"document": ["md", "txt", "html"],
"image": ["png", "jpg", "jpeg", "gif", "svg"],
"multimedia": ["mp4", "mp3", "webm"],
"other": [...] # Everything else
}
```
### For Folders
**Step 1:** Count file types in folder
```
photos/vacation/
IMG_001.jpg → jpg: 1
IMG_002.jpg → jpg: 2
IMG_003.jpg → jpg: 3
notes.txt → txt: 1
```
**Step 2:** Most common type wins
```
Most common: jpg (3 files)
→ categories: ["image", "jpg"]
```
## Template Search Order
### For Files: `blog/my-post.md`
**Given:**
- Path: `blog/my-post.md`
- Type: `file`
- Categories: `["document", "md"]`
- Extension: `md`
**Search order:**
1. **Exact file match** in current directory
```
templates/blog/my-post.html
```
2. **Type + extension** in current directory
```
templates/blog/__file.md.html
```
3. **Type + category** in current directory (most specific category first)
```
templates/blog/__file.document.html
```
4. **Generic type** in current directory
```
templates/blog/__file.html
```
5. **Move up one directory**, repeat steps 2-4
```
templates/__file.md.html
templates/__file.document.html
templates/__file.html
```
6. **Default template** (if exists)
```
templates/default.html
```
**First match wins!**
### For Folders: `photos/vacation/`
**Given:**
- Path: `photos/vacation/`
- Type: `folder`
- Categories: `["image"]` (most files are images)
**Search order:**
1. **Exact folder match**
```
templates/photos/vacation/__folder.html
```
2. **Type + category** in current directory
```
templates/photos/__folder.image.html
```
3. **Generic folder** in current directory
```
templates/photos/__folder.html
```
4. **Move up one directory**, repeat steps 2-3
```
templates/__folder.image.html
templates/__folder.html
```
5. **Default template**
```
templates/default.html
```
## Practical Examples
### Example 1: Blog Post
**Content:** `content/blog/posts/2024-my-post.md`
**Template search:**
```
1. templates/blog/posts/2024-my-post.html ✗ Not found
2. templates/blog/posts/__file.md.html ✗ Not found
3. templates/blog/posts/__file.document.html ✗ Not found
4. templates/blog/posts/__file.html ✗ Not found
5. templates/blog/__file.md.html ✓ FOUND!
```
**Result:** Uses `templates/blog/__file.md.html`
**Why:** Most specific template found in the hierarchy.
### Example 2: Homepage
**Content:** `content/index.md`
**Template search:**
```
1. templates/index.html ✓ FOUND!
```
**Result:** Uses `templates/index.html`
**Why:** Exact file match (most specific possible).
### Example 3: Photo Gallery
**Content:** `content/photos/vacation/` (folder with 50 JPG files)
**Categories:** `["image"]`
**Template search:**
```
1. templates/photos/vacation/__folder.html ✗ Not found
2. templates/photos/__folder.image.html ✓ FOUND!
```
**Result:** Uses `templates/photos/__folder.image.html`
**Why:** Type + category match in parent directory.
### Example 4: Deep Nesting
**Content:** `content/docs/guides/advanced/testing.md`
**Template search:**
```
1. templates/docs/guides/advanced/testing.html ✗ Not found
2. templates/docs/guides/advanced/__file.md.html ✗ Not found
3. templates/docs/guides/__file.md.html ✗ Not found
4. templates/docs/__file.md.html ✓ FOUND!
```
**Result:** Uses `templates/docs/__file.md.html`
**Why:** Climbs directory tree until finding a match.
## Template Cascade
Templates **cascade down** through directories:
```
templates/
├── __file.md.html ← Default for ALL markdown
└── blog/
└── __file.md.html ← Override for blog/ only
```
**Rendering `content/about.md`:**
- Uses `templates/__file.md.html`
**Rendering `content/blog/post.md`:**
- Uses `templates/blog/__file.md.html` (more specific)
### Multi-Level Cascade
```
templates/
├── __file.md.html ← Level 1: Site default
├── blog/
│ └── __file.md.html ← Level 2: Blog default
└── blog/
└── tutorials/
└── __file.md.html ← Level 3: Tutorial-specific
```
**Effect:**
- `content/about.md` → Level 1 template
- `content/blog/news.md` → Level 2 template
- `content/blog/tutorials/python.md` → Level 3 template
## Category Priority
Files can belong to multiple categories. **More specific categories come first.**
### Category Hierarchy
For `my-post.md`:
```
Categories: ["md", "document"]
↑ ↑
specific general
```
**Search order:**
```
1. __file.md.html ← Most specific
2. __file.document.html ← More general
3. __file.html ← Most general
```
## Debug Mode
Enable debug mode to see template discovery in action:
```toml
# config.toml
[server]
debug = true
```
**Console output when visiting a page:**
```
[DEBUG] Template discovery for: blog/my-post.md
[DEBUG] Content type: file
[DEBUG] Categories: ['md', 'document']
[DEBUG] Extension: md
[DEBUG]
[DEBUG] Searching templates:
[DEBUG] 1. templates/blog/my-post.html - NOT FOUND
[DEBUG] 2. templates/blog/__file.md.html - FOUND!
[DEBUG]
[DEBUG] Using template: templates/blog/__file.md.html
```
### Debug Test Page
Create a test page to understand discovery:
```markdown
---
title: "Template Discovery Test"
---
# Current Template Info
**Current Path:** {{ currentPath }}
**Styles Loaded:**
{% for style in styles %}
- {{ style }}
{% endfor %}
**Metadata:**
{{ metadata }}
```
Visit this page and check the console to see which template was used.
## Edge Cases
### No Template Found
If no template matches:
**For files:**
- Foldsite serves the file directly (download)
- Useful for PDFs, images, etc.
**For folders:**
- Returns 404 error
- Need at least one `__folder.html` template
### Hidden Files
Files/folders starting with `___` are ignored:
```
content/
├── post.md ✓ Will be rendered
└── ___draft.md ✗ Ignored completely
```
No template search is performed for hidden content.
### Multiple Extensions
Only the last extension is considered:
```
my-file.tar.gz → extension: "gz"
```
Not: `tar.gz` or `tar`
## Style Discovery
Styles follow **similar logic** to templates but accumulate instead of first-match:
### Style Search for `blog/post.md`
**All matching styles are loaded (in order):**
```
1. /base.css ← Always loaded
2. /blog/__file.md.css ← If exists
3. /blog/__file.document.css ← If exists
4. /__file.md.css ← If exists (from root)
5. /blog/post.md.css ← If exists (specific file)
```
**All found styles are included** (not just first match).
**Rendering:**
```html
<link rel="stylesheet" href="/styles/base.css">
<link rel="stylesheet" href="/styles/blog/__file.md.css">
<link rel="stylesheet" href="/styles/__file.md.css">
```
## Optimization Tips
### 1. Keep Templates at Appropriate Levels
**Good:**
```
templates/
├── __file.md.html ← General template
└── blog/
└── __file.md.html ← Blog-specific customization
```
**Avoid:**
```
templates/
├── post1.html
├── post2.html
├── post3.html ← Repetitive!
└── ...
```
### 2. Use Categories Effectively
**Good:**
```
templates/
├── __file.md.html ← For markdown
├── __folder.image.html ← For galleries
└── __file.document.html ← For all documents
```
**Avoid:**
```
templates/
├── __file.html ← Too generic
└── (nothing else)
```
### 3. Understand the Cascade
Place templates where they make sense:
**Project structure:**
```
content/
├── blog/ → Frequent posts, custom layout
├── docs/ → Technical docs, different layout
└── about.md → One-off pages
```
**Template structure:**
```
templates/
├── __file.md.html ← Default for one-off pages
├── blog/
│ └── __file.md.html ← Blog-specific
└── docs/
└── __file.md.html ← Docs-specific
```
## Common Patterns
### Pattern: Section-Specific Layouts
Different sections need different layouts:
```
templates/
├── base.html ← Shared wrapper
├── __file.md.html ← Default content
├── blog/
│ ├── __file.md.html ← Blog posts
│ └── __folder.md.html ← Blog index
├── portfolio/
│ ├── __file.md.html ← Project pages
│ └── __folder.md.html ← Portfolio grid
└── docs/
├── __file.md.html ← Documentation pages
└── __folder.md.html ← Docs index
```
### Pattern: Gradual Specialization
Start general, add specificity as needed:
**Phase 1: MVP**
```
templates/
├── base.html
└── __file.md.html
```
**Phase 2: Add Gallery**
```
templates/
├── base.html
├── __file.md.html
└── __folder.image.html ← New!
```
**Phase 3: Custom Blog**
```
templates/
├── base.html
├── __file.md.html
├── __folder.image.html
└── blog/
└── __file.md.html ← New!
```
### Pattern: Override Single Page
Override one specific page:
```
templates/
├── __file.md.html ← All markdown
└── index.html ← Special homepage
```
## Troubleshooting
### Template Not Being Used
**Check:**
1. **File name** - Is it exactly right?
2. **Location** - Is it in the right directory?
3. **Extension** - `.html`, not `.htm` or `.jinja`
4. **Debug mode** - What does the console say?
**Debug:**
```bash
# Enable debug
[server]
debug = true
# Restart and check console output
```
### Wrong Template Used
**Likely cause:** More specific template exists
**Example:**
```
Want: templates/__file.md.html
Using: templates/blog/__file.md.html ← More specific!
```
**Solution:** Update the more specific template, or remove it.
### Styles Not Loading
**Check:**
1. **Style file exists** in `styles/` directory
2. **Path matches** template expectations
3. **Browser dev tools** - Are styles being requested?
**Remember:** Unlike templates, **all matching styles load**.
## Next Steps
- **[Template System Overview](index.md)** - Basics of templates
- **[Template Helpers](template-helpers.md)** - Functions available in templates
- **[Recipes](../recipes/)** - Working examples
- **[Styles Guide](../styles/)** - CSS cascade system
Understanding template discovery gives you complete control over your site's presentation. Use it wisely!

View File

@ -0,0 +1,793 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Template Helper Functions"
description: "Complete reference for all Jinja2 helper functions in Foldsite"
summary: "Comprehensive API documentation for Foldsite's template helper functions - discover content, navigate your site, and build dynamic features."
quick_tips:
- "All helpers are automatically available in every template"
- "Helpers return Python objects you can loop over and filter"
- "Most helpers are cached for performance - safe to call multiple times"
---
# Template Helper Functions
Foldsite provides powerful helper functions you can use in any template. These functions access your content dynamically, enabling features like recent posts, navigation menus, breadcrumbs, and more.
## Content Discovery
### get_folder_contents(path)
Get all files and folders in a directory with rich metadata.
**Parameters:**
- `path` (str, optional) - Relative path from content root. Defaults to current directory.
**Returns:** List of `TemplateFile` objects with attributes:
- `name` (str) - Filename with extension
- `path` (str) - Relative path from content root
- `proper_name` (str) - Filename without extension
- `extension` (str) - File extension (`.md`, `.jpg`, etc.)
- `categories` (list[str]) - File categories (`['document']`, `['image']`, etc.)
- `date_modified` (str) - Last modified date (`YYYY-MM-DD`)
- `date_created` (str) - Creation date (`YYYY-MM-DD`)
- `size_kb` (int) - File size in kilobytes
- `metadata` (dict | None) - Markdown frontmatter or image EXIF data
- `dir_item_count` (int) - Number of items if it's a directory
- `is_dir` (bool) - True if it's a directory
**Example:**
```jinja
<h2>Files in this folder:</h2>
<ul>
{% for file in get_folder_contents(currentPath) %}
<li>
<a href="/{{ file.path }}">{{ file.proper_name }}</a>
({{ file.size_kb }} KB, modified {{ file.date_modified }})
</li>
{% endfor %}
</ul>
```
**Sort and filter:**
```jinja
{# Sort by date, newest first #}
{% for file in get_folder_contents()|sort(attribute='date_created', reverse=True) %}
...
{% endfor %}
{# Filter to only documents #}
{% for file in get_folder_contents() %}
{% if 'document' in file.categories %}
<a href="/{{ file.path }}">{{ file.proper_name }}</a>
{% endif %}
{% endfor %}
```
### get_sibling_content_files(path)
Get files in the same directory as the current page.
**Parameters:**
- `path` (str, optional) - Relative path. Defaults to current directory.
**Returns:** List of tuples `(filename, relative_path)`
**Example:**
```jinja
<nav class="sibling-nav">
<h3>Other pages in this section:</h3>
{% for name, path in get_sibling_content_files(currentPath) %}
<a href="/{{ path }}"
{% if path == currentPath %}class="active"{% endif %}>
{{ name }}
</a>
{% endfor %}
</nav>
```
### get_sibling_content_folders(path)
Get folders in the same directory as the current page.
**Parameters:**
- `path` (str, optional) - Relative path. Defaults to current directory.
**Returns:** List of tuples `(folder_name, relative_path)`
**Example:**
```jinja
<nav class="folder-nav">
<h3>Sections:</h3>
{% for name, path in get_sibling_content_folders(currentPath) %}
<a href="/{{ path }}">{{ name }}</a>
{% endfor %}
</nav>
```
### get_text_document_preview(path)
Get a preview (first 100 characters) of a text document.
**Parameters:**
- `path` (str) - Relative path to the document
**Returns:** String (first 100 characters of the file)
**Example:**
```jinja
{% for file in get_folder_contents() %}
{% if 'document' in file.categories %}
<article>
<h3><a href="/{{ file.path }}">{{ file.proper_name }}</a></h3>
<p>{{ get_text_document_preview(file.path) }}...</p>
</article>
{% endif %}
{% endfor %}
```
### get_rendered_markdown(path)
Get fully rendered markdown content without Jinja2 templating. Perfect for displaying markdown files (like `index.md`) within folder views or embedding content from one markdown file into a template.
**Parameters:**
- `path` (str) - Relative path to the markdown file
**Returns:** Dictionary with:
- `html` (str | None) - Rendered HTML content from markdown
- `metadata` (dict | None) - Frontmatter metadata from the markdown file
- `exists` (bool) - True if file was found and rendered successfully
**Example:**
```jinja
{# Display index.md in a folder view #}
{% set index_path = (currentPath + '/index.md') if currentPath else 'index.md' %}
{% set index = get_rendered_markdown(index_path) %}
{% if index.exists %}
<section class="folder-index">
{{ index.html | safe }}
{% if index.metadata.author %}
<p class="author">By {{ index.metadata.author }}</p>
{% endif %}
</section>
{% endif %}
```
**Use in folder templates:**
```jinja
{# Show index.md content at the top of a folder listing #}
<div class="folder-view">
{% set index = get_rendered_markdown(currentPath + '/index.md') %}
{% if index.exists %}
<div class="folder-introduction">
{{ index.html | safe }}
<hr>
</div>
{% endif %}
{# Then show other files in the folder #}
<div class="folder-contents">
{% for file in get_folder_contents(currentPath) %}
{% if file.name != 'index.md' %}
<a href="/{{ file.path }}">{{ file.proper_name }}</a>
{% endif %}
{% endfor %}
</div>
</div>
```
**Embed content from another file:**
```jinja
{# Include shared content from another markdown file #}
{% set about = get_rendered_markdown('about/team-bio.md') %}
{% if about.exists %}
<aside class="team-bio">
{{ about.html | safe }}
</aside>
{% endif %}
```
### get_markdown_metadata(path)
Get metadata (frontmatter) from a markdown file **without rendering the content**. Perfect for displaying static markdown metadata in any location - like showing post titles, descriptions, or custom fields without the overhead of rendering the full markdown.
**Parameters:**
- `path` (str) - Relative path to the markdown file
**Returns:** Dictionary with:
- `metadata` (dict | None) - Frontmatter metadata from the markdown file
- `exists` (bool) - True if file was found successfully
- `error` (str, optional) - Error message if reading failed
**Example:**
```jinja
{# Display metadata from a specific post without rendering it #}
{% set post_meta = get_markdown_metadata('blog/my-awesome-post.md') %}
{% if post_meta.exists %}
<div class="featured-post">
<h2>{{ post_meta.metadata.title }}</h2>
<p class="description">{{ post_meta.metadata.description }}</p>
<p class="date">Published: {{ post_meta.metadata.date }}</p>
<div class="tags">
{% for tag in post_meta.metadata.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
<a href="/blog/my-awesome-post.md">Read more →</a>
</div>
{% endif %}
```
**Build a custom post grid using metadata only:**
```jinja
{# Create cards showing metadata from multiple posts #}
<div class="post-grid">
{% for post_path in ['blog/intro.md', 'blog/tutorial.md', 'blog/advanced.md'] %}
{% set meta = get_markdown_metadata(post_path) %}
{% if meta.exists %}
<article class="post-card">
<h3>{{ meta.metadata.title }}</h3>
<time datetime="{{ meta.metadata.date }}">{{ meta.metadata.date }}</time>
{% if meta.metadata.author %}
<p class="author">By {{ meta.metadata.author }}</p>
{% endif %}
{% if meta.metadata.excerpt %}
<p>{{ meta.metadata.excerpt }}</p>
{% endif %}
<a href="/{{ post_path }}">Read full post</a>
</article>
{% endif %}
{% endfor %}
</div>
```
**Show custom metadata fields:**
```jinja
{# Display reading time, difficulty, or any custom frontmatter field #}
{% set meta = get_markdown_metadata('tutorials/python-basics.md') %}
{% if meta.exists %}
<div class="tutorial-info">
<h2>{{ meta.metadata.title }}</h2>
{% if meta.metadata.difficulty %}
<span class="badge">{{ meta.metadata.difficulty }}</span>
{% endif %}
{% if meta.metadata.reading_time %}
<span class="time">{{ meta.metadata.reading_time }} min read</span>
{% endif %}
{% if meta.metadata.prerequisites %}
<div class="prerequisites">
<strong>Prerequisites:</strong>
<ul>
{% for prereq in meta.metadata.prerequisites %}
<li>{{ prereq }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
{% endif %}
```
**Performance tip:** Use `get_markdown_metadata` instead of `get_rendered_markdown` when you only need frontmatter data. It's much faster since it doesn't process the markdown content.
## Blog Functions
### get_recent_posts(limit, folder)
Get recent blog posts sorted by date (newest first).
**Parameters:**
- `limit` (int, optional) - Maximum number of posts. Default: 5
- `folder` (str, optional) - Search within specific folder. Default: "" (search everywhere)
**Returns:** List of post dictionaries with:
- `title` (str) - From frontmatter or filename
- `date` (str) - From frontmatter
- `path` (str) - Relative path to post
- `url` (str) - Full URL to post
- `metadata` (dict) - Full frontmatter including tags, description, etc.
**Example:**
```jinja
<section class="recent-posts">
<h2>Recent Posts</h2>
{% for post in get_recent_posts(limit=5, folder='blog') %}
<article>
<h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
<time datetime="{{ post.date }}">{{ post.date }}</time>
{% if post.metadata.description %}
<p>{{ post.metadata.description }}</p>
{% endif %}
{% if post.metadata.tags %}
<div class="tags">
{% for tag in post.metadata.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
</article>
{% endfor %}
</section>
```
**Search in specific folder:**
```jinja
{# Only posts from blog/tutorials/ #}
{% for post in get_recent_posts(limit=10, folder='blog/tutorials') %}
<a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}
```
### get_posts_by_tag(tag, limit)
Get posts filtered by a specific tag.
**Parameters:**
- `tag` (str) - Tag to filter by (case-insensitive)
- `limit` (int, optional) - Maximum posts. Default: 10
**Returns:** List of post dictionaries (same format as `get_recent_posts`)
**Example:**
```jinja
<h2>Python Posts</h2>
{% for post in get_posts_by_tag('python', limit=10) %}
<article>
<h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
<time>{{ post.date }}</time>
</article>
{% endfor %}
```
**Dynamic tag pages:**
```jinja
{# If currentPath is 'tags/python.md' #}
{% set tag = currentPath.split('/')[-1].replace('.md', '') %}
<h1>Posts tagged: {{ tag }}</h1>
{% for post in get_posts_by_tag(tag) %}
...
{% endfor %}
```
### get_related_posts(current_post_path, limit)
Find posts related to the current post based on shared tags.
**Parameters:**
- `current_post_path` (str) - Path to current post
- `limit` (int, optional) - Maximum related posts. Default: 3
**Returns:** List of post dictionaries with an additional `overlap_score` field (number of shared tags)
**Example:**
```jinja
{% set related = get_related_posts(currentPath, limit=3) %}
{% if related %}
<aside class="related-posts">
<h3>You might also like:</h3>
{% for post in related %}
<article>
<a href="{{ post.url }}">{{ post.title }}</a>
<small>{{ post.overlap_score }} shared tags</small>
</article>
{% endfor %}
</aside>
{% endif %}
```
### get_all_tags()
Get all tags used across the site with post counts.
**Returns:** List of tag dictionaries with:
- `name` (str) - Tag name
- `count` (int) - Number of posts with this tag
**Example:**
```jinja
<div class="tag-cloud">
{% for tag in get_all_tags() %}
<a href="/tags/{{ tag.name|lower }}"
class="tag"
style="font-size: {{ 0.8 + (tag.count * 0.1) }}em;">
{{ tag.name }} ({{ tag.count }})
</a>
{% endfor %}
</div>
```
**Sort by popularity:**
```jinja
{% for tag in get_all_tags()|sort(attribute='count', reverse=True) %}
<a href="/tags/{{ tag.name|lower }}">
{{ tag.name }} <span class="count">{{ tag.count }}</span>
</a>
{% endfor %}
```
## Navigation
### generate_breadcrumbs(current_path)
Generate breadcrumb navigation based on URL path.
**Parameters:**
- `current_path` (str) - Current page path
**Returns:** List of breadcrumb dictionaries with:
- `title` (str) - Display title (from metadata or derived from path)
- `url` (str) - URL to this level
- `is_current` (bool) - True if this is the current page
**Example:**
```jinja
<nav class="breadcrumbs" aria-label="Breadcrumb">
{% for crumb in generate_breadcrumbs(currentPath) %}
{% if not crumb.is_current %}
<a href="{{ crumb.url }}">{{ crumb.title }}</a>
{% else %}
<span aria-current="page">{{ crumb.title }}</span>
{% endif %}
{% if not loop.last %} / {% endif %}
{% endfor %}
</nav>
```
**Styled example:**
```jinja
<ol class="breadcrumb">
{% for crumb in generate_breadcrumbs(currentPath) %}
<li class="breadcrumb-item {% if crumb.is_current %}active{% endif %}">
{% if not crumb.is_current %}
<a href="{{ crumb.url }}">{{ crumb.title }}</a>
{% else %}
{{ crumb.title }}
{% endif %}
</li>
{% endfor %}
</ol>
```
### get_navigation_items(max_items)
Get top-level pages and folders for site navigation.
**Parameters:**
- `max_items` (int, optional) - Maximum items to return. Default: 10
**Returns:** List of navigation dictionaries with:
- `title` (str) - Display title (from metadata or filename)
- `url` (str) - URL to page/folder
- `path` (str) - Relative path
- `is_folder` (bool, optional) - True if it's a folder
**Example:**
```jinja
<nav class="main-nav">
<a href="/">Home</a>
{% for item in get_navigation_items(max_items=10) %}
<a href="{{ item.url }}"
{% if currentPath.startswith(item.path) %}class="active"{% endif %}>
{{ item.title }}
</a>
{% endfor %}
</nav>
```
**With icons for folders:**
```jinja
{% for item in get_navigation_items() %}
<a href="{{ item.url }}">
{% if item.is_folder %}📁{% endif %}
{{ item.title }}
</a>
{% endfor %}
```
## Gallery Functions
### get_photo_albums()
Get all photo gallery directories (folders containing mostly images).
**Returns:** List of album dictionaries with:
- `name` (str) - Album name
- `path` (str) - Relative path
- `url` (str) - Full URL
- `image_count` (int) - Number of images
- `total_files` (int) - Total files in album
**Example:**
```jinja
<div class="photo-albums">
<h2>Photo Galleries</h2>
{% for album in get_photo_albums() %}
<a href="{{ album.url }}" class="album-card">
<h3>{{ album.name }}</h3>
<p>{{ album.image_count }} photos</p>
</a>
{% endfor %}
</div>
```
## Built-in Jinja2 Filters
In addition to Foldsite helpers, you can use standard Jinja2 filters:
### String Filters
```jinja
{{ "hello"|upper }} {# HELLO #}
{{ "HELLO"|lower }} {# hello #}
{{ "hello world"|title }} {# Hello World #}
{{ "hello world"|capitalize }} {# Hello world #}
{{ " text "|trim }} {# text #}
{{ "hello"|reverse }} {# olleh #}
```
### List Filters
```jinja
{{ items|length }} {# Count items #}
{{ items|first }} {# First item #}
{{ items|last }} {# Last item #}
{{ items|join(', ') }} {# Join with comma #}
{{ items|sort }} {# Sort list #}
{{ items|sort(attribute='date') }} {# Sort by attribute #}
{{ items|reverse }} {# Reverse list #}
{{ items|unique }} {# Remove duplicates #}
```
### Other Useful Filters
```jinja
{{ value|default('N/A') }} {# Default if falsy #}
{{ html|safe }} {# Don't escape HTML #}
{{ number|round(2) }} {# Round to 2 decimals #}
{{ date|replace('-', '/') }} {# Replace strings #}
```
## Common Patterns
### Pattern: Blog Homepage with Recent Posts
```jinja
<main>
<h1>Welcome to My Blog</h1>
<section class="recent-posts">
{% for post in get_recent_posts(limit=10) %}
<article class="post-card">
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
<time datetime="{{ post.date }}">{{ post.date }}</time>
{% if post.metadata.description %}
<p>{{ post.metadata.description }}</p>
{% endif %}
{% if post.metadata.tags %}
<div class="tags">
{% for tag in post.metadata.tags %}
<a href="/tags/{{ tag|lower }}" class="tag">{{ tag }}</a>
{% endfor %}
</div>
{% endif %}
</article>
{% endfor %}
</section>
<aside class="sidebar">
<h3>Popular Tags</h3>
<div class="tag-cloud">
{% for tag in get_all_tags()|sort(attribute='count', reverse=True)[:10] %}
<a href="/tags/{{ tag.name|lower }}">{{ tag.name }} ({{ tag.count }})</a>
{% endfor %}
</div>
</aside>
</main>
```
### Pattern: Folder Index with Previews
```jinja
<div class="folder-index">
<h1>{{ currentPath.split('/')[-1]|title }}</h1>
<nav class="breadcrumbs">
{% for crumb in generate_breadcrumbs(currentPath) %}
{% if not crumb.is_current %}
<a href="{{ crumb.url }}">{{ crumb.title }}</a> /
{% else %}
{{ crumb.title }}
{% endif %}
{% endfor %}
</nav>
{# Show index.md content if it exists #}
{% set index = get_rendered_markdown(currentPath + '/index.md') %}
{% if index.exists %}
<section class="folder-introduction">
{{ index.html | safe }}
<hr>
</section>
{% endif %}
<div class="content-grid">
{% for file in get_folder_contents(currentPath)|sort(attribute='date_created', reverse=True) %}
{% if 'document' in file.categories and file.name != 'index.md' %}
<div class="content-card">
<h3><a href="/{{ file.path }}">{{ file.proper_name }}</a></h3>
<p class="date">{{ file.date_created }}</p>
{% if file.metadata and file.metadata.description %}
<p>{{ file.metadata.description }}</p>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
```
### Pattern: Photo Gallery with EXIF Data
```jinja
<div class="gallery">
{% set photos = get_folder_contents(currentPath)|sort(attribute='date_created', reverse=True) %}
<h1>{{ currentPath.split('/')[-1]|title }}</h1>
<p>{{ photos|length }} photos</p>
<div class="photo-grid">
{% for photo in photos %}
{% if 'image' in photo.categories %}
<figure class="photo">
<a href="/download/{{ photo.path }}">
<img src="/download/{{ photo.path }}?max_width=600"
alt="{{ photo.name }}"
loading="lazy">
</a>
<figcaption>
{% if photo.metadata and photo.metadata.exif %}
<p>{{ photo.metadata.exif.DateTimeOriginal }}</p>
{% if photo.metadata.exif.Model %}
<p>{{ photo.metadata.exif.Model }}</p>
{% endif %}
{% endif %}
</figcaption>
</figure>
{% endif %}
{% endfor %}
</div>
</div>
```
### Pattern: Documentation with Sibling Pages
```jinja
<article class="doc-page">
<nav class="doc-sidebar">
<h3>In This Section:</h3>
<ul>
{% for name, path in get_sibling_content_files(currentPath) %}
<li>
<a href="/{{ path }}"
{% if path == currentPath %}class="active"{% endif %}>
{{ name.replace('.md', '')|replace('-', ' ')|title }}
</a>
</li>
{% endfor %}
</ul>
</nav>
<div class="doc-content">
<nav class="breadcrumbs">
{% for crumb in generate_breadcrumbs(currentPath) %}
<a href="{{ crumb.url }}">{{ crumb.title }}</a>
{% if not loop.last %} {% endif %}
{% endfor %}
</nav>
{{ content|safe }}
{% set related = get_related_posts(currentPath, limit=3) %}
{% if related %}
<aside class="related-docs">
<h4>Related Documentation:</h4>
<ul>
{% for doc in related %}
<li><a href="{{ doc.url }}">{{ doc.title }}</a></li>
{% endfor %}
</ul>
</aside>
{% endif %}
</div>
</article>
```
## Performance Tips
### Caching
Most helper functions are cached automatically. It's safe to call them multiple times:
```jinja
{# These don't cause multiple filesystem scans #}
{% set posts = get_recent_posts(limit=5) %}
... use posts ...
{% set posts_again = get_recent_posts(limit=5) %} {# Cached result #}
```
### Filtering vs. Multiple Calls
Filter results in templates rather than calling helpers multiple times:
```jinja
✓ Efficient:
{% set all_files = get_folder_contents() %}
{% set docs = all_files|selectattr('categories', 'contains', 'document') %}
{% set images = all_files|selectattr('categories', 'contains', 'image') %}
✗ Less efficient:
{% for file in get_folder_contents() %}
{% if 'document' in file.categories %}...{% endif %}
{% endfor %}
{% for file in get_folder_contents() %} {# Second call #}
{% if 'image' in file.categories %}...{% endif %}
{% endfor %}
```
## Debugging
Enable debug mode to see which templates and helpers are being used:
```toml
# config.toml
[server]
debug = true
```
Add debug output to templates:
```jinja
{# Show what get_recent_posts returns #}
{% set posts = get_recent_posts(limit=3) %}
<pre>{{ posts|pprint }}</pre>
{# Show current variables #}
<pre>
currentPath: {{ currentPath }}
metadata: {{ metadata|pprint }}
</pre>
```
## Next Steps
- **[Template Recipes](../recipes/)** - See these helpers in complete working examples
- **[Template System Overview](index.md)** - Understand how templates work
- **[Template Discovery](template-discovery.md)** - Learn how Foldsite finds templates
With these helpers, you can build sophisticated, dynamic sites while keeping your content as simple files and folders!

View File

@ -0,0 +1,314 @@
---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Theme Gallery"
description: "Download and use community-created Foldsite themes"
summary: "Browse ready-to-use themes for Foldsite. Download complete template and style packages to jumpstart your site."
quick_tips:
- "Themes are complete template + style packages you can drop into your project"
- "All themes are free and open source"
- "Customize themes to make them your own"
---
# Theme Gallery
Ready-to-use themes for Foldsite. Download, customize, and launch your site quickly.
## What is a Theme?
A Foldsite theme is a complete package of:
- **Templates** (`templates/` directory)
- **Styles** (`styles/` directory)
- **Example content** (optional)
- **Configuration** (optional)
Simply download and drop into your Foldsite project.
## Official Themes
### Default Theme
**Included in:** Foldsite repository (`example_site/`)
**Type:** Blog + Gallery
**Best for:** Personal sites, blogs with photos
**Features:**
- Responsive sidebar navigation
- Blog post support with metadata
- Photo gallery views
- Breadcrumb navigation
- Clean, minimal design
**Install:**
```bash
# Copy from example_site
cp -r example_site/template my-site/templates
cp -r example_site/style my-site/styles
```
**Preview:** See [Tanishq Dubey's site](https://tanishq.page)
---
### Documentation Theme
**Included in:** Foldsite repository (`docs/`)
**Type:** Documentation
**Best for:** Project docs, technical writing, knowledge bases
**Features:**
- Hierarchical navigation
- Sibling page links
- Code syntax highlighting
- Breadcrumb trails
- Clean, readable typography
**Install:**
```bash
# Copy from docs
cp -r docs/templates my-site/templates
cp -r docs/styles my-site/styles
```
**Preview:** This documentation site!
---
## Community Themes
*Community-contributed themes will appear here as they're created and submitted.*
### How to Submit a Theme
Created a theme you want to share?
**Requirements:**
- Complete templates and styles
- README with installation instructions
- Screenshot or demo site
- MIT or similar permissive license
- No external dependencies (except common CDNs)
**Submission process:**
1. Create GitHub repository with your theme
2. Open issue on Foldsite repo with "Theme Submission" label
3. Include:
- Theme name and description
- Screenshot or demo URL
- Repository link
- What makes it unique
4. We'll review and add to gallery
**Theme structure:**
```
my-theme/
├── README.md
├── LICENSE
├── templates/
│ ├── base.html
│ ├── __file.md.html
│ └── ...
├── styles/
│ ├── base.css
│ └── ...
├── screenshots/
│ └── preview.png
└── example-content/ (optional)
└── ...
```
---
## Using a Theme
### Installation
1. **Download theme** (clone or download ZIP)
2. **Copy to your project:**
```bash
cp -r theme-name/templates my-site/templates
cp -r theme-name/styles my-site/styles
```
3. **Configure paths** in `config.toml`:
```toml
[paths]
templates_dir = "/path/to/my-site/templates"
styles_dir = "/path/to/my-site/styles"
```
4. **Test:**
```bash
python main.py --config config.toml
```
### Customization
Themes are starting points. Make them your own!
**Easy customizations:**
- Change colors in CSS
- Modify fonts
- Adjust spacing and sizing
- Replace logo/branding
- Update footer text
**Advanced customizations:**
- Modify templates
- Add new template variants
- Change layout structure
- Add custom helper functions
- Integrate JavaScript libraries
**Best practice:** Keep original theme in git so you can track your changes.
---
## Theme Development
Want to create your own theme?
### Theme Checklist
A complete theme should include:
**Required Templates:**
- [ ] `base.html` - Main page wrapper
- [ ] `__file.md.html` - Markdown file display
- [ ] `__folder.md.html` - Folder index for documents
- [ ] `__error.html` - Error pages
**Optional but Recommended:**
- [ ] `index.html` - Custom homepage
- [ ] `__folder.image.html` - Photo galleries
- [ ] `__file.document.html` - Document-specific layout
**Styles:**
- [ ] `base.css` - Base styles, always loaded
- [ ] `__file.md.css` - Markdown file styles
- [ ] `__folder.image.css` - Gallery styles
- [ ] Responsive design (mobile-friendly)
**Documentation:**
- [ ] README with installation instructions
- [ ] Screenshot or demo
- [ ] List of features
- [ ] Customization guide
- [ ] License (MIT recommended)
### Design Guidelines
**For consistency and usability:**
1. **Mobile-first** - Design for small screens first
2. **Accessible** - Follow WCAG guidelines
3. **Fast** - Minimize CSS, optimize images
4. **Semantic HTML** - Use proper elements
5. **Print-friendly** - Consider print stylesheets
**Typography:**
- Readable font sizes (16px+ for body)
- Good line height (1.5+)
- Proper contrast ratios
- System fonts or fast-loading web fonts
**Colors:**
- Consistent color palette
- Sufficient contrast
- Dark mode consideration (optional)
**Layout:**
- Clear visual hierarchy
- Consistent spacing
- Responsive breakpoints
- Touch-friendly (44px+ tap targets)
### Testing Your Theme
Before sharing, test with:
- **Various content types** - Markdown, images, mixed
- **Different structures** - Flat vs. deep hierarchies
- **Edge cases** - Long titles, no metadata, many tags
- **Devices** - Mobile, tablet, desktop
- **Browsers** - Chrome, Firefox, Safari
### Inspiration
Look at themes from other static site generators:
- Jekyll themes
- Hugo themes
- 11ty themes
- Gatsby themes
Adapt patterns (don't copy code) to Foldsite's template system.
---
## Coming Soon
**Future theme additions:**
- **Minimal Blog** - Ultra-simple, typography-focused
- **Photo Portfolio** - Full-screen galleries, minimal UI
- **Magazine** - Multi-column, content-rich
- **Landing Page** - Single-page, marketing-focused
- **Academic** - Papers, publications, research-focused
Want to help create these? See [Develop Foldsite](develop/).
---
## Theme Showcase
*Once community themes are available, this section will showcase screenshots and live demos.*
---
## Frequently Asked Questions
**Q: Are themes free?**
A: Yes! All themes in the official gallery are free and open source.
**Q: Can I sell themes I create?**
A: The themes themselves must be open source if listed here, but you could offer customization services commercially.
**Q: Do themes work with all Foldsite versions?**
A: Themes should specify compatible versions. Most work across versions unless core template system changes.
**Q: Can I request a specific theme?**
A: Open an issue with "Theme Request" label. No guarantees, but community might help!
**Q: How do I update a theme?**
A: If you've customized it, manually merge changes. Otherwise, re-download and replace.
**Q: Can I mix themes?**
A: Yes! Take templates from different themes. Just ensure styles don't conflict.
---
## Contributing
Help grow the theme ecosystem:
- **Create themes** - Share your designs
- **Improve docs** - Help others use themes
- **Test themes** - Report issues
- **Showcase sites** - Inspire others
See [Develop Foldsite](develop/) for contribution guidelines.
---
## Support
Need help with themes?
- **[Support](support.md)** - Get help
- **[Templates Guide](templates/)** - Learn the system
- **[Recipes](recipes/)** - See examples
- **GitHub Issues** - Report theme bugs
---
*This gallery is just getting started. As the Foldsite community grows, expect many more themes! Consider contributing the first one!*

View File

@ -0,0 +1,266 @@
/* Document Layout - For documentation/blog post pages */
/* Based on reference_post.html design */
:root {
/* Document-specific spacing */
--document-margin-x: 120px;
--document-max-width: 1200px;
}
/* Main content wrapper for document pages */
.document-layout {
/* max-width: var(--document-max-width); */
margin: 0 auto;
padding: var(--spacing-exp-7) var(--document-margin-x);
box-sizing: border-box;
}
.document {
display: flex;
flex-direction: row;
height: 100vh;
}
.document-header-holder {
display: flex;
height: 70vh;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
/* text-align: end; */
}
/* Document header with metadata */
.document-header {
margin-bottom: var(--spacing-exp-6);
max-width: 15%;
}
.document-metadata {
font-family: var(--fontFamily-mono);
font-size: var(--fontSize-small);
color: var(--swatch-4);
margin-bottom: var(--spacing-2);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.document-title {
font-size: 3.8rem;
font-weight: 420;
line-height: 1;
margin: var(--spacing-2) 0;
letter-spacing: -0.02em;
}
.document-subtitle {
font-size: 1.6rem;
font-weight: 500;
color: var(--swatch-2);
line-height: var(--lineHeight-default);
margin-top: var(--spacing-1);
}
/* Document content area */
.document-quick-tips {
font-size: 1rem;
font-weight: 500;
color: var(--swatch-2);
line-height: var(--lineHeight-default);
margin-top: var(--spacing-1);
}
.document-content-wrapper {
flex-grow: 2;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
flex-wrap: nowrap;
overflow-y: auto;
margin-left: 1vw;
margin-right: 1vw;
}
.document-content {
font-size: var(--fontSize-default);
line-height: var(--lineHeight-default);
width: fit-content;
padding-bottom: var(--spacing-exp-7);
}
.document-content > * + * {
margin-top: 1em;
}
.document-content h1 {
font-size: 3.8rem;
width: fit-content;
}
/* Section headers within document */
.document-content h2 {
margin-top: var(--spacing-exp-6);
margin-bottom: var(--spacing-2);
font-size: 2.4rem;
}
.document-content h3 {
margin-top: var(--spacing-exp-5);
margin-bottom: var(--spacing-1);
font-size: 1.8rem;
}
.document-content h4 {
margin-top: var(--spacing-2);
margin-bottom: var(--spacing-1);
font-size: 1.3rem;
}
/* Enhanced code blocks for documentation */
.document-content pre {
margin: var(--spacing-2) 0;
background: var(--background-3);
border-radius: var(--border-radius-small);
overflow-x: auto;
max-width: 120ch;
}
.document-content pre code {
font-family: var(--fontFamily-mono);
font-size: 0.95rem;
line-height: 1.6;
}
/* Document-specific column layouts */
.document-content .column-set {
margin: var(--spacing-exp-5) 0;
}
/* Two-column code examples */
.code-comparison {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-2);
margin: var(--spacing-2) 0;
}
/* Callout boxes for notes/warnings */
.callout {
padding: var(--spacing-2);
margin: var(--spacing-2) 0;
border-left: 3px solid var(--swatch-5);
background: var(--background-3);
}
.callout.note {
border-left-color: var(--swatch-3);
}
.callout.warning {
border-left-color: var(--swatch-1);
background: rgba(255, 200, 0, 0.1);
}
/* Table of contents navigation */
.document-toc {
position: sticky;
top: var(--spacing-2);
font-size: var(--fontSize-secondary);
padding: var(--spacing-2);
background: var(--background-3);
border-radius: var(--border-radius-small);
}
.document-toc ul {
list-style: none;
padding-left: 0;
}
.document-toc li {
margin: var(--spacing-half) 0;
}
.document-toc a {
color: var(--swatch-2);
text-decoration: none;
}
.document-toc a:hover {
color: var(--swatch-1);
text-decoration: underline;
}
/* Footer navigation (prev/next) */
.document-footer {
max-width: 15%;
}
.document-footer-holder {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
height: 40vh;
}
.document-nav-link {
display: flex;
flex-direction: column;
text-decoration: none;
color: var(--swatch-2);
}
.document-nav-link:hover {
color: var(--swatch-1);
}
.document-nav-label {
font-size: var(--fontSize-small);
font-family: var(--fontFamily-mono);
color: var(--swatch-4);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: var(--spacing-half);
}
.document-nav-label-selected {
font-size: var(--fontSize-small);
font-family: var(--fontFamily-mono);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: var(--spacing-half);
color:rgb(236, 113, 42);
}
.document-nav-title {
font-size: var(--fontSize-header);
font-weight: 500;
}
/* Responsive adjustments */
@media (max-width: 1024px) {
:root {
--document-margin-x: 60px;
}
}
@media (max-width: 768px) {
:root {
--document-margin-x: var(--spacing-2);
}
.document-title {
font-size: 2.5rem;
}
.code-comparison {
grid-template-columns: 1fr;
}
.document-footer {
flex-direction: column;
gap: var(--spacing-2);
}
}

View File

@ -1,23 +0,0 @@
article {
max-width: 800px;
margin: 0 auto;
}
article h1,
article h2,
article h3,
article h4,
article h5,
article h6 {
margin-top: 1.5rem;
}
article p {
margin: 1rem 0;
}
article code {
background: var(--code-background-color);
padding: 0.2rem 0.4rem;
border-radius: 3px;
}

View File

@ -0,0 +1,266 @@
/* Document Layout - For documentation/blog post pages */
/* Based on reference_post.html design */
:root {
/* Document-specific spacing */
--document-margin-x: 120px;
--document-max-width: 1200px;
}
/* Main content wrapper for document pages */
.document-layout {
/* max-width: var(--document-max-width); */
margin: 0 auto;
padding: var(--spacing-exp-7) var(--document-margin-x);
box-sizing: border-box;
}
.document {
display: flex;
flex-direction: row;
height: 100vh;
}
.document-header-holder {
display: flex;
height: 70vh;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
/* text-align: end; */
}
/* Document header with metadata */
.document-header {
margin-bottom: var(--spacing-exp-6);
max-width: 15%;
}
.document-metadata {
font-family: var(--fontFamily-mono);
font-size: var(--fontSize-small);
color: var(--swatch-4);
margin-bottom: var(--spacing-2);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.document-title {
font-size: 3.8rem;
font-weight: 420;
line-height: 1;
margin: var(--spacing-2) 0;
letter-spacing: -0.02em;
}
.document-subtitle {
font-size: 1.6rem;
font-weight: 500;
color: var(--swatch-2);
line-height: var(--lineHeight-default);
margin-top: var(--spacing-1);
}
/* Document content area */
.document-quick-tips {
font-size: 1rem;
font-weight: 500;
color: var(--swatch-2);
line-height: var(--lineHeight-default);
margin-top: var(--spacing-1);
}
.document-content-wrapper {
flex-grow: 2;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
flex-wrap: nowrap;
overflow-y: auto;
margin-left: 1vw;
margin-right: 1vw;
}
.document-content {
font-size: var(--fontSize-default);
line-height: var(--lineHeight-default);
width: fit-content;
padding-bottom: var(--spacing-exp-7);
}
.document-content > * + * {
margin-top: 1em;
}
.document-content h1 {
font-size: 3.8rem;
width: fit-content;
}
/* Section headers within document */
.document-content h2 {
margin-top: var(--spacing-exp-6);
margin-bottom: var(--spacing-2);
font-size: 2.4rem;
}
.document-content h3 {
margin-top: var(--spacing-exp-5);
margin-bottom: var(--spacing-1);
font-size: 1.8rem;
}
.document-content h4 {
margin-top: var(--spacing-2);
margin-bottom: var(--spacing-1);
font-size: 1.3rem;
}
/* Enhanced code blocks for documentation */
.document-content pre {
margin: var(--spacing-2) 0;
background: var(--background-3);
border-radius: var(--border-radius-small);
overflow-x: auto;
max-width: 120ch;
}
.document-content pre code {
font-family: var(--fontFamily-mono);
font-size: 0.95rem;
line-height: 1.6;
}
/* Document-specific column layouts */
.document-content .column-set {
margin: var(--spacing-exp-5) 0;
}
/* Two-column code examples */
.code-comparison {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-2);
margin: var(--spacing-2) 0;
}
/* Callout boxes for notes/warnings */
.callout {
padding: var(--spacing-2);
margin: var(--spacing-2) 0;
border-left: 3px solid var(--swatch-5);
background: var(--background-3);
}
.callout.note {
border-left-color: var(--swatch-3);
}
.callout.warning {
border-left-color: var(--swatch-1);
background: rgba(255, 200, 0, 0.1);
}
/* Table of contents navigation */
.document-toc {
position: sticky;
top: var(--spacing-2);
font-size: var(--fontSize-secondary);
padding: var(--spacing-2);
background: var(--background-3);
border-radius: var(--border-radius-small);
}
.document-toc ul {
list-style: none;
padding-left: 0;
}
.document-toc li {
margin: var(--spacing-half) 0;
}
.document-toc a {
color: var(--swatch-2);
text-decoration: none;
}
.document-toc a:hover {
color: var(--swatch-1);
text-decoration: underline;
}
/* Footer navigation (prev/next) */
.document-footer {
max-width: 15%;
}
.document-footer-holder {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
height: 40vh;
}
.document-nav-link {
display: flex;
flex-direction: column;
text-decoration: none;
color: var(--swatch-2);
}
.document-nav-link:hover {
color: var(--swatch-1);
}
.document-nav-label {
font-size: var(--fontSize-small);
font-family: var(--fontFamily-mono);
color: var(--swatch-4);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: var(--spacing-half);
}
.document-nav-label-selected {
font-size: var(--fontSize-small);
font-family: var(--fontFamily-mono);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: var(--spacing-half);
color:rgb(236, 113, 42);
}
.document-nav-title {
font-size: var(--fontSize-header);
font-weight: 500;
}
/* Responsive adjustments */
@media (max-width: 1024px) {
:root {
--document-margin-x: 60px;
}
}
@media (max-width: 768px) {
:root {
--document-margin-x: var(--spacing-2);
}
.document-title {
font-size: 2.5rem;
}
.code-comparison {
grid-template-columns: 1fr;
}
.document-footer {
flex-direction: column;
gap: var(--spacing-2);
}
}

View File

@ -1,280 +1,263 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
/* Foldsite Documentation Base Styles */
/* Design system extracted from reference designs */
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
:root {
/* Typography Scale */
--fontSize-default: 14.5px;
--fontSize-small: 12px;
--fontSize-secondary: 13.5px;
--fontSize-header: 17px;
--fontSize-large: 22px;
--lineHeight-default: 1.65;
/* Color Swatches - Primary opacity-based system */
--swatch-1: rgba(0, 0, 0, 0.85);
--swatch-2: rgba(0, 0, 0, 0.75);
--swatch-3: rgba(0, 0, 0, 0.6);
--swatch-4: rgba(0, 0, 0, 0.4);
--swatch-5: rgba(0, 0, 0, 0.25);
--swatch-6: rgba(0, 0, 0, 0.15);
/* Base Colors */
--color-default: rgba(0, 0, 0, 0.75);
--color-default-secondary: rgba(0, 0, 0, 0.4);
--background-1: #FAF9F6;
--background-2: #ffffff;
--background-3: #fcfcfc;
--background-force-dark: #111111;
/* Spacing System - Consistent scale */
--spacing-1: 15px;
--spacing-half: calc(15px * 0.5);
--spacing-2: calc(15px * 2);
--spacing-3: calc(15px * 3);
--spacing-4: calc(15px * 4);
/* Exponential spacing for larger gaps */
--spacing-exp-1: 5px;
--spacing-exp-half: calc(5px * 0.5);
--spacing-exp-2: calc(5px * 2);
--spacing-exp-3: calc(5px * 3);
--spacing-exp-4: calc(5px * 5);
--spacing-exp-5: calc(5px * 8);
--spacing-exp-6: calc(5px * 13);
--spacing-exp-7: calc(5px * 21);
--spacing-exp-8: calc(5px * 34);
/* Typography */
--fontFamily-default: 'Lekton', monospace;
--fontFamily-mono: "Lekton", monospace;
--fontFamily-display: 'Lekton', monospace;
--fontFamily-serif: 'Lekton', monospace;
/* UI Elements */
--border-radius-small: 5px;
--opacity-downstate-default: 0.7;
--ui-border: rgba(0, 0, 0, 0.14);
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
/* Reset */
*, *::before, *::after {
box-sizing: border-box;
}
.doto-500 {
font-family: "Doto", sans-serif;
font-optical-sizing: auto;
font-weight: 500;
font-style: normal;
font-variation-settings:
"ROND" 0;
}
.lekton-regular {
font-family: "Lekton", monospace;
font-weight: 400;
font-style: normal;
}
.lekton-bold {
font-family: "Lekton", monospace;
font-weight: 700;
font-style: normal;
}
.lekton-regular-italic {
font-family: "Lekton", monospace;
font-weight: 400;
font-style: italic;
}
html {
overflow-anchor: none;
text-size-adjust: 100%;
}
body {
margin: 0;
padding: 0;
font-family: var(--fontFamily-default);
font-size: var(--fontSize-default);
line-height: var(--lineHeight-default);
color: var(--color-default);
background-color: var(--background-1);
text-rendering: optimizelegibility;
-webkit-font-smoothing: antialiased;
height: 100vh;
overflow: hidden;
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
margin: 0;
font-weight: 500;
line-height: 1.2;
color: var(--swatch-1);
font-family: var(--fontFamily-display);
}
h1 {
font-size: 3.8rem;
letter-spacing: -0.02em;
font-weight: 420;
line-height: 1;
}
ol,
ul {
list-style: none;
h2 {
font-size: 2.4rem;
letter-spacing: -0.01em;
}
blockquote,
q {
quotes: none;
h3 {
font-size: 1.8rem;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
h4 {
font-size: 1.3rem;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
@property --font-color {
syntax: "<color>";
inherits: true;
initial-value: oklch(25.11% 0.006 258.36);
}
@property --background-color {
syntax: "<color>";
inherits: true;
initial-value: #F6F0F0;
}
@property --code-background-color {
syntax: "<color>";
inherits: true;
initial-value: #c7c1c1;
}
@property --hover-color {
syntax: "<color>";
inherits: true;
initial-value: #A4B465;
}
@property --url-color {
syntax: "<color>";
inherits: true;
initial-value: #626F47;
}
@media (prefers-color-scheme: dark) {
:root {
--font-color: oklch(91.87% 0.003 264.54);
--background-color: #29261f;
--hover-color: #626F47;
--url-color: #A4B465;
--code-background-color: #3d392e;
}
}
body {
font-family: "Open Sans", sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
font-variation-settings:
"wdth" 100;
display: flex;
justify-content: center;
background-color: var(--background-color);
color: var(--font-color);
p {
margin: 0 0 1em 0;
}
a {
color: var(--url-color);
text-decoration: none;
transition: all 0.25s ease-in-out;
color: var(--swatch-1);
text-decoration: underline;
text-decoration-color: var(--swatch-5);
transition: text-decoration-color 0.2s;
}
a:hover {
color: var(--hover-color);
transition: all 0.25s ease-in-out;
text-decoration-color: var(--swatch-1);
}
a:visited {
color: #C14600;
transition: all 0.25s ease-in-out;
/* Code */
code {
font-family: 'Menlo', 'Monaco', var(--fontFamily-mono);
font-size: 0.9em;
background: var(--background-3);
padding: 0.2em 0.4em;
border-radius: 3px;
}
a:visited:hover {
color: var(--hover-color);
transition: all 0.25s ease-in-out;
pre {
background: var(--background-3);
border-radius: var(--border-radius-small);
overflow-x: auto;
line-height: 1.5;
}
@supports (font-size-adjust: 1) {
.content {
font-size-adjust: 0.5;
}
pre code {
background: none;
padding: 0;
}
ul {
list-style: square;
/* Horizontal Rules */
hr {
border: 0;
height: 1px;
background: var(--ui-border);
margin: var(--spacing-exp-4) 0;
}
/* Lists */
ul, ol {
margin: 0 0 1em 0;
padding-left: 2em;
}
li {
line-height: 160%;
margin-bottom: 0.5rem;
margin: 0.5em 0;
}
.content {
line-height: calc(1ex / 0.32);
text-rendering: optimizeLegibility;
max-width: 80ch;
padding-left: 1rem;
/* Images */
img {
max-width: 100%;
height: auto;
}
.content h1 {
font-size: 2.5em;
line-height: calc(1ex / 0.42);
margin: calc(1ex / 0.42) 0;
/* Blockquotes */
blockquote {
margin: var(--spacing-2) 0;
padding-left: var(--spacing-2);
border-left: 3px solid var(--swatch-5);
color: var(--swatch-3);
font-style: italic;
}
.content h2 {
font-size: 2em;
line-height: calc(1ex / 0.42);
margin: calc(1ex / 0.42) 0;
/* Tables */
table {
border-collapse: collapse;
width: 100%;
margin: var(--spacing-2) 0;
}
.content h3 {
font-size: 1.75em;
line-height: calc(1ex / 0.38);
margin: calc(1ex / 0.38) 0;
th, td {
padding: var(--spacing-half) var(--spacing-1);
text-align: left;
border-bottom: 1px solid var(--ui-border);
}
.content h4 {
font-size: 1.5em;
line-height: calc(1ex / 0.37);
margin: calc(1ex / 0.37) 0;
th {
font-weight: 600;
color: var(--swatch-1);
}
.content p {
font-size: 1em;
line-height: calc(1ex / 0.32);
margin: calc(1ex / 0.32) 0;
text-align: justify;
hyphens: auto;
/* Utilities */
.mono {
font-family: var(--fontFamily-mono);
font-size: 0.95rem;
}
.sidebar {
padding-top: 4rem;
line-height: calc(1ex / 0.32);
text-rendering: optimizeLegibility;
.secondary {
color: var(--color-default-secondary);
}
.holder {
display: flex;
flex-direction: row;
margin: auto;
}
.small {
font-size: var(--fontSize-small);
}
.text-center {
text-align: center;
}
/* Column System - Mimics reference design */
.column-set {
display: grid;
gap: var(--spacing-2);
margin: var(--spacing-2) 0;
}
.column-set[data-columns="2"] {
grid-template-columns: repeat(2, 1fr);
}
.column-set[data-columns="3"] {
grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 768px) {
.column-set {
grid-template-columns: 1fr;
}
}

96
docs/templates/__file.document.html vendored Normal file
View File

@ -0,0 +1,96 @@
<div class="document-layout">
<article class="document">
<header class="document-header">
<hr>
<div class="document-header-holder">
{% if metadata and metadata.date %}
<div class="document-metadata">
{{ metadata.date }}
{% if metadata.author %} — {{ metadata.author }}{% endif %}
</div>
{% endif %}
<div>
<div class="document-file-nav-holder">
{% set siblings = get_sibling_content_files(currentPath) %}
{% if siblings and siblings|length > 1 %}
{% set current_index = namespace(value=-1) %}
{% for sibling in siblings %}
{% if sibling[1] == currentPath %}
<a href="/{{ sibling[1] }}" class="document-nav-link">
<span class="document-nav-label-selected">🖹 {{ sibling[0] }}</span>
</a>
{% else %}
<a href="/{{ sibling[1] }}" class="document-nav-link">
<span class="document-nav-label">🖹 {{ sibling[0] }}</span>
</a>
{% endif %}
{% endfor %}
{% endif %}
</div>
<div class="document-folder-nav-holder">
{% set siblingsfolders = get_sibling_content_folders(currentPath) %}
{% if siblingsfolders and siblingsfolders|length > 1 %}
{% set current_index = namespace(value=-1) %}
{% for siblingf in siblingsfolders %}
{% if siblingf.path == currentPath %}
{% set current_index.value = loop.index0 %}
{% endif %}
<a href="/{{ siblingf[1] }}" class="document-nav-link">
<span class="document-nav-label">🗀 {{ siblingf[0] }}</span>
</a>
{% endfor %}
{% endif %}
</div>
</div>
</div>
</header>
<div class="document-content-wrapper">
<div class="document-content">
<hr>
{{ content|safe }}
</div>
</div>
{% if metadata and metadata.tags %}
<footer class="document-footer">
<div class="document-tags">
<span class="mono small secondary">Tagged: </span>
{% for tag in metadata.tags %}
<a href="/tags/{{ tag|lower }}" class="mono small">{{ tag }}</a>{% if not loop.last %}, {% endif %}
{% endfor %}
</div>
</footer>
{% endif %}
<div class="document-footer">
<hr>
<div class="document-footer-holder">
{% if metadata and metadata.summary %}
<p class="document-subtitle">{{ metadata.summary }}</p>
{% endif %}
{% if metadata and metadata.quick_tips %}
<div class="document-quick-tips">
<h4>Quick Tips:</h4>
<hr>
<ul>
{% for tip in metadata.quick_tips %}
<li>{{ tip }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
</div>
</article>
</div>

View File

@ -1,7 +0,0 @@
<article>
{{ content|safe }}
</article>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/dockerfile.min.js"></script>
<script>hljs.highlightAll();</script>

120
docs/templates/__folder.html vendored Normal file
View File

@ -0,0 +1,120 @@
<div class="document-layout">
{% set index_path = (currentPath + '/index.md') if currentPath else 'index.md' %}
{% set index = get_rendered_markdown(index_path) %}
<article class="document">
<header class="document-header">
<hr>
<div class="document-header-holder">
{% if index.exists %}
{% if index.metadata and index.metadata.date %}
<div class="document-metadata">
{{ index.metadata.date }}
{% if index.metadata.author %} — {{ index.metadata.author }}{% endif %}
</div>
{% endif %}
{% endif %}
<div>
<div class="document-file-nav-holder">
{% set siblings = get_sibling_content_files(currentPath) %}
{% if siblings and siblings|length > 1 %}
{% set current_index = namespace(value=-1) %}
{% for sibling in siblings %}
{% if sibling[1] == currentPath %}
<a href="/{{ sibling[1] }}" class="document-nav-link">
<span class="document-nav-label-selected">🖹 {{ sibling[0] }}</span>
</a>
{% else %}
<a href="/{{ sibling[1] }}" class="document-nav-link">
<span class="document-nav-label">🖹 {{ sibling[0] }}</span>
</a>
{% endif %}
{% endfor %}
{% endif %}
</div>
<div class="document-folder-nav-holder">
{% set siblingsfolders = get_sibling_content_folders(currentPath) %}
{% if siblingsfolders and siblingsfolders|length > 1 %}
{% for siblingf in siblingsfolders %}
{% if siblingf[1] == currentPath %}
<a href="/{{ siblingf[1] }}" class="document-nav-link">
<span class="document-nav-label-selected">🗀 {{ siblingf[0] }}</span>
</a>
{% for child in get_folder_contents(siblingf[1]) %}
{% if child.name == "index.md" %}
<a href="/{{ child.path }}" class="document-nav-link">
<span class="document-nav-label-selected">∟ 🖹 {{ child.name }}</span>
</a>
{% else %}
<a href="/{{ child.path }}" class="document-nav-link">
<span class="document-nav-label">∟ 🖹 {{ child.name }}</span>
</a>
{% endif %}
{% endfor %}
{% else %}
<a href="/{{ siblingf[1] }}" class="document-nav-link">
<span class="document-nav-label">🗀 {{ siblingf[0] }}</span>
</a>
{% endif %}
{% endfor %}
{% endif %}
</div>
</div>
</div>
</header>
<div class="document-content-wrapper">
<div class="document-content">
<hr>
{% if index.exists %}
<section class="folder-index">
{{ index.html | safe }}
<hr>
</section>
{% endif %}
</div>
</div>
{% if metadata and metadata.tags %}
<footer class="document-footer">
<div class="document-tags">
<span class="mono small secondary">Tagged: </span>
{% for tag in metadata.tags %}
<a href="/tags/{{ tag|lower }}" class="mono small">{{ tag }}</a>{% if not loop.last %}, {% endif %}
{% endfor %}
</div>
</footer>
{% endif %}
<!-- Navigation to sibling documents -->
<div class="document-footer">
<hr>
{% if index.exists %}
<div class="document-footer-holder">
{% if index.metadata and index.metadata.summary %}
<p class="document-subtitle">{{ index.metadata.summary }}</p>
{% endif %}
{% if index.metadata and index.metadata.quick_tips %}
<div class="document-quick-tips">
<h4>Quick Tips:</h4>
<hr>
<ul>
{% for tip in index.metadata.quick_tips %}
<li>{{ tip }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
{% endif %}
</div>
</article>
</div>

View File

@ -1,43 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
{% if metadata and metadata.title %}
<title>{{ metadata.title }} — Foldsite</title>
<meta name="description" content="{{ metadata.description or 'A thoughtful static site generator' }}">
{% if metadata.tags %}
<meta name="keywords" content="{{ metadata.tags | join(', ') }}">
{% endif %}
{% else %}
<title>Foldsite — Documentation</title>
<meta name="description" content="A thoughtful static site generator built with Python">
{% endif %}
<!-- Open Graph / Social Media -->
<meta property="og:type" content="website">
<meta property="og:title" content="{{ metadata.title if metadata and metadata.title else 'Foldsite' }}">
<meta property="og:description" content="{{ metadata.description if metadata and metadata.description else 'A thoughtful static site generator' }}">
<!-- Load layout-specific styles -->
{% for style in styles %}
<link rel="stylesheet" href="/styles{{ style }}">
{% endfor %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Outfit:wght@100..900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Doto:wght@100..900&family=Lekton:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='base.css') }}">
{% for style in styles %}
<link rel="stylesheet" href="/styles{{ style }}" type="text/css">
{% endfor %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/django.min.js"></script>
{% block extra_styles %}{% endblock %}
</head>
<body>
<div class="holder">
<div class="sidebar"> <!-- Changed <sidebar> to <div> -->
<ul>
<li><a href="/">⌂ Home</a></li>
<hr>
{% for f in get_folder_contents() %}
{% if not f.is_dir %}
{% if f.proper_name == "index" %}
{% else %}
<li><a href="/{{ f.path }}">{{ f.proper_name }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
</ul>
</div>
<div class="content"> <!-- <main> tag remains the same -->
{{ content|safe }}
<div class="footer">
<p>&copy; DWS</p>
</div>
</div>
</div>
</body>
{{ content|safe }}
</html>
<script>hljs.highlightAll();</script>
</body>
</html>