refactor: improved search

This commit is contained in:
Matthias Jung
2026-04-05 23:10:04 +02:00
parent 5dd4b81e8a
commit bdedf0d16a
+57 -41
View File
@@ -1,5 +1,13 @@
<h1>Fragensuche</h1> <h1>Fragensuche</h1>
<style>
#question-search-results .breadcrumb,
#question-search-results .breadcrumb li,
#question-search-results .breadcrumb-item {
margin-bottom: 0;
}
</style>
<div class="card mb-4"> <div class="card mb-4">
<div class="card-body"> <div class="card-body">
<div class="row g-3 align-items-end"> <div class="row g-3 align-items-end">
@@ -104,7 +112,7 @@
if (query.length === 0) { if (query.length === 0) {
resultsBox.innerHTML = resultsBox.innerHTML =
'<div class="alert alert-light border" role="status">Bitte gib eine Frage-ID ein, z. B. <strong>AB123</strong>.</div>'; '<div class="alert alert-light border" role="status">Bitte gib eine Frage-ID ein, z. B. <strong>VD102</strong>.</div>';
return; return;
} }
@@ -114,7 +122,7 @@
(questionId) => questionId.includes(query) && !questionId.startsWith(query) (questionId) => questionId.includes(query) && !questionId.startsWith(query)
); );
const matches = [...exactMatches, ...startsWithMatches, ...includesMatches].slice(0, 50); const matches = [...exactMatches, ...startsWithMatches, ...includesMatches];
if (matches.length === 0) { if (matches.length === 0) {
resultsBox.innerHTML = resultsBox.innerHTML =
@@ -122,44 +130,52 @@
return; return;
} }
const cards = matches.flatMap((questionId) => { const cards = matches.map((questionId) => {
const entries = Array.isArray(questionIndex[questionId]) ? questionIndex[questionId] : []; const entry = questionIndex[questionId] || {};
return entries const sectionIdentRaw = entry.section || "";
.map((entry) => { const chapterTitle = escapeHtml(entry.chapter_title || "Unbekanntes Kapitel");
const sectionIdentRaw = entry.section || ""; const sectionTitle = escapeHtml(entry.section_title || entry.section || "Unbekannte Sektion");
const chapterTitle = escapeHtml(entry.chapter_title || "Unbekanntes Kapitel"); const hasSolution = Boolean(entry.has_solution);
const sectionTitle = escapeHtml(entry.section_title || entry.section || "Unbekannte Sektion"); const editions = sortEditions(Array.isArray(entry.editions) ? entry.editions : []);
const editions = sortEditions(Array.isArray(entry.editions) ? entry.editions : []); const editionLinks = editions
const editionLinks = editions .map((edition) => {
.map((edition) => { const editionSafe = escapeHtml(edition);
const editionSafe = escapeHtml(edition); const editionLabel = escapeHtml(getEditionLabel(edition));
const editionLabel = escapeHtml(getEditionLabel(edition)); const targetClass = getTargetClass(edition);
const targetClass = getTargetClass(edition); const targetColor = targetColorByClass[targetClass] || "#6c757d";
const targetColor = targetColorByClass[targetClass] || "#6c757d"; const targetTitle = targetClass ? `Zielklasse ${targetClass}` : "Zielklasse unbekannt";
const targetTitle = targetClass ? `Zielklasse ${targetClass}` : "Zielklasse unbekannt"; const href = `${encodeURIComponent(edition)}_${encodeURIComponent(sectionIdentRaw)}.html#${encodeURIComponent(questionId)}`;
const href = `${encodeURIComponent(edition)}_${encodeURIComponent(sectionIdentRaw)}.html#${encodeURIComponent(questionId)}`; return `<a class="btn btn-sm text-white text-decoration-none d-inline-flex align-items-center gap-2 px-3 py-2 text-nowrap" style="background-color: ${targetColor}; border-color: ${targetColor};" href="${href}" title="${escapeHtml(targetTitle)}" aria-label="${editionSafe}: ${editionLabel}"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" fill="currentColor" aria-hidden="true" focusable="false" style="width: 0.95rem; height: 0.95rem;"><path d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"></path></svg>${editionLabel}</a>`;
return `<a class="badge text-white text-decoration-none" style="background-color: ${targetColor};" href="${href}" title="${escapeHtml(targetTitle)}" aria-label="${editionSafe}: ${editionLabel}">${editionLabel}</a>`; })
}) .join("");
.join(""); const solutionLink = hasSolution
return ` ? `<a class="btn btn-sm text-dark d-inline-flex align-items-center gap-2 px-3 py-2 text-nowrap" style="background-color: #ffc107; border-color: #ffc107;" href="${encodeURIComponent(questionId)}.html" aria-label="Lösungsweg zu ${escapeHtml(questionId)}">
<div class="card mb-3"> <svg class="solution-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" fill="currentColor" aria-hidden="true" focusable="false" style="width: 1rem; height: 1rem;">
<div class="card-header"> <path d="M420.9 448C428.2 425.7 442.8 405.5 459.3 388.1C492 353.7 512 307.2 512 256C512 150 426 64 320 64C214 64 128 150 128 256C128 307.2 148 353.7 180.7 388.1C197.2 405.5 211.9 425.7 219.1 448L420.8 448zM416 496L224 496L224 512C224 556.2 259.8 592 304 592L336 592C380.2 592 416 556.2 416 512L416 496zM312 176C272.2 176 240 208.2 240 248C240 261.3 229.3 272 216 272C202.7 272 192 261.3 192 248C192 181.7 245.7 128 312 128C325.3 128 336 138.7 336 152C336 165.3 325.3 176 312 176z"></path>
<nav aria-label="breadcrumb" class="mt-1"> </svg>
<ol class="breadcrumb mb-0 small"> Lösungsweg
<li class="breadcrumb-item"> </a>`
<div class="fw-semibold">${escapeHtml(questionId)}</div> : "";
</li>
<li class="breadcrumb-item">${chapterTitle}</li> return `
<li class="breadcrumb-item">${sectionTitle}</li> <div class="card mb-3">
</ol> <div class="card-header d-flex flex-nowrap align-items-center gap-2 overflow-auto">
</nav> <div class="fw-semibold">${escapeHtml(questionId)}</div>
</div> <nav aria-label="breadcrumb" class="mb-0 d-flex align-items-center text-nowrap">
<div class="card-body py-3"> <ol class="breadcrumb mb-0 align-items-center flex-nowrap">
<div class="d-flex flex-wrap gap-2">${editionLinks || '<span class="badge text-bg-secondary">-</span>'}</div> <li class="breadcrumb-item">${chapterTitle}</li>
</div> <li class="breadcrumb-item">${sectionTitle}</li>
</ol>
</nav>
</div>
<div class="card-body py-3">
<div class="d-flex flex-wrap align-items-center gap-2">
${editionLinks || '<span class="badge text-bg-secondary">-</span>'}
${solutionLink}
</div> </div>
`; </div>
}); </div>
`;
}); });
resultsBox.innerHTML = ` resultsBox.innerHTML = `
@@ -170,7 +186,7 @@
async function loadQuestionIndex() { async function loadQuestionIndex() {
try { try {
const response = await fetch("/question_index.json", { const response = await fetch("/assets/question_index.json", {
cache: "no-cache", cache: "no-cache",
headers: { headers: {
Accept: "application/json", Accept: "application/json",
@@ -200,7 +216,7 @@
errorBox.classList.remove("d-none"); errorBox.classList.remove("d-none");
errorBox.textContent = errorBox.textContent =
"Suchindex konnte nicht geladen werden: " + error.message; "Suchindex konnte nicht geladen werden: " + error.message;
console.error("Failed to load /question_index.json", error); console.error("Failed to load question_index.json", error);
} }
} }