docs refactor
All checks were successful
All checks were successful
This commit is contained in:
@ -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`).
|
@ -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
219
docs/content/about.md
Normal 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.**
|
735
docs/content/deployment/docker.md
Normal file
735
docs/content/deployment/docker.md
Normal 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.
|
380
docs/content/deployment/index.md
Normal file
380
docs/content/deployment/index.md
Normal 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!
|
629
docs/content/deployment/local-development.md
Normal file
629
docs/content/deployment/local-development.md
Normal 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. 🚀
|
810
docs/content/deployment/production.md
Normal file
810
docs/content/deployment/production.md
Normal 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.
|
594
docs/content/develop/index.md
Normal file
594
docs/content/develop/index.md
Normal 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!
|
441
docs/content/directory-structure.md
Normal file
441
docs/content/directory-structure.md
Normal 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
356
docs/content/explore.md
Normal 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!*
|
@ -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!
|
||||
|
682
docs/content/recipes/index.md
Normal file
682
docs/content/recipes/index.md
Normal 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>© 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!
|
770
docs/content/styles/index.md
Normal file
770
docs/content/styles/index.md
Normal 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
314
docs/content/support.md
Normal 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!
|
578
docs/content/templates/index.md
Normal file
578
docs/content/templates/index.md
Normal 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>© 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!
|
558
docs/content/templates/template-discovery.md
Normal file
558
docs/content/templates/template-discovery.md
Normal 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!
|
793
docs/content/templates/template-helpers.md
Normal file
793
docs/content/templates/template-helpers.md
Normal 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!
|
314
docs/content/theme-gallery.md
Normal file
314
docs/content/theme-gallery.md
Normal 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!*
|
266
docs/styles/__file.document.css
Normal file
266
docs/styles/__file.document.css
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
266
docs/styles/__folder.document.css
Normal file
266
docs/styles/__folder.document.css
Normal 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);
|
||||
}
|
||||
}
|
@ -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
96
docs/templates/__file.document.html
vendored
Normal 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>
|
7
docs/templates/__file.md.html
vendored
7
docs/templates/__file.md.html
vendored
@ -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
120
docs/templates/__folder.html
vendored
Normal 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>
|
65
docs/templates/base.html
vendored
65
docs/templates/base.html
vendored
@ -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>© DWS</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
{{ content|safe }}
|
||||
|
||||
</html>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Reference in New Issue
Block a user