Example主题调整,规范组件命名

This commit is contained in:
tangly1024.com
2024-04-28 15:03:48 +08:00
parent cda0890785
commit 2385ad868f
26 changed files with 773 additions and 638 deletions

View File

@@ -3,20 +3,30 @@ import dynamic from 'next/dynamic'
const NotionPage = dynamic(() => import('@/components/NotionPage'))
/**
* 公告模块
* 其实就是一篇文章
* @param {*} param0
* @returns
*/
const Announcement = ({ post, className }) => {
const { locale } = useGlobal()
if (!post || Object.keys(post).length === 0) {
return <></>
}
return <aside className="rounded shadow overflow-hidden mb-6">
return (
<aside className='rounded shadow overflow-hidden mb-6'>
<h3 className='text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b'>
<i className='mr-2 fas fa-bullhorn' />
{locale.COMMON.ANNOUNCEMENT}
</h3>
<h3 className="text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b">
<i className="mr-2 fas fa-bullhorn" />{locale.COMMON.ANNOUNCEMENT}
</h3>
{post && (<div id="announcement-content">
<NotionPage post={post} className='text-center ' />
</div>)}
</aside>
{post && (
<div id='announcement-content'>
<NotionPage post={post} className='text-center' />
</div>
)}
</aside>
)
}
export default Announcement

View File

@@ -1,51 +0,0 @@
import Link from 'next/link'
import { useGlobal } from '@/lib/global'
import { formatDateFmt } from '@/lib/utils/formatDate'
export const ArticleInfo = (props) => {
const { post } = props
const { locale } = useGlobal()
return (
<section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8">
<div>
{post?.type !== 'Page' && <>
<Link
href={`/category/${post?.category}`}
passHref
className="cursor-pointer text-md mr-2 hover:text-black dark:hover:text-white border-b dark:border-gray-500 border-dashed">
<i className="mr-1 fas fa-folder-open" />
{post?.category}
</Link>
<span className='mr-2'>|</span>
</>}
{post?.type !== 'Page' && (<>
<Link
href={`/archive#${formatDateFmt(post?.publishDate, 'yyyy-MM')}`}
passHref
className="pl-1 mr-2 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 border-b dark:border-gray-500 border-dashed">
{post?.publishDay}
</Link>
<span className='mr-2'>|</span>
<span className='mx-2 text-gray-400 dark:text-gray-500'>
{locale.COMMON.LAST_EDITED_TIME}: {post?.lastEditedDay}
</span>
<span className='mr-2'>|</span>
<span className="hidden busuanzi_container_page_pv font-light mr-2">
<i className='mr-1 fas fa-eye' />
&nbsp;
<span className="mr-2 busuanzi_value_page_pv" />
</span>
</>)}
</div>
</section>
)
}

View File

@@ -1,52 +0,0 @@
import { useGlobal } from '@/lib/global'
import { useEffect, useRef } from 'react'
/**
* 加密文章校验组件
* @param {password, validPassword} props
* @param password 正确的密码
* @param validPassword(bool) 回调函数校验正确回调入参为true
* @returns
*/
export const ArticleLock = props => {
const { validPassword } = props
const { locale } = useGlobal()
const submitPassword = () => {
const p = document.getElementById('password')
if (!validPassword(p?.value)) {
const tips = document.getElementById('tips')
if (tips) {
tips.innerHTML = ''
tips.innerHTML = `<div class='text-red-500 animate__shakeX animate__animated'>${locale.COMMON.PASSWORD_ERROR}</div>`
}
}
}
const passwordInputRef = useRef(null)
useEffect(() => {
// 选中密码输入框并将其聚焦
passwordInputRef.current.focus()
}, [])
return <div id='container' className='w-full flex justify-center items-center h-96 '>
<div className='text-center space-y-3'>
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
<div className='flex mx-4'>
<input id="password" type='password'
onKeyDown={(e) => {
if (e.key === 'Enter') {
submitPassword()
}
}}
ref={passwordInputRef} // 绑定ref到passwordInputRef变量
className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg font-light leading-10 text-black dark:bg-gray-500 bg-gray-50'
></input>
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 rounded-r duration-300 bg-gray-300" >
<i className={'duration-200 cursor-pointer fas fa-key dark:text-black'} >&nbsp;{locale.COMMON.SUBMIT}</i>
</div>
</div>
<div id='tips'>
</div>
</div>
</div>
}

View File

@@ -0,0 +1,87 @@
import LazyImage from '@/components/LazyImage'
import NotionIcon from '@/components/NotionIcon'
import TwikooCommentCount from '@/components/TwikooCommentCount'
import { siteConfig } from '@/lib/config'
import { checkContainHttp, sliceUrlFromHttp } from '@/lib/utils'
import Link from 'next/link'
import CONFIG from '../config'
/**
* 博客列表的单个卡片
* @param {*} param0
* @returns
*/
const BlogItem = ({ post }) => {
const showPageCover =
siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG) &&
post?.pageCoverThumbnail
const url = checkContainHttp(post.slug)
? sliceUrlFromHttp(post.slug)
: `${siteConfig('SUB_PATH', '')}/${post.slug}`
return (
<article
className={`${showPageCover ? 'flex md:flex-row flex-col-reverse' : ''} replace mb-12 `}>
<div className={`${showPageCover ? 'md:w-7/12' : ''}`}>
<h2 className='mb-4'>
<Link
href={`/${post.slug}`}
className='text-black dark:text-gray-100 text-xl md:text-2xl no-underline hover:underline'>
{siteConfig('POST_TITLE_ICON') && (
<NotionIcon icon={post.pageIcon} />
)}
{post?.title}
</Link>
</h2>
<div className='mb-4 text-sm text-gray-700 dark:text-gray-300'>
by{' '}
<a href='#' className='text-gray-700 dark:text-gray-300'>
{siteConfig('AUTHOR')}
</a>{' '}
on {post.date?.start_date || post.createdTime}
<TwikooCommentCount post={post} className='pl-1' />
{post.category && (
<>
<span className='font-bold mx-1'> | </span>
<Link
href={`/category/${post.category}`}
className='text-gray-700 dark:text-gray-300 hover:underline'>
{post.category}
</Link>
</>
)}
{/* <span className="font-bold mx-1"> | </span> */}
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
</div>
{!post.results && (
<p className='line-clamp-3 text-gray-700 dark:text-gray-400 leading-normal'>
{post.summary}
</p>
)}
{/* 搜索结果 */}
{post.results && (
<p className='line-clamp-3 mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7'>
{post.results.map((r, index) => (
<span key={index}>{r}</span>
))}
</p>
)}
</div>
{/* 图片封面 */}
{showPageCover && (
<div className='md:w-5/12 w-full h-44 overflow-hidden p-1'>
<Link href={url} passHref legacyBehavior>
<LazyImage
src={post?.pageCoverThumbnail}
className='w-full bg-cover hover:scale-110 duration-200'
/>
</Link>
</div>
)}
</article>
)
}
export default BlogItem

