diff options
| author | Mitja Felicijan <m@mitjafelicijan.com> | 2023-06-29 18:40:17 +0200 |
|---|---|---|
| committer | Mitja Felicijan <m@mitjafelicijan.com> | 2023-06-29 18:40:17 +0200 |
| commit | 93d47662b2ce5a6dc4867687386c912e8cd13720 (patch) | |
| tree | a609e947843bdd297121fd49a9e16e3dea2a1fd4 /themes/simple/layouts/partials/search.html | |
| parent | 89f8bc803364ca52ca0dee1a429261b9f3d9ed0e (diff) | |
| download | mitjafelicijan.com-93d47662b2ce5a6dc4867687386c912e8cd13720.tar.gz | |
Added search option with lunr.js
Diffstat (limited to 'themes/simple/layouts/partials/search.html')
| -rw-r--r-- | themes/simple/layouts/partials/search.html | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/themes/simple/layouts/partials/search.html b/themes/simple/layouts/partials/search.html new file mode 100644 index 0000000..3e31753 --- /dev/null +++ b/themes/simple/layouts/partials/search.html | |||
| @@ -0,0 +1,123 @@ | |||
| 1 | <section class="search-modal mb-10 hidden"> | ||
| 2 | <input class="bg-gray-100 w-full px-3 py-2 rounded outline-none" type="search" placeholder="Search here..."> | ||
| 3 | <section class="results bg-white border border-gray-200 shadow-md p-2 flex flex-col gap-2 rounded mt-4 hidden"></section> | ||
| 4 | </section> | ||
| 5 | |||
| 6 | <script src="https://unpkg.com/lunr/lunr.js"></script> | ||
| 7 | <script> | ||
| 8 | (async function() { | ||
| 9 | const debounceDelay = 700; | ||
| 10 | |||
| 11 | // Fetch search index generated by Hugo. | ||
| 12 | const req = await fetch('/index.json'); | ||
| 13 | window.searchDocuments = await req.json(); | ||
| 14 | |||
| 15 | window.searchIndex = lunr(function() { | ||
| 16 | this.ref('permalink'); | ||
| 17 | this.field('title', { boost: 20 }); | ||
| 18 | this.field('tags', { boost: 10 }); | ||
| 19 | this.field('summary', { boost: 1 }); | ||
| 20 | |||
| 21 | window.searchDocuments.forEach(function(doc) { | ||
| 22 | this.add(doc); | ||
| 23 | }, this); | ||
| 24 | |||
| 25 | console.log('Search index processed...'); | ||
| 26 | }) | ||
| 27 | |||
| 28 | // Connect DOM and search the index. | ||
| 29 | let cachedSearchTerm = null; | ||
| 30 | const searchModal = document.querySelector('.search-modal'); | ||
| 31 | const searchInput = document.querySelector('.search-modal input'); | ||
| 32 | const searchResults = document.querySelector('.search-modal .results'); | ||
| 33 | |||
| 34 | // Display search modal. | ||
| 35 | window.showSearchModal = function() { | ||
| 36 | searchModal.classList.remove('hidden'); | ||
| 37 | searchInput.focus(); | ||
| 38 | } | ||
| 39 | |||
| 40 | // Detect OS and sets proper search button text. | ||
| 41 | const searchButtonElement = document.querySelector('.search-button'); | ||
| 42 | const searchButtonTextElement = document.querySelector('.search-button-text'); | ||
| 43 | if (searchButtonElement) { | ||
| 44 | let searchButtonText = 'ctrl+k'; | ||
| 45 | if (navigator.platform.toUpperCase().indexOf('MAC') >= 0) { | ||
| 46 | searchButtonText = 'cmd+k'; | ||
| 47 | } | ||
| 48 | searchButtonTextElement.innerText = searchButtonText; | ||
| 49 | searchButtonElement.classList.remove('hidden'); | ||
| 50 | } | ||
| 51 | |||
| 52 | // On keyboard shortcut shows search modal. | ||
| 53 | document.addEventListener('keydown', function(event) { | ||
| 54 | // Handles macOS CMD+k. | ||
| 55 | if ((event.ctrlKey || event.metaKey) && event.key === 'k') { | ||
| 56 | event.preventDefault(); | ||
| 57 | showSearchModal(); | ||
| 58 | } | ||
| 59 | |||
| 60 | // Handles Windows/Linux Ctrl+k. | ||
| 61 | if (event.ctrlKey && event.key === 'k') { | ||
| 62 | event.preventDefault(); | ||
| 63 | showSearchModal(); | ||
| 64 | } | ||
| 65 | }); | ||
| 66 | |||
| 67 | // Debounce magic. | ||
| 68 | function debounce(func, delay) { | ||
| 69 | let timerId; | ||
| 70 | return function (...args) { | ||
| 71 | clearTimeout(timerId); | ||
| 72 | timerId = setTimeout(() => { | ||
| 73 | func.apply(this, args); | ||
| 74 | }, delay); | ||
| 75 | }; | ||
| 76 | } | ||
| 77 | |||
| 78 | // Do the actual search. | ||
| 79 | searchInput.addEventListener('keyup', debounce((evt)=> { | ||
| 80 | const query = evt.target.value.trim().toLowerCase(); | ||
| 81 | if (query.length && query != cachedSearchTerm) { | ||
| 82 | const results = searchIndex.search(query); | ||
| 83 | |||
| 84 | if (results.length == 0) { | ||
| 85 | searchResults.classList.add('hidden'); | ||
| 86 | } else { | ||
| 87 | searchResults.innerText = ''; | ||
| 88 | searchResults.classList.remove('hidden'); | ||
| 89 | cachedSearchTerm = query; | ||
| 90 | |||
| 91 | results.forEach(resultItem => { | ||
| 92 | const item = window.searchDocuments.find(doc => doc.permalink === resultItem.ref); | ||
| 93 | |||
| 94 | const link = document.createElement('a'); | ||
| 95 | link.href = item.permalink; | ||
| 96 | link.classList.add('hover:bg-gray-100', 'cursor-pointer', 'py-2', 'px-3', 'rounded'); | ||
| 97 | |||
| 98 | const title = document.createElement('div'); | ||
| 99 | title.classList.add('text-gray-800', 'font-medium'); | ||
| 100 | title.innerHTML = item.title; | ||
| 101 | link.appendChild(title); | ||
| 102 | |||
| 103 | const meta = document.createElement('div'); | ||
| 104 | meta.classList.add('text-gray-500', 'flex', 'items-center', 'gap-2'); | ||
| 105 | |||
| 106 | const section = document.createElement('span'); | ||
| 107 | section.classList.add('uppercase', 'text-xs', 'font-medium', 'bg-gray-200', 'px-1', 'rounded') | ||
| 108 | section.innerText = item.type; | ||
| 109 | meta.appendChild(section); | ||
| 110 | |||
| 111 | const summary = document.createElement('span'); | ||
| 112 | const summaryText = item.summary.length > 80 ? `${item.summary.substring(0, 80)}...` : item.summary; | ||
| 113 | summary.innerHTML = summaryText; | ||
| 114 | meta.appendChild(summary); | ||
| 115 | |||
| 116 | link.appendChild(meta); | ||
| 117 | searchResults.appendChild(link); | ||
| 118 | }); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | }, debounceDelay)); | ||
| 122 | })(); | ||
| 123 | </script> | ||
