初步完成HEXO主题

This commit is contained in:
tangly1024
2022-01-24 17:19:08 +08:00
parent 952e258ed7
commit 5afa7fa9fd
18 changed files with 369 additions and 56 deletions

View File

@@ -6,6 +6,6 @@ import { Layout404 } from '@/themes'
* @constructor
*/
export default function Custom404 () {
return <Layout404 />
export default function Custom404 (props) {
return <Layout404 {...props}/>
}

View File

@@ -11,7 +11,7 @@ import { LayoutSlug } from '@/themes'
*/
const About = (props) => {
if (!props.post) {
return <Custom404 />
return <Custom404 {...props} />
}
return <LayoutSlug {...props} />
}

View File

@@ -11,7 +11,7 @@ import Custom404 from '@/pages/404'
*/
const Slug = (props) => {
if (!props.post) {
return <Custom404 />
return <Custom404 {...props} />
}
return <LayoutSlug {...props} />
}

View File

@@ -6,7 +6,7 @@ import Custom404 from '@/pages/404'
const Page = (props) => {
if (!props?.meta) {
return <Custom404 />
return <Custom404 {...props} />
}
return <LayoutPage {...props} />
}

View File

@@ -16,7 +16,7 @@ export async function getStaticProps ({ params }) {
latestPosts
} = await getGlobalNotionData({
from,
includePage: true,
includePage: false,
tagsCount: 0
})
const filteredPosts = allPosts.filter(

View File

@@ -1,6 +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 = () => {
return <div>
404 Not found.
</div>
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 (
<LayoutBase {...props} meta={{ title: `${BLOG.TITLE} | 页面找不到啦` }}>
<div className="text-black w-full h-screen text-center justify-center content-center items-center flex flex-col">
<div className="dark:text-gray-200">
<h2 className="inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top">
<FontAwesomeIcon icon={faSpinner} spin={true} className="mr-2" />
404
</h2>
<div className="inline-block text-left h-32 leading-10 items-center">
<h2 className="m-0 p-0">页面无法加载即将返回首页</h2>
</div>
</div>
</div>
</LayoutBase>
)
}

View File

@@ -1,5 +1,64 @@
export const LayoutArchive = ({ posts, tags, categories, postCount }) => {
return <div>
Archive Page
</div>
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 <LayoutBase {...props} meta={meta}>
<Card className='w-full'>
<div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 min-h-full">
{Object.keys(archivePosts).map(archiveTitle => (
<BlogPostArchive
key={archiveTitle}
posts={archivePosts[archiveTitle]}
archiveTitle={archiveTitle}
/>
))}
</div>
</Card>
</LayoutBase>
}

View File

@@ -1,5 +1,17 @@
export const LayoutCategory = ({ tags, posts, category, categories, latestPosts, postCount }) => {
return <div>
Category - {category}
</div>
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 <LayoutBase {...props} meta={meta}>
<BlogPostListScroll posts={posts} tags={tags} currentCategory={category}/>
</LayoutBase>
}

View File

@@ -1,11 +1,43 @@
export const LayoutCategoryIndex = ({
tags,
allPosts,
categories,
postCount,
latestPosts
}) => {
return <div>
CategoryIndex
</div>
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 (
<LayoutBase {...props} meta={meta}>
<Card className="bg-white dark:bg-gray-700 w-full min-h-screen">
<div className="dark:text-gray-200 mb-5 mx-3">
<FontAwesomeIcon icon={faTh} className="mr-4" />
{locale.COMMON.CATEGORY}:
</div>
<div id="category-list" className="duration-200 flex flex-wrap mx-8">
{Object.keys(categories).map(category => {
return (
<Link key={category} href={`/category/${category}`} passHref>
<div
className={
' duration-300 dark:hover:text-white rounded-lg px-5 cursor-pointer py-2 hover:bg-blue-600 hover:text-white'
}
>
<FontAwesomeIcon icon={faFolder} className="mr-4" />
{category}({categories[category]})
</div>
</Link>
)
})}
</div>
</Card>
</LayoutBase>
)
}

View File

@@ -1,5 +1,9 @@
export const LayoutPage = ({ page, posts, tags, meta, categories, postCount, latestPosts }) => {
return <div>
Page - {page}
</div>
import BlogPostListPage from './components/BlogPostListPage'
import LayoutBase from './LayoutBase'
export const LayoutPage = (props) => {
const { page, posts, postCount } = props
return <LayoutBase {...props}>
<BlogPostListPage page={page} posts={posts} postCount={postCount} />
</LayoutBase>
}

View File

@@ -1,11 +1,11 @@
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 = ({
posts,
tags,
categories,
postCount
}) => {
export const LayoutSearch = (props) => {
const { posts } = props
let filteredPosts
const searchKey = getSearchKey()
if (searchKey) {
@@ -18,11 +18,15 @@ export const LayoutSearch = ({
filteredPosts = posts
}
console.log(filteredPosts)
return <div>
Search {searchKey}
</div>
const { locale } = useGlobal()
const meta = {
title: `${searchKey || ''} | ${locale.NAV.SEARCH} | ${BLOG.TITLE} `,
description: BLOG.DESCRIPTION,
type: 'website'
}
return <LayoutBase {...props} meta={meta}>
<BlogPostListPage {...props} posts={filteredPosts}/>
</LayoutBase>
}
function getSearchKey () {

View File

@@ -1,5 +1,19 @@
export const LayoutTag = ({ tags, posts, tag, categories, postCount, latestPosts }) => {
return <div>
Tag - {tag}
</div>
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 <LayoutBase {...props} meta={meta}>
<BlogPostListScroll posts={posts} tags={tags} currentTag={tag}/>
</LayoutBase>
}

View File

@@ -1,5 +1,36 @@
export const LayoutTagIndex = ({ tags, categories, postCount, latestPosts }) => {
return <div>
TagIndex
</div>
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 (
<LayoutBase {...props} meta={meta}>
<Card className='w-full'>
<div className="dark:text-gray-200 mb-5 ml-4">
<FontAwesomeIcon icon={faTag} className="mr-4" />
{locale.COMMON.TAGS}:
</div>
<div id="tags-list" className="duration-200 flex flex-wrap ml-8">
{tags.map(tag => {
return (
<div key={tag.name} className="px-2">
<TagItemMini key={tag.name} tag={tag} />
</div>
)
})}
</div>
</Card>
</LayoutBase>
)
}

View File

@@ -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 <div>
<div className='pt-16 pb-4 text-3xl dark:text-gray-300' id={archiveTitle}>{archiveTitle}</div>
<ul>
{posts.map(post => (
<li key={post.id} className='border-l-2 p-1 text-xs md:text-base items-center hover:scale-x-105 hover:border-gray-500 dark:hover:border-gray-300 dark:border-gray-400 transform duration-500'>
<div id={post?.date?.start_date}><span className='text-gray-400'>{post.date.start_date}</span> &nbsp;
<Link href={`${BLOG.PATH}/article/${post.slug}`} passHref>
<a className='dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600'>{post.title}</a>
</Link>
</div>
</li>
))}
</ul>
</div>
}
}
export default BlogPostArchive

View File

@@ -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 <BlogPostListEmpty currentSearch={currentSearch} />
} else {
return <div id='container' ref={targetRef} className='w-full'>
{/* 文章列表 */}
<div className='flex flex-wrap space-y-1 lg:space-y-4'>
{postsToShow.map(post => (
<BlogPostCard key={post.id} post={post} showSummary={showSummary}/>
))}
</div>
<div>
<div onClick={() => {
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} 😰`} </div>
</div>
</div>
}
}
/**
* 获取从第1页到指定页码的文章
* @param page 第几页
* @param totalPosts 所有文章
* @param postsPerPage 每页文章数量
* @returns {*}
*/
const getPostByPage = function (page, totalPosts, postsPerPage) {
return totalPosts.slice(
0,
postsPerPage * page
)
}
export default BlogPostListScroll

View File

@@ -4,6 +4,9 @@ import Link from 'next/link'
import React from 'react'
const CategoryGroup = ({ currentCategory, categories }) => {
if (!categories) {
return <></>
}
return <>
<div id='category-list' className='dark:border-gray-600 flex flex-wrap font-sans mx-4'>
{Object.keys(categories).map(category => {

View File

@@ -27,7 +27,7 @@ const PaginationNumber = ({ page, totalPage }) => {
} } passHref >
<div
rel='prev'
className={`${currentPage === 1 ? 'invisible' : 'block'} border-white dark:border-gray-700 hover:border-gray-400 dark:hover:border-gray-400 w-6 text-center cursor-pointer duration-200 hover:font-bold`}
className={`${currentPage === 1 ? 'invisible' : 'block'} border-white dark:border-blue-700 hover:border-blue-400 dark:hover:border-blue-400 w-6 text-center cursor-pointer duration-200 hover:font-bold`}
>
<FontAwesomeIcon icon={faAngleLeft}/>
</div>
@@ -39,7 +39,7 @@ const PaginationNumber = ({ page, totalPage }) => {
<Link href={ { pathname: `/page/${currentPage + 1}`, query: router.query.s ? { s: router.query.s } : {} } } passHref>
<div
rel='next'
className={`${+showNext ? 'block' : 'invisible'} border-t-2 border-white dark:border-gray-700 hover:border-gray-400 dark:hover:border-gray-400 w-6 text-center cursor-pointer duration-500 hover:font-bold`}
className={`${+showNext ? 'block' : 'invisible'} border-t-2 border-white dark:border-blue-700 hover:border-blue-400 dark:hover:border-blue-400 w-6 text-center cursor-pointer duration-500 hover:font-bold`}
>
<FontAwesomeIcon icon={faAngleRight}/>
</div>
@@ -50,8 +50,8 @@ const PaginationNumber = ({ page, totalPage }) => {
function getPageElement (page, currentPage) {
return <Link href={page === 1 ? '/' : `/page/${page}`} key={page} passHref>
<a className={(page + '' === currentPage + '' ? 'font-bold bg-blue-500 dark:bg-blue-400 text-white ' : 'border-t-2 duration-500 border-white hover:border-gray-400 ') +
' border-white dark:border-gray-700 dark:hover:border-gray-400 cursor-pointer w-6 text-center font-light hover:font-bold'}>
<a className={(page + '' === currentPage + '' ? 'font-bold bg-blue-500 dark:bg-blue-400 text-white ' : 'border-t-2 duration-500 border-white hover:border-blue-400 ') +
' border-white dark:border-blue-700 dark:hover:border-blue-400 cursor-pointer w-6 text-center font-light hover:font-bold'}>
{page}
</a>
</Link>

View File

@@ -10,6 +10,7 @@ const CONFIG_HEXO = {
MENU_ARCHIVE: true, // 显示归档
MENU_SEARCH: true, // 显示搜索
POST_LIST_COVER: true // 文章封面
POST_LIST_COVER: true, // 文章封面
POST_LIST_SUMMARY: true // 文章摘要
}
export default CONFIG_HEXO