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>
}