diff --git a/layouts/partials/head-css.html b/layouts/partials/head-css.html index 603da38615..eb01fc4eed 100644 --- a/layouts/partials/head-css.html +++ b/layouts/partials/head-css.html @@ -8,4 +8,7 @@ {{ $css := resources.Get $scssMain | toCSS (dict "enableSourceMap" false) | postCSS | minify | fingerprint }} +{{ end }} +{{ if and (.Site.Params.offlineSearch) (not .Site.Params.gcs_engine_id) }} + {{ end }} \ No newline at end of file diff --git a/layouts/partials/head.html b/layouts/partials/head.html index 303eb8b8fa..193ee0d9b7 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -23,4 +23,8 @@ src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"> -{{ partial "hooks/head-end.html" . }} +{{ if and (.Site.Params.offlineSearch) (not .Site.Params.gcs_engine_id) }} + + +{{end}} +{{ partial "hooks/head-end.html" . }} \ No newline at end of file diff --git a/layouts/partials/search-input.html b/layouts/partials/search-input.html index ee82a69884..eb7ef5a8fc 100644 --- a/layouts/partials/search-input.html +++ b/layouts/partials/search-input.html @@ -1,3 +1,8 @@ -{{ with or .Site.Params.gcs_engine_id .Site.Params.algolia_docsearch }} - +{{ if or .Site.Params.gcs_engine_id .Site.Params.algolia_docsearch }} + +{{ else if .Site.Params.offlineSearch }} +
+ +
+
{{ end }} \ No newline at end of file diff --git a/layouts/search-index/single.html b/layouts/search-index/single.html new file mode 100644 index 0000000000..571965cd3f --- /dev/null +++ b/layouts/search-index/single.html @@ -0,0 +1,5 @@ +{{- $.Scratch.Add "index" slice -}} +{{- 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 -}} \ No newline at end of file diff --git a/static/css/offline-search.css b/static/css/offline-search.css new file mode 100644 index 0000000000..3973f65805 --- /dev/null +++ b/static/css/offline-search.css @@ -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; +} \ No newline at end of file diff --git a/static/js/offline-search.js b/static/js/offline-search.js new file mode 100644 index 0000000000..b660ac23aa --- /dev/null +++ b/static/js/offline-search.js @@ -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 = ''; + } + }; + + request.onerror = function() { + $searchResults.innerHTML = ''; + }; + + // 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 = '' + resultDetails[result.ref].title + '
' + 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 = ''; + } + // 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"; +}); \ No newline at end of file diff --git a/userguide/config.toml b/userguide/config.toml index bea899235c..4b78757446 100644 --- a/userguide/config.toml +++ b/userguide/config.toml @@ -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 @@ -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. diff --git a/userguide/content/en/search-index.md b/userguide/content/en/search-index.md new file mode 100644 index 0000000000..16c60f70d1 --- /dev/null +++ b/userguide/content/en/search-index.md @@ -0,0 +1,4 @@ +--- +type: "search-index" +url: "index.json" +--- \ No newline at end of file