View File

@@ -0,0 +1,42 @@
import { siteConfig } from '@/lib/config'
import { checkContainHttp, sliceUrlFromHttp } from '@/lib/utils'
import Link from 'next/link'
/**
* 博客归档列表;仅归档页面使用
* 按照日期将文章分组
* @param {*} param0
* @returns
*/
export default function BlogListArchive({ archiveTitle, archivePosts }) {
return (
<div key={archiveTitle}>
<div id={archiveTitle} className='pt-16 pb-4 text-3xl dark:text-gray-300'>
{archiveTitle}
</div>
<ul>
{archivePosts[archiveTitle].map(post => {
const url = checkContainHttp(post.slug)
? sliceUrlFromHttp(post.slug)
: `${siteConfig('SUB_PATH', '')}/${post.slug}`
return (
<li
key={post.id}
className='border-l-2 p-1 text-xs md:text-base items-center hover:scale-x-105 hover:border-gray-500 dark:hover:border-gray-300 dark:border-gray-400 transform duration-500'>
<div id={post?.publishDay}>
<span className='text-gray-400'>{post?.publishDay}</span> &nbsp;
<Link
href={url}
className='dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600'>
{post.title}
</Link>
</div>
</li>
)
})}
</ul>
</div>
)
}

View File

@@ -1,38 +0,0 @@
import { siteConfig } from '@/lib/config'
import { checkContainHttp, sliceUrlFromHttp } from '@/lib/utils'
import Link from 'next/link'
/**
* 按照日期将文章分组
* 归档页面用到
* @param {*} param0
* @returns
*/
export default function BlogListGroupByDate({ archiveTitle, archivePosts }) {
return <div key={archiveTitle}>
<div id={archiveTitle} className="pt-16 pb-4 text-3xl dark:text-gray-300" >
{archiveTitle}
</div>
<ul>
{archivePosts[archiveTitle].map(post => {
const url = checkContainHttp(post.slug) ? sliceUrlFromHttp(post.slug) : `${siteConfig('SUB_PATH', '')}/${post.slug}`
return <li
key={post.id}
className="border-l-2 p-1 text-xs md:text-base items-center hover:scale-x-105 hover:border-gray-500 dark:hover:border-gray-300 dark:border-gray-400 transform duration-500"
>
<div id={post?.publishDay}>
<span className="text-gray-400">
{post?.publishDay}
</span>{' '}
&nbsp;
<Link href={url} className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">
{post.title}
</Link>
</div>
</li>
})}
</ul>
</div>
}

View File

