141 lines
5 KiB
Go
141 lines
5 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
|
|
|
|
// Fuer legt fest, wer die Seite sieht. Wer nicht dazugehört, findet sie
|
|
// weder in der Navigation, noch lässt sie sich direkt aufrufen (404).
|
|
// Geheim ist darin nichts — aber sie beschreibt Knöpfe, die andere gar
|
|
// nicht sehen.
|
|
Fuer Zielgruppe
|
|
}
|
|
|
|
// Zielgruppe einer Hilfeseite.
|
|
type Zielgruppe int
|
|
|
|
const (
|
|
// FuerAlle: jeder, der angemeldet ist.
|
|
FuerAlle Zielgruppe = iota
|
|
// FuerAdmins: Workspace-Admins und der Plattform-Admin.
|
|
FuerAdmins
|
|
// FuerPlattformAdmin: nur der Plattform-Admin.
|
|
FuerPlattformAdmin
|
|
)
|
|
|
|
// Leser beschreibt, wer die Hilfe gerade liest — so viel, wie für die
|
|
// Auswahl der Seiten nötig ist.
|
|
type Leser struct {
|
|
PlattformAdmin bool
|
|
WorkspaceAdmin bool
|
|
}
|
|
|
|
// DarfLesen sagt, ob l die Seite sehen darf.
|
|
func (s Seite) DarfLesen(l Leser) bool {
|
|
switch s.Fuer {
|
|
case FuerPlattformAdmin:
|
|
return l.PlattformAdmin
|
|
case FuerAdmins:
|
|
return l.PlattformAdmin || l.WorkspaceAdmin
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
// 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: "workspace-admin", Titel: "Für Workspace-Admins", Kurz: "Projekte und Zugänge einer Abteilung verwalten, Protokoll, Suche über alle Projekte.", Fuer: FuerAdmins},
|
|
{Slug: "plattform-admin", Titel: "Für Plattform-Admins", Kurz: "Workspaces, Workspace-Admins, Schreibbudget, System, Sicherung.", Fuer: FuerPlattformAdmin},
|
|
}
|
|
|
|
// 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 l sehen darf, in der Reihenfolge der
|
|
// Navigation.
|
|
func Sichtbar(l Leser) []Seite {
|
|
var ergebnis []Seite
|
|
for _, s := range seiten {
|
|
if s.DarfLesen(l) {
|
|
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)
|
|
}
|