# QuillDrop

**QuillDrop** ist ein modernes, minimalistisches Blog-CMS, geschrieben in Go. Es kombiniert die Geschwindigkeit eines Static Site Generators mit der Flexibilität eines dynamischen HTTP-Servers - ohne externe Datenbank, ohne JavaScript-Frameworks, ohne Overhead.

## Philosophie

> Write. Save. Published.

QuillDrop folgt dem Prinzip der maximalen Einfachheit: Markdown-Dateien schreiben, speichern - fertig. Kein Build-Tool-Chaos, kein Node.js, keine Datenbank. Ein einzelnes Go-Binary erledigt alles.

## Features

### Dual-Mode Betrieb

QuillDrop unterstützt zwei Betriebsmodi in einem einzigen Binary:

- **`quilldrop serve`** - Startet einen dynamischen HTTP-Server für lokale Entwicklung und Vorschau. Ideal zum Schreiben und sofortigen Testen neuer Posts.
- **`quilldrop generate`** - Generiert eine komplette statische Website als HTML-Dateien. Perfekt für Deployment auf Nginx, Apache, CDN oder GitHub Pages.

### Markdown mit YAML-Frontmatter

Posts und Seiten werden als einfache Markdown-Dateien mit YAML-Frontmatter geschrieben:

```yaml
---
title: "Mein neuer Blogpost"
date: 2025-11-06 12:00:00
author: "Max Mustermann"
cover: "/images/posts/2025/11/cover.webp"
tags: [Kubernetes, DevOps, Self-Hosted]
categories: [Technik]
preview: "Kurze Vorschau des Posts..."
draft: false
toc: true
---

# Hier beginnt der Post

Normales Markdown mit allen Extras...
```

Unterstützte Frontmatter-Felder:

| Feld | Beschreibung |
|------|-------------|
| `title` | Titel des Posts |
| `date` | Veröffentlichungsdatum (mehrere Formate unterstützt) |
| `update` | Letzte Aktualisierung |
| `author` | Autor des Posts |
| `cover` / `featureImage` | Cover-Bild (mit Fallback) |
| `tags` | Liste von Tags |
| `categories` | Liste von Kategorien |
| `preview` | Benutzerdefinierte Vorschau (sonst automatisch aus erstem Absatz) |
| `draft` | Entwurf - wird nicht veröffentlicht |
| `toc` | Inhaltsverzeichnis automatisch generieren |
| `hide` | Post verstecken |
| `top` | Post oben anpinnen |

### Erweitertes Markdown-Rendering

