Skip to content

Latest commit

 

History

History
156 lines (128 loc) · 4.23 KB

File metadata and controls

156 lines (128 loc) · 4.23 KB
layout title published
search
Client side search
true
<style> input#search { display: block; margin: 0 auto; } input::placeholder { opacity: 0.3; } </style> <script src="/public/js/fuse.js"></script>

<script> let searchIndex = []; let fuse; // Load search index and initialize Fuse async function loadData() { const response = await fetch('../search.json'); searchIndex = await response.json(); fuse = new Fuse(searchIndex, { keys: [ { name: 'title', weight: 0.5 }, { name: 'content', weight: 0.4 }, { name: 'tags', weight: 0.1 } ], includeScore: true, findAllMatches: false, distance: 2000000, includeMatches: true, threshold: 0.3, shouldSort: true, useExtendedSearch: true, }); } // Format date function formatDate(dateString) { const date = new Date(dateString); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${day}. ${month}. ${year}`; } // Generate highlighted link function generateHighlightedLink(item, query) { const url = new URL(item.id, window.location.origin); url.hash = `#:~:text=${encodeURIComponent(query)}`; return url.toString(); } // Perform search, boost newer stuff slightly, updated 19.03.2026 function performSearch(query) { const resultsContainer = document.getElementById('results'); resultsContainer.innerHTML = ''; if (!query || query.length <= 2) return; const now = Date.now() / 1000; // current time in seconds const results = fuse.search(query).map(result => { const item = result.item; // --- TEXT RELEVANCE --- // Fuse score: 0 = perfect match → invert it const relevance = 1 - result.score; // --- RECENCY --- const ageDays = item.timestamp ? (now - item.timestamp) / 86400 : 9999; // fallback for missing timestamps // Smooth decay (new posts get boost, old ones fade) const recency = Math.exp(-ageDays / 30); // ~1.0 = today // ~0.37 = 1 month // ~0.05 = 3 months // --- SMART COMBINATION --- // Only boost when relevance is already decent const boost = recency * 0.15 * relevance; const finalScore = relevance + boost; return { item, finalScore }; }); // Sort by final score results.sort((a, b) => b.finalScore - a.finalScore); // Render results.forEach(({ item }) => { const div = document.createElement('div'); div.className = 'result'; div.innerHTML = ` ${item.title} ${item.date ? ` (${formatDate(item.date)})` : ''}

${item.content.substring(0, 100)}...

`; resultsContainer.appendChild(div); }); } // Update URL without reloading function updateURL(query) { const url = new URL(window.location); if (query) { url.searchParams.set("q", query); } else { url.searchParams.delete("q"); } window.history.replaceState({}, "", url); } // Init everything document.addEventListener("DOMContentLoaded", async () => { await loadData(); const input = document.getElementById('search'); // If URL has ?q= parameter, auto search const params = new URLSearchParams(window.location.search); const initialQuery = params.get("q"); if (initialQuery) { input.value = initialQuery; performSearch(initialQuery); } // Live search while typing input.addEventListener('input', function() { const query = this.value; updateURL(query); performSearch(query); }); }); </script>