This commit is contained in:
team3
2026-07-10 17:56:41 +02:00
parent ab1a28f0e7
commit 14bcf1b8e9
6 changed files with 59 additions and 7 deletions

View File

@@ -169,6 +169,11 @@ def _mathe_auftraege(text: str, wo: str) -> list[str]:
if re.search(r"\\n(?![a-zA-Z])", text): # \neq etc. bleiben unberührt
auftraege.append(f"{wo}: literales „\\n“ im Text — durch echten"
f" Zeilenumbruch bzw. Leerzeile ersetzen.")
lange = [m for m in re.findall(r"\$([^$\n]+?)\$", text) if len(m) > 60]
if lange: # Inline-Formeln brechen nie um — auf schmalen Screens Überlauf
auftraege.append(f"{wo}: {len(lange)} überlange Inline-Formel(n)"
f" (>60 Zeichen, z. B. „{lange[0][:40]}…“): abgesetzt"
f" als $$…$$ setzen.")
formeln = ([{"tex": m, "display": True} for m in re.findall(r"\$\$([\s\S]+?)\$\$", text)]
+ [{"tex": m, "display": False} for m in re.findall(r"\$([^$\n]+?)\$", text)])
for f in _katex_fehler(formeln):

View File

@@ -14,7 +14,10 @@ const neuName = ref('')
const neuArt = ref('thema')
const fehler = ref('')
const theme = ref(localStorage.getItem('theme') || 'auto')
const navAuf = ref(localStorage.getItem('navAuf') !== '0')
// mobil (≤768px) startet die Navigation eingeklappt, sofern nie manuell gesetzt
const navAuf = ref(localStorage.getItem('navAuf') !== null
? localStorage.getItem('navAuf') !== '0'
: window.innerWidth > 768)
function navToggle() {
navAuf.value = !navAuf.value
@@ -176,9 +179,11 @@ function runBadge(run) {
<button v-for="t in ['board', 'guide', 'üben', 'kennzahlen']" :key="t"
:class="{ aktiv: tab === t }" @click="tab = t">{{ t }}</button>
</div>
<template v-if="tab === 'board'">
<button @click="starten" :disabled="!topic || state?.run?.status === 'running'">Start</button>
<button class="sekundaer" @click="stoppen" :disabled="state?.run?.status !== 'running'">Stop</button>
<template v-if="transferOk">
</template>
<template v-if="transferOk && tab === 'board'">
<button class="sekundaer" title="Thema auf den Server pushen (überschreibt dort)"
:disabled="!topic || !!uebertragung || state?.run?.status === 'running'"
@click="uebertragen('push')"> Push</button>

View File

@@ -34,10 +34,18 @@ export const api = {
}
export function wsVerbinden(onEvent) {
const url = (import.meta.env.DEV ? 'ws://localhost:8000' : `ws://${location.host}`) + '/ws'
// Protokoll an die Seite koppeln: HTTPS erlaubt nur wss:// (Mixed Content)
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws'
const url = (import.meta.env.DEV ? 'ws://localhost:8000' : `${wsProto}://${location.host}`) + '/ws'
let ws
const verbinden = () => {
ws = new WebSocket(url)
try {
ws = new WebSocket(url)
} catch (e) {
// nie den Aufrufer mitreißen (der SecurityError killte sonst den Poll)
console.error('WebSocket nicht verfügbar:', e)
return
}
ws.onopen = () => onEvent({ typ: 'verbunden' }) // nach Reconnect: Stand neu laden
ws.onmessage = (e) => onEvent(JSON.parse(e.data))
ws.onclose = () => setTimeout(verbinden, 2000)

View File

@@ -186,11 +186,12 @@ input, select {
}
body.vollbild .sidebar, body.vollbild .topbar { display: none; }
body.vollbild .nav-toggle { display: none; }
/* Eingeklappt: Themen-Sidebar + Topbar weg — die Kapitelübersicht bleibt */
/* Eingeklappt: Themen-Sidebar + Topbar weg — die Kapitelübersicht bleibt.
Der Inhalt rückt nach unten, damit das fixe ☰ nichts überlappt. */
body.nav-zu .sidebar, body.nav-zu .topbar { display: none; }
body.nav-zu .guide-nav { padding-top: 44px; }
body.nav-zu .hauptbereich { padding-top: 38px; }
.nav-toggle {
position: fixed; top: 6px; left: 8px; z-index: 20;
position: fixed; top: 6px; left: 8px; z-index: 40; /* über den mobilen Drawern */
background: none; color: var(--dim); border: 0; padding: 4px 8px; font-size: 16px;
}
.nav-toggle:hover { color: var(--text); }
@@ -241,3 +242,32 @@ table.kennzahlen th, table.kennzahlen td {
}
table.kennzahlen th:first-child, table.kennzahlen td:first-child { text-align: left; }
table.kennzahlen th { color: var(--dim); }
/* ── Mobil (≤768px): Navigationen als Drawer, Board mit Spalten-Snap ────────── */
.markdown .katex { max-width: 100%; overflow-x: auto; overflow-y: hidden; }
@media (max-width: 768px) {
.sidebar, .guide-nav {
position: fixed; top: 0; bottom: 0; left: 0; z-index: 30;
width: min(85vw, 320px); min-width: 0;
border-right: 1px solid var(--rand); box-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);
background: var(--panel);
}
/* Guide-Tab: der Drawer zeigt die Kapitelliste, sonst die Themenliste */
body:has(.guide-nav) .sidebar { display: none; }
/* mobil versteckt ☰ nur die Drawer — die Topbar (Tabs!) bleibt sichtbar */
body.nav-zu .topbar { display: flex; }
body.nav-zu .hauptbereich { padding-top: 0; }
/* mobil heißt ☰-zu: ALLE Drawer weg — auch die Kapitelnav (Desktop behält sie) */
body.nav-zu .guide-nav { display: none; }
.sidebar .topics { padding-top: 44px; }
.guide-nav { padding-top: 44px; }
.topbar { padding-left: 40px; gap: 6px; }
.topbar .badge { display: none; }
.board { scroll-snap-type: x mandatory; padding: 8px; gap: 8px; }
.spalte { min-width: 86vw; max-width: 86vw; scroll-snap-align: start; }
.seite { padding: 12px; }
.seite.guide-text { max-width: 100%; padding: 14px 16px; }
.guide-section { padding: 12px 14px; }
.guide-text .kapitel-titel { margin-top: 20px; }
.flash { max-width: 100%; padding: 18px; margin: 16px auto; }
}

View File

@@ -45,6 +45,8 @@ Regeln (hart):
Nur KaTeX-STANDARDBEFEHLE — kein LaTeX-Textsatz wie `\textsc`, `\usepackage`.
Ein Parse-Gate prüft jede Formel mit dem echten Renderer.
Hervorhebung im Text NUR als `**fett**` — nie einzelne `*…*`.
Formeln über ~50 Zeichen ABGESETZT als `$$…$$` — lange Inline-Formeln laufen
auf schmalen Bildschirmen aus dem Text.
PDF-Artefakte aus den Belegen in saubere Notation übersetzen: „u 6= v" ist
$u \neq v$, „V 0" ist $V'$ — du lehrst, du zitierst nicht.
5. LÄNGE: „lang" hat {min_woerter}{max_woerter} Wörter. Die Obergrenze ist ein HARTES

View File

@@ -203,3 +203,5 @@ def test_katex_gate_und_markdown_hygiene(monkeypatch):
assert "Beträge" in auftraege
assert "einzelnes „*“" in auftraege
assert "literales" in auftraege
lang_formel = "Es gilt $" + "x + ".join(["y"] * 30) + "$ hier."
assert "überlange Inline-Formel" in " ".join(guide._mathe_auftraege(lang_formel, "T"))