'use client' import CONFIG from './config' import CommonHead from '@/components/CommonHead' import TopNav from './components/TopNav' import AsideLeft from './components/AsideLeft' import BLOG from '@/blog.config' import { isBrowser, loadExternalResource } from '@/lib/utils' import { useGlobal } from '@/lib/global' 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 { createContext, useContext, useEffect, useState } from 'react' import Link from 'next/link' import { Transition } from '@headlessui/react' import dynamic from 'next/dynamic' const Live2D = dynamic(() => import('@/components/Live2D')) const Mark = dynamic(() => import('mark.js')) // 主题全局状态 const ThemeGlobalFukasawa = createContext() export const useFukasawaGlobal = () => useContext(ThemeGlobalFukasawa) /** * 基础布局 采用左右两侧布局,移动端使用顶部导航栏 * @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 = const { onLoading } = useGlobal() // 侧边栏折叠从 本地存储中获取 open 状态的初始值 const [isCollapsed, setIsCollapse] = useState(() => { if (typeof window !== 'undefined') { return localStorage.getItem('fukasawa-sidebar-collapse') === 'true' || CONFIG.SIDEBAR_COLLAPSE_SATUS_DEFAULT } return CONFIG.SIDEBAR_COLLAPSE_SATUS_DEFAULT }) // 在组件卸载时保存 open 状态到本地存储中 useEffect(() => { if (isBrowser()) { localStorage.setItem('fukasawa-sidebar-collapse', isCollapsed) } }, [isCollapsed]) // 增加一个状态以触发 Transition 组件的动画 const [showTransition, setShowTransition] = useState(true) useEffect(() => { // 当 location 或 children 发生变化时,触发动画 setShowTransition(false) setTimeout(() => setShowTransition(true), 5) }, [onLoading]) if (isBrowser()) { loadExternalResource('/css/theme-fukasawa.css', 'css') } return (
{/* 侧边抽屉 */}
{headerSlot}
{children}
) } /** * 首页 * @param {*} props notion数据 * @returns 首页就是一个博客列表 */ const LayoutIndex = (props) => { return } /** * 博客列表 * @param {*} props */ const LayoutPostList = (props) => { return {BLOG.POST_LIST_STYLE === 'page' ? : } } /** * 文章详情 * @param {*} props * @returns */ const LayoutSlug = (props) => { const { lock, validPassword } = props return ( {lock ? : } ) } /** * 搜索页 */ 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 } /** * 归档页面 */ const LayoutArchive = (props) => { const { archivePosts } = props return
{Object.keys(archivePosts).map(archiveTitle => ( ))}
} /** * 404 * @param {*} props * @returns */ const Layout404 = props => { return 404 } /** * 分类列表 * @param {*} props * @returns */ const LayoutCategoryIndex = (props) => { const { locale } = useGlobal() const { categoryOptions } = props return (
{locale.COMMON.CATEGORY}:
{categoryOptions?.map(category => { return (
{category.name}({category.count})
) })}
) } /** * 标签列表 * @param {*} props * @returns */ const LayoutTagIndex = (props) => { const { locale } = useGlobal() const { tagOptions } = props return
{locale.COMMON.TAGS}:
{tagOptions.map(tag => { return (
) })}
} export { CONFIG as THEME_CONFIG, LayoutIndex, LayoutSearch, LayoutArchive, LayoutSlug, Layout404, LayoutPostList, LayoutCategoryIndex, LayoutTagIndex }