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} + +
+ +
+ + {/* 右下角悬浮 */} +
+
+ + {floatSlot} +
+
+ +
+ +
) +} + +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} +
+ +
+
+ + + + {post.category} + + + | + + {post.type[0] !== 'Page' && (<> + + + {date} + + + )} + +
+ | + 次访问 +
+
+
+
+
+
+ ) + + 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}: +
+
+ {tags.map(tag => { + return ( +
+ +
+ ) + })} +
+
+
+ ) +} 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 ( +
+
+ +
+ + + {post.title} + + + + + + {(!showPreview || showSummary) &&

+ {post.summary} +

} + + {showPreview && post?.blockMap &&
+ +
} + +
+ + + + {post.category} + + +
+
{post.tagItems.map(tag => ())}
+
+
+ +
+ + {CONFIG_HEXO.POST_LIST_COVER && post?.page_cover && ( + + + {/* eslint-disable-next-line @next/next/no-img-element */} + {/* {post.title} */} + {post.title} + + + )} +
+
+ + ) +} + +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} +
+ {children} +
+
+} +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 <> +
+ {Object.keys(categories).map(category => { + const selected = currentCategory === category + return + +
{category}({categories[category]})
+
+ + })} +
+ +} + +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 ( + + +
+ +
{post.title}
+
+
+ + ) + })} + +} +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 +
+
{BLOG.TITLE}
+
+ +} +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 ( +
+
+
+ + {slot} +
+
+ + {/* 背景蒙版 */} +
+
+ ) +} + +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.AUTHOR} +
+
{BLOG.TITLE}
+ +
+ + + + + +
+ 统计 +
+
+
+
文章数:
{postCount}
+
+
+
访问量:
+
+
+
访客数:
+
+
+ + +
+ 分类 +
+ +
+ + + + + + +
+} 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 ( +
+
标签
+
+ { + tags.map(tag => { + const selected = tag.name === currentTag + 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 && ( +
+
+
{locale.COMMON.CATEGORY}
+ + + {locale.COMMON.MORE} + + +
+ +
+ ) } + + { tags && ( +
+
+
{locale.COMMON.TAGS}
+ + + {locale.COMMON.MORE} + + +
+
+ +
+
+ ) } + + + return (
+ + + {/* 导航栏 */} + + +
) +} + +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' //