mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-13 23:16:47 +00:00
文章详情页
This commit is contained in:
@@ -36,6 +36,7 @@ export async function getStaticProps ({ params: { page } }) {
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
|
||||
// 处理分页
|
||||
const postsToShow = allPosts.slice(
|
||||
BLOG.postsPerPage * (page - 1),
|
||||
|
||||
@@ -4,7 +4,7 @@ import LayoutBase from './LayoutBase'
|
||||
export const LayoutPage = (props) => {
|
||||
return <LayoutBase {...props}>
|
||||
|
||||
<BlogListPage posts={props.posts} postCount={props.postCount}/>
|
||||
<BlogListPage page={props.page} posts={props.posts} postCount={props.postCount}/>
|
||||
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import BLOG from '@/blog.config'
|
||||
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 ArticleDetail from './components/ArticleDetail'
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutSlug = (props) => {
|
||||
return <LayoutBase {...props}>
|
||||
首页
|
||||
</LayoutBase>
|
||||
const { post } = props
|
||||
const meta = {
|
||||
title: `${post.title} | ${BLOG.title}`,
|
||||
description: post.summary,
|
||||
type: 'article',
|
||||
tags: post.tags
|
||||
}
|
||||
|
||||
return (
|
||||
<LayoutBase meta={meta} {...props} >
|
||||
<ArticleDetail {...props} />
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
|
||||
26
themes/Fukasawa/components/ArticleArount.js
Normal file
26
themes/Fukasawa/components/ArticleArount.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import Link from 'next/link'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faAngleDoubleLeft, faAngleDoubleRight } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
/**
|
||||
* 上一篇,下一篇文章
|
||||
* @param {prev,next} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function ArticleAround ({ prev, next }) {
|
||||
if (!prev || !next) {
|
||||
return <></>
|
||||
}
|
||||
return <section className='text-gray-800 h-28 flex items-center justify-between space-x-5 my-4'>
|
||||
<Link href={`/article/${prev.slug}`} passHref>
|
||||
<a className='text-sm cursor-pointer justify-center items-center flex w-full h-full bg-gray-400 bg-opacity-50 hover:bg-gray-700 hover:text-white duration-300'>
|
||||
<FontAwesomeIcon icon={faAngleDoubleLeft} className='mr-1' />{prev.title}
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/article/${next.slug}`} passHref>
|
||||
<a className='text-sm cursor-pointer justify-center items-center flex w-full h-full bg-gray-400 bg-opacity-50 hover:bg-gray-700 hover:text-white duration-300'>{next.title}
|
||||
<FontAwesomeIcon icon={faAngleDoubleRight} className='ml-1 my-1' />
|
||||
</a>
|
||||
</Link>
|
||||
</section>
|
||||
}
|
||||
155
themes/Fukasawa/components/ArticleDetail.js
Normal file
155
themes/Fukasawa/components/ArticleDetail.js
Normal file
@@ -0,0 +1,155 @@
|
||||
import Comment from '@/components/Comment'
|
||||
import formatDate from '@/lib/formatDate'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { faEye, faFolderOpen } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import mediumZoom from 'medium-zoom'
|
||||
import Link from 'next/link'
|
||||
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 { useEffect, useRef } from 'react'
|
||||
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
|
||||
import ArticleAround from './ArticleArount'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
|
||||
const { locale } = useGlobal()
|
||||
const date = formatDate(post?.date?.start_date || post.createdTime, locale.LOCALE)
|
||||
|
||||
const zoom = typeof window !== 'undefined' && mediumZoom({
|
||||
container: '.notion-viewport',
|
||||
background: 'rgba(0, 0, 0, 0.2)',
|
||||
margin: getMediumZoomMargin()
|
||||
})
|
||||
const zoomRef = useRef(zoom ? zoom.clone() : null)
|
||||
|
||||
useEffect(() => {
|
||||
// 将所有container下的所有图片添加medium-zoom
|
||||
const container = document.getElementById('container')
|
||||
const imgList = container.getElementsByTagName('img')
|
||||
if (imgList && zoomRef.current) {
|
||||
for (let i = 0; i < imgList.length; i++) {
|
||||
(zoomRef.current).attach(imgList[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (<div id="container" className=" max-w-4xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
|
||||
<article itemScope itemType="https://schema.org/Movie"
|
||||
className="subpixel-antialiased py-10 px-5 lg:pt-24 md:px-24 dark:border-gray-700 bg-white dark:bg-gray-800"
|
||||
>
|
||||
|
||||
<header className='animate__slideInDown animate__animated'>
|
||||
{post.type && !post.type.includes('Page') && post?.page_cover && (
|
||||
<div className="w-full relative md:flex-shrink-0 overflow-hidden">
|
||||
<img alt={post.title} src={post?.page_cover} className='object-center w-full' />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 文章Title */}
|
||||
<div className="font-bold text-3xl text-black dark:text-white font-serif pt-10">
|
||||
{post.title}
|
||||
</div>
|
||||
|
||||
<section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8">
|
||||
<div>
|
||||
<Link href={`/category/${post.category}`} passHref>
|
||||
<a className="cursor-pointer text-md mr-2 hover:text-black dark:hover:text-white border-b dark:border-gray-500 border-dashed">
|
||||
<FontAwesomeIcon icon={faFolderOpen} className="mr-1" />
|
||||
{post.category}
|
||||
</a>
|
||||
</Link>
|
||||
<span className='mr-2'>|</span>
|
||||
|
||||
{post.type[0] !== 'Page' && (<>
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
>
|
||||
<a className="pl-1 mr-2 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 border-b dark:border-gray-500 border-dashed">
|
||||
{date}
|
||||
</a>
|
||||
</Link>
|
||||
<span className='mr-2'>|</span>
|
||||
</>)}
|
||||
|
||||
<div className="hidden busuanzi_container_page_pv font-light mr-2">
|
||||
<FontAwesomeIcon icon={faEye} className='mr-1'/>
|
||||
|
||||
<span className="mr-2 busuanzi_value_page_pv"/>
|
||||
<span className='mr-2'>|</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</header>
|
||||
|
||||
{/* Notion文章主体 */}
|
||||
<section id='notion-article' className='px-1'>
|
||||
{post.blockMap && (
|
||||
<NotionRenderer
|
||||
recordMap={post.blockMap}
|
||||
mapPageUrl={mapPageUrl}
|
||||
components={{
|
||||
equation: Equation,
|
||||
code: Code,
|
||||
collectionRow: CollectionRow,
|
||||
collection: Collection
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
|
||||
{/* 文章内嵌广告 */}
|
||||
<ins className="adsbygoogle"
|
||||
style={{ display: 'block', textAlign: 'center' }}
|
||||
data-adtest="on"
|
||||
data-ad-layout="in-article"
|
||||
data-ad-format="fluid"
|
||||
data-ad-client="ca-pub-2708419466378217"
|
||||
data-ad-slot="3806269138"/>
|
||||
</section>
|
||||
|
||||
</article>
|
||||
|
||||
<ArticleAround prev={prev} next={next}/>
|
||||
|
||||
{/* 评论互动 */}
|
||||
<div className="duration-200 shadow px-12 w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||
<div className='text-2xl mt-8 mx-8'>发表评论</div>
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
const mapPageUrl = id => {
|
||||
return 'https://www.notion.so/' + id.replace(/-/g, '')
|
||||
}
|
||||
|
||||
function getMediumZoomMargin () {
|
||||
const width = window.innerWidth
|
||||
|
||||
if (width < 500) {
|
||||
return 8
|
||||
} else if (width < 800) {
|
||||
return 20
|
||||
} else if (width < 1280) {
|
||||
return 30
|
||||
} else if (width < 1600) {
|
||||
return 40
|
||||
} else if (width < 1920) {
|
||||
return 48
|
||||
} else {
|
||||
return 72
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import BlogCard from './BlogCard'
|
||||
import PaginationNumber from './PaginationNumber'
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogCard from './BlogCard'
|
||||
import BlogPostListEmpty from './BlogListEmpty'
|
||||
import PaginationSimple from './PaginationSimple'
|
||||
|
||||
/**
|
||||
* 文章列表分页表格
|
||||
@@ -13,21 +13,21 @@ import BlogPostListEmpty from './BlogListEmpty'
|
||||
*/
|
||||
const BlogListPage = ({ page = 1, posts = [], postCount }) => {
|
||||
const totalPage = Math.ceil(postCount / BLOG.postsPerPage)
|
||||
|
||||
const showNext = page < totalPage
|
||||
if (!posts || posts.length === 0) {
|
||||
return <BlogPostListEmpty />
|
||||
} else {
|
||||
return (
|
||||
<div id="container">
|
||||
{/* 文章列表 */}
|
||||
<div className="grid 2xl:grid-cols-3 lg:grid-cols-2 md:grid-cols-1 ">
|
||||
<div style={{ columnCount: 3 }}>
|
||||
{posts.map(post => (
|
||||
<div key={post.id} className='justify-center flex'>
|
||||
<div key={post.id} className='justify-center flex' style={{ breakInside: 'avoid' }}>
|
||||
<BlogCard key={post.id} post={post} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<PaginationNumber page={page} totalPage={totalPage} />
|
||||
<PaginationSimple page={page} showNext={showNext} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Card = ({ children, headerSlot, className }) => {
|
||||
return <div className={className}>
|
||||
<>{headerSlot}</>
|
||||
<section className="shadow px-2 py-4 bg-white dark:bg-gray-800 hover:shadow-xl duration-200">
|
||||
<section className="shadow mb-4 p-2 bg-white dark:bg-gray-800 hover:shadow-lg duration-200">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,10 @@ import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
function GroupCategory ({ currentCategory, categories }) {
|
||||
if (!categories) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return <>
|
||||
<div id='category-list' className='dark:border-gray-600 flex flex-wrap'>
|
||||
{Object.keys(categories).map(category => {
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faAngleLeft, faAngleRight } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
/**
|
||||
* 数字翻页插件
|
||||
* @param page 当前页码
|
||||
* @param showNext 是否有下一页
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const PaginationNumber = ({ page, totalPage }) => {
|
||||
const router = useRouter()
|
||||
const currentPage = +page
|
||||
const showNext = page !== totalPage
|
||||
const pages = generatePages(page, currentPage, totalPage)
|
||||
|
||||
return (
|
||||
<div className='my-5 flex justify-center items-end font-medium text-black hover:shadow-xl duration-500 bg-white dark:bg-gray-700 dark:text-gray-300 py-3 shadow space-x-2'>
|
||||
|
||||
{/* 上一页 */}
|
||||
<Link
|
||||
href={ {
|
||||
pathname: (currentPage - 1 === 1 ? `${BLOG.path || '/'}` : `/page/${currentPage - 1}`), query: router.query.s ? { s: router.query.s } : {}
|
||||
} } 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`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faAngleLeft}/>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{pages}
|
||||
|
||||
{/* 下一页 */}
|
||||
<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`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faAngleRight}/>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function getPageElement (page, currentPage) {
|
||||
return <Link href={page === 1 ? '/' : `/page/${page}`} key={page} passHref>
|
||||
<a className={(page + '' === currentPage + '' ? 'font-bold bg-gray-500 dark:bg-gray-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'}>
|
||||
{page}
|
||||
</a>
|
||||
</Link>
|
||||
}
|
||||
function generatePages (page, currentPage, totalPage) {
|
||||
const pages = []
|
||||
const groupCount = 7 // 最多显示页签数
|
||||
if (totalPage <= groupCount) {
|
||||
for (let i = 1; i <= totalPage; i++) {
|
||||
pages.push(getPageElement(i, page))
|
||||
}
|
||||
} else {
|
||||
pages.push(getPageElement(1, page))
|
||||
const dynamicGroupCount = groupCount - 2
|
||||
let startPage = currentPage - 2
|
||||
if (startPage <= 1) {
|
||||
startPage = 2
|
||||
}
|
||||
if (startPage + dynamicGroupCount > totalPage) {
|
||||
startPage = totalPage - dynamicGroupCount
|
||||
}
|
||||
if (startPage > 2) {
|
||||
pages.push(<div key={-1}>... </div>)
|
||||
}
|
||||
|
||||
for (let i = 0; i < dynamicGroupCount; i++) {
|
||||
if (startPage + i < totalPage) {
|
||||
pages.push(getPageElement(startPage + i, page))
|
||||
}
|
||||
}
|
||||
|
||||
if (startPage + dynamicGroupCount < totalPage) {
|
||||
pages.push(<div key={-2}>... </div>)
|
||||
}
|
||||
|
||||
pages.push(getPageElement(totalPage, page))
|
||||
}
|
||||
return pages
|
||||
}
|
||||
export default PaginationNumber
|
||||
42
themes/Fukasawa/components/PaginationSimple.js
Normal file
42
themes/Fukasawa/components/PaginationSimple.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
/**
|
||||
* 简易翻页插件
|
||||
* @param page 当前页码
|
||||
* @param showNext 是否有下一页
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const PaginationSimple = ({ page, showNext }) => {
|
||||
const { locale } = useGlobal()
|
||||
const router = useRouter()
|
||||
const currentPage = +page
|
||||
return (
|
||||
<div className='my-10 flex justify-between font-medium text-black dark:text-gray-100 space-x-2'>
|
||||
<Link
|
||||
href={ {
|
||||
pathname: (currentPage === 2 ? `${BLOG.path || '/'}` : `/page/${currentPage - 1}`), query: router.query.s ? { s: router.query.s } : {}
|
||||
} } passHref >
|
||||
<a
|
||||
rel='prev'
|
||||
className={`${currentPage === 1 ? 'invisible' : 'block'} text-center w-full duration-200 px-4 py-2 hover:border-black border-b-2 hover:font-bold`}
|
||||
>
|
||||
← {locale.PAGINATION.PREV}
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={ { pathname: `/page/${currentPage + 1}`, query: router.query.s ? { s: router.query.s } : {} } } passHref>
|
||||
<a
|
||||
rel='next'
|
||||
className={`${+showNext ? 'block' : 'invisible'} text-center w-full duration-200 px-4 py-2 hover:border-black border-b-2 hover:font-bold`}
|
||||
>
|
||||
{locale.PAGINATION.NEXT} →
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaginationSimple
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 直接将./NEXT 替换成对应的主题路径
|
||||
* 修改 from 后面的路径,实现主题切换
|
||||
*/
|
||||
|
||||
// export * from './NEXT' // 切换主题
|
||||
|
||||
Reference in New Issue
Block a user