munin/internal/hilfe/hilfe.go
Thomas Adamek 466f1e40af Workspaces, Änderungshistorie, Papierkorb, Nutzungsübersicht, Hilfe
Umsetzung der Ausbaustufen 2.1, 3.6, 3.8, 3.9, 4.1, 4.2, 4.4, 4.6,
5.1–5.4 (Details in AUSBAUSTUFEN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 07:44:52 +02:00

110 lines
4.3 KiB
Go

// Package hilfe enthält die eingebaute Onboarding-Dokumentation der
// Weboberfläche: wie man Munin einrichtet, benutzt und was die Begriffe
// bedeuten.
//
// # Warum eingebettet und nicht als Einträge in einem Projekt?
//
// Die Hilfe beschreibt das Programm selbst. Sie muss also zu genau der Fassung
// passen, die gerade läuft — ändert sich eine Funktion, ändert sich im selben
// Commit auch ihre Beschreibung. Als Einträge in der Datenbank würde sie
// unbemerkt veralten, und in jedem neuen Workspace müsste sie erst jemand
// anlegen.
//
// Die Seiten sind Markdown-Dateien im Ordner seiten/. //go:embed packt sie
// beim Kompilieren ins Binary, genau wie templates/ und static/.
package hilfe
import (
"embed"
"fmt"
"munin/internal/markdown"
)
//go:embed seiten/*.md
var seitenFS embed.FS
// Seite beschreibt eine Hilfeseite.
type Seite struct {
Slug string // Teil der Adresse: /hilfe/<Slug>
Titel string // Überschrift und Name in der Navigation
Kurz string // ein Satz für die Übersicht
// NurAdmin blendet die Seite für alle aus, die nicht als Plattform-Admin
// angemeldet sind: Sie steht dann weder in der Navigation, noch lässt sie
// sich direkt aufrufen. Geheim ist darin nichts — aber für ein Team mit
// Projekt-Key beschreibt sie nur Knöpfe, die es gar nicht sieht.
NurAdmin bool
}
// seiten legt Reihenfolge und Inhalt der Navigation fest. Die Datei zu einer
// Seite heißt immer seiten/<Slug>.md — so gibt es nur eine Stelle, an der ein
// Name steht, und der Test in hilfe_test.go prüft, dass beides zusammenpasst.
var seiten = []Seite{
{Slug: "start", Titel: "Willkommen", Kurz: "Was Munin ist und wo man anfängt."},
{Slug: "begriffe", Titel: "Begriffe", Kurz: "Workspace, Projekt, Eintrag, Bezeichner, Rollen — was sie bedeuten."},
{Slug: "mcp-einrichten", Titel: "Agent anbinden", Kurz: "Claude Code, Cline oder OpenCode mit Munin verbinden."},
{Slug: "mit-agenten-arbeiten", Titel: "Mit Agenten arbeiten", Kurz: "Wie man Agenten anspricht, damit sie das Gedächtnis wirklich nutzen."},
{Slug: "weboberflaeche", Titel: "Weboberfläche", Kurz: "Einträge im Browser suchen, schreiben, vergleichen und verknüpfen."},
{Slug: "zugaenge", Titel: "Zugänge und Sicherheit", Kurz: "Keys, Rollen, Protokoll und was man beim Teilen von Wissen bedenken sollte."},
{Slug: "plattform-admin", Titel: "Für Plattform-Admins", Kurz: "Workspaces und Projekte anlegen, Schreibbudget, Export.", NurAdmin: true},
}
// Alle gibt alle Seiten in der Reihenfolge der Navigation zurück, auch die
// nur für Admins. Für die Anzeige ist Sichtbar die richtige Funktion.
//
// Zurückgegeben wird eine Kopie: Wer die Liste verändert, soll damit nicht aus
// Versehen die Navigation aller anderen Anfragen verändern.
func Alle() []Seite {
kopie := make([]Seite, len(seiten))
copy(kopie, seiten)
return kopie
}
// Sichtbar gibt die Seiten zurück, die jemand sehen darf: alle für einen
// Plattform-Admin, sonst alle außer den NurAdmin-Seiten.
func Sichtbar(istAdmin bool) []Seite {
var ergebnis []Seite
for _, s := range seiten {
if s.NurAdmin && !istAdmin {
continue
}
ergebnis = append(ergebnis, s)
}
return ergebnis
}
// Finde sucht eine Seite über ihren Slug.
func Finde(slug string) (Seite, bool) {
for _, s := range seiten {
if s.Slug == slug {
return s, true
}
}
return Seite{}, false
}
// Markdown liefert den Rohtext einer Seite.
func (s Seite) Markdown() (string, error) {
inhalt, err := seitenFS.ReadFile("seiten/" + s.Slug + ".md")
if err != nil {
return "", fmt.Errorf("Hilfeseite %q fehlt: %w", s.Slug, err)
}
return string(inhalt), nil
}
// Render wandelt eine Hilfeseite in HTML.
//
// Es ist derselbe Renderer wie für Einträge (internal/markdown), damit die
// Hilfe nichts zeigt, was im eigenen Eintrag dann anders aussieht. Einziger
// Unterschied: Überschriften bekommen hier eine id, damit die Oberfläche mit
// "?"-Links direkt auf einen Abschnitt zeigen kann, etwa
// /hilfe/weboberflaeche#markdown-im-inhalt.
//
// Roh-HTML bleibt auch hier gesperrt. Die Seiten stammen zwar aus dem
// Repository und nicht von Nutzern, aber die Platzhalter (siehe
// web/handlers_hilfe.go) setzen Werte aus der Anfrage ein — und dann soll
// daraus auf keinen Fall HTML werden können.
func Render(quelle string) (string, error) {
return markdown.RenderMitAnkern(quelle)
}