mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-15 23:16:48 +00:00
Merge remote-tracking branch 'origin/main' into theme-medium
This commit is contained in:
@@ -14,6 +14,7 @@ const BLOG = {
|
||||
BEI_AN: process.env.NEXT_PUBLIC_BEI_AN || '', // 备案号 闽ICP备XXXXXXX
|
||||
APPEARANCE: 'auto', // ['light', 'dark', 'auto'],
|
||||
FONT: 'font-serif tracking-wider subpixel-antialiased', // 文章字体 ['font-sans', 'font-serif', 'font-mono'] @see https://www.tailwindcss.cn/docs/font-family
|
||||
FONT_AWESOME_PATH: 'https://cdn.bootcdn.net/ajax/libs/font-awesome/5.15.4/css/all.min.css', // 图标库CDN ,国内推荐BootCDN,国外推荐 CloudFlare https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css
|
||||
BACKGROUND_LIGHT: '#eeeeee', // use hex value, don't forget '#' e.g #fffefc
|
||||
BACKGROUND_DARK: '#111827', // use hex value, don't forget '#'
|
||||
PATH: '', // leave this empty unless you want to deploy in a folder
|
||||
@@ -24,6 +25,9 @@ const BLOG = {
|
||||
POSTS_PER_PAGE: 6, // post counts per page
|
||||
POSTS_SORT_BY: 'notion', // 排序方式 'date'按时间,'notion'由notion控制
|
||||
|
||||
PREVIEW_CATEGORY_COUNT: 16, // 首页最多展示的分类数量,0为不限制
|
||||
PREVIEW_TAG_COUNT: 16, // 首页最多展示的标签数量,0为不限制
|
||||
|
||||
// 社交链接,不需要可留空白,例如 CONTACT_WEIBO:''
|
||||
CONTACT_EMAIL: 'tlyong1992@hotmail.com',
|
||||
CONTACT_WEIBO: '',
|
||||
@@ -36,13 +40,14 @@ const BLOG = {
|
||||
COMMENT_CUSDIS_HOST: process.env.NEXT_PUBLIC_COMMENT_CUSDIS_HOST || 'https://cusdis.com', // data-host, change this if you're using self-hosted version
|
||||
COMMENT_CUSDIS_SCRIPT_SRC: process.env.NEXT_PUBLIC_COMMENT_CUSDIS_SCRIPT_SRC || 'https://cusdis.com/js/cusdis.es.js', // change this if you're using self-hosted version
|
||||
|
||||
COMMENT_UTTERRANCES_REPO: process.env.NEXT_PUBLIC_COMMENT_UTTERRANCES_REPO || '', // e.g 'tangly1024/NotionNext' see https://utteranc.es/
|
||||
COMMENT_UTTERRANCES_REPO: process.env.NEXT_PUBLIC_COMMENT_UTTERRANCES_REPO || '', // 你的代码仓库名, 例如我是 'tangly1024/NotionNext'; 更多文档参考 https://utteranc.es/
|
||||
|
||||
COMMENT_GITALK_REPO: process.env.NEXT_PUBLIC_COMMENT_GITALK_REPO || '', // e.g NotionNext see https://gitalk.github.io/
|
||||
COMMENT_GITALK_OWNER: process.env.NEXT_PUBLIC_COMMENT_GITALK_OWNER || '', // e.g tangly1024
|
||||
COMMENT_GITALK_ADMIN: process.env.NEXT_PUBLIC_COMMENT_GITALK_ADMIN || '', // e.g 'tangly1024'
|
||||
COMMENT_GITALK_CLIENT_ID: process.env.NEXT_PUBLIC_COMMENT_GITALK_CLIENT_ID || '', // e.g 20位ID
|
||||
COMMENT_GITALK_CLIENT_SECRET: process.env.NEXT_PUBLIC_COMMENT_GITALK_CLIENT_SECRET || '', // e.g 40位ID
|
||||
// gitalk评论插件 更多参考 https://gitalk.github.io/
|
||||
COMMENT_GITALK_REPO: process.env.NEXT_PUBLIC_COMMENT_GITALK_REPO || '', // 你的Github仓库名,例如 'NotionNext'
|
||||
COMMENT_GITALK_OWNER: process.env.NEXT_PUBLIC_COMMENT_GITALK_OWNER || '', // 你的用户名 e.g tangly1024
|
||||
COMMENT_GITALK_ADMIN: process.env.NEXT_PUBLIC_COMMENT_GITALK_ADMIN || '', // 管理员用户名、一般是自己 e.g 'tangly1024'
|
||||
COMMENT_GITALK_CLIENT_ID: process.env.NEXT_PUBLIC_COMMENT_GITALK_CLIENT_ID || '', // e.g 20位ID , 在gitalk后台获取
|
||||
COMMENT_GITALK_CLIENT_SECRET: process.env.NEXT_PUBLIC_COMMENT_GITALK_CLIENT_SECRET || '', // e.g 40位ID, 在gitalk后台获取
|
||||
COMMENT_GITALK_DISTRACTION_FREE_MODE: false,
|
||||
|
||||
COMMENT_GITTER_ROOM: process.env.NEXT_PUBLIC_COMMENT_GITTER_ROOM || '', // gitter聊天室 see https://gitter.im/ 不需要则留空
|
||||
|
||||
@@ -25,6 +25,10 @@ const Comment = ({ frontMatter }) => {
|
||||
return (
|
||||
<div className='comment mt-5 text-gray-800 dark:text-gray-300'>
|
||||
<Tabs>
|
||||
{BLOG.COMMENT_UTTERRANCES_REPO && (<div key='Utterance'>
|
||||
<UtterancesComponent issueTerm={frontMatter.id} className='px-2' />
|
||||
</div>)}
|
||||
|
||||
{BLOG.COMMENT_CUSDIS_APP_ID && (<div key='Cusdis'>
|
||||
<ReactCusdis
|
||||
lang={locale.LOCALE.toLowerCase()}
|
||||
@@ -38,10 +42,6 @@ const Comment = ({ frontMatter }) => {
|
||||
/>
|
||||
</div>)}
|
||||
|
||||
{BLOG.COMMENT_UTTERRANCES_REPO && (<div key='Utterance'>
|
||||
<UtterancesComponent issueTerm={frontMatter.id} className='px-2' />
|
||||
</div>)}
|
||||
|
||||
{BLOG.COMMENT_GITALK_CLIENT_ID && (<div key='GitTalk'>
|
||||
<GitalkComponent
|
||||
options={{
|
||||
|
||||
@@ -43,9 +43,8 @@ const Tabs = ({ className, children }) => {
|
||||
</ul>
|
||||
<div>
|
||||
{children.map((item, index) => {
|
||||
return <section key={index}
|
||||
className={`${currentTab === index ? 'block animate__animated animate__fadeIn animate__faster' : 'hidden'}`}>
|
||||
{item}
|
||||
return <section key={index} className={ 'animate__animated animate__fadeIn animate__faster'}>
|
||||
{currentTab === index && item}
|
||||
</section>
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* @param allPosts
|
||||
* @returns {Promise<{}|*[]>}
|
||||
*/
|
||||
export async function getAllCategories (allPosts) {
|
||||
if (!allPosts) {
|
||||
export async function getAllCategories ({ allPosts, categoryOptions, sliceCount = 0 }) {
|
||||
if (!allPosts || !categoryOptions) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 计数
|
||||
let categories = allPosts.map(p => p.category)
|
||||
categories = [...categories.flat()]
|
||||
const categoryObj = {}
|
||||
@@ -18,5 +18,19 @@ export async function getAllCategories (allPosts) {
|
||||
categoryObj[category] = 1
|
||||
}
|
||||
})
|
||||
return categoryObj
|
||||
const list = []
|
||||
categoryOptions.forEach(c => {
|
||||
const count = categoryObj[c.value]
|
||||
if (count) {
|
||||
list.push({ id: c.id, name: c.value, color: c.color, count })
|
||||
}
|
||||
})
|
||||
|
||||
// 按照数量排序
|
||||
// list.sort((a, b) => b.count - a.count)
|
||||
if (sliceCount && sliceCount > 0) {
|
||||
return list.slice(0, sliceCount)
|
||||
} else {
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,13 @@
|
||||
* @param tagOptions tags的下拉选项
|
||||
* @returns {Promise<{}|*[]>}
|
||||
*/
|
||||
export async function getAllTags ({ allPosts, sliceCount = 16, tagOptions }) {
|
||||
if (!allPosts) {
|
||||
export async function getAllTags ({ allPosts, sliceCount = 0, tagOptions }) {
|
||||
if (!allPosts || !tagOptions) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 计数
|
||||
let tags = allPosts.map(p => p.tags)
|
||||
tags = [...tags.flat()]
|
||||
|
||||
// 标签计数
|
||||
const tagObj = {}
|
||||
tags.forEach(tag => {
|
||||
if (tag in tagObj) {
|
||||
@@ -23,13 +21,16 @@ export async function getAllTags ({ allPosts, sliceCount = 16, tagOptions }) {
|
||||
tagObj[tag] = 1
|
||||
}
|
||||
})
|
||||
|
||||
// 按照标签数量排序
|
||||
const list = Object.keys(tagObj).map((tag) => {
|
||||
const color = tagOptions.find(option => option.value === tag)?.color || 'gray'
|
||||
return { name: tag, count: tagObj[tag], color }
|
||||
const list = []
|
||||
tagOptions.forEach(c => {
|
||||
const count = tagObj[c.value]
|
||||
if (count) {
|
||||
list.push({ id: c.id, name: c.value, color: c.color, count })
|
||||
}
|
||||
})
|
||||
list.sort((a, b) => b.count - a.count)
|
||||
|
||||
// 按照数量排序
|
||||
// list.sort((a, b) => b.count - a.count)
|
||||
if (sliceCount && sliceCount > 0) {
|
||||
return list.slice(0, sliceCount)
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getAllTags } from './getAllTags'
|
||||
* @param {*} pageId
|
||||
* @param {*} from
|
||||
* @param latestPostCount 截取最新文章数量
|
||||
* @param categoryCount
|
||||
* @param tagsCount 截取标签数量
|
||||
* @param pageType 过滤的文章类型,数组格式 ['Page','Post']
|
||||
* @returns {
|
||||
@@ -27,17 +28,29 @@ export async function getGlobalNotionData ({
|
||||
pageId = BLOG.NOTION_PAGE_ID,
|
||||
from,
|
||||
latestPostCount = 5,
|
||||
tagsCount = 16,
|
||||
categoryCount = BLOG.PREVIEW_CATEGORY_COUNT,
|
||||
tagsCount = BLOG.PREVIEW_TAG_COUNT,
|
||||
pageType = ['Post']
|
||||
}) {
|
||||
const notionPageData = await getNotionPageData({ pageId, from })
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const categoryOptions = notionPageData.categoryOptions
|
||||
const allPosts = await getAllPosts({ notionPageData, from, pageType })
|
||||
const postCount = await getAllPostCount({ notionPageData, from })
|
||||
const customNav = await getCustomNav({ notionPageData })
|
||||
const categories = await getAllCategories(allPosts)
|
||||
const categories = await getAllCategories({ allPosts, categoryOptions, sliceCount: categoryCount })
|
||||
const tags = await getAllTags({ allPosts, tagOptions, sliceCount: tagsCount })
|
||||
// 深拷贝
|
||||
const latestPosts = await getLatestPosts({ notionPageData, from, latestPostCount })
|
||||
return { allPosts, latestPosts, categories, postCount, customNav, tags }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新文章
|
||||
* @param {*}} param0
|
||||
* @returns
|
||||
*/
|
||||
async function getLatestPosts ({ notionPageData, from, latestPostCount }) {
|
||||
const allPosts = await getAllPosts({ notionPageData, from, pageType: ['Post'] })
|
||||
let latestPosts = Object.create(allPosts)
|
||||
// 时间排序
|
||||
latestPosts.sort((a, b) => {
|
||||
@@ -48,14 +61,7 @@ export async function getGlobalNotionData ({
|
||||
|
||||
// 只取前五
|
||||
latestPosts = latestPosts.slice(0, latestPostCount)
|
||||
return {
|
||||
allPosts,
|
||||
latestPosts,
|
||||
categories,
|
||||
postCount,
|
||||
customNav,
|
||||
tags
|
||||
}
|
||||
return latestPosts
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,10 +109,17 @@ async function getCustomNav ({ notionPageData }) {
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function getTagOptions (schema) {
|
||||
if (!schema) return {}
|
||||
const tagSchema = Object.values(schema).find(e => e.name === 'tags')
|
||||
return tagSchema?.options || {}
|
||||
}
|
||||
|
||||
function getCategoryOptions (schema) {
|
||||
if (!schema) return {}
|
||||
const categorySchema = Object.values(schema).find(e => e.name === 'category')
|
||||
return categorySchema?.options || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用NotionAPI获取Page数据
|
||||
* @returns {Promise<JSX.Element|null|*>}
|
||||
@@ -116,7 +129,6 @@ async function getPageRecordMapByNotionAPI ({ pageId, from }) {
|
||||
if (!pageRecordMap) {
|
||||
return []
|
||||
}
|
||||
|
||||
pageId = idToUuid(pageId)
|
||||
const collection = Object.values(pageRecordMap.collection)[0]?.value
|
||||
const collectionQuery = pageRecordMap.collection_query
|
||||
@@ -124,6 +136,7 @@ async function getPageRecordMapByNotionAPI ({ pageId, from }) {
|
||||
const schema = collection?.schema
|
||||
const rawMetadata = block[pageId].value
|
||||
const tagOptions = getTagOptions(schema)
|
||||
const categoryOptions = getCategoryOptions(schema)
|
||||
|
||||
// Check Type Page-Database和Inline-Database
|
||||
if (
|
||||
@@ -140,6 +153,7 @@ async function getPageRecordMapByNotionAPI ({ pageId, from }) {
|
||||
block,
|
||||
schema,
|
||||
tagOptions,
|
||||
categoryOptions,
|
||||
rawMetadata
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ const MyApp = ({ Component, pageProps }) => {
|
||||
{BLOG.ANALYTICS_GOOGLE_ID && <Gtag />}
|
||||
{JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && <Busuanzi/>}
|
||||
{BLOG.ADSENSE_GOOGLE_ID && <GoogleAdsense/>}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossOrigin="anonymous" referrerPolicy="no-referrer" />
|
||||
{/* FontawesomeCDN */}
|
||||
<link href={BLOG.FONT_AWESOME_PATH} rel="stylesheet" referrerPolicy="no-referrer" />
|
||||
<Component {...pageProps} />
|
||||
</GlobalContextProvider>
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function Category (props) {
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'category-index-props'
|
||||
const { allPosts, categories, tags, postCount, latestPosts, customNav } = await getGlobalNotionData({ from })
|
||||
const { allPosts, categories, tags, postCount, latestPosts, customNav } = await getGlobalNotionData({ from, categoryCount: 0 })
|
||||
|
||||
return {
|
||||
props: {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { LayoutSearch } from '@/themes'
|
||||
|
||||
export async function getStaticProps () {
|
||||
const {
|
||||
allPosts,
|
||||
categories,
|
||||
tags,
|
||||
postCount,
|
||||
latestPosts,
|
||||
customNav
|
||||
} = await getGlobalNotionData({ from: 'search-props', pageType: ['Post'] })
|
||||
return {
|
||||
props: {
|
||||
posts: allPosts,
|
||||
tags,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts,
|
||||
customNav
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
const Search = (props) => {
|
||||
return <LayoutSearch {...props} />
|
||||
}
|
||||
|
||||
export default Search
|
||||
135
pages/search/[keyword].js
Normal file
135
pages/search/[keyword].js
Normal file
@@ -0,0 +1,135 @@
|
||||
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 getServerSideProps ({ params: { keyword } }) {
|
||||
const {
|
||||
allPosts,
|
||||
categories,
|
||||
tags,
|
||||
postCount,
|
||||
latestPosts,
|
||||
customNav
|
||||
} = await getGlobalNotionData({ from: 'search-props', pageType: ['Post'] })
|
||||
|
||||
const filterPosts = await filterByMemCache(allPosts, keyword)
|
||||
return {
|
||||
props: {
|
||||
posts: filterPosts,
|
||||
tags,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts,
|
||||
customNav,
|
||||
keyword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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} />
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象的指定字段拼接到字符串
|
||||
* @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' && isIterable(textArray)) {
|
||||
let result = ''
|
||||
for (const textObj of textArray) {
|
||||
result = result + getTextContent(textObj)
|
||||
}
|
||||
return result
|
||||
} else if (typeof textArray === 'string') {
|
||||
return textArray
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象是否可以遍历
|
||||
* @param {*} obj
|
||||
* @returns
|
||||
*/
|
||||
const isIterable = obj => obj != null && typeof obj[Symbol.iterator] === 'function'
|
||||
|
||||
/**
|
||||
* 在内存缓存中进行全文索引
|
||||
* @param {*} allPosts
|
||||
* @param keyword 关键词
|
||||
* @returns
|
||||
*/
|
||||
async function filterByMemCache (allPosts, keyword) {
|
||||
const filterPosts = []
|
||||
for (const post of allPosts) {
|
||||
const cacheKey = 'page_block_' + post.id
|
||||
// const page = await getPostBlocks(post.id, 'search')
|
||||
const page = await getDataFromCache(cacheKey)
|
||||
const tagContent = post.tags ? post.tags.join(' ') : ''
|
||||
const categoryContent = post.category ? post.category.join(' ') : ''
|
||||
const articleInfo = post.title + post.summary + tagContent + categoryContent
|
||||
let hit = articleInfo.indexOf(keyword) > -1
|
||||
let indexContent = [post.summary]
|
||||
if (page && page.block) {
|
||||
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')
|
||||
})
|
||||
}
|
||||
// console.log('搜索是否命中缓存', page !== null, indexContent)
|
||||
post.results = []
|
||||
let hitCount = 0
|
||||
const re = new RegExp(`${keyword}`, 'gim')
|
||||
for (const i in indexContent) {
|
||||
const c = indexContent[i]
|
||||
const index = c.toLowerCase().indexOf(keyword.toLowerCase())
|
||||
const referText = c?.replace(re, `<span class='text-red-500'>${keyword}</span>`)
|
||||
if (index > -1) {
|
||||
hit = true
|
||||
hitCount += 1
|
||||
post.results.push(`<span>${referText}</span>`)
|
||||
} else {
|
||||
if ((post.results.length - 1) / hitCount < 3 || i === 0) {
|
||||
post.results.push(`<span>${referText}</span>`)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hit) {
|
||||
filterPosts.push(post)
|
||||
}
|
||||
}
|
||||
return filterPosts
|
||||
}
|
||||
|
||||
export default Index
|
||||
62
pages/search/index.js
Normal file
62
pages/search/index.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { LayoutSearch } from '@/themes'
|
||||
import BLOG from '@/blog.config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export async function getStaticProps () {
|
||||
const {
|
||||
allPosts,
|
||||
categories,
|
||||
tags,
|
||||
postCount,
|
||||
latestPosts,
|
||||
customNav
|
||||
} = await getGlobalNotionData({ from: 'search-props', pageType: ['Post'] })
|
||||
return {
|
||||
props: {
|
||||
posts: allPosts,
|
||||
tags,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts,
|
||||
customNav
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
const Search = (props) => {
|
||||
const { posts } = props
|
||||
let filteredPosts
|
||||
const searchKey = getSearchKey()
|
||||
// 静态过滤
|
||||
if (searchKey) {
|
||||
filteredPosts = posts.filter(post => {
|
||||
const tagContent = post.tags ? post.tags.join(' ') : ''
|
||||
const categoryContent = post.category ? post.category.join(' ') : ''
|
||||
const searchContent = post.title + post.summary + tagContent + categoryContent
|
||||
return searchContent.toLowerCase().includes(searchKey.toLowerCase())
|
||||
})
|
||||
} else {
|
||||
filteredPosts = posts
|
||||
}
|
||||
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${searchKey || ''} | ${locale.NAV.SEARCH} | ${BLOG.TITLE} `,
|
||||
description: BLOG.DESCRIPTION,
|
||||
type: 'website'
|
||||
}
|
||||
return <LayoutSearch {...props} posts={filteredPosts} meta={meta} currentSearch={searchKey} />
|
||||
}
|
||||
|
||||
function getSearchKey () {
|
||||
const router = useRouter()
|
||||
if (router.query && router.query.s) {
|
||||
return router.query.s
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export default Search
|
||||
@@ -1,31 +1,6 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutSearch = (props) => {
|
||||
const { posts } = props
|
||||
let filteredPosts
|
||||
const searchKey = getSearchKey()
|
||||
if (searchKey) {
|
||||
filteredPosts = posts.filter(post => {
|
||||
const tagContent = post.tags ? post.tags.join(' ') : ''
|
||||
const searchContent = post.title + post.summary + tagContent
|
||||
return searchContent.toLowerCase().includes(searchKey.toLowerCase())
|
||||
})
|
||||
} else {
|
||||
filteredPosts = posts
|
||||
}
|
||||
|
||||
console.log(filteredPosts)
|
||||
|
||||
return <LayoutBase {...props}>
|
||||
Search {searchKey}
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
function getSearchKey () {
|
||||
const router = useRouter()
|
||||
if (router.query && router.query.s) {
|
||||
return router.query.s
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ export const LayoutCategoryIndex = (props) => {
|
||||
<i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}:
|
||||
</div>
|
||||
<div id='category-list' className='duration-200 flex flex-wrap'>
|
||||
{Object.keys(categories).map(category => {
|
||||
return <Link key={category} href={`/category/${category}`} passHref>
|
||||
{categories && categories.map(category => {
|
||||
return <Link key={category.name} href={`/category/${category.name}`} passHref>
|
||||
<div
|
||||
className={'hover:text-black dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600 px-5 cursor-pointer py-2 hover:bg-gray-100'}>
|
||||
<i className='mr-4 fas fa-folder' />{category}({categories[category]})
|
||||
<i className='mr-4 fas fa-folder' />{category.name}({category.count})
|
||||
</div>
|
||||
</Link>
|
||||
})}
|
||||
|
||||
@@ -3,8 +3,6 @@ import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutIndex = (props) => {
|
||||
return <LayoutBase {...props}>
|
||||
|
||||
<BlogListPage posts={props.posts} postCount={props.postCount}/>
|
||||
|
||||
<BlogListPage {...props}/>
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -1,30 +1,8 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import BlogListPage from './components/BlogListPage'
|
||||
|
||||
export const LayoutSearch = (props) => {
|
||||
let filteredPosts
|
||||
const searchKey = getSearchKey()
|
||||
if (searchKey) {
|
||||
filteredPosts = props.posts.filter(post => {
|
||||
const tagContent = post.tags ? post.tags.join(' ') : ''
|
||||
const searchContent = post.title + post.summary + tagContent
|
||||
return searchContent.toLowerCase().includes(searchKey.toLowerCase())
|
||||
})
|
||||
} else {
|
||||
filteredPosts = props.posts
|
||||
}
|
||||
|
||||
console.log(filteredPosts)
|
||||
|
||||
return <LayoutBase {...props}>
|
||||
Search {searchKey}
|
||||
<BlogListPage {...props}/>
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
function getSearchKey () {
|
||||
const router = useRouter()
|
||||
if (router.query && router.query.s) {
|
||||
return router.query.s
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ import Catalog from './Catalog'
|
||||
|
||||
function AsideLeft (props) {
|
||||
const { tags, currentTag, categories, currentCategory, post } = props
|
||||
console.log(post)
|
||||
|
||||
return <div className='w-72 bg-white min-h-screen px-10 py-14 hidden lg:block'>
|
||||
<Logo />
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import PaginationSimple from './PaginationSimple'
|
||||
*/
|
||||
const BlogListPage = ({ page = 1, posts = [], postCount }) => {
|
||||
const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
|
||||
const showNext = page < totalPage && posts.length < postCount
|
||||
const showNext = page < totalPage && posts.length <= BLOG.POSTS_PER_PAGE && posts.length < postCount
|
||||
const [colCount, changeCol] = useState(3)
|
||||
|
||||
function updateCol () {
|
||||
|
||||
@@ -8,14 +8,14 @@ function GroupCategory ({ currentCategory, categories }) {
|
||||
|
||||
return <>
|
||||
<div id='category-list' className='dark:border-gray-600 flex flex-wrap'>
|
||||
{Object.keys(categories).map(category => {
|
||||
const selected = currentCategory === category
|
||||
return <Link key={category} href={`/category/${category}`} passHref>
|
||||
{categories.map(category => {
|
||||
const selected = currentCategory === category.name
|
||||
return <Link key={category.name} href={`/category/${category.name}`} passHref>
|
||||
<a className={(selected
|
||||
? 'hover:text-white dark:hover:text-white bg-gray-600 text-white '
|
||||
: 'dark:text-gray-400 text-gray-500 hover:text-white hover:bg-gray-500 dark:hover:text-white') +
|
||||
' text-sm w-full items-center duration-300 px-2 cursor-pointer py-1 font-light'}>
|
||||
<i className={`${selected ? 'text-white fa-folder-open' : 'fa-folder text-gray-400'} fas mr-2`} />{category}({categories[category]})
|
||||
<i className={`${selected ? 'text-white fa-folder-open' : 'fa-folder text-gray-400'} fas mr-2`} />{category.name}({category.count})
|
||||
</a>
|
||||
</Link>
|
||||
})}
|
||||
|
||||
@@ -36,16 +36,28 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
||||
setSearchKey('')
|
||||
}
|
||||
|
||||
let lock = false
|
||||
const updateSearchKey = (val) => {
|
||||
setSearchKey(val)
|
||||
if (!lock) {
|
||||
setSearchKey(val)
|
||||
}
|
||||
}
|
||||
function lockSearchInput () {
|
||||
lock = true
|
||||
}
|
||||
|
||||
function unLockSearchInput () {
|
||||
lock = false
|
||||
}
|
||||
return <div className='flex w-full bg-gray-100'>
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type='text'
|
||||
className={'w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={lockSearchInput}
|
||||
onCompositionUpdate={lockSearchInput}
|
||||
onCompositionEnd={unLockSearchInput}
|
||||
onChange={e => updateSearchKey(e.target.value)}
|
||||
defaultValue={searchKey}
|
||||
/>
|
||||
|
||||
@@ -46,14 +46,14 @@ const LayoutBase = (props) => {
|
||||
|
||||
<main id='wrapper' className='flex w-full justify-center py-8 min-h-screen'>
|
||||
|
||||
<div id='container-inner' className='pt-14 w-full mx-auto flex justify-between space-x-4 max-w-6xl'>
|
||||
<div id='container-inner' className='pt-14 w-full mx-auto flex justify-between space-x-4 max-w-7xl'>
|
||||
<div className='flex-grow w-full'>{children}</div>
|
||||
<SideRight {...props}/>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
{/* 右下角悬浮 */}
|
||||
{/* 右下角悬浮 */}
|
||||
<div className='right-8 bottom-12 lg:right-2 fixed justify-end z-20 font-sans'>
|
||||
<div className={(show ? 'animate__animated ' : 'hidden') + ' animate__fadeInUp rounded-md glassmorphism justify-center duration-500 animate__faster flex space-x-2 items-center cursor-pointer '}>
|
||||
<JumpToTopButton percent={percent}/>
|
||||
|
||||
@@ -20,16 +20,16 @@ export const LayoutCategoryIndex = props => {
|
||||
{locale.COMMON.CATEGORY}:
|
||||
</div>
|
||||
<div id="category-list" className="duration-200 flex flex-wrap mx-8">
|
||||
{Object.keys(categories).map(category => {
|
||||
{categories.map(category => {
|
||||
return (
|
||||
<Link key={category} href={`/category/${category}`} passHref>
|
||||
<Link key={category.name} href={`/category/${category.name}`} passHref>
|
||||
<div
|
||||
className={
|
||||
' duration-300 dark:hover:text-white rounded-lg px-5 cursor-pointer py-2 hover:bg-blue-600 hover:text-white'
|
||||
' duration-300 dark:hover:text-white rounded-lg px-5 cursor-pointer py-2 hover:bg-blue-400 hover:text-white'
|
||||
}
|
||||
>
|
||||
<i className="mr-4 fas fa-folder" />
|
||||
{category}({categories[category]})
|
||||
{category.name}({category.count})
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -1,38 +1,9 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutSearch = (props) => {
|
||||
const { posts } = props
|
||||
let filteredPosts
|
||||
const searchKey = getSearchKey()
|
||||
if (searchKey) {
|
||||
filteredPosts = posts.filter(post => {
|
||||
const tagContent = post.tags ? post.tags.join(' ') : ''
|
||||
const searchContent = post.title + post.summary + tagContent
|
||||
return searchContent.toLowerCase().includes(searchKey.toLowerCase())
|
||||
})
|
||||
} else {
|
||||
filteredPosts = posts
|
||||
}
|
||||
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${searchKey || ''} | ${locale.NAV.SEARCH} | ${BLOG.TITLE} `,
|
||||
description: BLOG.DESCRIPTION,
|
||||
type: 'website'
|
||||
}
|
||||
return <LayoutBase {...props} meta={meta}>
|
||||
<BlogPostListPage {...props} posts={filteredPosts}/>
|
||||
return <LayoutBase {...props}>
|
||||
<BlogPostListPage {...props}/>
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
function getSearchKey () {
|
||||
const router = useRouter()
|
||||
if (router.query && router.query.s) {
|
||||
return router.query.s
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export const LayoutSlug = props => {
|
||||
showTag={false}
|
||||
floatSlot={floatSlot}
|
||||
>
|
||||
<div className="w-full lg:shadow-xl lg:hover:shadow-2xl lg:border lg:border-gray-100 lg:rounded-xl lg:px-2 lg:py-4 lg:bg-white lg:dark:bg-gray-800 lg:duration-300">
|
||||
<div className="w-full lg:shadow-md lg:hover:shadow-2xl lg:border lg:border-gray-100 lg:rounded-xl lg:px-2 lg:py-4 lg:bg-white lg:dark:bg-gray-800 lg:duration-300">
|
||||
<ArticleDetail {...props} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import CONFIG_HEXO from '../config_hexo'
|
||||
const BlogPostCard = ({ post, showSummary }) => {
|
||||
const showPreview = CONFIG_HEXO.POST_LIST_PREVIEW && post.blockMap
|
||||
return (
|
||||
<div className='w-full shadow-xl hover:shadow-2xl border border-gray-100 rounded-xl bg-white dark:bg-gray-800 duration-300'>
|
||||
<div className='w-full shadow hover:shadow-2xl border border-gray-100 rounded-xl bg-white dark:bg-gray-800 duration-300'>
|
||||
<div key={post.id} className='animate__animated animate__fadeIn flex flex-col-reverse lg:flex-row justify-between duration-300'>
|
||||
|
||||
<div className='lg:p-8 p-4 flex flex-col w-full'>
|
||||
|
||||
@@ -20,7 +20,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount }) => {
|
||||
return (
|
||||
<div id="container" className='w-full'>
|
||||
{/* 文章列表 */}
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-6 px-2">
|
||||
{posts.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} />
|
||||
))}
|
||||
|
||||
@@ -55,7 +55,7 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_HE
|
||||
return <div id='container' ref={targetRef} className='w-full'>
|
||||
|
||||
{/* 文章列表 */}
|
||||
<div className='flex flex-wrap space-y-1 lg:space-y-4'>
|
||||
<div className='flex flex-wrap space-y-1 lg:space-y-4 px-2'>
|
||||
{postsToShow.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} showSummary={showSummary}/>
|
||||
))}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Card = ({ children, headerSlot, className }) => {
|
||||
return <div className={className}>
|
||||
<>{headerSlot}</>
|
||||
<section className="shadow-xl hover:shadow-2xl border border-gray-100 rounded-xl px-2 py-4 bg-white dark:bg-gray-800 duration-300">
|
||||
<section className="shadow-md hover:shadow-2xl border border-gray-100 rounded-xl px-2 py-4 bg-white dark:bg-gray-800 duration-300">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -53,7 +53,7 @@ const Catalog = ({ toc }) => {
|
||||
|
||||
return <div className='px-3'>
|
||||
<div className='w-full'><i className='mr-1 fas fa-stream' /> 目录</div>
|
||||
<div className='w-full py-1'>
|
||||
<div className='w-full py-3'>
|
||||
<Progress/>
|
||||
</div>
|
||||
<nav className='font-sans overflow-y-auto scroll-hidden text-black'>
|
||||
|
||||
@@ -7,14 +7,14 @@ const CategoryGroup = ({ currentCategory, categories }) => {
|
||||
}
|
||||
return <>
|
||||
<div id='category-list' className='dark:border-gray-600 flex flex-wrap font-sans mx-4'>
|
||||
{Object.keys(categories).map(category => {
|
||||
const selected = currentCategory === category
|
||||
return <Link key={category} href={`/category/${category}`} passHref>
|
||||
{categories.map(category => {
|
||||
const selected = currentCategory === category.name
|
||||
return <Link key={category.name} href={`/category/${category.name}`} passHref>
|
||||
<a className={(selected
|
||||
? 'hover:text-white dark:hover:text-white bg-blue-600 text-white '
|
||||
: 'dark:text-gray-400 text-gray-500 hover:text-white dark:hover:text-white hover:bg-blue-600') +
|
||||
' text-sm w-full items-center duration-300 px-2 cursor-pointer py-1 font-light'}>
|
||||
<div> <i className={`mr-2 fas ${selected ? 'fa-folder-open' : 'fa-folder'}`} />{category}({categories[category]})</div>
|
||||
<div> <i className={`mr-2 fas ${selected ? 'fa-folder-open' : 'fa-folder'}`} />{category.name}({category.count})</div>
|
||||
</a>
|
||||
</Link>
|
||||
})}
|
||||
|
||||
@@ -40,17 +40,17 @@ export default function HeaderArticle ({ post }) {
|
||||
style={{ backgroundImage: headerImage }}
|
||||
>
|
||||
<header className="animate__slideInDown animate__animated bg-black bg-opacity-70 absolute top-0 w-full h-96 py-10 flex justify-center items-center font-sans">
|
||||
<div>
|
||||
<div className='mt-24'>
|
||||
{/* 文章Title */}
|
||||
<div className="font-bold text-3xl shadow-text flex justify-center text-white dark:text-white font-sans">
|
||||
<div className="font-bold text-xl shadow-text flex justify-center text-white dark:text-white font-sans">
|
||||
{post.title}
|
||||
</div>
|
||||
|
||||
<section className="flex-wrap shadow-text flex justify-center mt-2 text-white dark:text-gray-400 font-light leading-8">
|
||||
<section className="flex-wrap shadow-text flex text-sm justify-center mt-2 text-white dark:text-gray-400 font-light leading-8">
|
||||
<div>
|
||||
{post.category && <>
|
||||
<Link href={`/category/${post.category}`} passHref>
|
||||
<a className="cursor-pointer text-md mr-2 dark:hover:text-white border-b dark:border-gray-500 border-dashed">
|
||||
<a className="cursor-pointer mr-2 dark:hover:text-white border-b dark:border-gray-500 border-dashed">
|
||||
<i className="mr-1 fas fa-folder-open" />
|
||||
{post.category}
|
||||
</a>
|
||||
|
||||
@@ -25,9 +25,9 @@ const LatestPostsGroup = ({ posts }) => {
|
||||
const selected = currentPath === `${BLOG.PATH}/article/${post.slug}`
|
||||
return (
|
||||
<Link key={post.id} title={post.title} href={`${BLOG.PATH}/article/${post.slug}`} passHref>
|
||||
<a className={ 'my-1 flex font-light'}>
|
||||
<div className={ (selected ? 'text-white bg-blue-600 ' : 'text-gray-500 dark:text-gray-400 ') + ' text-xs py-1.5 flex overflow-x-hidden whitespace-nowrap hover:bg-blue-600 px-2 duration-200 w-full ' +
|
||||
'hover:text-white dark:hover:text-white cursor-pointer' }>
|
||||
<a className={ 'my-1 mx-5 flex font-light'}>
|
||||
<div className={ (selected ? 'text-white bg-blue-400 ' : 'text-gray-500 dark:text-gray-400 ') + ' text-xs py-1.5 flex overflow-x-hidden whitespace-nowrap hover:bg-blue-400 px-2 duration-200 w-full rounded ' +
|
||||
'hover:text-white dark:hover:text-white cursor-pointer items-center'}>
|
||||
<i className='mr-2 fas fa-file-alt'/>
|
||||
<div className='truncate'>{post.title}</div>
|
||||
</div>
|
||||
|
||||
@@ -11,11 +11,11 @@ const MenuButtonGroup = (props) => {
|
||||
const archiveSlot = <div className='bg-blue-300 dark:bg-blue-500 rounded-md text-gray-50 px-1 text-xs'>{postCount}</div>
|
||||
|
||||
let links = [
|
||||
{ id: 0, icon: 'fas fa-home', name: locale.NAV.INDEX, to: '/' || '/', show: true },
|
||||
{ id: 1, icon: 'fas fa-th', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_HEXO.MENU_CATEGORY },
|
||||
{ id: 2, icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_HEXO.MENU_TAG },
|
||||
{ id: 3, icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', slot: archiveSlot, show: CONFIG_HEXO.MENU_ARCHIVE },
|
||||
{ id: 4, icon: 'fas fa-user', name: locale.NAV.ABOUT, to: '/about', show: CONFIG_HEXO.MENU_ABOUT }
|
||||
{ icon: 'fas fa-home', name: locale.NAV.INDEX, to: '/' || '/', show: true },
|
||||
{ icon: 'fas fa-th', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_HEXO.MENU_CATEGORY },
|
||||
{ icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_HEXO.MENU_TAG },
|
||||
{ icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', slot: archiveSlot, show: CONFIG_HEXO.MENU_ARCHIVE },
|
||||
{ icon: 'fas fa-user', name: locale.NAV.ABOUT, to: '/about', show: CONFIG_HEXO.MENU_ABOUT }
|
||||
]
|
||||
if (customNav) {
|
||||
links = links.concat(customNav)
|
||||
@@ -24,11 +24,11 @@ const MenuButtonGroup = (props) => {
|
||||
{links.map(link => {
|
||||
if (link.show) {
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
return <Link key={`${link.id}-${link.to}`} title={link.to} href={link.to} >
|
||||
<a className={'py-1.5 my-1 px-5 duration-300 text-base justify-between hover:bg-blue-600 hover:text-white hover:shadow-lg cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-blue-600 text-white' : ' ')} >
|
||||
return <Link key={`${link.to}`} title={link.to} href={link.to} >
|
||||
<a className={'py-1.5 my-1 px-5 duration-300 text-base justify-between hover:bg-blue-400 rounded-md hover:text-white hover:shadow-lg cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-blue-400 rounded-md text-white' : ' ')} >
|
||||
<div className='my-auto items-center justify-center flex '>
|
||||
<i className={link.icon} />
|
||||
<i className={`${link.icon} w-4 text-center`} />
|
||||
<div className={'ml-4'}>{link.name}</div>
|
||||
</div>
|
||||
{link.slot}
|
||||
|
||||
@@ -29,7 +29,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
|
||||
return (
|
||||
<div className="h-4 w-full shadow-2xl bg-gray-400 font-sans">
|
||||
<div
|
||||
className="h-4 bg-blue-600 duration-200"
|
||||
className="h-4 bg-blue-400 duration-200"
|
||||
style={{ width: `${percent}%` }}
|
||||
>
|
||||
{showPercent && (
|
||||
|
||||
@@ -35,19 +35,30 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
||||
searchInputRef.current.value = ''
|
||||
setSearchKey('')
|
||||
}
|
||||
|
||||
let lock = false
|
||||
const updateSearchKey = (val) => {
|
||||
setSearchKey(val)
|
||||
if (!lock) {
|
||||
setSearchKey(val)
|
||||
}
|
||||
}
|
||||
function lockSearchInput () {
|
||||
lock = true
|
||||
}
|
||||
|
||||
function unLockSearchInput () {
|
||||
lock = false
|
||||
}
|
||||
return <div className='flex'>
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type='text'
|
||||
className={'w-full rounded-lg text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100'}
|
||||
className={'w-full rounded-lg text-sm pl-5 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100'}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={lockSearchInput}
|
||||
onCompositionUpdate={lockSearchInput}
|
||||
onCompositionEnd={unLockSearchInput}
|
||||
onChange={e => updateSearchKey(e.target.value)}
|
||||
defaultValue={searchKey}
|
||||
defaultValue={currentSearch}
|
||||
/>
|
||||
|
||||
<div className='-ml-8 cursor-pointer dark:hover:bg-gray-800 float-right items-center justify-center py-2'
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function SideRight (props) {
|
||||
showTag
|
||||
} = props
|
||||
return (
|
||||
<div className='w-72 space-y-4 hidden lg:block'>
|
||||
<div className='w-96 space-y-4 hidden lg:block'>
|
||||
<Card>
|
||||
<div
|
||||
className='justify-center items-center flex hover:rotate-45 py-6 hover:scale-105 transform duration-200 cursor-pointer'
|
||||
|
||||
@@ -91,7 +91,7 @@ const TopNav = ({ tags, currentTag, categories, currentCategory, postCount }) =>
|
||||
|
||||
{/* 导航栏 */}
|
||||
<div id='sticky-nav' className={`${CONFIG_HEXO.NAV_TYPE !== 'normal' ? 'fixed bg-white' : ' bg-none -mb-10'} bg-opacity-70 text-black w-full top-0 z-20 transform duration-500 font-sans`}>
|
||||
<div className='w-full flex justify-between items-center px-4 py-2 shadow-md'>
|
||||
<div className='w-full flex justify-between items-center px-4 py-2 shad'>
|
||||
<div className='flex'>
|
||||
<Logo/>
|
||||
</div>
|
||||
@@ -104,7 +104,7 @@ const TopNav = ({ tags, currentTag, categories, currentCategory, postCount }) =>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Collapse isOpen={isOpen} className='shadow-xl'>
|
||||
<Collapse isOpen={isOpen} className='shadow-xl rounded-b-xl'>
|
||||
<div className='bg-white pt-1 py-2 px-5'>
|
||||
<MenuButtonGroup postCount={postCount}/>
|
||||
<SearchInput/>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import LayoutBase from './LayoutBase'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import SearchInput from './components/SearchInput'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import TagGroups from './components/TagGroups'
|
||||
|
||||
@@ -45,10 +45,10 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
collection: Collection
|
||||
}}
|
||||
/>
|
||||
<div className='article-cover pointer-events-none'>
|
||||
<div className='pointer-events-none border-t pt-8 border-dashed'>
|
||||
<div className='w-full justify-start flex'>
|
||||
<Link href={`${BLOG.PATH}/article/${post.slug}`} passHref>
|
||||
<a className='hover:bg-opacity-100 hover:scale-105 duration-200 pointer-events-auto transform text-red-500 cursor-pointer'>
|
||||
<a className='hover:bg-opacity-100 hover:scale-105 duration-200 pointer-events-auto transform font-bold text-green-500 cursor-pointer'>
|
||||
{locale.COMMON.ARTICLE_DETAIL}
|
||||
<i className='ml-1 fas fa-angle-right'/></a>
|
||||
|
||||
@@ -58,7 +58,6 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
</div> }
|
||||
</div>
|
||||
<hr className='w-full'/>
|
||||
|
||||
</div>
|
||||
|
||||
)
|
||||
|
||||
@@ -78,7 +78,7 @@ const Catalog = ({ toc }) => {
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
<JumpToTopButton className='text-gray-400 hover:bg-gray-100 py-1 duration-200'/>
|
||||
<JumpToTopButton className='text-gray-400 hover:text-green-500 hover:bg-gray-100 py-1 duration-200'/>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,9 @@ const CategoryGroup = ({ currentCategory, categories }) => {
|
||||
return <div id='category-list' className='pt-4'>
|
||||
<div className='mb-2'><i className='mr-2 fas fa-th' />分类</div>
|
||||
<div className='flex flex-wrap'>
|
||||
{Object.keys(categories).map(category => {
|
||||
const selected = currentCategory === category
|
||||
const categoryCount = +categories[category]
|
||||
return <CategoryItem key={category} selected={selected} category={category} categoryCount={categoryCount}/>
|
||||
{categories.map(category => {
|
||||
const selected = currentCategory === category.name
|
||||
return <CategoryItem key={category.name} selected={selected} category={category.name} categoryCount={category.count}/>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ export default function CategoryItem ({ selected, category, categoryCount }) {
|
||||
? 'hover:text-white dark:hover:text-white bg-green-600 text-white '
|
||||
: 'dark:text-green-400 text-gray-500 hover:text-white dark:hover:text-white hover:bg-green-600') +
|
||||
' flex text-sm items-center duration-300 cursor-pointer py-1 font-light px-2 whitespace-nowrap'}>
|
||||
<div><i className={`mr-2 fas ${selected ? 'fa-folder-open' : 'fa-folder'}`} />{category} {categoryCount && (categoryCount)}
|
||||
<div><i className={`mr-2 fas ${selected ? 'fa-folder-open' : 'fa-folder'}`} />{category} {categoryCount && `(${categoryCount})`}
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
@@ -37,8 +37,18 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
|
||||
setSearchKey('')
|
||||
}
|
||||
|
||||
let lock = false
|
||||
const updateSearchKey = (val) => {
|
||||
setSearchKey(val)
|
||||
if (!lock) {
|
||||
setSearchKey(val)
|
||||
}
|
||||
}
|
||||
function lockSearchInput () {
|
||||
lock = true
|
||||
}
|
||||
|
||||
function unLockSearchInput () {
|
||||
lock = false
|
||||
}
|
||||
|
||||
return <div className={'flex w-full bg-gray-100 ' + className}>
|
||||
@@ -47,6 +57,9 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
|
||||
type='text'
|
||||
className={'w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={lockSearchInput}
|
||||
onCompositionUpdate={lockSearchInput}
|
||||
onCompositionEnd={unLockSearchInput}
|
||||
onChange={e => updateSearchKey(e.target.value)}
|
||||
defaultValue={searchKey}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -12,16 +12,16 @@ export const LayoutCategoryIndex = (props) => {
|
||||
type: 'website'
|
||||
}
|
||||
return <LayoutBase meta={meta} totalPosts={allPosts} {...props}>
|
||||
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
|
||||
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow h-full'>
|
||||
<div className='dark:text-gray-200 mb-5'>
|
||||
<i className='mr-4 fas faTh' />{locale.COMMON.CATEGORY}:
|
||||
</div>
|
||||
<div id='category-list' className='duration-200 flex flex-wrap'>
|
||||
{Object.keys(categories).map(category => {
|
||||
return <Link key={category} href={`/category/${category}`} passHref>
|
||||
{categories.map(category => {
|
||||
return <Link key={category.name} href={`/category/${category.name}`} passHref>
|
||||
<div
|
||||
className={'hover:text-black dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600 px-5 cursor-pointer py-2 hover:bg-gray-100'}>
|
||||
<i className='mr-4 fas fa-folder' />{category}({categories[category]})
|
||||
<i className='mr-4 fas fa-folder' />{category.name}({category.count})
|
||||
</div>
|
||||
</Link>
|
||||
})}
|
||||
|
||||
@@ -2,52 +2,22 @@ import LayoutBase from './LayoutBase'
|
||||
import StickyBar from './components/StickyBar'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import BLOG from '@/blog.config'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export const LayoutSearch = (props) => {
|
||||
const { posts, tags } = props
|
||||
let filteredPosts
|
||||
const searchKey = getSearchKey()
|
||||
if (searchKey) {
|
||||
filteredPosts = posts.filter(post => {
|
||||
const tagContent = post.tags ? post.tags.join(' ') : ''
|
||||
const searchContent = post.title + post.summary + tagContent
|
||||
return searchContent.toLowerCase().includes(searchKey.toLowerCase())
|
||||
})
|
||||
} else {
|
||||
filteredPosts = posts
|
||||
}
|
||||
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${searchKey || ''} | ${locale.NAV.SEARCH} | ${BLOG.TITLE} `,
|
||||
description: BLOG.DESCRIPTION,
|
||||
type: 'website'
|
||||
}
|
||||
const { posts } = props
|
||||
|
||||
return (
|
||||
<LayoutBase
|
||||
meta={meta}
|
||||
currentSearch={searchKey}
|
||||
{...props}
|
||||
>
|
||||
<LayoutBase {...props} >
|
||||
<StickyBar>
|
||||
<div className="p-4 dark:text-gray-200">
|
||||
<i className="mr-1 fas fa-search" />{' '}
|
||||
{filteredPosts.length} {locale.COMMON.RESULT_OF_SEARCH}
|
||||
{posts?.length} {locale.COMMON.RESULT_OF_SEARCH}
|
||||
</div>
|
||||
</StickyBar>
|
||||
<div className="md:mt-5">
|
||||
<BlogPostListScroll posts={filteredPosts} tags={tags} showSummary={true}/>
|
||||
<BlogPostListScroll {...props} showSummary={true}/>
|
||||
</div>
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
|
||||
function getSearchKey () {
|
||||
const router = useRouter()
|
||||
if (router.query && router.query.s) {
|
||||
return router.query.s
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -15,13 +15,9 @@ export const LayoutTag = (props) => {
|
||||
type: 'website'
|
||||
}
|
||||
|
||||
// 将当前选中的标签置顶🔝
|
||||
const currentTag = tags?.find(r => r?.name === tag)
|
||||
const newTags = currentTag ? [currentTag].concat(tags.filter(r => r?.name !== tag)) : tags.filter(r => r?.name !== tag)
|
||||
|
||||
return <LayoutBase meta={meta} currentTag={tag} {...props}>
|
||||
<StickyBar>
|
||||
<TagList tags={newTags} currentTag={tag}/>
|
||||
<TagList tags={tags} currentTag={tag}/>
|
||||
</StickyBar>
|
||||
<div className='md:mt-8'>
|
||||
<BlogPostListScroll posts={posts} tags={tags} currentTag={tag}/>
|
||||
|
||||
@@ -12,7 +12,7 @@ export const LayoutTagIndex = (props) => {
|
||||
type: 'website'
|
||||
}
|
||||
return <LayoutBase meta={meta} {...props}>
|
||||
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
|
||||
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow h-full'>
|
||||
<div className='dark:text-gray-200 mb-5'><i className='fas fa-tags mr-4'/>{locale.COMMON.TAGS}:</div>
|
||||
<div id='tags-list' className='duration-200 flex flex-wrap'>
|
||||
{ tags.map(tag => {
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function ArticleDetail (props) {
|
||||
</>)}
|
||||
|
||||
<div className="hidden busuanzi_container_page_pv font-light mr-2">
|
||||
<i className='mr-1 fa-eye'/>
|
||||
<i className='mr-1 fas fa-eye'/>
|
||||
|
||||
<span className="mr-2 busuanzi_value_page_pv"/>
|
||||
<span className='mr-2'>|</span>
|
||||
|
||||
@@ -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'
|
||||
@@ -59,9 +65,9 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
/>
|
||||
</div> }
|
||||
|
||||
<div className='article-cover pointer-events-none text-right'>
|
||||
<div className='text-right border-t pt-8 border-dashed'>
|
||||
<Link href={`${BLOG.PATH}/article/${post.slug}`}>
|
||||
<a className='hover:bg-opacity-100 hover:scale-105 hover:underline pointer-events-auto transform duration-300 p-3 text-white bg-gray-800 cursor-pointer'>
|
||||
<a className='hover:bg-opacity-100 hover:underline transform duration-300 p-3 text-white bg-gray-800 dark:bg-black cursor-pointer'>
|
||||
{locale.COMMON.ARTICLE_DETAIL}
|
||||
<i className='ml-1 fas fa-angle-right' /></a>
|
||||
</Link>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -2,16 +2,17 @@ import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
const CategoryGroup = ({ currentCategory, categories }) => {
|
||||
if (!categories) return <></>
|
||||
return <>
|
||||
<div id='category-list' className='dark:border-gray-600 flex flex-wrap'>
|
||||
{Object.keys(categories).map(category => {
|
||||
const selected = currentCategory === category
|
||||
return <Link key={category} href={`/category/${category}`} passHref>
|
||||
{categories.map(category => {
|
||||
const selected = currentCategory === category.name
|
||||
return <Link key={category.name} href={`/category/${category.name}`} passHref>
|
||||
<a className={(selected
|
||||
? 'hover:text-white dark:hover:text-white bg-gray-600 text-white '
|
||||
: 'dark:text-gray-400 text-gray-500 hover:text-white hover:bg-gray-500 dark:hover:text-white') +
|
||||
' text-sm w-full items-center duration-300 px-2 cursor-pointer py-1 font-light'}>
|
||||
<i className={`${selected ? 'text-white fa-folder-open ' : 'text-gray-400 fa-folder '} mr-2 fas`} />{category}({categories[category]})
|
||||
<i className={`${selected ? 'text-white fa-folder-open ' : 'text-gray-400 fa-folder '} mr-2 fas`} />{category.name}({category.count})
|
||||
</a>
|
||||
</Link>
|
||||
})}
|
||||
|
||||
@@ -10,10 +10,10 @@ const CategoryList = ({ currentCategory, categories }) => {
|
||||
|
||||
return <ul className='flex py-1 space-x-3'>
|
||||
<li className='w-16 py-2 dark:text-gray-200 whitespace-nowrap'>{locale.COMMON.CATEGORY}</li>
|
||||
{Object.keys(categories).map(category => {
|
||||
const selected = category === currentCategory
|
||||
{categories.map(category => {
|
||||
const selected = category.name === currentCategory
|
||||
return (
|
||||
<Link key={category} href={`/category/${category}`} passHref>
|
||||
<Link key={category.name} href={`/category/${category.name}`} passHref>
|
||||
<li
|
||||
className={`cursor-pointer border rounded-xl duration-200 mr-1 my-1 px-2 py-1 font-light text-sm whitespace-nowrap dark:text-gray-300
|
||||
${selected
|
||||
@@ -23,7 +23,7 @@ const CategoryList = ({ currentCategory, categories }) => {
|
||||
>
|
||||
<a>
|
||||
<i className={`${selected ? 'fa-folder-open ' : 'fa-folder '} fas mr-1`}/>
|
||||
{`${category} `}
|
||||
{`${category.name} (${category.count})`}
|
||||
</a>
|
||||
</li>
|
||||
</Link>)
|
||||
|
||||
@@ -2,9 +2,11 @@ import { useRouter } from 'next/router'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { useImperativeHandle, useRef, useState } from 'react'
|
||||
|
||||
let lock = false
|
||||
|
||||
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()
|
||||
@@ -15,12 +17,14 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
||||
}
|
||||
}
|
||||
})
|
||||
const handleSearch = (key) => {
|
||||
const handleSearch = () => {
|
||||
const key = searchInputRef.current.value
|
||||
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 +39,26 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
||||
}
|
||||
const cleanSearch = () => {
|
||||
searchInputRef.current.value = ''
|
||||
setSearchKey('')
|
||||
setShowClean(false)
|
||||
}
|
||||
function lockSearchInput () {
|
||||
lock = true
|
||||
}
|
||||
|
||||
function unLockSearchInput () {
|
||||
lock = false
|
||||
}
|
||||
const [showClean, setShowClean] = useState(false)
|
||||
const updateSearchKey = (val) => {
|
||||
setSearchKey(val)
|
||||
if (lock) {
|
||||
return
|
||||
}
|
||||
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'>
|
||||
@@ -47,15 +66,18 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
||||
ref={searchInputRef}
|
||||
type='text'
|
||||
placeholder={currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`}
|
||||
className={'w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 border-gray-300 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
|
||||
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}
|
||||
onCompositionStart={lockSearchInput}
|
||||
onCompositionUpdate={lockSearchInput}
|
||||
onCompositionEnd={unLockSearchInput}
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -5,6 +5,7 @@ import Card from './Card'
|
||||
import CategoryGroup from './CategoryGroup'
|
||||
import TagGroups from './TagGroups'
|
||||
import CONFIG_NEXT from '../config_next'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
/**
|
||||
* 侧边平铺
|
||||
@@ -16,18 +17,13 @@ import CONFIG_NEXT from '../config_next'
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const SideAreaRight = ({
|
||||
tags,
|
||||
currentTag,
|
||||
slot,
|
||||
categories,
|
||||
currentCategory
|
||||
}) => {
|
||||
const SideAreaRight = (props) => {
|
||||
const { tags, currentTag, slot, categories, currentCategory } = props
|
||||
const { locale } = useGlobal()
|
||||
if (!CONFIG_NEXT.RIGHT_BAR) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
return (<aside id='right' className='hidden 2xl:block flex-col w-60 ml-4'>
|
||||
|
||||
{CONFIG_NEXT.RIGHT_AD && <Card className='mb-2'>
|
||||
@@ -44,15 +40,16 @@ const SideAreaRight = ({
|
||||
</Card>}
|
||||
|
||||
<div className="sticky top-4">
|
||||
{slot}
|
||||
|
||||
{/* 分类 */}
|
||||
{CONFIG_NEXT.RIGHT_CATEGORY_LIST && categories && (
|
||||
{CONFIG_NEXT.RIGHT_CATEGORY_LIST && router.asPath !== '/category' && categories && (
|
||||
<Card>
|
||||
<div className='text-sm px-2 flex flex-nowrap justify-between font-light'>
|
||||
<div className='pb-1 text-gray-600 dark:text-gray-300'><i icon={faThList} className='mr-2' />{locale.COMMON.CATEGORY}</div>
|
||||
<div className='pb-2 text-gray-600 dark:text-gray-300'><i className='mr-2 fas fa-th-list' />{locale.COMMON.CATEGORY}</div>
|
||||
<Link href={'/category'} passHref>
|
||||
<a className='text-gray-400 hover:text-black dark:text-gray-400 dark:hover:text-white hover:underline cursor-pointer'>
|
||||
{locale.COMMON.MORE} <i icon={faAngleRight} />
|
||||
{locale.COMMON.MORE} <i className='fas fa-angle-right' />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -60,9 +57,7 @@ const SideAreaRight = ({
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{slot}
|
||||
|
||||
{CONFIG_NEXT.RIGHT_TAG_LIST && tags && (
|
||||
{CONFIG_NEXT.RIGHT_TAG_LIST && router.asPath !== '/tag' && tags && (
|
||||
<Card>
|
||||
<div className="text-sm pb-1 px-2 flex flex-nowrap justify-between font-light dark:text-gray-200">
|
||||
<div className="text-gray-600 dark:text-gray-200">
|
||||
|
||||
@@ -12,10 +12,10 @@ const CONFIG_NEXT = {
|
||||
POST_LIST_SUMMARY: false, // 显示用户自定义摘要,有预览时优先只展示预览
|
||||
|
||||
// 右侧组件
|
||||
RIGHT_BAR: false, // 是否显示右侧栏
|
||||
RIGHT_BAR: true, // 是否显示右侧栏
|
||||
RIGHT_LATEST_POSTS: false, // 右侧栏最新文章
|
||||
RIGHT_CATEGORY_LIST: false, // 右侧边栏文章分类列表
|
||||
RIGHT_TAG_LIST: false, // 右侧边栏标签分类列表
|
||||
RIGHT_CATEGORY_LIST: true, // 右侧边栏文章分类列表
|
||||
RIGHT_TAG_LIST: true, // 右侧边栏标签分类列表
|
||||
RIGHT_AD: false, // 右侧广告
|
||||
|
||||
// 菜单
|
||||
|
||||
Reference in New Issue
Block a user