commit 5593d825b623d13d75ef32e7c83c3ba9ef6dde43 Author: Tom Date: Wed Jul 15 20:14:07 2026 +0200 initial commit diff --git a/.claude/skills/praesentation-erstellen/SKILL.md b/.claude/skills/praesentation-erstellen/SKILL.md new file mode 100644 index 0000000..a6f2a32 --- /dev/null +++ b/.claude/skills/praesentation-erstellen/SKILL.md @@ -0,0 +1,151 @@ +--- +name: praesentation-erstellen +description: Hilft interaktiv beim Aufbau einer neuen slidewalk-Präsentation aus Markdown-Slides – von Themenklärung über Gliederung bis zu den fertigen, nummerierten .md-Dateien mit Frontmatter. Funktioniert sowohl von Grund auf (Thema im Dialog klären) als auch ausgehend von einem bestehenden Markdown-/Text-Dokument, das in ein Deck umgewandelt werden soll. Themenneutral: funktioniert für jeden Vortrag, jede Zielgruppe. Nutzen, wenn ein neues Slide-Deck für slidewalk entstehen soll (nicht für das Reviewen bestehender Decks). +--- + +# Präsentation erstellen + +Baut gemeinsam mit dem Nutzer ein neues slidewalk-Deck: ein Ordner mit +nummerierten `.md`-Dateien, die slidewalks Konventionen folgen. Das Thema ist +dabei beliebig (Business-Pitch, technischer Vortrag, Schulung, Statusupdate, +…) — dieser Skill trifft keine Annahmen über den Inhalt, sondern erfragt die +Struktur. + +Zwei Startpunkte, je nachdem was der Nutzer mitbringt: + +- **Modus A — von Grund auf**: Nutzer hat noch kein Ausgangsmaterial, nur ein + Thema im Kopf. Struktur entsteht im Dialog (Schritt 1a). +- **Modus B — aus bestehendem Dokument**: Nutzer hat schon einen Text + (Report, Notizen, README, Konzeptpapier, …) und will daraus ein Deck + ableiten. Struktur entsteht aus dem Dokument, nicht aus offenen Fragen + (Schritt 1b). + +Ab Schritt 2 (Gliederung abstimmen) laufen beide Modi gleich weiter. + +Maßgeblich für alle Markdown-/Frontmatter-Details ist +[`docs/markdown-guide.md`](../../../docs/markdown-guide.md); bei +Unsicherheit dort nachschlagen statt zu raten. Ein lauffähiges +Referenzbeispiel liegt unter [`examples/demo/`](../../../examples/demo/). + +## Ablauf + +### 1a. Rahmen klären (Modus A — von Grund auf) + +Bevor irgendetwas geschrieben wird, per Rückfrage klären (nicht alles auf +einmal abfragen, sondern nur was für die Struktur nötig ist): + +- **Thema & Kernbotschaft**: Worum geht es, was soll das Publikum am Ende + mitnehmen? +- **Zielgruppe & Anlass**: Wer sitzt im Publikum, wie viel Vorwissen ist da, + formell oder informell? +- **Ungefährer Umfang**: Wie viele Slides / wie lange soll der Vortrag + dauern? (Faustregel: 1–2 Minuten Sprechzeit pro Slide.) +- **Zielordner**: Wo sollen die Dateien liegen? Falls nicht genannt, + sinnvollen Vorschlag machen (z. B. `content/` im aktuellen Projekt) und + bestätigen lassen — nicht ungefragt einen bestehenden Ordner überschreiben. +- **Neu oder Erweiterung**: Entsteht ein komplett neues Deck, oder werden + Slides in einen bestehenden Ordner eingeschoben? Falls Erweiterung: erst + vorhandene Dateien lesen, um Nummerierungslücken und Stil zu übernehmen. + +### 1b. Gliederung aus bestehendem Dokument ableiten (Modus B) + +Statt Thema und Kernbotschaft zu erfragen, wird das Dokument komplett +gelesen und als Materialgrundlage genommen: + +- **Nicht mechanisch an Überschriften entlang splitten.** Eine `#`/`##` pro + Slide ergibt fast immer schlechte Decks: manche Abschnitte sind zu lang + für eine Slide, manche zu kurz und sollten zusammengelegt werden, und + Fließtext-Absätze müssen ohnehin zu Stichpunkten verdichtet werden. + Überschriften sind ein Anhaltspunkt für die grobe Reihenfolge, nicht die + Schnittkante. +- **Verdichten statt kopieren.** Prosa-Abschnitte in Kernaussagen/Stichpunkte + übersetzen (siehe Schritt 3, Body-Konventionen); Prozesse, Vergleiche oder + Zeitpläne, die im Dokument in Textform stehen, als Kandidaten für ein + Mermaid-Diagramm oder eine Tabelle vormerken statt sie in Prosa zu + belassen. +- **Nur das übernehmen, was im Dokument steht.** Keine Fakten ergänzen oder + interpretieren, die dort nicht stehen — bei Unklarheiten (z. B. ein Absatz + lässt sich nicht eindeutig kürzen ohne Bedeutung zu verlieren) nachfragen + statt zu raten. +- **Trotzdem kurz nachfragen, was sich nicht aus dem Dokument ergibt**: + ungefährer Zielumfang (ganzes Dokument in eine Kurzfassung pressen oder + eher 1:1 in der Tiefe abbilden?), Zielordner, neu oder Erweiterung eines + bestehenden Decks — wie in Modus A. +- Das Ergebnis dieses Schritts ist eine Gliederung (Liste von Slide-Titeln + plus grober Inhaltsangabe je Slide), die genau wie in Modus A in Schritt 2 + zur Bestätigung vorgelegt wird — noch keine fertigen Dateien. + +### 2. Gliederung vorschlagen und abstimmen + +Aus den Antworten (Modus A) bzw. aus dem Dokument (Modus B) eine Liste von +Slide-Titeln (Gliederung) ableiten und dem Nutzer **vor** dem Schreiben der +Dateien zur Bestätigung vorlegen. Typische Grobstruktur, an die Situation +anpassen (in Modus B ergibt sich diese meist schon aus dem Dokument): + +1. Titel-/Intro-Slide +2. Kontext/Problem +3. Kernteil (mehrere Slides, ein Gedanke pro Slide) +4. Beleg/Details (Daten, Vergleich, Architektur, Zeitplan — je nach Thema) +5. Zusammenfassung/Call-to-Action +6. Danke/Fragen + +Erst nach Zustimmung des Nutzers mit Schritt 3 weitermachen. Bei Unsicherheit +über den Detailgrad lieber kurz nachfragen als zu viele Slides auf Verdacht +zu erzeugen. + +### 3. Dateien anlegen + +Für jede Slide der Gliederung: + +- **Dateiname**: Lücken-Nummerierung, drei Stellen als Standard + (`010-intro.md`, `020-kontext.md`, `025-nachtrag.md`, …), damit später + Slides eingeschoben werden können, ohne umzubenennen. Bei einer + Erweiterung eines bestehenden Decks: bestehendes Nummernschema + übernehmen. +- **Frontmatter**: `title` immer setzen (informativ, auch wenn nicht + automatisch im HTML sichtbar). `class` nur setzen, wenn eine + Layout-Variante gebraucht wird, die auch in `web/assets/style.css` + existiert oder gemeinsam mit dem Nutzer ergänzt wird — nicht auf Verdacht + erfinden. `notes` nur füllen, wenn der Nutzer tatsächlich Sprechernotizen + will. `skip` nur bei explizit als Entwurf markierten Slides. `incremental: + true` setzen, wenn Stichpunkte einer Slide im Vortrag einzeln + nacheinander aufgedeckt werden sollen (Bullet-Listen als "Fragments"; + navigiert wird dann automatisch erst Fragment für Fragment, dann zum + nächsten Slide) — nicht standardmäßig auf jede Liste anwenden, sondern + nur wenn der Nutzer das so vorträgt. +- **Body**: Stichpunkte statt Fließtext, eine Kernaussage pro Slide. Tabellen + für Vergleiche/Status, Blockquotes für Zitate/Hervorhebungen, Mermaid-Codeblöcke + (`graph`, `sequenceDiagram`, `gantt`, `pie`, …) für Prozesse, Architektur + oder Zeitpläne statt sie in Prosa zu beschreiben. Codeblöcke mit + Sprachangabe (```go, ```js, …) werden automatisch Syntax-highlighted, + ohne weiteres Zutun. Bilder (`![Alt](img/datei.png)`) einfach neben die + Slide-Dateien legen (auch in Unterordnern) — sie werden von Dev-Server + und Export automatisch mitgeliefert, kein manuelles Kopieren nötig. Rohes + HTML nur einsetzen, wenn reines Markdown eine Anforderung wirklich nicht + abdeckt (z. B. `class="fragment"` auf einem einzelnen Element für ein + gezieltes Fragment außerhalb einer Liste). + +### 4. Ergebnis prüfen lassen + +Nach dem Schreiben der Dateien: + +- Vorschau anbieten: `slidewalk ` (Dev-Server mit Live-Reload). Bei + größeren Decks auf den Übersichts-/Grid-Modus hinweisen (Taste `O` im + Browser zeigt alle Slides als Miniaturansicht, Klick springt hin). +- Falls gewünscht, statischen Export anbieten: `slidewalk -build `. +- Kurz zusammenfassen, welche Dateien mit welcher Absicht entstanden sind, + damit der Nutzer gezielt Feedback geben kann (z. B. "Slide 3 ist noch zu + textlastig"). + +## Leitplanken + +- Keine Inhalte erfinden, die der Nutzer nicht genannt hat oder die nicht aus + bereitgestelltem Material (Notizen, bestehende Dokumente) stammen — bei + fehlenden Fakten nachfragen statt zu raten. In Modus B gilt das + besonders für das Verdichten: kürzen und in Stichpunkte übersetzen ist + erlaubt und gewollt, inhaltlich umdeuten oder ergänzen nicht. +- Immer die im Projekt vorhandenen Konventionen respektieren: Lücken-Nummerierung, + Frontmatter-Felder aus `docs/markdown-guide.md`, vorhandene `class`-Werte in + `web/assets/style.css` wiederverwenden statt neue zu erfinden. +- Zielordner nicht ungefragt leeren oder überschreiben; bei Konflikten mit + bestehenden Dateien nachfragen. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51da456 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# OS +.DS_Store +Thumbs.db + +# Build-Artefakte (go build -o slidewalk, Cross-Compile- und -build-Exports +# landen laut README typischerweise in ./dist) +/slidewalk +/dist/ + +# Editoren/IDEs +.vscode/ +.idea/ +*.swp +*.swo + +# Go test/coverage +*.test +*.out +coverage.html + +# lokale Umgebungsvariablen, falls mal welche dazukommen +.env +.env.local diff --git a/.opencode/skills/praesentation-erstellen/SKILL.md b/.opencode/skills/praesentation-erstellen/SKILL.md new file mode 100644 index 0000000..a6f2a32 --- /dev/null +++ b/.opencode/skills/praesentation-erstellen/SKILL.md @@ -0,0 +1,151 @@ +--- +name: praesentation-erstellen +description: Hilft interaktiv beim Aufbau einer neuen slidewalk-Präsentation aus Markdown-Slides – von Themenklärung über Gliederung bis zu den fertigen, nummerierten .md-Dateien mit Frontmatter. Funktioniert sowohl von Grund auf (Thema im Dialog klären) als auch ausgehend von einem bestehenden Markdown-/Text-Dokument, das in ein Deck umgewandelt werden soll. Themenneutral: funktioniert für jeden Vortrag, jede Zielgruppe. Nutzen, wenn ein neues Slide-Deck für slidewalk entstehen soll (nicht für das Reviewen bestehender Decks). +--- + +# Präsentation erstellen + +Baut gemeinsam mit dem Nutzer ein neues slidewalk-Deck: ein Ordner mit +nummerierten `.md`-Dateien, die slidewalks Konventionen folgen. Das Thema ist +dabei beliebig (Business-Pitch, technischer Vortrag, Schulung, Statusupdate, +…) — dieser Skill trifft keine Annahmen über den Inhalt, sondern erfragt die +Struktur. + +Zwei Startpunkte, je nachdem was der Nutzer mitbringt: + +- **Modus A — von Grund auf**: Nutzer hat noch kein Ausgangsmaterial, nur ein + Thema im Kopf. Struktur entsteht im Dialog (Schritt 1a). +- **Modus B — aus bestehendem Dokument**: Nutzer hat schon einen Text + (Report, Notizen, README, Konzeptpapier, …) und will daraus ein Deck + ableiten. Struktur entsteht aus dem Dokument, nicht aus offenen Fragen + (Schritt 1b). + +Ab Schritt 2 (Gliederung abstimmen) laufen beide Modi gleich weiter. + +Maßgeblich für alle Markdown-/Frontmatter-Details ist +[`docs/markdown-guide.md`](../../../docs/markdown-guide.md); bei +Unsicherheit dort nachschlagen statt zu raten. Ein lauffähiges +Referenzbeispiel liegt unter [`examples/demo/`](../../../examples/demo/). + +## Ablauf + +### 1a. Rahmen klären (Modus A — von Grund auf) + +Bevor irgendetwas geschrieben wird, per Rückfrage klären (nicht alles auf +einmal abfragen, sondern nur was für die Struktur nötig ist): + +- **Thema & Kernbotschaft**: Worum geht es, was soll das Publikum am Ende + mitnehmen? +- **Zielgruppe & Anlass**: Wer sitzt im Publikum, wie viel Vorwissen ist da, + formell oder informell? +- **Ungefährer Umfang**: Wie viele Slides / wie lange soll der Vortrag + dauern? (Faustregel: 1–2 Minuten Sprechzeit pro Slide.) +- **Zielordner**: Wo sollen die Dateien liegen? Falls nicht genannt, + sinnvollen Vorschlag machen (z. B. `content/` im aktuellen Projekt) und + bestätigen lassen — nicht ungefragt einen bestehenden Ordner überschreiben. +- **Neu oder Erweiterung**: Entsteht ein komplett neues Deck, oder werden + Slides in einen bestehenden Ordner eingeschoben? Falls Erweiterung: erst + vorhandene Dateien lesen, um Nummerierungslücken und Stil zu übernehmen. + +### 1b. Gliederung aus bestehendem Dokument ableiten (Modus B) + +Statt Thema und Kernbotschaft zu erfragen, wird das Dokument komplett +gelesen und als Materialgrundlage genommen: + +- **Nicht mechanisch an Überschriften entlang splitten.** Eine `#`/`##` pro + Slide ergibt fast immer schlechte Decks: manche Abschnitte sind zu lang + für eine Slide, manche zu kurz und sollten zusammengelegt werden, und + Fließtext-Absätze müssen ohnehin zu Stichpunkten verdichtet werden. + Überschriften sind ein Anhaltspunkt für die grobe Reihenfolge, nicht die + Schnittkante. +- **Verdichten statt kopieren.** Prosa-Abschnitte in Kernaussagen/Stichpunkte + übersetzen (siehe Schritt 3, Body-Konventionen); Prozesse, Vergleiche oder + Zeitpläne, die im Dokument in Textform stehen, als Kandidaten für ein + Mermaid-Diagramm oder eine Tabelle vormerken statt sie in Prosa zu + belassen. +- **Nur das übernehmen, was im Dokument steht.** Keine Fakten ergänzen oder + interpretieren, die dort nicht stehen — bei Unklarheiten (z. B. ein Absatz + lässt sich nicht eindeutig kürzen ohne Bedeutung zu verlieren) nachfragen + statt zu raten. +- **Trotzdem kurz nachfragen, was sich nicht aus dem Dokument ergibt**: + ungefährer Zielumfang (ganzes Dokument in eine Kurzfassung pressen oder + eher 1:1 in der Tiefe abbilden?), Zielordner, neu oder Erweiterung eines + bestehenden Decks — wie in Modus A. +- Das Ergebnis dieses Schritts ist eine Gliederung (Liste von Slide-Titeln + plus grober Inhaltsangabe je Slide), die genau wie in Modus A in Schritt 2 + zur Bestätigung vorgelegt wird — noch keine fertigen Dateien. + +### 2. Gliederung vorschlagen und abstimmen + +Aus den Antworten (Modus A) bzw. aus dem Dokument (Modus B) eine Liste von +Slide-Titeln (Gliederung) ableiten und dem Nutzer **vor** dem Schreiben der +Dateien zur Bestätigung vorlegen. Typische Grobstruktur, an die Situation +anpassen (in Modus B ergibt sich diese meist schon aus dem Dokument): + +1. Titel-/Intro-Slide +2. Kontext/Problem +3. Kernteil (mehrere Slides, ein Gedanke pro Slide) +4. Beleg/Details (Daten, Vergleich, Architektur, Zeitplan — je nach Thema) +5. Zusammenfassung/Call-to-Action +6. Danke/Fragen + +Erst nach Zustimmung des Nutzers mit Schritt 3 weitermachen. Bei Unsicherheit +über den Detailgrad lieber kurz nachfragen als zu viele Slides auf Verdacht +zu erzeugen. + +### 3. Dateien anlegen + +Für jede Slide der Gliederung: + +- **Dateiname**: Lücken-Nummerierung, drei Stellen als Standard + (`010-intro.md`, `020-kontext.md`, `025-nachtrag.md`, …), damit später + Slides eingeschoben werden können, ohne umzubenennen. Bei einer + Erweiterung eines bestehenden Decks: bestehendes Nummernschema + übernehmen. +- **Frontmatter**: `title` immer setzen (informativ, auch wenn nicht + automatisch im HTML sichtbar). `class` nur setzen, wenn eine + Layout-Variante gebraucht wird, die auch in `web/assets/style.css` + existiert oder gemeinsam mit dem Nutzer ergänzt wird — nicht auf Verdacht + erfinden. `notes` nur füllen, wenn der Nutzer tatsächlich Sprechernotizen + will. `skip` nur bei explizit als Entwurf markierten Slides. `incremental: + true` setzen, wenn Stichpunkte einer Slide im Vortrag einzeln + nacheinander aufgedeckt werden sollen (Bullet-Listen als "Fragments"; + navigiert wird dann automatisch erst Fragment für Fragment, dann zum + nächsten Slide) — nicht standardmäßig auf jede Liste anwenden, sondern + nur wenn der Nutzer das so vorträgt. +- **Body**: Stichpunkte statt Fließtext, eine Kernaussage pro Slide. Tabellen + für Vergleiche/Status, Blockquotes für Zitate/Hervorhebungen, Mermaid-Codeblöcke + (`graph`, `sequenceDiagram`, `gantt`, `pie`, …) für Prozesse, Architektur + oder Zeitpläne statt sie in Prosa zu beschreiben. Codeblöcke mit + Sprachangabe (```go, ```js, …) werden automatisch Syntax-highlighted, + ohne weiteres Zutun. Bilder (`![Alt](img/datei.png)`) einfach neben die + Slide-Dateien legen (auch in Unterordnern) — sie werden von Dev-Server + und Export automatisch mitgeliefert, kein manuelles Kopieren nötig. Rohes + HTML nur einsetzen, wenn reines Markdown eine Anforderung wirklich nicht + abdeckt (z. B. `class="fragment"` auf einem einzelnen Element für ein + gezieltes Fragment außerhalb einer Liste). + +### 4. Ergebnis prüfen lassen + +Nach dem Schreiben der Dateien: + +- Vorschau anbieten: `slidewalk ` (Dev-Server mit Live-Reload). Bei + größeren Decks auf den Übersichts-/Grid-Modus hinweisen (Taste `O` im + Browser zeigt alle Slides als Miniaturansicht, Klick springt hin). +- Falls gewünscht, statischen Export anbieten: `slidewalk -build `. +- Kurz zusammenfassen, welche Dateien mit welcher Absicht entstanden sind, + damit der Nutzer gezielt Feedback geben kann (z. B. "Slide 3 ist noch zu + textlastig"). + +## Leitplanken + +- Keine Inhalte erfinden, die der Nutzer nicht genannt hat oder die nicht aus + bereitgestelltem Material (Notizen, bestehende Dokumente) stammen — bei + fehlenden Fakten nachfragen statt zu raten. In Modus B gilt das + besonders für das Verdichten: kürzen und in Stichpunkte übersetzen ist + erlaubt und gewollt, inhaltlich umdeuten oder ergänzen nicht. +- Immer die im Projekt vorhandenen Konventionen respektieren: Lücken-Nummerierung, + Frontmatter-Felder aus `docs/markdown-guide.md`, vorhandene `class`-Werte in + `web/assets/style.css` wiederverwenden statt neue zu erfinden. +- Zielordner nicht ungefragt leeren oder überschreiben; bei Konflikten mit + bestehenden Dateien nachfragen. diff --git a/README.md b/README.md new file mode 100644 index 0000000..629fa45 --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# slidewalk + +Ein minimalistisches, selbstgebautes Tool zum Präsentieren von Markdown-Slides +im Browser, mit Mermaid-Support. Geschrieben in Go, das Ergebnis ist ein +einziges Binary. + +- Nur Markdown als Quelle (rohes HTML ist bei Bedarf inline erlaubt) +- Eine Datei = ein Slide, Reihenfolge über Dateiname-Präfix (`010-intro.md`, `020-...`) +- Vendored Mermaid, kein CDN nötig +- Syntax-Highlighting für Codeblöcke (serverseitig via Chroma, kein Client-JS) +- Tastatur- und Maus-Navigation, URL-Hash spiegelt den aktuellen Slide +- Dev-Server mit Live-Reload oder statischer Export als ein HTML-Dokument +- Dark Mode von Anfang an + +## Installation + +Voraussetzung ist ein installiertes Go (siehe `go.mod` für die Mindestversion). + +```sh +go build -o slidewalk ./cmd/slidewalk +``` + +Das erzeugt ein einziges Binary `slidewalk` ohne weitere Laufzeit-Abhängigkeiten +(alle Assets sind über `embed.FS` eingebettet). + +## Nutzung + +Ein Beispiel-Deck liegt unter `examples/demo/`. + +### Dev-Server (Standard) + +```sh +slidewalk ./examples/demo +``` + +Startet einen lokalen Server (Standard-Adresse `127.0.0.1:8080`) und liefert die +Präsentation aus. Änderungen an den Markdown-Dateien lösen automatisch ein +Neuladen im Browser aus (Live-Reload über Server-Sent Events). + +Flags müssen **vor** dem Ordner-Pfad stehen: + +```sh +slidewalk [-addr ] +``` + +Während der Entwicklung von slidewalk selbst (ohne vorheriges `go build`) +geht das auch direkt mit `go run`: + +```sh +go run ./cmd/slidewalk ./examples/demo +``` + +### Statischer Export + +```sh +slidewalk -build ./dist ./examples/demo +``` + +Schreibt die gerenderte Präsentation als eigenständiges HTML-Dokument nach +`./dist`. Alle benötigten Assets (`style.css`, `chroma.css`, `nav.js`, +`mermaid.min.js`) landen dabei in `./dist/vendor/` — außer der exportierten +HTML-Datei selbst liegt also nichts von slidewalk lose im Zielordner. Eigene +Dateien (Bilder etc.) werden unverändert an ihrem relativen Pfad mitkopiert. +Das Ergebnis lässt sich ohne Server direkt im Browser öffnen oder auf +beliebigem Static Hosting ausliefern. + +```sh +slidewalk [-build ] [-out ] +``` + +| Flag | Default | Bedeutung | +| --------- | -------------- | ------------------------------------------------------------ | +| `-build` | – | Zielordner für den statischen Export; ohne diesen Flag startet der Dev-Server | +| `-out` | `index.html` | Name der exportierten HTML-Datei (nur mit `-build`) | +| `-addr` | `127.0.0.1:8080` | Adresse, auf der der Dev-Server lauscht | + +## Navigation + +| Eingabe | Aktion | +| --------------------------------- | ----------------- | +| `→`, `Space`, `PageDown` | nächstes Fragment, dann nächster Slide | +| `←`, `PageUp` | vorheriges Fragment, dann vorheriger Slide | +| `Home` | erster Slide | +| `End` | letzter Slide | +| `O` | Übersichts-/Grid-Modus an/aus | +| `Escape` (im Grid-Modus) | Grid-Modus verlassen, ohne Slide zu wechseln | +| Klick links/rechts, Pfeil-Buttons | Slide wechseln | +| Klick auf Slide (im Grid-Modus) | zu diesem Slide springen, Grid-Modus verlassen | + +Der aktuelle Slide (und ggf. Fragment-Fortschritt) wird im URL-Hash +gespiegelt (z. B. `#3` oder `#3.2` für Slide 3, zweites Fragment sichtbar) +und beim Laden daraus wiederhergestellt. + +## Fragments (Bullet-Points einzeln einblenden) + +Setzt eine Slide-Datei `incremental: true` im Frontmatter, werden ihre +Top-Level-Listeneinträge (`
    `/`
      `) einzeln nacheinander eingeblendet, +statt alle auf einmal zu erscheinen — praktisch beim Durchgehen von +Stichpunkten im Vortrag. Zusätzlich (oder alternativ) lässt sich jedes +beliebige Element per raw HTML mit `class="fragment"` einzeln als Fragment +markieren. + +## Frontmatter-Referenz + +Jede Slide-Datei kann optional ein YAML-Frontmatter am Dateianfang haben: + +```markdown +--- +title: Meine Überschrift +class: intro +notes: Nur für die Sprechernotizen gedacht. +skip: false +--- + +# Der eigentliche Slide-Inhalt +``` + +| Feld | Typ | Bedeutung | +| -------- | ------- | -------------------------------------------------------------------------- | +| `title` | string | Titel des Slides; wird beim Anzeigen des Slides als Browser-Tab-Titel gesetzt (`Slide-Titel · Deck-Titel`) | +| `class` | string | Wird als zusätzliche CSS-Klasse auf das `
      `-Element angewendet | +| `notes` | string | Sprechernotizen; werden gerendert, aber per CSS standardmäßig versteckt | +| `skip` | bool | Wenn `true`, wird die Datei beim Rendern/Export komplett übersprungen | +| `incremental` | bool | Wenn `true`, werden Top-Level-Listeneinträge der Slide einzeln nacheinander eingeblendet (Fragments) | + +Mermaid-Diagramme werden über normale Codeblöcke mit der Sprache `mermaid` +eingebunden: + +````markdown +```mermaid +graph TD + A --> B +``` +```` + +Eine ausführliche Anleitung mit Beispielen für alle unterstützten +Markdown-Features (Tabellen, Task-Listen, Fußnoten, Mermaid-Diagrammtypen, +rohes HTML als Fallback etc.) findest du unter +[`docs/markdown-guide.md`](docs/markdown-guide.md). + +## Code-Dokumentation + +Eine Übersicht über die Architektur, welche Datei wofür zuständig ist und +wie die Packages zusammenspielen, findest du unter +[`docs/architektur.md`](docs/architektur.md). + +## Cross-Compiling + +Go kompiliert ohne zusätzliche Werkzeuge für andere Betriebssysteme/ +Architekturen, gesteuert über die Umgebungsvariablen `GOOS`/`GOARCH`. Da alle +Assets über `embed.FS` eingebettet sind, entsteht in jedem Fall ein einziges, +eigenständiges Binary. + +```sh +# macOS (Apple Silicon) +GOOS=darwin GOARCH=arm64 go build -o dist/slidewalk-darwin-arm64 ./cmd/slidewalk + +# macOS (Intel) +GOOS=darwin GOARCH=amd64 go build -o dist/slidewalk-darwin-amd64 ./cmd/slidewalk + +# Linux (x86_64) +GOOS=linux GOARCH=amd64 go build -o dist/slidewalk-linux-amd64 ./cmd/slidewalk + +# Linux (ARM64, z. B. Raspberry Pi 4/5, AWS Graviton) +GOOS=linux GOARCH=arm64 go build -o dist/slidewalk-linux-arm64 ./cmd/slidewalk + +# Windows (x86_64) +GOOS=windows GOARCH=amd64 go build -o dist/slidewalk-windows-amd64.exe ./cmd/slidewalk +``` + +Alle Zielsysteme lassen sich von einer einzigen Maschine aus bauen (kein +Cross-Compiler nötig, da reines Go ohne CGO). `go tool dist list` zeigt alle +unterstützten `GOOS`/`GOARCH`-Kombinationen. + +## Entwicklung + +```sh +go vet ./... +go test ./... +``` diff --git a/cmd/slidewalk/main.go b/cmd/slidewalk/main.go new file mode 100644 index 0000000..394f9a2 --- /dev/null +++ b/cmd/slidewalk/main.go @@ -0,0 +1,61 @@ +// Command slidewalk presents a folder of Markdown slides in the browser, or, +// with -build, exports them as a static HTML page. +package main + +import ( + "flag" + "fmt" + "os" + + "slidewalk/internal/export" + "slidewalk/internal/watch" +) + +func main() { + if err := run(os.Args[1:]); err != nil { + fmt.Fprintln(os.Stderr, "slidewalk:", err) + os.Exit(1) + } +} + +func run(args []string) error { + fs := flag.NewFlagSet("slidewalk", flag.ContinueOnError) + buildDir := fs.String("build", "", "Zielordner für den statischen Export (wenn gesetzt, kein Dev-Server)") + outName := fs.String("out", "index.html", "Name der exportierten HTML-Datei (nur mit -build)") + addr := fs.String("addr", "127.0.0.1:8080", "Adresse, auf der der Dev-Server lauscht") + fs.Usage = func() { + fmt.Fprintln(fs.Output(), "Nutzung: slidewalk [-build ] [-out ] [-addr ] ") + fs.PrintDefaults() + } + if err := fs.Parse(args); err != nil { + return err + } + + if fs.NArg() != 1 { + fs.Usage() + return fmt.Errorf("erwarte genau einen Ordner-Pfad als Argument, habe %d erhalten", fs.NArg()) + } + dir := fs.Arg(0) + + info, err := os.Stat(dir) + if err != nil { + return fmt.Errorf("ungültiger Ordner-Pfad %q: %w", dir, err) + } + if !info.IsDir() { + return fmt.Errorf("ungültiger Ordner-Pfad %q: ist kein Verzeichnis", dir) + } + + if *buildDir != "" { + if err := export.Export(dir, *buildDir, *outName); err != nil { + return fmt.Errorf("export fehlgeschlagen: %w", err) + } + fmt.Printf("Präsentation nach %s exportiert (%s)\n", *buildDir, *outName) + return nil + } + + fmt.Printf("Dev-Server läuft auf %s (Ordner: %s)\n", *addr, dir) + if err := watch.Serve(*addr, dir); err != nil { + return fmt.Errorf("dev-server fehlgeschlagen: %w", err) + } + return nil +} diff --git a/docs/architektur.md b/docs/architektur.md new file mode 100644 index 0000000..fc5103e --- /dev/null +++ b/docs/architektur.md @@ -0,0 +1,318 @@ +# Architektur & Code-Übersicht + +Diese Dokumentation erklärt, wie der Code von slidewalk aufgebaut ist: welche +Datei wofür zuständig ist, wie die Packages zusammenspielen und wo du +ansetzen musst, wenn du etwas ändern willst. Für die Autoring-Seite (wie man +Slides schreibt) siehe [`docs/markdown-guide.md`](markdown-guide.md); für +Installation/Nutzung siehe [`README.md`](../README.md). + +## 1. Der große Überblick + +Alles dreht sich um einen einzigen Datenfluss: **Ordner mit `.md`-Dateien +rein, eine HTML-Seite raus.** Dev-Server und statischer Export sind nur zwei +verschiedene "Auslässe" für denselben Kern: + +```mermaid +graph LR + A["Ordner mit
      010-x.md, 020-y.md, ..."] --> B["internal/parser
      ParseDir"] + B -->|"[]Slide"| C["internal/render
      Render"] + C -->|"HTML-Bytes"| D{{"cmd/slidewalk"}} + D -->|"ohne -build"| E["internal/watch
      Server"] + D -->|"mit -build"| F["internal/export
      Export"] + E --> G["Browser
      (HTTP + SSE)"] + F --> H["Ordner mit
      index.html + Assets"] +``` + +Vier Packages, klare Verantwortung, jedes einzeln testbar: + +| Package | Beantwortet die Frage | +| ------------------ | --------------------------------------------------------------- | +| `internal/parser` | Wie wird aus einer `.md`-Datei ein `Slide` (Struct mit HTML)? | +| `internal/render` | Wie werden mehrere `Slide`s zu **einer** HTML-Seite? | +| `internal/watch` | Wie liefere ich die Seite über HTTP aus und melde Änderungen? | +| `internal/export` | Wie schreibe ich die Seite + Assets als statische Dateien? | +| `web` | Wo liegen Templates/CSS/JS, und wie kommen sie ins Binary? | +| `cmd/slidewalk` | Wie übersetze ich CLI-Flags in einen Aufruf von `watch` oder `export`? | + +Sowohl `internal/watch` als auch `internal/export` rufen **ausschließlich** +`parser.ParseDir` und `render.Render` auf — es gibt keine zweite Stelle im +Code, die Markdown parst oder HTML zusammenbaut. Wenn sich am +HTML-Slide-Markup etwas ändern soll, reicht eine Änderung in +`internal/render`. + +## 2. `internal/parser` — Markdown-Dateien einlesen + +**Zuständig:** aus einem Ordner-Pfad eine sortierte Liste von `Slide`-Structs +machen. + +| Datei | Enthält | +| --------------------- | ------- | +| `parser.go` | Öffentliche API: `Slide`-Struct, `ParseDir(dir)`, Datei-Discovery (`discoverSlideFiles`), Zusammenbau eines einzelnen Slides (`parseSlide`) | +| `frontmatter.go` | YAML-Frontmatter-Handling: `splitFrontmatter` trennt den `---`-Block vom Markdown-Body, `parseFrontmatter` parst ihn in das `frontmatter`-Struct (`title`, `class`, `notes`, `skip`, `incremental`) | +| `markdown.go` | Die `goldmark`-Instanz (`md`) mit allen Extensions, plus der custom Fenced-Code-Block-Renderer (Mermaid-Sonderfall + Chroma-Syntax-Highlighting) | +| `*_test.go` | Table-driven Tests je Datei | + +### Ablauf von `ParseDir` (`parser.go:35`) + +1. `discoverSlideFiles(dir)` listet alle `.md`-Dateien im Ordner (nicht + rekursiv) und sortiert sie alphabetisch — das ist der Mechanismus hinter + der Präfix-Reihenfolge (`010-...`, `020-...`). +2. Für jede Datei: `os.ReadFile` → `parseSlide(name, data)`. +3. `parseSlide` (`parser.go:134`) ruft zuerst `parseFrontmatter` auf. Ist + `skip: true` gesetzt, bricht es sofort ab (zweiter Rückgabewert `skip + bool`), ohne den Markdown-Body überhaupt zu rendern. +4. Sonst: `renderMarkdown(body)` aus `markdown.go` wandelt den Rest in HTML + um, das Ergebnis landet als `template.HTML` im `Slide`-Struct. +5. `ParseDir` filtert alle als "skip" markierten Slides aus der + zurückgegebenen Liste heraus. + +### Fenced-Code-Blöcke: Mermaid & Syntax-Highlighting (`markdown.go`) + +`goldmark` rendert Fenced-Code-Blöcke standardmäßig als +`
      `. slidewalk braucht davon zwei
      +Sonderfälle, deshalb registriert `mermaidExtension` einen eigenen
      +`NodeRenderer` (`mermaidRenderer`) mit höherer Priorität als goldmarks
      +Standard-Renderer für `ast.KindFencedCodeBlock` — er überschreibt dessen
      +Registrierung (`renderFencedCodeBlock`, `markdown.go`):
      +
      +- Sprache `mermaid` → `
      ` **ohne** ``-Wrapper, + das Format, das `mermaid.js` erwartet. +- Sonstige Sprache, für die `lexers.Get` (Chroma) einen Lexer findet → + `renderHighlighted` tokenisiert den Codeblock-Inhalt und lässt ihn per + `chromahtml.Formatter` (Modus `WithClasses(true)`, also CSS-Klassen statt + Inline-Styles) als `
      ` mit ``-Tokens + rendern. Die eigentlichen Farben kommen aus der separat generierten + `web/assets/chroma.css` (siehe unten), nicht aus dem HTML selbst. +- Keine Sprache, oder eine, für die Chroma keinen Lexer kennt → fällt + zurück auf `renderDefaultFencedCodeBlock`, eine Kopie von goldmarks + Standardverhalten (`
      `, funktioniert auch
      +  ganz ohne Sprachangabe).
      +
      +Zusätzlich aktiviert: `extension.GFM` (Tabellen, Strikethrough, Auto-Links,
      +Task-Listen), `extension.Footnote`, sowie `html.WithUnsafe()` — Letzteres
      +erlaubt rohes HTML inline/als Block im Markdown (siehe
      +[Markdown-Anleitung](markdown-guide.md#5-rohes-html-in-markdown)).
      +
      +## 3. `internal/render` — aus Slides wird eine HTML-Seite
      +
      +**Zuständig:** `[]parser.Slide` + ein paar Metadaten → ein vollständiges
      +HTML-Dokument. Das ist die **einzige** Stelle im Code, die das finale
      +Seiten-Markup erzeugt.
      +
      +| Datei         | Enthält |
      +| ------------- | ------- |
      +| `render.go`   | `Page`-Struct (Input), `Render(w, page)` (Ausführung), interne `pageView`/`slideView`-Structs, die tatsächlich ans Template gehen |
      +
      +`Render` (`render.go:50`) macht im Kern nur zwei Dinge:
      +
      +1. Für jeden `parser.Slide` einen `slideView` bauen — dabei entsteht der
      +   1-basierte `Index`, der später als `data-slide="n"` im HTML landet.
      +2. `pageTemplate.Execute(w, pageView{...})` — das eigentliche Template liegt
      +   nicht in diesem Package, sondern wird über `web.Templates` (siehe unten)
      +   eingebettet und beim Package-Init einmalig geparst
      +   (`template.Must(template.ParseFS(...))`, `render.go:12`).
      +
      +Wichtig: `Page.DevReload` (bool) ist der einzige Unterschied zwischen
      +Dev-Server- und Export-Ausgabe. Ist er `true`, bindet das Template
      +`live-reload.js` ein; beim Export bleibt er auf dem Zero-Value `false`.
      +Dadurch entsteht kein zweiter Codepfad — nur ein Flag.
      +
      +`slideView.Incremental` reicht die Frontmatter-Einstellung `incremental`
      +unverändert durch: Ist sie gesetzt, bekommt das `
      `-Element im +Template zusätzlich `data-incremental="true"`. Das ist die einzige +serverseitige Beteiligung an den Fragments — die eigentliche +Ein-/Ausblende-Logik läuft komplett im Browser (`nav.js`, siehe Abschnitt 6). + +## 4. `web` — Templates und Assets + +**Zuständig:** alles, was nicht Go-Code ist (HTML-Template, CSS, JS, +Mermaid-Library), so einbetten, dass am Ende ein einziges Binary ohne externe +Dateien entsteht. + +| Datei/Ordner | Enthält | +| ------------------------------------ | ------- | +| `embed.go` | Zwei `embed.FS`-Variablen: `Templates` (bindet `templates/`) und `Assets` (bindet `assets/`) | +| `templates/page.html.tmpl` | Das eine HTML-Template für die ganze Seite: `` mit Stylesheet-Links (`vendor/style.css`, `vendor/chroma.css`), `{{range .Slides}}` für die `
      `-Elemente, Mermaid-Init-Script, Nav-Buttons/Klick-Zonen/Fortschrittsanzeige, ` + + +{{if .DevReload}} +{{end}} +