fix: Filter-Navigation vollstaendig implementiert - hashchange-Event-Listener und Navigation-Klick-Handler aufgeraeumt
- toten Nav-Link 'Kategorien' entfernt (type existiert nicht in templates.json) - Filter-State (currentType, currentQuery) + gemeinsamer applyFilters()-Helper statt drei duplizierter Bloecke (hashchange, nav-click, init). Behebt Active-Class-Inkonsistenz zwischen Initial-Load und hashchange-Handler. - Such- und Typ-Filter jetzt gekoppelt: applyFilters wendet beide kombiniert auf allTemplates an (kein Cache-Bypass via loadTemplates mehr). - setNavActive leitet den aktiven Link aus dem href ab, nicht aus textContent -> keine Sonderbehandlung fuer 'Alle' noetig. Verifiziert: JS parst (node --check), GET / 200, applyFilters hat genau eine Definition, search- und hashchange-Handler rufen loadTemplates nicht mehr auf. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9be2ccfd9b
commit
1e2072c4fe
1 changed files with 48 additions and 61 deletions
109
web/index.html
109
web/index.html
|
|
@ -510,7 +510,6 @@
|
||||||
<a href="#?type=system">System</a>
|
<a href="#?type=system">System</a>
|
||||||
<a href="#?type=user">User</a>
|
<a href="#?type=user">User</a>
|
||||||
<a href="#?type=custom">Custom</a>
|
<a href="#?type=custom">Custom</a>
|
||||||
<a href="#?type=categories">Kategorien</a>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="filter-bar">
|
<div class="filter-bar">
|
||||||
|
|
@ -939,16 +938,44 @@ $ python web/serve.py</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter state
|
||||||
|
let currentType = null;
|
||||||
|
let currentQuery = '';
|
||||||
|
|
||||||
|
function parseTypeFromHash() {
|
||||||
|
const match = window.location.hash.match(/[?&]type=([^&]+)/);
|
||||||
|
return match ? decodeURIComponent(match[1]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNavActive(type) {
|
||||||
|
document.querySelectorAll('.nav a').forEach(a => {
|
||||||
|
const m = (a.getAttribute('href') || '').match(/type=([^&]+)/);
|
||||||
|
const aType = m ? m[1] : null;
|
||||||
|
a.classList.toggle('active', aType === type);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilters() {
|
||||||
|
let list = allTemplates;
|
||||||
|
if (currentType) {
|
||||||
|
list = list.filter(t => t.type === currentType);
|
||||||
|
}
|
||||||
|
if (currentQuery) {
|
||||||
|
const q = currentQuery;
|
||||||
|
list = list.filter(t =>
|
||||||
|
(t.name || '').toLowerCase().includes(q) ||
|
||||||
|
(t.description || '').toLowerCase().includes(q) ||
|
||||||
|
(t.tags || []).some(tag => tag.toLowerCase().includes(q))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setNavActive(currentType);
|
||||||
|
renderTemplates(list);
|
||||||
|
}
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
document.getElementById('search').addEventListener('input', async (e) => {
|
document.getElementById('search').addEventListener('input', (e) => {
|
||||||
const query = e.target.value.toLowerCase();
|
currentQuery = e.target.value.toLowerCase();
|
||||||
const templates = await loadTemplates();
|
applyFilters();
|
||||||
const filtered = templates.filter(t =>
|
|
||||||
t.name.toLowerCase().includes(query) ||
|
|
||||||
t.description.toLowerCase().includes(query) ||
|
|
||||||
t.tags.some(tag => tag.toLowerCase().includes(query))
|
|
||||||
);
|
|
||||||
renderTemplates(filtered);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close modal on overlay click
|
// Close modal on overlay click
|
||||||
|
|
@ -961,69 +988,29 @@ $ python web/serve.py</div>
|
||||||
if (e.key === 'Escape') closeModal();
|
if (e.key === 'Escape') closeModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle navigation filter clicks via hash
|
// Hash -> type filter
|
||||||
window.addEventListener('hashchange', async () => {
|
window.addEventListener('hashchange', () => {
|
||||||
const hash = window.location.hash.substring(2);
|
currentType = parseTypeFromHash();
|
||||||
let templates = await loadTemplates();
|
applyFilters();
|
||||||
let typeFilter = hash.split('=')[1];
|
|
||||||
|
|
||||||
// Filter templates by type
|
|
||||||
if (typeFilter && typeFilter !== 'Alle') {
|
|
||||||
templates = templates.filter(t => t.type === typeFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update active state in navigation
|
|
||||||
document.querySelectorAll('.nav a').forEach(a => {
|
|
||||||
a.classList.remove('active');
|
|
||||||
if (a.textContent === 'Alle' && (!typeFilter || typeFilter === 'Alle')) {
|
|
||||||
a.classList.add('active');
|
|
||||||
} else if (a.getAttribute('href').includes(`type=${typeFilter}`)) {
|
|
||||||
a.classList.add('active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
renderTemplates(templates);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Click handler for navigation
|
// Nav clicks set the hash; hashchange drives filtering
|
||||||
document.querySelectorAll('.nav a').forEach(link => {
|
document.querySelectorAll('.nav a').forEach(link => {
|
||||||
link.addEventListener('click', (e) => {
|
link.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// Spezialbehandlung für "Alle"
|
const target = link.getAttribute('href') || '#';
|
||||||
if (link.textContent.trim() === 'Alle') {
|
if (window.location.hash === target || (target === '#' && window.location.hash === '')) {
|
||||||
window.location.hash = '#';
|
return; // same target, no hashchange would fire
|
||||||
} else {
|
|
||||||
const href = link.getAttribute('href');
|
|
||||||
const type = href.substring(href.indexOf('=') + 1);
|
|
||||||
window.location.hash = `#?type=${type}`;
|
|
||||||
}
|
}
|
||||||
|
window.location.hash = target;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initial load
|
// Initial load
|
||||||
loadTemplates().then(t => {
|
loadTemplates().then(t => {
|
||||||
allTemplates = t;
|
allTemplates = t;
|
||||||
|
currentType = parseTypeFromHash();
|
||||||
// Check for initial hash
|
applyFilters();
|
||||||
const hash = window.location.hash.substring(2);
|
|
||||||
let typeFilter = hash ? hash.split('=')[1] : undefined;
|
|
||||||
let filteredTemplates = t;
|
|
||||||
|
|
||||||
if (typeFilter && typeFilter !== 'Alle') {
|
|
||||||
filteredTemplates = t.filter(template => template.type === typeFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update active state
|
|
||||||
document.querySelectorAll('.nav a').forEach(a => {
|
|
||||||
a.classList.remove('active');
|
|
||||||
if (!typeFilter || a.textContent === 'Alle') {
|
|
||||||
a.classList.add('active');
|
|
||||||
} else if (a.getAttribute('href').includes(`type=${typeFilter}`)) {
|
|
||||||
a.classList.add('active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
renderTemplates(filteredTemplates);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue