+
+
+
+ Wissen
+
+
+ Was sich die Box nachts erschließt — Traum-Notizen, Muster und Skill-Ideen aus dem Wissens-Vault, verlinkt wie ein Wiki.
+
+
+ {files.length > 0 && (
+
+
+
+
+ )}
+
+
+ {data && !data.available && (
+
+ Der Wissens-Vault liegt auf der Box (~/wissens-vault) — im lokalen Dev-Modus nicht verfügbar.
+
+ )}
+
+ {isLoading && (
+
+ Vault wird geladen …
+
+ )}
+
+ {data?.available && files.length === 0 && (
+
+ Noch keine Notizen. Der nächtliche Traum (03:15) legt sie an — morgen früh steht hier die erste.
+
+ )}
+
+ {showGraph ? (
+
+ {graphLoading && (
+
+
+ Graph wird berechnet …
+
+
+ )}
+
+ {graphData && (
+ {
+ if (id) {
+ setActive(id)
+ setShowGraph(false)
+ }
+ }}
+ />
+ )}
+
+
+ ) : files.length > 0 && (
+
+ {/* Datei-Liste */}
+
+
+ {/* Inhalt */}
+
+ {docLoading ? (
+
Notiz wird geladen …
+ ) : doc ? (
+ <>
+
+ {doc.path}
+ {doc.mtime > 0 && (
+
+ Stand {new Date(doc.mtime * 1000).toLocaleString("de-DE", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })}
+
+ )}
+
+
+ >
+ ) : null}
+
+
+ )}
+
+ )
+}
+
+// ── Leichtgewichtiger Markdown-Renderer (Zeilen-basiert, XSS-frei da reines JSX) ──
+function Markdown({ text, onWikiLink, known }: {
+ text: string
+ onWikiLink: (name: string) => void
+ known: Map
+}) {
+ const lines = text.split("\n")
+ const out: React.ReactNode[] = []
+ let list: React.ReactNode[] = []
+ let code: string[] | null = null
+
+ const flushList = (key: string) => {
+ if (list.length) {
+ out.push()
+ list = []
+ }
+ }
+
+ lines.forEach((raw, i) => {
+ const key = `l${i}`
+ if (code !== null) {
+ if (raw.trimEnd() === "```") {
+ out.push({code.join("\n")})
+ code = null
+ } else code.push(raw)
+ return
+ }
+ if (raw.trimStart().startsWith("```")) { flushList(key); code = []; return }
+
+ const line = raw.trimEnd()
+ if (!line.trim()) { flushList(key); return }
+
+ const h = line.match(/^(#{1,4})\s+(.*)$/)
+ if (h) {
+ flushList(key)
+ const level = h[1].length
+ const cls = level === 1 ? "text-lg font-bold mt-1 mb-3" : level === 2 ? "text-base font-bold mt-4 mb-2" : "text-sm font-bold mt-3 mb-1.5"
+ out.push({inline(h[2], onWikiLink, known, key)}
)
+ return
+ }
+ const li = line.match(/^\s*[-*•]\s+(.*)$/)
+ if (li) {
+ list.push({inline(li[1], onWikiLink, known, key)})
+ return
+ }
+ flushList(key)
+ out.push({inline(line, onWikiLink, known, key)}
)
+ })
+ flushList("end")
+ if (code !== null) out.push({(code as string[]).join("\n")})
+
+ return {out}
+}
+
+// Inline: [[wiki-links]], **fett**, *kursiv*, `code` — per Regex-Split, reines JSX (kein HTML-Inject).
+function inline(text: string, onWikiLink: (n: string) => void, known: Map, keyBase: string): React.ReactNode[] {
+ const parts = text.split(/(\[\[[^\]]+\]\]|\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g)
+ return parts.map((p, i) => {
+ const key = `${keyBase}-${i}`
+ const wiki = p.match(/^\[\[([^\]|]+)(?:\|([^\]]+))?\]\]$/)
+ if (wiki) {
+ const target = wiki[1].trim()
+ const label = (wiki[2] ?? wiki[1]).trim()
+ const exists = known.has(target.toLowerCase())
+ return exists ? (
+
+ ) : (
+ {label}
+ )
+ }
+ if (p.startsWith("**") && p.endsWith("**")) return {p.slice(2, -2)}
+ if (p.startsWith("*") && p.endsWith("*") && p.length > 2) return {p.slice(1, -1)}
+ if (p.startsWith("`") && p.endsWith("`")) return {p.slice(1, -1)}
+ return {p}
+ })
+}