mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-14 07:26:52 +00:00
19
README.md
19
README.md
@@ -44,20 +44,21 @@
|
||||
- 使用 Tailwind CSS,轻松实现二次开发
|
||||
|
||||
**🕸 网址美观、搜索引擎优化**
|
||||
- 更多特性、欢迎移步[我的博客](https://tangly1024.com/article/notion-next)查看
|
||||
|
||||
## 更新日志
|
||||
请移步 [更新文档](https://docs.tangly1024.com/zh/changelog)查看
|
||||
|
||||
## 更多特性
|
||||
欢迎移步[我的博客](https://tangly1024.com/article/notion-next)查看
|
||||
## 快速起步
|
||||
|
||||
- 给这个项目点个小星星 😉
|
||||
- 将 [这个 Notion 模板](https://tanghh.notion.site/02ab3b8678004aa69e9e415905ef32a5) 制作副本,并分享这个页面给所有人
|
||||
- [Fork](https://github.com/tangly1024/NotionNext/fork) 这个项目
|
||||
- 在 `blog.config.js` 配置相关选项,更多关于配置的说明,请移步[NotionNext文档](https://docs.tangly1024.com/zh)
|
||||
- _(可选)_ 用自己的图片替换 `/public` 文件夹里的 `avatar.jpg`、`favicon.svg` 和 `favicon.ico`
|
||||
- 在 [Vercel](https://vercel.com) 上部署这个项目, 设定一下环境变量:
|
||||
- `NOTION_PAGE_ID`: 你刚刚分享出去的 Notion 页面网址中的页面 ID,通常是网址中工作区地址后的 32 位字符串
|
||||
- `NOTION_ACCESS_TOKEN`(可选): 如果你决定不分享你的数据库,你可以使用 token 来让网页从 Notion 数据库中抓取数据。你可以在你的浏览器 cookies 中找到它,名称是 `token_v2'。
|
||||
- Notion token 的有效期只有 180 天,请确保在 Vercel Dashboard 上手动更新,我们可能会在未来切换到官方 API 来解决这个问题。此外,如果数据库是非公开到,Notion 中的图片可能无法正常显示到网页上。
|
||||
- 在 `blog.config.js` 配置相关选项,更多关于配置的说明,请移步[NotionNext文档](https://docs.tangly1024.com/zh)
|
||||
|
||||
- **稍微等等就可以访问了!** 简单吗?
|
||||
|
||||
|
||||
@@ -76,10 +77,10 @@ yarn run start # 本地启动NextJS服务
|
||||
|
||||
## 引用技术
|
||||
|
||||
- **生成**: Next.js SSG 和 Incremental Static Regeneration
|
||||
- **页面渲染**: [React-notion-x](https://github.com/NotionX/react-notion-x)
|
||||
- **样式**: Tailwind CSS 和 `@tailwindcss/jit` compiler
|
||||
- **评论**: Gitalk,Cusdis,Utterances
|
||||
- **框架**: Next.js
|
||||
- **渲染**: [React-notion-x](https://github.com/NotionX/react-notion-x)
|
||||
- **样式**: [Tailwind CSS](https://www.tailwindcss.cn/) 和 `@tailwindcss/jit` compiler
|
||||
- **评论**: Gitalk, Cusdis, Utterances
|
||||
- **图标**:[fontawesome](https://fontawesome.com/v5.15/icons?d=gallery)
|
||||
|
||||
## 页面样式主题
|
||||
|
||||
@@ -6,14 +6,15 @@ import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
|
||||
import Card from './Card'
|
||||
import TagItemMini from './TagItemMini'
|
||||
|
||||
const BlogPostCard = ({ post, showSummary }) => {
|
||||
const { locale } = useGlobal()
|
||||
const showPreview = BLOG.home?.showPreview && post.blockMap
|
||||
return (
|
||||
<div key={post.id} className='shadow border animate__animated animate__fadeIn flex flex-col-reverse justify-between md:hover:shadow-xl duration-300
|
||||
w-full bg-white dark:bg-gray-800 dark:hover:bg-gray-700 dark:border-gray-600'>
|
||||
<Card className='w-full'>
|
||||
<div key={post.id} className='animate__animated animate__fadeIn flex flex-col-reverse justify-between duration-300'>
|
||||
|
||||
<div className='lg:p-8 p-4 flex flex-col w-full'>
|
||||
<Link href={`${BLOG.path}/article/${post.slug}`} passHref>
|
||||
@@ -74,6 +75,8 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
</Link>
|
||||
)}
|
||||
</div >
|
||||
</Card>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
9
components/Card.js
Normal file
9
components/Card.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const Card = ({ children, headerSlot, className }) => {
|
||||
return <div className={className}>
|
||||
<>{headerSlot}</>
|
||||
<section className="shadow mb-5 px-2 py-4 bg-white dark:bg-gray-800 hover:shadow-xl duration-200">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
export default Card
|
||||
@@ -12,7 +12,7 @@ const CategoryGroup = ({ currentCategory, categories }) => {
|
||||
<a className={(selected
|
||||
? 'hover:text-white dark:hover:text-white bg-gray-600 text-white '
|
||||
: 'dark:text-gray-400 text-gray-500 hover:text-white hover:bg-gray-500 dark:hover:text-white') +
|
||||
' text-sm w-full items-center duration-300 px-3 mx-2 cursor-pointer py-1 font-light'}>
|
||||
' text-sm w-full items-center duration-300 px-2 cursor-pointer py-1 font-light'}>
|
||||
<FontAwesomeIcon icon={selected ? faFolderOpen : faFolder} className={`${selected ? 'text-white' : 'text-gray-400'} mr-2`} />{category}({categories[category]})
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
@@ -28,7 +28,7 @@ const Collapse = props => {
|
||||
}
|
||||
}, [props.isOpen])
|
||||
return (
|
||||
<div ref={collapseRef} className=' overflow-hidden duration-200'>
|
||||
<div ref={collapseRef} className='overflow-hidden duration-200'>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { faCopyright, faEye, faShieldAlt, faUsers } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
const Footer = ({ title }) => {
|
||||
@@ -14,7 +13,7 @@ const Footer = ({ title }) => {
|
||||
<FontAwesomeIcon icon={faCopyright} /> {` ${y}`} <span> <a href={BLOG.link} className='underline font-bold text-gray-500 dark:text-gray-300 '>{BLOG.author}</a>. Powered by <a href='https://notion.so' className='underline font-bold text-gray-500 dark:text-gray-300'>Notion</a> & <a href='https://github.com/tangly1024/NotionNext' className='underline font-bold text-gray-500 dark:text-gray-300'>NotionNext</a>.</span>
|
||||
<br />
|
||||
<FontAwesomeIcon icon={faShieldAlt} /> <a href='https://beian.miit.gov.cn/' className='ml-1 font-bold'>闽ICP备20010331号</a>
|
||||
<span > <Link href='/article/privacy-policy' ><a className='ml-1 mr-1 font-bold underline'>隐私政策</a></Link></span>
|
||||
{/* <span > <Link href='/article/privacy-policy' ><a className='ml-1 mr-1 font-bold underline'>隐私政策</a></Link></span> */}
|
||||
|
||||
<span className='hidden busuanzi_container_site_pv'>
|
||||
<FontAwesomeIcon icon={faEye}/><span className='px-1 busuanzi_value_site_pv'> </span>pv
|
||||
|
||||
@@ -19,16 +19,16 @@ const LatestPostsGroup = ({ posts }) => {
|
||||
const currentPath = useRouter().asPath
|
||||
const { locale } = useGlobal()
|
||||
|
||||
return <section className='mt-8'>
|
||||
<div className='text-sm pb-4 px-5 flex flex-nowrap justify-between'>
|
||||
return <>
|
||||
<div className='text-sm pb-1 px-2 flex flex-nowrap justify-between'>
|
||||
<div className='font-light text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faArchive} className='mr-2' />{locale.COMMON.LATEST_POSTS}</div>
|
||||
</div>
|
||||
{posts.map(post => {
|
||||
const selected = currentPath === `${BLOG.path}/article/${post.slug}`
|
||||
return (
|
||||
<Link key={post.id} title={post.title} href={`${BLOG.path}/article/${post.slug}`} passHref>
|
||||
<a className={ 'my-1 px-2 flex font-light'}>
|
||||
<div className={ (selected ? 'text-white bg-gray-600 ' : 'text-gray-500 dark:text-gray-400 ') + ' text-xs py-1.5 flex overflow-x-hidden whitespace-nowrap hover:bg-gray-500 px-3 w-full ' +
|
||||
<a className={ 'my-1 flex font-light'}>
|
||||
<div className={ (selected ? 'text-white bg-gray-600 ' : 'text-gray-500 dark:text-gray-400 ') + ' text-xs py-1.5 flex overflow-x-hidden whitespace-nowrap hover:bg-gray-500 px-2 duration-200 w-full ' +
|
||||
'hover:text-white dark:hover:text-white cursor-pointer' }>
|
||||
<FontAwesomeIcon icon={faFileAlt} className='mr-2'/>
|
||||
<div className='truncate'>{post.title}</div>
|
||||
@@ -37,6 +37,6 @@ const LatestPostsGroup = ({ posts }) => {
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</section>
|
||||
</>
|
||||
}
|
||||
export default LatestPostsGroup
|
||||
|
||||
@@ -23,7 +23,7 @@ const MenuButtonGroup = ({ allowCollapse = false, postCount }) => {
|
||||
if (link.show) {
|
||||
const selected = (router.pathname === link.to) || (router.asPath === link.to)
|
||||
return <Link key={`${link.id}-${link.to}`} title={link.to} href={link.to} >
|
||||
<a className={'py-2 px-5 mx-2 duration-300 text-base justify-between hover:bg-gray-700 hover:text-white hover:shadow-lg cursor-pointer font-light flex flex-nowrap items-center ' +
|
||||
<a className={'py-1.5 px-5 duration-300 text-base justify-between hover:bg-gray-700 hover:text-white hover:shadow-lg cursor-pointer font-light flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-gray-200 text-black' : ' ')} >
|
||||
<div className='my-auto items-center justify-center flex '>
|
||||
<FontAwesomeIcon icon={link.icon} />
|
||||
|
||||
@@ -8,6 +8,7 @@ import Analytics from './Analytics'
|
||||
import Tabs from '@/components/Tabs'
|
||||
import BLOG from '@/blog.config'
|
||||
import Logo from './Logo'
|
||||
import Card from './Card'
|
||||
|
||||
/**
|
||||
* 侧边平铺
|
||||
@@ -30,17 +31,16 @@ const SideAreaLeft = ({ title, tags, currentTag, post, postCount, categories, cu
|
||||
{/* 菜单 */}
|
||||
<section className='shadow hidden lg:block mb-5 pb-4 bg-white dark:bg-gray-800 hover:shadow-xl duration-200'>
|
||||
<Logo/>
|
||||
<div className='pt-2 font-sans'>
|
||||
<div className='pt-2 px-2 font-sans'>
|
||||
<MenuButtonGroup allowCollapse={true} postCount={postCount} />
|
||||
</div>
|
||||
{BLOG.menu.showSearch && <div className='px-5 pt-2 font-sans'>
|
||||
{BLOG.menu.showSearch && <div className='px-2 pt-2 font-sans'>
|
||||
<SearchInput currentTag={currentTag} currentSearch={currentSearch} />
|
||||
</div>}
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
<section className='sticky top-4'>
|
||||
<Card className='sticky top-4'>
|
||||
<Tabs>
|
||||
{showToc && (
|
||||
<div key={locale.COMMON.TABLE_OF_CONTENTS} className='dark:text-gray-400 text-gray-600 bg-white dark:bg-gray-800 duration-200'>
|
||||
@@ -53,7 +53,7 @@ const SideAreaLeft = ({ title, tags, currentTag, post, postCount, categories, cu
|
||||
<Analytics postCount={postCount}/>
|
||||
</div>
|
||||
</Tabs>
|
||||
</section>
|
||||
</Card>
|
||||
|
||||
</aside>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { faAngleDoubleRight, faAngleRight, faTags, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faAngleDoubleRight, faAngleRight, faTag, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import Card from './Card'
|
||||
import CategoryGroup from './CategoryGroup'
|
||||
import TagGroups from './TagGroups'
|
||||
|
||||
@@ -38,25 +39,25 @@ const SideAreaRight = ({
|
||||
|
||||
return (<aside id='right' className='hidden 2xl:block flex-col w-60 ml-4'>
|
||||
|
||||
<section className="shadow mb-5 py-4 px-2 bg-white dark:bg-gray-800 hover:shadow-xl duration-200">
|
||||
{/* 展示广告 */}
|
||||
<ins
|
||||
className="adsbygoogle"
|
||||
style={{ display: 'block' }}
|
||||
data-adtest="on"
|
||||
data-ad-client="ca-pub-2708419466378217"
|
||||
data-ad-slot="8807314373"
|
||||
data-ad-format="auto"
|
||||
data-full-width-responsive="true"
|
||||
></ins>
|
||||
</section>
|
||||
<Card>
|
||||
{/* 展示广告 */}
|
||||
<ins
|
||||
className="adsbygoogle"
|
||||
style={{ display: 'block' }}
|
||||
data-adtest="on"
|
||||
data-ad-client="ca-pub-2708419466378217"
|
||||
data-ad-slot="8807314373"
|
||||
data-ad-format="auto"
|
||||
data-full-width-responsive="true"
|
||||
></ins>
|
||||
</Card>
|
||||
|
||||
<div className="sticky top-8">
|
||||
<div className="sticky top-4">
|
||||
|
||||
{/* 分类 */}
|
||||
{widget?.showCategoryList && categories && (
|
||||
<section className='shadow py-4 mb-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200'>
|
||||
<div className='text-sm px-5 mb-2 flex flex-nowrap justify-between font-light'>
|
||||
<Card>
|
||||
<div className='text-sm px-2 flex flex-nowrap justify-between font-light'>
|
||||
<div className='pb-1 text-gray-600 dark:text-gray-300'><FontAwesomeIcon icon={faThList} className='mr-2' />{locale.COMMON.CATEGORY}</div>
|
||||
<Link href='/category' passHref>
|
||||
<a className='text-gray-400 hover:text-black dark:text-gray-400 dark:hover:text-white hover:underline cursor-pointer'>
|
||||
@@ -65,16 +66,16 @@ const SideAreaRight = ({
|
||||
</Link>
|
||||
</div>
|
||||
<CategoryGroup currentCategory={currentCategory} categories={categories} />
|
||||
</section>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{slot}
|
||||
|
||||
{widget?.showTagList && tags && (
|
||||
<section className="shadow py-4 mb-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200">
|
||||
<div className="text-sm pb-1 px-5 flex flex-nowrap justify-between font-light dark:text-gray-200">
|
||||
<Card>
|
||||
<div className="text-sm pb-1 px-2 flex flex-nowrap justify-between font-light dark:text-gray-200">
|
||||
<div className="text-gray-600 dark:text-gray-200">
|
||||
<FontAwesomeIcon icon={faTags} className="mr-2" />
|
||||
<FontAwesomeIcon icon={faTag} className="mr-2" />
|
||||
{locale.COMMON.TAGS}
|
||||
</div>
|
||||
<Link href="/tag" passHref>
|
||||
@@ -84,10 +85,10 @@ const SideAreaRight = ({
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="px-5 pt-2">
|
||||
<div className="px-2 pt-2">
|
||||
<TagGroups tags={tags} currentTag={currentTag} />
|
||||
</div>
|
||||
</section>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ const Tabs = ({ children }) => {
|
||||
})
|
||||
|
||||
if (count === 1) {
|
||||
return <section className='shadow hover:shadow-xl duration-200'>
|
||||
return <section className='duration-200'>
|
||||
{children}
|
||||
</section>
|
||||
}
|
||||
@@ -29,11 +29,11 @@ const Tabs = ({ children }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<section >
|
||||
{<div className='shadow hidden lg:block mb-5 py-4 px-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200'>
|
||||
< >
|
||||
{<div className='hidden lg:block mb-5 bg-white dark:bg-gray-800 duration-200'>
|
||||
<ul className='flex justify-center space-x-5 pb-4 dark:text-gray-400 text-gray-600'>
|
||||
{children.map((item, index) => {
|
||||
return <li key={index} className={currentTab === index ? 'font-black border-b-2 border-red-400 text-red-400 animate__animated animate__jello ' : 'font-extralight cursor-pointer'} onClick={() => { tabClickHandle(index) }}>
|
||||
return <li key={index} className={(currentTab === index ? 'font-black border-b-2 border-red-400 text-red-400 animate__animated animate__jello ' : 'font-extralight cursor-pointer') + ' text-sm font-sans '} onClick={() => { tabClickHandle(index) }}>
|
||||
{item?.key}
|
||||
</li>
|
||||
})}
|
||||
@@ -45,7 +45,7 @@ const Tabs = ({ children }) => {
|
||||
})}
|
||||
</div>}
|
||||
|
||||
</section>)
|
||||
</>)
|
||||
}
|
||||
|
||||
export default Tabs
|
||||
|
||||
@@ -52,8 +52,8 @@ const Toc = ({ toc }) => {
|
||||
setActiveSection(currentSectionId)
|
||||
}, throttleMs))
|
||||
|
||||
return <>
|
||||
<div className='w-full'>
|
||||
return <div className='px-3'>
|
||||
<div className='w-full pb-1'>
|
||||
<Progress/>
|
||||
</div>
|
||||
<nav className='font-sans overflow-y-auto scroll-hidden'>
|
||||
@@ -79,7 +79,7 @@ const Toc = ({ toc }) => {
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Toc
|
||||
|
||||
@@ -22,17 +22,17 @@ const TocDrawer = ({ post, cRef }) => {
|
||||
}
|
||||
const { locale } = useGlobal()
|
||||
return <>
|
||||
<div className='fixed top-0 right-0 z-40'>
|
||||
<div className='fixed top-0 right-0 z-40 '>
|
||||
{/* 侧边菜单 */}
|
||||
<div
|
||||
className={(showDrawer ? 'animate__slideInRight ' : ' -mr-72 animate__slideOutRight') +
|
||||
' shadow-card animate__animated animate__faster max-h-96 ' +
|
||||
' w-60 duration-200 fixed right-4 top-16 rounded overflow-y-auto'}>
|
||||
' shadow-card animate__animated animate__faster max-h-96 ' +
|
||||
' w-60 duration-200 fixed right-4 top-16 rounded overflow-y-auto py-2 bg-white dark:bg-gray-600'}>
|
||||
{post && <>
|
||||
<div className='text-xl font-bold text-center text-black dark:text-white bg-white dark:bg-gray-600 py-2 px-6'>
|
||||
<div className='font-bold pb-2 text-center text-black dark:text-white '>
|
||||
{locale.COMMON.TABLE_OF_CONTENTS}
|
||||
</div>
|
||||
<div className='p-6 dark:text-gray-400 text-gray-600 bg-white dark:bg-gray-800'>
|
||||
<div className='dark:text-gray-400 text-gray-600 dark:bg-gray-800'>
|
||||
<Toc toc={post.toc}/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -111,7 +111,7 @@ const TopNav = ({ tags, currentTag, post, slot, categories, currentCategory, aut
|
||||
</div>
|
||||
|
||||
<Collapse isOpen={isOpen}>
|
||||
<div className='bg-white py-1'>
|
||||
<div className='bg-white py-1 px-5'>
|
||||
<MenuButtonGroup postCount={postCount}/>
|
||||
</div>
|
||||
</Collapse>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import ArticleDetail from '@/components/ArticleDetail'
|
||||
import Card from '@/components/Card'
|
||||
import LatestPostsGroup from '@/components/LatestPostsGroup'
|
||||
import Live2D from '@/components/Live2D'
|
||||
import TocDrawer from '@/components/TocDrawer'
|
||||
import TocDrawerButton from '@/components/TocDrawerButton'
|
||||
@@ -50,6 +52,9 @@ const Slug = ({
|
||||
latestPosts={latestPosts}
|
||||
categories={categories}
|
||||
floatSlot={floatSlot}
|
||||
rightAreaSlot={
|
||||
BLOG.widget?.showLatestPost && <Card><LatestPostsGroup posts={latestPosts} /></Card>
|
||||
}
|
||||
>
|
||||
<ArticleDetail
|
||||
post={post}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogPostListPage from '@/components/BlogPostListPage'
|
||||
import BlogPostListScroll from '@/components/BlogPostListScroll'
|
||||
import Card from '@/components/Card'
|
||||
import Header from '@/components/Header'
|
||||
import LatestPostsGroup from '@/components/LatestPostsGroup'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
@@ -56,7 +57,7 @@ const Index = ({ posts, tags, meta, categories, postCount, latestPosts }) => {
|
||||
tags={tags}
|
||||
sideBarSlot={<LatestPostsGroup posts={latestPosts} />}
|
||||
rightAreaSlot={
|
||||
BLOG.widget?.showLatestPost && <LatestPostsGroup posts={latestPosts} />
|
||||
BLOG.widget?.showLatestPost && <Card><LatestPostsGroup posts={latestPosts} /></Card>
|
||||
}
|
||||
postCount={postCount}
|
||||
categories={categories}
|
||||
|
||||
BIN
public/avatar.jpg
Normal file → Executable file
BIN
public/avatar.jpg
Normal file → Executable file
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 78 KiB |
Reference in New Issue
Block a user