summaryrefslogtreecommitdiff
path: root/static/js/filterCards.js
blob: a24137f45ee9561e308d5f19fd9bf6cbc95e721e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
document.addEventListener('DOMContentLoaded', () => {
    const cards = document.querySelectorAll('.card');
    const filterLinks = document.querySelectorAll('.filter-controls a');
    const allProjectsFilter = document.querySelector('#all-projects-filter');
    if (!cards.length || !filterLinks.length) return;
    allProjectsFilter.style.display = 'block';

    // Create a Map for O(1) lookups of links by filter value.
    const linkMap = new Map(
        Array.from(filterLinks).map(link => [link.dataset.filter, link])
    );

    // Pre-process cards data for faster filtering.
    const cardData = Array.from(cards).map(card => ({
        element: card,
        tags: card.dataset.tags?.toLowerCase().split(',').filter(Boolean) ?? []
    }));

    function getTagSlugFromUrl(url) {
        return url.split('/').filter(Boolean).pop();
    }

    function getFilterFromHash() {
        if (!window.location.hash) return 'all';
        const hash = decodeURIComponent(window.location.hash.slice(1));
        const matchingLink = Array.from(filterLinks).find(link =>
            getTagSlugFromUrl(link.getAttribute('href')) === hash
        );
        return matchingLink?.dataset.filter ?? 'all';
    }

    function setActiveFilter(filterValue, updateHash = true) {
        if (updateHash) {
            if (filterValue === 'all') {
                history.pushState(null, '', window.location.pathname);
            } else {
                const activeLink = linkMap.get(filterValue);
                if (activeLink) {
                    const tagSlug = getTagSlugFromUrl(activeLink.getAttribute('href'));
                    history.pushState(null, '', `#${tagSlug}`);
                }
            }
        }
        const isAll = filterValue === 'all';
        const display = isAll ? '' : 'none';
        const ariaHidden = isAll ? 'false' : 'true';
        requestAnimationFrame(() => {
            filterLinks.forEach(link => {
                const isActive = link.dataset.filter === filterValue;
                link.classList.toggle('active', isActive);
                link.setAttribute('aria-pressed', isActive);
            });
            if (isAll) {
                cardData.forEach(({ element }) => {
                    element.style.display = display;
                    element.setAttribute('aria-hidden', ariaHidden);
                });
            } else {
                cardData.forEach(({ element, tags }) => {
                    const shouldShow = tags.includes(filterValue);
                    element.style.display = shouldShow ? '' : 'none';
                    element.setAttribute('aria-hidden', !shouldShow);
                });
            }
        });
    }

    const filterContainer = filterLinks[0].parentElement.parentElement;
    filterContainer.addEventListener('click', e => {
        const link = e.target.closest('a');
        if (!link) return;
        e.preventDefault();
        const filterValue = link.dataset.filter;
        if (filterValue) setActiveFilter(filterValue);
    });

    filterContainer.addEventListener('keydown', e => {
        const link = e.target.closest('a');
        if (!link) return;
        if (e.key === ' ' || e.key === 'Enter') {
            e.preventDefault();
            link.click();
        }
    });

    filterLinks.forEach(link => {
        link.setAttribute('role', 'button');
        link.setAttribute('aria-pressed', link.classList.contains('active'));
    });

    window.addEventListener('popstate', () => {
        setActiveFilter(getFilterFromHash(), false);
    });

    const initialFilter = getFilterFromHash();
    if (initialFilter !== 'all') {
        setActiveFilter(initialFilter, false);
    }
});