|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import CONFIG from './config' |
|
import { useEffect, useState } from 'react' |
|
import Footer from './components/Footer' |
|
import SideRight from './components/SideRight' |
|
import NavBar from './components/NavBar' |
|
import { useGlobal } from '@/lib/global' |
|
import BlogPostListPage from './components/BlogPostListPage' |
|
import BlogPostListScroll from './components/BlogPostListScroll' |
|
import Hero from './components/Hero' |
|
import { useRouter } from 'next/router' |
|
import SearchNav from './components/SearchNav' |
|
import BlogPostArchive from './components/BlogPostArchive' |
|
import { ArticleLock } from './components/ArticleLock' |
|
import PostHeader from './components/PostHeader' |
|
import Comment from '@/components/Comment' |
|
import NotionPage from '@/components/NotionPage' |
|
import ArticleAdjacent from './components/ArticleAdjacent' |
|
import ArticleCopyright from './components/ArticleCopyright' |
|
import ArticleRecommend from './components/ArticleRecommend' |
|
import ShareBar from '@/components/ShareBar' |
|
import Link from 'next/link' |
|
import CategoryBar from './components/CategoryBar' |
|
import { Transition } from '@headlessui/react' |
|
import { Style } from './style' |
|
import { NoticeBar } from './components/NoticeBar' |
|
import { HashTag } from '@/components/HeroIcons' |
|
import LatestPostsGroup from './components/LatestPostsGroup' |
|
import FloatTocButton from './components/FloatTocButton' |
|
import replaceSearchResult from '@/components/Mark' |
|
import LazyImage from '@/components/LazyImage' |
|
import WWAds from '@/components/WWAds' |
|
import { AdSlot } from '@/components/GoogleAdsense' |
|
import { siteConfig } from '@/lib/config' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const LayoutBase = props => { |
|
const { |
|
children, |
|
slotTop, |
|
className |
|
} = props |
|
|
|
|
|
const { fullWidth } = useGlobal() |
|
const router = useRouter() |
|
|
|
const headerSlot = ( |
|
<header> |
|
{/* 顶部导航 */} |
|
<NavBar {...props} /> |
|
|
|
{/* 通知横幅 */} |
|
{router.route === '/' |
|
? <> |
|
<NoticeBar /> |
|
<Hero {...props} /> |
|
</> |
|
: null} |
|
{fullWidth ? null : <PostHeader {...props} />} |
|
</header> |
|
) |
|
|
|
|
|
const slotRight = (router.route === '/404' || fullWidth) ? null : <SideRight {...props} /> |
|
|
|
const maxWidth = fullWidth ? 'max-w-[96rem] mx-auto' : 'max-w-[86rem]' |
|
|
|
const HEO_HERO_BODY_REVERSE = siteConfig('HEO_HERO_BODY_REVERSE', false, CONFIG) |
|
|
|
return ( |
|
<div |
|
id="theme-heo" |
|
className="bg-[#f7f9fe] dark:bg-[#18171d] h-full min-h-screen flex flex-col" |
|
> |
|
|
|
<Style /> |
|
|
|
{/* 顶部嵌入 导航栏,首页放hero,文章页放文章详情 */} |
|
{headerSlot} |
|
|
|
{/* 主区块 */} |
|
<main |
|
id="wrapper-outer" |
|
className={`flex-grow w-full ${maxWidth} mx-auto relative md:px-5`} |
|
> |
|
<div |
|
id="container-inner" |
|
className={ |
|
`${HEO_HERO_BODY_REVERSE ? 'flex-row-reverse' : ''} w-full mx-auto lg:flex justify-center relative z-10` |
|
} |
|
> |
|
<div className={`w-full h-auto ${className || ''}`}> |
|
{/* 主区上部嵌入 */} |
|
{slotTop} |
|
{children} |
|
</div> |
|
|
|
<div className='lg:px-2'></div> |
|
|
|
<div className="hidden xl:block"> |
|
{/* 主区快右侧 */} |
|
{slotRight} |
|
</div> |
|
</div> |
|
|
|
</main> |
|
|
|
{/* 页脚 */} |
|
<Footer title={siteConfig('TITLE')} /> |
|
</div> |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const LayoutIndex = props => { |
|
return ( |
|
<div id="post-outer-wrapper" className="px-5 md:px-0"> |
|
{/* 文章分类条 */} |
|
<CategoryBar {...props} /> |
|
{siteConfig('POST_LIST_STYLE') === 'page' |
|
? ( |
|
<BlogPostListPage {...props} /> |
|
) |
|
: ( |
|
<BlogPostListScroll {...props} /> |
|
)} |
|
</div> |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
const LayoutPostList = props => { |
|
return ( |
|
<div id="post-outer-wrapper" className="px-5 md:px-0"> |
|
{/* 文章分类条 */} |
|
<CategoryBar {...props} /> |
|
{siteConfig('POST_LIST_STYLE') === 'page' |
|
? ( |
|
<BlogPostListPage {...props} /> |
|
) |
|
: ( |
|
<BlogPostListScroll {...props} /> |
|
)} |
|
</div> |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
const LayoutSearch = props => { |
|
const { keyword } = props |
|
const router = useRouter() |
|
const currentSearch = keyword || router?.query?.s |
|
|
|
useEffect(() => { |
|
|
|
if (currentSearch) { |
|
setTimeout(() => { |
|
replaceSearchResult({ |
|
doms: document.getElementsByClassName('replace'), |
|
search: currentSearch, |
|
target: { |
|
element: 'span', |
|
className: 'text-red-500 border-b border-dashed' |
|
} |
|
}) |
|
}, 100) |
|
} |
|
}, []) |
|
return ( |
|
<div |
|
{...props} |
|
currentSearch={currentSearch} |
|
> |
|
<div id="post-outer-wrapper" className="px-5 md:px-0"> |
|
{!currentSearch |
|
? ( |
|
<SearchNav {...props} /> |
|
) |
|
: ( |
|
<div id="posts-wrapper"> |
|
{siteConfig('POST_LIST_STYLE') === 'page' |
|
? ( |
|
<BlogPostListPage {...props} /> |
|
) |
|
: ( |
|
<BlogPostListScroll {...props} /> |
|
)} |
|
</div> |
|
)} |
|
</div> |
|
</div> |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
const LayoutArchive = props => { |
|
const { archivePosts } = props |
|
|
|
|
|
|
|
return ( |
|
<div className="p-5 rounded-xl border dark:border-gray-600 max-w-6xl w-full bg-white dark:bg-[#1e1e1e]"> |
|
{/* 文章分类条 */} |
|
<CategoryBar {...props} border={false} /> |
|
|
|
<div className="px-3"> |
|
{Object.keys(archivePosts).map(archiveTitle => ( |
|
<BlogPostArchive |
|
key={archiveTitle} |
|
posts={archivePosts[archiveTitle]} |
|
archiveTitle={archiveTitle} |
|
/> |
|
))} |
|
</div> |
|
</div> |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
const LayoutSlug = props => { |
|
const { post, lock, validPassword } = props |
|
const { locale, fullWidth } = useGlobal() |
|
|
|
const [hasCode, setHasCode] = useState(false) |
|
|
|
useEffect(() => { |
|
const hasCode = document.querySelectorAll('[class^="language-"]').length > 0 |
|
setHasCode(hasCode) |
|
}, []) |
|
|
|
const commentEnable = siteConfig('COMMENT_TWIKOO_ENV_ID') || siteConfig('COMMENT_WALINE_SERVER_URL') || siteConfig('COMMENT_VALINE_APP_ID') || |
|
siteConfig('COMMENT_GISCUS_REPO') || siteConfig('COMMENT_CUSDIS_APP_ID') || siteConfig('COMMENT_UTTERRANCES_REPO') || |
|
siteConfig('COMMENT_GITALK_CLIENT_ID') || siteConfig('COMMENT_WEBMENTION_ENABLE') |
|
|
|
return ( |
|
<> |
|
<div className={`w-full ${fullWidth ? '' : 'xl:max-w-5xl'} ${hasCode ? 'xl:w-[73.15vw]' : ''} lg:hover:shadow lg:border rounded-2xl lg:px-2 lg:py-4 bg-white dark:bg-[#18171d] dark:border-gray-600 article`}> |
|
{lock && <ArticleLock validPassword={validPassword} />} |
|
|
|
{!lock && ( |
|
<div |
|
id="article-wrapper" |
|
className="overflow-x-auto flex-grow mx-auto md:w-full md:px-5 " |
|
> |
|
<article |
|
data-aos="fade-up" |
|
data-aos-duration="300" |
|
data-aos-once="false" |
|
data-aos-anchor-placement="top-bottom" |
|
itemScope |
|
itemType="https://schema.org/Movie" |
|
className="subpixel-antialiased overflow-y-hidden" |
|
> |
|
{/* Notion文章主体 */} |
|
<section className="px-5 justify-center mx-auto"> |
|
<WWAds orientation="horizontal" className="w-full" /> |
|
{post && <NotionPage post={post} />} |
|
<WWAds orientation="horizontal" className="w-full" /> |
|
</section> |
|
|
|
{/* 分享 */} |
|
<ShareBar post={post} /> |
|
{post?.type === 'Post' && ( |
|
<div className="px-5"> |
|
{/* 版权 */} |
|
<ArticleCopyright {...props} /> |
|
{/* 文章推荐 */} |
|
<ArticleRecommend {...props} /> |
|
{/* 上一篇\下一篇文章 */} |
|
<ArticleAdjacent {...props} /> |
|
</div> |
|
)} |
|
</article> |
|
|
|
{fullWidth |
|
? null |
|
: <div className={`${commentEnable && post ? '' : 'hidden'}`}> |
|
<hr className="my-4 border-dashed" /> |
|
{/* 评论区上方广告 */} |
|
<div className="py-2"> |
|
<AdSlot /> |
|
</div> |
|
{/* 评论互动 */} |
|
<div className="duration-200 overflow-x-auto px-5"> |
|
<div className="text-2xl dark:text-white"> |
|
<i className="fas fa-comment mr-1" /> |
|
{locale.COMMON.COMMENTS} |
|
</div> |
|
<Comment frontMatter={post} className="" /> |
|
</div> |
|
</div>} |
|
</div> |
|
)} |
|
</div> |
|
<FloatTocButton {...props} /> |
|
</> |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
const Layout404 = props => { |
|
|
|
const { onLoading, fullWidth } = useGlobal() |
|
return ( |
|
<> |
|
{/* 主区块 */} |
|
<main |
|
id="wrapper-outer" |
|
className={`flex-grow ${fullWidth ? '' : 'max-w-4xl'} w-screen mx-auto px-5`} |
|
> |
|
<div id="error-wrapper" className={'w-full mx-auto justify-center'}> |
|
<Transition |
|
show={!onLoading} |
|
appear={true} |
|
enter="transition ease-in-out duration-700 transform order-first" |
|
enterFrom="opacity-0 translate-y-16" |
|
enterTo="opacity-100" |
|
leave="transition ease-in-out duration-300 transform" |
|
leaveFrom="opacity-100 translate-y-0" |
|
leaveTo="opacity-0 -translate-y-16" |
|
unmount={false} |
|
> |
|
{/* 404卡牌 */} |
|
<div className="error-content flex flex-col md:flex-row w-full mt-12 h-[30rem] md:h-96 justify-center items-center bg-white dark:bg-[#1B1C20] border dark:border-gray-800 rounded-3xl"> |
|
{/* 左侧动图 */} |
|
<LazyImage |
|
className="error-img h-60 md:h-full p-4" |
|
src={'https://bu.dusays.com/2023/03/03/6401a7906aa4a.gif'} |
|
></LazyImage> |
|
|
|
{/* 右侧文字 */} |
|
<div className="error-info flex-1 flex flex-col justify-center items-center space-y-4"> |
|
<h1 className="error-title font-extrabold md:text-9xl text-7xl dark:text-white"> |
|
404 |
|
</h1> |
|
<div className='dark:text-white'>请尝试站内搜索寻找文章</div> |
|
<Link href="/"> |
|
<button className="bg-blue-500 py-2 px-4 text-white shadow rounded-lg hover:bg-blue-600 hover:shadow-md duration-200 transition-all"> |
|
回到主页 |
|
</button> |
|
</Link> |
|
</div> |
|
</div> |
|
|
|
{/* 404页面底部显示最新文章 */} |
|
<div className="mt-12"> |
|
<LatestPostsGroup {...props} /> |
|
</div> |
|
</Transition> |
|
</div> |
|
</main> |
|
</> |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
const LayoutCategoryIndex = props => { |
|
const { categoryOptions } = props |
|
const { locale } = useGlobal() |
|
|
|
return ( |
|
<div id="category-outer-wrapper" className="mt-8 px-5 md:px-0"> |
|
<div className="text-4xl font-extrabold dark:text-gray-200 mb-5"> |
|
{locale.COMMON.CATEGORY} |
|
</div> |
|
<div |
|
id="category-list" |
|
className="duration-200 flex flex-wrap m-10 justify-center" |
|
> |
|
{categoryOptions?.map(category => { |
|
return ( |
|
<Link |
|
key={category.name} |
|
href={`/category/${category.name}`} |
|
passHref |
|
legacyBehavior |
|
> |
|
<div |
|
className={ |
|
'group mr-5 mb-5 flex flex-nowrap items-center border bg-white text-2xl rounded-xl dark:hover:text-white px-4 cursor-pointer py-3 hover:text-white hover:bg-indigo-600 transition-all hover:scale-110 duration-150' |
|
} |
|
> |
|
<HashTag className={'w-5 h-5 stroke-gray-500 stroke-2'} /> |
|
{category.name} |
|
<div className="bg-[#f1f3f8] ml-1 px-2 rounded-lg group-hover:text-indigo-600 "> |
|
{category.count} |
|
</div> |
|
</div> |
|
</Link> |
|
) |
|
})} |
|
</div> |
|
</div> |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
const LayoutTagIndex = props => { |
|
const { tagOptions } = props |
|
const { locale } = useGlobal() |
|
|
|
return ( |
|
<div id="tag-outer-wrapper" className="px-5 mt-8 md:px-0"> |
|
<div className="text-4xl font-extrabold dark:text-gray-200 mb-5"> |
|
{locale.COMMON.TAGS} |
|
</div> |
|
<div |
|
id="tag-list" |
|
className="duration-200 flex flex-wrap space-x-5 space-y-5 m-10 justify-center" |
|
> |
|
{tagOptions.map(tag => { |
|
return ( |
|
<Link |
|
key={tag.name} |
|
href={`/tag/${tag.name}`} |
|
passHref |
|
legacyBehavior |
|
> |
|
<div |
|
className={ |
|
'group flex flex-nowrap items-center border bg-white text-2xl rounded-xl dark:hover:text-white px-4 cursor-pointer py-3 hover:text-white hover:bg-indigo-600 transition-all hover:scale-110 duration-150' |
|
} |
|
> |
|
<HashTag className={'w-5 h-5 stroke-gray-500 stroke-2'} /> |
|
{tag.name} |
|
<div className="bg-[#f1f3f8] ml-1 px-2 rounded-lg group-hover:text-indigo-600 "> |
|
{tag.count} |
|
</div> |
|
</div> |
|
</Link> |
|
) |
|
})} |
|
</div> |
|
</div> |
|
) |
|
} |
|
|
|
export { |
|
CONFIG as THEME_CONFIG, |
|
LayoutBase, |
|
LayoutIndex, |
|
LayoutSearch, |
|
LayoutArchive, |
|
LayoutSlug, |
|
Layout404, |
|
LayoutCategoryIndex, |
|
LayoutPostList, |
|
LayoutTagIndex |
|
} |
|
|