summaryrefslogtreecommitdiff
path: root/assets/js/search.js
diff options
context:
space:
mode:
authorArne Rief <riearn@proton.me>2025-12-22 14:02:07 +0100
committerArne Rief <riearn@proton.me>2025-12-22 14:02:07 +0100
commit038054b8206a9c25e84adeb0f0f355abd22d6143 (patch)
tree47700e52b88e0db08f0ca113de22f4c0d05cc405 /assets/js/search.js
parent32c1a5dd203435e8bef324306d1516e28ce14615 (diff)
CSS & JS splitting, small fix section badge
Diffstat (limited to 'assets/js/search.js')
-rw-r--r--assets/js/search.js118
1 files changed, 118 insertions, 0 deletions
diff --git a/assets/js/search.js b/assets/js/search.js
new file mode 100644
index 0000000..f10695a
--- /dev/null
+++ b/assets/js/search.js
@@ -0,0 +1,118 @@
+/* Search Entire Site for Content */
+function initSearch() {
+ const input = document.getElementById("search-input");
+ 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");
+
+ // Only initialize search on the search page
+ if (!input || !template) return;
+
+ 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;
+