@@ -1,45 +1,61 @@
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import Link from 'next/link'
import { useRouter } from 'next/router'
import CONFIG from '../config'
import BlogPostCard from './BlogPostCard'
import BlogItem from './BlogItem'
/**
* 使用分页插件的博客列表
* @param {*} props
* @returns
*/
export const BlogListPage = props => {
const { page = 1, posts, postCount } = props
const { locale } = useGlobal()
const router = useRouter()
const totalPage = Math.ceil(postCount / parseInt(siteConfig('POSTS_PER_PAGE')))
const totalPage = Math.ceil(
postCount / parseInt(siteConfig('POSTS_PER_PAGE'))
)
const currentPage = +page
const showPrev = currentPage > 1
const showNext = page < totalPage
const pagePrefix = router.asPath.split('?')[0].replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
const pagePrefix = router.asPath
.split('?')[0]
.replace(/\/page\/[1-9]\d*/, '')
.replace(/\/$/, '')
const showPageCover = siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG)
return (
<div className={`w-full ${showPageCover ? 'md:pr-2' : 'md:pr-12'} mb-12`}>
<div className={`w-full ${showPageCover ? 'md:pr-2' : 'md:pr-12'} mb-12`}>
<div id='posts-wrapper'>
{posts?.map(post => (
<BlogItem key={post.id} post={post} />
))}
</div>
<div id="posts-wrapper">
{posts?.map(post => (
<BlogPostCard key={post.id} post = {post}/>
))}
</div>
<div className="flex justify-between text-xs">
<Link
href={{ pathname: currentPage - 1 === 1 ? `${pagePrefix}/` : `${pagePrefix}/page/${currentPage - 1}`, query: router.query.s ? { s: router.query.s } : {} }}
className={`${showPrev ? 'bg-black ' : 'bg-gray pointer-events-none '} text-white no-underline py-2 px-3 rounded`}>
{locale.PAGINATION.PREV}
</Link>
<Link
href={{ pathname: `${pagePrefix}/page/${currentPage + 1}`, query: router.query.s ? { s: router.query.s } : {} }}
className={`${showNext ? 'bg-black ' : 'bg-gray pointer-events-none '} text-white no-underline py-2 px-3 rounded`}>
{locale.PAGINATION.NEXT}
</Link>
</div>
</div>
<div className='flex justify-between text-xs'>
<Link
href={{
pathname:
currentPage - 1 === 1
? `${pagePrefix}/`
: `${pagePrefix}/page/${currentPage - 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
className={`${showPrev ? 'bg-black ' : 'bg-gray pointer-events-none '} text-white no-underline py-2 px-3 rounded`}>
{locale.PAGINATION.PREV}
</Link>
<Link
href={{
pathname: `${pagePrefix}/page/${currentPage + 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
className={`${showNext ? 'bg-black ' : 'bg-gray pointer-events-none '} text-white no-underline py-2 px-3 rounded`}>
{locale.PAGINATION.NEXT}
</Link>
</div>
</div>
)
}

View File

@@ -1,10 +1,14 @@
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { useCallback, useEffect, useRef, useState } from 'react'
import throttle from 'lodash.throttle'
import BlogPostCard from './BlogPostCard'
import { useCallback, useEffect, useRef, useState } from 'react'
import CONFIG from '../config'
import BlogItem from './BlogItem'
/**
* 使用滚动无限加载的博客列表
* @param {*} props
* @returns
*/
export const BlogListScroll = props => {
const { posts } = props
const { locale } = useGlobal()
@@ -13,7 +17,10 @@ export const BlogListScroll = props => {
let hasMore = false
const postsToShow = posts
? Object.assign(posts).slice(0, parseInt(siteConfig('POSTS_PER_PAGE')) * page)
? Object.assign(posts).slice(
0,
parseInt(siteConfig('POSTS_PER_PAGE')) * page
)
: []
if (posts) {
@@ -28,13 +35,19 @@ export const BlogListScroll = props => {
const targetRef = useRef(null)
// 监听滚动自动分页加载
const scrollTrigger = useCallback(throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500))
const scrollTrigger = useCallback(
throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef
? targetRef.current
? targetRef.current.clientHeight
: 0
: 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500)
)
const showPageCover = siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG)
useEffect(() => {
@@ -46,21 +59,20 @@ export const BlogListScroll = props => {
})
return (
<div
id='posts-wrapper'
className={`w-full ${showPageCover ? 'md:pr-2' : 'md:pr-12'}} mb-12`}
ref={targetRef}>
{postsToShow?.map(post => (
<BlogItem key={post.id} post={post} />
))}
<div id='posts-wrapper' className={`w-full ${showPageCover ? 'md:pr-2' : 'md:pr-12'}} mb-12`} ref={targetRef}>
{postsToShow?.map(post => (
<BlogPostCard key={post.id} post={post} />
))}
<div
onClick={handleGetMore}
className="w-full my-4 py-4 text-center cursor-pointer "
>
{' '}
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
</div>
</div>
<div
onClick={handleGetMore}
className='w-full my-4 py-4 text-center cursor-pointer '>
{' '}
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
</div>
</div>
)
}

View File

@@ -1,57 +0,0 @@
import { siteConfig } from '@/lib/config'
import CONFIG from '../config'
import Link from 'next/link'
import TwikooCommentCount from '@/components/TwikooCommentCount'
import LazyImage from '@/components/LazyImage'
import { checkContainHttp, sliceUrlFromHttp } from '@/lib/utils'
import NotionIcon from '@/components/NotionIcon'
const BlogPostCard = ({ post }) => {
const showPageCover = siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG) && post?.pageCoverThumbnail
const url = checkContainHttp(post.slug) ? sliceUrlFromHttp(post.slug) : `${siteConfig('SUB_PATH', '')}/${post.slug}`
return <article className={`${showPageCover ? 'flex md:flex-row flex-col-reverse' : ''} replace mb-12 `}>
<div className={`${showPageCover ? 'md:w-7/12' : ''}`}>
<h2 className="mb-4">
<Link
href={`/${post.slug}`}
className="text-black dark:text-gray-100 text-xl md:text-2xl no-underline hover:underline">
{siteConfig('POST_TITLE_ICON') && <NotionIcon icon={post.pageIcon} />}{post?.title}
</Link>
</h2>
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
by <a href="#" className="text-gray-700 dark:text-gray-300">{siteConfig('AUTHOR')}</a> on {post.date?.start_date || post.createdTime}
<TwikooCommentCount post={post} className='pl-1'/>
{post.category && <>
<span className="font-bold mx-1"> | </span>
<Link href={`/category/${post.category}`} className="text-gray-700 dark:text-gray-300 hover:underline">{post.category}</Link>
</>}
{/* <span className="font-bold mx-1"> | </span> */}
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
</div>
{!post.results && <p className="line-clamp-3 text-gray-700 dark:text-gray-400 leading-normal">
{post.summary}
</p>}
{/* 搜索结果 */}
{post.results && (
<p className="line-clamp-3 mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
{post.results.map((r, index) => (
<span key={index}>{r}</span>
))}
</p>
)}
</div>
{/* 图片封面 */}
{showPageCover && (
<div className="md:w-5/12 w-full h-44 overflow-hidden p-1">
<Link href={url} passHref legacyBehavior>
<LazyImage src={post?.pageCoverThumbnail} className='w-full bg-cover hover:scale-110 duration-200' />
</Link>
</div>
)}
</article>
}
export default BlogPostCard

View File

@@ -1,20 +0,0 @@
import Link from 'next/link'
/**
* 文章分类
* @param {*} param0
* @returns
*/
export default function CategoryItem({ category }) {
return (
<Link
key={category.name}
href={`/category/${category.name}`}
passHref
legacyBehavior>
<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.name}({category.count})
</div>
</Link>
)
}

View File

@@ -1,35 +0,0 @@
import { useEffect, useState } from 'react'
import { siteConfig } from '@/lib/config'
import Link from 'next/link'
import { RecentComments } from '@waline/client'
/**
* @see https://waline.js.org/guide/get-started.html
* @param {*} props
* @returns
*/
const ExampleRecentComments = (props) => {
const [comments, updateComments] = useState([])
const [onLoading, changeLoading] = useState(true)
useEffect(() => {
RecentComments({
serverURL: siteConfig('COMMENT_WALINE_SERVER_URL'),
count: 5
}).then(({ comments }) => {
changeLoading(false)
updateComments(comments)
})
}, [])
return <>
{onLoading && <div>Loading...<i className='ml-2 fas fa-spinner animate-spin' /></div>}
{!onLoading && comments && comments.length === 0 && <div>No Comments</div>}
{!onLoading && comments && comments.length > 0 && comments.map((comment) => <div key={comment.objectId} className='pb-2'>
<div className='dark:text-gray-300 text-gray-600 text-xs waline-recent-content wl-content' dangerouslySetInnerHTML={{ __html: comment.comment }} />
<div className='dark:text-gray-400 text-gray-400 text-sm text-right cursor-pointer hover:text-red-500 hover:underline pt-1'><Link href={{ pathname: comment.url, hash: comment.objectId, query: { target: 'comment' } }}>--{comment.nick}</Link></div>
</div>)}
</>
}
export default ExampleRecentComments

View File

@@ -1,24 +1,27 @@
import Link from 'next/link'
import { siteConfig } from '@/lib/config'
import Link from 'next/link'
import { MenuList } from './MenuList'
/**
* 网站顶部
* @returns
*/
export const Header = (props) => {
export const Header = props => {
return (
<header className="w-full px-6 bg-white dark:bg-black relative z-10">
<div className="container mx-auto max-w-4xl md:flex justify-between items-center">
<Link
href='/'
className="py-6 w-full text-center md:text-left md:w-auto text-gray-dark no-underline flex justify-center items-center">
<header className='w-full px-6 bg-white dark:bg-black relative z-10'>
<div className='container mx-auto max-w-4xl md:flex justify-between items-center'>
<Link
href='/'
className='py-6 w-full text-center md:text-left md:w-auto text-gray-dark no-underline flex justify-center items-center'>
{siteConfig('TITLE')}
</Link>
<div className='w-full md:w-auto text-center md:text-right'>
{/* 右侧文字 */}
</div>
</div>
{siteConfig('TITLE')}
</Link>
<div className="w-full md:w-auto text-center md:text-right">
{/* 右侧文字 */}
</div>
</div>
</header>
{/* 菜单 */}
<MenuList {...props} />
</header>
)
}

View File

@@ -1,18 +0,0 @@
import { useGlobal } from '@/lib/global'
/**
* 跳转到网页顶部
* 当屏幕下滑500像素后会出现该控件
* @param targetRef 关联高度的目标html标签
* @param showPercent 是否显示百分比
* @returns {JSX.Element}
* @constructor
*/
const JumpToTopButton = () => {
const { locale } = useGlobal()
return <div title={locale.POST.TOP} className='cursor-pointer p-2 text-center' onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
><i className='fas fa-angle-up text-2xl' />
</div>
}
export default JumpToTopButton

View File

@@ -1,8 +0,0 @@
export default function LoadingCover() {
return <div id='cover-loading' className={'z-50 opacity-50 pointer-events-none transition-all duration-300'}>
<div className='w-full h-screen flex justify-center items-center'>
<i className="fa-solid fa-spinner text-2xl text-black dark:text-white animate-spin"> </i>
</div>
</div>
}

View File

@@ -1,6 +1,11 @@
import Link from 'next/link'
import { useState } from 'react'
/**
* 支持下拉二级的菜单
* @param {*} param0
* @returns
*/
export const MenuItemDrop = ({ link }) => {
const [show, changeShow] = useState(false)
const hasSubMenu = link?.subMenus?.length > 0

View File

@@ -0,0 +1,73 @@
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import CONFIG from '../config'
import { MenuItemDrop } from './MenuItemDrop'
/**
* 导航菜单列表
* @param {*} props
* @returns
*/
export const MenuList = props => {
const { customNav, customMenu } = props
const { locale } = useGlobal()
let links = [
{
id: 1,
icon: 'fas fa-search',
name: locale.NAV.SEARCH,
to: '/search',
show: siteConfig('EXAMPLE_MENU_SEARCH', null, CONFIG)
},
{
id: 2,
icon: 'fas fa-archive',
name: locale.NAV.ARCHIVE,
to: '/archive',
show: siteConfig('EXAMPLE_MENU_ARCHIVE', null, CONFIG)
},
{
id: 3,
icon: 'fas fa-folder',
name: locale.COMMON.CATEGORY,
to: '/category',
show: siteConfig('EXAMPLE_MENU_CATEGORY', null, CONFIG)
},
{
id: 4,
icon: 'fas fa-tag',
name: locale.COMMON.TAGS,
to: '/tag',
show: siteConfig('EXAMPLE_MENU_TAG', null, CONFIG)
}
]
if (customNav) {
links = links.concat(customNav)
}
// 如果 开启自定义菜单,则不再使用 Page生成菜单。
if (siteConfig('CUSTOM_MENU')) {
links = customMenu
}
if (!links || links.length === 0) {
return null
}
return (
<nav className='w-full bg-white md:pt-0 px-6 relative z-20 border-t border-b border-gray-light dark:border-hexo-black-gray dark:bg-black'>
<div className='container mx-auto max-w-4xl md:flex justify-between items-center text-sm md:text-md md:justify-start'>
<ul className='w-full text-center md:text-left flex flex-wrap justify-center items-stretch md:justify-start md:items-start'>
{links.map((link, index) => (
<MenuItemDrop key={index} link={link} />
))}
</ul>
{/* <div className="w-full md:w-1/3 text-center md:text-right"> */}
{/* <!-- extra links --> */}
{/* </div> */}
</div>
</nav>
)
}

View File

@@ -1,47 +0,0 @@
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import CONFIG from '../config'
import { MenuItemDrop } from './MenuItemDrop'
/**
* 菜单导航
* @param {*} props
* @returns
*/
export const Nav = (props) => {
const { customNav, customMenu } = props
const { locale } = useGlobal()
let links = [
{ id: 1, icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: siteConfig('EXAMPLE_MENU_SEARCH', null, CONFIG) },
{ id: 2, icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: siteConfig('EXAMPLE_MENU_ARCHIVE', null, CONFIG) },
{ id: 3, icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: siteConfig('EXAMPLE_MENU_CATEGORY', null, CONFIG) },
{ id: 4, icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: siteConfig('EXAMPLE_MENU_TAG', null, CONFIG) }
]
if (customNav) {
links = links.concat(customNav)
}
// 如果 开启自定义菜单,则不再使用 Page生成菜单。
if (siteConfig('CUSTOM_MENU')) {
links = customMenu
}
if (!links || links.length === 0) {
return null
}
return (
<nav className="w-full bg-white md:pt-0 px-6 relative z-20 border-t border-b border-gray-light dark:border-hexo-black-gray dark:bg-black">
<div className="container mx-auto max-w-4xl md:flex justify-between items-center text-sm md:text-md md:justify-start">
<ul className="w-full text-center md:text-left flex flex-wrap justify-center items-stretch md:justify-start md:items-start">
{links.map((link, index) => <MenuItemDrop key={index} link={link} />)}
</ul>
{/* <div className="w-full md:w-1/3 text-center md:text-right"> */}
{/* <!-- extra links --> */}
{/* </div> */}
</div>
</nav>
)
}

View File

@@ -1,14 +0,0 @@
import Link from 'next/link'
/**
* 旧的普通菜单
* @param {*} props
* @returns
*/
export const NormalMenuItem = (props) => {
const { link } = props
return link?.show && <Link href={link.to} key={link.to}
className="px-2 md:pl-0 md:mr-3 my-4 md:pr-3 text-gray-700 dark:text-gray-200 no-underline md:border-r border-gray-light">
{link.name}
</Link>
}

View File

@@ -0,0 +1,63 @@
import { useGlobal } from '@/lib/global'
import { useEffect, useRef } from 'react'
/**
* 文章锁;通过此组件校验密码访问文章
* @param {password, validPassword} props
* @param password 正确的密码
* @param validPassword(bool) 回调函数校验正确回调入参为true
* @returns
*/
export const PostLock = props => {
const { validPassword } = props
const { locale } = useGlobal()
const submitPassword = () => {
const p = document.getElementById('password')
if (!validPassword(p?.value)) {
const tips = document.getElementById('tips')
if (tips) {
tips.innerHTML = ''
tips.innerHTML = `<div class='text-red-500 animate__shakeX animate__animated'>${locale.COMMON.PASSWORD_ERROR}</div>`
}
}
}
const passwordInputRef = useRef(null)
useEffect(() => {
// 选中密码输入框并将其聚焦
passwordInputRef.current.focus()
}, [])
return (
<div
id='container'
className='w-full flex justify-center items-center h-96 '>
<div className='text-center space-y-3'>
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
<div className='flex mx-4'>
<input
id='password'
type='password'
onKeyDown={e => {
if (e.key === 'Enter') {
submitPassword()
}
}}
ref={passwordInputRef} // 绑定ref到passwordInputRef变量
className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg font-light leading-10 text-black dark:bg-gray-500 bg-gray-50'></input>
<div
onClick={submitPassword}
className='px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 rounded-r duration-300 bg-gray-300'>
<i
className={
'duration-200 cursor-pointer fas fa-key dark:text-black'
}>
&nbsp;{locale.COMMON.SUBMIT}
</i>
</div>
</div>
<div id='tips'></div>
</div>
</div>
)
}

View File

@@ -0,0 +1,51 @@
import { useGlobal } from '@/lib/global'
import { formatDateFmt } from '@/lib/utils/formatDate'
import Link from 'next/link'
/**
* 文章详情的元信息
* 标题、作者、分类、标签、创建日期等等。
*/
export const PostMeta = props => {
const { post } = props
const { locale } = useGlobal()
return (
<section className='flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8'>
<div>
{post?.type !== 'Page' && (
<>
<Link
href={`/category/${post?.category}`}
passHref
className='cursor-pointer text-md mr-2 hover:text-black dark:hover:text-white border-b dark:border-gray-500 border-dashed'>
<i className='mr-1 fas fa-folder-open' />
{post?.category}
</Link>
<span className='mr-2'>|</span>
</>
)}
{post?.type !== 'Page' && (
<>
<Link
href={`/archive#${formatDateFmt(post?.publishDate, 'yyyy-MM')}`}
passHref
className='pl-1 mr-2 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 border-b dark:border-gray-500 border-dashed'>
{post?.publishDay}
</Link>
<span className='mr-2'>|</span>
<span className='mx-2 text-gray-400 dark:text-gray-500'>
{locale.COMMON.LAST_EDITED_TIME}: {post?.lastEditedDay}
</span>
<span className='mr-2'>|</span>
<span className='hidden busuanzi_container_page_pv font-light mr-2'>
<i className='mr-1 fas fa-eye' />
&nbsp;
<span className='mr-2 busuanzi_value_page_pv' />
</span>
</>
)}
</div>
</section>
)
}

View File

@@ -0,0 +1,62 @@
import { siteConfig } from '@/lib/config'
import { RecentComments } from '@waline/client'
import Link from 'next/link'
import { useEffect, useState } from 'react'
/**
* 最近评论列表
* 基于Waline实现
* @see https://waline.js.org/guide/get-started.html
* @param {*} props
* @returns
*/
const RecentCommentListForExample = props => {
const [comments, updateComments] = useState([])
const [onLoading, changeLoading] = useState(true)
useEffect(() => {
RecentComments({
serverURL: siteConfig('COMMENT_WALINE_SERVER_URL'),
count: 5
}).then(({ comments }) => {
changeLoading(false)
updateComments(comments)
})
}, [])
return (
<>
{onLoading && (
<div>
Loading...
<i className='ml-2 fas fa-spinner animate-spin' />
</div>
)}
{!onLoading && comments && comments.length === 0 && (
<div>No Comments</div>
)}
{!onLoading &&
comments &&
comments.length > 0 &&
comments.map(comment => (
<div key={comment.objectId} className='pb-2'>
<div
className='dark:text-gray-300 text-gray-600 text-xs waline-recent-content wl-content'
dangerouslySetInnerHTML={{ __html: comment.comment }}
/>
<div className='dark:text-gray-400 text-gray-400 text-sm text-right cursor-pointer hover:text-red-500 hover:underline pt-1'>
<Link
href={{
pathname: comment.url,
hash: comment.objectId,
query: { target: 'comment' }
}}>
--{comment.nick}
</Link>
</div>
</div>
))}
</>
)
}
export default RecentCommentListForExample

View File

@@ -1,9 +1,14 @@
import { useRouter } from 'next/router'
import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import { useImperativeHandle, useRef, useState } from 'react'
let lock = false
/**
* 搜索输入框
* @param {*} param0
* @returns
*/
const SearchInput = ({ currentTag, keyword, cRef }) => {
const { locale } = useGlobal()
const router = useRouter()

View File

@@ -1,68 +1,92 @@
import { siteConfig } from '@/lib/config'
import Live2D from '@/components/Live2D'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import dynamic from 'next/dynamic'
import Link from 'next/link'
import Announcement from './Announcement'
const ExampleRecentComments = dynamic(() => import('./ExampleRecentComments'))
const ExampleRecentComments = dynamic(
() => import('./RecentCommentListForExample')
)
export const SideBar = (props) => {
/**
* 侧边栏
*/
export const SideBar = props => {
const { locale } = useGlobal()
const { latestPosts, categoryOptions, notice } = props
return (
<div className="w-full md:w-64 sticky top-8">
<aside className="rounded shadow overflow-hidden mb-6">
<h3 className="text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b">{locale.COMMON.CATEGORY}</h3>
<div className="p-4">
<ul className="list-reset leading-normal">
{categoryOptions?.map(category => {
return (
<Link
key={category.name}
href={`/category/${category.name}`}
passHref
legacyBehavior>
<li> <a href={`/category/${category.name}`} className="text-gray-darkest text-sm">{category.name}({category.count})</a></li>
</Link>
)
})}
</ul>
</div>
</aside>
<aside className="rounded shadow overflow-hidden mb-6">
<h3 className="text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b">{locale.COMMON.LATEST_POSTS}</h3>
<div className="p-4">
<ul className="list-reset leading-normal">
{latestPosts?.map(p => {
return (
<Link key={p.id} href={`/${p.slug}`} passHref legacyBehavior>
<li> <a href={`/${p.slug}`} className="text-gray-darkest text-sm">{p.title}</a></li>
</Link>
)
})}
</ul>
</div>
</aside>
<Announcement post={notice}/>
{siteConfig('COMMENT_WALINE_SERVER_URL') && siteConfig('COMMENT_WALINE_RECENT') && <aside className="rounded shadow overflow-hidden mb-6">
<h3 className="text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b">{locale.COMMON.RECENT_COMMENTS}</h3>
<div className="p-4">
<ExampleRecentComments/>
</div>
</aside>}
<aside className="rounded overflow-hidden mb-6">
<Live2D />
</aside>
<div className='w-full md:w-64 sticky top-8'>
<aside className='rounded shadow overflow-hidden mb-6'>
<h3 className='text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b'>
{locale.COMMON.CATEGORY}
</h3>
<div className='p-4'>
<ul className='list-reset leading-normal'>
{categoryOptions?.map(category => {
return (
<Link
key={category.name}
href={`/category/${category.name}`}
passHref
legacyBehavior>
<li>
{' '}
<a
href={`/category/${category.name}`}
className='text-gray-darkest text-sm'>
{category.name}({category.count})
</a>
</li>
</Link>
)
})}
</ul>
</div>
</aside>
<aside className='rounded shadow overflow-hidden mb-6'>
<h3 className='text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b'>
{locale.COMMON.LATEST_POSTS}
</h3>
<div className='p-4'>
<ul className='list-reset leading-normal'>
{latestPosts?.map(p => {
return (
<Link key={p.id} href={`/${p.slug}`} passHref legacyBehavior>
<li>
{' '}
<a
href={`/${p.slug}`}
className='text-gray-darkest text-sm'>
{p.title}
</a>
</li>
</Link>
)
})}
</ul>
</div>
</aside>
{/* 公告栏 */}
<Announcement post={notice} />
{/* 最近评论 */}
{siteConfig('COMMENT_WALINE_SERVER_URL') &&
siteConfig('COMMENT_WALINE_RECENT') && (
<aside className='rounded shadow overflow-hidden mb-6'>
<h3 className='text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b'>
{locale.COMMON.RECENT_COMMENTS}
</h3>
<div className='p-4'>
<ExampleRecentComments />
</div>
</aside>
)}
<aside className='rounded overflow-hidden mb-6'>
<Live2D />
</aside>
</div>
)
}

View File

@@ -1,18 +0,0 @@
import Link from 'next/link'
/**
* 标签
* @param {*} param0
* @returns
*/
export default function TagItem({ tag }) {
return <div key={tag.name} className='p-2'>
<Link
key={tag}
href={`/tag/${encodeURIComponent(tag.name)}`}
passHref
className={`cursor-pointer inline-block rounded hover:bg-gray-500 hover:text-white duration-200 mr-2 py-1 px-2 text-xs whitespace-nowrap dark:hover:text-white text-gray-600 hover:shadow-xl dark:border-gray-400 notion-${tag.color}_background dark:bg-gray-800`}>
<div className='font-light dark:text-gray-400'><i className='mr-1 fas fa-tag' /> {tag.name + (tag.count ? `(${tag.count})` : '')} </div>
</Link>
</div>
}

View File

@@ -1,20 +0,0 @@
import NotionIcon from '@/components/NotionIcon'
import { siteConfig } from '@/lib/config'
/**
* 标题栏
* @param {*} props
* @returns
*/
export const Title = (props) => {
const { post } = props
const title = post?.title || siteConfig('TITLE')
const description = post?.description || siteConfig('AUTHOR')
return <div className="text-center px-6 py-12 mb-6 bg-gray-100 dark:bg-hexo-black-gray dark:border-hexo-black-gray border-b">
<h1 className="text-xl md:text-4xl pb-4">{siteConfig('POST_TITLE_ICON') && <NotionIcon icon={post?.pageIcon} />}{title}</h1>
<p className="leading-loose text-gray-dark">
{description}
</p>
</div>
}

View File

@@ -1,31 +1,28 @@
'use client'
import CONFIG from './config'
import Comment from '@/components/Comment'
import replaceSearchResult from '@/components/Mark'
import NotionIcon from '@/components/NotionIcon'
import NotionPage from '@/components/NotionPage'
import ShareBar from '@/components/ShareBar'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { isBrowser } from '@/lib/utils'
import { Transition } from '@headlessui/react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { Header } from './components/Header'
import { Nav } from './components/Nav'
import { Footer } from './components/Footer'
import { Title } from './components/Title'
import { SideBar } from './components/SideBar'
import BlogListArchive from './components/BlogListArchive'
import { BlogListPage } from './components/BlogListPage'
import { BlogListScroll } from './components/BlogListScroll'
import { useGlobal } from '@/lib/global'
import { ArticleLock } from './components/ArticleLock'
import { ArticleInfo } from './components/ArticleInfo'
import JumpToTopButton from './components/JumpToTopButton'
import NotionPage from '@/components/NotionPage'
import Comment from '@/components/Comment'
import ShareBar from '@/components/ShareBar'
import { Footer } from './components/Footer'
import { Header } from './components/Header'
import { PostLock } from './components/PostLock'
import { PostMeta } from './components/PostMeta'
import SearchInput from './components/SearchInput'
import replaceSearchResult from '@/components/Mark'
import { isBrowser } from '@/lib/utils'
import BlogListGroupByDate from './components/BlogListGroupByDate'
import CategoryItem from './components/CategoryItem'
import TagItem from './components/TagItem'
import { useRouter } from 'next/router'
import { Transition } from '@headlessui/react'
import { SideBar } from './components/SideBar'
import CONFIG from './config'
import { Style } from './style'
import { siteConfig } from '@/lib/config'
/**
* 基础布局框架
@@ -36,84 +33,105 @@ import { siteConfig } from '@/lib/config'
*/
const LayoutBase = props => {
const { children } = props
const { onLoading, fullWidth } = useGlobal()
const { onLoading, fullWidth, locale } = useGlobal()
const router = useRouter()
const { category, tag } = props
const { post, category, tag } = props
const title = post?.title || siteConfig('TITLE')
const description = post?.description || siteConfig('AUTHOR')
// 顶部如果是按照分类或标签查看文章列表,列表顶部嵌入一个横幅
// 如果是搜索,则列表顶部嵌入 搜索框
let slotTop = null
if (category) {
slotTop = <div className='pb-12'><i className="mr-1 fas fa-folder-open" />{category}</div>
slotTop = (
<div className='pb-12'>
<i className='mr-1 fas fa-folder-open' />
{category}
</div>
)
} else if (tag) {
slotTop = <div className='pb-12'>#{tag}</div>
} else if (props.slotTop) {
slotTop = props.slotTop
} else if (router.route === '/search') {
// 嵌入一个搜索框在顶部
slotTop = <div className='pb-12'><SearchInput {...props} /></div>
slotTop = (
<div className='pb-12'>
<SearchInput {...props} />
</div>
)
}
// 增加一个状态以触发 Transition 组件的动画
// const [showTransition, setShowTransition] = useState(true)
// useEffect(() => {
// // 当 location 或 children 发生变化时,触发动画
// setShowTransition(false)
// setTimeout(() => setShowTransition(true), 5)
// }, [onLoading])
return (
<div id='theme-example' className={`${siteConfig('FONT_STYLE')} dark:text-gray-300 bg-white dark:bg-black scroll-smooth`} >
<div
id='theme-example'
className={`${siteConfig('FONT_STYLE')} dark:text-gray-300 bg-white dark:bg-black scroll-smooth`}>
<Style />
<Style/>
{/* 页头 */}
<Header {...props} />
{/* 页头 */}
<Header {...props} />
{/* 主体 */}
<div id='container-inner' className='w-full relative z-10'>
{/* 标题栏 */}
{!fullWidth && (
<div className='text-center px-6 py-12 mb-6 bg-gray-100 dark:bg-hexo-black-gray dark:border-hexo-black-gray border-b'>
<h1 className='text-xl md:text-4xl pb-4'>
{siteConfig('POST_TITLE_ICON') && (
<NotionIcon icon={post?.pageIcon} />
)}
{title}
</h1>
<p className='leading-loose text-gray-dark'>{description}</p>
</div>
)}
{/* 菜单 */}
<Nav {...props} />
<div
id='container-wrapper'
className={
(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE'))
? 'flex-row-reverse'
: '') +
'relative container mx-auto justify-center md:flex items-start py-8 px-2'
}>
{/* 内容 */}
<div
className={`w-full ${fullWidth ? '' : 'max-w-3xl'} xl:px-14 lg:px-4`}>
<Transition
show={!onLoading}
appear={true}
enter='transition ease-in-out duration-700 transform order-first'
enterFrom='opacity-0 translate-y-16'
enterTo='opacity-100'
leave='transition ease-in-out duration-300 transform'
leaveFrom='opacity-100 translate-y-0'
leaveTo='opacity-0 -translate-y-16'
unmount={false}>
{/* 嵌入模块 */}
{slotTop}
{children}
</Transition>
</div>
{/* 主体 */}
<div id='container-inner' className="w-full relative z-10">
{/* 标题栏 */}
{fullWidth ? null : <Title {...props} />}
<div id='container-wrapper' className={(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE')) ? 'flex-row-reverse' : '') + 'relative container mx-auto justify-center md:flex items-start py-8 px-2'}>
{/* 内容 */}
<div className={`w-full ${fullWidth ? '' : 'max-w-3xl'} xl:px-14 lg:px-4`}>
<Transition
show={!onLoading}
appear={true}
enter="transition ease-in-out duration-700 transform order-first"
enterFrom="opacity-0 translate-y-16"
enterTo="opacity-100"
leave="transition ease-in-out duration-300 transform"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 -translate-y-16"
unmount={false}
>
{/* 嵌入模块 */}
{slotTop}
{children}
</Transition>
</div>
{/* 侧边栏 */}
{!fullWidth && <SideBar {...props} />}
</div>
</div>
{/* 页脚 */}
<Footer {...props} />
{/* 回顶按钮 */}
<div className='fixed right-4 bottom-4 z-10'>
<JumpToTopButton />
</div>
{/* 侧边栏 */}
{!fullWidth && <SideBar {...props} />}
</div>
</div>
{/* 页脚 */}
<Footer {...props} />
{/* 回顶按钮 */}
<div className='fixed right-4 bottom-4 z-10'>
<div
title={locale.POST.TOP}
className='cursor-pointer p-2 text-center'
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}>
<i className='fas fa-angle-up text-2xl' />
</div>
</div>
</div>
)
}
@@ -133,9 +151,13 @@ const LayoutIndex = props => {
*/
const LayoutPostList = props => {
return (
<>
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</>
<>
{siteConfig('POST_LIST_STYLE') === 'page' ? (
<BlogListPage {...props} />
) : (
<BlogListScroll {...props} />
)}
</>
)
}
@@ -150,29 +172,34 @@ const LayoutSlug = props => {
useEffect(() => {
// 404
if (!post) {
setTimeout(() => {
if (isBrowser) {
const article = document.getElementById('notion-article')
if (!article) {
router.push('/404').then(() => {
console.warn('找不到页面', router.asPath)
})
setTimeout(
() => {
if (isBrowser) {
const article = document.getElementById('notion-article')
if (!article) {
router.push('/404').then(() => {
console.warn('找不到页面', router.asPath)
})
}
}
}
}, siteConfig('POST_WAITING_TIME_FOR_404') * 1000)
},
siteConfig('POST_WAITING_TIME_FOR_404') * 1000
)
}
}, [post])
return (
<>
{lock
? <ArticleLock validPassword={validPassword} />
: <div id="article-wrapper" className="px-2">
<ArticleInfo post={post} />
<NotionPage post={post} />
<ShareBar post={post} />
<Comment frontMatter={post} />
</div>}
</>
<>
{lock ? (
<PostLock validPassword={validPassword} />
) : (
<div id='article-wrapper' className='px-2'>
<PostMeta post={post} />
<NotionPage post={post} />
<ShareBar post={post} />
<Comment frontMatter={post} />
</div>
)}
</>
)
}
@@ -181,7 +208,7 @@ const LayoutSlug = props => {
* @param {*} props
* @returns
*/
const Layout404 = (props) => {
const Layout404 = props => {
return <>404 Not found.</>
}
@@ -220,13 +247,19 @@ const LayoutSearch = props => {
*/
const LayoutArchive = props => {
const { archivePosts } = props
return (<>
<div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full">
{Object.keys(archivePosts).map(archiveTitle => (
<BlogListGroupByDate key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />
))}
</div>
</>)
return (
<>
<div className='mb-10 pb-20 md:py-12 p-3 min-h-screen w-full'>
{Object.keys(archivePosts).map(archiveTitle => (
<BlogListArchive
key={archiveTitle}
archiveTitle={archiveTitle}
archivePosts={archivePosts}
/>
))}
</div>
</>
)
}
/**
@@ -237,11 +270,25 @@ const LayoutArchive = props => {
const LayoutCategoryIndex = props => {
const { categoryOptions } = props
return (
<>
<div id='category-list' className='duration-200 flex flex-wrap'>
{categoryOptions?.map(category => <CategoryItem key={category.name} category={category} />)}
<>
<div id='category-list' className='duration-200 flex flex-wrap'>
{categoryOptions?.map(category => (
<Link
key={category.name}
href={`/category/${category.name}`}
passHref
legacyBehavior>
<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.name}({category.count})
</div>
</>
</Link>
))}
</div>
</>
)
}
@@ -250,26 +297,39 @@ const LayoutCategoryIndex = props => {
* @param {*} props
* @returns
*/
const LayoutTagIndex = (props) => {
const LayoutTagIndex = props => {
const { tagOptions } = props
return (
<>
<div id='tags-list' className='duration-200 flex flex-wrap'>
{tagOptions.map(tag => <TagItem key={tag.name} tag={tag} />)}
</div>
</>
<>
<div id='tags-list' className='duration-200 flex flex-wrap'>
{tagOptions.map(tag => (
<div key={tag.name} className='p-2'>
<Link
key={tag}
href={`/tag/${encodeURIComponent(tag.name)}`}
passHref
className={`cursor-pointer inline-block rounded hover:bg-gray-500 hover:text-white duration-200 mr-2 py-1 px-2 text-xs whitespace-nowrap dark:hover:text-white text-gray-600 hover:shadow-xl dark:border-gray-400 notion-${tag.color}_background dark:bg-gray-800`}>
<div className='font-light dark:text-gray-400'>
<i className='mr-1 fas fa-tag' />{' '}
{tag.name + (tag.count ? `(${tag.count})` : '')}{' '}
</div>
</Link>
</div>
))}
</div>
</>
)
}
export {
CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex,
LayoutSearch,
LayoutArchive,
LayoutSlug,
Layout404,
LayoutPostList,
LayoutArchive,
LayoutBase,
LayoutCategoryIndex,
LayoutTagIndex
LayoutIndex,
LayoutPostList,
LayoutSearch,
LayoutSlug,
LayoutTagIndex,
CONFIG as THEME_CONFIG
}