141 lines
5 KiB
Go
141 lines
5 KiB
Go
// Package help 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 pages/. //go:embed packt sie
|
|
// beim Kompilieren ins Binary, genau wie templates/ und static/.
|
|
package help
|
|
|
|
import (
|
|
"embed"
|
|
"fmt"
|
|
|
|
"munin/internal/markdown"
|
|
)
|
|
|
|
//go:embed pages/*.md
|
|
var pagesFS embed.FS
|
|
|
|
// Page beschreibt eine Hilfeseite.
|
|
type Page struct {
|
|
Slug string // Teil der Adresse: /hilfe/<Slug>
|
|
Title string // Überschrift und Name in der Navigation
|
|
Summary string // ein Satz für die Übersicht
|
|
|
|
// For 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.
|
|
For Audience
|
|
}
|
|
|
|
// Audience einer Hilfeseite.
|
|
type Audience int
|
|
|
|
const (
|
|
// ForEveryone: jeder, der angemeldet ist.
|
|
ForEveryone Audience = iota
|
|
// ForAdmins: Workspace-Admins und der Plattform-Admin.
|
|
ForAdmins
|
|
// ForPlatformAdmin: nur der Plattform-Admin.
|
|
ForPlatformAdmin
|
|
)
|
|
|
|
// Reader beschreibt, wer die Hilfe gerade liest — so viel, wie für die
|
|
// Auswahl der Seiten nötig ist.
|
|
type Reader struct {
|
|
PlatformAdmin bool
|
|
WorkspaceAdmin bool
|
|
}
|
|
|
|
// CanRead sagt, ob l die Seite sehen darf.
|
|
func (s Page) CanRead(l Reader) bool {
|
|
switch s.For {
|
|
case ForPlatformAdmin:
|
|
return l.PlatformAdmin
|
|
case ForAdmins:
|
|
return l.PlatformAdmin || l.WorkspaceAdmin
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
// pages legt Reihenfolge und Inhalt der Navigation fest. Die Datei zu einer
|
|
// Page heißt immer pages/<Slug>.md — so gibt es nur eine Stelle, an der ein
|
|
// Name steht, und der Test in help_test.go prüft, dass beides zusammenpasst.
|
|
var pages = []Page{
|
|
{Slug: "start", Title: "Willkommen", Summary: "Was Munin ist und wo man anfängt."},
|
|
{Slug: "begriffe", Title: "Begriffe", Summary: "Workspace, Projekt, Eintrag, Bezeichner, Rollen — was sie bedeuten."},
|
|
{Slug: "mcp-einrichten", Title: "Agent anbinden", Summary: "Claude Code, Cline oder OpenCode mit Munin verbinden."},
|
|
{Slug: "mit-agenten-arbeiten", Title: "Mit Agenten arbeiten", Summary: "Wie man Agenten anspricht, damit sie das Gedächtnis wirklich nutzen."},
|
|
{Slug: "weboberflaeche", Title: "Weboberfläche", Summary: "Einträge im Browser suchen, schreiben, vergleichen und verknüpfen."},
|
|
{Slug: "zugaenge", Title: "Zugänge und Sicherheit", Summary: "Keys, Rollen, Protokoll und was man beim Teilen von Wissen bedenken sollte."},
|
|
{Slug: "workspace-admin", Title: "Für Workspace-Admins", Summary: "Projekte und Zugänge einer Abteilung verwalten, Protokoll, Suche über alle Projekte.", For: ForAdmins},
|
|
{Slug: "plattform-admin", Title: "Für Plattform-Admins", Summary: "Workspaces, Workspace-Admins, Schreibbudget, System, Sicherung.", For: ForPlatformAdmin},
|
|
}
|
|
|
|
// All 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 All() []Page {
|
|
clone := make([]Page, len(pages))
|
|
copy(clone, pages)
|
|
return clone
|
|
}
|
|
|
|
// Visible gibt die Seiten zurück, die l sehen darf, in der Reihenfolge der
|
|
// Navigation.
|
|
func Visible(l Reader) []Page {
|
|
var result []Page
|
|
for _, s := range pages {
|
|
if s.CanRead(l) {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Find sucht eine Seite über ihren Slug.
|
|
func Find(slug string) (Page, bool) {
|
|
for _, s := range pages {
|
|
if s.Slug == slug {
|
|
return s, true
|
|
}
|
|
}
|
|
return Page{}, false
|
|
}
|
|
|
|
// Markdown liefert den Rohtext einer Seite.
|
|
func (s Page) Markdown() (string, error) {
|
|
content, err := pagesFS.ReadFile("pages/" + s.Slug + ".md")
|
|
if err != nil {
|
|
return "", fmt.Errorf("Hilfeseite %q fehlt: %w", s.Slug, err)
|
|
}
|
|
return string(content), 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_help.go) setzen Werte aus der Anfrage ein — und dann soll
|
|
// daraus auf keinen Fall HTML werden können.
|
|
func Render(source string) (string, error) {
|
|
return markdown.RenderWithAnchors(source)
|
|
}
|