diff --git a/.env.local b/.env.local
index db9decf2..073b8bbb 100644
--- a/.env.local
+++ b/.env.local
@@ -1,5 +1,5 @@
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
-NEXT_PUBLIC_VERSION=4.4.3
+NEXT_PUBLIC_VERSION=4.4.6
# 可在此添加环境变量,去掉最左边的(# )注释即可
@@ -62,6 +62,7 @@ NEXT_PUBLIC_VERSION=4.4.3
# NEXT_PUBLIC_ALGOLIA_INDEX=
# NEXT_PUBLIC_PREVIEW_CATEGORY_COUNT=
# NEXT_PUBLIC_PREVIEW_TAG_COUNT=
+# NEXT_PUBLIC_POST_TITLE_ICON=
# NEXT_PUBLIC_POST_DISABLE_GALLERY_CLICK=
# NEXT_PUBLIC_FIREWORKS=
# NEXT_PUBLIC_FIREWORKS_COLOR=
diff --git a/blog.config.js b/blog.config.js
index abfc4cd2..2eb564ab 100644
--- a/blog.config.js
+++ b/blog.config.js
@@ -9,7 +9,7 @@ const BLOG = {
THEME: process.env.NEXT_PUBLIC_THEME || 'simple', // 当前主题,在themes文件夹下可找到所有支持的主题;主题名称就是文件夹名,例如 example,fukasawa,gitbook,heo,hexo,landing,matery,medium,next,nobelium,plog,simple
THEME_SWITCH: process.env.NEXT_PUBLIC_THEME_SWITCH || false, // 是否显示切换主题按钮
LANG: process.env.NEXT_PUBLIC_LANG || 'zh-CN', // e.g 'zh-CN','en-US' see /lib/lang.js for more.
- SINCE: process.env.NEXT_SINCE || 2021, // e.g if leave this empty, current year will be used.
+ SINCE: process.env.NEXT_PUBLIC_SINCE || 2021, // e.g if leave this empty, current year will be used.
APPEARANCE: process.env.NEXT_PUBLIC_APPEARANCE || 'light', // ['light', 'dark', 'auto'], // light 日间模式 , dark夜间模式, auto根据时间和主题自动夜间模式
APPEARANCE_DARK_TIME: process.env.NEXT_PUBLIC_APPEARANCE_DARK_TIME || [18, 6], // 夜间模式起至时间,false时关闭根据时间自动切换夜间模式
@@ -230,6 +230,7 @@ const BLOG = {
PREVIEW_CATEGORY_COUNT: 16, // 首页最多展示的分类数量,0为不限制
PREVIEW_TAG_COUNT: 16, // 首页最多展示的标签数量,0为不限制
+ POST_TITLE_ICON: process.env.NEXT_PUBLIC_POST_TITLE_ICON || true, // 是否显示标题icon
POST_DISABLE_GALLERY_CLICK:
process.env.NEXT_PUBLIC_POST_DISABLE_GALLERY_CLICK || false, // 画册视图禁止点击,方便在友链页面的画册插入链接
diff --git a/components/Comment.js b/components/Comment.js
index 4f4107b8..ba0d4256 100644
--- a/components/Comment.js
+++ b/components/Comment.js
@@ -1,9 +1,10 @@
-import dynamic from 'next/dynamic'
import Tabs from '@/components/Tabs'
-import { isBrowser, isSearchEngineBot } from '@/lib/utils'
-import { useRouter } from 'next/router'
-import Artalk from './Artalk'
import { siteConfig } from '@/lib/config'
+import { isBrowser, isSearchEngineBot } from '@/lib/utils'
+import dynamic from 'next/dynamic'
+import { useRouter } from 'next/router'
+import { useEffect, useRef, useState } from 'react'
+import Artalk from './Artalk'
const WalineComponent = dynamic(
() => {
@@ -57,11 +58,14 @@ const ValineComponent = dynamic(() => import('@/components/ValineComponent'), {
/**
* 评论组件
+ * 只有当前组件在浏览器可见范围内才会加载内容
* @param {*} param0
* @returns
*/
-const Comment = ({ siteInfo, frontMatter, className }) => {
+const Comment = ({ frontMatter, className }) => {
const router = useRouter()
+ const [shouldLoad, setShouldLoad] = useState(false)
+ const commentRef = useRef(null)
const COMMENT_ARTALK_SERVER = siteConfig('COMMENT_ARTALK_SERVER')
const COMMENT_TWIKOO_ENV_ID = siteConfig('COMMENT_TWIKOO_ENV_ID')
@@ -73,16 +77,39 @@ const Comment = ({ siteInfo, frontMatter, className }) => {
const COMMENT_GITALK_CLIENT_ID = siteConfig('COMMENT_GITALK_CLIENT_ID')
const COMMENT_WEBMENTION_ENABLE = siteConfig('COMMENT_WEBMENTION_ENABLE')
- if (isSearchEngineBot()) {
- return null
- }
+ useEffect(() => {
+ // Check if the component is visible in the viewport
+ const observer = new IntersectionObserver(entries => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ setShouldLoad(true)
+ observer.unobserve(entry.target)
+ }
+ })
+ })
+
+ if (commentRef.current) {
+ observer.observe(commentRef.current)
+ }
+
+ return () => {
+ if (commentRef.current) {
+ observer.unobserve(commentRef.current)
+ }
+ }
+ }, [frontMatter])
// 当连接中有特殊参数时跳转到评论区
- if (isBrowser && ('giscus' in router.query || router.query.target === 'comment')) {
+ if (
+ isBrowser &&
+ ('giscus' in router.query || router.query.target === 'comment')
+ ) {
setTimeout(() => {
const url = router.asPath.replace('?target=comment', '')
history.replaceState({}, '', url)
- document?.getElementById('comment')?.scrollIntoView({ block: 'start', behavior: 'smooth' })
+ document
+ ?.getElementById('comment')
+ ?.scrollIntoView({ block: 'start', behavior: 'smooth' })
}, 1000)
}
@@ -90,48 +117,85 @@ const Comment = ({ siteInfo, frontMatter, className }) => {
return <>Loading...>
}
+ if (isSearchEngineBot()) {
+ return null
+ }
+
return (
-
diff --git a/themes/example/components/Announcement.js b/themes/example/components/Announcement.js
index 1f265417..9ffba0e9 100644
--- a/themes/example/components/Announcement.js
+++ b/themes/example/components/Announcement.js
@@ -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
+ )
}
export default Announcement
diff --git a/themes/example/components/ArticleInfo.js b/themes/example/components/ArticleInfo.js
deleted file mode 100644
index b502a9c9..00000000
--- a/themes/example/components/ArticleInfo.js
+++ /dev/null
@@ -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 (
-
-
- {post?.type !== 'Page' && <>
-
-
-
- {post?.category}
-
-
- |
- >}
-
- {post?.type !== 'Page' && (<>
-
-
- {post?.publishDay}
-
-
- |
-
- {locale.COMMON.LAST_EDITED_TIME}: {post?.lastEditedDay}
-
- |
-
-
-
-
-
- >)}
-
-
-
-
- )
-}
diff --git a/themes/example/components/ArticleLock.js b/themes/example/components/ArticleLock.js
deleted file mode 100644
index 3744c183..00000000
--- a/themes/example/components/ArticleLock.js
+++ /dev/null
@@ -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 = `${locale.COMMON.PASSWORD_ERROR}
`
- }
- }
- }
- const passwordInputRef = useRef(null)
- useEffect(() => {
- // 选中密码输入框并将其聚焦
- passwordInputRef.current.focus()
- }, [])
-
- return
-
-
{locale.COMMON.ARTICLE_LOCK_TIPS}
-
-
{
- 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'
- >
-
- {locale.COMMON.SUBMIT}
-
-
-
-
-
-
-}
diff --git a/themes/example/components/BlogItem.js b/themes/example/components/BlogItem.js
new file mode 100644
index 00000000..f1e54ffe
--- /dev/null
+++ b/themes/example/components/BlogItem.js
@@ -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 (
+
+
+
+
+ {siteConfig('POST_TITLE_ICON') && (
+
+ )}
+ {post?.title}
+
+
+
+
+ by{' '}
+
+ {siteConfig('AUTHOR')}
+ {' '}
+ on {post.date?.start_date || post.createdTime}
+
+ {post.category && (
+ <>
+
|
+
+ {post.category}
+
+ >
+ )}
+ {/*
| */}
+ {/*
2 Comments */}
+
+
+ {!post.results && (
+
+ {post.summary}
+
+ )}
+ {/* 搜索结果 */}
+ {post.results && (
+
+ {post.results.map((r, index) => (
+ {r}
+ ))}
+
+ )}
+
+ {/* 图片封面 */}
+ {showPageCover && (
+
+
+
+
+
+ )}
+
+ )
+}
+
+export default BlogItem
diff --git a/themes/example/components/BlogListArchive.js b/themes/example/components/BlogListArchive.js
new file mode 100644
index 00000000..2f4b1d68
--- /dev/null
+++ b/themes/example/components/BlogListArchive.js
@@ -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 (
+
+
+ {archiveTitle}
+
+
+
+
+ )
+}
diff --git a/themes/example/components/BlogListGroupByDate.js b/themes/example/components/BlogListGroupByDate.js
deleted file mode 100644
index 7a8fe083..00000000
--- a/themes/example/components/BlogListGroupByDate.js
+++ /dev/null
@@ -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
-
- {archiveTitle}
-
-
-
-
-}
diff --git a/themes/example/components/BlogListPage.js b/themes/example/components/BlogListPage.js
index 5ae2cf5c..d1068ffd 100644
--- a/themes/example/components/BlogListPage.js
+++ b/themes/example/components/BlogListPage.js
@@ -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 (
-
+
+
+ {posts?.map(post => (
+
+ ))}
+
-
- {posts?.map(post => (
-
- ))}
-
-
-
-
- {locale.PAGINATION.PREV}
-
-
- {locale.PAGINATION.NEXT}
-
-
-
+
+
+ {locale.PAGINATION.PREV}
+
+
+ {locale.PAGINATION.NEXT}
+
+
+
)
}
diff --git a/themes/example/components/BlogListScroll.js b/themes/example/components/BlogListScroll.js
index e876f312..ea883975 100644
--- a/themes/example/components/BlogListScroll.js
+++ b/themes/example/components/BlogListScroll.js
@@ -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 (
+
+ {postsToShow?.map(post => (
+
+ ))}
-
-
- {postsToShow?.map(post => (
-
- ))}
-
-
- {' '}
- {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
-
-
-
+
+ {' '}
+ {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
+
+
)
}
diff --git a/themes/example/components/BlogPostCard.js b/themes/example/components/BlogPostCard.js
deleted file mode 100644
index b66dcca3..00000000
--- a/themes/example/components/BlogPostCard.js
+++ /dev/null
@@ -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
-
-
-
- {post?.title}
-
-
-
-
- by
{siteConfig('AUTHOR')} on {post.date?.start_date || post.createdTime}
-
- {post.category && <>
-
|
-
{post.category}
- >}
- {/*
| */}
- {/*
2 Comments */}
-
-
- {!post.results &&
- {post.summary}
-
}
- {/* 搜索结果 */}
- {post.results && (
-
- {post.results.map((r, index) => (
- {r}
- ))}
-
- )}
-
- {/* 图片封面 */}
- {showPageCover && (
-
-
-
-
-
- )}
-
-}
-
-export default BlogPostCard
diff --git a/themes/example/components/CategoryItem.js b/themes/example/components/CategoryItem.js
deleted file mode 100644
index d69de464..00000000
--- a/themes/example/components/CategoryItem.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import Link from 'next/link'
-
-/**
- * 文章分类
- * @param {*} param0
- * @returns
- */
-export default function CategoryItem({ category }) {
- return (
-
-
- {category.name}({category.count})
-
-
- )
-}
diff --git a/themes/example/components/ExampleRecentComments.js b/themes/example/components/ExampleRecentComments.js
deleted file mode 100644
index 9dbdfa7f..00000000
--- a/themes/example/components/ExampleRecentComments.js
+++ /dev/null
@@ -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 && Loading...
}
- {!onLoading && comments && comments.length === 0 && No Comments
}
- {!onLoading && comments && comments.length > 0 && comments.map((comment) => )}
-
- >
-}
-
-export default ExampleRecentComments
diff --git a/themes/example/components/Header.js b/themes/example/components/Header.js
index 2ec1e27b..fdb049e3 100644
--- a/themes/example/components/Header.js
+++ b/themes/example/components/Header.js
@@ -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 (
-
+ {/* 菜单 */}
+
+
)
}
diff --git a/themes/example/components/JumpToTopButton.js b/themes/example/components/JumpToTopButton.js
deleted file mode 100644
index f5e22b61..00000000
--- a/themes/example/components/JumpToTopButton.js
+++ /dev/null
@@ -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 window.scrollTo({ top: 0, behavior: 'smooth' })}
- >
-
-}
-
-export default JumpToTopButton
diff --git a/themes/example/components/LoadingCover.js b/themes/example/components/LoadingCover.js
deleted file mode 100644
index 75976180..00000000
--- a/themes/example/components/LoadingCover.js
+++ /dev/null
@@ -1,8 +0,0 @@
-
-export default function LoadingCover() {
- return
-}
diff --git a/themes/example/components/MenuItemDrop.js b/themes/example/components/MenuItemDrop.js
index 94d70605..a097abeb 100644
--- a/themes/example/components/MenuItemDrop.js
+++ b/themes/example/components/MenuItemDrop.js
@@ -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
diff --git a/themes/example/components/MenuList.js b/themes/example/components/MenuList.js
new file mode 100644
index 00000000..09613b1a
--- /dev/null
+++ b/themes/example/components/MenuList.js
@@ -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 (
+
+ )
+}
diff --git a/themes/example/components/Nav.js b/themes/example/components/Nav.js
deleted file mode 100644
index 01fbe03f..00000000
--- a/themes/example/components/Nav.js
+++ /dev/null
@@ -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 (
-
- )
-}
diff --git a/themes/example/components/NormalMenuItem.js b/themes/example/components/NormalMenuItem.js
deleted file mode 100644
index aec74837..00000000
--- a/themes/example/components/NormalMenuItem.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import Link from 'next/link'
-
-/**
- * 旧的普通菜单
- * @param {*} props
- * @returns
- */
-export const NormalMenuItem = (props) => {
- const { link } = props
- return link?.show &&
- {link.name}
-
-}
diff --git a/themes/example/components/PostLock.js b/themes/example/components/PostLock.js
new file mode 100644
index 00000000..ca20fc77
--- /dev/null
+++ b/themes/example/components/PostLock.js
@@ -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 = `${locale.COMMON.PASSWORD_ERROR}
`
+ }
+ }
+ }
+ const passwordInputRef = useRef(null)
+ useEffect(() => {
+ // 选中密码输入框并将其聚焦
+ passwordInputRef.current.focus()
+ }, [])
+
+ return (
+
+
+
{locale.COMMON.ARTICLE_LOCK_TIPS}
+
+
{
+ 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'>
+
+
+ {locale.COMMON.SUBMIT}
+
+
+
+
+
+
+ )
+}
diff --git a/themes/example/components/PostMeta.js b/themes/example/components/PostMeta.js
new file mode 100644
index 00000000..19a6cab2
--- /dev/null
+++ b/themes/example/components/PostMeta.js
@@ -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 (
+
+
+ {post?.type !== 'Page' && (
+ <>
+
+
+ {post?.category}
+
+ |
+ >
+ )}
+
+ {post?.type !== 'Page' && (
+ <>
+
+ {post?.publishDay}
+
+ |
+
+ {locale.COMMON.LAST_EDITED_TIME}: {post?.lastEditedDay}
+
+ |
+
+
+
+
+
+ >
+ )}
+
+
+ )
+}
diff --git a/themes/example/components/RecentCommentListForExample.js b/themes/example/components/RecentCommentListForExample.js
new file mode 100644
index 00000000..e6c97c4c
--- /dev/null
+++ b/themes/example/components/RecentCommentListForExample.js
@@ -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 && (
+
+ Loading...
+
+
+ )}
+ {!onLoading && comments && comments.length === 0 && (
+ No Comments
+ )}
+ {!onLoading &&
+ comments &&
+ comments.length > 0 &&
+ comments.map(comment => (
+
+
+
+
+ --{comment.nick}
+
+
+
+ ))}
+ >
+ )
+}
+
+export default RecentCommentListForExample
diff --git a/themes/example/components/SearchInput.js b/themes/example/components/SearchInput.js
index 4f375d4e..419e9984 100644
--- a/themes/example/components/SearchInput.js
+++ b/themes/example/components/SearchInput.js
@@ -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()
diff --git a/themes/example/components/SideBar.js b/themes/example/components/SideBar.js
index ea0531b7..5cfe9d9f 100644
--- a/themes/example/components/SideBar.js
+++ b/themes/example/components/SideBar.js
@@ -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 (
-
-
-
- {locale.COMMON.CATEGORY}
-
-
-
-
-
-
- {locale.COMMON.LATEST_POSTS}
-
-
-
- {latestPosts?.map(p => {
- return (
-
- - {p.title}
-
- )
- })}
-
-
-
-
-
-
- {siteConfig('COMMENT_WALINE_SERVER_URL') && siteConfig('COMMENT_WALINE_RECENT') &&
- {locale.COMMON.RECENT_COMMENTS}
-
-
-
-
- }
-
-
+
+
+
+ {locale.COMMON.CATEGORY}
+
+
+
+
+
+ {locale.COMMON.LATEST_POSTS}
+
+
+
+
+ {latestPosts?.map(p => {
+ return (
+
+ -
+ {' '}
+
+ {p.title}
+
+
+
+ )
+ })}
+
+
+
+ {/* 公告栏 */}
+
+
+ {/* 最近评论 */}
+ {siteConfig('COMMENT_WALINE_SERVER_URL') &&
+ siteConfig('COMMENT_WALINE_RECENT') && (
+
+
+ {locale.COMMON.RECENT_COMMENTS}
+
+
+
+
+
+
+ )}
+
+
)
}
diff --git a/themes/example/components/TagItem.js b/themes/example/components/TagItem.js
deleted file mode 100644
index 5d12dfdb..00000000
--- a/themes/example/components/TagItem.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import Link from 'next/link'
-
-/**
- * 标签
- * @param {*} param0
- * @returns
- */
-export default function TagItem({ tag }) {
- return
-
-
{tag.name + (tag.count ? `(${tag.count})` : '')}
-
-
-}
diff --git a/themes/example/components/Title.js b/themes/example/components/Title.js
deleted file mode 100644
index 73ca9d7f..00000000
--- a/themes/example/components/Title.js
+++ /dev/null
@@ -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
-
{title}
-
- {description}
-
-
-}
diff --git a/themes/example/index.js b/themes/example/index.js
index 2bb24981..b1893935 100644
--- a/themes/example/index.js
+++ b/themes/example/index.js
@@ -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 = {category}
+ slotTop = (
+
+
+ {category}
+
+ )
} else if (tag) {
slotTop = #{tag}
} else if (props.slotTop) {
slotTop = props.slotTop
} else if (router.route === '/search') {
// 嵌入一个搜索框在顶部
- slotTop =
+ slotTop = (
+
+
+
+ )
}
- // 增加一个状态以触发 Transition 组件的动画
- // const [showTransition, setShowTransition] = useState(true)
- // useEffect(() => {
- // // 当 location 或 children 发生变化时,触发动画
- // setShowTransition(false)
- // setTimeout(() => setShowTransition(true), 5)
- // }, [onLoading])
-
return (
-
+
+
-
+ {/* 页头 */}
+
- {/* 页头 */}
-
+ {/* 主体 */}
+
+ {/* 标题栏 */}
+ {!fullWidth && (
+
+
+ {siteConfig('POST_TITLE_ICON') && (
+
+ )}
+ {title}
+
+
{description}
+
+ )}
- {/* 菜单 */}
-
+
+ {/* 内容 */}
+
+
+ {/* 嵌入模块 */}
+ {slotTop}
+ {children}
+
+
- {/* 主体 */}
-
-
- {/* 标题栏 */}
- {fullWidth ? null :
}
-
-
-
- {/* 内容 */}
-
-
- {/* 嵌入模块 */}
- {slotTop}
- {children}
-
-
-
- {/* 侧边栏 */}
- {!fullWidth &&
}
-
-
-
-
-
- {/* 页脚 */}
-
-
- {/* 回顶按钮 */}
-
-
-
+ {/* 侧边栏 */}
+ {!fullWidth &&
}
+
+
+ {/* 页脚 */}
+
+
+ {/* 回顶按钮 */}
+
+
window.scrollTo({ top: 0, behavior: 'smooth' })}>
+
+
+
+
)
}
@@ -133,9 +151,13 @@ const LayoutIndex = props => {
*/
const LayoutPostList = props => {
return (
- <>
- {siteConfig('POST_LIST_STYLE') === 'page' ?
:
}
- >
+ <>
+ {siteConfig('POST_LIST_STYLE') === 'page' ? (
+
+ ) : (
+
+ )}
+ >
)
}
@@ -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
- ?
- :
}
- >
+ <>
+ {lock ? (
+
+ ) : (
+
+ )}
+ >
)
}
@@ -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 (<>
-
- {Object.keys(archivePosts).map(archiveTitle => (
-
- ))}
-
- >)
+ return (
+ <>
+
+ {Object.keys(archivePosts).map(archiveTitle => (
+
+ ))}
+
+ >
+ )
}
/**
@@ -237,11 +270,25 @@ const LayoutArchive = props => {
const LayoutCategoryIndex = props => {
const { categoryOptions } = props
return (
- <>
-
- {categoryOptions?.map(category =>
)}
+ <>
+
+ {categoryOptions?.map(category => (
+
+
+
+ {category.name}({category.count})
- >
+
+ ))}
+
+ >
)
}
@@ -250,26 +297,39 @@ const LayoutCategoryIndex = props => {
* @param {*} props
* @returns
*/
-const LayoutTagIndex = (props) => {
+const LayoutTagIndex = props => {
const { tagOptions } = props
return (
- <>
-
- {tagOptions.map(tag => )}
-
- >
+ <>
+
+ >
)
}
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
}
diff --git a/themes/fukasawa/components/ArticleDetail.js b/themes/fukasawa/components/ArticleDetail.js
index 544d80de..6883d8e0 100644
--- a/themes/fukasawa/components/ArticleDetail.js
+++ b/themes/fukasawa/components/ArticleDetail.js
@@ -10,6 +10,7 @@ import LazyImage from '@/components/LazyImage'
import { formatDateFmt } from '@/lib/utils/formatDate'
import WWAds from '@/components/WWAds'
import NotionIcon from '@/components/NotionIcon'
+import { siteConfig } from '@/lib/config'
/**
*
@@ -37,7 +38,7 @@ export default function ArticleDetail(props) {
{/* 文章Title */}
- {post.title}
+ {siteConfig('POST_TITLE_ICON') && }{post.title}
diff --git a/themes/fukasawa/components/BlogCard.js b/themes/fukasawa/components/BlogCard.js
index 0f945f77..6e31efac 100644
--- a/themes/fukasawa/components/BlogCard.js
+++ b/themes/fukasawa/components/BlogCard.js
@@ -71,7 +71,7 @@ const BlogCard = ({ index, post, showSummary, siteInfo }) => {
passHref
href={url}
className={`break-words cursor-pointer font-bold hover:underline text-xl ${showPreview ? 'justify-center' : 'justify-start'} leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}>
- {post.title}
+ {siteConfig('POST_TITLE_ICON') && } {post.title}
diff --git a/themes/fukasawa/components/Catalog.js b/themes/fukasawa/components/Catalog.js
index 7823ff74..0e1cde78 100644
--- a/themes/fukasawa/components/Catalog.js
+++ b/themes/fukasawa/components/Catalog.js
@@ -71,7 +71,7 @@ const Catalog = ({ toc }) => {
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
>
{tocItem.text}
diff --git a/themes/game/components/BlogPost.js b/themes/game/components/BlogPost.js
index b5845cf9..fbe52fdf 100644
--- a/themes/game/components/BlogPost.js
+++ b/themes/game/components/BlogPost.js
@@ -15,7 +15,7 @@ const BlogPost = ({ post }) => {
- {post.title}
+ {siteConfig('POST_TITLE_ICON') && }{post.title}
-
+ {siteConfig('POST_TITLE_ICON') && }
{post?.title}
diff --git a/themes/gitbook/components/BlogPostCard.js b/themes/gitbook/components/BlogPostCard.js
index a2870973..352d0988 100644
--- a/themes/gitbook/components/BlogPostCard.js
+++ b/themes/gitbook/components/BlogPostCard.js
@@ -14,7 +14,7 @@ const BlogPostCard = ({ post, className }) => {
- {post.title}
+ {siteConfig('POST_TITLE_ICON') && } {post.title}
{/* 最新文章加个红点 */}
{post?.isLatest && siteConfig('GITBOOK_LATEST_POST_RED_BADGE', false, CONFIG) &&
}
diff --git a/themes/gitbook/components/BlogPostListPage.js b/themes/gitbook/components/BlogPostListPage.js
deleted file mode 100644
index 3d68f154..00000000
--- a/themes/gitbook/components/BlogPostListPage.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import BlogPostCard from './BlogPostCard'
-import { siteConfig } from '@/lib/config'
-import NavPostListEmpty from './NavPostListEmpty'
-import PaginationSimple from './PaginationSimple'
-
-/**
- * 文章列表分页表格
- * @param page 当前页
- * @param posts 所有文章
- * @param tags 所有标签
- * @returns {JSX.Element}
- * @constructor
- */
-const BlogPostListPage = ({ page = 1, posts = [], postCount }) => {
- const totalPage = Math.ceil(postCount / parseInt(siteConfig('POSTS_PER_PAGE')))
-
- if (!posts || posts.length === 0) {
- return
- }
-
- return (
-
-
- {/* 文章列表 */}
- {posts?.map(post => (
-
- ))}
-
-
-
- )
-}
-
-export default BlogPostListPage
diff --git a/themes/gitbook/components/Catalog.js b/themes/gitbook/components/Catalog.js
index ebee78ae..cf8e606f 100644
--- a/themes/gitbook/components/Catalog.js
+++ b/themes/gitbook/components/Catalog.js
@@ -74,7 +74,7 @@ const Catalog = ({ post }) => {
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
>
{tocItem.text}
diff --git a/themes/gitbook/components/NavPostItem.js b/themes/gitbook/components/NavPostItem.js
index 97ab3425..409531a8 100644
--- a/themes/gitbook/components/NavPostItem.js
+++ b/themes/gitbook/components/NavPostItem.js
@@ -1,9 +1,8 @@
-import BlogPostCard from './BlogPostCard'
-import { useState } from 'react'
-import Collapse from '@/components/Collapse'
import Badge from '@/components/Badge'
+import Collapse from '@/components/Collapse'
import { siteConfig } from '@/lib/config'
import CONFIG from '../config'
+import BlogPostCard from './BlogPostCard'
/**
* 导航列表
@@ -12,36 +11,53 @@ import CONFIG from '../config'
* @returns {JSX.Element}
* @constructor
*/
-const NavPostItem = (props) => {
- const { group } = props
- const [isOpen, changeIsOpen] = useState(group?.selected)
+const NavPostItem = props => {
+ const { group, expanded, toggleItem } = props // 接收传递的展开状态和切换函数
+ // const [isOpen, setIsOpen] = useState(expanded) // 使用展开状态作为组件内部状态
+ // 当展开状态改变时触发切换函数,并根据传入的展开状态更新内部状态
const toggleOpenSubMenu = () => {
- changeIsOpen(!isOpen)
+ toggleItem() // 调用父组件传递的切换函数
+ // setIsOpen(!expanded) // 更新内部状态为传入的展开状态的相反值
}
const groupHasLatest = group?.items?.some(post => post.isLatest)
if (group?.category) {
- return <>
-
-
{group?.category}
-
- {groupHasLatest && siteConfig('GITBOOK_LATEST_POST_RED_BADGE', false, CONFIG) && !isOpen &&
}
+ return (
+ <>
+
+
{group?.category}
+
+
+
+ {groupHasLatest &&
+ siteConfig('GITBOOK_LATEST_POST_RED_BADGE', false, CONFIG) &&
+ !expanded &&
}
+
+
+ {group?.items?.map(post => (
+
+
-
- {group?.items?.map(post => (
-
))
- }
-
- >
+ ))}
+
+ >
+ )
} else {
- return <>
- {group?.items?.map(post => (
-
))
- }
- >
+ return (
+ <>
+ {group?.items?.map(post => (
+
+
+
+ ))}
+ >
+ )
}
}
diff --git a/themes/gitbook/components/NavPostList.js b/themes/gitbook/components/NavPostList.js
index 6fbf92b5..be84e9ba 100644
--- a/themes/gitbook/components/NavPostList.js
+++ b/themes/gitbook/components/NavPostList.js
@@ -1,8 +1,9 @@
-import NavPostListEmpty from './NavPostListEmpty'
-import { useRouter } from 'next/router'
-import NavPostItem from './NavPostItem'
-import CONFIG from '../config'
import { siteConfig } from '@/lib/config'
+import { useGlobal } from '@/lib/global'
+import { useRouter } from 'next/router'
+import { useEffect, useState } from 'react'
+import CONFIG from '../config'
+import NavPostItem from './NavPostItem'
/**
* 博客列表滚动分页
@@ -11,11 +12,23 @@ import { siteConfig } from '@/lib/config'
* @returns {JSX.Element}
* @constructor
*/
-const NavPostList = (props) => {
+const NavPostList = props => {
const { filteredNavPages } = props
+ const { locale, currentSearch } = useGlobal()
const router = useRouter()
- let selectedSth = false
- const groupedArray = filteredNavPages?.reduce((groups, item) => {
+
+ // 存放被展开的分组
+ const [expandedGroups, setExpandedGroups] = useState([])
+
+ // 排他折叠
+ const GITBOOK_EXCLUSIVE_COLLAPSE = siteConfig(
+ 'GITBOOK_EXCLUSIVE_COLLAPSE',
+ null,
+ CONFIG
+ )
+
+ // 按照分类、分组折叠内榕
+ const categoryFolders = filteredNavPages?.reduce((groups, item) => {
const categoryName = item?.category ? item?.category : '' // 将category转换为字符串
let existingGroup = null
@@ -35,32 +48,79 @@ const NavPostList = (props) => {
return groups
}, [])
- // 处理是否选中
- groupedArray?.map((group) => {
- let groupSelected = false
- for (const post of group?.items) {
+ // 首次打开页面时,跟踪是否已经选择了一个项
+ categoryFolders?.forEach(group => {
+ let hasExpandFolder = false
+ group.items.forEach(post => {
if (router.asPath.split('?')[0] === '/' + post.slug) {
- groupSelected = true
- selectedSth = true
+ hasExpandFolder = true
}
- }
- group.selected = groupSelected
- return null
+ })
+ group.selected = hasExpandFolder
})
// 如果都没有选中默认打开第一个
- if (!selectedSth && groupedArray && groupedArray?.length > 0) {
- groupedArray[0].selected = true
+ useEffect(() => {
+ setTimeout(() => {
+ if (expandedGroups.length === 0) {
+ setExpandedGroups([0])
+ }
+ }, 500)
+ }, [router])
+
+ // 折叠项切换,当折叠或展开数组时会调用
+ const toggleItem = index => {
+ let newExpandedGroups = [...expandedGroups] // 创建一个新的展开分组数组
+
+ // 如果expandedGroups中不存在,增加入,若存在则移除
+ if (expandedGroups.includes(index)) {
+ // 如果expandedGroups中包含index,则移除index
+ newExpandedGroups = newExpandedGroups.filter(
+ expandedIndex => expandedIndex !== index
+ )
+ } else {
+ // 如果expandedGroups中不包含index,则加入index
+ newExpandedGroups.push(index)
+ }
+ // 是否排他
+ if (GITBOOK_EXCLUSIVE_COLLAPSE) {
+ // 如果折叠菜单排他性为 true,则只展开当前分组,关闭其他已展开的分组
+ newExpandedGroups = newExpandedGroups.filter(
+ expandedIndex => expandedIndex === index
+ )
+ }
+
+ // 更新展开分组数组
+ setExpandedGroups(newExpandedGroups)
+ }
+ if (!categoryFolders || categoryFolders.length === 0) {
+ // 空白内容
+ return (
+
+
+ {locale.COMMON.NO_RESULTS_FOUND}{' '}
+ {currentSearch &&
{currentSearch}
}
+
+
+ )
}
- if (!groupedArray || groupedArray.length === 0) {
- return
- } else {
- return
- {/* 文章列表 */}
- {groupedArray?.map((group, index) => )}
-
- }
+ return (
+
+ {/* 文章列表 */}
+ {categoryFolders?.map((group, index) => (
+ toggleItem(index)} // 将切换函数传递给子组件
+ />
+ ))}
+
+ )
}
export default NavPostList
diff --git a/themes/gitbook/components/NavPostListEmpty.js b/themes/gitbook/components/NavPostListEmpty.js
deleted file mode 100644
index 54f2f179..00000000
--- a/themes/gitbook/components/NavPostListEmpty.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { useGlobal } from '@/lib/global'
-
-/**
- * 空白博客 列表
- * @returns {JSX.Element}
- * @constructor
- */
-const NavPostListEmpty = ({ currentSearch }) => {
- const { locale } = useGlobal()
- return
-
{locale.COMMON.NO_RESULTS_FOUND} {(currentSearch &&
{currentSearch}
)}
-
-}
-export default NavPostListEmpty
diff --git a/themes/gitbook/config.js b/themes/gitbook/config.js
index 88daae8b..3e376bbb 100644
--- a/themes/gitbook/config.js
+++ b/themes/gitbook/config.js
@@ -1,10 +1,10 @@
const CONFIG = {
-
GITBOOK_INDEX_PAGE: 'about', // 文档首页显示的文章,请确此路径包含在您的notion数据库中
GITBOOK_AUTO_SORT: process.env.NEXT_PUBLIC_GITBOOK_AUTO_SORT || true, // 是否自动按分类名 归组排序文章;自动归组可能会打乱您Notion中的文章顺序
- GITBOOK_LATEST_POST_RED_BADGE: process.env.NEXT_PUBLIC_GITBOOK_LATEST_POST_RED_BADGE || true, // 是否给最新文章显示红点
+ GITBOOK_LATEST_POST_RED_BADGE:
+ process.env.NEXT_PUBLIC_GITBOOK_LATEST_POST_RED_BADGE || true, // 是否给最新文章显示红点
// 菜单
GITBOOK_MENU_CATEGORY: true, // 显示分类
@@ -12,8 +12,12 @@ const CONFIG = {
GITBOOK_MENU_ARCHIVE: true, // 显示归档
GITBOOK_MENU_SEARCH: true, // 显示搜索
+ // 导航文章自动排他折叠
+ GITBOOK_EXCLUSIVE_COLLAPSE: true, // 一次只展开一个分类,其它文件夹自动关闭。
+
// Widget
- GITBOOK_WIDGET_REVOLVER_MAPS: process.env.NEXT_PUBLIC_WIDGET_REVOLVER_MAPS || 'false', // 地图插件
+ GITBOOK_WIDGET_REVOLVER_MAPS:
+ process.env.NEXT_PUBLIC_WIDGET_REVOLVER_MAPS || 'false', // 地图插件
GITBOOK_WIDGET_TO_TOP: true // 跳回顶部
}
export default CONFIG
diff --git a/themes/gitbook/index.js b/themes/gitbook/index.js
index 5f4169ba..7d52f5a7 100644
--- a/themes/gitbook/index.js
+++ b/themes/gitbook/index.js
@@ -166,7 +166,7 @@ const LayoutBase = (props) => {
{/* 右侧侧推拉抽屉 */}
{fullWidth
? null
- :
+ :
@@ -275,7 +275,7 @@ const LayoutSlug = (props) => {
{!lock &&
{/* title */}
-
{post?.title}
+
{siteConfig('POST_TITLE_ICON') && }{post?.title}
{/* Notion文章主体 */}
{post && (
diff --git a/themes/heo/components/BlogPostCard.js b/themes/heo/components/BlogPostCard.js
index ab6cd1c7..4d32f231 100644
--- a/themes/heo/components/BlogPostCard.js
+++ b/themes/heo/components/BlogPostCard.js
@@ -44,7 +44,7 @@ const BlogPostCard = ({ index, post, showSummary, siteInfo }) => {
href={url}
passHref
className={' group-hover:text-indigo-700 dark:hover:text-yellow-700 dark:group-hover:text-yellow-600 text-black dark:text-gray-100 line-clamp-2 replace cursor-pointer text-xl font-extrabold leading-tight'}>
- {post.title}
+ {siteConfig('POST_TITLE_ICON') && }{post.title}
diff --git a/themes/heo/components/Catalog.js b/themes/heo/components/Catalog.js
index 81ea8d1a..1ec3dabb 100644
--- a/themes/heo/components/Catalog.js
+++ b/themes/heo/components/Catalog.js
@@ -74,7 +74,7 @@ const Catalog = ({ toc }) => {
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
>
{tocItem.text}
diff --git a/themes/heo/components/Hero.js b/themes/heo/components/Hero.js
index 9143e064..d5462707 100644
--- a/themes/heo/components/Hero.js
+++ b/themes/heo/components/Hero.js
@@ -18,16 +18,13 @@ const Hero = props => {
const HEO_HERO_REVERSE = siteConfig('HEO_HERO_REVERSE', false, CONFIG)
return (
+ id='hero-wrapper'
+ className='recent-top-post-group w-full overflow-hidden select-none px-5 mb-4'>
+ recent-post-top rounded-[12px] 2xl:px-5 recent-top-post-group max-w-[86rem] overflow-x-scroll w-full mx-auto flex-row flex-nowrap flex relative`}>
{/* 左侧banner组 */}
@@ -49,9 +46,8 @@ function BannerGroup(props) {
return (
// 左侧英雄区
+ id='bannerGroup'
+ className='flex flex-col justify-between flex-1 mr-2 max-w-[42rem]'>
{/* 动图 */}
{/* 导航分类 */}
@@ -78,17 +74,16 @@ function Banner(props) {
return (
-
-
+ className='hidden xl:flex xl:flex-col group h-full bg-white dark:bg-[#1e1e1e] rounded-xl border dark:border-gray-700 mb-3 relative overflow-hidden'>
+
+
{siteConfig('HEO_HERO_TITLE_1', null, CONFIG)}
{siteConfig('HEO_HERO_TITLE_2', null, CONFIG)}
-
+
{siteConfig('HEO_HERO_TITLE_3', null, CONFIG)}
@@ -98,15 +93,14 @@ function Banner(props) {
{/* 遮罩 */}
-
-
随便逛逛
-
+ }>
+
@@ -120,38 +114,38 @@ function Banner(props) {
* 英雄区左上角banner条中斜向滚动的图标
*/
function TagsGroupBar() {
- const groupIcons = siteConfig('HEO_GROUP_ICONS', null, CONFIG).concat(siteConfig('HEO_GROUP_ICONS', null, CONFIG))
+ const groupIcons = siteConfig('HEO_GROUP_ICONS', null, CONFIG).concat(
+ siteConfig('HEO_GROUP_ICONS', null, CONFIG)
+ )
return (
-
-
+
+
{groupIcons?.map((g, index) => {
return (
-
+
@@ -168,42 +162,39 @@ function TagsGroupBar() {
*/
function GroupMenu() {
return (
-
+
-
+ className='group relative overflow-hidden bg-gradient-to-r from-blue-500 to-blue-400 flex h-20 justify-start items-center text-white rounded-xl xl:hover:w-1/2 xl:w-1/3 transition-all duration-500 ease-in'>
+
{siteConfig('HEO_HERO_CATEGORY_1', null, CONFIG)?.title}
-
+
-
-
+
+
-
+ className='group relative overflow-hidden bg-gradient-to-r from-red-500 to-yellow-500 flex h-20 justify-start items-center text-white rounded-xl xl:hover:w-1/2 xl:w-1/3 transition-all duration-500 ease-in'>
+
{siteConfig('HEO_HERO_CATEGORY_2', null, CONFIG)?.title}
-
+
-
-
+
+
{/* 第三个标签在小屏上不显示 */}
-
+ className='group relative overflow-hidden bg-gradient-to-r from-teal-300 to-cyan-300 hidden h-20 xl:flex justify-start items-center text-white rounded-xl xl:hover:w-1/2 xl:w-1/3 transition-all duration-500 ease-in'>
+
{siteConfig('HEO_HERO_CATEGORY_3', null, CONFIG)?.title}
-
+
-
@@ -225,30 +216,28 @@ function TopGroup(props) {
return (
+ className='flex-1 relative w-full'>
{/* 置顶推荐文章 */}
+ id='top-group'
+ className='w-full flex space-x-3 xl:space-x-0 xl:grid xl:grid-cols-3 xl:gap-3 xl:h-[342px]'>
{topPosts?.map((p, index) => {
return (
-
+
-
+
{p?.title}
{/* hover 悬浮的 ‘荐’ 字 */}
-
@@ -256,7 +245,7 @@ function TopGroup(props) {
)
})}
-
+
)
}
@@ -277,7 +266,11 @@ function getTopPosts({ latestPosts, allNavPages }) {
let sortPosts = []
// 排序方式
- if (JSON.parse(siteConfig('HEO_HERO_RECOMMEND_POST_SORT_BY_UPDATE_TIME', null, CONFIG))) {
+ if (
+ JSON.parse(
+ siteConfig('HEO_HERO_RECOMMEND_POST_SORT_BY_UPDATE_TIME', null, CONFIG)
+ )
+ ) {
sortPosts = Object.create(allNavPages).sort((a, b) => {
const dateA = new Date(a?.lastEditedDate)
const dateB = new Date(b?.lastEditedDate)
@@ -293,7 +286,11 @@ function getTopPosts({ latestPosts, allNavPages }) {
break
}
// 查找标签
- if (post?.tags?.indexOf(siteConfig('HEO_HERO_RECOMMEND_POST_TAG', null, CONFIG)) >= 0) {
+ if (
+ post?.tags?.indexOf(
+ siteConfig('HEO_HERO_RECOMMEND_POST_TAG', null, CONFIG)
+ ) >= 0
+ ) {
topPosts.push(post)
}
}
@@ -306,6 +303,7 @@ function getTopPosts({ latestPosts, allNavPages }) {
*/
function TodayCard({ cRef, siteInfo }) {
const router = useRouter()
+ const link = siteConfig('HEO_HERO_TITLE_LINK', null, CONFIG)
// 卡牌是否盖住下层
const [isCoverUp, setIsCoverUp] = useState(true)
@@ -334,56 +332,55 @@ function TodayCard({ cRef, siteInfo }) {
* @param {*} e
*/
function handleCardClick(e) {
- router.push(siteConfig('HEO_HERO_TITLE_LINK', null, CONFIG))
+ router.push(link)
}
return (
+ } overflow-hidden absolute hidden xl:flex flex-1 flex-col h-full top-0 w-full`}>
+ } shadow transition-all duration-200 today-card h-full bg-[#0E57D5] rounded-xl relative overflow-hidden flex items-end`}>
-
-
{siteConfig('HEO_HERO_TITLE_4', null, CONFIG)}
-
{siteConfig('HEO_HERO_TITLE_5', null, CONFIG)}
+ id='today-card-info'
+ className='z-10 flex justify-between w-full relative text-white p-10 items-end'>
+
+
+ {siteConfig('HEO_HERO_TITLE_4', null, CONFIG)}
+
+
+ {siteConfig('HEO_HERO_TITLE_5', null, CONFIG)}
+
+ } flex items-center px-3 h-10 justify-center bg-[#425aef] hover:bg-[#4259efcb] transition-colors duration-100 rounded-3xl`}>
-
+ }}>
)
diff --git a/themes/heo/components/PostHeader.js b/themes/heo/components/PostHeader.js
index 925c8040..97ce4fff 100644
--- a/themes/heo/components/PostHeader.js
+++ b/themes/heo/components/PostHeader.js
@@ -65,7 +65,7 @@ export default function PostHeader({ post, siteInfo }) {
{/* 文章Title */}
- {post.title}
+ {siteConfig('POST_TITLE_ICON') && }{post.title}
{/* 标题底部补充信息 */}
diff --git a/themes/hexo/components/BlogPostCardInfo.js b/themes/hexo/components/BlogPostCardInfo.js
index 2232a6e8..5ab14856 100644
--- a/themes/hexo/components/BlogPostCardInfo.js
+++ b/themes/hexo/components/BlogPostCardInfo.js
@@ -25,7 +25,7 @@ export const BlogPostCardInfo = ({ post, showPreview, showPageCover, showSummary
className={`line-clamp-2 replace cursor-pointer text-2xl ${showPreview ? 'text-center' : ''
} leading-tight font-normal text-gray-600 dark:text-gray-100 hover:text-indigo-700 dark:hover:text-indigo-400`}>
-
{post.title}
+ {siteConfig('POST_TITLE_ICON') &&
}
{post.title}
diff --git a/themes/hexo/components/Catalog.js b/themes/hexo/components/Catalog.js
index 3c167594..0bc2e616 100644
--- a/themes/hexo/components/Catalog.js
+++ b/themes/hexo/components/Catalog.js
@@ -79,7 +79,7 @@ const Catalog = ({ toc }) => {
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
>
{tocItem.text}
diff --git a/themes/hexo/components/Header.js b/themes/hexo/components/Header.js
new file mode 100644
index 00000000..9c3da194
--- /dev/null
+++ b/themes/hexo/components/Header.js
@@ -0,0 +1,187 @@
+import { siteConfig } from '@/lib/config'
+import { useGlobal } from '@/lib/global'
+import throttle from 'lodash.throttle'
+import Link from 'next/link'
+import { useRouter } from 'next/router'
+import { useCallback, useEffect, useRef, useState } from 'react'
+import CONFIG from '../config'
+import CategoryGroup from './CategoryGroup'
+import Logo from './Logo'
+import { MenuListTop } from './MenuListTop'
+import SearchButton from './SearchButton'
+import SearchDrawer from './SearchDrawer'
+import SideBar from './SideBar'
+import SideBarDrawer from './SideBarDrawer'
+import TagGroups from './TagGroups'
+
+let windowTop = 0
+
+/**
+ * 顶部导航
+ * @param {*} param0
+ * @returns
+ */
+const Header = props => {
+ const searchDrawer = useRef()
+ const { tags, currentTag, categories, currentCategory } = props
+ const { locale } = useGlobal()
+ const router = useRouter()
+ const [isOpen, changeShow] = useState(false)
+ const showSearchButton = siteConfig('HEXO_MENU_SEARCH', false, CONFIG)
+
+ const toggleMenuOpen = () => {
+ changeShow(!isOpen)
+ }
+
+ const toggleSideBarClose = () => {
+ changeShow(false)
+ }
+
+ // 监听滚动
+ useEffect(() => {
+ window.addEventListener('scroll', topNavStyleHandler)
+ router.events.on('routeChangeComplete', topNavStyleHandler)
+ topNavStyleHandler()
+ return () => {
+ router.events.off('routeChangeComplete', topNavStyleHandler)
+ window.removeEventListener('scroll', topNavStyleHandler)
+ }
+ }, [])
+
+ const throttleMs = 200
+
+ const topNavStyleHandler = useCallback(
+ throttle(() => {
+ const scrollS = window.scrollY
+ const nav = document.querySelector('#sticky-nav')
+ // 首页和文章页会有头图
+ const header = document.querySelector('#header')
+ // 导航栏和头图是否重叠
+ const scrollInHeader =
+ header && (scrollS < 10 || scrollS < header?.clientHeight - 50) // 透明导航条的条件
+
+ // const textWhite = header && scrollInHeader
+
+ if (scrollInHeader) {
+ nav && nav.classList.replace('bg-white', 'bg-none')
+ nav && nav.classList.replace('border', 'border-transparent')
+ nav && nav.classList.replace('drop-shadow-md', 'shadow-none')
+ nav && nav.classList.replace('dark:bg-hexo-black-gray', 'transparent')
+ } else {
+ nav && nav.classList.replace('bg-none', 'bg-white')
+ nav && nav.classList.replace('border-transparent', 'border')
+ nav && nav.classList.replace('shadow-none', 'drop-shadow-md')
+ nav && nav.classList.replace('transparent', 'dark:bg-hexo-black-gray')
+ }
+
+ if (scrollInHeader) {
+ nav && nav.classList.replace('text-black', 'text-white')
+ } else {
+ nav && nav.classList.replace('text-white', 'text-black')
+ }
+
+ // 导航栏不在头图里,且页面向下滚动一定程度 隐藏导航栏
+ const showNav =
+ scrollS <= windowTop ||
+ scrollS < 5 ||
+ (header && scrollS <= header.clientHeight + 100)
+ if (!showNav) {
+ nav && nav.classList.replace('top-0', '-top-20')
+ windowTop = scrollS
+ } else {
+ nav && nav.classList.replace('-top-20', 'top-0')
+ windowTop = scrollS
+ }
+ }, throttleMs)
+ )
+
+ const searchDrawerSlot = (
+ <>
+ {categories && (
+
+
+
+
+ {locale.COMMON.CATEGORY}
+
+
+ {locale.COMMON.MORE}
+
+
+
+
+ )}
+
+ {tags && (
+
+
+
+
+ {locale.COMMON.TAGS}
+
+
+ {locale.COMMON.MORE}
+
+
+
+
+
+
+ )}
+ >
+ )
+
+ return (
+
+
+
+ {/* 导航栏 */}
+
+
+
+
+
+
+ {/* 右侧功能 */}
+
+
+ {' '}
+
+
+
+ {isOpen ? (
+
+ ) : (
+
+ )}
+
+ {showSearchButton &&
}
+
+
+
+
+ {/* 折叠侧边栏 */}
+
+
+
+
+ )
+}
+
+export default Header
diff --git a/themes/hexo/components/Hero.js b/themes/hexo/components/Hero.js
index 67ae3468..91059205 100644
--- a/themes/hexo/components/Hero.js
+++ b/themes/hexo/components/Hero.js
@@ -66,7 +66,7 @@ const Hero = props => {
{/* 站点标题 */}
- {siteConfig('TITLE')}
+ {siteInfo?.title || siteConfig('TITLE')}
{/* 站点欢迎语 */}
diff --git a/themes/hexo/components/Logo.js b/themes/hexo/components/Logo.js
index 30054acc..2279834c 100644
--- a/themes/hexo/components/Logo.js
+++ b/themes/hexo/components/Logo.js
@@ -1,11 +1,20 @@
import { siteConfig } from '@/lib/config'
import Link from 'next/link'
-
+/**
+ * Logo
+ * 实际值支持文字
+ * @param {*} props
+ * @returns
+ */
const Logo = props => {
+ const { siteInfo } = props
return (
-
{siteConfig('TITLE') }
+
+ {' '}
+ {siteInfo?.title || siteConfig('TITLE')}
+
)
diff --git a/themes/hexo/components/MenuItemCollapse.js b/themes/hexo/components/MenuItemCollapse.js
index 828079c7..1782ce52 100644
--- a/themes/hexo/components/MenuItemCollapse.js
+++ b/themes/hexo/components/MenuItemCollapse.js
@@ -29,13 +29,13 @@ export const MenuItemCollapse = props => {
return (
<>
{!hasSubMenu && (
+ className=' font-extralight flex justify-between pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1'>
{link?.icon && }
{link?.name}
@@ -63,7 +63,7 @@ export const MenuItemCollapse = props => {
return (
+ className='dark:hover:bg-indigo-500 hover:bg-indigo-500 hover:text-white dark:bg-black dark:text-gray-200 text-left px-10 justify-start bg-gray-50 tracking-widest transition-all duration-200 py-3 pr-6'>
{link?.icon && }{' '}
diff --git a/themes/hexo/components/MenuItemDrop.js b/themes/hexo/components/MenuItemDrop.js
index b74d7a8f..18ea74b6 100644
--- a/themes/hexo/components/MenuItemDrop.js
+++ b/themes/hexo/components/MenuItemDrop.js
@@ -1,6 +1,10 @@
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
@@ -25,7 +29,7 @@ export const MenuItemDrop = ({ link }) => {
{hasSubMenu && (
<>
-
+
{link?.icon &&
} {link?.name}
@@ -42,7 +46,7 @@ export const MenuItemDrop = ({ link }) => {
return (
+ className='cursor-pointer hover:bg-indigo-500 hover:text-white tracking-widest transition-all duration-200 dark:border-gray-800 py-1 pr-6 pl-3'>
{link?.icon && }
diff --git a/themes/hexo/components/MenuListSide.js b/themes/hexo/components/MenuListSide.js
index 3a146d43..ab0b4278 100644
--- a/themes/hexo/components/MenuListSide.js
+++ b/themes/hexo/components/MenuListSide.js
@@ -1,17 +1,41 @@
-import { useGlobal } from '@/lib/global'
import { siteConfig } from '@/lib/config'
-import { MenuItemCollapse } from './MenuItemCollapse'
+import { useGlobal } from '@/lib/global'
import CONFIG from '../config'
-
-export const MenuListSide = (props) => {
+import { MenuItemCollapse } from './MenuItemCollapse'
+/**
+ * 侧拉抽屉菜单
+ * @param {*} props
+ * @returns
+ */
+export const MenuListSide = props => {
const { customNav, customMenu } = props
const { locale } = useGlobal()
let links = [
- { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: siteConfig('HEXO_MENU_ARCHIVE', null, CONFIG) },
- { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: siteConfig('HEXO_MENU_SEARCH', null, CONFIG) },
- { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: siteConfig('HEXO_MENU_CATEGORY', null, CONFIG) },
- { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: siteConfig('HEXO_MENU_TAG', null, CONFIG) }
+ {
+ icon: 'fas fa-archive',
+ name: locale.NAV.ARCHIVE,
+ to: '/archive',
+ show: siteConfig('HEXO_MENU_ARCHIVE', null, CONFIG)
+ },
+ {
+ icon: 'fas fa-search',
+ name: locale.NAV.SEARCH,
+ to: '/search',
+ show: siteConfig('HEXO_MENU_SEARCH', null, CONFIG)
+ },
+ {
+ icon: 'fas fa-folder',
+ name: locale.COMMON.CATEGORY,
+ to: '/category',
+ show: siteConfig('HEXO_MENU_CATEGORY', null, CONFIG)
+ },
+ {
+ icon: 'fas fa-tag',
+ name: locale.COMMON.TAGS,
+ to: '/tag',
+ show: siteConfig('HEXO_MENU_TAG', null, CONFIG)
+ }
]
if (customNav) {
@@ -34,8 +58,10 @@ export const MenuListSide = (props) => {
}
return (
-
+
)
}
diff --git a/themes/hexo/components/PostHeader.js b/themes/hexo/components/PostHeader.js
deleted file mode 100644
index 6441d942..00000000
--- a/themes/hexo/components/PostHeader.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import Link from 'next/link'
-import TagItemMini from './TagItemMini'
-import { useGlobal } from '@/lib/global'
-import NotionIcon from '@/components/NotionIcon'
-import LazyImage from '@/components/LazyImage'
-import { formatDateFmt } from '@/lib/utils/formatDate'
-import { siteConfig } from '@/lib/config'
-
-export default function PostHeader({ post, siteInfo }) {
- const { locale, fullWidth } = useGlobal()
-
- if (!post) {
- return <>>
- }
-
- // 文章全屏隐藏标头
- if (fullWidth) {
- return
- }
-
- const headerImage = post?.pageCover ? post.pageCover : siteInfo?.pageCover
-
- return (
-
- )
-}
diff --git a/themes/hexo/components/PostHero.js b/themes/hexo/components/PostHero.js
new file mode 100644
index 00000000..4cedf597
--- /dev/null
+++ b/themes/hexo/components/PostHero.js
@@ -0,0 +1,99 @@
+import LazyImage from '@/components/LazyImage'
+import NotionIcon from '@/components/NotionIcon'
+import { siteConfig } from '@/lib/config'
+import { useGlobal } from '@/lib/global'
+import { formatDateFmt } from '@/lib/utils/formatDate'
+import Link from 'next/link'
+import TagItemMini from './TagItemMini'
+
+/**
+ * 文章详情页的Hero块
+ */
+export default function PostHero({ post, siteInfo }) {
+ const { locale, fullWidth } = useGlobal()
+
+ if (!post) {
+ return <>>
+ }
+
+ // 文章全屏隐藏标头
+ if (fullWidth) {
+ return
+ }
+
+ const headerImage = post?.pageCover ? post.pageCover : siteInfo?.pageCover
+
+ return (
+
+ )
+}
diff --git a/themes/hexo/components/SideBar.js b/themes/hexo/components/SideBar.js
index 913ce0a9..64def6ec 100644
--- a/themes/hexo/components/SideBar.js
+++ b/themes/hexo/components/SideBar.js
@@ -1,5 +1,5 @@
-import { siteConfig } from '@/lib/config'
import LazyImage from '@/components/LazyImage'
+import { siteConfig } from '@/lib/config'
import { useRouter } from 'next/router'
import MenuGroupCard from './MenuGroupCard'
import { MenuListSide } from './MenuListSide'
@@ -11,22 +11,33 @@ import { MenuListSide } from './MenuListSide'
* @returns {JSX.Element}
* @constructor
*/
-const SideBar = (props) => {
+const SideBar = props => {
const { siteInfo } = props
const router = useRouter()
return (
-
-
-
-
{ router.push('/') }}
- className='justify-center items-center flex hover:rotate-45 py-6 hover:scale-105 dark:text-gray-100 transform duration-200 cursor-pointer'>
-
-
-
-
-
-
+
+
+
+
{
+ router.push('/')
+ }}
+ className='justify-center items-center flex hover:rotate-45 py-6 hover:scale-105 dark:text-gray-100 transform duration-200 cursor-pointer'>
+ {/* 头像 */}
+
+
+ {/* 总览 */}
+
+
+ {/* 侧拉抽屉的菜单 */}
+
+
)
}
diff --git a/themes/hexo/components/TopNav.js b/themes/hexo/components/TopNav.js
deleted file mode 100644
index 6f199fa6..00000000
--- a/themes/hexo/components/TopNav.js
+++ /dev/null
@@ -1,159 +0,0 @@
-import { useGlobal } from '@/lib/global'
-import Link from 'next/link'
-import { useCallback, useEffect, useRef, useState } from 'react'
-import CategoryGroup from './CategoryGroup'
-import Logo from './Logo'
-import SearchDrawer from './SearchDrawer'
-import TagGroups from './TagGroups'
-import { MenuListTop } from './MenuListTop'
-import throttle from 'lodash.throttle'
-import SideBar from './SideBar'
-import SideBarDrawer from './SideBarDrawer'
-import { siteConfig } from '@/lib/config'
-import SearchButton from './SearchButton'
-import CONFIG from '../config'
-import { useRouter } from 'next/router'
-
-let windowTop = 0
-
-/**
- * 顶部导航
- * @param {*} param0
- * @returns
- */
-const TopNav = props => {
- const searchDrawer = useRef()
- const { tags, currentTag, categories, currentCategory } = props
- const { locale } = useGlobal()
- const router = useRouter()
- const [isOpen, changeShow] = useState(false)
- const showSearchButton = siteConfig('HEXO_MENU_SEARCH', false, CONFIG)
-
- const toggleMenuOpen = () => {
- changeShow(!isOpen)
- }
-
- const toggleSideBarClose = () => {
- changeShow(false)
- }
-
- // 监听滚动
- useEffect(() => {
- window.addEventListener('scroll', topNavStyleHandler)
- router.events.on('routeChangeComplete', topNavStyleHandler)
- topNavStyleHandler()
- return () => {
- router.events.off('routeChangeComplete', topNavStyleHandler)
- window.removeEventListener('scroll', topNavStyleHandler)
- }
- }, [])
-
- const throttleMs = 200
-
- const topNavStyleHandler = useCallback(throttle(() => {
- const scrollS = window.scrollY
- const nav = document.querySelector('#sticky-nav')
- // 首页和文章页会有头图
- const header = document.querySelector('#header')
- // 导航栏和头图是否重叠
- const scrollInHeader = header && (scrollS < 10 || scrollS < header?.clientHeight - 50) // 透明导航条的条件
-
- // const textWhite = header && scrollInHeader
-
- if (scrollInHeader) {
- nav && nav.classList.replace('bg-white', 'bg-none')
- nav && nav.classList.replace('border', 'border-transparent')
- nav && nav.classList.replace('drop-shadow-md', 'shadow-none')
- nav && nav.classList.replace('dark:bg-hexo-black-gray', 'transparent')
- } else {
- nav && nav.classList.replace('bg-none', 'bg-white')
- nav && nav.classList.replace('border-transparent', 'border')
- nav && nav.classList.replace('shadow-none', 'drop-shadow-md')
- nav && nav.classList.replace('transparent', 'dark:bg-hexo-black-gray')
- }
-
- if (scrollInHeader) {
- nav && nav.classList.replace('text-black', 'text-white')
- } else {
- nav && nav.classList.replace('text-white', 'text-black')
- }
-
- // 导航栏不在头图里,且页面向下滚动一定程度 隐藏导航栏
- const showNav = scrollS <= windowTop || scrollS < 5 || (header && scrollS <= header.clientHeight + 100)
- if (!showNav) {
- nav && nav.classList.replace('top-0', '-top-20')
- windowTop = scrollS
- } else {
- nav && nav.classList.replace('-top-20', 'top-0')
- windowTop = scrollS
- }
- }, throttleMs)
- )
-
- const searchDrawerSlot = <>
- {categories && (
-
-
-
{locale.COMMON.CATEGORY}
-
-
- {locale.COMMON.MORE}
-
-
-
-
-
- )}
-
- {tags && (
-
-
-
{locale.COMMON.TAGS}
-
-
- {locale.COMMON.MORE}
-
-
-
-
-
-
-
- )}
- >
-
- return (
-
-
- {/* 导航栏 */}
-
-
-
-
-
-
- {/* 右侧功能 */}
-
-
-
- {isOpen ? : }
-
- {showSearchButton &&
}
-
-
-
-
- {/* 折叠侧边栏 */}
-
-
-
-
)
-}
-
-export default TopNav
diff --git a/themes/hexo/index.js b/themes/hexo/index.js
index 664b9698..8dd08ec9 100644
--- a/themes/hexo/index.js
+++ b/themes/hexo/index.js
@@ -19,9 +19,10 @@ import BlogPostListPage from './components/BlogPostListPage'
import BlogPostListScroll from './components/BlogPostListScroll'
import Card from './components/Card'
import Footer from './components/Footer'
+import Header from './components/Header'
import Hero from './components/Hero'
import JumpToCommentButton from './components/JumpToCommentButton'
-import PostHeader from './components/PostHeader'
+import PostHero from './components/PostHero'
import RightFloatArea from './components/RightFloatArea'
import SearchNav from './components/SearchNav'
import SideRight from './components/SideRight'
@@ -29,7 +30,6 @@ import SlotBar from './components/SlotBar'
import TagItemMini from './components/TagItemMini'
import TocDrawer from './components/TocDrawer'
import TocDrawerButton from './components/TocDrawerButton'
-import TopNav from './components/TopNav'
import CONFIG from './config'
import { Style } from './style'
@@ -54,7 +54,7 @@ const LayoutBase = props => {
const router = useRouter()
const headerSlot = post ? (
-
+
) : router.route === '/' &&
siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG) ? (
@@ -89,7 +89,7 @@ const LayoutBase = props => {
{/* 顶部导航 */}
-
+
{/* 顶部嵌入 */}
{
className="h-full w-full group-hover:scale-125 group-hover:brightness-50 brightness-90 rounded-t-md transform object-cover duration-500"
/>
- {post.title}
+ {siteConfig('POST_TITLE_ICON') && }{post.title}
diff --git a/themes/matery/components/BlogPostListPage.js b/themes/matery/components/BlogPostListPage.js
index 7f7c4b6f..05478669 100644
--- a/themes/matery/components/BlogPostListPage.js
+++ b/themes/matery/components/BlogPostListPage.js
@@ -1,5 +1,5 @@
-import BlogPostCard from './BlogPostCard'
import { siteConfig } from '@/lib/config'
+import BlogPostCard from './BlogPostCard'
import BlogPostListEmpty from './BlogPostListEmpty'
import PaginationSimple from './PaginationSimple'
@@ -12,7 +12,9 @@ import PaginationSimple from './PaginationSimple'
* @constructor
*/
const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
- const totalPage = Math.ceil(postCount / parseInt(siteConfig('POSTS_PER_PAGE')))
+ const totalPage = Math.ceil(
+ postCount / parseInt(siteConfig('POSTS_PER_PAGE'))
+ )
const showPagination = postCount >= parseInt(siteConfig('POSTS_PER_PAGE'))
if (!posts || posts.length === 0 || page > totalPage) {
return
@@ -21,12 +23,17 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
{/* 文章列表 */}
-
- {posts?.map(post => (
-
+
+ {posts?.map((post, index) => (
+
+ {' '}
+
+
))}
- {showPagination &&
}
+ {showPagination && (
+
+ )}
)
}
diff --git a/themes/matery/components/Catalog.js b/themes/matery/components/Catalog.js
index 45817545..490253fb 100644
--- a/themes/matery/components/Catalog.js
+++ b/themes/matery/components/Catalog.js
@@ -78,7 +78,7 @@ const Catalog = ({ toc }) => {
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
>
{tocItem.text}
diff --git a/themes/matery/components/Footer.js b/themes/matery/components/Footer.js
index b75368b4..a2266cbb 100644
--- a/themes/matery/components/Footer.js
+++ b/themes/matery/components/Footer.js
@@ -3,31 +3,60 @@ import { siteConfig } from '@/lib/config'
const Footer = ({ title }) => {
const d = new Date()
const currentYear = d.getFullYear()
- const copyrightDate = (function() {
- if (Number.isInteger(siteConfig('SINCE')) && siteConfig('SINCE') < currentYear) {
+ const copyrightDate = (function () {
+ if (
+ Number.isInteger(siteConfig('SINCE')) &&
+ siteConfig('SINCE') < currentYear
+ ) {
return siteConfig('SINCE') + '-' + currentYear
}
return currentYear
})()
return (
-