diff --git a/README.md b/README.md index 0244ff9..67bb218 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,25 @@ And it will show like this:

block content

+## Support local search + +add to `config.toml` + +```toml +[params] + enableSearch = true + +[outputs] + home = ["html", "json"] + +[outputFormats.json] + mediaType = "application/json" + baseName = "index" + isPlainText = false +``` + +We can do local search now, it is implemented by `fuse.js`. + ## deploy.sh example There are various way to deploy to github, here is a link to official [document](https://gohugo.io/hosting-and-deployment/hosting-on-github/). diff --git a/config.template.toml b/config.template.toml index c1d2450..4e24926 100644 --- a/config.template.toml +++ b/config.template.toml @@ -25,6 +25,7 @@ pygmentsUseClasses = true location = "China" userStatusEmoji = "😀" enableGitalk = true + enableSearch = true [params.gitalk] clientID = "Your client ID" # Your client ID diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 6d52152..560dc38 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -4,6 +4,7 @@ {{ partial "header" . }} + {{ partial "search.html" .}} {{ block "content" . }}{{ end }} {{ partial "footer.html" . }} diff --git a/layouts/_default/index.json b/layouts/_default/index.json new file mode 100644 index 0000000..73cee8e --- /dev/null +++ b/layouts/_default/index.json @@ -0,0 +1,67 @@ +{{- /* Copy from https://github.com/HugoBlox/hugo-blox-builder/blob/main/modules/blox-bootstrap/layouts/index.json. */ -}} +{{- /* Generate the search index. */ -}} +{{- $index := slice -}} +{{- $pages := site.RegularPages -}} +{{- /* Add the index page of multi-page content separately since it's not in RegularPages above. */ -}} +{{- $pages := $pages | union (where (where site.Pages "Kind" "section") "Type" "book") -}} +{{- /* Add author pages to index so their bios can be searched. Hide empty `/authors/` node. */ -}} +{{- $pages := $pages | union (where (where site.Pages "Section" "authors") "Params.superuser" "!=" nil) -}} + +{{- range $pages -}} + {{- /* Do not index drafts or private pages. */ -}} + {{- if and (not .Draft) (not .Params.private) | and (ne .Params.searchable false) -}} + + {{- /* Generate page description. */ -}} + {{- $desc := "" -}} + {{- if .Params.summary -}} + {{- $desc = .Params.summary -}} + {{- else if .Params.abstract -}} + {{- $desc = .Params.abstract -}} + {{- else -}} + {{- $desc = .Summary -}} + {{- end -}} + + {{- $authors := .Params.authors -}} + {{- $title := .Title}} + {{- $rel_permalink := .RelPermalink -}} + {{- $permalink := .Permalink -}} + + {{/* Correct the title and URL for author profile pages. */}} + {{- if eq .Section "authors" -}} + {{- $username := path.Base .File.Dir -}} + {{- with site.GetPage (printf "/authors/%s" $username) -}} + {{- $permalink = .Permalink -}} + {{- $rel_permalink = .RelPermalink -}} + {{- end -}} + {{- else -}} + {{/* Include a user's display name rather than username where possible. */}} + {{- if .Params.authors -}} + {{- $authorLen := len .Params.authors -}} + {{- if gt $authorLen 0 -}} + {{- $authors = slice -}} + {{- range $k, $v := .Params.authors -}} + {{- $person_page_path := (printf "/authors/%s" (urlize $v)) -}} + {{- $person_page := site.GetPage $person_page_path -}} + {{- if and $person_page $person_page.File -}} + {{- $authors = $authors | append $person_page.Title -}} + {{- else -}} + {{- $authors = $authors | append ($v | plainify) -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- /* Add page to index. */ -}} + {{/* Exclude virtual pages which aren't backed by a file */}} + {{- if .File }} + {{/* exclude "content/readme.md". */}} + {{ $hidden_file := "/readme/" }} + {{ if ne $hidden_file $rel_permalink }} + {{- $index = $index | append (dict "objectID" .File.UniqueID "date" .Date.UTC.Unix "publishdate" .PublishDate "lastmod" .Lastmod.UTC.Unix "expirydate" .ExpiryDate.UTC.Unix "lang" .Lang "permalink" $permalink "relpermalink" $rel_permalink "title" $title "summary" (plainify $desc | htmlUnescape) "content" (.Plain | htmlUnescape | truncate 5000) "authors" $authors "kind" .Kind "type" .Type "section" .Section "tags" .Params.Tags "categories" .Params.Categories) -}} + {{ end }} + {{ end -}} + {{- end -}} +{{- end -}} + +{{- $index | jsonify -}} diff --git a/layouts/_default/list.html b/layouts/_default/list.html index 549c220..d3d2031 100644 --- a/layouts/_default/list.html +++ b/layouts/_default/list.html @@ -1,8 +1,6 @@ {{ define "content" }}
-
- {{ partial "user-profile.html" . }} - - {{ partial "posts.html" .}} + {{ partial "user-profile.html" . }} + {{ partial "posts.html" .}}
-{{end }} \ No newline at end of file +{{ end }} diff --git a/layouts/_default/single.html b/layouts/_default/single.html index ce9d4d7..8dd5e86 100644 --- a/layouts/_default/single.html +++ b/layouts/_default/single.html @@ -1,4 +1,6 @@ {{ define "content" }} -{{ partial "post.html" .}} -{{ partial "gitalk.html" . }} -{{end }} \ No newline at end of file +
+ {{ partial "post.html" .}} + {{ partial "gitalk.html" . }} +
+{{ end }} diff --git a/layouts/partials/header.html b/layouts/partials/header.html index f2706aa..a1602d3 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -19,6 +19,17 @@ diff --git a/layouts/partials/script.html b/layouts/partials/script.html index fac5071..7952214 100644 --- a/layouts/partials/script.html +++ b/layouts/partials/script.html @@ -47,3 +47,8 @@ } {{ end }} + +{{ if .Site.Params.enableSearch }} + + +{{ end }} diff --git a/layouts/partials/search.html b/layouts/partials/search.html new file mode 100644 index 0000000..a9b0c31 --- /dev/null +++ b/layouts/partials/search.html @@ -0,0 +1,2 @@ + diff --git a/static/js/search.js b/static/js/search.js new file mode 100644 index 0000000..24d90df --- /dev/null +++ b/static/js/search.js @@ -0,0 +1,116 @@ +(() => { + fetch('/index.json') + .then(response => response.json()) + .then(data => { + const fuse = new Fuse(data, { + keys: ['title', 'content'], + shouldSort: true, + includeMatches: true, + minMatchCharLength: 2, + threshold: 0.0, + ignoreLocation: true, + }) + + document.getElementById('search-form').addEventListener('submit', function (e) { + e.preventDefault(); + const data = new FormData(e.target) + // data.entries() returns iterator, [...data.entries()] returns [["q", "input"]] + const input = [...data.entries()][0][1] + const results = fuse.search(input) + displayResults(input, results) + }); + }); +})(); + +function displayResults(input, results) { + const searchResults = document.getElementById('search-result'); + searchResults.setAttribute('style', 'display: block;') + searchResults.nextElementSibling.setAttribute('style', 'display: none;') + let html = renderResultsCountHtml(results.length, input) + if (results.length > 0) { + let li = renderResultsItemHtml(results) + html += `` + } + searchResults.innerHTML = html +} + +function renderResultsCountHtml(count, input) { + let html = ` +
+
+ ${count} + results + for "${input}" +
+
+` + return html +} + +function renderResultsItemHtml(results) { + // modified from https://github.com/brunocechet/Fuse.js-with-highlight + var highlighter = function(resultItem){ + resultItem.matches.forEach((matchItem) => { + var text = resultItem.item[matchItem.key]; + var result = [] + var matches = [].concat(matchItem.indices); + var pair = matches.shift() + + for (var i = 0; i < text.length; i++) { + var char = text.charAt(i) + if (pair && i == pair[0]) { + result.push('') + } + result.push(char) + if (pair && i == pair[1]) { + result.push('') + pair = matches.shift() + } + } + resultItem.highlight = result.join(''); + + if(resultItem.children && resultItem.children.length > 0){ + resultItem.children.forEach((child) => { + highlighter(child); + }); + } + }); + }; + + let html = `` + results.forEach(result => { + highlighter(result) + // truncated highlight content + let truncated = result.highlight.substring(0, 2000) + const reg = /([a-zA-Z0-9\u4e00-\u9fa5]+<\/span>)/g + let array = truncated.split(reg) + // drop unstable part + array.pop() + let content = "" + if (array.length > 0) { + content = array.join('') + } else { + // fallback to no highlighted truncated content + content = result.item.content.substring(0, 2000) + } + html += ` +
  • +
    + + +
    +
    + ${content} ... +
    +
    + +
    +
  • +` + }) + return html +}