mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-15 15:09:25 +00:00
feature:
样式调整; 顶部菜单调整
This commit is contained in:
192
components/ArticleDetail.js
Normal file
192
components/ArticleDetail.js
Normal file
@@ -0,0 +1,192 @@
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
import { useRouter } from 'next/router'
|
||||
import Progress from '@/components/Progress'
|
||||
import TagItem from '@/components/TagItem'
|
||||
import formatDate from '@/lib/formatDate'
|
||||
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
import Comment from '@/components/Comment'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
|
||||
import 'prismjs'
|
||||
import 'prismjs/components/prism-bash'
|
||||
import 'prismjs/components/prism-markup'
|
||||
import 'prismjs/components/prism-python'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/components/prism-typescript'
|
||||
import RecommendPosts from '@/components/RecommendPosts'
|
||||
import TocDrawer from '@/components/TocDrawer'
|
||||
import TocDrawerButton from '@/components/TocDrawerButton'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faEye, faFolderOpen } from '@fortawesome/free-solid-svg-icons'
|
||||
import BlogAround from '@/components/BlogAround'
|
||||
import { useRef } from 'react'
|
||||
|
||||
const mapPageUrl = id => {
|
||||
return 'https://www.notion.so/' + id.replace(/-/g, '')
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, next }) {
|
||||
const targetRef = useRef(null)
|
||||
const drawerRight = useRef(null)
|
||||
const url = BLOG.link + useRouter().asPath
|
||||
const { locale } = useGlobal()
|
||||
const date = formatDate(post?.date?.start_date || post.createdTime, BLOG.lang)
|
||||
return (
|
||||
<>
|
||||
<Progress targetRef={targetRef} />
|
||||
|
||||
<div id="article-wrapper" ref={targetRef} className="flex-grow">
|
||||
<div className="max-w-5xl mx-auto mt-16 xl:mt-32 w-screen md:w-full">
|
||||
<article
|
||||
itemScope
|
||||
itemType="https://schema.org/Movie"
|
||||
className="duration-300 hover:shadow-2xl pt-10 animate__fadeIn animate__animated subpixel-antialiased lg:pt-32 lg:px-52 px-5 py-2 dark:border-gray-700 bg-white dark:bg-gray-800"
|
||||
>
|
||||
{post.type && !post.type.includes('Page') && (
|
||||
<>
|
||||
<header className="w-full h-60 lg:h-96 transform duration-200 md:flex-shrink-0 overflow-hidden">
|
||||
<Image
|
||||
src={
|
||||
post.page_cover && post.page_cover.length > 1
|
||||
? post.page_cover
|
||||
: BLOG.defaultImgCover
|
||||
}
|
||||
loading="eager"
|
||||
objectFit="cover"
|
||||
layout="fill"
|
||||
alt={post.title}
|
||||
/>
|
||||
</header>
|
||||
</>
|
||||
)}
|
||||
{/* 文章Title */}
|
||||
<h2 className="font-bold text-2xl text-black dark:text-white font-serif pt-10">
|
||||
{' '}
|
||||
{post.title}
|
||||
</h2>
|
||||
<hr className="mt-4" />
|
||||
<section className="flex-nowrap flex mt-1 dark:text-white font-light">
|
||||
<Link href={`/category/${post.category}`} passHref>
|
||||
<a className="cursor-pointer text-md py-2 ml-1 mr-3 text-gray-500 dark:text-gray-300 hover:text-black dark:hover:text-white">
|
||||
<FontAwesomeIcon icon={faFolderOpen} className="mr-1" />
|
||||
{post.category}
|
||||
</a>
|
||||
</Link>
|
||||
{post.type[0] !== 'Page' && (
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
>
|
||||
<a className="pl-1 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 text-gray-400 dark:text-gray-400 leading-10">
|
||||
{date}
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<div id="busuanzi_container_page_pv" className="hidden">
|
||||
<FontAwesomeIcon
|
||||
icon={faEye}
|
||||
className="text-gray-500 dark:text-gray-400 mt-3 ml-5"
|
||||
/>
|
||||
|
||||
<span
|
||||
id="busuanzi_value_page_pv"
|
||||
className="text-gray-500 dark:text-gray-400 leading-6"
|
||||
></span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="px-1 py-2 my-1 text-sm font-light bg-gray-50 text-gray-600 dark:bg-gray-700 dark:text-gray-400">
|
||||
{post.summary}
|
||||
</section>
|
||||
|
||||
{/* Notion文章主体 */}
|
||||
{blockMap && (
|
||||
<NotionRenderer
|
||||
className={`${BLOG.font}`}
|
||||
recordMap={blockMap}
|
||||
mapPageUrl={mapPageUrl}
|
||||
components={{
|
||||
equation: Equation,
|
||||
code: Code,
|
||||
collectionRow: CollectionRow,
|
||||
collection: Collection
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 推荐文章 */}
|
||||
<RecommendPosts currentPost={post} recommendPosts={recommendPosts} />
|
||||
|
||||
{/* 版权声明 */}
|
||||
<section className="dark:text-gray-300 mt-6">
|
||||
<div className="text-2xl mb-2">版权声明</div>
|
||||
<ul className="text-sm dark:bg-gray-900 bg-gray-100 p-5 leading-8 border-l-4 border-red-500">
|
||||
<li>
|
||||
本文作者:{' '}
|
||||
<Link href="/about">
|
||||
<a className="hover:underline">{BLOG.author}</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
本文链接:{' '}
|
||||
<a className="hover:underline" href={url}>
|
||||
{url}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
本博客所有文章除特别声明外,均采用 BY-NC-SA
|
||||
许可协议。转载请注明出处!
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* 标签列表 */}
|
||||
<section className="md:flex md:justify-between">
|
||||
{post.tagItems && (
|
||||
<div className="flex flex-nowrap leading-8 p-1 py-4 overflow-x-auto">
|
||||
<div className="hidden md:block dark:text-gray-300">
|
||||
{locale.COMMON.TAGS}:
|
||||
</div>
|
||||
{post.tagItems.map(tag => (
|
||||
<TagItem key={tag.name} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<ShareBar post={post} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<BlogAround prev={prev} next={next} />
|
||||
</article>
|
||||
|
||||
{/* 评论互动 */}
|
||||
<div className="my-10 w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-700">
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 悬浮目录按钮 */}
|
||||
<div className="block lg:hidden">
|
||||
<TocDrawerButton
|
||||
onClick={() => {
|
||||
drawerRight.current.handleSwitchVisible()
|
||||
}}
|
||||
/>
|
||||
{/* 目录侧边栏 */}
|
||||
<TocDrawer post={post} cRef={drawerRight} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,9 @@ import { faAngleDoubleLeft, faAngleDoubleRight } from '@fortawesome/free-solid-s
|
||||
* @returns
|
||||
*/
|
||||
export default function BlogAround ({ prev, next }) {
|
||||
if (!prev || !next) {
|
||||
return <></>
|
||||
}
|
||||
return <section className='text-gray-800 mb-8 lg:mb-32 border-t dark:text-gray-300 px-5 flex flex-wrap lg:flex-nowrap lg:space-x-10 justify-between py-2'>
|
||||
<Link href={`/article/${prev.slug}`} passHref>
|
||||
<a className='text-sm py-3 text-blue-500 hover:underline cursor-pointer'>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
const BlogPostListEmpty = ({ currentSearch }) => {
|
||||
return <div className='min-h-screen flex justify-center mx-auto'>
|
||||
<div className='align-middle text-center my-auto'>
|
||||
<div className='text-center my-auto'>
|
||||
<p className='text-gray-500 dark:text-gray-300'>没有找到文章 {(currentSearch && <div>{currentSearch}</div>)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { faInfo } from '@fortawesome/free-solid-svg-icons'
|
||||
*/
|
||||
const ContactButton = () => {
|
||||
return (
|
||||
<Link href='/article/about'>
|
||||
<Link href='/about'>
|
||||
<a className={'fixed right-10 bottom-40 animate__fadeInRight animate__animated animate__faster'}>
|
||||
<span
|
||||
className='dark:bg-black bg-white px-5 py-3 cursor-pointer shadow-card text-xl hover:bg-blue-500 transform duration-200 hover:text-white hover:shadow'>
|
||||
|
||||
28
components/FloatDarkModeButton.js
Normal file
28
components/FloatDarkModeButton.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import DarkModeButton from './DarkModeButton'
|
||||
|
||||
let windowTop = 0
|
||||
export default function FloatDarkModeButton () {
|
||||
const [show, switchShow] = useState(false)
|
||||
const scrollListener = () => {
|
||||
const scrollY = window.pageYOffset
|
||||
const shouldShow = scrollY > 100 && scrollY < windowTop
|
||||
windowTop = scrollY
|
||||
if (shouldShow !== show) {
|
||||
switchShow(shouldShow)
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={(show ? 'animate__fadeInRight ' : 'hidden lg:block') + ' animate__animated animate__faster shadow-card fixed right-5 bottom-36 py-1.5 px-2.5 rounded-full' +
|
||||
' text-black shadow-card dark:border-gray-500 glassmorphism dark:bg-gray-700 dark:text-gray-200'}
|
||||
>
|
||||
<DarkModeButton />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import Router from 'next/router'
|
||||
|
||||
const InfoCard = () => {
|
||||
return <>
|
||||
<div className='flex flex-col align-middle justify-center cursor-pointer' onClick={ () => { Router.push('/') }}>
|
||||
<div className='flex flex-col items-center justify-center cursor-pointer' onClick={ () => { Router.push('/') }}>
|
||||
<div className='hover:rotate-45 hover:scale-125 transform duration-200 mx-auto'>
|
||||
<Image
|
||||
alt={BLOG.title}
|
||||
|
||||
@@ -36,13 +36,13 @@ const JumpToTopButton = ({ targetRef, showPercent = true }) => {
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
return (<div id='jump-to-top' className='right-5 fixed flex bottom-36 duration-500 z-20'>
|
||||
return (<div id='jump-to-top' className='right-5 fixed flex bottom-52 duration-500 z-20'>
|
||||
<div onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
||||
className={(show ? '' : 'hidden') + ' animate__fadeInRight rounded-full glassmorphism p-2 cursor-pointer animate__animated animate__faster shadow-card'}>
|
||||
className={(show ? '' : 'hidden') + ' animate__fadeInRight animate__animated animate__faster shadow-card rounded-full glassmorphism p-2 cursor-pointer '}>
|
||||
<div className='text-center'>
|
||||
<div className='w-10 dark:text-gray-100 transform hover:scale-125 duration-200' title={locale.POST.TOP} >
|
||||
<FontAwesomeIcon icon={faArrowUp} />
|
||||
</div>
|
||||
<div className='w-10 dark:text-gray-200 transform hover:scale-125 duration-200' title={locale.POST.TOP} >
|
||||
<FontAwesomeIcon icon={faArrowUp} />
|
||||
</div>
|
||||
{showPercent && (<div className='w-10 text-xs dark:text-gray-200'>{percent}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ const MenuButtonGroup = ({ allowCollapse = false }) => {
|
||||
const links = [
|
||||
{ id: 0, icon: faHome, name: locale.NAV.INDEX, to: '/' || '/', show: true },
|
||||
{ id: 1, icon: faArchive, name: locale.NAV.ARCHIVE, to: '/archive', show: BLOG.showArchive },
|
||||
{ id: 2, icon: faInfoCircle, name: locale.NAV.ABOUT, to: '/article/about', show: BLOG.showAbout }
|
||||
{ id: 2, icon: faInfoCircle, name: locale.NAV.ABOUT, to: '/about', show: BLOG.showAbout }
|
||||
// { id: 7, icon: 'faGithub', name: 'Github', to: 'https://github.com/tangly1024', show: true },
|
||||
// { id: 5, icon: 'faWeibo', name: '微博', to: 'https://weibo.com/tangly1024', show: true },
|
||||
// { id: 4, icon: 'faEnvelope', name: locale.NAV.MAIL, to: 'mailto:tlyong1992@hotmail.com', show: true }
|
||||
@@ -28,7 +28,7 @@ const MenuButtonGroup = ({ allowCollapse = false }) => {
|
||||
if (link.show) {
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
return <Link key={link.id + link.icon} title={link.to} href={link.to} >
|
||||
<a className={'py-2 px-5 text-lg hover:bg-blue-400 hover:text-white hover:shadow-lg cursor-pointer duration-100 font-light flex flex-nowrap align-middle ' +
|
||||
<a className={'py-2 px-5 text-lg hover:bg-blue-400 hover:text-white hover:shadow-lg cursor-pointer duration-100 font-light flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-blue-500 text-white ' : ' ')} >
|
||||
<div className='my-auto justify-center flex'>
|
||||
<FontAwesomeIcon icon={link.icon} />
|
||||
|
||||
@@ -3,49 +3,19 @@ import Link from 'next/link'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
/**
|
||||
* 洗牌乱序:从数组的最后位置开始,从前面随机一个位置,对两个数进行交换,直到循环完毕
|
||||
* @param arr
|
||||
* @returns {*}
|
||||
* 展示文章推荐
|
||||
*/
|
||||
function shuffleSort (arr) {
|
||||
let i = arr.length - 1
|
||||
while (i > 0) {
|
||||
const rIndex = Math.floor(Math.random() * i)
|
||||
const temp = arr[rIndex]
|
||||
arr[rIndex] = arr[i]
|
||||
arr[i] = temp
|
||||
i--
|
||||
const RecommendPosts = ({ recommendPosts }) => {
|
||||
if (!recommendPosts) {
|
||||
return <></>
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
const RecommendPosts = ({ currentPost, totalPosts }) => {
|
||||
let filteredPosts = totalPosts
|
||||
// 筛选同标签
|
||||
if (currentPost.tags && currentPost.tags.length) {
|
||||
const currentTag = currentPost.tags[0]
|
||||
filteredPosts = totalPosts.filter(
|
||||
post =>
|
||||
post &&
|
||||
post.tags &&
|
||||
post.tags.includes(currentTag) &&
|
||||
post.slug !== currentPost.slug
|
||||
)
|
||||
}
|
||||
shuffleSort(filteredPosts)
|
||||
|
||||
// 筛选前5个
|
||||
if (filteredPosts.length > 5) {
|
||||
filteredPosts = filteredPosts.slice(0, 5)
|
||||
}
|
||||
|
||||
const { locale } = useGlobal()
|
||||
|
||||
return (
|
||||
<div className="dark:text-gray-300 pt-2">
|
||||
<div className="mb-2 text-2xl">{locale.COMMON.RELATE_POSTS}</div>
|
||||
<ul className="list-disc pl-6 text-sm dark:bg-gray-900 bg-gray-100 p-2 my-2 border-l-4 border-yellow-500">
|
||||
{filteredPosts.map(post => (
|
||||
{recommendPosts.map(post => (
|
||||
<li className="py-1" key={post.id}>
|
||||
<Link href={`/article/${post.slug}`}>
|
||||
<a className="cursor-pointer hover:text-blue-500 hover:underline">
|
||||
|
||||
@@ -49,7 +49,7 @@ const SearchInput = ({ currentTag, currentSearch }) => {
|
||||
/>
|
||||
{(searchKey && searchKey.length && <FontAwesomeIcon icon={faTimes} className='text-gray-300 float-right m-3 cursor-pointer' onClick={cleanSearch} />)}
|
||||
|
||||
<div className='p-3 bg-gray-50 flex border-l dark:border-gray-700 dark:hover:bg-gray-800 dark:bg-gray-600 justify-center align-middle cursor-pointer'
|
||||
<div className='p-3 bg-gray-50 flex border-l dark:border-gray-700 dark:hover:bg-gray-800 dark:bg-gray-600 justify-center items-center cursor-pointer'
|
||||
onClick={() => { handleSearch(searchKey) }}>
|
||||
<FontAwesomeIcon spin={onLoading} icon={onLoading ? faSpinner : faSearch} className='hover:scale-125 hover:text-black transform duration-200 dark:text-gray-300 dark:hover:text-white text-gray-600 cursor-pointer ' />
|
||||
</div>
|
||||
|
||||
@@ -26,13 +26,16 @@ import { faAngleDoubleRight, faArchive, faTags, faThList } from '@fortawesome/fr
|
||||
const SideBar = ({ tags, currentTag, post, posts, categories, currentCategory, currentSearch }) => {
|
||||
const { locale } = useGlobal()
|
||||
return <aside id='sidebar' className='pt-5 bg-white dark:bg-gray-900 w-72 z-10 dark:border-gray-500 border-gray-200 scroll-hidden h-full'>
|
||||
<InfoCard />
|
||||
<hr className='dark:border-gray-700 mt-6 py-1' />
|
||||
<section className='hidden lg:block'>
|
||||
<InfoCard />
|
||||
<hr className='dark:border-gray-700 mt-6 py-1' />
|
||||
</section>
|
||||
|
||||
<div className={(!post ? 'sticky top-0' : '') + ' bg-white dark:bg-gray-900 pb-4'}>
|
||||
|
||||
{/* <hr className='dark:border-gray-700' /> */}
|
||||
<MenuButtonGroup allowCollapse={true} />
|
||||
<section className='hidden lg:block'>
|
||||
<MenuButtonGroup allowCollapse={true} />
|
||||
</section>
|
||||
|
||||
<section className='p-5'>
|
||||
<SearchInput currentTag={currentTag} currentSearch={currentSearch} />
|
||||
|
||||
@@ -51,7 +51,7 @@ const Toc = ({ toc }) => {
|
||||
}, throttleMs))
|
||||
|
||||
return <>
|
||||
<nav className=' dark:text-gray-400 dark:bg-gray-900 overflow-y-auto scroll-hidden p-2'>
|
||||
<nav className=' dark:text-gray-100 glassmorphism overflow-y-auto scroll-hidden p-6'>
|
||||
{toc.map((tocItem) => {
|
||||
const id = uuidToId(tocItem.id)
|
||||
return (
|
||||
|
||||
@@ -25,11 +25,11 @@ const TocDrawer = ({ post, cRef }) => {
|
||||
<div className='fixed top-0 right-0 z-40'>
|
||||
{/* 侧边菜单 */}
|
||||
<div
|
||||
className={(showDrawer ? 'animate__slideInRight ' : ' -mr-72 animate__slideOutRight') + ' border ' +
|
||||
' dark:border-gray-800 bg-white dark:bg-gray-700 shadow-xl animate__animated animate__faster max-h-96 ' +
|
||||
className={(showDrawer ? 'animate__slideInRight ' : ' -mr-72 animate__slideOutRight') +
|
||||
' shadow-xl animate__animated animate__faster max-h-96 ' +
|
||||
' w-60 duration-200 fixed right-4 top-16 rounded overflow-y-auto'}>
|
||||
{post && <>
|
||||
<div className='text-xl font-bold text-black bg-gray-50 dark:text-white dark:bg-black py-3 px-6'>
|
||||
<div className='text-xl font-bold text-black dark:text-white glassmorphism py-3 px-6'>
|
||||
{locale.COMMON.TABLE_OF_CONTENTS}
|
||||
</div>
|
||||
<Toc toc={post.toc}/>
|
||||
|
||||
@@ -17,7 +17,6 @@ const TocDrawerButton = (props) => {
|
||||
const scrollListener = () => {
|
||||
const scrollY = window.pageYOffset
|
||||
const shouldShow = scrollY > 100 && scrollY < windowTop
|
||||
console.log('目录', shouldShow, scrollY, windowTop)
|
||||
windowTop = scrollY
|
||||
|
||||
if (shouldShow !== show) {
|
||||
@@ -30,14 +29,12 @@ const TocDrawerButton = (props) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<div id='toc-drawer-button' className='right-5 fixed bottom-56 duration-500 z-40' onClick={props.onClick}>
|
||||
<div className='transform hover:scale-105 duration-200'>
|
||||
<div id='toc-drawer-button' className='right-5 fixed bottom-72 duration-500 z-40' onClick={props.onClick}>
|
||||
<div className={(show ? 'animate__fadeInRight' : 'hidden') + ' px-2 py-4 animate__animated glassmorphism rounded-full cursor-pointer shadow-card' }>
|
||||
<div className='text-center dark:text-gray-100'>
|
||||
<div className='w-10 text-md' title={locale.COMMON.TABLE_OF_CONTENTS} ><FontAwesomeIcon icon={faListOl} /> </div>
|
||||
</div>
|
||||
<div className='w-10 dark:text-gray-200 text-center transform hover:scale-125 duration-200' title={locale.POST.TOP} >
|
||||
<div className='w-10 text-md' title={locale.COMMON.TABLE_OF_CONTENTS} ><FontAwesomeIcon icon={faListOl} /> </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { useRef } from 'react'
|
||||
import DarkModeButton from '@/components/DarkModeButton'
|
||||
import SideBarDrawer from '@/components/SideBarDrawer'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faBars } from '@fortawesome/free-solid-svg-icons'
|
||||
import BLOG from '@/blog.config'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
const TopNav = ({ tags, currentTag, post, posts, currentSearch, categories, currentCategory }) => {
|
||||
const drawer = useRef()
|
||||
const { locale } = useGlobal()
|
||||
|
||||
return (<div id='top-nav' className='block lg:hidden'>
|
||||
{/* 侧面抽屉 */}
|
||||
@@ -15,24 +17,39 @@ const TopNav = ({ tags, currentTag, post, posts, currentSearch, categories, curr
|
||||
|
||||
{/* 导航栏 */}
|
||||
<div id='sticky-nav' className='fixed w-full top-0 z-20 transform duration-500 glassmorphism'>
|
||||
<div className='text-sm m-auto w-full flex flex-row justify-between items-center px-4 py-2 shadow-xl '>
|
||||
<div className='w-full flex justify-between items-center p-4 shadow-xl'>
|
||||
{/* 左侧LOGO */}
|
||||
<div className='flex ml-12'>
|
||||
<div onClick={() => { drawer.current.handleSwitchSideDrawerVisible() }}
|
||||
className='fixed top-3 left-0 z-30 ml-5 text-gray-600 text-2xl cursor-pointer dark:text-gray-300'>
|
||||
<FontAwesomeIcon icon={faBars} className='hover:scale-125 transform duration-200'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<div className='relative w-12' ><Image
|
||||
alt={BLOG.title}
|
||||
layout='fill'
|
||||
loading='lazy'
|
||||
src='/avatar.svg'
|
||||
className='rounded-full border-black'
|
||||
/></div>
|
||||
<Link href='/' passHref>
|
||||
<div className='cursor-pointer'>
|
||||
<h1 className='text-xl pb-0.5 hover:scale-105 duration-200 transform font-serif dark:text-gray-200 whitespace-nowrap overflow-x-hidden'>{BLOG.title }</h1>
|
||||
</div>
|
||||
<a>
|
||||
<h1 className='cursor-pointer ml-1 leading-7 text-xl hover:scale-105 duration-200 transform font-serif dark:text-gray-200 whitespace-nowrap overflow-x-hidden'>{BLOG.title }</h1>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* 右侧功能 */}
|
||||
<div className='flex flex-nowrap space-x-1 ml-2 pt-1 dark:text-gray-200'>
|
||||
<DarkModeButton />
|
||||
<div className='mr-2 flex justify-end items-center text-sm flex-nowrap space-x-4 font-serif dark:text-gray-200'>
|
||||
<Link href='/'>
|
||||
<a>{locale.NAV.INDEX}</a>
|
||||
</Link>
|
||||
<Link href='/archive'>
|
||||
<a>{locale.NAV.ARCHIVE}</a>
|
||||
</Link>
|
||||
<Link href='/article/about'>
|
||||
<a>{locale.NAV.ABOUT}</a>
|
||||
</Link>
|
||||
<div onClick={() => { drawer.current.handleSwitchSideDrawerVisible() }}
|
||||
className='ppb-1z-30 text-gray-600 text-2xl flex items-center cursor-pointer dark:text-gray-300'>
|
||||
<FontAwesomeIcon icon={faBars} className='hover:scale-125 transform duration-200'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,8 @@ import JumpToTopButton from '@/components/JumpToTopButton'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import DarkModeButton from '@/components/DarkModeButton'
|
||||
import LoadingCover from '@/components/LoadingCover'
|
||||
import LeftFloatButton from '@/components/LeftFloatButton'
|
||||
import FloatDarkModeButton from '@/components/FloatDarkModeButton'
|
||||
|
||||
/**
|
||||
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
|
||||
@@ -89,11 +91,8 @@ const BaseLayout = ({
|
||||
<Footer />
|
||||
|
||||
<JumpToTopButton targetRef={targetRef} showPercent={true} />
|
||||
<div className='hidden lg:block fixed right-5 bottom-52 py-1.5 px-2.5 rounded-full
|
||||
bg-white text-black shadow-card dark:border-gray-500 dark:bg-gray-700 dark:text-white'>
|
||||
<DarkModeButton />
|
||||
<FloatDarkModeButton/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function Custom404 () {
|
||||
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 align-middle'>
|
||||
<div className='inline-block text-left h-32 leading-10 items-center'>
|
||||
<h2 className='m-0 p-0'>页面无法加载,即将返回首页</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
70
pages/about.js
Normal file
70
pages/about.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import ArticleDetail from '@/components/ArticleDetail'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { getAllCategories, getAllPosts, getAllTags, getPostBlocks } from '@/lib/notion'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
import Custom404 from '@/pages/404'
|
||||
import { getPageTableOfContents } from 'notion-utils'
|
||||
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 React from 'react'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
/**
|
||||
* 关于页面,默认取notion中slug为about的文章
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const About = ({ post, blockMap, tags, prev, next, allPosts, categories }) => {
|
||||
if (!post) {
|
||||
return <Custom404 />
|
||||
}
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${BLOG.title} | ${locale.NAV.ABOUT}`,
|
||||
description: post.summary,
|
||||
type: 'post',
|
||||
tags: []
|
||||
}
|
||||
|
||||
return <BaseLayout meta={meta} tags={tags} post={post} totalPosts={allPosts} categories={categories}>
|
||||
<ArticleDetail post={post} blockMap={blockMap} allPosts={allPosts}/>
|
||||
</BaseLayout>
|
||||
}
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'about-props'
|
||||
const notionPageData = await getNotionPageData({ from })
|
||||
let allPosts = await getAllPosts({ notionPageData, from, includePage: true })
|
||||
const post = allPosts.find(p => p.slug === 'about')
|
||||
|
||||
if (!post) {
|
||||
return { props: {}, revalidate: 1 }
|
||||
}
|
||||
|
||||
const blockMap = await getPostBlocks(post.id, 'slug')
|
||||
post.toc = []
|
||||
if (blockMap) {
|
||||
post.content = Object.keys(blockMap.block)
|
||||
post.toc = getPageTableOfContents(post, blockMap)
|
||||
}
|
||||
|
||||
allPosts = allPosts.filter(post => post.type[0] === 'Post')
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const tags = await getAllTags({ allPosts, tagOptions })
|
||||
const categories = await getAllCategories(allPosts)
|
||||
const index = allPosts.indexOf(post)
|
||||
const prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
|
||||
const next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
|
||||
|
||||
return {
|
||||
props: { post, blockMap, tags, prev, next, allPosts, categories },
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
export default About
|
||||
@@ -1,42 +1,19 @@
|
||||
import { getAllCategories, getAllPosts, getAllTags, getPostBlocks } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import { getPageTableOfContents } from 'notion-utils'
|
||||
import { useRouter } from 'next/router'
|
||||
import Progress from '@/components/Progress'
|
||||
import TagItem from '@/components/TagItem'
|
||||
import formatDate from '@/lib/formatDate'
|
||||
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
import Comment from '@/components/Comment'
|
||||
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import React, { useRef } from 'react'
|
||||
import Custom404 from '@/pages/404'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
|
||||
import 'prismjs'
|
||||
import 'prismjs/components/prism-bash'
|
||||
import 'prismjs/components/prism-markup'
|
||||
import 'prismjs/components/prism-python'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/components/prism-typescript'
|
||||
import RecommendPosts from '@/components/RecommendPosts'
|
||||
import TocDrawer from '@/components/TocDrawer'
|
||||
import TocDrawerButton from '@/components/TocDrawerButton'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import ArticleDetail from '@/components/ArticleDetail'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faEye, faFolderOpen } from '@fortawesome/free-solid-svg-icons'
|
||||
import BlogAround from '@/components/BlogAround'
|
||||
|
||||
import '@/styles/notion.css'
|
||||
import 'rc-dropdown/assets/index.css'
|
||||
import 'prismjs/themes/prism-okaidia.css'
|
||||
|
||||
const mapPageUrl = id => {
|
||||
return 'https://www.notion.so/' + id.replace(/-/g, '')
|
||||
}
|
||||
const ArticleDetail = ({ post, blockMap, tags, prev, next, allPosts, categories }) => {
|
||||
/**
|
||||
* 根据notion的slug访问页面
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const Slug = ({ post, blockMap, tags, prev, next, allPosts, recommendPosts, categories }) => {
|
||||
if (!post) {
|
||||
return <Custom404 />
|
||||
}
|
||||
@@ -46,111 +23,9 @@ const ArticleDetail = ({ post, blockMap, tags, prev, next, allPosts, categories
|
||||
type: 'article',
|
||||
tags: post.tags
|
||||
}
|
||||
const targetRef = useRef(null)
|
||||
const drawerRight = useRef(null)
|
||||
const url = BLOG.link + useRouter().asPath
|
||||
const { locale } = useGlobal()
|
||||
const date = formatDate(post?.date?.start_date || post.createdTime, BLOG.lang)
|
||||
|
||||
return <BaseLayout meta={meta} tags={tags} post={post} totalPosts={allPosts} categories={categories}>
|
||||
<Progress targetRef={targetRef} />
|
||||
|
||||
<div id='article-wrapper' ref={targetRef} className='flex-grow'>
|
||||
|
||||
<div className='max-w-5xl mx-auto mt-16 xl:mt-32 w-screen md:w-full'>
|
||||
|
||||
<article itemScope itemType="https://schema.org/Movie" className='duration-300 hover:shadow-2xl pt-10 animate__fadeIn animate__animated subpixel-antialiased lg:pt-32 lg:px-52 px-5 py-2 dark:border-gray-700 bg-white dark:bg-gray-800'>
|
||||
{post.type && !post.type.includes('Page') && (<>
|
||||
<header className='w-full h-60 lg:h-96 transform duration-200 md:flex-shrink-0 overflow-hidden'>
|
||||
<Image src={(post.page_cover && post.page_cover.length > 1) ? post.page_cover : BLOG.defaultImgCover} loading='eager' objectFit='cover' layout='fill' alt={post.title} />
|
||||
</header>
|
||||
</>)}
|
||||
{/* 文章Title */}
|
||||
<h2 className='font-bold text-2xl text-black dark:text-white font-serif pt-10'> {post.title}</h2>
|
||||
<hr className='mt-4' />
|
||||
<section className='flex-nowrap flex mt-1 dark:text-white font-light'>
|
||||
<Link href={`/category/${post.category}`} passHref>
|
||||
<a className='cursor-pointer text-md py-2 ml-1 mr-3 text-gray-500 dark:text-gray-300 hover:text-black dark:hover:text-white'>
|
||||
<FontAwesomeIcon icon={faFolderOpen} className='mr-1' />{post.category}
|
||||
</a>
|
||||
</Link>
|
||||
{post.type[0] !== 'Page' && (
|
||||
<Link href={`/archive#${post?.date?.start_date?.substr(0, 7)}`} passHref>
|
||||
<a className='pl-1 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 text-gray-400 dark:text-gray-400 leading-10'>
|
||||
{date}
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<div id='busuanzi_container_page_pv' className='hidden'>
|
||||
<FontAwesomeIcon icon={faEye} className='text-gray-500 dark:text-gray-400 mt-3 ml-5' />
|
||||
<span id='busuanzi_value_page_pv' className='text-gray-500 dark:text-gray-400 leading-6'></span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className='px-1 py-2 my-1 text-sm font-light bg-gray-50 text-gray-600 dark:bg-gray-700 dark:text-gray-400'>
|
||||
{post.summary}
|
||||
</section>
|
||||
|
||||
{/* Notion文章主体 */}
|
||||
{blockMap && (
|
||||
<NotionRenderer className={`${BLOG.font}`} recordMap={blockMap} mapPageUrl={mapPageUrl}
|
||||
components={{
|
||||
equation: Equation,
|
||||
code: Code,
|
||||
collectionRow: CollectionRow,
|
||||
collection: Collection
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 推荐文章 */}
|
||||
<RecommendPosts currentPost={post} totalPosts={allPosts} />
|
||||
|
||||
{/* 版权声明 */}
|
||||
<section className='dark:text-gray-300 mt-6'>
|
||||
<div className='text-2xl mb-2'>版权声明</div>
|
||||
<ul className='text-sm dark:bg-gray-900 bg-gray-100 p-5 leading-8 border-l-4 border-red-500'>
|
||||
<li>本文作者: <Link href='/article/about'><a className='hover:underline'>{BLOG.author}</a></Link></li>
|
||||
<li>本文链接: <a className='hover:underline' href={url}>{url}</a></li>
|
||||
<li>本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* 标签列表 */}
|
||||
<section className='md:flex md:justify-between'>
|
||||
{post.tagItems && (
|
||||
<div className='flex flex-nowrap leading-8 p-1 py-4 overflow-x-auto'>
|
||||
<div className='hidden md:block dark:text-gray-300'>{locale.COMMON.TAGS}:</div>
|
||||
{post.tagItems.map(tag => (
|
||||
<TagItem key={tag.name} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<ShareBar post={post} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<BlogAround prev={prev} next={next}/>
|
||||
|
||||
</article>
|
||||
|
||||
{/* 评论互动 */}
|
||||
<div
|
||||
className='my-10 w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-700'>
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 悬浮目录按钮 */}
|
||||
<div className='block lg:hidden'>
|
||||
<TocDrawerButton onClick={() => { drawerRight.current.handleSwitchVisible() }} />
|
||||
{/* 目录侧边栏 */}
|
||||
<TocDrawer post={post} cRef={drawerRight} />
|
||||
</div>
|
||||
|
||||
<ArticleDetail post={post} blockMap={blockMap} recommendPosts={recommendPosts} prev={prev} next={next}/>
|
||||
</BaseLayout>
|
||||
}
|
||||
|
||||
@@ -191,10 +66,58 @@ export async function getStaticProps ({ params: { slug } }) {
|
||||
const prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
|
||||
const next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
|
||||
|
||||
const recommendPosts = getRecommendPost(post, allPosts)
|
||||
|
||||
return {
|
||||
props: { post, blockMap, tags, prev, next, allPosts, categories },
|
||||
props: { post, blockMap, tags, prev, next, allPosts, recommendPosts, categories },
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
export default ArticleDetail
|
||||
/**
|
||||
*
|
||||
* @param {获取文章推荐文章} post
|
||||
* @param {*} allPosts
|
||||
* @param {*} count
|
||||
* @returns
|
||||
*/
|
||||
function getRecommendPost (post, allPosts, count = 5) {
|
||||
let filteredPosts = Object.create(allPosts)
|
||||
// 筛选同标签
|
||||
if (post.tags && post.tags.length) {
|
||||
const currentTag = post.tags[0]
|
||||
filteredPosts = filteredPosts.filter(
|
||||
p =>
|
||||
p &&
|
||||
p.tags &&
|
||||
p.tags.includes(currentTag) &&
|
||||
p.slug !== post.slug
|
||||
)
|
||||
}
|
||||
shuffleSort(filteredPosts)
|
||||
|
||||
// 筛选前5个
|
||||
if (filteredPosts.length > count) {
|
||||
filteredPosts = filteredPosts.slice(0, count)
|
||||
}
|
||||
return filteredPosts
|
||||
}
|
||||
|
||||
/**
|
||||
* 洗牌乱序:从数组的最后位置开始,从前面随机一个位置,对两个数进行交换,直到循环完毕
|
||||
* @param arr
|
||||
* @returns {*}
|
||||
*/
|
||||
function shuffleSort (arr) {
|
||||
let i = arr.length - 1
|
||||
while (i > 0) {
|
||||
const rIndex = Math.floor(Math.random() * i)
|
||||
const temp = arr[rIndex]
|
||||
arr[rIndex] = arr[i]
|
||||
arr[i] = temp
|
||||
i--
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
export default Slug
|
||||
|
||||
Reference in New Issue
Block a user