Skip to content

Instantly share code, notes, and snippets.

@amoscardino
Created Aug 12, 2021
Embed
What would you like to do?
moscardino.net Search
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
})))
}];
});
<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>
(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