-
-
Save amoscardino/2776104f9abf1574d3e89e5fb6448e32 to your computer and use it in GitHub Desktop.
moscardino.net Search
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const lunr = require('lunr'); | |
const stripTags = require('striptags'); | |
hexo.extend.generator.register('hexo-lunr-builder', function (locals) { | |
// Get all the post data needed for the index and export | |
let posts = locals.posts.map((post, i) => ({ | |
id: i, | |
title: post.title, | |
description: post.description, | |
thumbnail_image: post.thumbnail_image || post.image, | |
date: post.date.format('YYYY-MM-DD'), | |
text: stripTags(post.content), | |
keywords: post.tags.map(tag => tag.name), | |
path: `/${post.path}` | |
})); | |
// Build the index | |
let index = lunr(function () { | |
let builder = this; | |
// Define the index fields | |
builder.ref('id'); | |
builder.field('title', { boost: 3 }); | |
builder.field('description', { boost: 2 }); | |
builder.field('keywords', { boost: 2 }); | |
builder.field('text'); | |
// Add the posts to the index | |
posts.forEach(function (post) { | |
builder.add({ | |
id: post.id, | |
title: post.title, | |
description: post.description, | |
keywords: post.keywords, | |
text: post.text | |
}); | |
}); | |
}); | |
// Create the data file | |
return [{ | |
path: 'lunr-index.json', | |
data: JSON.stringify(index) | |
}, { | |
path: 'posts.json', | |
data: JSON.stringify(posts.map(post => ({ | |
id: post.id, | |
title: post.title, | |
description: post.description, | |
thumbnail_image: post.thumbnail_image, | |
date: post.date, | |
path: post.path | |
}))) | |
}]; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<h1 class="u-sr-only">Search</h1> | |
<div id="Search" class="container u-hidden" role="search"> | |
<input type="text" | |
class="search__input" | |
role="searchbox" | |
id="SearchInput" | |
placeholder="Type to search..." | |
aria-label="Type to search..." | |
spellcheck="false" | |
autocapitalize="off" | |
autocomplete="off" | |
autocorrect="off" /> | |
</div> | |
<div id="SearchResults" class="container container--postlist"></div> | |
<script src="/js/lunr.js" defer></script> | |
<script src="/js/search.js" defer></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(async function () { | |
// Load the pre-processed data from the json file | |
let postsPromise = fetch('/posts.json'); | |
let indexPromise = fetch('/lunr-index.json'); | |
let [postsResponse, indexResponse] = await Promise.all([postsPromise, indexPromise]); | |
let [postsData, indexData] = await Promise.all([postsResponse.json(), indexResponse.json()]); | |
let lunrIndex = lunr.Index.load(indexData); | |
let posts = postsData; | |
document.getElementById('Search').classList.remove('u-hidden'); | |
// From: https://davidwalsh.name/function-debounce | |
const debounce = (func, wait, immediate) => { | |
let timeout; | |
return function () { | |
let context = this; | |
let args = arguments; | |
let later = function () { | |
timeout = null; | |
if (!immediate) | |
func.apply(context, args); | |
}; | |
let callNow = immediate && !timeout; | |
clearTimeout(timeout); | |
timeout = setTimeout(later, wait); | |
if (callNow) | |
func.apply(context, args); | |
}; | |
}; | |
const writeToPage = html => document.getElementById('SearchResults').innerHTML = html; | |
const createTitleBox = text => { | |
return ` | |
<div class="post post--listitem post--archive-tag"> | |
<div class="post__inner"> | |
<p class="post__body">${text}</p> | |
</div> | |
</div> | |
`; | |
}; | |
const createSearchResult = post => { | |
let descriptionHtml = post.description && post.description.length | |
? `<p class="post__description">${post.description}</p>` | |
: ''; | |
return ` | |
<a href="${post.path}" class="post post--listitem"> | |
<div class="post__date">${post.date}</div> | |
<div class="post__inner ${post.thumbnail_image ? 'post__inner--with-image' : ''}" | |
style="--post-image: url('${post.thumbnail_image}');"> | |
<h2 class="post__title">${post.title}</h2> | |
${descriptionHtml} | |
</div> | |
</a> | |
`; | |
}; | |
const performSearch = (term) => { | |
if (!term.length) { | |
// Clear any previous results and exit | |
writeToPage(''); | |
return; | |
} | |
// Get the results from lunr | |
let results = lunrIndex.search(term); | |
let content = ''; | |
if (results.length) { | |
// Convert the results to an HTML string | |
content = results | |
.filter((_, i) => i < 10) // Top 10 results only | |
.map(result => ({ ...posts[result.ref], score: result.score })) | |
.map(post => createSearchResult(post)) | |
.join(''); | |
} | |
else | |
content = createTitleBox(`No results found for <strong>${term}</strong>.`); | |
// Update the page | |
writeToPage(content); | |
}; | |
// We are going to use a debounced search function so we can wait for the user to finish typing | |
const debouncedSearch = debounce(performSearch, 150); | |
// Then call the debounced search whenever the input value changes | |
document.getElementById('SearchInput').addEventListener('input', e => { | |
debouncedSearch(e.target.value); | |
}); | |
// Once we are ready, push focus to the search box | |
document.getElementById('SearchInput').focus(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment