summaryrefslogtreecommitdiff
path: root/assets/js/search.js
blob: 5308ce0f4f18885b26db18eb5856ddf825d48dff (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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/* Search Entire Site for Content */
function initSearch() {
    const input = document.getElementById("search-input");
    // Only initialize search on the search page
    if (!input) return;

    const resetBtn = document.getElementById("search-reset");
    const resultsCount = document.querySelector(".search-results__count");
    const resultsList = document.querySelector(".search-results__list");
    const template = document.getElementById("search-result-template");

    let allPosts = [];
    let searchTimeout;

    // Get JSON file with data of all posts
    fetch(input.dataset.indexUrl ?? "/index.json")
        .then((res) => res.json())
        .then((data) => allPosts = data)
        .catch((err) => console.error("Search index.json failed to load.", err));

    function clearResults() {
        resultsCount.hidden = true;
        resultsList.innerHTML = "";
        resultsList.hidden = true;
    }

    function renderSearchResults(matches) {
        clearResults();

        // Display how many results found for query
        resultsCount.hidden = false;
        resultsCount.querySelector("#search-results-number").textContent = String(matches.length ?? 0);

        // No posts matching query found
        if (!matches.length) return;

        // Hydrate post-card list item(s) from the template with JSON data
        matches.forEach((post) => {
            const li = template.content.firstElementChild.cloneNode(true);

            const link = li.querySelector(".post-card__link");
            link.href = post.url;
            link.innerHTML = post.title;

            const section = li.querySelector(".post-card__section-badge");
            if (section) section.textContent = post.section;

            const date = li.querySelector(".post-card__publish-date");
            date.textContent = new Date(post.date).toLocaleDateString();
            date.setAttribute("datetime", post.date);

            const summary = li.querySelector(".post-card__summary");
            if (summary) summary.textContent = post.summary;

            const tagsList = li.querySelector(".post-card__tags-list");
            if (tagsList) {
                tagsList.innerHTML = "";
                if (post.tags && post.tags.length) {
                    post.tags.slice(0, 3).forEach((tag) => {
                        const liTag = document.createElement("li");
                        liTag.className = "post-card__tags-item";
                        liTag.textContent = `#${tag}`;
                        tagsList.appendChild(liTag);
                    });

                    if (post.tags.length > 3) {
                        const more = document.createElement("li");
                        more.className = "post-card__tags-item post-card__tags-more";
                        more.textContent = `+${post.tags.length - 3}`;
                        tagsList.appendChild(more);
                    }
                }
            }

            resultsList.appendChild(li);
            resultsList.hidden = false;
        });
    }

    // Filter all posts for matches with the user's search query
    function searchPosts(query) {
        const normalizedQuery = query.trim().toLowerCase();

        // Only search if user entered at least 3 chars
        if (normalizedQuery.length < 3) {
            clearResults();
            return;
        }

        const matches = allPosts.filter((post) => (
            post.title.toLowerCase().includes(normalizedQuery) ||
            (post.summary && post.summary.toLowerCase().includes(normalizedQuery))
        ));

        // At least 1 post matching query found
        renderSearchResults(matches);
    }

    input.addEventListener("input", (event) => {
        clearTimeout(searchTimeout);
        // Debounce search
        searchTimeout = setTimeout(() => {
            searchPosts(event.target.value);
        }, 300);
    });

    resetBtn.addEventListener("click", () => {
        input.value = "";
        input.focus();
        clearResults();
    });

    // Focus input on page load - it's what the user is here for
    input.focus();
}

export default initSearch;