diff --git a/components/AlgoliaSearchModal.js b/components/AlgoliaSearchModal.js index 26ed7d88..6554e353 100644 --- a/components/AlgoliaSearchModal.js +++ b/components/AlgoliaSearchModal.js @@ -1,10 +1,11 @@ -import { useState, useImperativeHandle, useRef } from 'react' +import { useState, useImperativeHandle, useRef, useEffect } from 'react' import algoliasearch from 'algoliasearch' import replaceSearchResult from '@/components/Mark' import Link from 'next/link' import { useGlobal } from '@/lib/global' import throttle from 'lodash/throttle' import { siteConfig } from '@/lib/config' +import { useHotkeys } from 'react-hotkeys-hook'; /** * 结合 Algolia 实现的弹出式搜索框 @@ -19,7 +20,62 @@ export default function AlgoliaSearchModal({ cRef }) { const [totalPage, setTotalPage] = useState(0) const [totalHit, setTotalHit] = useState(0) const [useTime, setUseTime] = useState(0) + const inputRef = useRef(null) + const [activeIndex, setActiveIndex] = useState(0) + useHotkeys('ctrl+k', (e) => { + e.preventDefault() + setIsModalOpen(true) + }) + // 方向键调整选中 + useHotkeys('down', (e) => { + e.preventDefault() + if (activeIndex < searchResults.length - 1) { + setActiveIndex(activeIndex + 1) + } + }, { enableOnFormTags: true }) + useHotkeys('up', (e) => { + e.preventDefault() + if (activeIndex > 0) { + setActiveIndex(activeIndex - 1) + } + }, { enableOnFormTags: true }) + // esc关闭 + useHotkeys('esc', (e) => { + e.preventDefault() + setIsModalOpen(false) + }, { enableOnFormTags: true }) + // 跳转Search结果 + const onJumpSearchResult = () => { + if (searchResults.length > 0) { + window.location.href = `${siteConfig('SUB_PATH', '')}/${searchResults[activeIndex].slug}` + } + } + // enter跳转 + useHotkeys('enter', (e) => { + if (searchResults.length > 0) { + onJumpSearchResult(index) + } + }, { enableOnFormTags: true }) + const resetSearch = () => { + setActiveIndex(0) + setKeyword('') + setSearchResults([]) + setUseTime(0) + setTotalPage(0) + setTotalHit(0) + if (inputRef.current) inputRef.current.value = '' + } + + useEffect(() => { + if (isModalOpen) { + setTimeout(() => { + inputRef.current?.focus() + }, 100) + } else { + resetSearch() + } + }, [isModalOpen]) /** * 对外暴露方法 */ @@ -45,6 +101,7 @@ export default function AlgoliaSearchModal({ cRef }) { setUseTime(0) setTotalPage(0) setTotalHit(0) + setActiveIndex(0) if (!query || query === '') { return } @@ -56,7 +113,6 @@ export default function AlgoliaSearchModal({ cRef }) { setTotalPage(nbPages) setTotalHit(nbHits) setSearchResults(hits) - const doms = document.getElementById('search-wrapper').getElementsByClassName('replace') setTimeout(() => { @@ -65,7 +121,7 @@ export default function AlgoliaSearchModal({ cRef }) { search: query, target: { element: 'span', - className: 'text-blue-600 border-b border-dashed' + className: 'font-bold border-b border-dashed' } }) }, 200) // 延时高亮 @@ -75,8 +131,8 @@ export default function AlgoliaSearchModal({ cRef }) { } // 定义节流函数,确保在用户停止输入一段时间后才会调用处理搜索的方法 - const throttledHandleInputChange = useRef(throttle((query) => { - handleSearch(query, 0); + const throttledHandleInputChange = useRef(throttle((query, page = 0) => { + handleSearch(query, page); }, 1000)); // 用于存储搜索延迟的计时器 @@ -115,19 +171,16 @@ export default function AlgoliaSearchModal({ cRef }) { if (!siteConfig('ALGOLIA_APP_ID')) { return <> } - return (
{/* 模态框 */}
搜索
@@ -144,39 +197,53 @@ export default function AlgoliaSearchModal({ cRef }) { placeholder="在这里输入搜索关键词..." onChange={e => handleInputChange(e)} className="text-black dark:text-gray-200 bg-gray-50 dark:bg-gray-600 outline-blue-500 w-full px-4 my-2 py-1 mb-4 border rounded-md" + ref={inputRef} /> {/* 标签组 */}
- + { + searchResults.length === 0 && keyword && ( +
+

无法找到相关结果 + "{keyword}"

+
+ ) + }
    - {searchResults.map(result => ( -
  • + {searchResults.map((result, index) => ( +
  • setActiveIndex(index)} + onClick={() => onJumpSearchResult(index)} + className={`cursor-pointer replace my-2 p-2 duration-100 ${activeIndex === index ? 'bg-blue-600 ' : ''}`}> {result.title}
  • ))}
- -
- {totalHit > 0 && ( -
- 共搜索到 {totalHit} 条结果,用时 {useTime} 毫秒 -
- )} -
-
- - Algolia 提供搜索服务 - {' '} +
+
+ {totalHit > 0 && ( +

+ 共搜索到 {totalHit} 条结果,用时 {useTime} 毫秒 +

+ )} +
+
+ + Algolia 提供搜索服务 + +
+
{/* 遮罩 */} @@ -191,26 +258,26 @@ export default function AlgoliaSearchModal({ cRef }) { /** * 标签组 */ -function TagGroups(props) { +function TagGroups() { const { tagOptions } = useGlobal() // 获取tagOptions数组前十个 const firstTenTags = tagOptions?.slice(0, 10) return
- { - firstTenTags?.map((tag, index) => { - return -
-
{tag.name}
{tag.count ? {tag.count} : <>} -
+ { + firstTenTags?.map((tag, index) => { + return +
+
{tag.name}
{tag.count ? {tag.count} : <>} +
- - }) - } -
+ + }) + } +
} /** @@ -222,24 +289,23 @@ function Pagination(props) { if (totalPage <= 0) { return <> } - const pagesElement = [] + return ( +
+ {Array.from({ length: totalPage }, (_, i) => { + const classNames = page === i + ? 'font-bold text-white bg-blue-600 rounded' + : 'hover:text-blue-600 hover:font-bold' - for (let i = 0; i < totalPage; i++) { - const selected = page === i - pagesElement.push(getPageElement(i, selected, switchPage)) - } - return
- {pagesElement.map(p => p)} -
-} - -/** - * 获取分页按钮 - * @param {*} i - * @param {*} selected - */ -function getPageElement(i, selected, switchPage) { - return
switchPage(i)} className={`${selected ? 'font-bold text-white bg-blue-600 rounded' : 'hover:text-blue-600 hover:font-bold'} text-center cursor-pointer w-6 h-6 `}> - {i + 1} -
+ return ( +
switchPage(i)} + className={`text-center cursor-pointer w-6 h-6 ${classNames}`} + key={i} + > + {i + 1} +
+ ) + })} +
+ ) } diff --git a/package.json b/package.json index 088edf4d..8f7d27ea 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-facebook": "^8.1.4", + "react-hotkeys-hook": "^4.5.0", "react-notion-x": "6.16.0", "react-share": "^4.4.1", "react-tweet-embed": "~2.0.0", diff --git a/yarn.lock b/yarn.lock index 398a5d8f..7935bfbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4219,6 +4219,11 @@ react-hotkeys-hook@^3.0.3: dependencies: hotkeys-js "3.9.4" +react-hotkeys-hook@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz#807b389b15256daf6a813a1ec09e6698064fe97f" + integrity sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug== + react-image@^4.0.3: version "4.1.0" resolved "https://registry.yarnpkg.com/react-image/-/react-image-4.1.0.tgz#92f2d4a809a178b3bf69acd7bad7da7aa5e7364c"