| title | date | type | file | scripts | |
|---|---|---|---|---|---|
Search |
2020-12-03 13:46:33 -0500 |
search |
content/search/_index.md |
|
{{< search-form autofocus="true" placeholder="Search this site..." >}}
| /* search form */ | |
| [for="input-search"] { | |
| display: block; | |
| } | |
| [search-text] { | |
| border-width: 1px; | |
| font-size: 1em; | |
| min-width: 15em; | |
| padding: .25em 1em; | |
| } | |
| [search-submit]:focus { | |
| box-shadow: 0 0 1px 1px rgba(22, 22, 254, .5); | |
| outline: 0; | |
| } | |
| [search-submit]:hover { | |
| background-color: rgba(22,22,254, 1); | |
| border-style: dotted; | |
| } | |
| [search-submit] { | |
| background-color: rgba(22,22,254,.75); | |
| border: 1px solid gray; | |
| color: #fff; | |
| padding: .5em; | |
| position: relative; | |
| top: -.125em; | |
| width: 2.5em; | |
| } | |
| [result-count] { | |
| font-weight: bold; | |
| font-size: 1.25em; | |
| margin: 1em 0; | |
| } |
| <!-- /layouts/shortcodes/search-form.html --> | |
| <form search-form method="get" action="https://www.google.com/search"> | |
| <label for="input-search">Enter your search criteria:</label> | |
| <input search-text type="text" name="q" id="input-search" {{ if .Get "autofocus" }} | |
| autofocus{{ end }}{{ if .Get "placeholder" }} placeholder='{{ .Get "placeholder" }}' {{ end }} value=""> | |
| <!-- Should JS break, the form submits to google with site appended to q --> | |
| <input type="hidden" name="q" value="site:dfkaye.com"> | |
| <button type="submit" search-submit> | |
| <svg xmlns="http://www.w3.org/2000/svg" style="height: 1em; width: 1em;" viewBox="0 0 32 32" | |
| aria-labelledby="search-title"> | |
| <title id="search-title">Search</title> | |
| <path fill="currentColor" | |
| d="M31.008 27.23l-7.58-6.446c-.784-.705-1.622-1.03-2.3-.998C22.92 17.69 24 14.97 24 12 24 5.37 18.627 0 12 0S0 5.37 0 12c0 6.626 5.374 12 12 12 2.973 0 5.692-1.082 7.788-2.87-.03.676.293 1.514.998 2.298l6.447 7.58c1.105 1.226 2.908 1.33 4.008.23s.997-2.903-.23-4.007zM12 20c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 8-8 8z" /> | |
| </svg> | |
| </button> | |
| </form> | |
| <div search-results aria-live="polite"></div> | |
| {{- $.Scratch.Add "index" slice -}} | |
| {{- range .Site.RegularPages -}} | |
| {{- if ne .Params.noSearch true -}} | |
| {{- $.Scratch.Add "index" (dict "title" .Title "url" .Permalink "date" (.PublishDate.Format "January 2, 2006") "datetime" .PublishDate "tags" .Params.tags "content" (.Content | plainify) "description" (.Description | plainify) ) -}} | |
| {{- end -}} | |
| {{- end -}} | |
| <template search-index="{{- $.Scratch.Get "index" | jsonify -}}"></template> |
| /* /js/lib/search.js */ | |
| // Our handlers. | |
| function clearResults(list) { | |
| while (list.firstElementChild) { | |
| list.firstElementChild.remove() | |
| } | |
| } | |
| function render({ results, list }) { | |
| // DOM builder | |
| var parser = new DOMParser() | |
| var mime = "text/html" | |
| var count = results.length | |
| var plural = count != 1; | |
| var html = ` | |
| <aside> | |
| <p result-count="${count}">${count} result${plural ? "s" : ""} found.</p> | |
| ${count | |
| ? ` | |
| <!-- Results list --> | |
| <ul page-list></ul> | |
| ` | |
| : ` | |
| <!-- No results --> | |
| <p>Try a different search or browse the following page types:</p> | |
| <ul page-list> | |
| <li> | |
| <a href="/posts">Posts</a> | |
| </li> | |
| <li> | |
| <a href="/demos">Demos</a> | |
| </li> | |
| <li> | |
| <a href="/tags">Tags</a> | |
| </li> | |
| </ul> | |
| `} | |
| </aside>`; | |
| var aside = parser.parseFromString(html.trim(), mime).body.firstElementChild | |
| var ul = aside.querySelector("[page-list]") | |
| // This will populate page-list only if there are any results. | |
| results.forEach((result, i) => { | |
| var { datetime, date, url, title, description } = result; | |
| var html = ` | |
| <li page-item="${i}"> | |
| <aside> | |
| <time datetime="${datetime}">${date}</time> | |
| </aside> | |
| <h2 page-heading><a href="${url}">${title}</a></h2> | |
| <p page-description>${description}</p> | |
| </li> | |
| `; | |
| var li = parser.parseFromString(html.trim(), mime).body.firstElementChild | |
| ul.appendChild(li) | |
| }) | |
| list.appendChild(aside) | |
| } | |
| function normalize(text) { | |
| // Textarea for normalizing HTML | |
| // See https://blog.jeremylikness.com/blog/dynamic-search-in-a-static-hugo-website/#preparing-the-index | |
| // var normalizer = document.querySelector("[text-normalize]") | |
| var normalizer = document.createElement('textarea') | |
| normalizer.innerHTML = text; | |
| return normalizer.value; | |
| } | |
| function search({ text, entries }) { | |
| var set = {}; | |
| var titles = []; | |
| var contents = []; | |
| var tags = []; | |
| var results = []; | |
| normalize(text).trim().split(" ").forEach(term => { | |
| if (!term.length || /^\s+$/.test(term)) { | |
| // Ignore empty or whitespace terms. | |
| return | |
| } | |
| // Use this to verify at least one tag matches the term. | |
| var matchTerm = tag => term.toLowerCase() == tag.toLowerCase() | |
| entries.forEach(entry => { | |
| if (set[entry.title]) { | |
| // Ignore entry if already processed. | |
| return | |
| } | |
| if (entry.title.match(RegExp(term, "gi"))) { | |
| return ( | |
| set[entry.title] = titles.push(entry) | |
| ) | |
| } | |
| if (entry.content.match(RegExp(term, "gi"))) { | |
| return ( | |
| set[entry.title] = contents.push(entry) | |
| ) | |
| } | |
| if (entry.tags.some(matchTerm)) { | |
| return ( | |
| set[entry.title] = tags.push(entry) | |
| ) | |
| } | |
| }) | |
| results = titles.concat(contents, tags) | |
| }) | |
| return results | |
| } | |
| !(function init() { | |
| // Our search index is not a JSON file but a template attribute string. | |
| // Variant on global variable approach described by Chris Ferdinandi. | |
| // See https://gomakethings.com/how-to-create-a-vanilla-js-search-page-for-a-static-website/#creating-a-search-index | |
| var template = document.querySelector("[search-index]") | |
| var json = template.getAttribute("search-index") | |
| var entries = JSON.parse(json) | |
| // console.warn(json.length) | |
| // console.dir(entries) | |
| // Normalize entry text. | |
| entries.forEach(entry => { | |
| var title = normalize(entry.title.trim()) | |
| var content = normalize(entry.content.trim()) | |
| var tags = entry.tags.map(tag => normalize(tag)) | |
| entry.title = title | |
| entry.content = content | |
| entry.tags = tags | |
| }) | |
| // Our search elements. | |
| var form = document.querySelector("[search-form]"); | |
| var input = document.querySelector('#input-search'); | |
| var list = document.querySelector("[search-results]"); | |
| form.addEventListener("submit", function onSubmit(e) { | |
| e.preventDefault() | |
| var results = search({ text: input.value, entries }) | |
| clearResults(list) | |
| render({ results, list }) | |
| }) | |
| })(); |