mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-13 23:16:47 +00:00
Merge branch 'theme-Next' of https://github.com/tangly1024/NotionNext into theme-Next
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
</p>
|
||||
|
||||
演示地址:[https://tangly1024.com/](https://tangly1024.com/)
|
||||
|
||||
## 亮点 ✨
|
||||
|
||||
**🚀 秒开,设备全适配**
|
||||
@@ -44,12 +45,14 @@
|
||||
|
||||
**🕸 网址美观、搜索引擎优化**
|
||||
|
||||
## 更多特性
|
||||
欢迎移步[我的博客](https://tangly1024.com/article/notion-next)查看
|
||||
## 快速起步
|
||||
|
||||
- 给这个项目点个小星星 😉
|
||||
- 将 [这个 Notion 模板](https://tanghh.notion.site/02ab3b8678004aa69e9e415905ef32a5) 制作副本,并分享这个页面给所有人
|
||||
- [Fork](https://github.com/tangly1024/NotionNext/fork) 这个项目
|
||||
- 在 `blog.config.js` 配置相关选项
|
||||
- 在 `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 位字符串
|
||||
|
||||
108
blog.config.js
108
blog.config.js
@@ -1,52 +1,61 @@
|
||||
const BLOG = {
|
||||
title: '小唐笔记',
|
||||
author: 'tangly1024',
|
||||
email: 'tlyong1992@hotmail.com',
|
||||
link: 'https://tangly1024.com',
|
||||
description: '分享编程技术与记录生活',
|
||||
headerStrings: ['Hi,我是一个程序员', 'Hi,我是一个打工人', 'Hi,我是一个干饭人', '欢迎来到我的博客🎉'], // 首页文字
|
||||
bannerImage: './bg_image.jpg', // 首图
|
||||
title: '小唐笔记', // 站点标题
|
||||
description: '分享编程技术与记录生活', // 站点描述
|
||||
author: 'tangly1024', // 作者
|
||||
bio: '一个普通的干饭人🍚', // 作者简介
|
||||
email: 'tlyong1992@hotmail.com', // 联系邮箱
|
||||
link: 'https://tangly1024.com', // 网站地址
|
||||
keywords: ['Notion', '写作', '博客'], // 网站关键词
|
||||
home: { // 首页
|
||||
showHomeBanner: false, // 首页是否显示大图及标语 [true,false]
|
||||
homeBannerStrings: ['Hi,我是一个程序员', 'Hi,我是一个打工人', 'Hi,我是一个干饭人', '欢迎来到我的博客🎉'], // 首页大图标语文字
|
||||
homeBannerImage: './bg_image.jpg', // 背景图地址
|
||||
showPostCover: false, // 文章列表显示封面图
|
||||
showPreview: true, // 列表展示文章预览
|
||||
showSummary: false // 显示用户自定义摘要
|
||||
},
|
||||
lang: 'zh-CN', // ['zh-CN','en-US'] default lang => see /lib/lang.js for more.
|
||||
notionPageId: process.env.NOTION_PAGE_ID || 'bee1fccfa3bd47a1a7be83cc71372d83', // Important page_id!!!
|
||||
notionAccessToken: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public
|
||||
appearance: 'auto', // ['light', 'dark', 'auto'],
|
||||
font: 'font-sans tracking-wider subpixel-antialiased', // 文章字体 ['font-sans', 'font-serif', 'font-mono'] @see https://www.tailwindcss.cn/docs/font-family
|
||||
lightBackground: '#ffffff', // use hex value, don't forget '#' e.g #fffefc
|
||||
font: 'font-serif tracking-wider subpixel-antialiased', // 文章字体 ['font-sans', 'font-serif', 'font-mono'] @see https://www.tailwindcss.cn/docs/font-family
|
||||
lightBackground: '#eeeeee', // use hex value, don't forget '#' e.g #fffefc
|
||||
darkBackground: '#111827', // use hex value, don't forget '#'
|
||||
path: '', // leave this empty unless you want to deploy in a folder
|
||||
since: 2020, // if leave this empty, current year will be used.
|
||||
postListStyle: 'page', // ['page','scroll] 文章列表样式:页码分页、单页滚动加载
|
||||
postsPerPage: 6, // post counts per page
|
||||
sortByDate: false,
|
||||
showAbout: true, // WIP 是否显示关于
|
||||
showArchive: true, // WIP 是否显示归档
|
||||
autoCollapsedNavBar: false, // the automatically collapsed navigation bar
|
||||
socialLink: {
|
||||
topNavType: 'normal', // ['fixed','autoCollapse','normal'] 分别是固定顶部、固定底部滑动时自动折叠,不固定
|
||||
menu: { // 菜单栏设置
|
||||
showAbout: false, // 显示关于
|
||||
showCategory: true, // 显示分类
|
||||
showTag: true, // 显示标签
|
||||
showArchive: true, // 显示归档
|
||||
showSearch: true // 显示搜索
|
||||
},
|
||||
widget: { // 挂件及组件设置
|
||||
showPet: false, // 是否显示宠物挂件
|
||||
petLink: 'https://cdn.jsdelivr.net/npm/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json', // 挂件模型地址 @see https://github.com/xiazeyu/live2d-widget-models
|
||||
showToTop: true, // 是否显示回顶
|
||||
showToBottom: true, // 显示回底
|
||||
showDarkMode: true, // 显示日间/夜间模式切换
|
||||
showToc: true, // 移动端显示悬浮目录
|
||||
showShareBar: false, // 文章分享功能
|
||||
showRelatePosts: true, // 相关文章推荐
|
||||
showCopyRight: true, // 文章版权声明
|
||||
showLatestPost: false, // 右侧边栏显示最近更新
|
||||
showCategoryList: false, // 右侧边栏显示文章分类列表
|
||||
showTagList: false // 右侧边栏显示标签分类列表
|
||||
},
|
||||
socialLink: { // 社交链接,如不需要展示可以留空白,例如 weibo:''
|
||||
weibo: 'https://weibo.com/tangly1024',
|
||||
twitter: '',
|
||||
twitter: 'https://twitter.com/troy1024_1',
|
||||
github: 'https://github.com/tangly1024',
|
||||
telegram: ''
|
||||
telegram: 'https://t.me/tangly_1024'
|
||||
},
|
||||
seo: {
|
||||
keywords: ['Notion', '写作', '博客'],
|
||||
googleSiteVerification: '' // Remove the value or replace it with your own google site verification code
|
||||
},
|
||||
analytics: {
|
||||
provider: 'ga', // Currently we support Google Analytics and Ackee, please fill with 'ga' or 'ackee', leave it empty to disable it.
|
||||
ackeeConfig: {
|
||||
tracker: '', // e.g 'https://ackee.tangly1024.net/tracker.js'
|
||||
dataAckeeServer: '', // e.g https://ackee.tangly1024.net , don't end with a slash
|
||||
domainId: '' // e.g '0e2257a8-54d4-4847-91a1-0311ea48cc7b'
|
||||
},
|
||||
gaConfig: {
|
||||
measurementId: 'G-68EK0W049N' // e.g: G-XXXXXXXXXX
|
||||
},
|
||||
baiduAnalytics: 'f683ef76f06bb187cbed5546f6f28f28', // e.g only need xxxxx -> https://hm.baidu.com/hm.js?[xxxxx]
|
||||
busuanzi: true, // see http://busuanzi.ibruce.info/
|
||||
cnzzAnalytics: '' // 站长统计id only need xxxxxxxx -> https://s9.cnzz.com/z_stat.php?id=[xxxxxxxx]&web_id=[xxxxxxx]
|
||||
},
|
||||
comment: {
|
||||
// support provider: gitalk, utterances, cusdis
|
||||
provider: 'cusdis', // leave it empty if you don't need any comment plugin
|
||||
comment: { // 评论插件,支持 gitalk, utterances, cusdis
|
||||
provider: '', // 不需要则留空白
|
||||
gitalkConfig: {
|
||||
repo: 'NotionNext', // The repository of store comments
|
||||
owner: 'tangly1024',
|
||||
@@ -62,14 +71,31 @@ const BLOG = {
|
||||
},
|
||||
utterancesConfig: {
|
||||
repo: 'tangly1024/NotionNext'
|
||||
},
|
||||
gitter: '', // gitter聊天室
|
||||
DaoVoiceId: '', // DaoVoice http://dashboard.daovoice.io/get-started
|
||||
TidioId: '' // https://www.tidio.com/
|
||||
},
|
||||
// --- 高级设置
|
||||
analytics: { // 文章访问量统计
|
||||
busuanzi: true, // 展示网站阅读量、访问数 see http://busuanzi.ibruce.info/
|
||||
provider: 'ga', // 支持 Google Analytics and Ackee, please fill with 'ga' or 'ackee', leave it empty to disable it.
|
||||
baiduAnalytics: 'f683ef76f06bb187cbed5546f6f28f28', // e.g only need xxxxx -> https://hm.baidu.com/hm.js?[xxxxx]
|
||||
cnzzAnalytics: '', // 站长统计id only need xxxxxxxx -> https://s9.cnzz.com/z_stat.php?id=[xxxxxxxx]&web_id=[xxxxxxx]
|
||||
gaConfig: {
|
||||
measurementId: 'G-68EK0W049N' // e.g: G-XXXXXXXXXX
|
||||
},
|
||||
ackeeConfig: {
|
||||
tracker: '', // e.g 'https://ackee.tangly1024.net/tracker.js'
|
||||
dataAckeeServer: '', // e.g https://ackee.tangly1024.net , don't end with a slash
|
||||
domainId: '' // e.g '0e2257a8-54d4-4847-91a1-0311ea48cc7b'
|
||||
}
|
||||
},
|
||||
seo: {
|
||||
googleSiteVerification: '' // Remove the value or replace it with your own google site verification code
|
||||
},
|
||||
googleAdsenseId: 'ca-pub-2708419466378217', // 谷歌广告ID
|
||||
DaoVoiceId: '', // 在线聊天 DaoVoice http://dashboard.daovoice.io/get-started
|
||||
TidioId: '', // 在线聊天 https://www.tidio.com/
|
||||
isProd: process.env.VERCEL_ENV === 'production', // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables)
|
||||
showPet: true // 详情页是否显示宠物挂件
|
||||
|
||||
isProd: process.env.VERCEL_ENV === 'production'// distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables)
|
||||
}
|
||||
// export default BLOG
|
||||
module.exports = BLOG
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function ArticleCopyright ({ author, url }) {
|
||||
if (!BLOG.widget?.showCopyRight) {
|
||||
return <></>
|
||||
}
|
||||
const { locale } = useGlobal()
|
||||
return <section className="dark:text-gray-300 mt-6">
|
||||
<div className="text-2xl mb-2">{locale.COMMON.COPYRIGHT}</div>
|
||||
<ul className="text-sm dark:bg-gray-900 bg-gray-100 p-5 leading-8 border-l-4 border-red-500">
|
||||
<ul className="overflow-x-auto whitespace-nowrap text-sm dark:bg-gray-700 bg-gray-100 p-5 leading-8 border-l-2 border-blue-500">
|
||||
<li>
|
||||
<strong className='mr-2'>{locale.COMMON.AUTHOR}:</strong>
|
||||
<Link href="/about">
|
||||
@@ -19,7 +22,8 @@ export default function ArticleCopyright ({ author, url }) {
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{locale.COMMON.COPYRIGHT_NOTICE}
|
||||
<strong className='mr-2'>{locale.COMMON.COPYRIGHT}:</strong>
|
||||
{locale.COMMON.COPYRIGHT_NOTICE}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -19,7 +19,7 @@ import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/components/prism-markup'
|
||||
import 'prismjs/components/prism-python'
|
||||
import 'prismjs/components/prism-typescript'
|
||||
import { useRef } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
|
||||
import ArticleCopyright from './ArticleCopyright'
|
||||
import Live2D from './Live2D'
|
||||
@@ -30,7 +30,7 @@ import WordCount from './WordCount'
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, next }) {
|
||||
export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
|
||||
const targetRef = useRef(null)
|
||||
const drawerRight = useRef(null)
|
||||
const url = BLOG.link + useRouter().asPath
|
||||
@@ -43,26 +43,40 @@ export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, n
|
||||
margin: getMediumZoomMargin()
|
||||
})
|
||||
const zoomRef = useRef(zoom ? zoom.clone() : null)
|
||||
function attachZoom (image) {
|
||||
if (zoomRef.current) {
|
||||
(zoomRef.current).attach(image)
|
||||
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
const attachZoomRef = attachZoom
|
||||
})
|
||||
|
||||
return (<>
|
||||
<div id="article-wrapper" ref={targetRef} className="overflow-x-auto flex-grow max-w-5xl mx-auto w-screen md:w-full ">
|
||||
<div id="container" ref={targetRef} className="shadow md:hover:shadow-2xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
|
||||
<article itemScope itemType="https://schema.org/Movie"
|
||||
className="shadow md:hover:shadow-2xl duration-300 subpixel-antialiased py-10 px-5 lg:pt-24 md:px-24 xl:px-32 dark:border-gray-700 bg-white dark:bg-gray-800"
|
||||
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 && (
|
||||
{post.type && !post.type.includes('Page') && post?.page_cover && (
|
||||
<div className="w-full relative md:flex-shrink-0 overflow-hidden">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img alt={post.title} ref={attachZoomRef}src={post?.page_cover} className='object-center w-full' />
|
||||
<img alt={post.title} src={post?.page_cover} className='object-center w-full' />
|
||||
{/* <div className="w-full h-60 relative lg:h-96 transform duration-200 md:flex-shrink-0 overflow-hidden">
|
||||
<Image
|
||||
src={post?.page_cover}
|
||||
loading="eager"
|
||||
objectFit="cover"
|
||||
layout="fill"
|
||||
alt={post.title}
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* 文章Title */}
|
||||
<div className="font-bold text-3xl text-black dark:text-white font-serif pt-10">
|
||||
@@ -111,10 +125,9 @@ export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, n
|
||||
|
||||
{/* Notion文章主体 */}
|
||||
<section id='notion-article' className='px-1'>
|
||||
{blockMap && (
|
||||
{post.blockMap && (
|
||||
<NotionRenderer
|
||||
className={`${BLOG.font}`}
|
||||
recordMap={blockMap}
|
||||
recordMap={post.blockMap}
|
||||
mapPageUrl={mapPageUrl}
|
||||
components={{
|
||||
equation: Equation,
|
||||
@@ -137,12 +150,12 @@ export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, n
|
||||
data-ad-slot="3806269138"></ins>
|
||||
</section>
|
||||
|
||||
{/* 推荐文章 */}
|
||||
<RecommendPosts currentPost={post} recommendPosts={recommendPosts} />
|
||||
|
||||
{/* 版权声明 */}
|
||||
<ArticleCopyright author={BLOG.author} url={url} />
|
||||
|
||||
{/* 推荐文章 */}
|
||||
<RecommendPosts currentPost={post} recommendPosts={recommendPosts} />
|
||||
|
||||
{/* 标签列表 */}
|
||||
<section className="md:flex md:justify-between">
|
||||
{post.tagItems && (
|
||||
@@ -165,7 +178,7 @@ export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, n
|
||||
</article>
|
||||
|
||||
{/* 评论互动 */}
|
||||
<div className="mt-5 lg:px-40 md:hover:shadow-2xl duration-200 shadow w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-700">
|
||||
<div className="lg:px-40 md:hover:shadow-2xl duration-200 shadow w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -177,7 +190,6 @@ export default function ArticleDetail ({ post, blockMap, recommendPosts, prev, n
|
||||
</div>
|
||||
|
||||
{/* 宠物 */}
|
||||
{BLOG.showPet && <Live2D/>}
|
||||
<Live2D/>
|
||||
|
||||
</>)
|
||||
|
||||
@@ -3,24 +3,25 @@ import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faFolder } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faAngleDoubleRight, faFolder } from '@fortawesome/free-solid-svg-icons'
|
||||
import TagItemMini from './TagItemMini'
|
||||
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
const BlogPostCard = ({ post, tags }) => {
|
||||
const BlogPostCard = ({ post, showSummary }) => {
|
||||
const { locale } = useGlobal()
|
||||
return (
|
||||
<div key={post.id} className='shadow animate__animated animate__fadeIn flex xl:flex-row flex-col-reverse justify-between md:hover:shadow-xl duration-300
|
||||
<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'>
|
||||
|
||||
<div className='p-8 flex flex-col justify-between w-full'>
|
||||
<div className='lg:p-8 p-4 flex flex-col justify-between w-full'>
|
||||
<Link href={`${BLOG.path}/article/${post.slug}`} passHref>
|
||||
<a className='cursor-pointer text-xl xl:text-2xl leading-tight text-black dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400'>
|
||||
<a className='cursor-pointer font-bold text-3xl text-center leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400'>
|
||||
{post.title}
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<p className='my-8 text-gray-700 dark:text-gray-300 text-md font-light leading-7 italic'>{post.summary}</p>
|
||||
|
||||
<div className='flex items-center justify-between flex-wrap dark:text-gray-500 text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 '>
|
||||
<div className='flex mt-2 items-center justify-center flex-wrap dark:text-gray-500 text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 '>
|
||||
<div>
|
||||
<Link href={`/category/${post.category}`} passHref>
|
||||
<a className='cursor-pointer font-light text-sm hover:underline transform'>
|
||||
@@ -37,11 +38,36 @@ const BlogPostCard = ({ post, tags }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showSummary && <p className='mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7'>
|
||||
{post.summary}
|
||||
</p>}
|
||||
|
||||
{BLOG.home?.showPreview && post?.blockMap && <div className='max-h-screen overflow-hidden truncate max-w-full'>
|
||||
<NotionRenderer
|
||||
recordMap={post.blockMap}
|
||||
mapPageUrl={mapPageUrl}
|
||||
components={{
|
||||
equation: Equation,
|
||||
code: Code,
|
||||
collectionRow: CollectionRow,
|
||||
collection: Collection
|
||||
}}
|
||||
/>
|
||||
</div> }
|
||||
|
||||
<div className='border-b-2 w-full border-dashed py-2'></div>
|
||||
|
||||
<Link href={`${BLOG.path}/article/${post.slug}`} passHref>
|
||||
<div className='flex items-center cursor-pointer pt-6 justify-end leading-tight'>
|
||||
<a className='bg-black p-2 text-white'>{locale.COMMON.ARTICLE_DETAIL}
|
||||
<FontAwesomeIcon icon={faAngleDoubleRight} /></a>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{post?.page_cover && (
|
||||
{BLOG.home?.showPostCover && post?.page_cover && (
|
||||
<Link href={`${BLOG.path}/article/${post.slug}`} passHref>
|
||||
<div className='h-60 w-full xl:max-w-xs relative xl:h-full duration-200 cursor-pointer transform overflow-hidden'>
|
||||
<div className='h-72 w-full relative duration-200 cursor-pointer transform overflow-hidden'>
|
||||
<Image className='hover:scale-105 transform duration-500' src={post?.page_cover} alt={post.title} layout='fill' objectFit='cover' loading='lazy' />
|
||||
</div>
|
||||
</Link>
|
||||
@@ -50,4 +76,8 @@ const BlogPostCard = ({ post, tags }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const mapPageUrl = id => {
|
||||
return 'https://www.notion.so/' + id.replace(/-/g, '')
|
||||
}
|
||||
|
||||
export default BlogPostCard
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import BlogPostCard from '@/components/BlogPostCard'
|
||||
import Pagination from '@/components/Pagination'
|
||||
import PaginationNumber from './PaginationNumber'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
import { useRouter } from 'next/router'
|
||||
import BlogPostListEmpty from '@/components/BlogPostListEmpty'
|
||||
|
||||
/**
|
||||
@@ -13,58 +11,23 @@ import BlogPostListEmpty from '@/components/BlogPostListEmpty'
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const BlogPostListPage = ({ page = 1, posts = [], tags }) => {
|
||||
let filteredBlogPosts = posts
|
||||
const BlogPostListPage = ({ page = 1, posts = [], postCount }) => {
|
||||
const totalPage = Math.ceil(postCount / BLOG.postsPerPage)
|
||||
|
||||
// 处理查询过滤 支持标签、关键词过滤
|
||||
let currentSearch = ''
|
||||
const router = useRouter()
|
||||
if (router.query && router.query.s) {
|
||||
currentSearch = router.query.s
|
||||
filteredBlogPosts = posts.filter(post => {
|
||||
const tagContent = post.tags ? post.tags.join(' ') : ''
|
||||
const searchContent = post.title + post.summary + tagContent + post.slug
|
||||
return searchContent.toLowerCase().includes(currentSearch.toLowerCase())
|
||||
})
|
||||
}
|
||||
|
||||
// 处理分页
|
||||
const totalPages = Math.ceil(filteredBlogPosts.length / BLOG.postsPerPage)
|
||||
const postsToShow = filteredBlogPosts.slice(
|
||||
BLOG.postsPerPage * (page - 1),
|
||||
BLOG.postsPerPage * page
|
||||
)
|
||||
let showNext = false
|
||||
if (filteredBlogPosts) {
|
||||
const totalPosts = filteredBlogPosts.length
|
||||
showNext = page * BLOG.postsPerPage < totalPosts
|
||||
}
|
||||
|
||||
if (!postsToShow || postsToShow.length === 0) {
|
||||
if (!posts || posts.length === 0) {
|
||||
return <BlogPostListEmpty />
|
||||
} else {
|
||||
return <div id='post-list-wrapper' className='pt-16 md:pt-28 px-2 md:px-20'>
|
||||
{(!page || page === 1) && (<div className='py-5' />)}
|
||||
|
||||
{(page && page !== 1) && (
|
||||
<div className='pb-5'>
|
||||
<div className='dark:text-gray-200 flex justify-between py-1'>
|
||||
{page && page !== 1 && (<span>页 {page} / {totalPages}</span>)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
return (
|
||||
<div id="container">
|
||||
{/* 文章列表 */}
|
||||
<div className='flex flex-wrap'>
|
||||
{postsToShow.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} tags={tags} />
|
||||
<div className="flex flex-wrap lg:space-y-4 space-y-1">
|
||||
{posts.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Pagination page={page} showNext={showNext} />
|
||||
<PaginationNumber page={page} totalPage={totalPage} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import BlogPostCard from '@/components/BlogPostCard'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import throttle from 'lodash.throttle'
|
||||
import BlogPostCard from '@/components/BlogPostCard'
|
||||
import BlogPostListEmpty from '@/components/BlogPostListEmpty'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import throttle from 'lodash.throttle'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
/**
|
||||
* 博客列表滚动分页
|
||||
@@ -14,7 +13,7 @@ import { useGlobal } from '@/lib/global'
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const BlogPostListScroll = ({ posts = [], tags, currentSearch, currentCategory, currentTag }) => {
|
||||
const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = BLOG.home.showSummary }) => {
|
||||
const postsPerPage = BLOG.postsPerPage
|
||||
const [page, updatePage] = useState(1)
|
||||
const postsToShow = getPostByPage(page, posts, postsPerPage)
|
||||
@@ -53,12 +52,12 @@ const BlogPostListScroll = ({ posts = [], tags, currentSearch, currentCategory,
|
||||
if (!postsToShow || postsToShow.length === 0) {
|
||||
return <BlogPostListEmpty currentSearch={currentSearch} />
|
||||
} else {
|
||||
return <div id='post-list-wrapper' className='mt-10 md:mt-0' ref={targetRef}>
|
||||
return <div id='container' ref={targetRef}>
|
||||
|
||||
{/* 文章列表 */}
|
||||
<div className='flex flex-wrap space-y-8 mx-5 md:mx-0'>
|
||||
<div className='flex flex-wrap space-y-8'>
|
||||
{postsToShow.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} tags={tags}/>
|
||||
<BlogPostCard key={post.id} post={post} showSummary={showSummary}/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faFolder, faFolderOpen, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faFolder, faFolderOpen } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
const CategoryList = ({ currentCategory, categories }) => {
|
||||
@@ -11,7 +11,7 @@ const CategoryList = ({ currentCategory, categories }) => {
|
||||
const { locale } = useGlobal()
|
||||
|
||||
return <ul className='flex py-1 space-x-3'>
|
||||
<li className='w-16 py-2 dark:text-gray-200 whitespace-nowrap'><FontAwesomeIcon className='mr-2' icon={faThList} />{locale.COMMON.CATEGORY}</li>
|
||||
<li className='w-16 py-2 dark:text-gray-200 whitespace-nowrap'>{locale.COMMON.CATEGORY}</li>
|
||||
{Object.keys(categories).map(category => {
|
||||
const selected = category === currentCategory
|
||||
return (
|
||||
|
||||
@@ -26,42 +26,46 @@ const Comment = ({ frontMatter }) => {
|
||||
const router = useRouter()
|
||||
const { theme } = useGlobal()
|
||||
|
||||
return <div className='comment text-gray-800 dark:text-gray-300'>
|
||||
{BLOG.comment.provider === 'gitalk' && (<div className='m-10'>
|
||||
<GitalkComponent
|
||||
options={{
|
||||
id: frontMatter.id,
|
||||
title: frontMatter.title,
|
||||
clientID: BLOG.comment.gitalkConfig.clientID,
|
||||
clientSecret: BLOG.comment.gitalkConfig.clientSecret,
|
||||
repo: BLOG.comment.gitalkConfig.repo,
|
||||
owner: BLOG.comment.gitalkConfig.owner,
|
||||
admin: BLOG.comment.gitalkConfig.admin,
|
||||
distractionFreeMode: BLOG.comment.gitalkConfig.distractionFreeMode
|
||||
}}
|
||||
/>
|
||||
</div>)}
|
||||
{BLOG.comment.provider === 'utterances' && (<div className='m-10'>
|
||||
<UtterancesComponent issueTerm={frontMatter.id} className='px-2' />
|
||||
</div>
|
||||
)}
|
||||
{BLOG.comment.provider === 'cusdis' && (<>
|
||||
<script defer src='https://cusdis.com/js/widget/lang/zh-cn.js' />
|
||||
<div className='m-10'>
|
||||
<CusdisComponent
|
||||
attrs={{
|
||||
host: BLOG.comment.cusdisConfig.host,
|
||||
appId: BLOG.comment.cusdisConfig.appId,
|
||||
pageId: frontMatter.id,
|
||||
pageTitle: frontMatter.title,
|
||||
pageUrl: BLOG.link + router.asPath,
|
||||
theme: theme
|
||||
}}
|
||||
lang={BLOG.lang.toLowerCase()}
|
||||
/>
|
||||
return (
|
||||
BLOG.comment.provider !== '' && (
|
||||
<div className='comment mt-5 text-gray-800 dark:text-gray-300'>
|
||||
{BLOG.comment.provider === 'gitalk' && (<div className='m-10'>
|
||||
<GitalkComponent
|
||||
options={{
|
||||
id: frontMatter.id,
|
||||
title: frontMatter.title,
|
||||
clientID: BLOG.comment.gitalkConfig.clientID,
|
||||
clientSecret: BLOG.comment.gitalkConfig.clientSecret,
|
||||
repo: BLOG.comment.gitalkConfig.repo,
|
||||
owner: BLOG.comment.gitalkConfig.owner,
|
||||
admin: BLOG.comment.gitalkConfig.admin,
|
||||
distractionFreeMode: BLOG.comment.gitalkConfig.distractionFreeMode
|
||||
}}
|
||||
/>
|
||||
</div>)}
|
||||
{BLOG.comment.provider === 'utterances' && (<div className='m-10'>
|
||||
<UtterancesComponent issueTerm={frontMatter.id} className='px-2' />
|
||||
</div>
|
||||
)}
|
||||
{BLOG.comment.provider === 'cusdis' && (<>
|
||||
<script defer src='https://cusdis.com/js/widget/lang/zh-cn.js' />
|
||||
<div className='m-10'>
|
||||
<CusdisComponent
|
||||
attrs={{
|
||||
host: BLOG.comment.cusdisConfig.host,
|
||||
appId: BLOG.comment.cusdisConfig.appId,
|
||||
pageId: frontMatter.id,
|
||||
pageTitle: frontMatter.title,
|
||||
pageUrl: BLOG.link + router.asPath,
|
||||
theme: theme
|
||||
}}
|
||||
lang={BLOG.lang.toLowerCase()}
|
||||
/>
|
||||
</div>
|
||||
</>)}
|
||||
</div>
|
||||
</>)}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default Comment
|
||||
|
||||
@@ -9,7 +9,7 @@ const CommonHead = ({ meta }) => {
|
||||
const title = meta?.title || BLOG.title
|
||||
const description = meta?.description || BLOG.description
|
||||
const type = meta?.type || 'website'
|
||||
const keywords = meta?.tags || BLOG.seo.keywords
|
||||
const keywords = meta?.tags || BLOG.keywords
|
||||
|
||||
return <Head>
|
||||
<title>{title}</title>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { loadUserThemeFromCookies, saveTheme, useGlobal } from '@/lib/global'
|
||||
import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { loadUserThemeFromCookies, saveTheme } from '@/lib/theme'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
export default function FloatDarkModeButton () {
|
||||
if (!BLOG.widget?.showDarkMode) {
|
||||
return <></>
|
||||
}
|
||||
const [show, switchShow] = useState(false)
|
||||
const scrollListener = () => {
|
||||
const scrollY = window.pageYOffset
|
||||
@@ -14,9 +17,10 @@ export default function FloatDarkModeButton () {
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
scrollListener()
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
})
|
||||
}, [show])
|
||||
|
||||
const { changeTheme } = useGlobal()
|
||||
const userTheme = loadUserThemeFromCookies()
|
||||
@@ -32,11 +36,12 @@ export default function FloatDarkModeButton () {
|
||||
|
||||
return (
|
||||
<div
|
||||
id='float-dark-mode-button'
|
||||
onClick={handleChangeDarkMode}
|
||||
className={
|
||||
(show ? '' : 'hidden lg:block') +
|
||||
' animate__fadeInRight px-3.5 py-3 animate__animated animate__faster shadow-card fixed right-3 bottom-24 z-10 duration-200 text-xs cursor-pointer rounded-xl' +
|
||||
' text-black shadow-card dark:border-gray-500 glassmorphism dark:bg-gray-700 dark:text-gray-200'
|
||||
(show ? '' : ' hidden ') +
|
||||
' animate__fadeInRight animate__animated animate__faster fixed right-1 bottom-28 z-10 duration-500 text-xs cursor-pointer ' +
|
||||
' text-black dark:border-gray-500 flex justify-center items-center w-8 h-8 glassmorphism dark:bg-gray-700 dark:text-gray-200'
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
|
||||
@@ -9,9 +9,9 @@ const Footer = ({ title }) => {
|
||||
const y = d.getFullYear()
|
||||
return (
|
||||
<footer
|
||||
className='bg-gray-800 dark:bg-black dark:border-gray-900 border-t flex-shrink-0 justify-center text-center m-auto w-full text-gray-400 text-sm p-6'
|
||||
className='dark:bg-gray-900 flex-shrink-0 justify-center text-center m-auto w-full text-gray-400 text-sm p-6'
|
||||
>
|
||||
<FontAwesomeIcon icon={faCopyright} /> {` ${y}`} <span> <a href={BLOG.link} className='underline font-bold text-gray-100'>{BLOG.author}</a>. Powered by <a href='https://notion.so' className='underline font-bold text-gray-100'>Notion</a> & <a href='https://github.com/tangly1024/NotionNext' className='underline font-bold text-gray-100'>NotionNext</a>.</span>
|
||||
<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>
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function Header () {
|
||||
if (!typed && window && document.getElementById('typed')) {
|
||||
changeType(
|
||||
new Typed('#typed', {
|
||||
strings: BLOG.headerStrings,
|
||||
strings: BLOG.home.homeBannerStrings,
|
||||
typeSpeed: 200,
|
||||
backSpeed: 100,
|
||||
backDelay: 400,
|
||||
@@ -99,7 +99,7 @@ export default function Header () {
|
||||
className="duration-500 md:bg-fixed w-full bg-cover bg-center h-screen bg-black"
|
||||
style={{
|
||||
backgroundImage:
|
||||
`linear-gradient(rgba(0, 0, 0, 0.8), rgba(0,0,0,0.2), rgba(0, 0, 0, 0.8) ),url("${BLOG.bannerImage}")`
|
||||
`linear-gradient(rgba(0, 0, 0, 0.8), rgba(0,0,0,0.2), rgba(0, 0, 0, 0.8) ),url("${BLOG.home.homeBannerImage}")`
|
||||
}}
|
||||
>
|
||||
<div className="absolute flex h-full items-center lg:-mt-14 justify-center w-full text-4xl md:text-7xl text-white">
|
||||
|
||||
@@ -9,7 +9,7 @@ const InfoCard = ({ postCount }) => {
|
||||
<div className='flex flex-col items-center justify-center '>
|
||||
<div className='hover:rotate-45 hover:scale-125 transform duration-200 cursor-pointer' onClick={ () => { Router.push('/about') }}>
|
||||
<Image
|
||||
alt={BLOG.title}
|
||||
alt={BLOG.author}
|
||||
width={120}
|
||||
height={120}
|
||||
loading='lazy'
|
||||
@@ -17,8 +17,8 @@ const InfoCard = ({ postCount }) => {
|
||||
className='rounded-full'
|
||||
/>
|
||||
</div>
|
||||
<div className='text-3xl font-serif dark:text-white py-2 hover:scale-105 transform duration-200'>{BLOG.title}</div>
|
||||
<div className='font-light dark:text-white py-2 hover:scale-105 transform duration-200'>{BLOG.description}</div>
|
||||
<div className='text-2xl font-serif dark:text-white py-2 hover:scale-105 transform duration-200'>{BLOG.author}</div>
|
||||
<div className='font-light dark:text-white py-2 hover:scale-105 transform duration-200'>{BLOG.bio}</div>
|
||||
<SocialButton/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { faArrowDown } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
@@ -12,13 +13,17 @@ import smoothscroll from 'smoothscroll-polyfill'
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const JumpToBottomButton = ({ targetRef, showPercent = false }) => {
|
||||
const JumpToBottomButton = ({ showPercent = false }) => {
|
||||
if (!BLOG.widget?.showToBottom) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const { locale } = useGlobal()
|
||||
const [show, switchShow] = useState(false)
|
||||
const [percent, changePercent] = useState(0)
|
||||
const scrollListener = () => {
|
||||
// 处理是否显示回到顶部按钮
|
||||
const clientHeight = targetRef ? (targetRef.current ? targetRef.current.clientHeight : 0) : 0
|
||||
const targetRef = document.getElementById('wrapper')
|
||||
const clientHeight = targetRef?.clientHeight
|
||||
const scrollY = window.pageYOffset
|
||||
const fullHeight = clientHeight - window.outerHeight
|
||||
let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
|
||||
@@ -29,6 +34,12 @@ const JumpToBottomButton = ({ targetRef, showPercent = false }) => {
|
||||
}
|
||||
changePercent(per)
|
||||
}
|
||||
|
||||
function scrollToBottom () {
|
||||
const targetRef = document.getElementById('wrapper')
|
||||
window.scrollTo({ top: targetRef.clientHeight, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
smoothscroll.polyfill()
|
||||
|
||||
@@ -36,15 +47,13 @@ const JumpToBottomButton = ({ targetRef, showPercent = false }) => {
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
return (<div id='jump-to-top' className='right-3 fixed flex bottom-36 duration-500 z-20'>
|
||||
<div onClick={() => window.scrollTo({ top: targetRef.current.clientHeight, behavior: 'smooth' })}
|
||||
className={(show ? '' : 'hidden') + ' animate__fadeInRight animate__animated animate__faster shadow-card rounded-xl glassmorphism py-3 cursor-pointer '}>
|
||||
<div className='text-center'>
|
||||
<div className='w-10 dark:text-gray-200 transform hover:scale-150 duration-200 text-xs' title={locale.POST.TOP} >
|
||||
return (<div id='jump-to-top' className='right-1 fixed flex bottom-36 z-20'>
|
||||
<div onClick={() => scrollToBottom()}
|
||||
className={(show ? '' : 'hidden') + ' animate__fadeInRight duration-500 animate__animated animate__faster glassmorphism flex justify-center items-center w-8 h-8 cursor-pointer '}>
|
||||
<div className='dark:text-gray-200 transform hover:scale-150 text-xs duration-200' title={locale.POST.TOP} >
|
||||
<FontAwesomeIcon icon={faArrowDown} />
|
||||
</div>
|
||||
{showPercent && (<div className='w-10 text-xs dark:text-gray-200'>{percent}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useGlobal } from '@/lib/global'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faArrowUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import smoothscroll from 'smoothscroll-polyfill'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
/**
|
||||
* 跳转到网页顶部
|
||||
@@ -12,18 +13,20 @@ import smoothscroll from 'smoothscroll-polyfill'
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const JumpToTopButton = ({ targetRef, showPercent = false }) => {
|
||||
const JumpToTopButton = ({ showPercent = false }) => {
|
||||
if (!BLOG.widget?.showToTop) {
|
||||
return <></>
|
||||
}
|
||||
const { locale } = useGlobal()
|
||||
const [show, switchShow] = useState(false)
|
||||
const [percent, changePercent] = useState(0)
|
||||
const scrollListener = () => {
|
||||
// 处理是否显示回到顶部按钮
|
||||
const clientHeight = targetRef ? (targetRef.current ? targetRef.current.clientHeight : 0) : 0
|
||||
const targetRef = document.getElementById('wrapper')
|
||||
const clientHeight = targetRef?.clientHeight
|
||||
const scrollY = window.pageYOffset
|
||||
const fullHeight = clientHeight - window.outerHeight
|
||||
let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
|
||||
if (per > 100) per = 100
|
||||
// const shouldShow = scrollY > 100 && per > 0 && scrollY < windowTop
|
||||
const shouldShow = scrollY > 100 && per > 0
|
||||
|
||||
if (shouldShow !== show) {
|
||||
@@ -37,15 +40,13 @@ const JumpToTopButton = ({ targetRef, showPercent = false }) => {
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
return (<div id='jump-to-top' className='right-3 fixed flex bottom-48 duration-500 z-20'>
|
||||
return (<div id='jump-to-top' className='right-1 fixed flex bottom-44 z-20'>
|
||||
<div onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
||||
className={(show ? '' : 'hidden') + ' animate__fadeInRight animate__animated animate__faster shadow-card rounded-xl glassmorphism py-3 cursor-pointer '}>
|
||||
<div className='text-center'>
|
||||
<div className='w-10 dark:text-gray-200 transform hover:scale-150 duration-200 text-xs' title={locale.POST.TOP} >
|
||||
className={(show ? '' : 'hidden') + ' animate__fadeInRight duration-500 animate__animated animate__faster flex justify-center items-center w-8 h-8 glassmorphism cursor-pointer '}>
|
||||
<div className='dark:text-gray-200 transform hover:scale-150 text-xs 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>
|
||||
</div>)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { faFileAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { faArchive, faFileAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
@@ -10,25 +11,19 @@ import { useRouter } from 'next/router'
|
||||
* @param sliceCount 截取展示的数量 默认6
|
||||
* @constructor
|
||||
*/
|
||||
const LatestPostsGroup = ({ posts, sliceCount = 5 }) => {
|
||||
// 深拷贝
|
||||
let postsSortByDate = Object.create(posts)
|
||||
|
||||
// 时间排序
|
||||
postsSortByDate.sort((a, b) => {
|
||||
const dateA = new Date(a?.lastEditedTime || a.createdTime)
|
||||
const dateB = new Date(b?.lastEditedTime || b.createdTime)
|
||||
return dateB - dateA
|
||||
})
|
||||
|
||||
// 只取前五
|
||||
postsSortByDate = postsSortByDate.slice(0, sliceCount)
|
||||
|
||||
const LatestPostsGroup = ({ posts }) => {
|
||||
if (!posts) {
|
||||
return <></>
|
||||
}
|
||||
// 获取当前路径
|
||||
const currentPath = useRouter().asPath
|
||||
const { locale } = useGlobal()
|
||||
|
||||
return <>
|
||||
{postsSortByDate.map(post => {
|
||||
return <section className='mt-8'>
|
||||
<div className='text-sm pb-4 px-5 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>
|
||||
@@ -42,6 +37,6 @@ const LatestPostsGroup = ({ posts, sliceCount = 5 }) => {
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
</section>
|
||||
}
|
||||
export default LatestPostsGroup
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
/* eslint-disable no-undef */
|
||||
import { useEffect } from 'react'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
let hasLoad = false
|
||||
export default function Live2D () {
|
||||
useEffect(() => {
|
||||
if (window && !hasLoad) {
|
||||
initLive2D()
|
||||
hasLoad = true
|
||||
}
|
||||
})
|
||||
return <div className='fixed right-0 bottom-0 hidden md:block lg:mr-24 2xl:mr-48 z-20'>
|
||||
if (!BLOG.widget?.showPet) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && !hasLoad) {
|
||||
initLive2D()
|
||||
hasLoad = true
|
||||
}
|
||||
|
||||
return <div className='fixed right-0 bottom-0 hidden md:block lg:mr-24 2xl:mr-40 z-20'>
|
||||
<canvas id="live2d"className='animate__slideInLeft animate__animated' width="280" height="250"></canvas>
|
||||
</div>
|
||||
}
|
||||
@@ -22,7 +25,7 @@ function initLive2D () {
|
||||
loadExternalResource('https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/live2d.min.js', 'js')
|
||||
]).then(() => {
|
||||
// https://github.com/xiazeyu/live2d-widget-models
|
||||
loadlive2d('live2d', 'https://cdn.jsdelivr.net/npm/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json')
|
||||
loadlive2d('live2d', BLOG.widget.petLink)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import React from 'react'
|
||||
|
||||
const Logo = () => {
|
||||
return <Link href='/' passHref>
|
||||
<div title={BLOG.title} className='mx-auto border dark:border-gray-600 text-center cursor-pointer text-xl dark:text-gray-300 font-semibold dark:hover:bg-gray-600 text-white p-2 hover:scale-105 hover:shadow-2xl duration-200 transform'>
|
||||
<span className='text-red-600'>Tangly</span>
|
||||
<span className='text-blue-400'>1024</span>
|
||||
</div>
|
||||
<div className='flex flex-col justify-center items-center cursor-pointer bg-black space-y-3 h-32 font-bold'>
|
||||
<div className='font-serif text-xl text-white'> {BLOG.title}</div>
|
||||
<div className='text-sm text-gray-300 font-light'> {BLOG.description}</div>
|
||||
</div>
|
||||
</Link>
|
||||
}
|
||||
export default Logo
|
||||
|
||||
@@ -3,7 +3,7 @@ import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faArchive, faHome, faTag, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faArchive, faHome, faTag, faTh, faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import BLOG from 'blog.config'
|
||||
|
||||
const MenuButtonGroup = ({ allowCollapse = false }) => {
|
||||
@@ -11,27 +11,18 @@ const MenuButtonGroup = ({ allowCollapse = false }) => {
|
||||
const router = useRouter()
|
||||
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: faThList, name: locale.COMMON.CATEGORY, to: '/category', show: BLOG.showArchive },
|
||||
{ id: 3, icon: faTag, name: locale.COMMON.TAGS, to: '/tag', show: BLOG.showArchive }
|
||||
// { 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 }
|
||||
// { id: 2, icon: 'faRssSquare', name: locale.NAV.RSS, to: '/feed', show: true },
|
||||
// { id: 3, icon: 'faCompass', name: '发现', to: 'https://search.tangly1024.com/', show: true }
|
||||
// { id: 6, icon: 'faMapMarker', name: 'Fuzhou', to: '#', show: true },
|
||||
// { id: 8, icon: 'faTwitter', name: 'Twitter', to: 'https://twitter.com/troy1024_1', show: true },
|
||||
// { id: 9, icon: 'faTelegram', name: 'Telegram', to: 'https://t.me/tangly_1024', show: true }
|
||||
{ id: 1, icon: faArchive, name: locale.NAV.ARCHIVE, to: '/archive', show: BLOG.menu.showArchive },
|
||||
{ id: 2, icon: faTh, name: locale.COMMON.CATEGORY, to: '/category', show: BLOG.menu.showCategory },
|
||||
{ id: 3, icon: faTag, name: locale.COMMON.TAGS, to: '/tag', show: BLOG.menu.showTag },
|
||||
{ id: 4, icon: faUser, name: locale.NAV.ABOUT, to: '/about', show: BLOG.menu.showAbout }
|
||||
]
|
||||
return <nav id='nav'>
|
||||
<div className='leading-8 text-gray-500 dark:text-gray-400 '>
|
||||
return <nav id='nav' className='leading-8 text-gray-500 dark:text-gray-400 '>
|
||||
{links.map(link => {
|
||||
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-1 my-1 px-5 mx-2 duration-300 text-base hover:bg-gray-500 hover:text-white hover:shadow-lg cursor-pointer font-light flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-gray-700 text-white ' : ' ')} >
|
||||
return <Link key={`${link.id}-${link.to}`} title={link.to} href={link.to} >
|
||||
<a className={'py-1 my-1 px-5 mx-2 duration-300 text-base 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 justify-center flex '>
|
||||
<FontAwesomeIcon icon={link.icon} />
|
||||
</div>
|
||||
@@ -39,10 +30,9 @@ const MenuButtonGroup = ({ allowCollapse = false }) => {
|
||||
</a>
|
||||
</Link>
|
||||
} else {
|
||||
return <></>
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
}
|
||||
export default MenuButtonGroup
|
||||
|
||||
94
components/PaginationNumber.js
Normal file
94
components/PaginationNumber.js
Normal file
@@ -0,0 +1,94 @@
|
||||
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
|
||||
@@ -4,13 +4,13 @@ import { useRouter } from 'next/router'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
/**
|
||||
* 翻页插件
|
||||
* 简易翻页插件
|
||||
* @param page 当前页码
|
||||
* @param showNext 是否有下一页
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const Pagination = ({ page, showNext }) => {
|
||||
const PaginationSimple = ({ page, showNext }) => {
|
||||
const { locale } = useGlobal()
|
||||
const router = useRouter()
|
||||
const currentPage = +page
|
||||
@@ -39,4 +39,4 @@ const Pagination = ({ page, showNext }) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default Pagination
|
||||
export default PaginationSimple
|
||||
@@ -14,6 +14,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
|
||||
const fullHeight = clientHeight - window.outerHeight
|
||||
let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
|
||||
if (per > 100) per = 100
|
||||
if (per < 0) per = 0
|
||||
changePercent(per)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
/**
|
||||
* 展示文章推荐
|
||||
*/
|
||||
const RecommendPosts = ({ recommendPosts }) => {
|
||||
if (!recommendPosts || recommendPosts.length < 1) {
|
||||
if (!BLOG.widget?.showRelatePosts || !recommendPosts || recommendPosts.length < 1) {
|
||||
return <></>
|
||||
}
|
||||
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">
|
||||
<div className="pt-2 border pl-4 py-2 my-4 dark:text-gray-300 ">
|
||||
<div className="mb-2 font-bold text-lg">{locale.COMMON.RELATE_POSTS} :</div>
|
||||
<ul className="font-light text-sm">
|
||||
{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">
|
||||
<a className="cursor-pointer hover:underline">
|
||||
{post.title}
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
@@ -21,7 +21,7 @@ const SearchDrawer = ({ cRef }) => {
|
||||
return (
|
||||
<div id='search-drawer-wrapper' ref={searchDrawer} className='hidden'>
|
||||
<div className='flex absolute px-5 w-full h-full left-0 top-14 z-50 justify-center'>
|
||||
<div className='md:max-w-3xl w-full mx-auto'>
|
||||
<div className='md:max-w-3xl w-full mx-auto animate__animated animate__faster animate__fadeIn'>
|
||||
<SearchInput cRef={searchInputRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,9 @@ import {
|
||||
import { faLink } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
const ShareBar = ({ post }) => {
|
||||
if (!BLOG.widget?.showShareBar) {
|
||||
return <></>
|
||||
}
|
||||
const router = useRouter()
|
||||
const shareUrl = BLOG.link + router.asPath
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import { useGlobal } from '@/lib/global'
|
||||
import React from 'react'
|
||||
import Analytics from './Analytics'
|
||||
import Tabs from '@/components/Tabs'
|
||||
import BLOG from '@/blog.config'
|
||||
import Logo from './Logo'
|
||||
|
||||
/**
|
||||
* 侧边平铺
|
||||
@@ -19,22 +21,26 @@ import Tabs from '@/components/Tabs'
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const SideAreaLeft = ({ title, tags, currentTag, post, posts, categories, currentCategory, currentSearch, targetRef }) => {
|
||||
const SideAreaLeft = ({ title, tags, currentTag, post, postCount, categories, currentCategory, currentSearch, targetRef }) => {
|
||||
const { locale } = useGlobal()
|
||||
const showToc = post && post.toc && post.toc.length > 1
|
||||
const postCount = posts?.length || 0
|
||||
return <>
|
||||
|
||||
<section className='sticky top-8 w-60'>
|
||||
return <aside id='left' className='hidden lg:block flex-col w-60 mr-4'>
|
||||
|
||||
<section className='w-60'>
|
||||
{/* 菜单 */}
|
||||
<section className='shadow hidden lg:block mb-5 py-4 bg-white dark:bg-gray-800 hover:shadow-xl duration-200'>
|
||||
<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'>
|
||||
<MenuButtonGroup allowCollapse={true} />
|
||||
<div className='px-5 pt-2'>
|
||||
<SearchInput currentTag={currentTag} currentSearch={currentSearch} />
|
||||
</div>
|
||||
{BLOG.menu.showSearch && <div className='px-5 pt-2'>
|
||||
<SearchInput currentTag={currentTag} currentSearch={currentSearch} />
|
||||
</div>}
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
<section 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'>
|
||||
@@ -43,13 +49,12 @@ const SideAreaLeft = ({ title, tags, currentTag, post, posts, categories, curren
|
||||
)}
|
||||
|
||||
<div key={locale.NAV.ABOUT} className='mb-5 bg-white dark:bg-gray-800 duration-200 py-6'>
|
||||
<InfoCard postCount={postCount} />
|
||||
<InfoCard />
|
||||
<Analytics postCount={postCount}/>
|
||||
</div>
|
||||
</Tabs>
|
||||
|
||||
</section>
|
||||
|
||||
</>
|
||||
</aside>
|
||||
}
|
||||
export default SideAreaLeft
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import LatestPostsGroup from '@/components/LatestPostsGroup'
|
||||
import BLOG from '@/blog.config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { faArchive } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faAngleDoubleRight, faAngleRight, faTags, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import CategoryGroup from './CategoryGroup'
|
||||
import TagGroups from './TagGroups'
|
||||
|
||||
/**
|
||||
* 侧边平铺
|
||||
@@ -21,30 +24,19 @@ const SideAreaRight = ({
|
||||
tags,
|
||||
currentTag,
|
||||
post,
|
||||
posts,
|
||||
slot,
|
||||
categories,
|
||||
currentCategory,
|
||||
currentSearch,
|
||||
targetRef
|
||||
}) => {
|
||||
const { locale } = useGlobal()
|
||||
// const postCount = posts?.length || 0
|
||||
// const showToc = post && post.toc && post.toc.length > 1
|
||||
const { widget } = BLOG
|
||||
if (!widget?.showCategoryList && !widget.showTagList && !widget.showLatestPost) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
{/* <section className='hidden lg:block mb-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200 py-8 '>
|
||||
<InfoCard postCount={postCount} />
|
||||
</section> */}
|
||||
|
||||
{/* 菜单 */}
|
||||
{/* <section className='hidden lg:block mb-5 py-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200'>
|
||||
<MenuButtonGroup allowCollapse={true} />
|
||||
<div className='px-5 pt-2'>
|
||||
<SearchInput currentTag={currentTag} currentSearch={currentSearch} />
|
||||
</div>
|
||||
</section> */}
|
||||
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">
|
||||
{/* 展示广告 */}
|
||||
@@ -59,53 +51,26 @@ const SideAreaRight = ({
|
||||
></ins>
|
||||
</section>
|
||||
|
||||
{/* <Analytics postCount={postCount}/> */}
|
||||
|
||||
<div className="sticky top-8">
|
||||
{/* {showToc && (
|
||||
<section className='pb-10 mb-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200'>
|
||||
<div className='border-b text-center text-2xl bg-white text-black dark:border-gray-700 dark:bg-gray-700 dark:text-white py-6 px-6'>
|
||||
{locale.COMMON.TABLE_OF_CONTENTS}
|
||||
</div>
|
||||
<Toc toc={post.toc} targetRef={targetRef} />
|
||||
</section>
|
||||
)} */}
|
||||
|
||||
{/* 分类 */}
|
||||
{/* {categories && (
|
||||
{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'>
|
||||
<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'>
|
||||
{locale.COMMON.MORE} <FontAwesomeIcon icon={faAngleDoubleRight} />
|
||||
{locale.COMMON.MORE} <FontAwesomeIcon icon={faAngleRight} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<CategoryGroup currentCategory={currentCategory} categories={categories} />
|
||||
</section>
|
||||
)} */}
|
||||
|
||||
{/* 最新文章 */}
|
||||
{posts && (
|
||||
<section className="shadow py-4 mb-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200">
|
||||
<div className="text-sm pb-2 px-5 flex flex-nowrap justify-between">
|
||||
<div className="font-light text-gray-600 dark:text-gray-300">
|
||||
<FontAwesomeIcon icon={faArchive} className="mr-2" />
|
||||
{locale.COMMON.LATEST_POSTS}
|
||||
<span className='text-red-500 text-xs ml-1'>NEW</span>
|
||||
</div>
|
||||
</div>
|
||||
<LatestPostsGroup posts={posts} />
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* <section className="shadow py-4 px-5 mb-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200">
|
||||
<SearchInput currentTag={currentTag} currentSearch={currentSearch}/>
|
||||
</section> */}
|
||||
{slot}
|
||||
|
||||
{/* 标签云 */}
|
||||
{/* {tags && (
|
||||
{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">
|
||||
<div className="text-gray-600 dark:text-gray-200">
|
||||
@@ -123,10 +88,10 @@ const SideAreaRight = ({
|
||||
<TagGroups tags={tags} currentTag={currentTag} />
|
||||
</div>
|
||||
</section>
|
||||
)} */}
|
||||
)}
|
||||
|
||||
</div>
|
||||
</>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
export default SideAreaRight
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import React from 'react'
|
||||
import MenuButtonGroup from '@/components/MenuButtonGroup'
|
||||
import CategoryGroup from '@/components/CategoryGroup'
|
||||
import InfoCard from '@/components/InfoCard'
|
||||
import TagGroups from '@/components/TagGroups'
|
||||
import LatestPostsGroup from '@/components/LatestPostsGroup'
|
||||
import CategoryGroup from '@/components/CategoryGroup'
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import Link from 'next/link'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { faAngleDoubleRight, faTag, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faAngleDoubleRight, faArchive, faTags, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* 侧边栏
|
||||
@@ -22,32 +19,16 @@ import { faAngleDoubleRight, faArchive, faTags, faThList } from '@fortawesome/fr
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const SideBar = ({ title, tags, currentTag, post, posts, categories, currentCategory, currentSearch }) => {
|
||||
const SideBar = ({ title, tags, currentTag, post, slot, categories, currentCategory, currentSearch }) => {
|
||||
const { locale } = useGlobal()
|
||||
return <aside id='sidebar' className='pt-5 bg-white dark:bg-gray-900 w-80 z-10 dark:border-gray-500 border-gray-200 scroll-hidden h-full'>
|
||||
|
||||
<InfoCard />
|
||||
return <aside id='sidebar' className='bg-white dark:bg-gray-900 w-80 z-10 dark:border-gray-500 border-gray-200 scroll-hidden h-full'>
|
||||
|
||||
<div className={(!post ? 'sticky top-0' : '') + ' bg-white dark:bg-gray-900 pb-4'}>
|
||||
|
||||
<section className='hidden lg:block'>
|
||||
<MenuButtonGroup allowCollapse={true} />
|
||||
<section className='py-5'>
|
||||
<InfoCard />
|
||||
</section>
|
||||
|
||||
<section className='p-5'>
|
||||
<SearchInput currentTag={currentTag} currentSearch={currentSearch} />
|
||||
</section>
|
||||
|
||||
{/* 最新文章 */}
|
||||
{posts && (
|
||||
<section className='mt-4'>
|
||||
<div className='text-sm pb-4 px-5 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>
|
||||
<LatestPostsGroup posts={posts} />
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 分类 */}
|
||||
{categories && (
|
||||
<section className='mt-8'>
|
||||
@@ -65,9 +46,9 @@ const SideBar = ({ title, tags, currentTag, post, posts, categories, currentCate
|
||||
|
||||
{/* 标签云 */}
|
||||
{tags && (
|
||||
<section className='mt-8'>
|
||||
<section className='mt-4'>
|
||||
<div className='text-sm py-2 px-5 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'/>{locale.COMMON.TAGS}</div>
|
||||
<div className='text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faTag} className='mr-2'/>{locale.COMMON.TAGS}</div>
|
||||
<Link href='/tag' passHref>
|
||||
<a className='text-gray-400 hover:text-black dark:hover:text-white hover:underline cursor-pointer'>
|
||||
{locale.COMMON.MORE} <FontAwesomeIcon icon={faAngleDoubleRight} />
|
||||
@@ -79,6 +60,9 @@ const SideBar = ({ title, tags, currentTag, post, posts, categories, currentCate
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{slot}
|
||||
|
||||
</div>
|
||||
|
||||
<section className='bg-white dark:bg-gray-900'>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import SideBar from '@/components/SideBar'
|
||||
import React, { useEffect, useImperativeHandle, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useEffect, useImperativeHandle } from 'react'
|
||||
|
||||
/**
|
||||
* 侧边栏抽屉面板,可以从侧面拉出
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const SideBarDrawer = ({ post, currentTag, cRef, tags, posts, categories, currentCategory }) => {
|
||||
const SideBarDrawer = ({ post, currentTag, cRef, tags, slot, categories, currentCategory }) => {
|
||||
// 暴露给父组件 通过cRef.current.handleMenuClick 调用
|
||||
useImperativeHandle(cRef, () => {
|
||||
return {
|
||||
handleSwitchSideDrawerVisible: () => switchSideDrawerVisible()
|
||||
handleSwitchSideDrawerVisible: () => switchSideDrawerVisible(true)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -19,11 +20,19 @@ const SideBarDrawer = ({ post, currentTag, cRef, tags, posts, categories, curren
|
||||
sideBarWrapperElement?.classList?.remove('hidden')
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
useEffect(() => {
|
||||
const sideBarDrawerRouteListener = url => {
|
||||
switchSideDrawerVisible(false)
|
||||
}
|
||||
router.events.on('routeChangeComplete', sideBarDrawerRouteListener)
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', sideBarDrawerRouteListener)
|
||||
}
|
||||
}, [router.events])
|
||||
|
||||
// 点击按钮更改侧边抽屉状态
|
||||
const [isShow, changeHiddenStatus] = useState(false)
|
||||
const switchSideDrawerVisible = () => {
|
||||
const showStatus = !isShow
|
||||
changeHiddenStatus(showStatus)
|
||||
const switchSideDrawerVisible = (showStatus) => {
|
||||
if (window) {
|
||||
const sideBarDrawer = window.document.getElementById('sidebar-drawer')
|
||||
const sideBarDrawerBackground = window.document.getElementById('sidebar-drawer-background')
|
||||
@@ -40,10 +49,10 @@ const SideBarDrawer = ({ post, currentTag, cRef, tags, posts, categories, curren
|
||||
|
||||
return <div id='sidebar-wrapper' className='hidden'>
|
||||
<div id='sidebar-drawer' className='-ml-80 bg-white dark:bg-gray-900 flex flex-col duration-300 fixed h-full left-0 overflow-y-scroll scroll-hidden top-0 z-50'>
|
||||
<SideBar tags={tags} post={post} posts={posts} categories={categories} currentCategory={currentCategory} />
|
||||
<SideBar tags={tags} post={post} slot={slot} categories={categories} currentCategory={currentCategory} />
|
||||
</div>
|
||||
{/* 背景蒙版 */}
|
||||
<div id='sidebar-drawer-background' onClick={switchSideDrawerVisible} className='hidden fixed top-0 left-0 z-30 w-full h-full bg-black bg-opacity-30'/>
|
||||
<div id='sidebar-drawer-background' onClick={() => { switchSideDrawerVisible(false) }} className='hidden animate__animated animate__fadeIn fixed top-0 duration-300 left-0 z-30 w-full h-full glassmorphism'/>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import React from 'react'
|
||||
* @constructor
|
||||
*/
|
||||
const SocialButton = () => {
|
||||
return <div className='w-52 justify-center flex'>
|
||||
<div className='space-x-3 text-xl text-gray-600 dark:text-gray-400 px-6'>
|
||||
return <div className='w-52 justify-center flex-wrap flex'>
|
||||
<div className='space-x-3 text-xl text-gray-600 dark:text-gray-400 '>
|
||||
{BLOG.socialLink.github && <a target='_blank' rel='noreferrer' title={'github'} href={BLOG.socialLink.github} >
|
||||
<FontAwesomeIcon icon={faGithub} className='transform hover:scale-125 duration-150'/>
|
||||
</a>}
|
||||
|
||||
@@ -36,7 +36,7 @@ const StickyBar = ({ children }) => {
|
||||
|
||||
return (
|
||||
<div id='sticky-bar' className='sticky flex-grow justify-center top-14 md:top-0 duration-500 z-10 pb-16'>
|
||||
<div className='glassmorphism dark:border-gray-600 px-5 absolute rounded-none md:rounded-xl shadow-xl border w-full hidden-scroll'>
|
||||
<div className='glassmorphism dark:border-gray-600 px-5 absolute shadow-md border w-full hidden-scroll'>
|
||||
<div id='tag-container' className="md:pl-3 overflow-x-auto">
|
||||
{ children }
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ const Tabs = ({ children }) => {
|
||||
})
|
||||
|
||||
if (count === 1) {
|
||||
return <section className='shadow'>
|
||||
return <section className='shadow hover:shadow-xl duration-200'>
|
||||
{children}
|
||||
</section>
|
||||
}
|
||||
@@ -30,16 +30,16 @@ 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='shadow hidden lg:block mb-5 py-4 px-5 bg-white dark:bg-gray-800 hover:shadow-xl 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' : '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'} onClick={() => { tabClickHandle(index) }}>
|
||||
{item?.key}
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
{children.map((item, index) => {
|
||||
return <section key={index} className={`${currentTab === index ? 'block' : 'hidden'}`}>
|
||||
return <section key={index} className={`${currentTab === index ? 'block animate__animated animate__fadeIn animate__faster' : 'hidden'}`}>
|
||||
{item}
|
||||
</section>
|
||||
})}
|
||||
|
||||
@@ -12,12 +12,12 @@ const TagItem = ({ tag, selected }) => {
|
||||
return (
|
||||
<Link href={selected ? '/' : `/tag/${encodeURIComponent(tag.name)}`} passHref>
|
||||
<li
|
||||
className={`notion-${tag.color}_background list-none cursor-pointer rounded-md
|
||||
className={`notion-${tag.color}_background dark:bg-gray-700 list-none cursor-pointer rounded-md
|
||||
duration-200 mr-1 my-1 px-2 py-1 text-sm whitespace-nowrap
|
||||
text-gray-600 hover:bg-gray-200 dark:hover:bg-gray-800 dark:hover:text-white`}>
|
||||
<a>
|
||||
hover:bg-gray-200 dark:hover:bg-gray-800 `}>
|
||||
<div className='text-gray-600 dark:text-gray-300 dark:hover:text-white'>
|
||||
{selected && <FontAwesomeIcon icon={faTag} className='mr-1'/>} {`${tag.name} `} {tag.count ? `(${tag.count})` : ''}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import React from 'react'
|
||||
import TagItem from '@/components/TagItem'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faTags } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
/**
|
||||
* 横向的标签列表
|
||||
* @param tags
|
||||
@@ -15,7 +12,7 @@ const TagList = ({ tags, currentTag }) => {
|
||||
return <></>
|
||||
}
|
||||
return <ul className='flex py-1 space-x-3'>
|
||||
<li className='w-20 py-2 dark:text-gray-200 whitespace-nowrap'><FontAwesomeIcon icon={faTags} className='mr-2'/>标签:</li>
|
||||
<li className='w-20 py-2 dark:text-gray-200 whitespace-nowrap'>标签:</li>
|
||||
{tags.map(tag => {
|
||||
const selected = tag.name === currentTag
|
||||
return <TagItem key={tag.name} tag={tag} selected={selected}/>
|
||||
|
||||
@@ -7,7 +7,7 @@ import BLOG from '@/blog.config'
|
||||
*/
|
||||
const ThirdPartyScript = () => {
|
||||
return (<>
|
||||
{BLOG.DaoVoiceId && (<>
|
||||
{BLOG.comment?.DaoVoiceId && (<>
|
||||
{/* DaoVoice 反馈 */}
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
@@ -18,7 +18,7 @@ const ThirdPartyScript = () => {
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
daovoice('init', {
|
||||
app_id: "${BLOG.DaoVoiceId}"
|
||||
app_id: "${BLOG.comment.DaoVoiceId}"
|
||||
});
|
||||
daovoice('update');
|
||||
`
|
||||
@@ -30,13 +30,25 @@ const ThirdPartyScript = () => {
|
||||
{BLOG.googleAdsenseId && (<script data-ad-client={BLOG.googleAdsenseId} async
|
||||
src='https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js'/>)}
|
||||
|
||||
{BLOG.TidioId && (<>
|
||||
{BLOG.comment?.TidioId && (<>
|
||||
{/* Tidio在线反馈 */}
|
||||
<script async
|
||||
src={`//code.tidio.co/${BLOG.TidioId}.js`}
|
||||
src={`//code.tidio.co/${BLOG.comment.TidioId}.js`}
|
||||
/>
|
||||
</>)}
|
||||
|
||||
{/* */}
|
||||
{BLOG.gitter && (<>
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
((window.gitter = {}).chat = {}).options = {
|
||||
room: 'tangly1024/community'
|
||||
};
|
||||
`
|
||||
}}/>
|
||||
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
|
||||
</>)}
|
||||
|
||||
{/* 代码统计 */}
|
||||
{BLOG.isProd && (<>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
import throttle from 'lodash.throttle'
|
||||
import { uuidToId } from 'notion-utils'
|
||||
import Progress from './Progress'
|
||||
@@ -12,8 +12,9 @@ import Progress from './Progress'
|
||||
*/
|
||||
const Toc = ({ toc, targetRef }) => {
|
||||
// 无目录就直接返回空
|
||||
if (!toc || toc.length < 1) return <></>
|
||||
|
||||
if (!toc || toc.length < 1) {
|
||||
return <></>
|
||||
}
|
||||
// 监听滚动事件
|
||||
React.useEffect(() => {
|
||||
window.addEventListener('scroll', actionSectionScrollSpy)
|
||||
@@ -26,7 +27,7 @@ const Toc = ({ toc, targetRef }) => {
|
||||
// 同步选中目录事件
|
||||
const [activeSection, setActiveSection] = React.useState(null)
|
||||
const throttleMs = 100
|
||||
const actionSectionScrollSpy = useCallback(throttle(() => {
|
||||
const actionSectionScrollSpy = React.useCallback(throttle(() => {
|
||||
const sections = document.getElementsByClassName('notion-h')
|
||||
let prevBBox = null
|
||||
let currentSectionId = activeSection
|
||||
@@ -63,14 +64,14 @@ const Toc = ({ toc, targetRef }) => {
|
||||
key={id}
|
||||
href={`#${id}`}
|
||||
className={`notion-table-of-contents-item duration-300 transform font-light
|
||||
notion-table-of-contents-item-indent-level-${tocItem.indentLevel}
|
||||
${activeSection === id && ' font-bold text-gray-600 dark:text-gray-300'}`}
|
||||
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
marginLeft: tocItem.indentLevel * 16
|
||||
}}
|
||||
className={`${activeSection === id && ' font-bold text-red-400 underline'}`}
|
||||
>
|
||||
{tocItem.text}
|
||||
</span>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faListOl } from '@fortawesome/free-solid-svg-icons'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
/**
|
||||
* 点击召唤目录抽屉
|
||||
@@ -11,6 +12,9 @@ import { faListOl } from '@fortawesome/free-solid-svg-icons'
|
||||
* @constructor
|
||||
*/
|
||||
const TocDrawerButton = (props) => {
|
||||
if (!BLOG.widget?.showToc) {
|
||||
return <></>
|
||||
}
|
||||
const { locale } = useGlobal()
|
||||
const [show, switchShow] = useState(false)
|
||||
const scrollListener = () => {
|
||||
@@ -27,10 +31,10 @@ const TocDrawerButton = (props) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<div id='toc-drawer-button' className='right-3 fixed bottom-60 duration-500 z-20'>
|
||||
<div onClick={props.onClick} className={(show ? 'animate__fadeInRight' : 'hidden') + ' py-3 px-3.5 animate__animated glassmorphism rounded-xl cursor-pointer shadow-card' }>
|
||||
<div className='dark:text-gray-200 text-center transform hover:scale-150 duration-200 text-xs' title={locale.POST.TOP} >
|
||||
<FontAwesomeIcon icon={faListOl} />
|
||||
<div id='toc-drawer-button' className='right-1 fixed bottom-52 z-20'>
|
||||
<div onClick={props.onClick} className={(show ? 'animate__fadeInRight' : 'hidden') + ' animate__animated animate__faster glassmorphism cursor-pointer' }>
|
||||
<div className='dark:text-gray-200 text-center transform hover:scale-150 duration-200 text-xs flex justify-center items-center w-8 h-8' title={locale.POST.TOP} >
|
||||
<FontAwesomeIcon icon={faListOl}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,10 +3,10 @@ import SideBarDrawer from '@/components/SideBarDrawer'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { faBars, faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import Link from 'next/link'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import SearchDrawer from './SearchDrawer'
|
||||
import throttle from 'lodash.throttle'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import Logo from './Logo'
|
||||
import SearchDrawer from './SearchDrawer'
|
||||
|
||||
let windowTop = 0
|
||||
|
||||
@@ -15,7 +15,7 @@ let windowTop = 0
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const TopNav = ({ tags, currentTag, post, posts, categories, currentCategory, autoHide = true }) => {
|
||||
const TopNav = ({ tags, currentTag, post, slot, categories, currentCategory, autoHide = true }) => {
|
||||
const drawer = useRef()
|
||||
const { locale } = useGlobal()
|
||||
const searchDrawer = useRef()
|
||||
@@ -35,38 +35,36 @@ const TopNav = ({ tags, currentTag, post, posts, categories, currentCategory, au
|
||||
|
||||
// 监听滚动
|
||||
useEffect(() => {
|
||||
if (autoHide) {
|
||||
if (BLOG.topNavType === 'autoCollapse') {
|
||||
scrollTrigger()
|
||||
window.addEventListener('scroll', scrollTrigger)
|
||||
}
|
||||
return () => {
|
||||
autoHide && window.removeEventListener('scroll', scrollTrigger)
|
||||
BLOG.autoCollapsedNavBar && window.removeEventListener('scroll', scrollTrigger)
|
||||
}
|
||||
}, [])
|
||||
return (<div id='top-nav' className='sticky top-0 z-40 block lg:hidden'>
|
||||
return (<div id='top-nav' className='z-40 block lg:hidden'>
|
||||
{/* 侧面抽屉 */}
|
||||
<SideBarDrawer post={post} currentTag={currentTag} cRef={drawer} tags={tags} posts={posts} categories={categories} currentCategory={currentCategory}/>
|
||||
<SideBarDrawer post={post} currentTag={currentTag} cRef={drawer} tags={tags} slot={slot} categories={categories} currentCategory={currentCategory}/>
|
||||
<SearchDrawer cRef={searchDrawer}/>
|
||||
|
||||
{/* 导航栏 */}
|
||||
<div id='sticky-nav' className='flex animate__animated animate__fadeIn fixed lg:relative w-full top-0 z-20 transform duration-500'>
|
||||
<div className='w-full flex justify-between items-center p-4 glassmorphism'>
|
||||
<div id='sticky-nav' className={`${BLOG.topNavType !== 'normal' ? 'fixed' : ''} flex animate__animated animate__fadeIn lg:relative w-full top-0 z-20 transform duration-500`}>
|
||||
<div className='w-full flex justify-between items-center p-4 bg-black text-white'>
|
||||
{/* 左侧LOGO 标题 */}
|
||||
<div className='flex flex-none flex-grow-0'>
|
||||
<div onClick={() => { drawer.current.handleSwitchSideDrawerVisible() }}
|
||||
className='w-8 cursor-pointer dark:text-gray-300'>
|
||||
className='w-8 cursor-pointer'>
|
||||
<FontAwesomeIcon icon={faBars} size={'lg'}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link href='/' passHref>
|
||||
<a>
|
||||
<h1 className='cursor-pointer ml-2 w-full hover:scale-105 duration-200 transform font-serif dark:text-gray-200 whitespace-nowrap overflow-x-hidden'>{ BLOG.title }</h1>
|
||||
</a>
|
||||
</Link>
|
||||
<div className='flex'>
|
||||
<Logo/>
|
||||
</div>
|
||||
|
||||
{/* 右侧功能 */}
|
||||
<div className='mr-1 flex flex-nowrap flex-grow justify-end items-center text-sm space-x-4 font-serif dark:text-gray-200'>
|
||||
<div className='mr-1 flex justify-end items-center text-sm space-x-4 font-serif dark:text-gray-200'>
|
||||
<div className="cursor-pointer block lg:hidden" onClick={() => { searchDrawer?.current?.show() }}>
|
||||
<FontAwesomeIcon icon={faSearch} className="mr-2" />{locale.NAV.SEARCH}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import CommonHead from '@/components/CommonHead'
|
||||
import FloatDarkModeButton from '@/components/FloatDarkModeButton'
|
||||
import Footer from '@/components/Footer'
|
||||
@@ -35,7 +36,9 @@ const BaseLayout = ({
|
||||
tags,
|
||||
meta,
|
||||
post,
|
||||
totalPosts,
|
||||
postCount,
|
||||
sideBarSlot,
|
||||
rightAreaSlot,
|
||||
currentSearch,
|
||||
currentCategory,
|
||||
currentTag,
|
||||
@@ -49,15 +52,15 @@ const BaseLayout = ({
|
||||
|
||||
<CommonHead meta={meta} />
|
||||
|
||||
<TopNav tags={tags} post={post} posts={totalPosts} currentSearch={currentSearch} categories={categories} currentCategory={currentCategory} />
|
||||
<TopNav tags={tags} post={post} slot={sideBarSlot} currentSearch={currentSearch} categories={categories} currentCategory={currentCategory} />
|
||||
|
||||
<>{headerSlot}</>
|
||||
|
||||
<main id='wrapper' className='flex justify-center flex-1 mx-auto md:pt-14 pb-12'>
|
||||
<aside id='left' className='hidden lg:block flex-col w-60 mr-4'>
|
||||
<SideAreaLeft targetRef={targetRef} post={post} posts={totalPosts} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
|
||||
</aside>
|
||||
<section id='center' className='flex-grow mt-14 md:mt-0 max-w-4xl min-h-screen' ref={targetRef}>
|
||||
<div className='h-0.5 w-full bg-gray-700 dark:bg-gray-600 hidden lg:block'></div>
|
||||
|
||||
<main id='wrapper' className='flex justify-center flex-1 mx-auto pb-12'>
|
||||
<SideAreaLeft targetRef={targetRef} post={post} postCount={postCount} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
|
||||
<section id='center' className={`${BLOG.topNavType !== 'normal' ? 'mt-14' : ''} flex-grow md:mt-0 max-w-5xl min-h-screen w-full`} ref={targetRef}>
|
||||
{onLoading
|
||||
? <LoadingCover/>
|
||||
: <>
|
||||
@@ -65,14 +68,12 @@ const BaseLayout = ({
|
||||
</>
|
||||
}
|
||||
</section>
|
||||
<aside id='right' className='hidden 2xl:block flex-col w-60 ml-4'>
|
||||
<SideAreaRight targetRef={targetRef} post={post} posts={totalPosts} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
|
||||
</aside>
|
||||
<SideAreaRight targetRef={targetRef} post={post} slot={rightAreaSlot} postCount={postCount} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
|
||||
</main>
|
||||
|
||||
<Footer title={meta.title}/>
|
||||
<JumpToTopButton targetRef={targetRef} showPercent={false} />
|
||||
<JumpToBottomButton targetRef={targetRef} showPercent={false}/>
|
||||
<JumpToTopButton showPercent={false} />
|
||||
<JumpToBottomButton showPercent={false}/>
|
||||
<FloatDarkModeButton/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ bszCaller = {
|
||||
try {
|
||||
e(t), scriptTag && scriptTag.parentElement && scriptTag.parentElement.removeChild && scriptTag.parentElement.removeChild(scriptTag)
|
||||
} catch (t) {
|
||||
console.log(t), bszTag.hides()
|
||||
// console.log(t), bszTag.hides()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
2
lib/cache/memory_cache.js
vendored
2
lib/cache/memory_cache.js
vendored
@@ -1,7 +1,7 @@
|
||||
import cache from 'memory-cache'
|
||||
import BLOG from 'blog.config'
|
||||
|
||||
const cacheTime = BLOG.isProd ? 60 : 60 * 60
|
||||
const cacheTime = BLOG.isProd ? 10 * 60 : 120 * 60 // 120 minutes for dev,10 minutes for prod
|
||||
|
||||
export async function getCacheFromMemory (key, options) {
|
||||
return cache.get(key)
|
||||
|
||||
@@ -26,7 +26,9 @@ export default {
|
||||
POSTS: 'Posts',
|
||||
VISITORS: 'Visitors',
|
||||
VIEWS: 'Views',
|
||||
COPYRIGHT_NOTICE: 'All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!'
|
||||
COPYRIGHT_NOTICE: 'All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!',
|
||||
RESULT_OF_SEARCH: 'Results Found',
|
||||
ARTICLE_DETAIL: 'Article Details'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: 'Prev',
|
||||
|
||||
@@ -21,14 +21,16 @@ export default {
|
||||
URL_COPIED: '链接已复制!',
|
||||
TABLE_OF_CONTENTS: '目录',
|
||||
RELATE_POSTS: '相关文章',
|
||||
COPYRIGHT: '版权声明',
|
||||
COPYRIGHT: '声明',
|
||||
AUTHOR: '作者',
|
||||
URL: '链接',
|
||||
ANALYTICS: '统计',
|
||||
POSTS: '篇文章',
|
||||
VISITORS: '位访客',
|
||||
VIEWS: '次查看',
|
||||
COPYRIGHT_NOTICE: '本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!'
|
||||
COPYRIGHT_NOTICE: '本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。',
|
||||
RESULT_OF_SEARCH: '篇搜索到的结果',
|
||||
ARTICLE_DETAIL: '文章详情'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: '上一页',
|
||||
|
||||
@@ -29,6 +29,7 @@ export async function getAllPosts ({ notionPageData, from, includePage = false }
|
||||
for (let i = 0; i < pageIds.length; i++) {
|
||||
const id = pageIds[i]
|
||||
const properties = (await getPageProperties(id, pageBlock, schema)) || null
|
||||
properties.slug = properties.slug ?? properties.id
|
||||
properties.createdTime = new Date(pageBlock[id].value?.created_time).toString()
|
||||
properties.lastEditedTime = new Date(pageBlock[id].value?.last_edited_time).toString()
|
||||
properties.fullWidth = pageBlock[id].value?.format?.page_full_width ?? false
|
||||
|
||||
@@ -1,7 +1,52 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { idToUuid } from 'notion-utils'
|
||||
import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
|
||||
import { getPostBlocks } from '@/lib/notion/getPostBlocks'
|
||||
import { idToUuid } from 'notion-utils'
|
||||
import { getAllCategories } from './getAllCategories'
|
||||
import { getAllPosts } from './getAllPosts'
|
||||
import { getAllTags } from './getAllTags'
|
||||
|
||||
/**
|
||||
* 获取博客数据
|
||||
* @param {*} pageId
|
||||
* @param {*} from
|
||||
* @returns
|
||||
* allPosts 所有博客
|
||||
* categories 所有分类
|
||||
* tags 所有标签
|
||||
*/
|
||||
export async function getGlobalNotionData ({
|
||||
pageId = BLOG.notionPageId,
|
||||
from,
|
||||
latestPostCount = 5,
|
||||
tagsCount = 16,
|
||||
includePage
|
||||
}) {
|
||||
const notionPageData = await getNotionPageData({ pageId, from })
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const allPosts = await getAllPosts({ notionPageData, from, includePage })
|
||||
const postCount = allPosts?.length
|
||||
const categories = await getAllCategories(allPosts)
|
||||
const tags = await getAllTags({ allPosts, tagOptions, sliceCount: tagsCount })
|
||||
// 深拷贝
|
||||
let latestPosts = Object.create(allPosts)
|
||||
// 时间排序
|
||||
latestPosts.sort((a, b) => {
|
||||
const dateA = new Date(a?.lastEditedTime || a.createdTime)
|
||||
const dateB = new Date(b?.lastEditedTime || b.createdTime)
|
||||
return dateB - dateA
|
||||
})
|
||||
|
||||
// 只取前五
|
||||
latestPosts = latestPosts.slice(0, latestPostCount)
|
||||
return {
|
||||
allPosts,
|
||||
latestPosts,
|
||||
categories,
|
||||
postCount,
|
||||
tags
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定notion的collection数据
|
||||
@@ -9,7 +54,7 @@ import { getPostBlocks } from '@/lib/notion/getPostBlocks'
|
||||
* @param from 请求来源
|
||||
* @returns {Promise<JSX.Element|*|*[]>}
|
||||
*/
|
||||
export async function getNotionPageData ({ pageId = BLOG.notionPageId, from }) {
|
||||
export async function getNotionPageData ({ pageId, from }) {
|
||||
// 尝试从缓存获取
|
||||
const cacheKey = 'page_record_map_' + pageId
|
||||
const data = await getDataFromCache(cacheKey)
|
||||
@@ -54,7 +99,10 @@ async function getPageRecordMapByNotionAPI ({ pageId, from }) {
|
||||
const tagOptions = getTagOptions(schema)
|
||||
|
||||
// Check Type Page-Database和Inline-Database
|
||||
if (rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view') {
|
||||
if (
|
||||
rawMetadata?.type !== 'collection_view_page' &&
|
||||
rawMetadata?.type !== 'collection_view'
|
||||
) {
|
||||
console.warn(`pageId "${pageId}" is not a database`)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -20,6 +20,22 @@ export async function getPostBlocks (id, from) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 去掉不用的字段
|
||||
for (const j in pageBlock?.block) {
|
||||
const b = pageBlock?.block[j]
|
||||
if (b) {
|
||||
delete b.role
|
||||
delete b.value?.version
|
||||
delete b.value?.created_time
|
||||
delete b.value?.last_edited_time
|
||||
delete b.value?.created_by_table
|
||||
delete b.value?.created_by_id
|
||||
delete b.value?.last_edited_by_table
|
||||
delete b.value?.last_edited_by_id
|
||||
delete b.value?.space_id
|
||||
}
|
||||
}
|
||||
|
||||
if (pageBlock) {
|
||||
await setDataToCache(cacheKey, pageBlock)
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ export function generateRss (posts) {
|
||||
posts.forEach(post => {
|
||||
feed.addItem({
|
||||
title: post.title,
|
||||
id: `${BLOG.link}/${post.slug}`,
|
||||
link: `${BLOG.link}/${post.slug}`,
|
||||
id: `${BLOG.link}/article/${post.slug}`,
|
||||
link: `${BLOG.link}/article/${post.slug}`,
|
||||
description: post.summary,
|
||||
date: new Date(post?.date?.start_date || post.createdTime)
|
||||
})
|
||||
|
||||
@@ -33,15 +33,15 @@
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"memory-cache": "^0.2.0",
|
||||
"next": "^12.0.5",
|
||||
"notion-client": "^4.12.0",
|
||||
"notion-utils": "^4.12.0",
|
||||
"notion-client": "4.13.0",
|
||||
"notion-utils": "4.12.0",
|
||||
"preact": "^10.5.15",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"react": "17.0.2",
|
||||
"react-cookies": "^0.1.1",
|
||||
"react-cusdis": "^2.0.1",
|
||||
"react-dom": "17.0.2",
|
||||
"react-notion-x": "4.6.5",
|
||||
"react-notion-x": "4.13.0",
|
||||
"smoothscroll-polyfill": "^0.4.4",
|
||||
"typed.js": "^2.0.12",
|
||||
"use-ackee": "^3.0.0"
|
||||
|
||||
@@ -16,12 +16,12 @@ export default function Custom404 () {
|
||||
// 延时3秒如果加载失败就返回首页
|
||||
setTimeout(() => {
|
||||
if (window) {
|
||||
const article = document.getElementById('article-wrapper')
|
||||
const article = document.getElementById('container')
|
||||
if (!article) {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
}, 3000)
|
||||
}, 30000000)
|
||||
})
|
||||
|
||||
return <BaseLayout meta={{ title: `${BLOG.title} | 页面找不到啦` }}>
|
||||
|
||||
@@ -18,7 +18,7 @@ class MyDocument extends Document {
|
||||
<ThirdPartyScript />
|
||||
</Head>
|
||||
|
||||
<body className='bg-gray-100 dark:bg-gray-900 duration-200'>
|
||||
<body className={`${BLOG.font} bg-day dark:bg-night duration-200`}>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import BLOG from '@/blog.config'
|
||||
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 {
|
||||
getPostBlocks
|
||||
} from '@/lib/notion'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import Custom404 from '@/pages/404'
|
||||
import { getPageTableOfContents } from 'notion-utils'
|
||||
import 'prismjs'
|
||||
@@ -12,7 +15,6 @@ 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的文章
|
||||
@@ -32,15 +34,22 @@ const About = ({ post, blockMap, tags, prev, next, allPosts, categories }) => {
|
||||
tags: []
|
||||
}
|
||||
|
||||
return <BaseLayout meta={meta} tags={tags} post={post} totalPosts={allPosts} categories={categories}>
|
||||
<ArticleDetail post={post} blockMap={blockMap} allPosts={allPosts}/>
|
||||
</BaseLayout>
|
||||
return (
|
||||
<BaseLayout
|
||||
meta={meta}
|
||||
tags={tags}
|
||||
post={post}
|
||||
totalPosts={allPosts}
|
||||
categories={categories}
|
||||
>
|
||||
<ArticleDetail post={post} blockMap={blockMap} allPosts={allPosts} prev={prev} next={next} />
|
||||
</BaseLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'about-props'
|
||||
const notionPageData = await getNotionPageData({ from })
|
||||
let allPosts = await getAllPosts({ notionPageData, from, includePage: true })
|
||||
const { allPosts, categories, tags, postCount, latestPosts } = await getGlobalNotionData({ from, includePage: true })
|
||||
const post = allPosts.find(p => p.slug === 'about')
|
||||
|
||||
if (!post) {
|
||||
@@ -50,20 +59,17 @@ export async function getStaticProps () {
|
||||
const blockMap = await getPostBlocks(post.id, 'slug')
|
||||
post.toc = []
|
||||
if (blockMap) {
|
||||
post.blockMap = 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 },
|
||||
props: { post, blockMap, tags, prev, next, allPosts, categories, postCount, latestPosts },
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
import { getAllCategories, getAllPosts, getAllTags } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import BlogPostArchive from '@/components/BlogPostArchive'
|
||||
import Live2D from '@/components/Live2D'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'index'
|
||||
const notionPageData = await getNotionPageData({ from })
|
||||
const allPosts = await getAllPosts({ notionPageData, from })
|
||||
const categories = await getAllCategories(allPosts)
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const tags = await getAllTags({ allPosts, tagOptions })
|
||||
const { allPosts, categories, tags, postCount } =
|
||||
await getGlobalNotionData({ from, includePage: true })
|
||||
|
||||
return {
|
||||
props: {
|
||||
allPosts,
|
||||
posts: allPosts,
|
||||
tags,
|
||||
categories
|
||||
categories,
|
||||
postCount
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
const Index = ({ allPosts, tags, categories }) => {
|
||||
const Index = ({ posts, tags, categories, postCount }) => {
|
||||
const { locale } = useGlobal()
|
||||
// 深拷贝
|
||||
const postsSortByDate = Object.create(allPosts)
|
||||
const postsSortByDate = Object.create(posts)
|
||||
|
||||
// 时间排序
|
||||
postsSortByDate.sort((a, b) => {
|
||||
@@ -42,7 +40,7 @@ const Index = ({ allPosts, tags, categories }) => {
|
||||
type: 'website'
|
||||
}
|
||||
|
||||
const archivePosts = { }
|
||||
const archivePosts = {}
|
||||
|
||||
postsSortByDate.forEach(post => {
|
||||
const date = post.date.start_date.slice(0, 7)
|
||||
@@ -53,29 +51,32 @@ const Index = ({ allPosts, tags, categories }) => {
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (window) {
|
||||
const anchor = window.location.hash
|
||||
if (anchor) {
|
||||
setTimeout(() => {
|
||||
const anchorElement = document.getElementById(anchor.substring(1))
|
||||
if (anchorElement) { anchorElement.scrollIntoView({ block: 'start', behavior: 'smooth' }) }
|
||||
}, 300)
|
||||
}
|
||||
useEffect(() => {
|
||||
if (window) {
|
||||
const anchor = window.location.hash
|
||||
if (anchor) {
|
||||
setTimeout(() => {
|
||||
const anchorElement = document.getElementById(anchor.substring(1))
|
||||
if (anchorElement) {
|
||||
anchorElement.scrollIntoView({ block: 'start', behavior: 'smooth' })
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<BaseLayout meta={meta} tags={tags} categories={categories}>
|
||||
<div className='mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 rounded-xl shadow-md min-h-full'>
|
||||
{Object.keys(archivePosts).map(archiveTitle => (
|
||||
<BlogPostArchive key={archiveTitle} posts={archivePosts[archiveTitle]} archiveTitle={archiveTitle}/>
|
||||
))}
|
||||
</div>
|
||||
{BLOG.showPet && <Live2D/>}
|
||||
<BaseLayout meta={meta} tags={tags} categories={categories} postCount={postCount}>
|
||||
<div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 shadow-md min-h-full">
|
||||
{Object.keys(archivePosts).map(archiveTitle => (
|
||||
<BlogPostArchive
|
||||
key={archiveTitle}
|
||||
posts={archivePosts[archiveTitle]}
|
||||
archiveTitle={archiveTitle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<Live2D />
|
||||
</BaseLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
import { getAllCategories, getAllPosts, getAllTags, getPostBlocks } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import { getPageTableOfContents } from 'notion-utils'
|
||||
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import Custom404 from '@/pages/404'
|
||||
|
||||
import ArticleDetail from '@/components/ArticleDetail'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import { getAllPosts, getPostBlocks } from '@/lib/notion'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import Custom404 from '@/pages/404'
|
||||
import { getPageTableOfContents } from 'notion-utils'
|
||||
|
||||
/**
|
||||
* 根据notion的slug访问页面
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const Slug = ({ post, blockMap, tags, prev, next, allPosts, recommendPosts, categories }) => {
|
||||
const Slug = ({
|
||||
post,
|
||||
tags,
|
||||
prev,
|
||||
next,
|
||||
allPosts,
|
||||
recommendPosts,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
}) => {
|
||||
if (!post) {
|
||||
return <Custom404 />
|
||||
}
|
||||
@@ -24,26 +32,42 @@ const Slug = ({ post, blockMap, tags, prev, next, allPosts, recommendPosts, cate
|
||||
tags: post.tags
|
||||
}
|
||||
|
||||
return <BaseLayout meta={meta} tags={tags} post={post} totalPosts={allPosts} categories={categories}>
|
||||
<ArticleDetail post={post} blockMap={blockMap} recommendPosts={recommendPosts} prev={prev} next={next}/>
|
||||
</BaseLayout>
|
||||
return (
|
||||
<BaseLayout
|
||||
meta={meta}
|
||||
tags={tags}
|
||||
post={post}
|
||||
postCount={postCount}
|
||||
latestPosts={latestPosts}
|
||||
totalPosts={allPosts}
|
||||
categories={categories}
|
||||
>
|
||||
<ArticleDetail
|
||||
post={post}
|
||||
recommendPosts={recommendPosts}
|
||||
prev={prev}
|
||||
next={next}
|
||||
/>
|
||||
</BaseLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticPaths () {
|
||||
let posts = []
|
||||
if (BLOG.isProd) {
|
||||
posts = await getAllPosts({ from: 'slug - paths', includePage: true })
|
||||
posts = await getAllPosts({ from: 'slug - paths', includePage: false })
|
||||
}
|
||||
return {
|
||||
paths: posts.map(row => `${BLOG.path}/article/${row.slug}`),
|
||||
paths: posts.map(row => ({ params: { slug: row.slug } })),
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticProps ({ params: { slug } }) {
|
||||
const from = `slug-props-${slug}`
|
||||
const notionPageData = await getNotionPageData({ from })
|
||||
let allPosts = await getAllPosts({ notionPageData, from, includePage: true })
|
||||
const { allPosts, categories, tags, postCount, latestPosts } =
|
||||
await getGlobalNotionData({ from, includePage: false })
|
||||
|
||||
const post = allPosts.find(p => p.slug === slug)
|
||||
|
||||
if (!post) {
|
||||
@@ -52,14 +76,12 @@ export async function getStaticProps ({ params: { slug } }) {
|
||||
|
||||
const blockMap = await getPostBlocks(post.id, 'slug')
|
||||
if (blockMap) {
|
||||
post.blockMap = blockMap
|
||||
post.content = Object.keys(blockMap.block)
|
||||
post.toc = getPageTableOfContents(post, blockMap)
|
||||
delete post.content
|
||||
}
|
||||
|
||||
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]
|
||||
@@ -68,7 +90,17 @@ export async function getStaticProps ({ params: { slug } }) {
|
||||
const recommendPosts = getRecommendPost(post, allPosts)
|
||||
|
||||
return {
|
||||
props: { post, blockMap, tags, prev, next, allPosts, recommendPosts, categories },
|
||||
props: {
|
||||
post,
|
||||
tags,
|
||||
prev,
|
||||
next,
|
||||
allPosts,
|
||||
recommendPosts,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
@@ -86,11 +118,7 @@ function getRecommendPost (post, allPosts, count = 5) {
|
||||
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
|
||||
p => p && p.tags && p.tags.includes(currentTag) && p.slug !== post.slug
|
||||
)
|
||||
}
|
||||
shuffleSort(filteredPosts)
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import { getAllCategories, getAllPosts, getAllTags } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogPostListScroll from '@/components/BlogPostListScroll'
|
||||
import CategoryList from '@/components/CategoryList'
|
||||
import StickyBar from '@/components/StickyBar'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import BlogPostListScroll from '@/components/BlogPostListScroll'
|
||||
import React from 'react'
|
||||
import CategoryList from '@/components/CategoryList'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import React from 'react'
|
||||
|
||||
export default function Category ({ tags, allPosts, filteredPosts, category, categories }) {
|
||||
export default function Category ({ tags, posts, category, categories, latestPosts, postCount }) {
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${category} | ${locale.COMMON.CATEGORY} | ${BLOG.title}`,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
return <BaseLayout meta={meta} tags={tags} currentCategory={category} totalPosts={allPosts} categories={categories}>
|
||||
return <BaseLayout meta={meta} tags={tags} currentCategory={category} postCount={postCount} latestPosts={latestPosts} categories={categories}>
|
||||
<StickyBar>
|
||||
<CategoryList currentCategory={category} categories={categories} />
|
||||
</StickyBar>
|
||||
<div className='md:mt-8'>
|
||||
<BlogPostListScroll posts={filteredPosts} tags={tags} currentCategory={category}/>
|
||||
<BlogPostListScroll posts={posts} tags={tags} currentCategory={category}/>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
}
|
||||
@@ -28,31 +27,26 @@ export default function Category ({ tags, allPosts, filteredPosts, category, cat
|
||||
export async function getStaticProps ({ params }) {
|
||||
const from = 'category-props'
|
||||
const category = params.category
|
||||
const notionPageData = await getNotionPageData({ from })
|
||||
const allPosts = await getAllPosts({ notionPageData, from })
|
||||
const categories = await getAllCategories(allPosts)
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const tags = await getAllTags({ allPosts, tagOptions })
|
||||
const { allPosts, categories, tags, postCount, latestPosts } = await getGlobalNotionData({ from })
|
||||
const filteredPosts = allPosts.filter(
|
||||
post => post && post.category && post.category.includes(category)
|
||||
)
|
||||
return {
|
||||
props: {
|
||||
tags,
|
||||
allPosts,
|
||||
filteredPosts,
|
||||
posts: filteredPosts,
|
||||
category,
|
||||
categories
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths () {
|
||||
let posts = []
|
||||
let categories = []
|
||||
posts = await getAllPosts({ from: 'category-path' })
|
||||
categories = await getAllCategories(posts)
|
||||
const from = 'category-paths'
|
||||
const { categories } = await getGlobalNotionData({ from })
|
||||
return {
|
||||
paths: Object.keys(categories).map(category => ({ params: { category } })),
|
||||
fallback: true
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import { getAllCategories, getAllPosts, getAllTags } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { faFolder, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
export default function Category ({ tags, allPosts, categories }) {
|
||||
export default function Category ({ tags, allPosts, categories, postCount, latestPosts }) {
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${locale.COMMON.CATEGORY} | ${BLOG.title}`,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
return <BaseLayout meta={meta} totalPosts={allPosts} tags={tags}>
|
||||
return <BaseLayout meta={meta} totalPosts={allPosts} tags={tags} postCount={postCount} latestPosts={latestPosts}>
|
||||
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
|
||||
<div className='dark:text-gray-200 mb-5'><FontAwesomeIcon icon={faThList} className='mr-4' />{locale.COMMON.CATEGORY}:</div>
|
||||
<div id='category-list' className='duration-200 flex flex-wrap'>
|
||||
@@ -31,17 +30,16 @@ export default function Category ({ tags, allPosts, categories }) {
|
||||
}
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'tag-index-props'
|
||||
const notionPageData = await getNotionPageData({ from })
|
||||
const allPosts = await getAllPosts({ notionPageData, from })
|
||||
const categories = await getAllCategories(allPosts)
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const tags = await getAllTags({ allPosts, sliceCount: 12, tagOptions })
|
||||
const from = 'category-index-props'
|
||||
const { allPosts, categories, tags, postCount, latestPosts } = await getGlobalNotionData({ from })
|
||||
|
||||
return {
|
||||
props: {
|
||||
tags,
|
||||
allPosts,
|
||||
categories
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
|
||||
@@ -1,25 +1,46 @@
|
||||
import { getAllCategories, getAllPosts, getAllTags } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import BlogPostListPage from '@/components/BlogPostListPage'
|
||||
import BlogPostListScroll from '@/components/BlogPostListScroll'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
import Header from '@/components/Header'
|
||||
import LatestPostsGroup from '@/components/LatestPostsGroup'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import { getPostBlocks } from '@/lib/notion'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'index'
|
||||
const notionPageData = await getNotionPageData({ from })
|
||||
const allPosts = await getAllPosts({ notionPageData, from })
|
||||
const categories = await getAllCategories(allPosts)
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const tags = await getAllTags({ allPosts, tagOptions })
|
||||
const { allPosts, latestPosts, categories, tags, postCount } = await getGlobalNotionData({ from })
|
||||
const meta = {
|
||||
title: `${BLOG.title}`,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
|
||||
// 处理分页
|
||||
const page = 1
|
||||
let postsToShow = []
|
||||
if (BLOG.postListStyle !== 'page') {
|
||||
postsToShow = Array.from(allPosts)
|
||||
} else {
|
||||
postsToShow = allPosts.slice(
|
||||
BLOG.postsPerPage * (page - 1),
|
||||
BLOG.postsPerPage * page
|
||||
)
|
||||
for (const i in postsToShow) {
|
||||
const post = postsToShow[i]
|
||||
const blockMap = await getPostBlocks(post.id, 'slug')
|
||||
if (blockMap) {
|
||||
post.blockMap = blockMap
|
||||
}
|
||||
}
|
||||
console.log('加载文章预览完成')
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
allPosts,
|
||||
posts: postsToShow,
|
||||
latestPosts,
|
||||
postCount,
|
||||
tags,
|
||||
categories,
|
||||
meta
|
||||
@@ -28,16 +49,26 @@ export async function getStaticProps () {
|
||||
}
|
||||
}
|
||||
|
||||
const Index = ({ allPosts, tags, meta, categories }) => {
|
||||
const Index = ({ posts, tags, meta, categories, postCount, latestPosts }) => {
|
||||
return (
|
||||
<BaseLayout
|
||||
headerSlot={<Header />}
|
||||
headerSlot={BLOG.home.showHomeBanner && <Header />}
|
||||
meta={meta}
|
||||
tags={tags}
|
||||
totalPosts={allPosts}
|
||||
sideBarSlot={<LatestPostsGroup posts={latestPosts} />}
|
||||
rightAreaSlot={
|
||||
BLOG.widget?.showLatestPost && <LatestPostsGroup posts={latestPosts} />
|
||||
}
|
||||
postCount={postCount}
|
||||
categories={categories}
|
||||
>
|
||||
<BlogPostListScroll posts={allPosts} tags={tags} />
|
||||
{BLOG.postListStyle !== 'page'
|
||||
? (
|
||||
<BlogPostListScroll posts={posts} tags={tags} showSummary={true} />
|
||||
)
|
||||
: (
|
||||
<BlogPostListPage posts={posts} tags={tags} postCount={postCount} />
|
||||
)}
|
||||
</BaseLayout>
|
||||
)
|
||||
}
|
||||
|
||||
78
pages/page/[page].js
Normal file
78
pages/page/[page].js
Normal file
@@ -0,0 +1,78 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogPostListPage from '@/components/BlogPostListPage'
|
||||
import Header from '@/components/Header'
|
||||
import LatestPostsGroup from '@/components/LatestPostsGroup'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import { getPostBlocks } from '@/lib/notion'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import Custom404 from '../404'
|
||||
|
||||
const Page = ({ page, posts, tags, meta, categories, postCount, latestPosts }) => {
|
||||
if (!meta || BLOG.postListStyle !== 'page') {
|
||||
return <Custom404/>
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseLayout
|
||||
headerSlot={BLOG.home.showHomeBanner && <Header />}
|
||||
meta={meta}
|
||||
tags={tags}
|
||||
sideBarSlot={<LatestPostsGroup posts={latestPosts} />}
|
||||
rightAreaSlot={BLOG.widget?.showLatestPost && <LatestPostsGroup posts={latestPosts} />}
|
||||
postCount={postCount}
|
||||
categories={categories}
|
||||
>
|
||||
<BlogPostListPage page={page} posts={posts} postCount={postCount} />
|
||||
</BaseLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticPaths () {
|
||||
const from = 'page-paths'
|
||||
const { postCount } = await getGlobalNotionData({ from })
|
||||
const totalPages = Math.ceil(postCount / BLOG.postsPerPage)
|
||||
return {
|
||||
// remove first page, we 're not gonna handle that.
|
||||
paths: Array.from({ length: totalPages - 1 }, (_, i) => ({ params: { page: '' + (i + 2) } })),
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticProps ({ params: { page } }) {
|
||||
const from = `page-${page}`
|
||||
const { allPosts, latestPosts, categories, tags, postCount } = await getGlobalNotionData({ from })
|
||||
const meta = {
|
||||
title: `${page} | Page | ${BLOG.title}`,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
// 处理分页
|
||||
const postsToShow = allPosts.slice(
|
||||
BLOG.postsPerPage * (page - 1),
|
||||
BLOG.postsPerPage * page
|
||||
)
|
||||
|
||||
for (const i in postsToShow) {
|
||||
const post = postsToShow[i]
|
||||
const blockMap = await getPostBlocks(post.id, 'slug')
|
||||
if (blockMap) {
|
||||
post.blockMap = blockMap
|
||||
}
|
||||
}
|
||||
console.log('加载文章预览完成')
|
||||
|
||||
return {
|
||||
props: {
|
||||
page,
|
||||
posts: postsToShow,
|
||||
postCount,
|
||||
latestPosts,
|
||||
tags,
|
||||
categories,
|
||||
meta
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
export default Page
|
||||
@@ -1,57 +1,66 @@
|
||||
import { getAllCategories, getAllPosts, getAllTags } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import StickyBar from '@/components/StickyBar'
|
||||
import BlogPostListScroll from '@/components/BlogPostListScroll'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
import StickyBar from '@/components/StickyBar'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'search-props'
|
||||
const notionPageData = await getNotionPageData({ from })
|
||||
const allPosts = await getAllPosts({ notionPageData, from })
|
||||
const categories = await getAllCategories(allPosts)
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const tags = await getAllTags({ allPosts, tagOptions })
|
||||
const { allPosts, categories, tags, postCount, latestPosts } =
|
||||
await getGlobalNotionData({ from })
|
||||
|
||||
return {
|
||||
props: {
|
||||
allPosts,
|
||||
posts: allPosts,
|
||||
tags,
|
||||
categories
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
const Search = ({ allPosts, tags, categories }) => {
|
||||
// 处理查询过滤 支持标签、关键词过滤
|
||||
const Search = ({ posts, tags, categories, postCount, latestPosts }) => {
|
||||
let filteredPosts = []
|
||||
const searchKey = getSearchKey()
|
||||
if (searchKey) {
|
||||
filteredPosts = allPosts.filter(post => {
|
||||
filteredPosts = posts.filter(post => {
|
||||
const tagContent = post.tags ? post.tags.join(' ') : ''
|
||||
const searchContent = post.title + post.summary + tagContent
|
||||
return searchContent.toLowerCase().includes(searchKey.toLowerCase())
|
||||
})
|
||||
} else {
|
||||
filteredPosts = posts
|
||||
}
|
||||
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${locale.NAV.SEARCH} ${searchKey}| ${BLOG.title} `,
|
||||
title: `${searchKey || ''} | ${locale.NAV.SEARCH} | ${BLOG.title} `,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
return (
|
||||
<BaseLayout meta={meta} tags={tags} totalPosts={allPosts} currentSearch={searchKey} categories={categories}>
|
||||
<StickyBar>
|
||||
<div className='p-4 dark:text-gray-200'><FontAwesomeIcon icon={faSearch} className='mr-1'/> {locale.NAV.SEARCH}: {searchKey}</div>
|
||||
</StickyBar>
|
||||
<div className='md:mt-5'>
|
||||
<BlogPostListScroll posts={filteredPosts} tags={tags} currentSearch={searchKey} />
|
||||
<BaseLayout
|
||||
meta={meta}
|
||||
tags={tags}
|
||||
postCount={postCount}
|
||||
currentSearch={searchKey}
|
||||
categories={categories}
|
||||
>
|
||||
<StickyBar>
|
||||
<div className="p-4 dark:text-gray-200">
|
||||
<FontAwesomeIcon icon={faSearch} className="mr-1" />{' '}
|
||||
{filteredPosts.length} {locale.COMMON.RESULT_OF_SEARCH}
|
||||
</div>
|
||||
</StickyBar>
|
||||
<div className="md:mt-5">
|
||||
<BlogPostListScroll posts={filteredPosts} tags={tags} showSummary={true}/>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { getAllCategories, getAllPosts, getAllTags } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import StickyBar from '@/components/StickyBar'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import BlogPostListScroll from '@/components/BlogPostListScroll'
|
||||
import StickyBar from '@/components/StickyBar'
|
||||
import TagList from '@/components/TagList'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { getAllPosts } from '@/lib/notion'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
|
||||
export default function Tag ({ tags, allPosts, filteredPosts, tag, categories }) {
|
||||
export default function Tag ({ tags, posts, tag, categories, postCount, latestPosts }) {
|
||||
const { locale } = useGlobal()
|
||||
|
||||
const meta = {
|
||||
@@ -21,12 +21,12 @@ export default function Tag ({ tags, allPosts, filteredPosts, tag, categories })
|
||||
const currentTag = tags?.find(r => r?.name === tag)
|
||||
const newTags = currentTag ? [currentTag].concat(tags.filter(r => r?.name !== tag)) : tags.filter(r => r?.name !== tag)
|
||||
|
||||
return <BaseLayout meta={meta} tags={tags} currentTag={tag} categories={categories} totalPosts={allPosts}>
|
||||
return <BaseLayout meta={meta} tags={tags} currentTag={tag} categories={categories} postCount={postCount} latestPosts={latestPosts}>
|
||||
<StickyBar>
|
||||
<TagList tags={newTags} currentTag={tag}/>
|
||||
</StickyBar>
|
||||
<div className='md:mt-8'>
|
||||
<BlogPostListScroll posts={filteredPosts} tags={tags} currentTag={tag}/>
|
||||
<BlogPostListScroll posts={posts} tags={tags} currentTag={tag}/>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
}
|
||||
@@ -34,21 +34,18 @@ export default function Tag ({ tags, allPosts, filteredPosts, tag, categories })
|
||||
export async function getStaticProps ({ params }) {
|
||||
const tag = params.tag
|
||||
const from = 'tag-props'
|
||||
const notionPageData = await getNotionPageData({ from })
|
||||
const allPosts = await getAllPosts({ notionPageData, from })
|
||||
const categories = await getAllCategories(allPosts)
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const tags = await getAllTags({ allPosts, tagOptions, sliceCount: 0 })
|
||||
const { allPosts, categories, tags, postCount, latestPosts } = await getGlobalNotionData({ from, includePage: true, tagsCount: 0 })
|
||||
const filteredPosts = allPosts.filter(
|
||||
post => post && post.tags && post.tags.includes(tag)
|
||||
)
|
||||
return {
|
||||
props: {
|
||||
tags,
|
||||
allPosts,
|
||||
filteredPosts,
|
||||
posts: filteredPosts,
|
||||
tag,
|
||||
categories
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import { getAllCategories, getAllPosts, getAllTags } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import TagItem from '@/components/TagItem'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
import BaseLayout from '@/layouts/BaseLayout'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import React from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { faTags } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import React from 'react'
|
||||
|
||||
export default function Tag ({ tags, allPosts, categories }) {
|
||||
export default function Tag ({ tags, allPosts, categories, postCount, latestPosts }) {
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${locale.COMMON.TAGS} | ${BLOG.title}`,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
return <BaseLayout meta={meta} categories={categories} totalPosts={allPosts}>
|
||||
return <BaseLayout meta={meta} categories={categories} totalPosts={allPosts} postCount={postCount} latestPosts={latestPosts}>
|
||||
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
|
||||
<div className='dark:text-gray-200 mb-5'><FontAwesomeIcon icon={faTags} className='mr-4'/>{locale.COMMON.TAGS}:</div>
|
||||
<div id='tags-list' className='duration-200 flex flex-wrap'>
|
||||
@@ -29,16 +28,15 @@ export default function Tag ({ tags, allPosts, categories }) {
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'tag-index-props'
|
||||
const notionPageData = await getNotionPageData({ from })
|
||||
const allPosts = await getAllPosts({ notionPageData, from })
|
||||
const categories = await getAllCategories(allPosts)
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const tags = await getAllTags({ allPosts, sliceCount: 0, tagOptions })
|
||||
const { allPosts, categories, tags, postCount, latestPosts } = await getGlobalNotionData({ from, includePage: true, tagsCount: 0 })
|
||||
|
||||
return {
|
||||
props: {
|
||||
tags,
|
||||
allPosts,
|
||||
categories
|
||||
posts: allPosts,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
|
||||
BIN
public/fonts/IBMPlexSansVar-Italic.woff2
Normal file
BIN
public/fonts/IBMPlexSansVar-Italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/IBMPlexSansVar-Roman.woff2
Normal file
BIN
public/fonts/IBMPlexSansVar-Roman.woff2
Normal file
Binary file not shown.
BIN
public/fonts/SourceSerif-Italic.var.woff2
Normal file
BIN
public/fonts/SourceSerif-Italic.var.woff2
Normal file
Binary file not shown.
BIN
public/fonts/SourceSerif.var.woff2
Normal file
BIN
public/fonts/SourceSerif.var.woff2
Normal file
Binary file not shown.
@@ -159,4 +159,8 @@ nav {
|
||||
background: rgba(31, 41, 55, .75);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.medium-zoom-overlay{
|
||||
background: none !important;
|
||||
}
|
||||
@@ -72,6 +72,7 @@
|
||||
line-height: 1.5;
|
||||
color: var(--fg-color);
|
||||
caret-color: var(--fg-color);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.notion > * {
|
||||
@@ -386,8 +387,9 @@
|
||||
}
|
||||
|
||||
.notion-h1 {
|
||||
font-size: 1.875em;
|
||||
font-size: 1.575em;
|
||||
margin-top: 1.08em;
|
||||
@apply border-b w-full
|
||||
}
|
||||
|
||||
.notion-header-anchor {
|
||||
@@ -649,7 +651,7 @@ svg.notion-page-icon {
|
||||
}
|
||||
|
||||
.notion-list li {
|
||||
padding: 6px 0;
|
||||
padding: 1px 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -1583,7 +1585,7 @@ svg.notion-page-icon {
|
||||
/* NOTION CSS OVERRIDE */
|
||||
|
||||
.notion {
|
||||
@apply text-gray-600 dark:text-gray-300;
|
||||
@apply dark:text-gray-300;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.notion,
|
||||
|
||||
@@ -1,9 +1,28 @@
|
||||
const BLOG = require('./blog.config')
|
||||
|
||||
const { fontFamily } = require('tailwindcss/defaultTheme')
|
||||
const CJK = require('./lib/cjk')
|
||||
const fontSansCJK = !CJK()
|
||||
? []
|
||||
: [`"Noto Sans CJK ${CJK()}"`, `"Noto Sans ${CJK()}"`]
|
||||
const fontSerifCJK = !CJK()
|
||||
? []
|
||||
: [`"Noto Serif CJK ${CJK()}"`, `"Noto Serif ${CJK()}"`]
|
||||
module.exports = {
|
||||
purge: ['./pages/**/*.js', './components/**/*.js', './layouts/**/*.js'],
|
||||
darkMode: BLOG.appearance === 'class' ? 'media' : 'class', // or 'media' or 'class'
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ['"IBM Plex Sans"', ...fontFamily.sans, ...fontSansCJK],
|
||||
serif: ['"Source Serif"', ...fontFamily.serif, ...fontSerifCJK],
|
||||
noEmoji: [
|
||||
'"IBM Plex Sans"',
|
||||
'ui-sans-serif',
|
||||
'system-ui',
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'sans-serif'
|
||||
]
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
day: {
|
||||
|
||||
Reference in New Issue
Block a user