From 3a1d508c65d8061013f66171df67e275d17cfac7 Mon Sep 17 00:00:00 2001 From: SauravDhakal Date: Wed, 1 Apr 2026 12:11:17 +0545 Subject: [PATCH] fix: Search --- assets/css/extended/custom.css | 114 +++++++++++++++++++++++- assets/js/fastsearch.js | 153 +++++++++++++++++++++++++++++++++ layouts/_default/index.json | 7 ++ layouts/notes/list.html | 1 + layouts/partials/footer.html | 2 +- layouts/partials/toc.html | 97 +++++++++++++++++++++ layouts/posts/list.html | 1 + 7 files changed, 370 insertions(+), 5 deletions(-) create mode 100644 assets/js/fastsearch.js create mode 100644 layouts/_default/index.json create mode 100644 layouts/partials/toc.html diff --git a/assets/css/extended/custom.css b/assets/css/extended/custom.css index 47392d0..88b6892 100644 --- a/assets/css/extended/custom.css +++ b/assets/css/extended/custom.css @@ -76,14 +76,19 @@ body { * UNIVERSAL HOVER ANIMATION (Fill-up Effect) * Target: Nav spans, main links (except entries), social links, and share icons. * */ + + nav #menu a span { position: relative; text-decoration: none !important; color: inherit; transition: color 0.3s ease; z-index: 1; - display: inline-block; - padding: 0 8px; /* Reduced vertical padding */ + padding: 8px; /* Reduced vertical padding */ +} + +.entry-content { + color: var(--primary) } main a:not(.entry-link, .anchor), @@ -143,6 +148,7 @@ nav #menu a:hover span, main a:hover:not(.entry-link, .anchor), .social-icons a:hover, .share-buttons a:hover, +#searchResults a:hover, .note-item a:hover .note-title, .note-item a:hover .note-date { color: var(--theme) !important; @@ -175,11 +181,12 @@ main h1 { background: transparent; } + .post-tags a { background: var(--tertiary); color: var(--primary); - padding: 4px 12px; - border-radius: var(--radius); + padding: 0px 8px; + border-radius: 0px; transition: all 0.3s ease; } @@ -207,6 +214,28 @@ main h1 { text-decoration: none; } +.breadcrumbs { + margin-bottom: 2rem; + font-size: 0.9rem; + color: var(--secondary); +} + +.post-header .post-title { + font-size: 3rem; + margin-bottom: 0.5rem; + color: var(--green); + font-weight: 700; + line-height: 1.2; +} + +.post-description { + font-style: italic; + font-size: 1.2rem; + margin-bottom: 1.5rem; + color: var(--secondary); + line-height: 1.6; +} + /* main h1::before { */ /* content: "#"; */ /* color: var(--red); */ @@ -271,6 +300,12 @@ main .post-entry:hover { line-height: 1.5; } +.delimiter { + margin: 0 10px; + font-weight: 800; + opacity: 0.5; +} + /* * * POSTS SECTION - Same compact style as notes but with description * */ @@ -301,6 +336,7 @@ main .post-entry:hover { color: inherit; transition: color 0.3s ease; z-index: 1; + width: 100%; } .post-item a::before { @@ -343,6 +379,14 @@ main .post-entry:hover { line-height: 1.5; } +/* Common list styles */ +.note-item a, +.post-item a { + display: flex !important; + justify-content: flex-start; + align-items: center; +} + /* * * POST CONTENT SPACINGS * */ @@ -378,11 +422,73 @@ main .post-entry:hover { } .post-content code { + font-size: 16px; background-color: var(--tertiary); color: var(--primary); border-radius: 4px; } +.highlight { + padding: 0.6em; +} + .post-content pre code { background-color: var(--entry) !important; } + +.post-content blockquote p { + margin: 0; + padding: 0.5em 0em; +} + +/* * +* SEARCH PAGE FIXES +* */ +#searchResults .post-entry { + border-radius: 0; /* Square borders like notes */ + padding: 0; + border: 1px solid var(--tertiary); + margin-bottom: 8px; +} + +#searchResults a { + position: relative; + display: flex !important; + justify-content: flex-start; + align-items: center; + padding: 12px 15px; + text-decoration: none !important; + color: var(--primary); + z-index: 1; +} + +#searchResults a .search-meta { + margin-left: auto; /* Push date to the right */ + font-size: 0.85rem; + color: var(--secondary); +} + +#searchResults a:hover .search-title, +#searchResults a:hover .search-meta { + color: var(--theme) !important; +} + +#searchResults a::before { + content: ""; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 0; + background-color: var(--green); + z-index: -1; + transition: height 0.3s cubic-bezier(0.19, 1, 0.22, 1); +} + +#searchResults a:hover::before { + height: 100%; +} + +#searchResults a:hover { + color: var(--theme) !important; +} diff --git a/assets/js/fastsearch.js b/assets/js/fastsearch.js new file mode 100644 index 0000000..8c92334 --- /dev/null +++ b/assets/js/fastsearch.js @@ -0,0 +1,153 @@ +import * as params from '@params'; + +let fuse; // holds our search engine +let resList = document.getElementById('searchResults'); +let sInput = document.getElementById('searchInput'); +let first, last, current_elem = null +let resultsAvailable = false; + +// load our search index +window.onload = function () { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + let data = JSON.parse(xhr.responseText); + if (data) { + // fuse.js options; check fuse.js website for details + let options = { + distance: 100, + threshold: 0.4, + ignoreLocation: true, + keys: [ + 'title', + 'permalink', + 'summary', + 'content' + ] + }; + if (params.fuseOpts) { + options = { + isCaseSensitive: params.fuseOpts.iscasesensitive ?? false, + includeScore: params.fuseOpts.includescore ?? false, + includeMatches: params.fuseOpts.includematches ?? false, + minMatchCharLength: params.fuseOpts.minmatchcharlength ?? 1, + shouldSort: params.fuseOpts.shouldsort ?? true, + findAllMatches: params.fuseOpts.findallmatches ?? false, + keys: params.fuseOpts.keys ?? ['title', 'permalink', 'summary', 'content'], + location: params.fuseOpts.location ?? 0, + threshold: params.fuseOpts.threshold ?? 0.4, + distance: params.fuseOpts.distance ?? 100, + ignoreLocation: params.fuseOpts.ignorelocation ?? true + } + } + fuse = new Fuse(data, options); // build the index from the json file + } + } else { + console.log(xhr.responseText); + } + } + }; + xhr.open('GET', "../index.json"); + xhr.send(); +} + +function activeToggle(ae) { + document.querySelectorAll('.focus').forEach(function (element) { + // rm focus class + element.classList.remove("focus") + }); + if (ae) { + ae.focus() + document.activeElement = current_elem = ae; + ae.parentElement.classList.add("focus") + } else { + document.activeElement.parentElement.classList.add("focus") + } +} + +function reset() { + resultsAvailable = false; + resList.innerHTML = sInput.value = ''; // clear inputbox and searchResults + sInput.focus(); // shift focus to input box +} + +// execute search as each character is typed +sInput.onkeyup = function (e) { + // run a search query (for "term") every time a letter is typed + // in the search box + if (fuse) { + let results; + if (params.fuseOpts) { + results = fuse.search(this.value.trim(), {limit: params.fuseOpts.limit}); // the actual query being run using fuse.js along with options + } else { + results = fuse.search(this.value.trim()); // the actual query being run using fuse.js + } + if (results.length !== 0) { + // build our html if result exists + let resultSet = ''; // our results bucket + + for (let item in results) { + resultSet += `
  • ` + + `
    # ${results[item].item.title}
    ` + + `
    ${results[item].item.date}
  • ` + } + + resList.innerHTML = resultSet; + resultsAvailable = true; + first = resList.firstChild; + last = resList.lastChild; + } else { + resultsAvailable = false; + resList.innerHTML = ''; + } + } +} + +sInput.addEventListener('search', function (e) { + // clicked on x + if (!this.value) reset() +}) + +// kb bindings +document.onkeydown = function (e) { + let key = e.key; + let ae = document.activeElement; + + let inbox = document.getElementById("searchbox").contains(ae) + + if (ae === sInput) { + let elements = document.getElementsByClassName('focus'); + while (elements.length > 0) { + elements[0].classList.remove('focus'); + } + } else if (current_elem) ae = current_elem; + + if (key === "Escape") { + reset() + } else if (!resultsAvailable || !inbox) { + return + } else if (key === "ArrowDown") { + e.preventDefault(); + if (ae == sInput) { + // if the currently focused element is the search input, focus the of first
  • + activeToggle(resList.firstChild.lastChild); + } else if (ae.parentElement != last) { + // if the currently focused element's parent is last, do nothing + // otherwise select the next search result + activeToggle(ae.parentElement.nextSibling.lastChild); + } + } else if (key === "ArrowUp") { + e.preventDefault(); + if (ae.parentElement == first) { + // if the currently focused element is first item, go to input box + activeToggle(sInput); + } else if (ae != sInput) { + // if the currently focused element is input box, do nothing + // otherwise select the previous search result + activeToggle(ae.parentElement.previousSibling.lastChild); + } + } else if (key === "ArrowRight") { + ae.click(); // click on active link + } +} diff --git a/layouts/_default/index.json b/layouts/_default/index.json new file mode 100644 index 0000000..07eaa83 --- /dev/null +++ b/layouts/_default/index.json @@ -0,0 +1,7 @@ +{{- $.Scratch.Add "index" slice -}} +{{- range site.RegularPages -}} + {{- if and (not .Params.searchHidden) (ne .Layout `archives`) (ne .Layout `search`) }} + {{- $.Scratch.Add "index" (dict "title" .Title "content" .Plain "permalink" .Permalink "summary" .Summary "date" (.Date.Format "Jan 2, 2006")) -}} + {{- end }} +{{- end -}} +{{- $.Scratch.Get "index" | jsonify -}} diff --git a/layouts/notes/list.html b/layouts/notes/list.html index 004d794..ce7227f 100644 --- a/layouts/notes/list.html +++ b/layouts/notes/list.html @@ -12,6 +12,7 @@
  • # {{ .Title }} + {{ .Date.Format "Jan 2, 2006" }}
  • diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index fdb4867..750f019 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -12,7 +12,7 @@ {{- end }} {{- if (not site.Params.disableScrollToTop) }} - + diff --git a/layouts/partials/toc.html b/layouts/partials/toc.html new file mode 100644 index 0000000..2cd976b --- /dev/null +++ b/layouts/partials/toc.html @@ -0,0 +1,97 @@ +{{- $headers := findRE "(.|\n])+?" .Content -}} +{{- $has_headers := ge (len $headers) 1 -}} +{{- if $has_headers -}} +
    +
    + + {{- i18n "toc" | default "Table of Contents" }} + + +
    + {{- if (.Param "UseHugoToc") }} + {{- .TableOfContents -}} + {{- else }} + {{- $largest := 6 -}} + {{- range $headers -}} + {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}} + {{- $headerLevel := len (seq $headerLevel) -}} + {{- if lt $headerLevel $largest -}} + {{- $largest = $headerLevel -}} + {{- end -}} + {{- end -}} + + {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} + + {{- $.Scratch.Set "bareul" slice -}} + + {{- else }} + + + {{- end -}} + {{- end }} + + {{- end }} +
    +
    +
    +{{- end }} diff --git a/layouts/posts/list.html b/layouts/posts/list.html index 9ba886b..a100b5f 100644 --- a/layouts/posts/list.html +++ b/layouts/posts/list.html @@ -12,6 +12,7 @@
  • # {{ .Title }} + {{- if .Summary }}