Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Offline search with Lunr.js #142

Merged
merged 8 commits into from
Nov 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions layouts/partials/head-css.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@
{{ $css := resources.Get $scssMain | toCSS (dict "enableSourceMap" false) | postCSS | minify | fingerprint }}
<link rel="preload" href="{{ $css.RelPermalink }}" as="style">
<link href="{{ $css.RelPermalink }}" rel="stylesheet" integrity="{{ $css.Data.integrity }}">
{{ end }}
{{ if and (.Site.Params.offlineSearch) (not .Site.Params.gcs_engine_id) }}
<link href="/css/offline-search.css" rel="stylesheet">
{{ end }}
6 changes: 5 additions & 1 deletion layouts/partials/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
{{ partial "hooks/head-end.html" . }}
{{ if and (.Site.Params.offlineSearch) (not .Site.Params.gcs_engine_id) }}
<script src="https://unpkg.com/lunr@2.1.6/lunr.js"></script>
<script src="/js/offline-search.js"></script>
{{end}}
{{ partial "hooks/head-end.html" . }}
9 changes: 7 additions & 2 deletions layouts/partials/search-input.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{{ with or .Site.Params.gcs_engine_id .Site.Params.algolia_docsearch }}
<input type="search" class="form-control td-search-input" placeholder="&#xf002 {{ T "ui_search" }}" aria-label="{{ T "ui_search" }}" autocomplete="off">
{{ if or .Site.Params.gcs_engine_id .Site.Params.algolia_docsearch }}
<input type="search" class="form-control td-search-input" placeholder="&#xf002 {{ T "ui_search" }}" aria-label="{{ T "ui_search" }}" autocomplete="off">
{{ else if .Site.Params.offlineSearch }}
<div id="search-nav-container">
<input type="search" id="search-input" autocomplete="off" class="form-control td-search-input" placeholder="&#xf002 {{ T "ui_search" }}" autocomplete="on">
<div id="search-results" class="container"></div>
</div>
{{ end }}
5 changes: 5 additions & 0 deletions layouts/search-index/single.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{- $.Scratch.Add "index" slice -}}
theletterf marked this conversation as resolved.
Show resolved Hide resolved
{{- range where .Site.Pages ".Params.exclude_search" "!=" true -}}
{{- $.Scratch.Add "index" (dict "title" .Title "ref" .Permalink "body" .Plain "excerpt" (.Summary | truncate 100)) -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}
39 changes: 39 additions & 0 deletions static/css/offline-search.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#search-results{
position: absolute;
width:90%;
font-size: 0.8rem;
top: 40px;
padding-left: 5px;
padding-right: 5px;
left:5%;
margin-top: -2px;
z-index: 1;
}

#search-nav-container {
position: relative;
width: 100%;
}

#search-results ul li{
list-style-type: none;
list-style: none;
padding: 8px;
}

.td-search-input.form-control:focus{
box-shadow: 0 0 0 1px #ccc!important;
}

#search-results ul{
list-style-type: none;
list-style: none;
padding: 8px;
border-bottom:1px solid #ccc;
border-right:1px solid #ccc;
border-left:1px solid #ccc;
border-top:1px solid #ccc;
border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem;
background: #fff;
}
103 changes: 103 additions & 0 deletions static/js/offline-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Adapted from code by Matt Walters https://www.mattwalters.net/posts/hugo-and-lunr/

var idx = null; // Lunr index
var resultDetails = []; // Will hold the data for the search results (titles and summaries)
var $searchInput; // The search box element in the navbar
var $searchResults; // Results shown in the navbar

$(window).on('load', function() {
// Set up for an Ajax call to request the JSON data file that is created by
// Hugo's build process, with the template we added above
var request = new XMLHttpRequest();
var query = '';

// Get dom objects for the elements we'll be interacting with
$searchResults = document.getElementById('search-results');
$searchInput = document.getElementById('search-input');

request.overrideMimeType("application/json");
request.open("GET", "/index.json", true); // Request the JSON file created during build
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
// Success response received in requesting the search-index file
var searchDocuments = JSON.parse(request.responseText);

// Build the index so Lunr can search it. The `ref` field will hold the URL
// to the page/post. title, excerpt, and body will be fields searched.
idx = lunr(function lunrIndex() {
this.ref('ref');
this.field('title');
this.field('body');

// Loop through all the items in the JSON file and add them to the index
// so they can be searched.
searchDocuments.forEach(function(doc) {
this.add(doc);
resultDetails[doc.ref] = {
'title': doc.title,
'excerpt': doc.excerpt,
};
}, this);
});
} else {
$searchResults.innerHTML = '<ul><li>Error loading search results</li></ul>';
}
};

request.onerror = function() {
$searchResults.innerHTML = '<ul><li>Error loading search results</li></ul>';
};

// Send the request to load the JSON
request.send();

// Register handler for the search input field
registerSearchHandler();
});

function registerSearchHandler() {
$searchInput.oninput = function(event) {
var query = event.target.value;
var results = search(query); // Perform the search

// Render search results
renderSearchResults(results);

// Remove search results if the user empties the search phrase input field
if ($searchInput.value == '') {
$searchResults.innerHTML = '';
}
}
}

function renderSearchResults(results) {
// Create a list of results
if (results.length > 0) {
var ul = document.createElement('ul');
results.forEach(function(result) {
// Create result item
var li = document.createElement('li');
li.innerHTML = '<a href="' + result.ref + '">' + resultDetails[result.ref].title + '</a><br>' + resultDetails[result.ref].excerpt + '...';
ul.appendChild(li);
});

// Remove any existing content so results aren't continually added as the user types
while ($searchResults.hasChildNodes()) {
$searchResults.removeChild(
$searchResults.lastChild
);
}
} else {
$searchResults.innerHTML = '<ul><li>No results found</li></ul>';
}
// Render the list
$searchResults.appendChild(ul);
}

function search(query) {
return idx.search(query);
}
// Disables enter key on input fields except textarea
$(document).on("keydown", ":input:not(textarea)", function(event) {
return event.key != "Enter";
});
7 changes: 7 additions & 0 deletions userguide/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pygmentsStyle = "tango"
[permalinks]
blog = "/:section/:year/:month/:day/:slug/"

[outputs]
home = [ "HTML", "JSON" ]
page = [ "HTML" ]

## Configuration for BlackFriday markdown parser: https://github.com/russross/blackfriday
[blackfriday]
plainIDAnchors = true
Expand Down Expand Up @@ -102,6 +106,9 @@ gcs_engine_id = "011217106833237091527:la2vtv2emlw"
# Enable Algolia DocSearch
algolia_docsearch = false

#Enable offline search with Lunr.js
offlineSearch = false

# User interface configuration
[params.ui]
# Enable to show the side bar menu in its compact state.
Expand Down
4 changes: 4 additions & 0 deletions userguide/content/en/search-index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
type: "search-index"
url: "index.json"
---