This commit is contained in:
team3
2026-07-12 21:21:41 +02:00
parent cde1721d10
commit a9b2f5ab9e
28 changed files with 106 additions and 706 deletions

View File

@@ -12,11 +12,9 @@
"@fontsource-variable/inter": "^5.2.8",
"dompurify": "^3.4.7",
"highlight.js": "^11.11.1",
"jsdom": "^29.1.1",
"katex": "^0.17.0",
"marked": "^18.0.4",
"marked-highlight": "^2.2.4",
"mermaid": "^11.4.1",
"vue": "^3.5.32"
},
"devDependencies": {

View File

@@ -7,9 +7,9 @@ import Ueben from './components/Ueben.vue'
import Kennzahlen from './components/Kennzahlen.vue'
const topics = ref([])
const topic = ref('')
const topic = ref(localStorage.getItem('topic') || '') // Auswahl über Reload halten
const state = ref(null)
const tab = ref('board')
const tab = ref(localStorage.getItem('tab') || 'board')
const neuName = ref('')
const neuArt = ref('thema')
const fehler = ref('')
@@ -24,6 +24,9 @@ function navToggle() {
localStorage.setItem('navAuf', navAuf.value ? '1' : '0')
}
watch(navAuf, (a) => document.body.classList.toggle('nav-zu', !a), { immediate: true })
// Ansicht über Reload halten: gewähltes Topic + Tab merken
watch(topic, (t) => localStorage.setItem('topic', t))
watch(tab, (t) => localStorage.setItem('tab', t))
let wsSchliessen = null
let timer = null
@@ -40,6 +43,8 @@ media.addEventListener('change', themeAnwenden)
async function topicsLaden() {
topics.value = await api.topics()
// gemerktes Topic gelöscht? → verwerfen, dann erst auf das erste zurückfallen
if (topic.value && !topics.value.some((t) => t.name === topic.value)) topic.value = ''
if (!topic.value && topics.value.length) topic.value = topics.value[0].name
}

View File

@@ -41,7 +41,6 @@ const EBENEN = [
{ key: 'inventar', titel: 'Atome' },
{ key: 'artefakte', titel: 'Artefakte' },
{ key: 'struktur', titel: 'Bausteine' },
{ key: 'diagramme', titel: 'Diagramme' },
{ key: 'guide', titel: 'Guide' },
]
@@ -80,10 +79,6 @@ function karten(key) {
return s.bausteine.map((b) => ({ id: b.id, text: `${b.ord + 1}. ${b.titel}`,
badges: [[b.status, statusFarbe(b.status)]] }))
}
if (key === 'diagramme') {
return (s.diagramme || []).map((d) => ({ id: d.id, text: `${d.ord + 1}. ${d.titel}`,
badges: [[d.status, statusFarbe(d.status)], [`${d.typ} · ${d.quelle}`, 'blau']] }))
}
return s.bausteine.map((b) => ({ id: b.id, text: `${b.ord + 1}. ${b.titel}`,
badges: [[b.stage || 'offen', b.stage === 'done' ? 'gruen' : 'blau']] }))
}

View File

@@ -1,14 +1,8 @@
<script setup>
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
import mermaid from 'mermaid'
import { api } from '../api.js'
import { render, lesestat } from '../markdown.js'
// securityLevel:'strict' → kein foreignObject/HTML-Label (umgeht DOMPurify-Filter),
// keine Klick-Handler. startOnLoad:false → wir rendern selbst nach jedem Einblenden.
mermaid.initialize({ startOnLoad: false, securityLevel: 'strict', theme: 'neutral' })
let mmId = 0
const props = defineProps({ topic: String, state: Object })
const daten = ref(null)
const level = ref('E') // Durchgang-Wahl: jede Stufe ist ein eigener Lese-Durchgang
@@ -46,27 +40,6 @@ function html(s) {
return htmlCache.get(key)
}
// Mermaid rendert asynchron ins DOM — nach jedem Einblenden (Scroll/Level/Ansicht)
// die noch nicht verarbeiteten ```mermaid-Blöcke im sichtbaren Bereich rendern.
async function renderMermaid() {
await nextTick()
const wurzel = scrollEl.value
if (!wurzel) return
for (const code of wurzel.querySelectorAll('code.language-mermaid:not([data-mm])')) {
code.setAttribute('data-mm', '1')
const pre = code.closest('pre')
try {
const { svg } = await mermaid.render(`mm-${mmId++}`, code.textContent)
const figur = document.createElement('div')
figur.className = 'mermaid-figur'
figur.innerHTML = svg
pre?.replaceWith(figur)
} catch {
pre?.classList.add('mermaid-fehler') // kaputt: als Codeblock stehen lassen
}
}
}
watch([aktiv, ansicht, level, daten], renderMermaid, { flush: 'post' })
// Nur aktives Kapitel ±1 im DOM; der Rest ist ein Platzhalter mit Schätzhöhe
const sichtbar = (i) => Math.abs(i - aktiv.value) <= 1

View File

@@ -64,7 +64,7 @@ const MARKER = /<!--\s*atom:.*?-->/g
export function lesestat(text) {
let displayFormeln = 0
const t = (text || '')
.replace(/```[\s\S]*?```/g, ' ') // Code-/Diagramm-Fences zählen nicht als Text
.replace(/```[\s\S]*?```/g, ' ') // Code-Fences zählen nicht als Text
.replace(MARKER, ' ')
.replace(/\$\$[\s\S]+?\$\$/g, () => { displayFormeln += 1; return ' ' })
.replace(/\\\[[\s\S]+?\\\]/g, () => { displayFormeln += 1; return ' ' })

View File

@@ -123,7 +123,7 @@ input, select {
/* Board: Ebenen-Spalten links→rechts, Listen VOLLSTÄNDIG scrollbar */
.board { display: flex; gap: 10px; padding: 12px; overflow-x: auto; flex: 1; min-height: 0; }
.spalte {
min-width: 265px; max-width: 320px; flex: 1; display: flex; flex-direction: column;
min-width: 0; max-width: 320px; flex: 1; display: flex; flex-direction: column;
background: var(--panel); border: 1px solid var(--rand); border-radius: 8px;
}
.spaltekopf { padding: 8px 10px; border-bottom: 1px solid var(--rand); display: grid; gap: 6px; }
@@ -223,8 +223,6 @@ body.nav-zu .hauptbereich { padding-top: 38px; }
}
.guide-section .ziel { color: var(--akzent); font-size: 13px; }
:is(.markdown, .kompakt) .katex-display { overflow-x: auto; overflow-y: hidden; padding: 4px 0; }
.mermaid-figur { overflow-x: auto; text-align: center; margin: 16px 0; }
.mermaid-figur svg { max-width: 100%; height: auto; }
.markdown blockquote {
margin: 12px 0; padding: 8px 14px; border-left: 3px solid var(--akzent);
background: var(--karte); border-radius: 0 6px 6px 0; color: var(--text);