diff --git a/lib/notion/getAllPosts.js b/lib/notion/getAllPosts.js
index d13ff5f2..6f38bf79 100644
--- a/lib/notion/getAllPosts.js
+++ b/lib/notion/getAllPosts.js
@@ -25,7 +25,13 @@ export async function getAllPosts ({ notionPageData, from, includePage = false }
const collectionQuery = notionPageData.collectionQuery
const data = []
+ if (!collectionQuery || collectionQuery.toString === '{}') {
+ console.warn('列表查询条件为空', notionPageData)
+ }
const pageIds = getAllPageIds(collectionQuery)
+ if (!pageIds || pageIds.length === 0) {
+ console.warn('页面ID列表为空')
+ }
for (let i = 0; i < pageIds.length; i++) {
const id = pageIds[i]
const properties = (await getPageProperties(id, pageBlock, schema)) || null
@@ -59,6 +65,9 @@ export async function getAllPosts ({ notionPageData, from, includePage = false }
}
})
+ if (!posts || posts.length === 0) {
+ console.warn('文章列表为空')
+ }
// Sort by date
if (BLOG.POSTS_SORT_BY === 'date') {
posts.sort((a, b) => {
diff --git a/pages/404.js b/pages/404.js
index 7fd48806..64cc2dc7 100644
--- a/pages/404.js
+++ b/pages/404.js
@@ -6,6 +6,6 @@ import { Layout404 } from '@/themes'
* @constructor
*/
-export default function Custom404 () {
- return
+export default function Custom404 (props) {
+ return
}
diff --git a/pages/about.js b/pages/about.js
index af382f68..895a94f3 100644
--- a/pages/about.js
+++ b/pages/about.js
@@ -11,7 +11,7 @@ import { LayoutSlug } from '@/themes'
*/
const About = (props) => {
if (!props.post) {
- return
+ return
}
return
}
diff --git a/pages/article/[slug].js b/pages/article/[slug].js
index 7903994f..bdc6d706 100644
--- a/pages/article/[slug].js
+++ b/pages/article/[slug].js
@@ -11,7 +11,7 @@ import Custom404 from '@/pages/404'
*/
const Slug = (props) => {
if (!props.post) {
- return
+ return
}
return
}
diff --git a/pages/page/[page].js b/pages/page/[page].js
index 2c5cadf0..43d16aec 100644
--- a/pages/page/[page].js
+++ b/pages/page/[page].js
@@ -6,7 +6,7 @@ import Custom404 from '@/pages/404'
const Page = (props) => {
if (!props?.meta) {
- return
+ return
}
return
}
diff --git a/pages/tag/[tag].js b/pages/tag/[tag].js
index 908c329d..2c72b75f 100644
--- a/pages/tag/[tag].js
+++ b/pages/tag/[tag].js
@@ -16,7 +16,7 @@ export async function getStaticProps ({ params }) {
latestPosts
} = await getGlobalNotionData({
from,
- includePage: true,
+ includePage: false,
tagsCount: 0
})
const filteredPosts = allPosts.filter(
diff --git a/public/bg_image.jpg b/public/bg_image.jpg
old mode 100755
new mode 100644
index cfbe96f5..d602ff36
Binary files a/public/bg_image.jpg and b/public/bg_image.jpg differ
diff --git a/styles/globals.css b/styles/globals.css
index 70eec009..cd9aab7b 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -188,4 +188,8 @@ nav {
width: 100%;
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,#fff 70%);
padding-bottom: 34px;
+}
+
+.shadow-text{
+ text-shadow: 0.1em 0.1em 0.2em black;
}
\ No newline at end of file
diff --git a/themes/Empty/LayoutArchive.js b/themes/Empty/LayoutArchive.js
index e46502fc..bf9ebdc5 100644
--- a/themes/Empty/LayoutArchive.js
+++ b/themes/Empty/LayoutArchive.js
@@ -1,5 +1,6 @@
-export const LayoutArchive = ({ posts, tags, categories, postCount }) => {
- return
+export const LayoutArchive = (props) => {
+ // const { posts, tags, categories, postCount } = props
+ return
Archive Page
}
diff --git a/themes/Empty/LayoutBase.js b/themes/Empty/LayoutBase.js
index d302c580..f906fef4 100644
--- a/themes/Empty/LayoutBase.js
+++ b/themes/Empty/LayoutBase.js
@@ -2,39 +2,18 @@ import CommonHead from '@/components/CommonHead'
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
- * @param children
- * @param layout
- * @param tags
- * @param meta
- * @param post
- * @param currentSearch
- * @param currentCategory
- * @param currentTag
- * @param categories
+
* @returns {JSX.Element}
* @constructor
*/
-const LayoutBase = ({
- children,
- headerSlot,
- tags,
- meta,
- post,
- postCount,
- sideBarSlot,
- floatSlot,
- rightAreaSlot,
- currentSearch,
- currentCategory,
- currentTag,
- categories
-}) => {
- return (<>
+const LayoutBase = (props) => {
+ const { children, meta } = props
+ return
{children}
- >)
+
}
export default LayoutBase
diff --git a/themes/Empty/LayoutCategory.js b/themes/Empty/LayoutCategory.js
index 60e0d11a..3a4bb0e9 100644
--- a/themes/Empty/LayoutCategory.js
+++ b/themes/Empty/LayoutCategory.js
@@ -1,5 +1,8 @@
-export const LayoutCategory = ({ tags, posts, category, categories, latestPosts, postCount }) => {
- return
+import LayoutBase from './LayoutBase'
+
+export const LayoutCategory = (props) => {
+ const { category } = props
+ return
Category - {category}
-
+
}
diff --git a/themes/Empty/LayoutCategoryIndex.js b/themes/Empty/LayoutCategoryIndex.js
index 467bde1c..fcde0a08 100644
--- a/themes/Empty/LayoutCategoryIndex.js
+++ b/themes/Empty/LayoutCategoryIndex.js
@@ -1,11 +1,8 @@
-export const LayoutCategoryIndex = ({
- tags,
- allPosts,
- categories,
- postCount,
- latestPosts
-}) => {
- return
+import LayoutBase from './LayoutBase'
+
+export const LayoutCategoryIndex = (props) => {
+ // const { tags, allPosts, categories, postCount, latestPosts } = props
+ return
CategoryIndex
-
+
}
diff --git a/themes/Empty/LayoutIndex.js b/themes/Empty/LayoutIndex.js
index 986951c3..9408f97a 100644
--- a/themes/Empty/LayoutIndex.js
+++ b/themes/Empty/LayoutIndex.js
@@ -1,3 +1,6 @@
-export const LayoutIndex = ({ posts, tags, meta, categories, postCount, latestPosts }) => {
- return
Index
+import LayoutBase from './LayoutBase'
+
+export const LayoutIndex = (props) => {
+ // const { posts, tags, meta, categories, postCount, latestPosts } = props
+ return
Index
}
diff --git a/themes/Empty/LayoutPage.js b/themes/Empty/LayoutPage.js
index c025ff3c..d4355d46 100644
--- a/themes/Empty/LayoutPage.js
+++ b/themes/Empty/LayoutPage.js
@@ -1,5 +1,8 @@
-export const LayoutPage = ({ page, posts, tags, meta, categories, postCount, latestPosts }) => {
- return
+import LayoutBase from '../Hexo/LayoutBase'
+
+export const LayoutPage = (props) => {
+ const { page } = props
+ return
Page - {page}
-
+
}
diff --git a/themes/Empty/LayoutSearch.js b/themes/Empty/LayoutSearch.js
index 76e4867c..c7469a60 100644
--- a/themes/Empty/LayoutSearch.js
+++ b/themes/Empty/LayoutSearch.js
@@ -1,11 +1,8 @@
import { useRouter } from 'next/router'
+import LayoutBase from './LayoutBase'
-export const LayoutSearch = ({
- posts,
- tags,
- categories,
- postCount
-}) => {
+export const LayoutSearch = (props) => {
+ const { posts } = props
let filteredPosts
const searchKey = getSearchKey()
if (searchKey) {
@@ -20,9 +17,9 @@ export const LayoutSearch = ({
console.log(filteredPosts)
- return
+ return
Search {searchKey}
-
+
}
function getSearchKey () {
diff --git a/themes/Empty/LayoutSlug.js b/themes/Empty/LayoutSlug.js
index 9335af67..4def0283 100644
--- a/themes/Empty/LayoutSlug.js
+++ b/themes/Empty/LayoutSlug.js
@@ -1,21 +1,45 @@
+import BLOG from '@/blog.config'
import 'prismjs'
import 'prismjs/components/prism-bash'
import 'prismjs/components/prism-javascript'
import 'prismjs/components/prism-markup'
import 'prismjs/components/prism-python'
import 'prismjs/components/prism-typescript'
+import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
+import LayoutBase from './LayoutBase'
-export const LayoutSlug = ({
- post,
- tags,
- prev,
- next,
- recommendPosts,
- categories,
- postCount,
- latestPosts
-}) => {
- return
- Slug
-
+const mapPageUrl = id => {
+ return 'https://www.notion.so/' + id.replace(/-/g, '')
+}
+
+export const LayoutSlug = (props) => {
+ const { post } = props
+ const meta = {
+ title: `${post.title} | ${BLOG.TITLE}`,
+ description: post.summary,
+ type: 'article',
+ tags: post.tags
+ }
+
+ return
+ Slug - {post?.title}
+
+ {/* Notion文章主体 */}
+
+ {post.blockMap && (
+
+ )}
+
+
+
+
}
diff --git a/themes/Empty/LayoutTag.js b/themes/Empty/LayoutTag.js
index cae36f38..5cfacb20 100644
--- a/themes/Empty/LayoutTag.js
+++ b/themes/Empty/LayoutTag.js
@@ -1,5 +1,8 @@
-export const LayoutTag = ({ tags, posts, tag, categories, postCount, latestPosts }) => {
- return
+import LayoutBase from './LayoutBase'
+
+export const LayoutTag = (props) => {
+ const { tag } = props
+ return
Tag - {tag}
-
+
}
diff --git a/themes/Empty/LayoutTagIndex.js b/themes/Empty/LayoutTagIndex.js
index deb76f8e..01a16e62 100644
--- a/themes/Empty/LayoutTagIndex.js
+++ b/themes/Empty/LayoutTagIndex.js
@@ -1,5 +1,8 @@
-export const LayoutTagIndex = ({ tags, categories, postCount, latestPosts }) => {
- return
+import LayoutBase from './LayoutBase'
+
+export const LayoutTagIndex = (props) => {
+ // const { tags, categories, postCount, latestPosts } = props
+ return
TagIndex
-
+
}
diff --git a/themes/Empty/config_empty.js b/themes/Empty/config_empty.js
index e69de29b..9f1ac567 100644
--- a/themes/Empty/config_empty.js
+++ b/themes/Empty/config_empty.js
@@ -0,0 +1,4 @@
+const CONFIG_EMPTY = {
+ TEST_CONFIG: 'TESET'
+}
+export default CONFIG_EMPTY
diff --git a/themes/Hexo/Layout404.js b/themes/Hexo/Layout404.js
new file mode 100644
index 00000000..a1dfe4cd
--- /dev/null
+++ b/themes/Hexo/Layout404.js
@@ -0,0 +1,39 @@
+import BLOG from '@/blog.config'
+import { faSpinner } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { useRouter } from 'next/router'
+import { useEffect } from 'react'
+import LayoutBase from './LayoutBase'
+
+export const Layout404 = props => {
+ const router = useRouter()
+ useEffect(() => {
+ // 延时3秒如果加载失败就返回首页
+ setTimeout(() => {
+ if (window) {
+ const article = document.getElementById('container')
+ if (!article) {
+ router.push('/').then(() => {
+ console.log('找不到页面', router.asPath)
+ })
+ }
+ }
+ }, 3000)
+ })
+
+ return (
+
+
+
+
+
+ 404
+
+
+
页面无法加载,即将返回首页
+
+
+
+
+ )
+}
diff --git a/themes/Hexo/LayoutArchive.js b/themes/Hexo/LayoutArchive.js
new file mode 100644
index 00000000..a42b7993
--- /dev/null
+++ b/themes/Hexo/LayoutArchive.js
@@ -0,0 +1,64 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import { useEffect } from 'react'
+import BlogPostArchive from './components/BlogPostArchive'
+import Card from './components/Card'
+import LayoutBase from './LayoutBase'
+
+export const LayoutArchive = (props) => {
+ const { posts } = props
+ const { locale } = useGlobal()
+ // 深拷贝
+ const postsSortByDate = Object.create(posts)
+
+ // 时间排序
+ postsSortByDate.sort((a, b) => {
+ const dateA = new Date(a?.date.start_date || a.createdTime)
+ const dateB = new Date(b?.date.start_date || b.createdTime)
+ return dateB - dateA
+ })
+
+ const meta = {
+ title: `${locale.NAV.ARCHIVE} | ${BLOG.TITLE}`,
+ description: BLOG.DESCRIPTION,
+ type: 'website'
+ }
+
+ const archivePosts = {}
+
+ postsSortByDate.forEach(post => {
+ const date = post.date.start_date.slice(0, 7)
+ if (archivePosts[date]) {
+ archivePosts[date].push(post)
+ } else {
+ archivePosts[date] = [post]
+ }
+ })
+
+ useEffect(() => {
+ if (window) {
+ const anchor = window.location.hash
+ if (anchor) {
+ setTimeout(() => {
+ const anchorElement = document.getElementById(anchor.substring(1))
+ if (anchorElement) {
+ anchorElement.scrollIntoView({ block: 'start', behavior: 'smooth' })
+ }
+ }, 300)
+ }
+ }
+ }, [])
+ return
+
+
+ {Object.keys(archivePosts).map(archiveTitle => (
+
+ ))}
+
+
+
+}
diff --git a/themes/Hexo/LayoutBase.js b/themes/Hexo/LayoutBase.js
new file mode 100644
index 00000000..26f55831
--- /dev/null
+++ b/themes/Hexo/LayoutBase.js
@@ -0,0 +1,69 @@
+import CommonHead from '@/components/CommonHead'
+import { useEffect, useState } from 'react'
+
+import Footer from './components/Footer'
+import JumpToTopButton from './components/JumpToTopButton'
+import SideRight from './components/SideRight'
+import TopNav from './components/TopNav'
+import smoothscroll from 'smoothscroll-polyfill'
+
+/**
+ * 基础布局 采用左右两侧布局,移动端使用顶部导航栏
+ * @param props
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const LayoutBase = (props) => {
+ const { children, headerSlot, floatSlot, meta } = props
+ const [show, switchShow] = useState(false)
+ const [percent, changePercent] = useState(0) // 页面阅读百分比
+
+ const scrollListener = () => {
+ const targetRef = document.getElementById('wrapper')
+ const clientHeight = targetRef?.clientHeight
+ const scrollY = window.pageYOffset
+ const fullHeight = clientHeight - window.outerHeight
+ let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
+ if (per > 100) per = 100
+ const shouldShow = scrollY > 100 && per > 0
+
+ if (shouldShow !== show) {
+ switchShow(shouldShow)
+ }
+ changePercent(per)
+ }
+ useEffect(() => {
+ smoothscroll.polyfill()
+ document.addEventListener('scroll', scrollListener)
+ return () => document.removeEventListener('scroll', scrollListener)
+ }, [show])
+
+ return (
+
+
+
+ {headerSlot}
+
+
+
+
+ {children}
+
+
+
+
+
+ {/* 右下角悬浮 */}
+
+
+
)
+}
+
+export default LayoutBase
diff --git a/themes/Hexo/LayoutCategory.js b/themes/Hexo/LayoutCategory.js
new file mode 100644
index 00000000..92c5a8a1
--- /dev/null
+++ b/themes/Hexo/LayoutCategory.js
@@ -0,0 +1,17 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import BlogPostListScroll from './components/BlogPostListScroll'
+import LayoutBase from './LayoutBase'
+
+export const LayoutCategory = (props) => {
+ const { tags, posts, category } = props
+ const { locale } = useGlobal()
+ const meta = {
+ title: `${category} | ${locale.COMMON.CATEGORY} | ${BLOG.TITLE}`,
+ description: BLOG.DESCRIPTION,
+ type: 'website'
+ }
+ return
+
+
+}
diff --git a/themes/Hexo/LayoutCategoryIndex.js b/themes/Hexo/LayoutCategoryIndex.js
new file mode 100644
index 00000000..f0d1e978
--- /dev/null
+++ b/themes/Hexo/LayoutCategoryIndex.js
@@ -0,0 +1,43 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import { faFolder, faTh } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import Link from 'next/link'
+import Card from './components/Card'
+import LayoutBase from './LayoutBase'
+
+export const LayoutCategoryIndex = props => {
+ const { categories } = props
+ const { locale } = useGlobal()
+ const meta = {
+ title: `${locale.COMMON.CATEGORY} | ${BLOG.TITLE}`,
+ description: BLOG.DESCRIPTION,
+ type: 'website'
+ }
+ return (
+
+
+
+
+ {locale.COMMON.CATEGORY}:
+
+
+ {Object.keys(categories).map(category => {
+ return (
+
+
+
+ {category}({categories[category]})
+
+
+ )
+ })}
+
+
+
+ )
+}
diff --git a/themes/Hexo/LayoutIndex.js b/themes/Hexo/LayoutIndex.js
new file mode 100644
index 00000000..d7a7c3af
--- /dev/null
+++ b/themes/Hexo/LayoutIndex.js
@@ -0,0 +1,10 @@
+import BlogPostListPage from './components/BlogPostListPage'
+import Header from './components/Header'
+import CONFIG_HEXO from './config_hexo'
+import LayoutBase from './LayoutBase'
+
+export const LayoutIndex = (props) => {
+ return
}>
+
+
+}
diff --git a/themes/Hexo/LayoutPage.js b/themes/Hexo/LayoutPage.js
new file mode 100644
index 00000000..d4ab7079
--- /dev/null
+++ b/themes/Hexo/LayoutPage.js
@@ -0,0 +1,9 @@
+import BlogPostListPage from './components/BlogPostListPage'
+import LayoutBase from './LayoutBase'
+
+export const LayoutPage = (props) => {
+ const { page, posts, postCount } = props
+ return
+
+
+}
diff --git a/themes/Hexo/LayoutSearch.js b/themes/Hexo/LayoutSearch.js
new file mode 100644
index 00000000..77be5014
--- /dev/null
+++ b/themes/Hexo/LayoutSearch.js
@@ -0,0 +1,38 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import { useRouter } from 'next/router'
+import BlogPostListPage from './components/BlogPostListPage'
+import LayoutBase from './LayoutBase'
+
+export const LayoutSearch = (props) => {
+ const { posts } = props
+ let filteredPosts
+ const searchKey = getSearchKey()
+ if (searchKey) {
+ filteredPosts = posts.filter(post => {
+ const tagContent = post.tags ? post.tags.join(' ') : ''
+ const searchContent = post.title + post.summary + tagContent
+ return searchContent.toLowerCase().includes(searchKey.toLowerCase())
+ })
+ } else {
+ filteredPosts = posts
+ }
+
+ const { locale } = useGlobal()
+ const meta = {
+ title: `${searchKey || ''} | ${locale.NAV.SEARCH} | ${BLOG.TITLE} `,
+ description: BLOG.DESCRIPTION,
+ type: 'website'
+ }
+ return
+
+
+}
+
+function getSearchKey () {
+ const router = useRouter()
+ if (router.query && router.query.s) {
+ return router.query.s
+ }
+ return null
+}
diff --git a/themes/Hexo/LayoutSlug.js b/themes/Hexo/LayoutSlug.js
new file mode 100644
index 00000000..c4d5ce7e
--- /dev/null
+++ b/themes/Hexo/LayoutSlug.js
@@ -0,0 +1,84 @@
+import BLOG from '@/blog.config'
+import formatDate from '@/lib/formatDate'
+import { useGlobal } from '@/lib/global'
+import { faFolderOpen } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import Link from 'next/link'
+import 'prismjs'
+import 'prismjs/components/prism-bash'
+import 'prismjs/components/prism-java'
+import 'prismjs/components/prism-javascript'
+import 'prismjs/components/prism-markup'
+import 'prismjs/components/prism-python'
+import 'prismjs/components/prism-typescript'
+import CONFIG_NEXT from '../NEXT/config_next'
+import ArticleDetail from './components/ArticleDetail'
+import Card from './components/Card'
+import LayoutBase from './LayoutBase'
+
+export const LayoutSlug = props => {
+ const { post } = props
+ const meta = {
+ title: `${post.title} | ${BLOG.TITLE}`,
+ description: post.summary,
+ type: 'article',
+ tags: post.tags
+ }
+
+ const { locale } = useGlobal()
+ const date = formatDate(
+ post?.date?.start_date || post.createdTime,
+ locale.LOCALE
+ )
+
+ const headerSlot = (
+
+
+
+
+ {/* 文章Title */}
+
+ {post.title}
+
+
+
+
+
+
+ )
+
+ return (
+
+
+
+
+
+ )
+}
diff --git a/themes/Hexo/LayoutTag.js b/themes/Hexo/LayoutTag.js
new file mode 100644
index 00000000..7f6249e1
--- /dev/null
+++ b/themes/Hexo/LayoutTag.js
@@ -0,0 +1,19 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import BlogPostListScroll from './components/BlogPostListScroll'
+import LayoutBase from './LayoutBase'
+
+export const LayoutTag = (props) => {
+ const { tags, posts, tag } = props
+ const { locale } = useGlobal()
+
+ const meta = {
+ title: `${tag} | ${locale.COMMON.TAGS} | ${BLOG.TITLE}`,
+ description: BLOG.DESCRIPTION,
+ type: 'website'
+ }
+
+ return
+
+
+}
diff --git a/themes/Hexo/LayoutTagIndex.js b/themes/Hexo/LayoutTagIndex.js
new file mode 100644
index 00000000..879fe696
--- /dev/null
+++ b/themes/Hexo/LayoutTagIndex.js
@@ -0,0 +1,36 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import { faTag } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import Card from './components/Card'
+import TagItemMini from './components/TagItemMini'
+import LayoutBase from './LayoutBase'
+
+export const LayoutTagIndex = props => {
+ const { tags } = props
+ const { locale } = useGlobal()
+ const meta = {
+ title: `${locale.COMMON.TAGS} | ${BLOG.TITLE}`,
+ description: BLOG.DESCRIPTION,
+ type: 'website'
+ }
+ return (
+
+
+
+
+ {locale.COMMON.TAGS}:
+
+
+
+
+ )
+}
diff --git a/themes/Hexo/components/ArticleDetail.js b/themes/Hexo/components/ArticleDetail.js
new file mode 100644
index 00000000..a30585b9
--- /dev/null
+++ b/themes/Hexo/components/ArticleDetail.js
@@ -0,0 +1,98 @@
+import Comment from '@/components/Comment'
+import mediumZoom from 'medium-zoom'
+import 'prismjs'
+import 'prismjs/components/prism-bash'
+import 'prismjs/components/prism-javascript'
+import 'prismjs/components/prism-markup'
+import 'prismjs/components/prism-python'
+import 'prismjs/components/prism-typescript'
+import { useEffect, useRef } from 'react'
+import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
+
+/**
+ *
+ * @param {*} param0
+ * @returns
+ */
+export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
+ const zoom = typeof window !== 'undefined' && mediumZoom({
+ container: '.notion-viewport',
+ background: 'rgba(0, 0, 0, 0.2)',
+ margin: getMediumZoomMargin()
+ })
+ const zoomRef = useRef(zoom ? zoom.clone() : null)
+
+ useEffect(() => {
+ // 将所有container下的所有图片添加medium-zoom
+ const container = document?.getElementById('container')
+ const imgList = container?.getElementsByTagName('img')
+ if (imgList && zoomRef.current) {
+ for (let i = 0; i < imgList.length; i++) {
+ (zoomRef.current).attach(imgList[i])
+ }
+ }
+ })
+
+ return (
+
+
+ {/* Notion文章主体 */}
+
+ {post.blockMap && (
+
+ )}
+
+
+
+
+
+
+ {/* 评论互动 */}
+
+
)
+}
+
+const mapPageUrl = id => {
+ return 'https://www.notion.so/' + id.replace(/-/g, '')
+}
+
+function getMediumZoomMargin () {
+ const width = window.innerWidth
+
+ if (width < 500) {
+ return 8
+ } else if (width < 800) {
+ return 20
+ } else if (width < 1280) {
+ return 30
+ } else if (width < 1600) {
+ return 40
+ } else if (width < 1920) {
+ return 48
+ } else {
+ return 72
+ }
+}
diff --git a/themes/Hexo/components/BlogPostArchive.js b/themes/Hexo/components/BlogPostArchive.js
new file mode 100644
index 00000000..5c3bc169
--- /dev/null
+++ b/themes/Hexo/components/BlogPostArchive.js
@@ -0,0 +1,32 @@
+import React from 'react'
+import Link from 'next/link'
+import BLOG from '@/blog.config'
+/**
+ * 博客归档列表
+ * @param posts 所有文章
+ * @param archiveTitle 归档标题
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const BlogPostArchive = ({ posts = [], archiveTitle }) => {
+ if (!posts || posts.length === 0) {
+ return <>>
+ } else {
+ return
+
{archiveTitle}
+
+ {posts.map(post => (
+ -
+
{post.date.start_date}
+
+
{post.title}
+
+
+
+ ))}
+
+
+ }
+}
+
+export default BlogPostArchive
diff --git a/themes/Hexo/components/BlogPostCard.js b/themes/Hexo/components/BlogPostCard.js
new file mode 100644
index 00000000..2c98753a
--- /dev/null
+++ b/themes/Hexo/components/BlogPostCard.js
@@ -0,0 +1,83 @@
+import BLOG from '@/blog.config'
+import { faFolder } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import Image from 'next/image'
+import Link from 'next/link'
+import React from 'react'
+import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
+import TagItemMini from './TagItemMini'
+import CONFIG_HEXO from '../config_hexo'
+
+const BlogPostCard = ({ post, showSummary }) => {
+ const showPreview = CONFIG_HEXO.POST_LIST_PREVIEW && post.blockMap
+ return (
+
+
+ )
+}
+
+const mapPageUrl = id => {
+ return 'https://www.notion.so/' + id.replace(/-/g, '')
+}
+
+export default BlogPostCard
diff --git a/themes/Hexo/components/BlogPostListEmpty.js b/themes/Hexo/components/BlogPostListEmpty.js
new file mode 100644
index 00000000..b1d87253
--- /dev/null
+++ b/themes/Hexo/components/BlogPostListEmpty.js
@@ -0,0 +1,12 @@
+
+/**
+ * 空白博客 列表
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const BlogPostListEmpty = ({ currentSearch }) => {
+ return
+
没有找到文章 {(currentSearch &&
{currentSearch}
)}
+
+}
+export default BlogPostListEmpty
diff --git a/themes/Hexo/components/BlogPostListPage.js b/themes/Hexo/components/BlogPostListPage.js
new file mode 100644
index 00000000..f10811af
--- /dev/null
+++ b/themes/Hexo/components/BlogPostListPage.js
@@ -0,0 +1,34 @@
+import BlogPostCard from './BlogPostCard'
+import PaginationNumber from './PaginationNumber'
+import BLOG from '@/blog.config'
+import BlogPostListEmpty from './BlogPostListEmpty'
+
+/**
+ * 文章列表分页表格
+ * @param page 当前页
+ * @param posts 所有文章
+ * @param tags 所有标签
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const BlogPostListPage = ({ page = 1, posts = [], postCount }) => {
+ const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
+
+ if (!posts || posts.length === 0) {
+ return
+ } else {
+ return (
+
+ {/* 文章列表 */}
+
+ {posts.map(post => (
+
+ ))}
+
+
+
+ )
+ }
+}
+
+export default BlogPostListPage
diff --git a/themes/Hexo/components/BlogPostListScroll.js b/themes/Hexo/components/BlogPostListScroll.js
new file mode 100644
index 00000000..53a1d93f
--- /dev/null
+++ b/themes/Hexo/components/BlogPostListScroll.js
@@ -0,0 +1,88 @@
+import BLOG from '@/blog.config'
+import BlogPostCard from './BlogPostCard'
+import BlogPostListEmpty from './BlogPostListEmpty'
+import { useGlobal } from '@/lib/global'
+import throttle from 'lodash.throttle'
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import CONFIG_HEXO from '../config_hexo'
+
+/**
+ * 博客列表滚动分页
+ * @param posts 所有文章
+ * @param tags 所有标签
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_HEXO.POST_LIST_SUMMARY }) => {
+ const postsPerPage = BLOG.POSTS_PER_PAGE
+ const [page, updatePage] = useState(1)
+ const postsToShow = getPostByPage(page, posts, postsPerPage)
+
+ let hasMore = false
+ if (posts) {
+ const totalCount = posts.length
+ hasMore = page * postsPerPage < totalCount
+ }
+
+ const handleGetMore = () => {
+ if (!hasMore) return
+ updatePage(page + 1)
+ }
+
+ // 监听滚动自动分页加载
+ 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))
+
+ // 监听滚动
+ useEffect(() => {
+ window.addEventListener('scroll', scrollTrigger)
+ return () => {
+ window.removeEventListener('scroll', scrollTrigger)
+ }
+ })
+
+ const targetRef = useRef(null)
+ const { locale } = useGlobal()
+
+ if (!postsToShow || postsToShow.length === 0) {
+ return
+ } else {
+ return
+
+ {/* 文章列表 */}
+
+ {postsToShow.map(post => (
+
+ ))}
+
+
+
+
{
+ handleGetMore()
+ }}
+ className='w-full my-4 py-4 text-center cursor-pointer glassmorphism shadow-xl rounded-xl dark:text-gray-200'
+ > {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}
+
+
+ }
+}
+
+/**
+ * 获取从第1页到指定页码的文章
+ * @param page 第几页
+ * @param totalPosts 所有文章
+ * @param postsPerPage 每页文章数量
+ * @returns {*}
+ */
+const getPostByPage = function (page, totalPosts, postsPerPage) {
+ return totalPosts.slice(
+ 0,
+ postsPerPage * page
+ )
+}
+export default BlogPostListScroll
diff --git a/themes/Hexo/components/Card.js b/themes/Hexo/components/Card.js
new file mode 100644
index 00000000..61feccfd
--- /dev/null
+++ b/themes/Hexo/components/Card.js
@@ -0,0 +1,9 @@
+const Card = ({ children, headerSlot, className }) => {
+ return
+ <>{headerSlot}>
+
+
+}
+export default Card
diff --git a/themes/Hexo/components/CategoryGroup.js b/themes/Hexo/components/CategoryGroup.js
new file mode 100644
index 00000000..e89159c4
--- /dev/null
+++ b/themes/Hexo/components/CategoryGroup.js
@@ -0,0 +1,27 @@
+import { faFolder, faFolderOpen } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import Link from 'next/link'
+import React from 'react'
+
+const CategoryGroup = ({ currentCategory, categories }) => {
+ if (!categories) {
+ return <>>
+ }
+ return <>
+
+ >
+}
+
+export default CategoryGroup
diff --git a/themes/Hexo/components/Collapse.js b/themes/Hexo/components/Collapse.js
new file mode 100644
index 00000000..7607b732
--- /dev/null
+++ b/themes/Hexo/components/Collapse.js
@@ -0,0 +1,38 @@
+import React, { useEffect, useRef } from 'react'
+
+const Collapse = props => {
+ const collapseRef = useRef(null)
+ const collapseSection = element => {
+ const sectionHeight = element.scrollHeight
+ requestAnimationFrame(function () {
+ element.style.height = sectionHeight + 'px'
+ requestAnimationFrame(function () {
+ element.style.height = 0 + 'px'
+ })
+ })
+ }
+ const expandSection = element => {
+ const sectionHeight = element.scrollHeight
+ element.style.height = sectionHeight + 'px'
+ const clearTime = setTimeout(() => {
+ element.style.height = 'auto'
+ }, 400)
+ clearTimeout(clearTime)
+ }
+ useEffect(() => {
+ const element = collapseRef.current
+ if (props.isOpen) {
+ expandSection(element)
+ } else {
+ collapseSection(element)
+ }
+ }, [props.isOpen])
+ return (
+
+ {props.children}
+
+ )
+}
+Collapse.defaultProps = { isOpen: false }
+
+export default Collapse
diff --git a/themes/Hexo/components/Footer.js b/themes/Hexo/components/Footer.js
new file mode 100644
index 00000000..57ad7797
--- /dev/null
+++ b/themes/Hexo/components/Footer.js
@@ -0,0 +1,30 @@
+import { faCopyright, faEye, faShieldAlt, faUsers, faHeart } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React from 'react'
+import BLOG from '@/blog.config'
+
+const Footer = ({ title }) => {
+ const d = new Date()
+ const currentYear = d.getFullYear()
+ const startYear = BLOG.SINCE && BLOG.SINCE !== currentYear && BLOG.SINCE + '-'
+ return (
+
+ )
+}
+
+export default Footer
diff --git a/themes/Hexo/components/Header.js b/themes/Hexo/components/Header.js
new file mode 100644
index 00000000..c55aac64
--- /dev/null
+++ b/themes/Hexo/components/Header.js
@@ -0,0 +1,118 @@
+import { useGlobal } from '@/lib/global'
+import { faAngleDown } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { useEffect, useState } from 'react'
+import Typed from 'typed.js'
+import CONFIG_HEXO from '../config_hexo'
+
+let wrapperTop = 0
+let windowTop = 0
+let autoScroll = false
+
+/**
+ *
+ * @returns 头图
+ */
+export default function Header () {
+ const [typed, changeType] = useState()
+ useEffect(() => {
+ if (!typed && window && document.getElementById('typed')) {
+ changeType(
+ new Typed('#typed', {
+ strings: CONFIG_HEXO.HOME_BANNER_GREETINGS,
+ typeSpeed: 200,
+ backSpeed: 100,
+ backDelay: 400,
+ showCursor: true,
+ smartBackspace: true
+ })
+ )
+ }
+ })
+ const { theme } = useGlobal()
+
+ const autoScrollEnd = () => {
+ if (autoScroll) {
+ windowTop = window.scrollY
+ autoScroll = false
+ }
+ }
+
+ const scrollTrigger = () => {
+ if (
+ (window.scrollY > windowTop) &
+ (window.scrollY < window.innerHeight) &&
+ !autoScroll
+ ) {
+ autoScroll = true
+ window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
+ setTimeout(autoScrollEnd, 500)
+ }
+ if (
+ (window.scrollY < windowTop) &
+ (window.scrollY < window.innerHeight) &&
+ !autoScroll
+ ) {
+ autoScroll = true
+ window.scrollTo({ top: 0, behavior: 'smooth' })
+ setTimeout(autoScrollEnd, 500)
+ }
+ windowTop = window.scrollY
+
+ updateTopNav()
+ }
+
+ const updateTopNav = () => {
+ if (theme !== 'dark') {
+ const stickyNavElement = document.getElementById('sticky-nav')
+ if (window.scrollY < window.innerHeight) {
+ stickyNavElement?.classList?.add('dark')
+ } else {
+ stickyNavElement?.classList?.remove('dark')
+ }
+ }
+ }
+
+ function updateHeaderHeight () {
+ setTimeout(() => {
+ if (window) {
+ const wrapperElement = document.getElementById('wrapper')
+ wrapperTop = wrapperElement?.offsetTop
+ }
+ }, 500)
+ }
+
+ useEffect(() => {
+ updateHeaderHeight()
+ updateTopNav()
+ window.addEventListener('scroll', scrollTrigger)
+ window.addEventListener('resize', updateHeaderHeight)
+ return () => {
+ window.removeEventListener('scroll', scrollTrigger)
+ window.removeEventListener('resize', updateHeaderHeight)
+ }
+ })
+
+ return (
+
+ )
+}
diff --git a/themes/Hexo/components/JumpToTopButton.js b/themes/Hexo/components/JumpToTopButton.js
new file mode 100644
index 00000000..f680f549
--- /dev/null
+++ b/themes/Hexo/components/JumpToTopButton.js
@@ -0,0 +1,28 @@
+import { useGlobal } from '@/lib/global'
+import { faArrowUp } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React from 'react'
+import CONFIG_HEXO from '../config_hexo'
+
+/**
+ * 跳转到网页顶部
+ * 当屏幕下滑500像素后会出现该控件
+ * @param targetRef 关联高度的目标html标签
+ * @param showPercent 是否显示百分比
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const JumpToTopButton = ({ showPercent = true, percent }) => {
+ if (!CONFIG_HEXO.WIDGET_TO_TOP) {
+ return <>>
+ }
+ const { locale } = useGlobal()
+ return (
window.scrollTo({ top: 0, behavior: 'smooth' })} >
+
+
+
+ {showPercent && (
{percent}%
)}
+
)
+}
+
+export default JumpToTopButton
diff --git a/themes/Hexo/components/LatestPostsGroup.js b/themes/Hexo/components/LatestPostsGroup.js
new file mode 100644
index 00000000..78782e69
--- /dev/null
+++ b/themes/Hexo/components/LatestPostsGroup.js
@@ -0,0 +1,42 @@
+import BLOG from '@/blog.config'
+import { useGlobal } from '@/lib/global'
+import { faArchive, faFileAlt } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import Link from 'next/link'
+import { useRouter } from 'next/router'
+
+/**
+ * 最新文章列表
+ * @param posts 所有文章数据
+ * @param sliceCount 截取展示的数量 默认6
+ * @constructor
+ */
+const LatestPostsGroup = ({ posts }) => {
+ if (!posts) {
+ return <>>
+ }
+ // 获取当前路径
+ const currentPath = useRouter().asPath
+ const { locale } = useGlobal()
+
+ return <>
+
+
{locale.COMMON.LATEST_POSTS}
+
+ {posts.map(post => {
+ const selected = currentPath === `${BLOG.PATH}/article/${post.slug}`
+ return (
+
+
+
+
+
+ )
+ })}
+ >
+}
+export default LatestPostsGroup
diff --git a/themes/Hexo/components/Logo.js b/themes/Hexo/components/Logo.js
new file mode 100644
index 00000000..a2852287
--- /dev/null
+++ b/themes/Hexo/components/Logo.js
@@ -0,0 +1,12 @@
+import Link from 'next/link'
+import BLOG from '@/blog.config'
+import React from 'react'
+
+const Logo = () => {
+ return
+
+
+}
+export default Logo
diff --git a/themes/Hexo/components/MenuButtonGroup.js b/themes/Hexo/components/MenuButtonGroup.js
new file mode 100644
index 00000000..53528653
--- /dev/null
+++ b/themes/Hexo/components/MenuButtonGroup.js
@@ -0,0 +1,41 @@
+import React from 'react'
+import Link from 'next/link'
+import { useRouter } from 'next/router'
+import { useGlobal } from '@/lib/global'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faArchive, faHome, faTag, faTh, faUser } from '@fortawesome/free-solid-svg-icons'
+import CONFIG_HEXO from '../config_hexo'
+
+const MenuButtonGroup = ({ postCount }) => {
+ const { locale } = useGlobal()
+ const router = useRouter()
+ const archiveSlot =
{postCount}
+
+ const links = [
+ { id: 0, icon: faHome, name: locale.NAV.INDEX, to: '/' || '/', show: true },
+ { id: 1, icon: faTh, name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_HEXO.MENU_CATEGORY },
+ { id: 2, icon: faTag, name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_HEXO.MENU_TAG },
+ { id: 3, icon: faArchive, name: locale.NAV.ARCHIVE, to: '/archive', slot: archiveSlot, show: CONFIG_HEXO.MENU_ARCHIVE },
+ { id: 4, icon: faUser, name: locale.NAV.ABOUT, to: '/about', show: CONFIG_HEXO.MENU_ABOUT }
+ ]
+ return
+}
+export default MenuButtonGroup
diff --git a/themes/Hexo/components/PaginationNumber.js b/themes/Hexo/components/PaginationNumber.js
new file mode 100644
index 00000000..f37d8704
--- /dev/null
+++ b/themes/Hexo/components/PaginationNumber.js
@@ -0,0 +1,94 @@
+import BLOG from '@/blog.config'
+import Link from 'next/link'
+import { useRouter } from 'next/router'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faAngleLeft, faAngleRight } from '@fortawesome/free-solid-svg-icons'
+
+/**
+ * 数字翻页插件
+ * @param page 当前页码
+ * @param showNext 是否有下一页
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const PaginationNumber = ({ page, totalPage }) => {
+ const router = useRouter()
+ const currentPage = +page
+ const showNext = page !== totalPage
+ const pages = generatePages(page, currentPage, totalPage)
+
+ return (
+
+
+ {/* 上一页 */}
+
+
+
+
+
+
+ {pages}
+
+ {/* 下一页 */}
+
+
+
+
+
+
+ )
+}
+
+function getPageElement (page, currentPage) {
+ return
+
+ {page}
+
+
+}
+function generatePages (page, currentPage, totalPage) {
+ const pages = []
+ const groupCount = 7 // 最多显示页签数
+ if (totalPage <= groupCount) {
+ for (let i = 1; i <= totalPage; i++) {
+ pages.push(getPageElement(i, page))
+ }
+ } else {
+ pages.push(getPageElement(1, page))
+ const dynamicGroupCount = groupCount - 2
+ let startPage = currentPage - 2
+ if (startPage <= 1) {
+ startPage = 2
+ }
+ if (startPage + dynamicGroupCount > totalPage) {
+ startPage = totalPage - dynamicGroupCount
+ }
+ if (startPage > 2) {
+ pages.push(
...
)
+ }
+
+ for (let i = 0; i < dynamicGroupCount; i++) {
+ if (startPage + i < totalPage) {
+ pages.push(getPageElement(startPage + i, page))
+ }
+ }
+
+ if (startPage + dynamicGroupCount < totalPage) {
+ pages.push(
...
)
+ }
+
+ pages.push(getPageElement(totalPage, page))
+ }
+ return pages
+}
+export default PaginationNumber
diff --git a/themes/Hexo/components/SearchDrawer.js b/themes/Hexo/components/SearchDrawer.js
new file mode 100644
index 00000000..3d107e0f
--- /dev/null
+++ b/themes/Hexo/components/SearchDrawer.js
@@ -0,0 +1,36 @@
+import { Router } from 'next/router'
+import { useImperativeHandle, useRef } from 'react'
+import SearchInput from './SearchInput'
+const SearchDrawer = ({ cRef, slot }) => {
+ const searchDrawer = useRef()
+ const searchInputRef = useRef()
+ useImperativeHandle(cRef, () => {
+ return {
+ show: () => {
+ searchDrawer?.current?.classList?.remove('hidden')
+ searchInputRef?.current?.focus()
+ }
+ }
+ })
+ const hidden = () => {
+ searchDrawer?.current?.classList?.add('hidden')
+ }
+ Router.events.on('routeChangeComplete', (...args) => {
+ hidden()
+ })
+ return (
+
+ )
+}
+
+export default SearchDrawer
diff --git a/themes/Hexo/components/SearchInput.js b/themes/Hexo/components/SearchInput.js
new file mode 100644
index 00000000..57e16633
--- /dev/null
+++ b/themes/Hexo/components/SearchInput.js
@@ -0,0 +1,68 @@
+import { useRouter } from 'next/router'
+import { useImperativeHandle, useRef, useState } from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faSearch, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons'
+
+const SearchInput = ({ currentTag, currentSearch, cRef }) => {
+ const [searchKey, setSearchKey] = useState(currentSearch || '')
+ const [onLoading, setLoadingState] = useState(false)
+ const router = useRouter()
+ const searchInputRef = useRef()
+ useImperativeHandle(cRef, () => {
+ return {
+ focus: () => {
+ searchInputRef?.current?.focus()
+ }
+ }
+ })
+ const handleSearch = (key) => {
+ if (key && key !== '') {
+ setLoadingState(true)
+ router.push({ pathname: '/search', query: { s: key } }).then(r => {
+ setLoadingState(false)
+ })
+ } else {
+ router.push({ pathname: '/' }).then(r => {
+ })
+ }
+ }
+ const handleKeyUp = (e) => {
+ if (e.keyCode === 13) { // 回车
+ handleSearch(searchInputRef.current.value)
+ } else if (e.keyCode === 27) { // ESC
+ cleanSearch()
+ }
+ }
+ const cleanSearch = () => {
+ searchInputRef.current.value = ''
+ setSearchKey('')
+ }
+
+ const updateSearchKey = (val) => {
+ setSearchKey(val)
+ }
+
+ return
+
updateSearchKey(e.target.value)}
+ defaultValue={searchKey}
+ />
+
+
{ handleSearch(searchKey) }}>
+
+
+
+ {(searchKey && searchKey.length &&
+
+
+
+ )}
+
+}
+
+export default SearchInput
diff --git a/themes/Hexo/components/SideRight.js b/themes/Hexo/components/SideRight.js
new file mode 100644
index 00000000..adac7e09
--- /dev/null
+++ b/themes/Hexo/components/SideRight.js
@@ -0,0 +1,64 @@
+import Router from 'next/router'
+import Image from 'next/image'
+import BLOG from '@/blog.config'
+import Card from './Card'
+import MenuButtonGroup from './MenuButtonGroup'
+import SearchInput from './SearchInput'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faChartArea, faTh } from '@fortawesome/free-solid-svg-icons'
+import CategoryGroup from './CategoryGroup'
+import LatestPostsGroup from './LatestPostsGroup'
+import TagGroups from './TagGroups'
+import SocialButton from './SocialButton'
+
+export default function SideRight (props) {
+ const { postCount, currentCategory, categories, latestPosts, tags, currentTag } = props
+ return
+
+ { Router.push('/') }}>
+
+
+ {BLOG.TITLE}
+
+
+
+
+
+
+
+
+ 统计
+
+
+
+
+
+ 分类
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/themes/Hexo/components/SocialButton.js b/themes/Hexo/components/SocialButton.js
new file mode 100644
index 00000000..4a4c6a2e
--- /dev/null
+++ b/themes/Hexo/components/SocialButton.js
@@ -0,0 +1,36 @@
+import BLOG from '@/blog.config'
+import { faGithub, faTelegram, faTwitter, faWeibo } from '@fortawesome/free-brands-svg-icons'
+import { faEnvelope, faRss } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React from 'react'
+
+/**
+ * 社交联系方式按钮组
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const SocialButton = () => {
+ return
+
+ {BLOG.CONTACT_GITHUB &&
+
+ }
+ {BLOG.CONTACT_TWITTER &&
+
+ }
+ {BLOG.CONTACT_TELEGRAM &&
+
+ }
+ {BLOG.CONTACT_WEIBO &&
+
+ }
+ {BLOG.CONTACT_EMAIL &&
+
+ }
+
+
+
+
+
+}
+export default SocialButton
diff --git a/themes/Hexo/components/TagGroups.js b/themes/Hexo/components/TagGroups.js
new file mode 100644
index 00000000..9f87776f
--- /dev/null
+++ b/themes/Hexo/components/TagGroups.js
@@ -0,0 +1,29 @@
+import { faTag } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import TagItemMini from './TagItemMini'
+
+/**
+ * 标签组
+ * @param tags
+ * @param currentTag
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const TagGroups = ({ tags, currentTag }) => {
+ if (!tags) return <>>
+ return (
+
+ )
+}
+
+export default TagGroups
diff --git a/themes/Hexo/components/TagItemMini.js b/themes/Hexo/components/TagItemMini.js
new file mode 100644
index 00000000..e687413f
--- /dev/null
+++ b/themes/Hexo/components/TagItemMini.js
@@ -0,0 +1,17 @@
+import { faTag } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import Link from 'next/link'
+
+const TagItemMini = ({ tag, selected = false }) => {
+ return
+
+ {selected && } {tag.name + (tag.count ? `(${tag.count})` : '')}
+
+
+}
+
+export default TagItemMini
diff --git a/themes/Hexo/components/TopNav.js b/themes/Hexo/components/TopNav.js
new file mode 100644
index 00000000..fc94ff5b
--- /dev/null
+++ b/themes/Hexo/components/TopNav.js
@@ -0,0 +1,123 @@
+import { useGlobal } from '@/lib/global'
+import { faAngleDoubleRight, faBars, faSearch, faTag, faThList, faTimes } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import throttle from 'lodash.throttle'
+import Link from 'next/link'
+import { useCallback, useEffect, useRef, useState } from 'react'
+import CategoryGroup from './CategoryGroup'
+import Collapse from './Collapse'
+import Logo from './Logo'
+import MenuButtonGroup from './MenuButtonGroup'
+import SearchDrawer from './SearchDrawer'
+import TagGroups from './TagGroups'
+import CONFIG_HEXO from '../config_hexo'
+
+let windowTop = 0
+
+/**
+ * 顶部导航
+ * @param {*} param0
+ * @returns
+ */
+const TopNav = ({ tags, currentTag, categories, currentCategory, postCount }) => {
+ const { locale } = useGlobal()
+ const searchDrawer = useRef()
+
+ const scrollTrigger = useCallback(throttle(() => {
+ const scrollS = window.scrollY
+ if (scrollS >= windowTop && scrollS > 10) {
+ const nav = document.querySelector('#sticky-nav')
+ nav && nav.classList.replace('top-0', '-top-40')
+ windowTop = scrollS
+ } else {
+ const nav = document.querySelector('#sticky-nav')
+ nav && nav.classList.replace('-top-40', 'top-0')
+ windowTop = scrollS
+ }
+ }, 200), [])
+
+ // 监听滚动
+ useEffect(() => {
+ if (CONFIG_HEXO.NAV_TYPE === 'autoCollapse') {
+ scrollTrigger()
+ window.addEventListener('scroll', scrollTrigger)
+ }
+ return () => {
+ CONFIG_HEXO.NAV_TYPE === 'autoCollapse' && window.removeEventListener('scroll', scrollTrigger)
+ }
+ }, [])
+
+ const [isOpen, changeShow] = useState(false)
+
+ const toggleMenuOpen = () => {
+ changeShow(!isOpen)
+ }
+
+ const searchDrawerSlot = <>
+ { categories && (
+
+ ) }
+
+ { tags && (
+
+ ) }
+ >
+
+ return (
+
+
+ {/* 导航栏 */}
+
+
+ {/* 左侧LOGO 标题 */}
+
+
+
+
+
+
+ {/* 右侧功能 */}
+
+
{ searchDrawer?.current?.show() }}>
+ {locale.NAV.SEARCH}
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default TopNav
diff --git a/themes/Hexo/config_hexo.js b/themes/Hexo/config_hexo.js
new file mode 100644
index 00000000..5a85be89
--- /dev/null
+++ b/themes/Hexo/config_hexo.js
@@ -0,0 +1,18 @@
+const CONFIG_HEXO = {
+ HOME_BANNER_ENABLE: true,
+ HOME_BANNER_GREETINGS: ['Hi,我是一个程序员', 'Hi,我是一个打工人', 'Hi,我是一个干饭人', '欢迎来到我的博客🎉'], // 首页大图标语文字
+ HOME_BANNER_IMAGE: './bg_image.jpg', // see /public/bg_image.jpg
+
+ // 菜单
+ MENU_ABOUT: false, // 显示关于
+ MENU_CATEGORY: true, // 显示分类
+ MENU_TAG: true, // 显示标签
+ MENU_ARCHIVE: true, // 显示归档
+ MENU_SEARCH: true, // 显示搜索
+
+ POST_LIST_COVER: true, // 文章封面
+ POST_LIST_SUMMARY: true, // 文章摘要
+ NAV_TYPE: 'autoCollapse', // ['fixed','autoCollapse','normal'] 分别是固定屏幕顶部、屏幕顶部自动折叠,不固定
+ WIDGET_TO_TOP: true
+}
+export default CONFIG_HEXO
diff --git a/themes/Hexo/index.js b/themes/Hexo/index.js
new file mode 100644
index 00000000..aabed077
--- /dev/null
+++ b/themes/Hexo/index.js
@@ -0,0 +1,10 @@
+export { LayoutIndex } from './LayoutIndex'
+export { LayoutSearch } from './LayoutSearch'
+export { LayoutArchive } from './LayoutArchive'
+export { LayoutSlug } from './LayoutSlug'
+export { Layout404 } from './Layout404'
+export { LayoutCategory } from './LayoutCategory'
+export { LayoutCategoryIndex } from './LayoutCategoryIndex'
+export { LayoutPage } from './LayoutPage'
+export { LayoutTag } from './LayoutTag'
+export { LayoutTagIndex } from './LayoutTagIndex'
diff --git a/themes/NEXT/Layout404.js b/themes/NEXT/Layout404.js
index ff9c8927..54e31429 100644
--- a/themes/NEXT/Layout404.js
+++ b/themes/NEXT/Layout404.js
@@ -18,7 +18,7 @@ export const Layout404 = () => {
})
}
}
- }, 30000000)
+ }, 3000)
})
return
diff --git a/themes/index.js b/themes/index.js
index 5ec2485d..4f34ef8d 100644
--- a/themes/index.js
+++ b/themes/index.js
@@ -2,6 +2,7 @@
* 修改 from 后面的路径,实现主题切换
*/
-export * from './NEXT' // 切换主题
+// export * from './Empty' // 空主题
+// export * from './NEXT'
// export * from './Fukasawa'
-// export * from './Empty'
+export * from './Hexo' //