update
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<script setup>
|
||||
import BausteinPanel from './BausteinPanel.vue'
|
||||
import FlashcardWidget from './FlashcardWidget.vue'
|
||||
import WorkedExampleBlock from './WorkedExampleBlock.vue'
|
||||
import { renderMarkdown, renderBloecke } from '../markdown.js'
|
||||
import { stufeFuer, STUFEN } from '../stufen.js'
|
||||
import { pruefeBlock, uebernehmeBlock, resetBausteinFortschritt } from '../api.js'
|
||||
@@ -8,6 +10,7 @@ import { useConfirm } from '../composables/useConfirm.js'
|
||||
|
||||
const props = defineProps({
|
||||
baustein: { type: Object, required: true }, // { title, md, num }
|
||||
artefakte: { type: Object, default: null }, // { karteikarte[], beispiel[], diagramm } für diesen Baustein
|
||||
topic: { type: String, required: true },
|
||||
provider: { type: String, default: 'claude' },
|
||||
status: { type: Object, default: null },
|
||||
@@ -196,6 +199,10 @@ watch(() => `${props.baustein.title}|${props.baustein.md}|${props.baustein.kompa
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="artefakte">
|
||||
<WorkedExampleBlock :beispiele="artefakte.beispiel || []" />
|
||||
<FlashcardWidget :karten="artefakte.karteikarte || []" />
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="menu.show" class="menu-overlay" @click="menueZu" @contextmenu.prevent="menueZu">
|
||||
<div class="block-menu" :style="{ top: menu.y + 'px', left: menu.x + 'px' }" @click.stop>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
||||
import { fetchGuideContent, chatGuide, fetchBausteinLernstand, fetchArtefakte } from '../api.js'
|
||||
import { renderMarkdown, renderMermaid } from '../markdown.js'
|
||||
import { renderMarkdown } from '../markdown.js'
|
||||
import { stufeFuer, schwelle } from '../stufen.js'
|
||||
import { useChat } from '../composables/useChat.js'
|
||||
import BausteinPanel from './BausteinPanel.vue'
|
||||
@@ -70,15 +70,12 @@ function setupLazy() {
|
||||
if (!e.isIntersecting) continue
|
||||
sichtbar[Number(e.target.dataset.num)] = true
|
||||
mdObserver.unobserve(e.target)
|
||||
nextTick(() => renderMermaid(e.target)) // Diagramme der nun sichtbaren Section rendern
|
||||
}
|
||||
}, { root: scrollEl.value, rootMargin: '800px 0px' })
|
||||
for (const el of scrollEl.value.querySelectorAll('.section-card')) mdObserver.observe(el)
|
||||
}
|
||||
|
||||
watch(content, () => nextTick(setupLazy))
|
||||
// Artefakte kommen asynchron nach dem Content — bereits sichtbare Diagramme nachrendern.
|
||||
watch(artefakte, () => nextTick(() => renderMermaid(scrollEl.value)))
|
||||
onUnmounted(() => mdObserver?.disconnect())
|
||||
|
||||
watch(() => props.previewGuide?.id, loadContent, { immediate: true })
|
||||
@@ -277,6 +274,11 @@ function extractContext() {
|
||||
<h1>{{ previewGuide.topic }}</h1>
|
||||
<span class="guide-format">{{ previewGuide.format }}</span>
|
||||
<span v-if="themaAbgeschlossen" class="thema-done" title="Alle Bausteine auf Meister">✓ Thema abgeschlossen</span>
|
||||
<span class="gh-spacer"></span>
|
||||
<div class="ansicht-toggle" title="Ausführlichkeit umschalten">
|
||||
<button :class="{ active: ansichtModus === 'kompakt' }" @click="$emit('setAnsicht', 'kompakt')">Kompakt</button>
|
||||
<button :class="{ active: ansichtModus === 'erklärend' }" @click="$emit('setAnsicht', 'erklärend')">Erklärend</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section
|
||||
@@ -303,7 +305,6 @@ function extractContext() {
|
||||
<div v-if="sichtbar[s.num]" class="section-body markdown" v-html="htmlFor(s)"></div>
|
||||
<div v-else class="section-body skeleton"></div>
|
||||
<template v-if="sichtbar[s.num] && artefakteVon(s.title)">
|
||||
<pre v-if="artefakteVon(s.title).diagramm" class="mermaid art-diagramm">{{ artefakteVon(s.title).diagramm.mermaid }}</pre>
|
||||
<WorkedExampleBlock :beispiele="artefakteVon(s.title).beispiel || []" />
|
||||
<FlashcardWidget :karten="artefakteVon(s.title).karteikarte || []" />
|
||||
</template>
|
||||
@@ -333,6 +334,7 @@ function extractContext() {
|
||||
<BausteinFokus
|
||||
v-if="fokusBaustein"
|
||||
:baustein="fokusBaustein"
|
||||
:artefakte="artefakteVon(fokusBaustein.title)"
|
||||
:topic="previewGuide.topic"
|
||||
:guide-id="previewGuide.id"
|
||||
:provider="provider"
|
||||
@@ -424,12 +426,41 @@ function extractContext() {
|
||||
align-items: baseline;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
/* Sticky: der Ausführlichkeits-Toggle bleibt beim Scrollen erreichbar. */
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: var(--bg-preview);
|
||||
padding: 0.6rem 0 0.5rem;
|
||||
|
||||
h1 {
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.gh-spacer { flex: 1; }
|
||||
|
||||
.ansicht-toggle {
|
||||
align-self: center;
|
||||
display: inline-flex;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ansicht-toggle button {
|
||||
border: none;
|
||||
background: var(--panel);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
padding: 0.3rem 0.7rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ansicht-toggle button.active {
|
||||
background: var(--accent);
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.guide-format {
|
||||
color: var(--text-faint);
|
||||
font-size: 0.9rem;
|
||||
@@ -549,19 +580,6 @@ function extractContext() {
|
||||
damit der IntersectionObserver die folgenden Karten gestaffelt erkennt. */
|
||||
.section-body.skeleton { min-height: 160px; }
|
||||
|
||||
/* Diagramm-Artefakt (Mermaid): zentriert, dezent gerahmt. Vor dem Rendern zeigt
|
||||
<pre> den Quelltext — danach ersetzt mermaid.run ihn durch SVG. */
|
||||
.art-diagramm {
|
||||
margin: 0.75rem 0;
|
||||
padding: 0.75rem;
|
||||
background: var(--panel-soft);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.art-diagramm :deep(svg) { max-width: 100%; height: auto; }
|
||||
|
||||
.empty-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -6,17 +6,6 @@ import katex from 'katex'
|
||||
import 'katex/dist/katex.min.css'
|
||||
import DOMPurify from 'dompurify'
|
||||
|
||||
// Mermaid lazy laden (schwere Lib): erst beim ersten Diagramm, hält das Initial-Bundle klein.
|
||||
// startOnLoad:false → wir rendern gezielt; securityLevel strict: kein HTML/JS in Graphen.
|
||||
let _mermaid = null
|
||||
async function getMermaid() {
|
||||
if (!_mermaid) {
|
||||
_mermaid = (await import('mermaid')).default
|
||||
_mermaid.initialize({ startOnLoad: false, securityLevel: 'strict', theme: 'default', flowchart: { htmlLabels: false } })
|
||||
}
|
||||
return _mermaid
|
||||
}
|
||||
|
||||
marked.use(markedHighlight({
|
||||
langPrefix: 'hljs language-',
|
||||
highlight(code, lang) {
|
||||
@@ -60,33 +49,7 @@ const inlineMath = {
|
||||
renderer(token) { return renderTex(token.text, false) },
|
||||
}
|
||||
|
||||
// ```mermaid → <pre class="mermaid">…</pre> (Quelltext escaped; mermaid.run liest den textContent).
|
||||
const mermaidBlock = {
|
||||
name: 'mermaidBlock',
|
||||
level: 'block',
|
||||
start(src) { const i = src.indexOf('```mermaid'); return i < 0 ? undefined : i },
|
||||
tokenizer(src) {
|
||||
const m = /^```mermaid[^\n]*\n([\s\S]+?)\n```/.exec(src)
|
||||
if (m) return { type: 'mermaidBlock', raw: m[0], text: m[1] }
|
||||
},
|
||||
renderer(token) {
|
||||
const esc = token.text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
return `<pre class="mermaid">${esc}</pre>`
|
||||
},
|
||||
}
|
||||
|
||||
marked.use({ extensions: [blockMath, inlineMath, mermaidBlock] })
|
||||
|
||||
// Mermaid-Knoten in einem Container rendern (nach v-html). Idempotent: bereits
|
||||
// verarbeitete (data-processed) überspringt mermaid.run; defekte Graphen bleiben als Text.
|
||||
export async function renderMermaid(root) {
|
||||
const nodes = root?.querySelectorAll?.('pre.mermaid:not([data-processed="true"])')
|
||||
if (!nodes || !nodes.length) return
|
||||
try {
|
||||
const m = await getMermaid()
|
||||
await m.run({ nodes })
|
||||
} catch { /* defektes Diagramm → Quelltext bleibt sichtbar */ }
|
||||
}
|
||||
marked.use({ extensions: [blockMath, inlineMath] })
|
||||
|
||||
// Rohes HTML im Markdown (z. B. <p>, <img> ohne Backticks aus Agenten-Output)
|
||||
// als Text anzeigen statt rendern — sonst verschluckt der Browser den Inhalt.
|
||||
|
||||
Reference in New Issue
Block a user