QuillDrop nutzt [Goldmark](https://github.com/yuin/goldmark) als Markdown-Engine mit folgenden Erweiterungen:

- **GitHub Flavored Markdown (GFM)** - Tabellen, Strikethrough, Autolinks, Task-Listen
- **Syntax Highlighting** - Über 200 Programmiersprachen mit dem Dracula-Theme via [Chroma](https://github.com/alecthomas/chroma)
- **Emoji-Support** - Shortcodes wie `:rocket:`, `:tada:`, `:satellite:`
- **Automatische Heading-IDs** - Für Ankerverlinkung und Inhaltsverzeichnis
- **Raw HTML** - Einbettung von HTML direkt im Markdown
- **Hugo-Kompatibilität** - `{{</* rawhtml */>}}` Shortcodes werden automatisch verarbeitet

### Responsives Design mit Dark/Light Theme

Das mitgelieferte Theme bietet:

- **Dark Mode als Default** mit einem hellen Alternativ-Theme
- **Theme Toggle** mit localStorage-Persistenz (bleibt nach Reload erhalten)
- **Futuristisches Design** - Dunkle Hintergrunde, Cyan-Akzente, subtile Glow-Effekte
- **Responsive Layout** - Mobile-first, optimiert für alle Bildschirmgrößen
- **Hamburger-Navigation** auf mobilen Geräten mit Fullscreen-Overlay und eigenem Stacking Context
- **Dropdown-Menus** für verschachtelte Navigation (Touch-optimiert auf Mobile)
- **Integrierte Suche** — Lupe in der Navbar mit Ctrl+K Shortcut
- **Typographie** - Inter als Textfont, JetBrains Mono für Code und Metadaten

### Navigation und Menü

Das Navigationsmenü wird vollständig über die `config.yaml` konfiguriert und unterstützt verschachtelte Dropdown-Menüs:

```yaml
menu:
  - label: "Home"
    url: "/"
  - label: "Projekte"
    children:
      - label: "VM-Manager"
        url: "/sites/projekte/vm-manager"
      - label: "VM-Tracker"
        url: "/sites/projekte/vm-tracker"
      - label: "QuillDrop"
        url: "/sites/projekte/quilldrop"
  - label: "Über mich"
    url: "/sites/ueber-mich"
  - label: "Tags"
    url: "/tags"
```

Neue Menüpunkte und Untermenüs können jederzeit durch einfaches Erweitern der YAML-Konfiguration hinzugefügt werden.

### Pagination

Die Startseite zeigt eine konfigurierbare Anzahl von Posts pro Seite (Standard: 5). Die Pagination bietet:

- **Intelligente Seitennummerierung** - Zeigt erste und letzte Seite, plus ein Fenster um die aktuelle Seite herum
- **Ellipsis** bei vielen Seiten (1 ... 10 11 **12** 13 14 ... 23)
- **Neuere/Ältere Buttons** für schnelle Navigation
- **Pretty URLs** - `/page/2`, `/page/3`, etc.
- SEO-freundlich: `/page/1` wird automatisch auf `/` umgeleitet (301)

### Tags und Kategorien

QuillDrop unterstützt sowohl Tags als auch Kategorien zur Strukturierung von Inhalten:

- **Tag-Übersicht** unter `/tags/` mit Anzahl der Posts pro Tag
- **Tag-Seiten** unter `/tags/kubernetes/` mit allen Posts eines Tags
- **Kategorie-Übersicht** unter `/categories/` mit Anzahl der Posts pro Kategorie
- **Kategorie-Seiten** unter `/categories/technik/` mit allen Posts einer Kategorie
- **Tag- und Kategorie-Badges** auf Post-Cards und Einzelseiten
- Tags und Kategorien werden aus dem YAML-Frontmatter (`tags`, `categories`) ausgelesen

### Volltextsuche

QuillDrop enthält eine integrierte Client-seitige Suche, die komplett ohne Backend auskommt:

- **Suchindex** — Beim Generieren wird eine `search-index.json` mit allen Posts erstellt
- **Lazy Loading** — Der Suchindex wird erst beim ersten Öffnen der Suche geladen
- **Multi-Term-Suche** — Mehrere Suchbegriffe werden mit UND verknüpft
- **Felder** — Durchsucht Titel, Vorschau, Tags und Kategorien
- **Keyboard-Shortcut** — `Ctrl+K` / `Cmd+K` öffnet die Suche
- **Lupe in der Navbar** — Klick auf das Such-Icon öffnet das Suchfeld
- **Debounce** — Suchergebnisse erscheinen nach 200ms Tippverzögerung
- **Maximal 8 Treffer** mit Highlighting der Suchbegriffe
- **Escape** oder Klick außerhalb schließt die Suche
- Kein externer Dienst, kein Framework — reines Vanilla JavaScript

### Artikel-Navigation

Am Ende jedes Blog-Posts wird eine Navigation zum vorherigen und nächsten Artikel angezeigt:

- **Neuerer Artikel** (← links) — Verlinkt zum chronologisch neueren Post
- **Älterer Artikel** (→ rechts) — Verlinkt zum chronologisch älteren Post
- Beim neuesten Artikel wird nur "Älterer Artikel" angezeigt
- Beim ältesten Artikel wird nur "Neuerer Artikel" angezeigt
- Zeigt jeweils den Titel des verlinkten Artikels an

### Inhaltsverzeichnis (Table of Contents)

Posts können ein automatisch generiertes Inhaltsverzeichnis aktivieren:

- Aktivierung über `toc: true` im Frontmatter
- Unterstützt **H1, H2 und H3** Überschriften
- **Relative Einrückung** — Das TOC erkennt die minimale Heading-Ebene und rückt relativ dazu ein
- Automatische Anchor-Links zu den jeweiligen Überschriften
- Wird client-seitig generiert für schnelle Seitenladezeit

### Statische Seiten

Neben Blog-Posts unterstützt QuillDrop statische Seiten für:

- Impressum, Datenschutzerklärung
- Über mich / About
- Projektseiten (mit Unterseiten)
- Beliebige weitere Seiten

Seiten werden als Markdown-Dateien im `sites/`-Verzeichnis abgelegt. Verschachtelte Verzeichnisse werden automatisch erkannt - z.B. wird `sites/projekte/vm-tracker/index.md` unter `/sites/projekte/vm-tracker` erreichbar.

### RSS Feed

Automatisch generierter RSS 2.0 Feed unter `/index.xml` mit:

- Den letzten 20 Posts
- Titel, Link, Vorschau und Veröffentlichungsdatum
- RSS-Autodiscovery im HTML-Head
- RSS-Icon in der Navigation
- URL `/index.xml` für Kompatibilität mit bestehenden Blog-Setups

### Sitemap

Parallel zum RSS-Feed wird eine `sitemap.xml` nach dem [sitemaps.org-Protokoll 0.9](https://www.sitemaps.org/protocol.html) erzeugt - unter `/sitemap.xml` sowohl im dynamischen Server als auch in der statischen Generierung. Enthalten sind:

- Startseite und alle Pagination-Seiten (`/page/2/`, ...)
- Alle Blog-Posts (`lastmod` aus `update`, sonst `date`)
- Alle statischen Seiten aus `sites/`
- Tag- und Kategorie-Übersichten sowie alle einzelnen Tag-/Kategorie-Seiten

Die Sitemap lässt sich in der Config an- und abschalten (Default: aktiv):

```yaml
sitemap:
  enabled: true   # false schaltet /sitemap.xml komplett ab
```

### robots.txt

Ebenfalls automatisch erzeugt: eine `robots.txt` unter `/robots.txt`. Sie verweist automatisch auf die Sitemap, sobald diese aktiviert ist, und lässt sich in der Config konfigurieren (Default: aktiv):

```yaml
robots:
  enabled: true
  userAgent: "*"        # optional, Default "*"
  disallow:             # optional
    - /search-index.json
  allow:                # optional
    - /
```

Ohne eigene `allow`/`disallow`-Regeln wird `Allow: /` geschrieben, d.h. die ganze Seite ist freigegeben:

```
User-agent: *
Allow: /

Sitemap: https://blog.kuepper.nrw/sitemap.xml
```

Ist `sitemap.enabled: false` oder `baseURL` leer, entfällt die `Sitemap:`-Zeile.

### Cover-Bilder

Posts können ein Cover-Bild definieren, das sowohl auf der Startseite (als Post-Card) als auch auf der Einzelansicht angezeigt wird:

- **21:9 Aspect Ratio** auf Post-Cards mit Zoom-on-Hover Effekt
- **Volle Breite** auf der Einzelpost-Seite
- **Lazy Loading** für optimale Performance
- **Fallback** von `cover` auf `featureImage`

## Architektur

### Projektstruktur

```
quilldrop/
├── main.go                          # CLI Entry Point
├── config.yaml                      # Konfiguration
├── content/                         # Blog-Posts (Markdown)
│   ├── 2025-11-06-mein-post.md
│   └── ...
├── sites/                           # Statische Seiten
│   ├── ueber-mich.md
│   ├── impressum.md
│   └── projekte/
│       └── mein-projekt/
│           └── index.md
├── static/                          # Seiten-Assets (Icons, Bilder, ...)
│   ├── icons/
│   └── images/
├── themes/                          # Themes — ein Verzeichnis je Theme
│   └── default/
│       ├── templates/
│       │   ├── base.html            # Base Layout + Navbar + Suche
│       │   ├── home.html            # Homepage + Pagination
│       │   ├── post.html            # Einzelner Post + Prev/Next Navigation
│       │   ├── page.html            # Statische Seite
│       │   ├── tags.html            # Tag-Übersicht
│       │   ├── tag.html             # Tag-Seite
│       │   ├── categories.html      # Kategorie-Übersicht
│       │   └── category.html        # Kategorie-Seite
│       └── static/                  # Theme-Assets (werden nach /static/ gemerged)
│           ├── css/style.css
│           └── js/
│               ├── theme.js         # Dark/Light Toggle + TOC Generator
│               └── search.js        # Client-seitige Volltextsuche
├── internal/
│   ├── config/config.go             # YAML Config Loader
│   ├── content/
│   │   ├── post.go                  # Post Struct + FlexTime + Tags/Categories
│   │   ├── parser.go                # Markdown + Frontmatter Parser
│   │   └── page.go                  # Statische Seiten Parser
│   ├── server/server.go             # HTTP Server
│   ├── generator/
│   │   ├── generator.go             # Static Site Generator
│   │   ├── robots.go                # robots.txt Generator
│   │   └── search.go                # Search-Index Generator (JSON)
│   └── templates/
│       ├── render.go                # Template Engine + Functions (lädt das Theme)
│       ├── rss.go                   # RSS Feed Generator
│       └── sitemap.go               # sitemap.xml Generator
└── output/                          # Generierte statische Dateien
```

### Technologie-Stack

| Komponente | Technologie |
|-----------|-------------|
| Sprache | Go (Standard Library + minimale Dependencies) |
| HTTP Server | `net/http` (Go Standard Library) |
| Templates | `html/template`, zur Laufzeit aus `themes/<name>/templates/` geladen |
| Markdown | Goldmark + GFM + Emoji + Chroma |
| Konfiguration | YAML via `gopkg.in/yaml.v3` |
| Syntax Highlighting | Chroma (Dracula Theme) |
| Fonts | Inter + JetBrains Mono (Google Fonts) |
| CSS | Vanilla CSS mit Custom Properties |
| JavaScript | Vanilla JS — Theme Toggle, Suche, TOC (kein Framework) |

### Dependencies

QuillDrop hat bewusst minimale Abhängigkeiten - **kein Web-Framework**, **kein CSS-Framework**, **kein JS-Framework**:

- `github.com/yuin/goldmark` - Markdown Parser (CommonMark-konform)
- `github.com/yuin/goldmark-emoji` - Emoji Shortcodes
- `github.com/yuin/goldmark-highlighting/v2` - Syntax Highlighting
- `github.com/alecthomas/chroma/v2` - Syntax Highlighting Engine
- `gopkg.in/yaml.v3` - YAML Parser

### Themes

Templates und deren Assets liegen ausserhalb des Binaries in `themes/<name>/`. Dadurch
kann dasselbe QuillDrop-Binary beliebig viele Websites bedienen, jede mit eigenem Design:

```
themes/
├── default/
│   ├── templates/       # Pflicht: base, home, post, page, tags, tag, categories, category
│   └── static/          # Optional: CSS, JS, Theme-Bilder
└── newdesign/
    ├── templates/
    └── static/
```

Das aktive Theme wird in der `config.yaml` gewählt:

```yaml
themesDir: "themes"   # Default
theme: "default"      # liest aus themes/default/
```

Oder temporär per Flag, z. B. um ein Redesign zu testen:

```bash
./quilldrop serve -theme newdesign
./quilldrop generate -theme newdesign
```

**Neues Theme anlegen:**

```bash
cp -r themes/default themes/newdesign      # oder: make new-theme NAME=newdesign
# themes/newdesign/templates/*.html und themes/newdesign/static/css/style.css anpassen
./quilldrop serve -theme newdesign -dev
```

**Live-Reload:** Mit `-dev` werden die Templates bei jedem Request neu aus dem
Theme-Verzeichnis gelesen — Änderungen an HTML, CSS und JS sind nach einem
Browser-Reload sichtbar, ohne den Server neu zu starten (im Dev-Modus wird
zusätzlich `Cache-Control: no-store` gesetzt, damit der Browser nichts
zwischenspeichert). Ein fehlerhaftes Template liefert eine 500 statt einer halb
gerenderten Seite; die genaue Fehlermeldung mit Datei und Zeile steht im
Server-Log. Ohne `-dev` werden die Templates wie bisher einmal beim Start
geparst. Neue oder geänderte Markdown-Dateien erfordern weiterhin einen Neustart.

Ein Theme muss alle acht Templates enthalten (`base.html`, `home.html`, `post.html`,
`page.html`, `tags.html`, `tag.html`, `categories.html`, `category.html`);
fehlt eines oder existiert das Theme nicht, bricht QuillDrop mit einer klaren
Fehlermeldung ab.

**Statische Assets** werden gemerged: zuerst `themes/<name>/static/`, danach das
eigene `static/` der Seite — gleichnamige Dateien der Seite gewinnen also
(z. B. `static/icons/favicon.ico`). Das Design gehört ins Theme (CSS, JS),
seitenspezifisches Material bleibt in `static/` (Icons, Bilder). Beides wird unter
`/static/` ausgeliefert; `static/images/` und `static/videos/` zusätzlich unter
`/images/` und `/videos/`.

## Konfiguration

Die gesamte Konfiguration erfolgt über eine einzige `config.yaml`:

```yaml
title: "Mein Blog"
description: "Tech Blog - DevOps, Kubernetes, Self-Hosted"
author: "Max Mustermann"
baseURL: "https://mein-blog.de"
port: 8080
postsPerPage: 5
contentDir: "content"
sitesDir: "sites"
staticDir: "static"
outputDir: "output"
themesDir: "themes"
theme: "default"

menu:
  - label: "Home"
    url: "/"
  - label: "Tags"
    url: "/tags"
  - label: "Über mich"
    url: "/sites/ueber-mich"
```

| Option | Default | Beschreibung |
|--------|---------|-------------|
| `title` | - | Titel der Website |
| `description` | - | Beschreibung (Meta-Tag + Hero) |
| `author` | - | Autor der Website |
| `baseURL` | - | Basis-URL für RSS, Sitemap und absolute Links |
| `port` | `8080` | Port für den dynamischen Server |
| `postsPerPage` | `5` | Anzahl Posts pro Seite |
| `contentDir` | `content` | Verzeichnis für Blog-Posts |
| `sitesDir` | `sites` | Verzeichnis für statische Seiten |
| `staticDir` | `static` | Verzeichnis für Seiten-Assets |
| `outputDir` | `output` | Ausgabeverzeichnis für statische Generierung |
| `themesDir` | `themes` | Verzeichnis mit allen Themes |
| `theme` | `default` | Aktives Theme, gelesen aus `<themesDir>/<theme>/` (Override: `-theme`) |
| `menu` | `[]` | Navigationsmenü mit optionalen Untermenüs |
| `sitemap.enabled` | `true` | Erzeugt/serviert `/sitemap.xml` |
| `robots.enabled` | `true` | Erzeugt/serviert `/robots.txt` (mit Sitemap-Verweis) |
| `robots.userAgent` | `*` | User-agent-Zeile in der `robots.txt` |
| `robots.allow` | `[]` | Zusätzliche `Allow:`-Regeln |
| `robots.disallow` | `[]` | Zusätzliche `Disallow:`-Regeln |

## Schnellstart

### Installation

```bash
# Repository klonen
git clone https://github.com/ruedigerp/quilldrop.git
cd quilldrop

# Dependencies laden
go mod download

# Binary bauen
go build -o quilldrop .
```

### Neuen Post erstellen

Eine neue Markdown-Datei im `content/`-Verzeichnis anlegen:

```bash
touch content/2025-12-01-mein-erster-post.md
```

```markdown
---
title: "Mein erster Post"
date: 2025-12-01 10:00:00
author: "Max Mustermann"
tags: [Blog, QuillDrop]
preview: "Das ist mein erster Post mit QuillDrop!"
toc: false
---

# Willkommen

Das ist mein erster Post mit **QuillDrop**.

```

### Lokale Vorschau

```bash
# Dynamischen Server starten
./quilldrop serve

# Oder direkt mit Go
go run . serve

# Mit Live-Reload für Templates (Theme-Entwicklung)
./quilldrop serve -dev
```

Dann im Browser: [http://localhost:8080](http://localhost:8080)

### Statische Seite generieren

```bash
# HTML-Dateien generieren
./quilldrop generate

# Generierte Dateien befinden sich in output/
ls output/
```

Die generierten Dateien im `output/`-Verzeichnis können direkt auf einen Webserver (Nginx, Apache, Caddy) oder CDN deployed werden.

## URL-Schema

Alle URLs verwenden konsequent Trailing Slashes, um serverseitige Redirects zu vermeiden:

| URL | Beschreibung |
|-----|-------------|
| `/` | Startseite (letzte N Posts) |
| `/page/2/` | Seite 2 der Post-Liste |
| `/posts/2025-11-06-mein-post/` | Einzelner Blog-Post |
| `/tags/` | Tag-Übersicht |
| `/tags/kubernetes/` | Posts mit Tag "Kubernetes" |
| `/categories/` | Kategorie-Übersicht |
| `/categories/technik/` | Posts in Kategorie "Technik" |
| `/sites/ueber-mich/` | Statische Seite |
| `/sites/projekte/vm-tracker/` | Verschachtelte Projektseite |
| `/index.xml` | RSS Feed |
| `/sitemap.xml` | Sitemap für Suchmaschinen (falls aktiviert) |
| `/robots.txt` | Crawler-Regeln + Sitemap-Verweis (falls aktiviert) |
| `/search-index.json` | Suchindex (JSON) |
| `/static/css/style.css` | Statische Assets |
| `/images/posts/2025/11/cover.webp` | Bilder |

## Warum QuillDrop?

- **Keine Datenbank** - Dateisystem als einzige Datenquelle
- **Keine Build-Pipeline** - Ein `go build` und fertig
- **Keine JS-Frameworks** - Vanilla JavaScript für Theme, Suche und TOC
- **Minimale Dependencies** - 5 Go-Packages, alle fokussiert auf Markdown
- **Blitzschnell** - Generiert 100+ Posts in unter 3 Sekunden
- **Einzelnes Binary** - Templates eingebettet, kein Runtime-Setup
- **Hugo-kompatibel** - Bestehende Hugo-Posts mit Frontmatter funktionieren
- **Dual-Mode** - Entwicklung mit Server, Produktion mit Static Generator

## Lizenz

QuillDrop ist Open Source.
