From 2dc1f8bb184c36329375af3b4bafc445e2c2210d Mon Sep 17 00:00:00 2001 From: Jens Lee Date: Sun, 23 May 2021 23:14:22 +0800 Subject: feat: add Algolia Search support (#329) updates #222 Co-authored-by: reuixiy --- .gitignore | 1 + assets/js/algolia-search.js | 109 +++++++++++++++++++++++ assets/js/lunr-search.js | 2 +- assets/scss/base/_responsive.scss | 2 +- assets/scss/main.scss | 6 +- config-examples/en/config.toml | 28 +++++- config-examples/zh-cn/config.toml | 30 ++++++- config-examples/zh-tw/config.toml | 30 ++++++- images/screenshot.png | Bin 170233 -> 106576 bytes images/tn.png | Bin 85870 -> 51495 bytes layouts/index.algolia.json | 5 ++ layouts/index.searchindex.json | 2 +- layouts/partials/menu.html | 2 +- layouts/partials/pages/post.html | 2 +- layouts/partials/script.html | 4 + layouts/partials/third-party/algolia-search.html | 4 + static/icons/android-chrome-512x512.png | Bin 23287 -> 22194 bytes static/icons/apple-touch-icon.png | Bin 3714 -> 3586 bytes static/icons/mstile-150x150.png | Bin 4330 -> 3924 bytes static/icons/safari-pinned-tab.svg | 78 +--------------- 20 files changed, 211 insertions(+), 94 deletions(-) create mode 100644 .gitignore create mode 100644 assets/js/algolia-search.js create mode 100644 layouts/index.algolia.json create mode 100644 layouts/partials/third-party/algolia-search.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/assets/js/algolia-search.js b/assets/js/algolia-search.js new file mode 100644 index 0000000..1c67f38 --- /dev/null +++ b/assets/js/algolia-search.js @@ -0,0 +1,109 @@ +window.addEventListener("DOMContentLoaded", event => { + let origContent = null; + + const search = instantsearch({ + appId: "{{ .Site.Params.algoliaAppId }}", + apiKey: "{{ .Site.Params.algoliaApiKey }}", + indexName: "{{ .Site.Params.algoliaIndexName }}", + }); + + search.addWidget({ + init: function (opts) { + const helper = opts.helper; + const input = document.getElementById("search-input"); + + input.addEventListener("input", function (e) { + helper + .setQuery(e.currentTarget.value) // update the parameters + .search(); // launch the query + }); + }, + }); + + search.addWidget({ + render: function (opts) { + let term = opts.state.query + if (!term) { + return + } + + const results = opts.results.hits; + + let target = document.querySelector(".main-inner") || document.querySelector("main.home"); + let replaced = []; + + while (target.firstChild) { + replaced.push(target.firstChild); + target.removeChild(target.firstChild); + } + + if (!origContent) { + origContent = replaced; + } + + let title = document.createElement("h1"); + + title.id = "search-results"; + title.className = "list-title"; + + if (results.length == 0) { + title.textContent = "{{ i18n "searchResultsNone" (dict "Term" "{}") }}".replace("{}", term); + } else if (results.length == 1) { + title.textContent = "{{ i18n "searchResultsTitle" (dict "Count" 1 "Term" "{}") }}".replace("{}", term); + } else { + title.textContent = "{{ i18n "searchResultsTitle" (dict "Count" 13579 "Term" "{}") }}".replace("{}", term).replace("13579", results.length); + } + + target.appendChild(title); + document.title = title.textContent; + + let template = document.getElementById("search-result"); + for (let result of results) { + let element = template.content.cloneNode(true); + element.querySelector(".summary-title-link").href = element.querySelector(".read-more-link").href = result.url; + element.querySelector(".summary-title-link").textContent = result.title; + element.querySelector(".summary").textContent = truncateToEndOfSentence(result.summary, 70); + target.appendChild(element); + } + title.scrollIntoView(true); + + {{ if .Site.Params.enableNavToggle }} + let navToggleLabel = document.querySelector('.nav-toggle'); + if (navToggleLabel && navToggleLabel.classList.contains("open")) { + document.getElementById(navToggleLabel.getAttribute("for")).click(); + } + {{ end }} + }, + }); + + search.start(); + + // This matches Hugo's own summary logic: + // https://github.com/gohugoio/hugo/blob/b5f39d23b86f9cb83c51da9fe4abb4c19c01c3b7/helpers/content.go#L543 + function truncateToEndOfSentence(text, minWords) + { + let match; + let result = ""; + let wordCount = 0; + let regexp = /(\S+)(\s*)/g; + while (match = regexp.exec(text)) { + wordCount++; + if (wordCount <= minWords) { + result += match[0]; + } + else + { + let char1 = match[1][match[1].length - 1]; + let char2 = match[2][0]; + if (/[.?!"]/.test(char1) || char2 == "\n") { + result += match[1]; + break; + } + else { + result += match[0]; + } + } + } + return result; + } +}, {once: true}); diff --git a/assets/js/lunr-search.js b/assets/js/lunr-search.js index 47d5434..0a7b91f 100644 --- a/assets/js/lunr-search.js +++ b/assets/js/lunr-search.js @@ -114,7 +114,7 @@ window.addEventListener("DOMContentLoaded", event => { try { let results = index.search(term); - let target = document.querySelector(".main-inner"); + let target = document.querySelector(".main-inner") || document.querySelector("main.home"); let replaced = []; while (target.firstChild) { replaced.push(target.firstChild); diff --git a/assets/scss/base/_responsive.scss b/assets/scss/base/_responsive.scss index 35fa0a3..a54e11e 100644 --- a/assets/scss/base/_responsive.scss +++ b/assets/scss/base/_responsive.scss @@ -128,7 +128,7 @@ #langs li { width: auto; } - @if ($enableLunrSearch) { + @if ($enableSearch) { .search-item { grid-column: 1 / -1; } diff --git a/assets/scss/main.scss b/assets/scss/main.scss index 97f0512..d6960b8 100644 --- a/assets/scss/main.scss +++ b/assets/scss/main.scss @@ -449,11 +449,11 @@ $fofPoster: url({{ $fofPoster }}); // Lunr search -{{ if and (eq .Site.Params.headerLayout "flex") .Site.Params.enableLunrSearch }} - $enableLunrSearch: true; +{{ if and (eq .Site.Params.headerLayout "flex") (or .Site.Params.enableLunrSearch .Site.Params.enableAlgoliaSearch) }} + $enableSearch: true; @import "components/search"; {{ else }} - $enableLunrSearch: false; + $enableSearch: false; {{ end }} diff --git a/config-examples/en/config.toml b/config-examples/en/config.toml index baf7212..e59bbee 100644 --- a/config-examples/en/config.toml +++ b/config-examples/en/config.toml @@ -141,9 +141,17 @@ uglyURLs = false mediaType = "application/json" baseName = "search" +# Search index for Algolia +[outputFormats.Algolia] + mediaType = "application/json" + baseName = "algolia" + isPlainText = true + notAlternative = true + # Hugo’s output control [outputs] page = ["HTML"] + # home = ["HTML", "SectionsAtom", "SectionsRSS", "SearchIndex", "Algolia"] home = ["HTML", "SectionsAtom", "SectionsRSS", "SearchIndex"] section = ["HTML"] taxonomy = ["HTML"] @@ -1360,12 +1368,30 @@ uglyURLs = false # Lunr search # Note: This requires SearchIndex - # output to be enabled. + # output to be enabled. enableLunrSearch = true # Note: https://lunrjs.com/ + ###################################### + # Algolia search + + # Note: This requires Algolia + # output to be enabled. + # And you need to upload the + # generated algolia.json to + # Algolia every time you rebuild + # your site. + + enableAlgoliaSearch = false + + algoliaAppId = "" + algoliaApiKey = "" + algoliaIndexName = "" + # Note: https://www.algolia.com/ + + ###################################### # 404 Page diff --git a/config-examples/zh-cn/config.toml b/config-examples/zh-cn/config.toml index e7c7082..bf66768 100644 --- a/config-examples/zh-cn/config.toml +++ b/config-examples/zh-cn/config.toml @@ -135,14 +135,21 @@ uglyURLs = false baseName = "rss" # lunr.js 的搜索索引 -# [outputFormats.SearchIndex] -# mediaType = "application/json" -# baseName = "search" +[outputFormats.SearchIndex] + mediaType = "application/json" + baseName = "search" + +# Algolia 的搜索索引 +[outputFormats.Algolia] + mediaType = "application/json" + baseName = "algolia" + isPlainText = true + notAlternative = true # Hugo 的输出控制 [outputs] page = ["HTML"] - # home = ["HTML", "SectionsAtom", "SectionsRSS", "SearchIndex"] + # home = ["HTML", "SectionsAtom", "SectionsRSS", "SearchIndex", "Algolia"] home = ["HTML", "SectionsAtom", "SectionsRSS"] section = ["HTML"] taxonomy = ["HTML"] @@ -1338,6 +1345,21 @@ uglyURLs = false # 说明:https://lunrjs.com/ + ###################################### + # Algolia search + + # 说明:需要开启 `Algolia` 的输出,且需要每 + # 次将生成的 algolia.json 文件上传到 + # Algolia + + enableAlgoliaSearch = false + + algoliaAppId = "" + algoliaApiKey = "" + algoliaIndexName = "" + # 说明:https://www.algolia.com/ + + ###################################### # 404 页面 diff --git a/config-examples/zh-tw/config.toml b/config-examples/zh-tw/config.toml index 3651d89..7a66eb0 100644 --- a/config-examples/zh-tw/config.toml +++ b/config-examples/zh-tw/config.toml @@ -135,14 +135,21 @@ uglyURLs = false baseName = "rss" # lunr.js 的搜尋索引 -# [outputFormats.SearchIndex] -# mediaType = "application/json" -# baseName = "search" +[outputFormats.SearchIndex] + mediaType = "application/json" + baseName = "search" + +# Algolia 的搜尋索引 +[outputFormats.Algolia] + mediaType = "application/json" + baseName = "algolia" + isPlainText = true + notAlternative = true # Hugo 的輸出控制 [outputs] page = ["HTML"] - # home = ["HTML", "SectionsAtom", "SectionsRSS", "SearchIndex"] + # home = ["HTML", "SectionsAtom", "SectionsRSS", "SearchIndex", "Algolia"] home = ["HTML", "SectionsAtom", "SectionsRSS"] section = ["HTML"] taxonomy = ["HTML"] @@ -1338,6 +1345,21 @@ uglyURLs = false # 說明:https://lunrjs.com/ + ###################################### + # Algolia 搜尋 + + # 說明:需要開啟 `Algolia` 的輸出,且需要每 + # 次將生成的 algolia.json 文件上傳到 + # Algolia + + enableAlgoliaSearch = false + + algoliaAppId = "" + algoliaApiKey = "" + algoliaIndexName = "" + # 說明:https://www.algolia.com/ + + ###################################### # 404 頁面 diff --git a/images/screenshot.png b/images/screenshot.png index d1bc7fa..415ff02 100644 Binary files a/images/screenshot.png and b/images/screenshot.png differ diff --git a/images/tn.png b/images/tn.png index 73ada5e..8bb4f51 100644 Binary files a/images/tn.png and b/images/tn.png differ diff --git a/layouts/index.algolia.json b/layouts/index.algolia.json new file mode 100644 index 0000000..d46134d --- /dev/null +++ b/layouts/index.algolia.json @@ -0,0 +1,5 @@ +{{- $.Scratch.Add "index" slice -}} +{{- range (where .Site.RegularPages "Section" "in" .Site.Params.mainSections) -}} + {{- $.Scratch.Add "index" (dict "objectID" .File.UniqueID "date" .Date.UTC.Unix "description" .Description "fuzzywordcount" .FuzzyWordCount "kind" .Kind "lang" .Lang "lastmod" .Lastmod.UTC.Unix "publishdate" .PublishDate.UTC.Unix "relpermalink" .RelPermalink "summary" .Summary "title" .Title "url" .Permalink "wordcount" .WordCount "section" .Section "tags" .Params.tags "categories" .Section "content" .Content)}} +{{- end -}} +{{- $.Scratch.Get "index" | jsonify -}} diff --git a/layouts/index.searchindex.json b/layouts/index.searchindex.json index df5e9d7..34a740c 100644 --- a/layouts/index.searchindex.json +++ b/layouts/index.searchindex.json @@ -1,5 +1,5 @@ [ - {{- range $index, $page := where .Site.Pages "Content" "ne" ("" | safeHTML) -}} + {{- range $index, $page := where .Site.RegularPages "Section" "in" .Site.Params.mainSections -}} {{- if gt $index 0 -}} , {{- end -}} diff --git a/layouts/partials/menu.html b/layouts/partials/menu.html index 91e9101..cd02605 100644 --- a/layouts/partials/menu.html +++ b/layouts/partials/menu.html @@ -29,7 +29,7 @@ {{ end }} {{ end }} {{ else if eq .Identifier "search" }} - {{ if and $.Site.Params.enableLunrSearch (eq $.Site.Params.headerLayout "flex") }} + {{ if and (eq $.Site.Params.headerLayout "flex") (or $.Site.Params.enableLunrSearch $.Site.Params.enableAlgoliaSearch) }} {{- $iconName := (string .Post) -}}