feat(ui): WCAG text contrast improvements and modern course & lesson navigation button system
This commit is contained in:
parent
ec6fc53c81
commit
dd43e98748
3 changed files with 790 additions and 22 deletions
|
|
@ -7,6 +7,16 @@
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
function escapeHtml(str) {
|
||||||
|
if (!str) return '';
|
||||||
|
return String(str)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
function getRootPrefix() {
|
function getRootPrefix() {
|
||||||
const scripts = document.querySelectorAll('script[src*="course_sidebar.js"]');
|
const scripts = document.querySelectorAll('script[src*="course_sidebar.js"]');
|
||||||
if (scripts.length > 0) {
|
if (scripts.length > 0) {
|
||||||
|
|
@ -104,7 +114,7 @@
|
||||||
|
|
||||||
if (!window.AUTH) {
|
if (!window.AUTH) {
|
||||||
const authScript = document.createElement('script');
|
const authScript = document.createElement('script');
|
||||||
authScript.src = `${rootPrefix}assets/app_auth.js?v=2.1.0`;
|
authScript.src = `${rootPrefix}assets/app_auth.js?v=2.2.3`;
|
||||||
authScript.onload = () => proceed();
|
authScript.onload = () => proceed();
|
||||||
authScript.onerror = () => proceed();
|
authScript.onerror = () => proceed();
|
||||||
document.head.appendChild(authScript);
|
document.head.appendChild(authScript);
|
||||||
|
|
@ -365,26 +375,425 @@
|
||||||
document.body.appendChild(mobileBtn);
|
document.body.appendChild(mobileBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workspace: Topbar "☰ Kapitel" Button einbinden
|
// ==========================================================================
|
||||||
if (context.isWorkspace) {
|
// 🧭 KURS- & MODUL-NAVIGATIONSSYSTEM (Keine langweiligen Hyperlinks!)
|
||||||
|
// ==========================================================================
|
||||||
|
const ORDERED_COURSE_KEYS = [
|
||||||
|
'python',
|
||||||
|
'bash',
|
||||||
|
'powershell',
|
||||||
|
'active_directory',
|
||||||
|
'git',
|
||||||
|
'dns_records',
|
||||||
|
'sql',
|
||||||
|
'html_css',
|
||||||
|
'javascript',
|
||||||
|
'java',
|
||||||
|
'csharp',
|
||||||
|
'cpp',
|
||||||
|
'go',
|
||||||
|
'rust',
|
||||||
|
'ihk_ap1',
|
||||||
|
'ihk_ap2_fisi'
|
||||||
|
];
|
||||||
|
|
||||||
|
const courseIdx = ORDERED_COURSE_KEYS.indexOf(context.courseId);
|
||||||
|
const prevCourseKey = courseIdx > 0 ? ORDERED_COURSE_KEYS[courseIdx - 1] : null;
|
||||||
|
const nextCourseKey = (courseIdx >= 0 && courseIdx < ORDERED_COURSE_KEYS.length - 1) ? ORDERED_COURSE_KEYS[courseIdx + 1] : null;
|
||||||
|
const prevCourse = prevCourseKey ? manifest[prevCourseKey] : null;
|
||||||
|
const nextCourse = nextCourseKey ? manifest[nextCourseKey] : null;
|
||||||
|
|
||||||
|
const allChapters = [];
|
||||||
|
(currentCourse.tracks || []).forEach(tr => {
|
||||||
|
(tr.chapters || []).forEach(ch => {
|
||||||
|
allChapters.push({
|
||||||
|
trackId: tr.id,
|
||||||
|
folder: ch.folder,
|
||||||
|
title: ch.title,
|
||||||
|
taskFile: ch.taskFile,
|
||||||
|
testFile: ch.testFile
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentChapIdx = allChapters.findIndex(ch => ch.folder === context.chapterFolder);
|
||||||
|
const prevChapter = currentChapIdx > 0 ? allChapters[currentChapIdx - 1] : null;
|
||||||
|
const nextChapter = (currentChapIdx >= 0 && currentChapIdx < allChapters.length - 1) ? allChapters[currentChapIdx + 1] : null;
|
||||||
|
|
||||||
|
function buildTheoryUrl(cid, tid, folder) {
|
||||||
|
if (cid === 'python') {
|
||||||
|
return `${rootPrefix}${tid || 'lehrpfad_1_grundlagen'}/${folder}/index.html`;
|
||||||
|
}
|
||||||
|
return `${rootPrefix}courses/${cid}/${folder}/index.html`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFirstLessonTheoryUrl(cid, courseObj) {
|
||||||
|
if (!courseObj || !courseObj.tracks || !courseObj.tracks.length) return `${rootPrefix}dashboard.html`;
|
||||||
|
const t = courseObj.tracks[0];
|
||||||
|
const f = t.chapters && t.chapters.length ? t.chapters[0].folder : '';
|
||||||
|
if (!f) return `${rootPrefix}dashboard.html`;
|
||||||
|
return buildTheoryUrl(cid, t.id, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildWorkspaceUrl(cid, tid, folder) {
|
||||||
|
return `${rootPrefix}workspace.html?course=${encodeURIComponent(cid)}&track=${encodeURIComponent(tid || '')}&chapter=${encodeURIComponent(folder)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFirstLessonWorkspaceUrl(cid, courseObj) {
|
||||||
|
if (!courseObj || !courseObj.tracks || !courseObj.tracks.length) return `${rootPrefix}workspace.html`;
|
||||||
|
const t = courseObj.tracks[0];
|
||||||
|
const f = t.chapters && t.chapters.length ? t.chapters[0].folder : '';
|
||||||
|
return buildWorkspaceUrl(cid, t.id, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. THEORIE-SEITEN: Prominente Navigationsleiste am Ende der Lerneinheit
|
||||||
|
function renderTheoryNavigator() {
|
||||||
|
const mainEl = document.querySelector('main.container') || document.querySelector('main') || document.querySelector('.container');
|
||||||
|
if (!mainEl) return;
|
||||||
|
|
||||||
|
const existingNav = document.getElementById('courseModuleNavigator');
|
||||||
|
if (existingNav) existingNav.remove();
|
||||||
|
|
||||||
|
// Legacy-Buttons ausblenden
|
||||||
|
const legacyActionDivs = mainEl.querySelectorAll('.card > div[style*="flex"]:last-child, main > div[style*="flex"]:last-child');
|
||||||
|
legacyActionDivs.forEach(div => {
|
||||||
|
if (div.querySelector('a[href*="workspace.html"]') || div.querySelector('a[href*="index.html"]')) {
|
||||||
|
div.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const nav = document.createElement('nav');
|
||||||
|
nav.className = 'course-module-navigator';
|
||||||
|
nav.id = 'courseModuleNavigator';
|
||||||
|
nav.setAttribute('aria-label', 'Kurs- und Modulnavigation');
|
||||||
|
|
||||||
|
// Linker Button (Vorheriges Modul oder Vorheriger Kurs)
|
||||||
|
let leftBtnHtml = '';
|
||||||
|
if (prevChapter) {
|
||||||
|
const prevUrl = buildTheoryUrl(context.courseId, prevChapter.trackId, prevChapter.folder);
|
||||||
|
leftBtnHtml = `
|
||||||
|
<a href="${prevUrl}" class="lesson-nav-btn lesson-nav-prev" title="Vorheriges Modul: ${escapeHtml(prevChapter.title)}">
|
||||||
|
<span class="nav-arrow">◀</span>
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">VORHERIGES MODUL</span>
|
||||||
|
<span class="nav-title">${escapeHtml(prevChapter.title)}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
} else if (prevCourse) {
|
||||||
|
const prevCourseUrl = buildFirstLessonTheoryUrl(prevCourseKey, prevCourse);
|
||||||
|
leftBtnHtml = `
|
||||||
|
<a href="${prevCourseUrl}" class="lesson-nav-btn lesson-nav-course-prev" title="Vorheriger Kurs: ${escapeHtml(prevCourse.title)}">
|
||||||
|
<span class="nav-arrow">⏮</span>
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">VORHERIGER KURS</span>
|
||||||
|
<span class="nav-title">${escapeHtml(prevCourse.icon || '📚')} ${escapeHtml(prevCourse.title)}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
leftBtnHtml = `
|
||||||
|
<a href="${rootPrefix}dashboard.html" class="lesson-nav-btn" style="opacity: 0.85;" title="Zurück zum Lern-Dashboard">
|
||||||
|
<span class="nav-arrow">🏠</span>
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">START</span>
|
||||||
|
<span class="nav-title">Zum Dashboard</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mittlere Buttons (Dashboard & Web-IDE)
|
||||||
|
const ideUrl = buildWorkspaceUrl(context.courseId, currentTrackId, context.chapterFolder);
|
||||||
|
const centerHtml = `
|
||||||
|
<div class="lesson-nav-center">
|
||||||
|
<a href="${rootPrefix}dashboard.html" class="lesson-nav-btn lesson-nav-dashboard" title="Zurück zur Übersicht aller Kurse">
|
||||||
|
<span>🏠</span>
|
||||||
|
<span>Dashboard</span>
|
||||||
|
</a>
|
||||||
|
<a href="${ideUrl}" class="lesson-nav-btn lesson-nav-ide" title="Aufgabe interaktiv in der Web-IDE lösen">
|
||||||
|
<span>💻</span>
|
||||||
|
<span>In Web-IDE lösen →</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Rechter Button (Nächstes Modul oder Nächster Kurs)
|
||||||
|
let rightBtnHtml = '';
|
||||||
|
if (nextChapter) {
|
||||||
|
const nextUrl = buildTheoryUrl(context.courseId, nextChapter.trackId, nextChapter.folder);
|
||||||
|
rightBtnHtml = `
|
||||||
|
<a href="${nextUrl}" class="lesson-nav-btn lesson-nav-next" title="Nächstes Modul: ${escapeHtml(nextChapter.title)}">
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">NÄCHSTES MODUL</span>
|
||||||
|
<span class="nav-title">${escapeHtml(nextChapter.title)}</span>
|
||||||
|
</span>
|
||||||
|
<span class="nav-arrow">▶</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
} else if (nextCourse) {
|
||||||
|
const nextCourseUrl = buildFirstLessonTheoryUrl(nextCourseKey, nextCourse);
|
||||||
|
rightBtnHtml = `
|
||||||
|
<a href="${nextCourseUrl}" class="lesson-nav-btn lesson-nav-course-next" title="Nächster Kurs: ${escapeHtml(nextCourse.title)}">
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">NÄCHSTER KURS</span>
|
||||||
|
<span class="nav-title">${escapeHtml(nextCourse.icon || '🚀')} ${escapeHtml(nextCourse.title)}</span>
|
||||||
|
</span>
|
||||||
|
<span class="nav-arrow">⏭</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
rightBtnHtml = `
|
||||||
|
<a href="${rootPrefix}dashboard.html" class="lesson-nav-btn lesson-nav-course-next" title="Alle Kurse gemeistert!">
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">ABSCHLUSS</span>
|
||||||
|
<span class="nav-title">🎉 Kurs gemeistert!</span>
|
||||||
|
</span>
|
||||||
|
<span class="nav-arrow">🏆</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.innerHTML = leftBtnHtml + centerHtml + rightBtnHtml;
|
||||||
|
mainEl.appendChild(nav);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. WORKSPACE-PANE: Navigationsbuttons innerhalb des Theorie-Tabs der Web-IDE
|
||||||
|
function renderWorkspaceTheoryInlineNavigator() {
|
||||||
|
const theoriePane = document.getElementById('tab-content-theorie');
|
||||||
|
if (!theoriePane) return;
|
||||||
|
|
||||||
|
const existingPaneNav = theoriePane.querySelector('.course-module-navigator');
|
||||||
|
if (existingPaneNav) existingPaneNav.remove();
|
||||||
|
|
||||||
|
const legacyActionDivs = theoriePane.querySelectorAll('.card > div[style*="flex"]:last-child, div[style*="flex"]:last-child');
|
||||||
|
legacyActionDivs.forEach(div => {
|
||||||
|
if (div.querySelector('a[href*="workspace.html"]') || div.querySelector('a[href*="index.html"]')) {
|
||||||
|
div.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const paneNav = document.createElement('nav');
|
||||||
|
paneNav.className = 'course-module-navigator';
|
||||||
|
paneNav.style.marginTop = '28px';
|
||||||
|
paneNav.setAttribute('aria-label', 'Modulnavigation');
|
||||||
|
|
||||||
|
// Left
|
||||||
|
let leftHtml = '';
|
||||||
|
if (prevChapter) {
|
||||||
|
const prevWsUrl = buildWorkspaceUrl(context.courseId, prevChapter.trackId, prevChapter.folder);
|
||||||
|
leftHtml = `
|
||||||
|
<a href="${prevWsUrl}" class="lesson-nav-btn lesson-nav-prev" title="Vorheriges Modul: ${escapeHtml(prevChapter.title)}">
|
||||||
|
<span class="nav-arrow">◀</span>
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">VORHERIGES MODUL</span>
|
||||||
|
<span class="nav-title">${escapeHtml(prevChapter.title)}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
} else if (prevCourse) {
|
||||||
|
const prevCourseWsUrl = buildFirstLessonWorkspaceUrl(prevCourseKey, prevCourse);
|
||||||
|
leftHtml = `
|
||||||
|
<a href="${prevCourseWsUrl}" class="lesson-nav-btn lesson-nav-course-prev" title="Vorheriger Kurs: ${escapeHtml(prevCourse.title)}">
|
||||||
|
<span class="nav-arrow">⏮</span>
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">VORHERIGER KURS</span>
|
||||||
|
<span class="nav-title">${escapeHtml(prevCourse.icon || '📚')} ${escapeHtml(prevCourse.title)}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
leftHtml = `
|
||||||
|
<a href="${rootPrefix}dashboard.html" class="lesson-nav-btn" title="Zurück zum Lern-Dashboard">
|
||||||
|
<span class="nav-arrow">🏠</span>
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">START</span>
|
||||||
|
<span class="nav-title">Dashboard</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center
|
||||||
|
const theoryFullUrl = buildTheoryUrl(context.courseId, currentTrackId, context.chapterFolder);
|
||||||
|
const centerHtml = `
|
||||||
|
<div class="lesson-nav-center">
|
||||||
|
<a href="${theoryFullUrl}" target="_blank" rel="noopener noreferrer" class="lesson-nav-btn lesson-nav-dashboard" title="Theorie in voller Seite lesen">
|
||||||
|
<span>📖</span>
|
||||||
|
<span>Vollbild-Theorie ↗</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Right
|
||||||
|
let rightHtml = '';
|
||||||
|
if (nextChapter) {
|
||||||
|
const nextWsUrl = buildWorkspaceUrl(context.courseId, nextChapter.trackId, nextChapter.folder);
|
||||||
|
rightHtml = `
|
||||||
|
<a href="${nextWsUrl}" class="lesson-nav-btn lesson-nav-next" title="Nächstes Modul: ${escapeHtml(nextChapter.title)}">
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">NÄCHSTES MODUL</span>
|
||||||
|
<span class="nav-title">${escapeHtml(nextChapter.title)}</span>
|
||||||
|
</span>
|
||||||
|
<span class="nav-arrow">▶</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
} else if (nextCourse) {
|
||||||
|
const nextCourseWsUrl = buildFirstLessonWorkspaceUrl(nextCourseKey, nextCourse);
|
||||||
|
rightHtml = `
|
||||||
|
<a href="${nextCourseWsUrl}" class="lesson-nav-btn lesson-nav-course-next" title="Nächster Kurs: ${escapeHtml(nextCourse.title)}">
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">NÄCHSTER KURS</span>
|
||||||
|
<span class="nav-title">${escapeHtml(nextCourse.icon || '🚀')} ${escapeHtml(nextCourse.title)}</span>
|
||||||
|
</span>
|
||||||
|
<span class="nav-arrow">⏭</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
rightHtml = `
|
||||||
|
<a href="${rootPrefix}dashboard.html" class="lesson-nav-btn lesson-nav-course-next" title="Alle Kurse gemeistert!">
|
||||||
|
<span class="nav-text">
|
||||||
|
<span class="nav-subtitle">ABSCHLUSS</span>
|
||||||
|
<span class="nav-title">🎉 Kurs gemeistert!</span>
|
||||||
|
</span>
|
||||||
|
<span class="nav-arrow">🏆</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
paneNav.innerHTML = leftHtml + centerHtml + rightHtml;
|
||||||
|
theoriePane.appendChild(paneNav);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global für dynamisches Nachladen in workspace.html bereitstellen
|
||||||
|
window.renderWorkspaceTheoryNavigator = renderWorkspaceTheoryInlineNavigator;
|
||||||
|
|
||||||
|
// 3. WORKSPACE: Topbar-Steuerleiste verdrahten
|
||||||
|
function renderWorkspaceNavigation() {
|
||||||
const topbarTitle = document.querySelector('.workspace-title');
|
const topbarTitle = document.querySelector('.workspace-title');
|
||||||
if (topbarTitle && !document.getElementById('btn-toggle-chapter-drawer')) {
|
if (!topbarTitle) return;
|
||||||
const toggleBtn = document.createElement('button');
|
|
||||||
|
// 1. Drawer Button "☰ Kapitel"
|
||||||
|
let toggleBtn = document.getElementById('btn-toggle-chapter-drawer');
|
||||||
|
if (!toggleBtn) {
|
||||||
|
toggleBtn = document.createElement('button');
|
||||||
toggleBtn.type = 'button';
|
toggleBtn.type = 'button';
|
||||||
toggleBtn.id = 'btn-toggle-chapter-drawer';
|
toggleBtn.id = 'btn-toggle-chapter-drawer';
|
||||||
toggleBtn.className = 'btn btn-secondary';
|
toggleBtn.className = 'workspace-topbar-btn';
|
||||||
toggleBtn.style.cssText = 'font-size: 0.8rem; padding: 4px 10px; margin-right: 10px; display: inline-flex; align-items: center; gap: 6px; border: 1px solid rgba(255,255,255,0.18); background: rgba(255,255,255,0.08); color: #f8fafc; font-weight: 700; cursor: pointer; border-radius: 6px;';
|
|
||||||
toggleBtn.innerHTML = '<span>☰</span> <span>Kapitel</span>';
|
toggleBtn.innerHTML = '<span>☰</span> <span>Kapitel</span>';
|
||||||
toggleBtn.title = 'Kapitel-Drawer aufklappen';
|
toggleBtn.title = 'Kapitel-Übersicht aufklappen';
|
||||||
toggleBtn.addEventListener('click', () => {
|
|
||||||
if (sidebar.classList.contains('open')) {
|
|
||||||
closeSidebar();
|
|
||||||
} else {
|
|
||||||
openSidebar();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
topbarTitle.insertBefore(toggleBtn, topbarTitle.firstChild);
|
topbarTitle.insertBefore(toggleBtn, topbarTitle.firstChild);
|
||||||
}
|
}
|
||||||
|
toggleBtn.onclick = () => {
|
||||||
|
if (sidebar.classList.contains('open')) {
|
||||||
|
closeSidebar();
|
||||||
|
} else {
|
||||||
|
openSidebar();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Dashboard Button
|
||||||
|
let dashBtn = document.getElementById('btn-topbar-dashboard');
|
||||||
|
if (!dashBtn) {
|
||||||
|
dashBtn = document.createElement('a');
|
||||||
|
dashBtn.href = `${rootPrefix}dashboard.html`;
|
||||||
|
dashBtn.id = 'btn-topbar-dashboard';
|
||||||
|
dashBtn.className = 'workspace-topbar-btn';
|
||||||
|
dashBtn.innerHTML = '<span>🏠</span> <span>Dashboard</span>';
|
||||||
|
dashBtn.title = 'Zurück zum Lern-Dashboard';
|
||||||
|
toggleBtn.insertAdjacentElement('afterend', dashBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Navigation Cluster
|
||||||
|
let navCluster = document.getElementById('workspace-nav-cluster');
|
||||||
|
if (!navCluster) {
|
||||||
|
navCluster = document.createElement('div');
|
||||||
|
navCluster.id = 'workspace-nav-cluster';
|
||||||
|
navCluster.className = 'workspace-nav-cluster';
|
||||||
|
navCluster.innerHTML = `
|
||||||
|
<button type="button" class="workspace-topbar-btn" id="btn-topbar-prev-course" title="Vorheriger Kurs">⏮ Kurs</button>
|
||||||
|
<button type="button" class="workspace-topbar-btn" id="btn-topbar-prev-chapter" title="Vorheriges Modul">◀ Zurück</button>
|
||||||
|
<span class="workspace-chapter-badge" id="workspace-chapter-badge">Modul - / -</span>
|
||||||
|
<button type="button" class="workspace-topbar-btn primary" id="btn-topbar-next-chapter" title="Nächstes Modul">Weiter ▶</button>
|
||||||
|
<button type="button" class="workspace-topbar-btn" id="btn-topbar-next-course" title="Nächster Kurs">Kurs ⏭</button>
|
||||||
|
`;
|
||||||
|
dashBtn.insertAdjacentElement('afterend', navCluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Badge
|
||||||
|
const badge = document.getElementById('workspace-chapter-badge');
|
||||||
|
if (badge) {
|
||||||
|
const modNumber = currentChapIdx >= 0 ? currentChapIdx + 1 : 1;
|
||||||
|
badge.innerText = `MODUL ${modNumber} / ${allChapters.length || 16}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnPrevCourse = document.getElementById('btn-topbar-prev-course');
|
||||||
|
if (btnPrevCourse) {
|
||||||
|
if (prevCourseKey && prevCourse) {
|
||||||
|
btnPrevCourse.classList.remove('disabled');
|
||||||
|
btnPrevCourse.disabled = false;
|
||||||
|
btnPrevCourse.title = `Zum vorherigen Kurs: ${prevCourse.title}`;
|
||||||
|
btnPrevCourse.onclick = () => {
|
||||||
|
window.location.href = buildFirstLessonWorkspaceUrl(prevCourseKey, prevCourse);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
btnPrevCourse.classList.add('disabled');
|
||||||
|
btnPrevCourse.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnPrevChap = document.getElementById('btn-topbar-prev-chapter');
|
||||||
|
if (btnPrevChap) {
|
||||||
|
if (prevChapter) {
|
||||||
|
btnPrevChap.classList.remove('disabled');
|
||||||
|
btnPrevChap.disabled = false;
|
||||||
|
btnPrevChap.title = `Vorheriges Modul: ${prevChapter.title}`;
|
||||||
|
btnPrevChap.onclick = () => {
|
||||||
|
window.location.href = buildWorkspaceUrl(context.courseId, prevChapter.trackId, prevChapter.folder);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
btnPrevChap.classList.add('disabled');
|
||||||
|
btnPrevChap.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnNextChap = document.getElementById('btn-topbar-next-chapter');
|
||||||
|
if (btnNextChap) {
|
||||||
|
if (nextChapter) {
|
||||||
|
btnNextChap.classList.remove('disabled');
|
||||||
|
btnNextChap.disabled = false;
|
||||||
|
btnNextChap.title = `Nächstes Modul: ${nextChapter.title}`;
|
||||||
|
btnNextChap.onclick = () => {
|
||||||
|
window.location.href = buildWorkspaceUrl(context.courseId, nextChapter.trackId, nextChapter.folder);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
btnNextChap.classList.add('disabled');
|
||||||
|
btnNextChap.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnNextCourse = document.getElementById('btn-topbar-next-course');
|
||||||
|
if (btnNextCourse) {
|
||||||
|
if (nextCourseKey && nextCourse) {
|
||||||
|
btnNextCourse.classList.remove('disabled');
|
||||||
|
btnNextCourse.disabled = false;
|
||||||
|
btnNextCourse.title = `Zum nächsten Kurs: ${nextCourse.title}`;
|
||||||
|
btnNextCourse.onclick = () => {
|
||||||
|
window.location.href = buildFirstLessonWorkspaceUrl(nextCourseKey, nextCourse);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
btnNextCourse.classList.add('disabled');
|
||||||
|
btnNextCourse.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderWorkspaceTheoryInlineNavigator();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.isWorkspace) {
|
||||||
|
renderWorkspaceNavigation();
|
||||||
|
} else {
|
||||||
|
renderTheoryNavigator();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aktives Kapitel automatisch mittig in der Sidebar sichtbar scrollen
|
// Aktives Kapitel automatisch mittig in der Sidebar sichtbar scrollen
|
||||||
|
|
|
||||||
351
assets/style.css
351
assets/style.css
|
|
@ -37,8 +37,8 @@
|
||||||
|
|
||||||
/* Typography */
|
/* Typography */
|
||||||
--text-main: #0f172a;
|
--text-main: #0f172a;
|
||||||
--text-body: #334155;
|
--text-body: #1e293b;
|
||||||
--text-muted: #64748b;
|
--text-muted: #475569;
|
||||||
--text-inverse: #f8fafc;
|
--text-inverse: #f8fafc;
|
||||||
|
|
||||||
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
|
@ -77,6 +77,17 @@ body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0284c7;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: color 0.15s ease, opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #0369a1;
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
APP LAYOUT (FLEX CONTAINER)
|
APP LAYOUT (FLEX CONTAINER)
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
@ -276,7 +287,7 @@ header h1 {
|
||||||
|
|
||||||
header p {
|
header p {
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
color: #94a3b8;
|
color: #e2e8f0;
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2540,3 +2551,337 @@ body.workspace-page.has-course-sidebar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
📊 HIGH-CONTRAST TABLES & CHEATSHEETS
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
margin: 16px 0;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--bg-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background: var(--bg-subtle);
|
||||||
|
border-bottom: 2px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-main);
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
color: var(--text-body);
|
||||||
|
line-height: 1.5;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:hover {
|
||||||
|
background: rgba(241, 245, 249, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
🎯 CHECKLISTS & TASK ITEMS
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.task-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: background 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-checkbox {
|
||||||
|
margin-top: 4px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
accent-color: var(--primary);
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-label {
|
||||||
|
font-size: 0.94rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
line-height: 1.5;
|
||||||
|
cursor: pointer;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
🧭 PREMIUM COURSE & LESSON NAVIGATOR COMPONENT
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.course-module-navigator {
|
||||||
|
margin-top: 36px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 22px;
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.06);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-nav-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none !important;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-nav-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Vorheriger Schritt (Secondary Button Card) */
|
||||||
|
.lesson-nav-prev {
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0f172a !important;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
}
|
||||||
|
.lesson-nav-prev:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-color: #94a3b8;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
color: #0284c7 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nächster Schritt (Primary Glowing Button Card) */
|
||||||
|
.lesson-nav-next {
|
||||||
|
background: linear-gradient(135deg, #0284c7 0%, #1d4ed8 100%);
|
||||||
|
color: #ffffff !important;
|
||||||
|
border: 1px solid #0284c7;
|
||||||
|
box-shadow: 0 4px 14px rgba(2, 132, 199, 0.35);
|
||||||
|
}
|
||||||
|
.lesson-nav-next:hover {
|
||||||
|
background: linear-gradient(135deg, #0369a1 0%, #1e40af 100%);
|
||||||
|
box-shadow: 0 6px 20px rgba(2, 132, 199, 0.5);
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kurs-Sprung Vorheriger Kurs */
|
||||||
|
.lesson-nav-course-prev {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #334155 !important;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
}
|
||||||
|
.lesson-nav-course-prev:hover {
|
||||||
|
background: #e2e8f0;
|
||||||
|
border-color: #94a3b8;
|
||||||
|
color: #0f172a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kurs-Sprung Nächster Kurs (Emerald Accent) */
|
||||||
|
.lesson-nav-course-next {
|
||||||
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||||
|
color: #ffffff !important;
|
||||||
|
border: 1px solid #10b981;
|
||||||
|
box-shadow: 0 4px 14px rgba(16, 185, 129, 0.35);
|
||||||
|
}
|
||||||
|
.lesson-nav-course-next:hover {
|
||||||
|
background: linear-gradient(135deg, #059669 0%, #047857 100%);
|
||||||
|
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.5);
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-nav-center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-nav-dashboard {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #334155 !important;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
.lesson-nav-dashboard:hover {
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: #0f172a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-nav-ide {
|
||||||
|
background: #10b981;
|
||||||
|
color: #ffffff !important;
|
||||||
|
border: 1px solid #059669;
|
||||||
|
padding: 10px 18px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||||
|
}
|
||||||
|
.lesson-nav-ide:hover {
|
||||||
|
background: #059669;
|
||||||
|
box-shadow: 0 6px 18px rgba(16, 185, 129, 0.45);
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.nav-subtitle,
|
||||||
|
.nav-sub {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.nav-title {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 800;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 210px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.lesson-nav-next .nav-text,
|
||||||
|
.lesson-nav-course-next .nav-text {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.nav-arrow {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
💻 WORKSPACE TOPBAR & NAVIGATION BUTTON CLUSTER
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.workspace-nav-cluster {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-topbar-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 5px 11px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #f1f5f9 !important;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none !important;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-topbar-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.18);
|
||||||
|
color: #ffffff !important;
|
||||||
|
border-color: rgba(255, 255, 255, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-topbar-btn.primary {
|
||||||
|
background: #0284c7;
|
||||||
|
border-color: #38bdf8;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-topbar-btn.primary:hover {
|
||||||
|
background: #0369a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-topbar-btn.success {
|
||||||
|
background: #10b981;
|
||||||
|
border-color: #34d399;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-topbar-btn.success:hover {
|
||||||
|
background: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-topbar-btn.disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-chapter-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 7px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(56, 189, 248, 0.18);
|
||||||
|
border: 1px solid rgba(56, 189, 248, 0.35);
|
||||||
|
color: #38bdf8;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.course-module-navigator {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.lesson-nav-btn {
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.lesson-nav-center {
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.lesson-nav-center .lesson-nav-btn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.nav-title {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.workspace-nav-cluster #btn-topbar-prev-course,
|
||||||
|
.workspace-nav-cluster #btn-topbar-next-course {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,10 +223,21 @@
|
||||||
<!-- Topbar -->
|
<!-- Topbar -->
|
||||||
<div class="workspace-topbar">
|
<div class="workspace-topbar">
|
||||||
<div class="workspace-title">
|
<div class="workspace-title">
|
||||||
<a href="dashboard.html" style="color: #94a3b8; text-decoration: none; font-size: 0.88rem;">🏠 Dashboard</a>
|
<button type="button" id="btn-toggle-chapter-drawer" class="workspace-topbar-btn" title="Kapitel-Übersicht öffnen">
|
||||||
<span style="color: #64748b;">/</span>
|
<span>☰</span> <span>Kapitel</span>
|
||||||
<span id="workspace-chapter-name">Lerneinheit...</span>
|
</button>
|
||||||
<span id="editor-save-indicator" style="font-size: 0.75rem; color: #10b981; opacity: 0.5; margin-left: 10px;">💾 Gespeichert</span>
|
<a href="dashboard.html" id="btn-topbar-dashboard" class="workspace-topbar-btn" title="Zurück zum Lern-Dashboard">
|
||||||
|
<span>🏠</span> <span>Dashboard</span>
|
||||||
|
</a>
|
||||||
|
<div class="workspace-nav-cluster" id="workspace-nav-cluster">
|
||||||
|
<button type="button" class="workspace-topbar-btn" id="btn-topbar-prev-course" title="Vorheriger Kurs">⏮ Kurs</button>
|
||||||
|
<button type="button" class="workspace-topbar-btn" id="btn-topbar-prev-chapter" title="Vorheriges Modul">◀ Zurück</button>
|
||||||
|
<span class="workspace-chapter-badge" id="workspace-chapter-badge">Modul - / -</span>
|
||||||
|
<button type="button" class="workspace-topbar-btn primary" id="btn-topbar-next-chapter" title="Nächstes Modul">Weiter ▶</button>
|
||||||
|
<button type="button" class="workspace-topbar-btn" id="btn-topbar-next-course" title="Nächster Kurs">Kurs ⏭</button>
|
||||||
|
</div>
|
||||||
|
<span id="workspace-chapter-name" style="font-size: 0.84rem; font-weight: 700; color: #f8fafc; margin-left: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 260px;">Lerneinheit...</span>
|
||||||
|
<span id="editor-save-indicator" style="font-size: 0.75rem; color: #10b981; opacity: 0.7; margin-left: 8px;">💾 Gespeichert</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="workspace-actions">
|
<div class="workspace-actions">
|
||||||
|
|
@ -524,6 +535,9 @@
|
||||||
const doc = parser.parseFromString(htmlDoc, "text/html");
|
const doc = parser.parseFromString(htmlDoc, "text/html");
|
||||||
const mainContent = doc.querySelector("main.container") || doc.body;
|
const mainContent = doc.querySelector("main.container") || doc.body;
|
||||||
document.getElementById("tab-content-theorie").innerHTML = mainContent.innerHTML;
|
document.getElementById("tab-content-theorie").innerHTML = mainContent.innerHTML;
|
||||||
|
if (window.renderWorkspaceTheoryNavigator) {
|
||||||
|
window.renderWorkspaceTheoryNavigator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Subgoals (Exakte Regex ohne Duplikate)
|
// 2. Subgoals (Exakte Regex ohne Duplikate)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue