Merge pull request #70 from tangly1024/feature-search

Feature search
This commit is contained in:
tangly1024
2022-03-02 11:29:37 +08:00
committed by GitHub
10 changed files with 152 additions and 17 deletions

View File

@@ -2,7 +2,7 @@ import BLOG from '@/blog.config'
import { NotionAPI } from 'notion-client'
import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
export async function getPostBlocks (id, from, slice) {
export async function getPostBlocks (id, from, slice, retryCount = 3) {
const cacheKey = 'page_block_' + id
let pageBlock = await getDataFromCache(cacheKey)
if (pageBlock) {
@@ -17,6 +17,10 @@ export async function getPostBlocks (id, from, slice) {
console.log('[请求成功]', `from:${from}`, `id:${id}`)
} catch (error) {
console.error('[请求失败]', `from:${from}`, `id:${id}`, `error:${error}`)
if (retryCount && retryCount > 0) { // 重试
console.error('[重试请求]', `from:${from}`, `id:${id}`, `剩余次数:${retryCount}`)
return getPostBlocks(id, from, slice, retryCount - 1)
}
return null
}

View File

@@ -27,7 +27,6 @@ export async function getStaticProps () {
BLOG.POSTS_PER_PAGE * page
)
if (THEME_CONFIG.POST_LIST_PREVIEW || BLOG.POST_LIST_PREVIEW) {
console.log('加载预览')
for (const i in postsToShow) {
const post = postsToShow[i]
const blockMap = await getPostBlocks(post.id, 'slug', BLOG.POST_PREVIEW_LINES)

View File

@@ -45,7 +45,6 @@ export async function getStaticProps ({ params: { page } }) {
)
// 加载预览
if (THEME_CONFIG.POST_LIST_PREVIEW || BLOG.POST_LIST_PREVIEW) {
console.log('加载预览')
for (const i in postsToShow) {
const post = postsToShow[i]
const blockMap = await getPostBlocks(post.id, 'slug', BLOG.POST_PREVIEW_LINES)

120
pages/search/[keyword].js Normal file
View File

@@ -0,0 +1,120 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import { LayoutSearch } from '@/themes'
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { getDataFromCache } from '@/lib/cache/cache_manager'
export async function getStaticPaths () {
return {
paths: [],
fallback: true
}
}
/**
* 将对象的指定字段拼接到字符串
* @param sourceTextArray
* @param targetObj
* @param key
* @returns {*}
*/
function appendText (sourceTextArray, targetObj, key) {
if (!targetObj) {
return sourceTextArray
}
const textArray = targetObj[key]
const text = textArray ? getTextContent(textArray) : ''
if (text && text !== 'Untitled') {
return sourceTextArray.concat(text)
}
return sourceTextArray
}
/**
* 递归获取层层嵌套的数组
* @param {*} textArray
* @returns
*/
function getTextContent (textArray) {
if (typeof textArray === 'object') {
let result = ''
textArray.forEach(textObj => {
result = result + getTextContent(textObj)
})
return result
} else if (typeof textArray === 'string') {
return textArray
}
}
export async function getStaticProps ({ params: { keyword } }) {
const {
allPosts,
categories,
tags,
postCount,
latestPosts,
customNav
} = await getGlobalNotionData({ from: 'search-props', pageType: ['Post'] })
const filterPosts = []
for (const post of allPosts) {
const cacheKey = 'page_block_' + post.id
const page = await getDataFromCache(cacheKey)
const tagContent = post.tags ? post.tags.join(' ') : ''
const categoryContent = post.category ? post.category.join(' ') : ''
let indexContent = [post.title, post.summary, tagContent, categoryContent]
console.log('搜索是否命中缓存', page !== null)
if (page !== null) {
const contentIds = Object.keys(page.block)
contentIds.forEach(id => {
const properties = page?.block[id]?.value?.properties
indexContent = appendText(indexContent, properties, 'title')
indexContent = appendText(indexContent, properties, 'caption')
})
}
post.results = []
let hit = false
const re = new RegExp(`${keyword}`, 'g')
indexContent.forEach(c => {
const index = c.toLowerCase().indexOf(keyword.toLowerCase())
if (index > -1) {
hit = true
const referText = c?.replace(re, `<span class='text-red-500'>${keyword}</span>`)
post.results.push(`<span>${referText}</span>`)
} else {
post.results.push(`<span>${c}</span>`)
}
})
if (hit) {
filterPosts.push(post)
}
}
return {
props: {
posts: filterPosts,
tags,
categories,
postCount,
latestPosts,
customNav,
keyword
},
revalidate: 1
}
}
const Index = (props) => {
const { keyword } = props
const { locale } = useGlobal()
const meta = {
title: `${keyword || ''} | ${locale.NAV.SEARCH} | ${BLOG.TITLE} `,
description: BLOG.DESCRIPTION,
type: 'website'
}
return <LayoutSearch {...props} meta={meta} currentSearch={keyword} />
}
export default Index

View File

@@ -23,7 +23,7 @@ export const Layout404 = (props) => {
<div
className='md:-mt-20 text-black w-full h-screen text-center justify-center content-center items-center flex flex-col'>
<div className='dark:text-gray-200'>
<h2 className='inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top'><i className='mr-2 fa-spinner animate-spin'/>404</h2>
<h2 className='inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top'><i className='mr-2 fas fa-spinner animate-spin'/>404</h2>
<div className='inline-block text-left h-32 leading-10 items-center'>
<h2 className='m-0 p-0'>页面无法加载即将返回首页</h2>
</div>

View File

@@ -41,10 +41,16 @@ const BlogPostCard = ({ post, showSummary }) => {
</div>
</div>
{(!showPreview || showSummary) && <p className='mt-4 mb-24 text-gray-700 dark:text-gray-300 text-sm font-light leading-7'>
{(!showPreview || showSummary) && !post.results && <p className='mt-4 mb-24 text-gray-700 dark:text-gray-300 text-sm font-light leading-7'>
{post.summary}
</p>}
{/* 搜索结果 */}
{post.results && <p className='mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7'>
{post.results.map(r => <span key={r}><span dangerouslySetInnerHTML={{ __html: r }}/>...</span>)}
</p>
}
{showPreview && post?.blockMap && <div className='overflow-ellipsis truncate'>
<NotionRenderer
bodyClassName='max-h-full'

View File

@@ -65,7 +65,7 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_NE
<div onClick={() => {
handleGetMore()
}}
className='w-full my-4 py-4 text-center cursor-pointer glassmorphism shadow-xl rounded-xl dark:text-gray-200'
className='w-full my-4 py-4 text-center cursor-pointer glassmorphism shadow hover:shadow-xl duration-200 dark:text-gray-200'
> {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`} </div>
</div>
</div>

View File

@@ -4,7 +4,7 @@ import { useImperativeHandle, useRef, useState } from 'react'
const SearchInput = ({ currentTag, currentSearch, cRef }) => {
const { locale } = useGlobal()
const [searchKey, setSearchKey] = useState(currentSearch || '')
// const [searchKey, setSearchKey] = useState(currentSearch || '')
const [onLoading, setLoadingState] = useState(false)
const router = useRouter()
const searchInputRef = useRef()
@@ -18,9 +18,10 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
const handleSearch = (key) => {
if (key && key !== '') {
setLoadingState(true)
router.push({ pathname: '/search', query: { s: key } }).then(r => {
setLoadingState(false)
})
// router.push({ pathname: '/search/' + key }).then(r => {
// setLoadingState(false)
// })
location.href = '/search/' + key
} else {
router.push({ pathname: '/' }).then(r => {
})
@@ -35,11 +36,17 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
}
const cleanSearch = () => {
searchInputRef.current.value = ''
setSearchKey('')
setShowClean(false)
}
const [showClean, setShowClean] = useState(false)
const updateSearchKey = (val) => {
setSearchKey(val)
searchInputRef.current.value = val
if (val) {
setShowClean(true)
} else {
setShowClean(false)
}
}
return <div className='flex border dark:border-gray-600 w-full bg-gray-100 dark:bg-gray-900'>
@@ -50,12 +57,12 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
className={'w-full text-sm pl-4 transition focus:shadow-lg font-light leading-10 border-gray-300 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
onKeyUp={handleKeyUp}
onChange={e => updateSearchKey(e.target.value)}
defaultValue={searchKey}
defaultValue={currentSearch}
/>
{(searchKey && searchKey.length && <i className='fas fa-times text-gray-300 float-right m-3 cursor-pointer' onClick={cleanSearch} />)}
{(showClean && <i className='fas fa-times text-gray-300 float-right m-3 cursor-pointer' onClick={cleanSearch} />)}
<div className='p-3 bg-gray-50 flex border-l dark:border-gray-700 dark:hover:bg-gray-800 dark:bg-gray-600 justify-center items-center cursor-pointer'
onClick={() => { handleSearch(searchKey) }}>
onClick={handleSearch}>
<i className={`${onLoading ? 'fa-spinner animate-spin ' : 'fa-search'} fas hover:scale-125 hover:text-black transform duration-200 dark:text-gray-300 dark:hover:text-white text-gray-600 cursor-pointer`} />
</div>
</div>

View File

@@ -19,7 +19,7 @@ import CONFIG_NEXT from '../config_next'
* @constructor
*/
const SideAreaLeft = (props) => {
const { currentTag, post, postCount, currentSearch } = props
const { post, postCount } = props
const { locale } = useGlobal()
const showToc = post && post.toc && post.toc.length > 1
return <aside id='left' className='hidden lg:block flex-col w-60 mr-4'>
@@ -32,7 +32,7 @@ const SideAreaLeft = (props) => {
<MenuButtonGroup allowCollapse={true} {...props} />
</div>
{CONFIG_NEXT.MENU_SEARCH && <div className='px-2 pt-2 font-sans'>
<SearchInput currentTag={currentTag} currentSearch={currentSearch} />
<SearchInput {...props} />
</div>}
</section>
</section>