blog.minskio.co.uk

Content and theme behind minskio.co.uk
Log | Files | Refs

search.html (3308B)


      1 <script src="https://unpkg.com/lunr/lunr.js"></script>
      2 <input type="text" id="searchBox" placeholder="Search...">
      3 <script>
      4   let idx = null;
      5   let store = null;
      6   let originalContent = "";
      7 
      8   // Escape regex special chars in search terms
      9   function escapeRegExp(string) {
     10     return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
     11   }
     12 
     13   // Highlight matched terms in text
     14   function highlightTerms(text, terms) {
     15     if (!terms.length) return text;
     16     const regex = new RegExp(`\\b(${terms.map(t => escapeRegExp(t)).join("|")})\\b`, "gi");
     17     return text.replace(regex, "<mark>$1</mark>");
     18   }
     19 
     20   // Get snippet around first matched term and highlight terms
     21   function getSnippetWithHighlight(text, terms, contextWords = 40) {
     22     if (!terms.length) return text;
     23 
     24     const div = document.createElement("div");
     25     div.innerHTML = text;
     26     const plainText = div.textContent || div.innerText || "";
     27 
     28     const words = plainText.split(/\s+/);
     29     let matchIndex = -1;
     30 
     31     for (let i = 0; i < words.length; i++) {
     32       if (terms.some(term => words[i].toLowerCase().includes(term.toLowerCase()))) {
     33         matchIndex = i;
     34         break;
     35       }
     36     }
     37 
     38     if (matchIndex === -1) {
     39       const snippet = words.slice(0, contextWords).join(" ");
     40       return snippet + "…";
     41     }
     42 
     43     const start = Math.max(0, Math.floor(matchIndex - contextWords / 2));
     44     const end = Math.min(words.length, Math.floor(matchIndex + contextWords / 2));
     45     const snippet = words.slice(start, end).join(" ");
     46 
     47     const highlighted = highlightTerms(snippet, terms);
     48     return highlighted + "…";
     49   }
     50 
     51   // Load Lunr index and data
     52   fetch("/index.json")
     53     .then(response => response.json())
     54     .then(data => {
     55       store = data;
     56 
     57       idx = lunr(function () {
     58         this.ref("permalink");
     59         this.field("title");
     60         this.field("content");
     61 
     62         data.forEach(doc => this.add(doc), this);
     63       });
     64     });
     65 
     66   // Save original content for restoring
     67   document.addEventListener("DOMContentLoaded", () => {
     68     const content = document.getElementById("content");
     69     if (content) {
     70       originalContent = content.innerHTML;
     71     }
     72   });
     73 
     74   // Search input event handler
     75   document.getElementById("searchBox").addEventListener("input", function () {
     76     const query = this.value.trim();
     77     const content = document.getElementById("content");
     78 
     79     if (!idx || !store || !content) return;
     80 
     81     if (!query) {
     82       content.innerHTML = originalContent;
     83       return;
     84     }
     85 
     86     const results = idx.search(query);
     87     const terms = query.split(/\s+/).filter(term => term.length > 1);
     88 
     89     if (results.length === 0) {
     90       content.innerHTML = `<p>No results found for "<strong>${query}</strong>".</p>`;
     91       return;
     92     }
     93 
     94     let html = `<h2>Search Results for "<em>${query}</em>":</h2><ul>`;
     95     results.forEach(result => {
     96       const item = store.find(page => page.permalink === result.ref);
     97       if (!item) return; // safety check
     98 
     99       const highlightedTitle = highlightTerms(item.title, terms);
    100       const highlightedSummary = getSnippetWithHighlight(item.summary, terms, 40);
    101 
    102       html += `
    103         <li>
    104           <a href="${item.permalink}">${highlightedTitle}</a>
    105           <p>${highlightedSummary}</p>
    106         </li>`;
    107     });
    108     html += "</ul>";
    109     content.innerHTML = html;
    110   });
    111 </script>