From e54a82b2fa6aff93b9b0b79e1b54f918eb1ba727 Mon Sep 17 00:00:00 2001 From: Spedon Wen Date: Sat, 13 Jul 2024 12:58:45 +0800 Subject: [PATCH] feat: integrate `pagefind` search functionality - Add support for `pagefind` search type - Update search configuration to include `pagefind` as an option - Modify search logic to handle `pagefind` initialization and usage - Update search result processing to integrate with `pagefind` - Add logging for search results - Refactor search type handling in templates - Update documentation and configuration examples to reflect new search options --- assets/js/theme.ts | 81 +++++++++++++++++++--- exampleSite/config/_default/params.en.toml | 6 +- layouts/partials/assets.html | 9 ++- layouts/posts/single.html | 4 +- 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/assets/js/theme.ts b/assets/js/theme.ts index 0e3113ef6..4893b9648 100644 --- a/assets/js/theme.ts +++ b/assets/js/theme.ts @@ -184,6 +184,7 @@ function initSearch() { const searchClear = document.getElementById(`search-clear-${suffix}`); const autocompleteJs = window.config["autocomplete.min.js"]; const algoliaJs = window.config["algoliasearch.min.js"]; + const pagefindJs = window.config["pagefind.js"]; const fuseJs = window.config["fuse.min.js"]; if (isMobile) { window._searchMobileOnce = true; @@ -195,8 +196,14 @@ function initSearch() { }); if (window.config?.search?.type === "algolia") { loadScript("algolia-script", algoliaJs, null); - } else { + } else if (window.config?.search?.type === "fuse") { loadScript("fuse-script", fuseJs, null); + } else { + import(pagefindJs).then((p) => { + if (p === undefined) return; + pagefind = p; + pagefind.init(); + }); } document.body.classList.add("blur"); header.classList.add("open"); @@ -243,8 +250,13 @@ function initSearch() { }); if (window.config?.search?.type === "algolia") { loadScript("algolia-script", algoliaJs, null); - } else { + } else if (window.config?.search?.type === "fuse") { loadScript("fuse-script", fuseJs, null); + } else { + import(pagefindJs).then((p) => { + window._pgfIndex = p; + window._pgfIndex.init(); + }); } document.body.classList.add("blur"); header.classList.add("open"); @@ -346,7 +358,7 @@ function initSearch() { } else if (searchConfig.type === "fuse") { const search = () => { const results = {}; - window._index + window._fuseIndex .search(query) .forEach(({ item, refIndex, matches }) => { let title = item.title; @@ -397,9 +409,10 @@ function initSearch() { context: content, }; }); + console.log(results); return Object.values(results).slice(0, maxResultLength); }; - if (!window._index) { + if (!window._fuseIndex) { fetch(searchConfig.fuseIndexURL) .then((response) => response.json()) .then((data) => { @@ -418,7 +431,7 @@ function initSearch() { includeMatches: true, keys: ["content", "title"], }; - window._index = new Fuse(data, options); + window._fuseIndex = new Fuse(data, options); finish(search()); }) .catch((err) => { @@ -426,6 +439,44 @@ function initSearch() { finish([]); }); } else finish(search()); + } else { + if (window._pgfIndex === undefined) { + import(pagefindJs).then((p) => { + window._pgfIndex = p; + window._pgfIndex.init(); + }); + } + window._pgfIndex.debouncedSearch(query, 300).then((resp) => { + if (resp === null || !("results" in resp)) { + finish([]); + return; + } + Promise.all( + resp.results.slice(0, maxResultLength).map((r) => r.data()), + ).then((res) => { + const results = {}; + for (const r of res) { + for (const _r of r.sub_results) { + if ( + _r.url === undefined || + !("anchor" in _r) || + _r.anchor.element != "h2" + ) + continue; + results[_r.url] = { + uri: _r.url, + title: r.meta.title, + date: r.meta.date, + context: _r.excerpt.replace( + /(.*?)<\/mark>/gi, + "$1", + ), + }; + } + } + finish(Object.values(results)); + }); + }); } }, templates: { @@ -441,11 +492,17 @@ function initSearch() { icon: '', href: "https://www.algolia.com/", } - : { - searchType: "Fuse.js", - icon: "", - href: "https://fusejs.io/", - }; + : searchConfig.type === "fuse" + ? { + searchType: "Fuse.js", + icon: "", + href: "https://fusejs.io/", + } + : { + searchType: "pagefind", + icon: "", + href: "https://pagefind.app", + }; return ``; }, }, @@ -540,7 +597,9 @@ function initToc() { const toc = document.getElementById("toc-auto"); const tocLinkElements = tocCore.querySelectorAll("a:first-child"); const tocLiElements = tocCore.getElementsByTagName("li"); - const headerLinkElements = document.getElementsByClassName("headerLink") as HTMLCollectionOf; + const headerLinkElements = document.getElementsByClassName( + "headerLink", + ) as HTMLCollectionOf; const headerIsFixed = document.body.getAttribute("header-desktop") !== "normal"; const headerHeight = document.getElementById("header-desktop").offsetHeight; diff --git a/exampleSite/config/_default/params.en.toml b/exampleSite/config/_default/params.en.toml index 40f9fa84d..ddcb2e982 100644 --- a/exampleSite/config/_default/params.en.toml +++ b/exampleSite/config/_default/params.en.toml @@ -26,9 +26,9 @@ keywords = ["Theme", "Hugo"] # 搜索配置 [search] enable = true - # type of search engine ("algolia", "fuse") - # 搜索引擎的类型 ("algolia", "fuse") - type = "algolia" + # type of search engine ("algolia", "fuse", "pagefind") + # 搜索引擎的类型 ("algolia", "fuse", "pagefind") + type = "pagefind" # max index length of the chunked content # 文章内容最长索引长度 contentLength = 4000 diff --git a/layouts/partials/assets.html b/layouts/partials/assets.html index 84f24f9a7..66547d73b 100644 --- a/layouts/partials/assets.html +++ b/layouts/partials/assets.html @@ -15,7 +15,7 @@ {{- $js := resources.Get $source -}} {{- $config = dict "algoliasearch.min.js" $js.RelPermalink | merge $config}} {{- $config = dict "type" "algolia" "algoliaIndex" $search.algolia.index "algoliaAppID" $search.algolia.appID "algoliaSearchKey" $search.algolia.searchKey | dict "search" | merge $config -}} - {{- else -}} + {{- else if eq $search.type "fuse" -}} {{- with .Site.Home.OutputFormats.Get "json" -}} {{- $config = dict "type" "fuse" "fuseIndexURL" .RelPermalink | dict "search" | merge $config -}} {{- end -}} @@ -23,6 +23,13 @@ {{- $js := resources.Get $source -}} {{- $config = dict "fuse.min.js" $js.RelPermalink | merge $config}} {{- $config = dict "isCaseSensitive" $search.fuse.isCaseSensitive "minMatchCharLength" $search.fuse.minMatchCharLength "findAllMatches" $search.fuse.findAllMatches "location" $search.fuse.location "threshold" $search.fuse.threshold "distance" $search.fuse.distance "ignoreLocation" $search.fuse.ignoreLocation "useExtendedSearch" $search.fuse.useExtendedSearch "ignoreFieldNorm" $search.fuse.ignoreFieldNorm | dict "search" | merge $config -}} + {{- else -}} + {{- with .Site.Home.OutputFormats.Get "json" -}} + {{- $config = dict "type" "pagefind" "fuseIndexURL" .RelPermalink | dict "search" | merge $config -}} + {{- end -}} + {{- $source := "/pagefind/pagefind.js" -}} + {{- $config = dict "pagefind.js" "/pagefind/pagefind.js" | merge $config}} + {{- $config = dict "isCaseSensitive" $search.fuse.isCaseSensitive "minMatchCharLength" $search.fuse.minMatchCharLength "findAllMatches" $search.fuse.findAllMatches "location" $search.fuse.location "threshold" $search.fuse.threshold "distance" $search.fuse.distance "ignoreLocation" $search.fuse.ignoreLocation "useExtendedSearch" $search.fuse.useExtendedSearch "ignoreFieldNorm" $search.fuse.ignoreFieldNorm | dict "search" | merge $config -}} {{- end -}} {{- end -}} diff --git a/layouts/posts/single.html b/layouts/posts/single.html index 7e7f707f0..78922bfde 100644 --- a/layouts/posts/single.html +++ b/layouts/posts/single.html @@ -34,7 +34,7 @@

{{ T "contents" }}

{{- /* Title */ -}} -

{{ .Title }}

+

{{ .Title }}

{{- /* Subtitle */ -}} {{- with $params.subtitle -}} @@ -203,7 +203,7 @@

{{ . }}

{{- end -}} {{- /* Content */ -}} -
+
{{- /* Outdated Article Reminder */ -}} {{- partial "single/outdatedArticleReminder.html" . -}} {{- dict "Content" .Content "Ruby" $params.ruby "Fraction" $params.fraction "Fontawesome" $params.fontawesome | partial "function/content.html" | safeHTML -}}