Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active December 5, 2020 19:25
Show Gist options
  • Select an option

  • Save dfkaye/47d22bd924b64c0f4d25ec39922320ad to your computer and use it in GitHub Desktop.

Select an option

Save dfkaye/47d22bd924b64c0f4d25ec39922320ad to your computer and use it in GitHub Desktop.
search implemented on my Hugo blog at https://dfkaye.com/search/
title date type file scripts
Search
2020-12-03 13:46:33 -0500
search
content/search/_index.md
/js/lib/search.js

{{< 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 })
})
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment