fukasawa theme

This commit is contained in:
tangly1024.com
2023-07-04 17:42:09 +08:00
parent c4f4156f7f
commit 900d1c847b
18 changed files with 245 additions and 279 deletions

View File

@@ -1,9 +1,10 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import React from 'react'
import React, { useEffect } from 'react'
import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { isBrowser } from '@/lib/utils'
const ArchiveIndex = props => {
const { siteInfo } = props
@@ -12,6 +13,20 @@ const ArchiveIndex = props => {
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
useEffect(() => {
if (isBrowser()) {
const anchor = window.location.hash
if (anchor) {
setTimeout(() => {
const anchorElement = document.getElementById(anchor.substring(1))
if (anchorElement) {
anchorElement.scrollIntoView({ block: 'start', behavior: 'smooth' })
}
}, 300)
}
}
}, [])
const meta = {
title: `${locale.NAV.ARCHIVE} | ${siteInfo?.title}`,
description: siteInfo?.description,

View File

@@ -1,4 +1,5 @@
import BLOG, { LINK } from '@/blog.config'
import BLOG from '@/blog.config'
import Link from 'next/link'
/**
* 按照日期将文章分组
@@ -20,17 +21,12 @@ export default function BlogListGroupByDate({ archiveTitle, archivePosts }) {
>
<div id={post?.publishTime}>
<span className="text-gray-400">
{post.date?.start_date}
{post?.publishTime}
</span>{' '}
&nbsp;
<LINK
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">
{post.title}
</LINK>
</Link>
</div>
</li>
))}

View File

@@ -13,10 +13,10 @@ export const Nav = (props) => {
const { locale } = useGlobal()
let links = [
{ icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_EXAMPLE.MENU_SEARCH },
{ icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_EXAMPLE.MENU_ARCHIVE },
{ icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_EXAMPLE.MENU_CATEGORY },
{ icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_EXAMPLE.MENU_TAG }
{ id: 1, icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_EXAMPLE.MENU_SEARCH },
{ id: 2, icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_EXAMPLE.MENU_ARCHIVE },
{ id: 3, icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_EXAMPLE.MENU_CATEGORY },
{ id: 4, icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_EXAMPLE.MENU_TAG }
]
if (customNav) {

View File

@@ -40,6 +40,7 @@ const LayoutBase = props => {
return (
<div id='theme-example' className='dark:text-gray-300 bg-white dark:bg-black'>
{/* 网页SEO信息 */}
<CommonHead meta={meta} />
{/* 页头 */}
@@ -97,6 +98,7 @@ const LayoutIndex = props => {
*/
const LayoutPostList = props => {
const { category, tag } = props
// 顶部如果是按照分类或标签查看文章列表,列表顶部嵌入一个横幅
let slotTop = null
if (category) {
slotTop = <div className='pb-12'><i className="mr-1 fas fa-folder-open" />{category}</div>

View File

@@ -1,7 +0,0 @@
import LayoutBase from './LayoutBase'
export const Layout404 = props => {
return <LayoutBase {...props}>404</LayoutBase>
}
export default Layout404

View File

@@ -1,32 +0,0 @@
import { useEffect } from 'react'
import BlogArchiveItem from './components/BlogPostArchive'
import LayoutBase from './LayoutBase'
export const LayoutArchive = (props) => {
const { archivePosts } = props
useEffect(() => {
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}>
<div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 shadow-md min-h-full">
{Object.keys(archivePosts).map(archiveTitle => (
<BlogArchiveItem
key={archiveTitle}
posts={archivePosts[archiveTitle]}
archiveTitle={archiveTitle}
/>
))}
</div>
</LayoutBase>
}
export default LayoutArchive

View File

@@ -1,58 +0,0 @@
import CommonHead from '@/components/CommonHead'
import TopNav from './components/TopNav'
import AsideLeft from './components/AsideLeft'
import Live2D from '@/components/Live2D'
import BLOG from '@/blog.config'
import { isBrowser, loadExternalResource } from '@/lib/utils'
import { useGlobal } from '@/lib/global'
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
* @param children
* @param layout
* @param tags
* @param meta
* @param post
* @param currentSearch
* @param currentCategory
* @param currentTag
* @param categories
* @returns {JSX.Element}
* @constructor
*/
const LayoutBase = (props) => {
const { children, headerSlot, meta } = props
const leftAreaSlot = <Live2D/>
if (isBrowser()) {
loadExternalResource('/css/theme-fukasawa.css', 'css')
}
const { onLoading } = useGlobal()
const LoadingCover = <div id='cover-loading' className={`${onLoading ? 'z-50 opacity-50' : '-z-10 opacity-0'} pointer-events-none transition-all duration-300`}>
<div className='w-full h-screen flex justify-center items-center'>
<i className="fa-solid fa-spinner text-2xl text-black dark:text-white animate-spin"> </i>
</div>
</div>
return (<div id='theme-fukasawa' >
<CommonHead meta={meta} />
<TopNav {...props}/>
<div className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + ' flex'}>
<AsideLeft {...props} slot={leftAreaSlot}/>
<main id='wrapper' className='relative flex w-full py-8 justify-center bg-day dark:bg-night'>
<div id='container-inner' className='2xl:max-w-6xl md:max-w-4xl w-full relative z-10'>
<div> {headerSlot} </div>
<div> {onLoading ? LoadingCover : children} </div>
</div>
</main>
</div>
</div>)
}
export default LayoutBase

View File

@@ -1,12 +0,0 @@
import BLOG from '@/blog.config'
import BlogListPage from './components/BlogListPage'
import BlogListScroll from './components/BlogListScroll'
import LayoutBase from './LayoutBase'
export const LayoutCategory = props => {
return <LayoutBase {...props}>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
</LayoutBase>
}
export default LayoutCategory

View File

@@ -1,35 +0,0 @@
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import LayoutBase from './LayoutBase'
export const LayoutCategoryIndex = (props) => {
const { locale } = useGlobal()
const { categoryOptions } = props
return (
<LayoutBase {...props}>
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
<div className='dark:text-gray-200 mb-5'>
<i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}:
</div>
<div id='category-list' className='duration-200 flex flex-wrap'>
{categoryOptions?.map(category => {
return (
<Link
key={category.name}
href={`/category/${category.name}`}
passHref
legacyBehavior>
<div
className={'hover:text-black dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600 px-5 cursor-pointer py-2 hover:bg-gray-100'}>
<i className='mr-4 fas fa-folder' />{category.name}({category.count})
</div>
</Link>
)
})}
</div>
</div>
</LayoutBase>
)
}
export default LayoutCategoryIndex

View File

@@ -1,12 +0,0 @@
import BLOG from '@/blog.config'
import BlogListPage from './components/BlogListPage'
import BlogListScroll from './components/BlogListScroll'
import LayoutBase from './LayoutBase'
export const LayoutIndex = (props) => {
return <LayoutBase {...props}>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
</LayoutBase>
}
export default LayoutIndex

View File

@@ -1,12 +0,0 @@
import BlogListPage from './components/BlogListPage'
import LayoutBase from './LayoutBase'
export const LayoutPage = (props) => {
return <LayoutBase {...props}>
<BlogListPage page={props.page} posts={props.posts} postCount={props.postCount}/>
</LayoutBase>
}
export default LayoutPage

View File

@@ -1,32 +0,0 @@
import LayoutBase from './LayoutBase'
import BLOG from '@/blog.config'
import BlogListPage from './components/BlogListPage'
import BlogListScroll from './components/BlogListScroll'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import Mark from 'mark.js'
import { isBrowser } from '@/lib/utils'
export const LayoutSearch = (props) => {
const { keyword } = props
const router = useRouter()
const currentSearch = keyword || router?.query?.s
useEffect(() => {
setTimeout(() => {
const container = isBrowser() && document.getElementById('posts-wrapper')
if (container && container.innerHTML) {
const re = new RegExp(currentSearch, 'gim')
const instance = new Mark(container)
instance.markRegExp(re, {
element: 'span',
className: 'text-red-500 border-b border-dashed'
})
}
}, 100)
})
return <LayoutBase {...props} currentSearch={currentSearch}>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
</LayoutBase>
}
export default LayoutSearch

View File

@@ -1,15 +0,0 @@
import ArticleDetail from './components/ArticleDetail'
import LayoutBase from './LayoutBase'
import { ArticleLock } from './components/ArticleLock'
export const LayoutSlug = (props) => {
const { lock, validPassword } = props
return (
<LayoutBase {...props} >
{!lock && <ArticleDetail {...props} />}
{lock && <ArticleLock validPassword={validPassword} />}
</LayoutBase>
)
}
export default LayoutSlug

View File

@@ -1,12 +0,0 @@
import BLOG from '@/blog.config'
import BlogListPage from './components/BlogListPage'
import BlogListScroll from './components/BlogListScroll'
import LayoutBase from './LayoutBase'
export const LayoutTag = (props) => {
return <LayoutBase {...props}>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
</LayoutBase>
}
export default LayoutTag

View File

@@ -1,24 +0,0 @@
import { useGlobal } from '@/lib/global'
import TagItemMini from './components/TagItemMini'
import LayoutBase from './LayoutBase'
export const LayoutTagIndex = (props) => {
const { locale } = useGlobal()
const { tagOptions } = props
return <LayoutBase {...props} >
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
<div className='dark:text-gray-200 mb-5'><i className='mr-4 fas fa-tag'/>{locale.COMMON.TAGS}:</div>
<div id="tags-list" className="duration-200 flex flex-wrap ml-8">
{tagOptions.map(tag => {
return (
<div key={tag.name} className="p-2">
<TagItemMini key={tag.name} tag={tag} />
</div>
)
})}
</div>
</div>
</LayoutBase>
}
export default LayoutTagIndex

View File

@@ -0,0 +1,11 @@
/**
* 加载过程的遮罩
* @returns
*/
export default function LoadingCover () {
return <div id='cover-loading' className={'z-50 opacity-50 pointer-events-none transition-all duration-300'}>
<div className='w-full h-screen flex justify-center items-center'>
<i className="fa-solid fa-spinner text-2xl text-black dark:text-white animate-spin"> </i>
</div>
</div>
}

View File

@@ -1,14 +1,209 @@
'use client'
import CONFIG_FUKA from './config_fuka'
import LayoutIndex from './LayoutIndex'
import LayoutSearch from './LayoutSearch'
import LayoutArchive from './LayoutArchive'
import LayoutSlug from './LayoutSlug'
import Layout404 from './Layout404'
import LayoutCategory from './LayoutCategory'
import LayoutCategoryIndex from './LayoutCategoryIndex'
import LayoutPage from './LayoutPage'
import LayoutTag from './LayoutTag'
import LayoutTagIndex from './LayoutTagIndex'
import CommonHead from '@/components/CommonHead'
import TopNav from './components/TopNav'
import AsideLeft from './components/AsideLeft'
import Live2D from '@/components/Live2D'
import BLOG from '@/blog.config'
import { isBrowser, loadExternalResource } from '@/lib/utils'
import { useGlobal } from '@/lib/global'
import LoadingCover from './components/LoadingCover'
import BlogListPage from './components/BlogListPage'
import BlogListScroll from './components/BlogListScroll'
import BlogArchiveItem from './components/BlogPostArchive'
import ArticleDetail from './components/ArticleDetail'
import { ArticleLock } from './components/ArticleLock'
import TagItemMini from './components/TagItemMini'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import Mark from 'mark.js'
import Link from 'next/link'
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
* @param children
* @param layout
* @param tags
* @param meta
* @param post
* @param currentSearch
* @param currentCategory
* @param currentTag
* @param categories
* @returns {JSX.Element}
* @constructor
*/
const LayoutBase = (props) => {
const { children, headerSlot, meta } = props
const leftAreaSlot = <Live2D />
const { onLoading } = useGlobal()
if (isBrowser()) {
loadExternalResource('/css/theme-fukasawa.css', 'css')
}
return (
<div id='theme-fukasawa'>
<CommonHead meta={meta} />
<TopNav {...props} />
<div className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + ' flex'}>
{/* 侧边抽屉 */}
<AsideLeft {...props} slot={leftAreaSlot} />
<main id='wrapper' className='relative flex w-full py-8 justify-center bg-day dark:bg-night'>
<div id='container-inner' className='2xl:max-w-6xl md:max-w-4xl w-full relative z-10'>
<div> {headerSlot} </div>
<div> {onLoading ? <LoadingCover /> : children} </div>
</div>
</main>
</div>
</div>)
}
/**
* 首页
* @param {*} props notion数据
* @returns 首页就是一个博客列表
*/
const LayoutIndex = (props) => {
return <LayoutPostList {...props} />
}
/**
* 博客列表
* @param {*} props
*/
const LayoutPostList = (props) => {
return <LayoutBase {...props}>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</LayoutBase>
}
/**
* 文章详情
* @param {*} props
* @returns
*/
const LayoutSlug = (props) => {
const { lock, validPassword } = props
return (
<LayoutBase {...props} >
{lock ? <ArticleLock validPassword={validPassword} /> : <ArticleDetail {...props} />}
</LayoutBase>
)
}
/**
* 搜索页
*/
const LayoutSearch = (props) => {
const { keyword } = props
const router = useRouter()
useEffect(() => {
setTimeout(() => {
const container = isBrowser() && document.getElementById('posts-wrapper')
if (container && container.innerHTML) {
const re = new RegExp(keyword, 'gim')
const instance = new Mark(container)
instance.markRegExp(re, {
element: 'span',
className: 'text-red-500 border-b border-dashed'
})
}
}, 300)
}, [router])
return <LayoutPostList />
}
/**
* 归档页面
*/
const LayoutArchive = (props) => {
const { archivePosts } = props
return <LayoutBase {...props}>
<div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 shadow-md min-h-full">
{Object.keys(archivePosts).map(archiveTitle => (
<BlogArchiveItem
key={archiveTitle}
posts={archivePosts[archiveTitle]}
archiveTitle={archiveTitle}
/>
))}
</div>
</LayoutBase>
}
/**
* 404
* @param {*} props
* @returns
*/
const Layout404 = props => {
return <LayoutBase {...props}>404</LayoutBase>
}
/**
* 分类列表
* @param {*} props
* @returns
*/
const LayoutCategoryIndex = (props) => {
const { locale } = useGlobal()
const { categoryOptions } = props
return (
<LayoutBase {...props}>
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
<div className='dark:text-gray-200 mb-5'>
<i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}:
</div>
<div id='category-list' className='duration-200 flex flex-wrap'>
{categoryOptions?.map(category => {
return (
<Link
key={category.name}
href={`/category/${category.name}`}
passHref
legacyBehavior>
<div
className={'hover:text-black dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600 px-5 cursor-pointer py-2 hover:bg-gray-100'}>
<i className='mr-4 fas fa-folder' />{category.name}({category.count})
</div>
</Link>
)
})}
</div>
</div>
</LayoutBase>
)
}
/**
* 标签列表
* @param {*} props
* @returns
*/
const LayoutTagIndex = (props) => {
const { locale } = useGlobal()
const { tagOptions } = props
return <LayoutBase {...props} >
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
<div className='dark:text-gray-200 mb-5'><i className='mr-4 fas fa-tag' />{locale.COMMON.TAGS}:</div>
<div id="tags-list" className="duration-200 flex flex-wrap ml-8">
{tagOptions.map(tag => {
return (
<div key={tag.name} className="p-2">
<TagItemMini key={tag.name} tag={tag} />
</div>
)
})}
</div>
</div>
</LayoutBase>
}
export {
CONFIG_FUKA as THEME_CONFIG,
@@ -17,9 +212,7 @@ export {
LayoutArchive,
LayoutSlug,
Layout404,
LayoutCategory,
LayoutPostList,
LayoutCategoryIndex,
LayoutPage,
LayoutTag,
LayoutTagIndex
}

View File

@@ -16,7 +16,7 @@ export const getLayoutByTheme = (router) => {
const themeQuery = getQueryParam(router.asPath, 'theme') || BLOG.THEME
const layout = getLayoutNameByPath(router.pathname)
if (themeQuery !== BLOG.THEME) {
return dynamic(() => import(`@/themes/${themeQuery}/${layout}`), { ssr: true })
return dynamic(() => import(`@/themes/${themeQuery}`).then(m => m[layout]), { ssr: true })
} else {
return ThemeComponents[layout]
}