diff --git a/data/assets.toml b/data/assets.toml index b977938e..d6cbe6ef 100644 --- a/data/assets.toml +++ b/data/assets.toml @@ -47,6 +47,14 @@ version = "3.2.5" sri = "sha256-X5PoE3KU5l+JcX+w09p/wHl9AzK333C4hJ2I9S5mD4M=" url = "https://cdnjs.cloudflare.com/ajax/libs/fancybox/%s/jquery.fancybox.min.js" +[js.fuse] + version = "3.2.1" + sri = "sha256-VzgmKYmhsGNNN4Ph1kMW+BjoYJM2jV5i4IlFoeZA9XI=" + url = "https://cdnjs.cloudflare.com/ajax/libs/fuse.js/%s/fuse.min.js" +[js.mark] + version = "8.11.1" + sri = "sha256-4HLtjeVgH0eIB3aZ9mLYF6E8oU5chNdjU6p6rrXpl9U=" + url = "https://cdnjs.cloudflare.com/ajax/libs/mark.js/%s/jquery.mark.min.js" [js.instantsearch] version = "2.9.0" sri = "sha256-cJXigylnJlxvAdfFNHeS+IiMcKWS3Rf/cy3bl9bb0ng=" diff --git a/exampleSite/config.toml b/exampleSite/config.toml index 261270d3..f3235019 100644 --- a/exampleSite/config.toml +++ b/exampleSite/config.toml @@ -306,8 +306,9 @@ enableGitInfo = false [params.search] # Search provider: # 0: No search engine - # 1: Algolia (https://www.algolia.com) - engine = 0 + # 1: Built-in (Fuse) + # 2: Algolia (https://www.algolia.com) + engine = 1 # Configuration of Algolia search engine. # Paste the values from your Algolia dashboard. diff --git a/exampleSite/content/home/search.md b/exampleSite/content/home/search.md index 227a2de1..e3b797a8 100644 --- a/exampleSite/content/home/search.md +++ b/exampleSite/content/home/search.md @@ -1,7 +1,7 @@ +++ # Search widget. widget = "search" -active = false +active = true date = 2018-07-23T00:00:00 title = "Search" diff --git a/layouts/partials/css/academic.css b/layouts/partials/css/academic.css index e1ae5a0f..d9ec0472 100644 --- a/layouts/partials/css/academic.css +++ b/layouts/partials/css/academic.css @@ -247,9 +247,32 @@ small, **************************************************/ #search-box { + position: relative; /* Required for search icon positioning. */ margin-bottom: 0.5rem; } +#search-box::before { + content: "\f002"; + font-family: FontAwesome; + font-size: 1rem; + opacity: 0.25; + line-height: 1rem; + position: absolute; + left: 0.7rem; + top: 0.6rem; + overflow-x: hidden; +} + +#search-query { + border: 1px solid #dedede; + border-radius: 1rem; + padding: 1rem 1rem 1rem 2rem; /* Wider left padding for search icon to fit in. */ + width: 250px; + line-height: 1rem; + height: 1rem; + font-size: 0.8rem; +} + .search-hit em { font-style: normal; background-color: #FFE0B2; @@ -1620,6 +1643,10 @@ body.dark { background-color: rgb(68, 71, 90); } +.dark #search-query { + background-color: rgb(68, 71, 90); +} + .dark .label-default { color: rgba(255, 255, 255, .68); background: rgba(255, 255, 255, .2); diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index e4087f65..756daa47 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -63,20 +63,37 @@ {{ end }} - {{/* Algolia search engine. */}} + {{ if ne .Site.Params.search.engine 0 }} + {{/* Configure search engine. */}} + + {{ end }} + + {{/* Fuse search engine. */}} {{ if eq .Site.Params.search.engine 1 }} + {{ printf "" (printf $js.fuse.url $js.fuse.version) $js.fuse.sri | safeHTML }} + {{ printf "" (printf $js.mark.url $js.mark.version) $js.mark.sri | safeHTML }} + + {{ end }} + + {{/* Algolia search engine. */}} + {{ if eq .Site.Params.search.engine 2 }} {{ if ($scr.Get "use_cdn") }} {{ printf "" (printf $js.instantsearch.url $js.instantsearch.version) $js.instantsearch.sri | safeHTML }} {{ end }} +{{ else }} +{{/* Algolia search result template. */}} + +{{ end }} diff --git a/static/js/search.js b/static/js/search.js new file mode 100644 index 00000000..e19938bf --- /dev/null +++ b/static/js/search.js @@ -0,0 +1,165 @@ +/************************************************* + * Academic: the website framework for Hugo. + * https://github.com/gcushen/hugo-academic + **************************************************/ + +/* --------------------------------------------------------------------------- +* Configuration. +* --------------------------------------------------------------------------- */ + +// Configure Fuse. +let fuseOptions = { + shouldSort: true, + includeMatches: true, + tokenize: true, + threshold: 0.0, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 2, + keys: [ + {name:'title', weight:0.8}, + {name:'summary', weight:0.6}, + {name:'content', weight:0.5}, + {name:'tags', weight:0.3} + ] +}; + +// Configure summary. +let summaryLength = 60; + +/* --------------------------------------------------------------------------- +* Functions. +* --------------------------------------------------------------------------- */ + +// Get query from URI. +function getSearchQuery(name) { + return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' '); +} + +// Set query in URI without reloading the page. +function updateURL(url) { + if (history.pushState) { + window.history.pushState({path:url}, '', url); + } +} + +// Pre-process new search query. +function initSearch(force, fuse) { + let query = $("#search-query").val(); + + // If query deleted, clear results. + if ( query.length < 1) + $('#search-hits').empty(); + + // Check for timer event (enter key not pressed) and query less than minimum length required. + if (!force && query.length < fuseOptions.minMatchCharLength) + return; + + // Do search. + $('#search-hits').empty(); + searchAcademic(query, fuse); + let newURL = window.location.protocol + "//" + window.location.host + window.location.pathname + '?q=' + encodeURIComponent(query) + window.location.hash; + updateURL(newURL); +} + +// Perform search. +function searchAcademic(query, fuse) { + let results = fuse.search(query); + // console.log({"results": results}); + if (results.length > 0) { + parseResults(query, results); + } else { + $('#search-hits').append('