From cc59e5cf925b7ecf534879ce74e7109654a5a90f Mon Sep 17 00:00:00 2001 From: "tangly1024.com" Date: Mon, 24 Jul 2023 17:51:51 +0800 Subject: [PATCH] =?UTF-8?q?Algolia=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blog.config.js | 9 +-- components/AlgoliaSearchModal.js | 92 +++++++++++++++++++++++++++ lib/algolia.js | 17 ++++- lib/sitemap.xml.js | 6 ++ pages/index.js | 6 ++ themes/heo/components/SearchButton.js | 29 +++++++-- 6 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 components/AlgoliaSearchModal.js diff --git a/blog.config.js b/blog.config.js index 3a07441a..d1a836db 100644 --- a/blog.config.js +++ b/blog.config.js @@ -134,10 +134,11 @@ const BLOG = { POSTS_PER_PAGE: 12, // post counts per page POSTS_SORT_BY: process.env.NEXT_PUBLIC_POST_SORT_BY || 'notion', // 排序方式 'date'按时间,'notion'由notion控制 - ALGOLIA_APP_ID: process.env.ALGOLIA_APP_ID || null, // 在这里查看 https://dashboard.algolia.com/account/api-keys/ - ALGOLIA_APP_KEY: process.env.ALGOLIA_APP_KEY || null, // 管理后台的KEY,不会暴露给前端,在这里查看 https://dashboard.algolia.com/account/api-keys/ - ALGOLIA_APP_SEARCH_KEY: process.env.ALGOLIA_APP_SEARCH_KEY || null, // 客户端搜索用的KEY - ALGOLIA_INDEX: process.env.ALGOLIA_INDEX || null, // 在Algolia中创建一个index用作数据库 + ALGOLIA_APP_ID: process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || null, // 在这里查看 https://dashboard.algolia.com/account/api-keys/ + ALGOLIA_ADMIN_APP_KEY: process.env.ALGOLIA_ADMIN_APP_KEY || null, // 管理后台的KEY,不要暴露在代码中,在这里查看 https://dashboard.algolia.com/account/api-keys/ + ALGOLIA_SEARCH_ONLY_APP_KEY: process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_ONLY_APP_KEY || null, // 客户端搜索用的KEY + ALGOLIA_INDEX: process.env.NEXT_PUBLIC_ALGOLIA_INDEX || null, // 在Algolia中创建一个index用作数据库 + ALGOLIA_RECREATE_DATA: process.env.ALGOLIA_RECREATE_DATA || process.env.npm_lifecycle_event === 'build', // 为true时重新构建索引数据; 默认在build时会构建 PREVIEW_CATEGORY_COUNT: 16, // 首页最多展示的分类数量,0为不限制 PREVIEW_TAG_COUNT: 16, // 首页最多展示的标签数量,0为不限制 diff --git a/components/AlgoliaSearchModal.js b/components/AlgoliaSearchModal.js new file mode 100644 index 00000000..32d0401d --- /dev/null +++ b/components/AlgoliaSearchModal.js @@ -0,0 +1,92 @@ +import { useState, useImperativeHandle } from 'react' +import BLOG from '@/blog.config' +import algoliasearch from 'algoliasearch' +import replaceSearchResult from '@/components/Mark' + +/** + * 结合 Algolia 实现的弹出式搜索框 + * 打开方式 cRef.current.openSearch() + * https://www.algolia.com/doc/api-reference/search-api-parameters/ + */ +export default function AlgoliaSearchModal({ cRef }) { + const [searchResults, setSearchResults] = useState([]) + const [isModalOpen, setIsModalOpen] = useState(false) + /** + * 对外暴露方法 + */ + useImperativeHandle(cRef, () => { + return { + openSearch: () => { + setIsModalOpen(true) + } + } + }) + + if (!BLOG.ALGOLIA_APP_ID) { + return <> + } + + const client = algoliasearch(BLOG.ALGOLIA_APP_ID, BLOG.ALGOLIA_SEARCH_ONLY_APP_KEY) + const index = client.initIndex(BLOG.ALGOLIA_INDEX) + + const handleSearch = async (query) => { + try { + const res = await index.search(query) + console.log(res) + const { hits } = res + setSearchResults(hits) + const doms = document.getElementById('search-wrapper').getElementsByClassName('replace') + replaceSearchResult({ + doms, + search: query, + target: { + element: 'span', + className: 'text-blue-600 border-b border-dashed' + } + }) + } catch (error) { + console.error('Algolia search error:', error) + } + } + + const closeModal = () => { + setIsModalOpen(false) + } + + return ( +
+ {/* 内容 */} +
+ +
+
搜索
+
+
+ + handleSearch(e.target.value)} + className="bg-gray-50 dark:bg-gray-600 outline-blue-500 w-full px-4 my-2 py-1 mb-4 border rounded-md" /> + + {/* 标签组 */} +
+ +
+ + + +
Algolia 提供搜索服务
+
+ + {/* 遮罩 */} +
+ +
+ ) +} diff --git a/lib/algolia.js b/lib/algolia.js index d54b3ea6..b7439438 100644 --- a/lib/algolia.js +++ b/lib/algolia.js @@ -2,12 +2,25 @@ import BLOG from '@/blog.config' import { getPageContentText } from '@/pages/search/[keyword]' import algoliasearch from 'algoliasearch' +/** + * 生成全文索引 + * @param {*} allPages + */ +const generateAlgoliaSearch = async({ allPages, force = false }) => { + allPages?.forEach(p => { + // 判断这篇文章是否需要重新创建索引 + if (p && !p.password) { + uploadDataToAlgolia(p) + } + }) +} + /** * 上传数据 */ const uploadDataToAlgolia = (post) => { // Connect and authenticate with your Algolia app - const client = algoliasearch(BLOG.ALGOLIA_APP_ID, BLOG.ALGOLIA_APP_KEY) + const client = algoliasearch(BLOG.ALGOLIA_APP_ID, BLOG.ALGOLIA_ADMIN_APP_KEY) // Create a new index and add a record const index = client.initIndex(BLOG.ALGOLIA_INDEX) @@ -26,4 +39,4 @@ const uploadDataToAlgolia = (post) => { }) } -export { uploadDataToAlgolia } +export { uploadDataToAlgolia, generateAlgoliaSearch } diff --git a/lib/sitemap.xml.js b/lib/sitemap.xml.js index e2725f23..6c8bd040 100644 --- a/lib/sitemap.xml.js +++ b/lib/sitemap.xml.js @@ -38,6 +38,12 @@ export async function generateSitemapXml({ allPages }) { console.warn('无法写入文件', error) } } + +/** + * 生成站点地图 + * @param {*} urls + * @returns + */ function createSitemapXml(urls) { let urlsXml = '' urls.forEach(u => { diff --git a/pages/index.js b/pages/index.js index fd478612..404fab19 100644 --- a/pages/index.js +++ b/pages/index.js @@ -6,6 +6,7 @@ import { generateRobotsTxt } from '@/lib/robots.txt' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { generateAlgoliaSearch } from '@/lib/algolia' /** * 首页布局 @@ -62,6 +63,11 @@ export async function getStaticProps() { generateRss(props?.latestPosts || []) } + // 生成全文索引 - 仅在 yarn build 时执行 && process.env.npm_lifecycle_event === 'build' + if (BLOG.ALGOLIA_APP_ID && JSON.parse(BLOG.ALGOLIA_RECREATE_DATA)) { + generateAlgoliaSearch({ allPages: props.allPages }) + } + delete props.allPages return { diff --git a/themes/heo/components/SearchButton.js b/themes/heo/components/SearchButton.js index aee73e49..e23907a4 100644 --- a/themes/heo/components/SearchButton.js +++ b/themes/heo/components/SearchButton.js @@ -1,9 +1,30 @@ +import BLOG from '@/blog.config' import { useGlobal } from '@/lib/global' -import Link from 'next/link' +import { useRouter } from 'next/router' +import AlgoliaSearchModal from '@/components/AlgoliaSearchModal' +import { useRef } from 'react' +/** + * 搜索按钮 + * @returns + */ export default function SearchButton() { const { locale } = useGlobal() - return - - + const router = useRouter() + const searchModal = useRef(null) + + function handleSearch() { + if (BLOG.ALGOLIA_APP_ID) { + searchModal.current.openSearch() + } else { + router.push('/search') + } + } + + return <> +
+ +
+ + }