Initial static site implementation
- Node.js build script with gray-matter and marked - Self-hosted fonts (DM Serif Display, Karla) - Swedish badge system for origin transparency - Filtering by category, region, tags, and search - URL-based filter state for shareable links - Individual entry pages - About and badge info pages - Privacy-focused: no cookies, no tracking, no external requests - Hosted in Lerum, Sweden
This commit is contained in:
194
public/filter.js
Normal file
194
public/filter.js
Normal file
@@ -0,0 +1,194 @@
|
||||
// Ursprung Sverige - Filtering functionality
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const categoryFilter = document.getElementById('category-filter');
|
||||
const regionFilter = document.getElementById('region-filter');
|
||||
const tagFilter = document.getElementById('tag-filter');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const clearFiltersBtn = document.getElementById('clear-filters');
|
||||
const entriesGrid = document.getElementById('entries-grid');
|
||||
const visibleCount = document.getElementById('visible-count');
|
||||
const noResults = document.getElementById('no-results');
|
||||
|
||||
if (!entriesGrid) return;
|
||||
|
||||
const entries = Array.from(entriesGrid.querySelectorAll('.entry-card'));
|
||||
|
||||
// Update URL with current filter state
|
||||
function updateURL() {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (categoryFilter?.value) {
|
||||
params.set('category', categoryFilter.value);
|
||||
}
|
||||
if (regionFilter?.value) {
|
||||
params.set('region', regionFilter.value);
|
||||
}
|
||||
if (tagFilter?.value) {
|
||||
params.set('tag', tagFilter.value);
|
||||
}
|
||||
if (searchInput?.value.trim()) {
|
||||
params.set('search', searchInput.value.trim());
|
||||
}
|
||||
|
||||
const queryString = params.toString();
|
||||
const newURL = queryString
|
||||
? `${window.location.pathname}?${queryString}`
|
||||
: window.location.pathname;
|
||||
|
||||
window.history.replaceState({}, '', newURL);
|
||||
}
|
||||
|
||||
// Apply filters from URL on page load
|
||||
function applyFiltersFromURL() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
if (params.has('category') && categoryFilter) {
|
||||
categoryFilter.value = params.get('category');
|
||||
}
|
||||
if (params.has('region') && regionFilter) {
|
||||
regionFilter.value = params.get('region');
|
||||
}
|
||||
if (params.has('tag') && tagFilter) {
|
||||
tagFilter.value = params.get('tag');
|
||||
}
|
||||
if (params.has('search') && searchInput) {
|
||||
searchInput.value = params.get('search');
|
||||
}
|
||||
|
||||
filterEntries(false); // Don't update URL on initial load
|
||||
}
|
||||
|
||||
function filterEntries(shouldUpdateURL = true) {
|
||||
const categoryValue = categoryFilter?.value.toLowerCase() || '';
|
||||
const regionValue = regionFilter?.value.toLowerCase() || '';
|
||||
const tagValue = tagFilter?.value.toLowerCase() || '';
|
||||
const searchValue = searchInput?.value.toLowerCase().trim() || '';
|
||||
|
||||
let visibleEntries = 0;
|
||||
|
||||
entries.forEach(entry => {
|
||||
const entryCategory = (entry.dataset.category || '').toLowerCase();
|
||||
const entryRegion = (entry.dataset.region || '').toLowerCase();
|
||||
const entryTags = (entry.dataset.tags || '').toLowerCase().split(',');
|
||||
const entryText = entry.textContent.toLowerCase();
|
||||
|
||||
const matchesCategory = !categoryValue || entryCategory === categoryValue;
|
||||
const matchesRegion = !regionValue || entryRegion === regionValue;
|
||||
const matchesTag = !tagValue || entryTags.includes(tagValue);
|
||||
const matchesSearch = !searchValue || entryText.includes(searchValue);
|
||||
|
||||
const isVisible = matchesCategory && matchesRegion && matchesTag && matchesSearch;
|
||||
|
||||
entry.hidden = !isVisible;
|
||||
if (isVisible) visibleEntries++;
|
||||
});
|
||||
|
||||
if (visibleCount) {
|
||||
visibleCount.textContent = visibleEntries;
|
||||
}
|
||||
|
||||
if (noResults) {
|
||||
noResults.hidden = visibleEntries > 0;
|
||||
}
|
||||
|
||||
if (entriesGrid) {
|
||||
entriesGrid.hidden = visibleEntries === 0;
|
||||
}
|
||||
|
||||
updateClearButtonVisibility();
|
||||
|
||||
if (shouldUpdateURL) {
|
||||
updateURL();
|
||||
}
|
||||
}
|
||||
|
||||
function updateClearButtonVisibility() {
|
||||
if (!clearFiltersBtn) return;
|
||||
|
||||
const hasFilters =
|
||||
(categoryFilter?.value || '') !== '' ||
|
||||
(regionFilter?.value || '') !== '' ||
|
||||
(tagFilter?.value || '') !== '' ||
|
||||
(searchInput?.value || '') !== '';
|
||||
|
||||
clearFiltersBtn.hidden = !hasFilters;
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
if (categoryFilter) categoryFilter.value = '';
|
||||
if (regionFilter) regionFilter.value = '';
|
||||
if (tagFilter) tagFilter.value = '';
|
||||
if (searchInput) searchInput.value = '';
|
||||
|
||||
filterEntries(); // This will also clear the URL
|
||||
}
|
||||
|
||||
// Handle clicking on filter buttons in cards
|
||||
function handleFilterClick(e) {
|
||||
const btn = e.target.closest('.filter-btn');
|
||||
if (!btn) return;
|
||||
|
||||
const filterType = btn.dataset.filterType;
|
||||
const filterValue = btn.dataset.filterValue;
|
||||
|
||||
if (filterType === 'category' && categoryFilter) {
|
||||
categoryFilter.value = filterValue;
|
||||
} else if (filterType === 'region' && regionFilter) {
|
||||
regionFilter.value = filterValue;
|
||||
} else if (filterType === 'tag' && tagFilter) {
|
||||
tagFilter.value = filterValue;
|
||||
}
|
||||
|
||||
filterEntries();
|
||||
|
||||
// Scroll to top to see filtered results
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// Handle browser back/forward
|
||||
window.addEventListener('popstate', () => {
|
||||
applyFiltersFromURL();
|
||||
});
|
||||
|
||||
// Debounce for search input
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function(...args) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
if (categoryFilter) {
|
||||
categoryFilter.addEventListener('change', () => filterEntries());
|
||||
}
|
||||
|
||||
if (regionFilter) {
|
||||
regionFilter.addEventListener('change', () => filterEntries());
|
||||
}
|
||||
|
||||
if (tagFilter) {
|
||||
tagFilter.addEventListener('change', () => filterEntries());
|
||||
}
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', debounce(() => filterEntries(), 300));
|
||||
}
|
||||
|
||||
if (clearFiltersBtn) {
|
||||
clearFiltersBtn.addEventListener('click', clearFilters);
|
||||
}
|
||||
|
||||
// Delegate click events for filter buttons in cards
|
||||
if (entriesGrid) {
|
||||
entriesGrid.addEventListener('click', handleFilterClick);
|
||||
}
|
||||
|
||||
// Apply URL filters on load
|
||||
applyFiltersFromURL();
|
||||
updateClearButtonVisibility();
|
||||
})();
|
||||
Reference in New Issue
Block a user