diff options
Diffstat (limited to 'themes/simple/layouts/partials')
| -rw-r--r-- | themes/simple/layouts/partials/head.html | 2 | ||||
| -rw-r--r-- | themes/simple/layouts/partials/navigation.html | 8 | ||||
| -rw-r--r-- | themes/simple/layouts/partials/search.html | 123 |
3 files changed, 132 insertions, 1 deletions
diff --git a/themes/simple/layouts/partials/head.html b/themes/simple/layouts/partials/head.html index 6d40525..fc481fc 100644 --- a/themes/simple/layouts/partials/head.html +++ b/themes/simple/layouts/partials/head.html | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | 6 | ||
| 7 | <link rel="alternate" type="application/rss+xml" href="/index.xml" title="{{ .Site.Author.name }}"> | 7 | <link rel="alternate" type="application/rss+xml" href="/index.xml" title="{{ .Site.Author.name }}"> |
| 8 | <link rel="alternate" type="application/rss+xml" href="/notes.xml" title="{{ .Site.Author.name }} - Notes"> | 8 | <link rel="alternate" type="application/rss+xml" href="/notes/index.xml" title="{{ .Site.Author.name }} - Notes"> |
| 9 | 9 | ||
| 10 | <link rel="stylesheet" href="/general/index.css?v={{ $cachebuster }}"> | 10 | <link rel="stylesheet" href="/general/index.css?v={{ $cachebuster }}"> |
| 11 | 11 | ||
diff --git a/themes/simple/layouts/partials/navigation.html b/themes/simple/layouts/partials/navigation.html index da42679..b65fe35 100644 --- a/themes/simple/layouts/partials/navigation.html +++ b/themes/simple/layouts/partials/navigation.html | |||
| @@ -7,6 +7,14 @@ | |||
| 7 | <nav itemscope itemtype="http://schema.org/SiteNavigationElement" class="flex items-center gap-1" role="toolbar"> | 7 | <nav itemscope itemtype="http://schema.org/SiteNavigationElement" class="flex items-center gap-1" role="toolbar"> |
| 8 | <meta itemprop="name" content="Main Menu"> | 8 | <meta itemprop="name" content="Main Menu"> |
| 9 | 9 | ||
| 10 | <!-- Search button --> | ||
| 11 | <span class="search-button flex gap-1 items-center text-gray-500 bg-gray-100 hover:bg-gray-200 rounded px-2 py-1 text-xs cursor-pointer mr-2 hidden" onclick="showSearchModal()"> | ||
| 12 | <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor" class="w-4 h-4"> | ||
| 13 | <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" /> | ||
| 14 | </svg> | ||
| 15 | <span class="search-button-text uppercase font-bold"></span> | ||
| 16 | </span> | ||
| 17 | |||
| 10 | <a href="/notes.html" class="font-medium px-2 hover:bg-yellow-100">Notes</a> | 18 | <a href="/notes.html" class="font-medium px-2 hover:bg-yellow-100">Notes</a> |
| 11 | <a href="https://telegram.me/mitjafelicijan" target="_blank" rel="noopener nofollow" itemprop="url" class="font-medium px-2 hover:bg-yellow-100">Telegram</a> | 19 | <a href="https://telegram.me/mitjafelicijan" target="_blank" rel="noopener nofollow" itemprop="url" class="font-medium px-2 hover:bg-yellow-100">Telegram</a> |
| 12 | <a href="https://git.mitjafelicijan.com" target="_blank" rel="noopener nofollow" itemprop="url" class="font-medium px-2 hover:bg-yellow-100">Git</a> | 20 | <a href="https://git.mitjafelicijan.com" target="_blank" rel="noopener nofollow" itemprop="url" class="font-medium px-2 hover:bg-yellow-100">Git</a> |
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> | ||
