Merge branch 'main' into theme-Fukasawa
# Conflicts: # blog.config.js
9
.gitignore
vendored
@@ -33,10 +33,13 @@ yarn-error.log*
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# sitemap
|
||||
/public/robots.txt
|
||||
/public/sitemap.xml
|
||||
# dev
|
||||
/data.json
|
||||
/yarn.lock
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
|
||||
# sitemap
|
||||
/public/robots.txt
|
||||
/public/sitemap.xml
|
||||
@@ -1,6 +0,0 @@
|
||||
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.625" y="0.625" width="48.75" height="48.75" rx="14.375" fill="black" stroke="white" stroke-width="1.25"/>
|
||||
<path d="M18.0876 23.1894L24.7284 18.7483L31.7252 23.2522C32.5632 23.2522 33.4011 23.0009 34.1134 22.5609L39.9791 18.7902L24.1208 11.5L9.75 19.6281L16.3489 22.7914C16.8935 23.0428 17.501 23.1685 18.0876 23.1894V23.1894ZM26.4881 36.1776L12.222 29.3483L9.75 30.7519L26.3624 38.6915L40 29.914L37.7794 28.9084L26.4881 36.1776Z" fill="white"/>
|
||||
<path d="M26.4881 32.2602L12.222 25.4519L9.75 26.8345L26.3624 34.7741L40 26.0175L37.7794 24.991L26.4881 32.2602Z" fill="white"/>
|
||||
<path d="M34.8257 23.5874C33.9249 24.174 32.8565 24.4882 31.7881 24.4882H31.7671L18.2552 24.4254C17.4382 24.4254 16.6002 24.2368 15.8461 23.8807L11.6354 21.8696L9.75 22.938L26.3624 30.8567L40 22.1001L38.345 21.3459L34.8257 23.5874V23.5874Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 944 B |
58
README.md
@@ -17,11 +17,7 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
演示地址:[https://www.tangly1024.com/](https://www.tangly1024.com/)
|
||||
|
||||
<details><summary>截图</summary>
|
||||
<img src='https://github.com/tangly1024/NotionNext/blob/main/screenshot.png?raw=true'/>
|
||||
</details>
|
||||
演示地址:[https://tangly1024.com/](https://tangly1024.com/)
|
||||
|
||||
## 亮点 ✨
|
||||
|
||||
@@ -37,8 +33,10 @@
|
||||
|
||||
**🚙 全功能,完全不操心**
|
||||
|
||||
- 评论、宽页面、搜索和标签
|
||||
- 订阅、网站统计、Web Vital 分析…… 还有更多功能待你发现
|
||||
- 评论、搜索、标签、分类
|
||||
- 订阅、网站统计
|
||||
- 本地化多语言
|
||||
- 服务端渲染、优秀的SEO
|
||||
|
||||
**🎨 美观,轻松自定义**
|
||||
|
||||
@@ -46,30 +44,54 @@
|
||||
- 使用 Tailwind CSS,轻松实现二次开发
|
||||
|
||||
**🕸 网址美观、搜索引擎优化**
|
||||
- 更多特性、欢迎移步[我的博客](https://tangly1024.com/article/notion-next)查看
|
||||
|
||||
## 更新日志
|
||||
请移步 [更新文档](https://docs.tangly1024.com/zh/changelog)查看
|
||||
|
||||
## 快速起步
|
||||
|
||||
- 给这个项目点个小星星 😉
|
||||
- 将 [这个 Notion 模板](https://www.notion.so/68be9021bca34b8e89f0246f27e608df) 制作副本,并分享这个页面给所有人
|
||||
- 将 [这个 Notion 模板](https://tanghh.notion.site/02ab3b8678004aa69e9e415905ef32a5) 制作副本,并分享这个页面给所有人
|
||||
- [Fork](https://github.com/tangly1024/NotionNext/fork) 这个项目
|
||||
- 在 `blog.config.js` 配置相关选项
|
||||
- _(可选)_ 用自己的图片替换 `/public` 文件夹里的 `avatar.svg`、`favicon.svg` 和 `favicon.ico`
|
||||
- _(可选)_ 用自己的图片替换 `/public` 文件夹里的 `avatar.jpg`、`favicon.svg` 和 `favicon.ico`
|
||||
- 在 [Vercel](https://vercel.com) 上部署这个项目, 设定一下环境变量:
|
||||
- `NOTION_PAGE_ID`: 你刚刚分享出去的 Notion 页面网址中的页面 ID,通常是网址中工作区地址后的 32 位字符串
|
||||
- `NOTION_ACCESS_TOKEN`(可选): 如果你决定不分享你的数据库,你可以使用 token 来让网页从 Notion 数据库中抓取数据。你可以在你的浏览器 cookies 中找到它,名称是 `token_v2'。
|
||||
- Notion token 的有效期只有 180 天,请确保在 Vercel Dashboard 上手动更新,我们可能会在未来切换到官方 API 来解决这个问题。此外,如果数据库是非公开到,Notion 中的图片可能无法正常显示到网页上。
|
||||
- 在 `blog.config.js` 配置相关选项,更多关于配置的说明,请移步[NotionNext文档](https://docs.tangly1024.com/zh)
|
||||
|
||||
- **稍微等等就可以访问了!** 简单吗?
|
||||
|
||||
## 技术细节
|
||||
|
||||
- **生成**: Next.js SSG 和 Incremental Static Regeneration
|
||||
- **页面渲染**: [React-notion-x](https://github.com/NotionX/react-notion-x)
|
||||
- **样式**: Tailwind CSS 和 `@tailwindcss/jit` compiler
|
||||
- **评论**: Gitalk,Cusdis,Utterances
|
||||
## 快速开发
|
||||
|
||||
```bash
|
||||
yarn # 安装依赖
|
||||
|
||||
yarn run dev # 本地开发
|
||||
|
||||
yarn run build # 本地打包编译
|
||||
|
||||
yarn run start # 本地启动NextJS服务
|
||||
|
||||
```
|
||||
|
||||
## 引用技术
|
||||
|
||||
- **框架**: Next.js
|
||||
- **渲染**: [React-notion-x](https://github.com/NotionX/react-notion-x)
|
||||
- **样式**: [Tailwind CSS](https://www.tailwindcss.cn/) 和 `@tailwindcss/jit` compiler
|
||||
- **评论**: Gitalk, Cusdis, Utterances
|
||||
- **图标**:[fontawesome](https://fontawesome.com/v5.15/icons?d=gallery)
|
||||
|
||||
## 页面样式主题
|
||||
- 仿照 [fukasawa](https://andersnoren.se/themes/fukasawa)
|
||||
- 仿照 [fukasawa](https://andersnoren.se/themes/fukasawa) 分支-https://github.com/tangly1024/NotionNext/tree/theme-Fukasawa
|
||||
<details><summary>fukasawa截图</summary>
|
||||
<img src='https://github.com/tangly1024/NotionNext/blob/main/docs/screenshot-fukasawa.png?raw=true'/>
|
||||
</details>
|
||||
- 仿照 [youtube] 主题 分支-https://github.com/tangly1024/NotionNext/tree/themw-youtube
|
||||
<details><summary>youtube截图</summary>
|
||||
<img src='https://github.com/tangly1024/NotionNext/blob/main/docs/screenshot-youtube.png?raw=true'/>
|
||||
</details>
|
||||
|
||||
## License
|
||||
|
||||
|
||||
114
blog.config.js
@@ -1,50 +1,68 @@
|
||||
const BLOG = {
|
||||
title: '塘里博客',
|
||||
author: 'tangly1024',
|
||||
email: 'tlyong1992@hotmail.com',
|
||||
link: 'https://tangly1024.com',
|
||||
description: '唐风集里,收卷波澜',
|
||||
lang: 'zh-CN', // ['zh-CN','en-US']
|
||||
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, // 列表展示文章预览
|
||||
previewLines: 12, // 预览文章的篇幅
|
||||
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', // ['font-sans', 'font-serif', 'font-mono']
|
||||
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.
|
||||
postsPerPage: 9,
|
||||
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: 'https://weibo.com/u/2245301913',
|
||||
seo: {
|
||||
keywords: ['Blog', 'Website', 'Notion'],
|
||||
googleSiteVerification: '' // Remove the value or replace it with your own google site verification code
|
||||
topNavType: 'normal', // ['fixed','autoCollapse','normal'] 分别是固定顶部、固定底部滑动时自动折叠,不固定
|
||||
menu: { // 菜单栏设置
|
||||
showAbout: false, // 显示关于
|
||||
showCategory: true, // 显示分类
|
||||
showTag: true, // 显示标签
|
||||
showArchive: true, // 显示归档
|
||||
showSearch: true // 显示搜索
|
||||
},
|
||||
notionPageId: process.env.NOTION_PAGE_ID || 'bee1fccfa3bd47a1a7be83cc71372d83', // DO NOT CHANGE THIS!!!
|
||||
notionAccessToken: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public
|
||||
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-5EV4HZD0XX' // e.g: G-XXXXXXXXXX
|
||||
},
|
||||
baidyAnalytics: 'f683ef76f06bb187cbed5546f6f28f28', // e.g only need xxxxx -> https://hm.baidu.com/hm.js?xxxxx
|
||||
busuanzi: true // see http://busuanzi.ibruce.info/
|
||||
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: false, // 显示回底
|
||||
showDarkMode: false, // 显示日间/夜间模式切换
|
||||
showToc: true, // 移动端显示悬浮目录
|
||||
showShareBar: false, // 文章分享功能
|
||||
showRelatePosts: true, // 相关文章推荐
|
||||
showCopyRight: true, // 文章版权声明
|
||||
showLatestPost: false, // 右侧边栏显示最近更新
|
||||
showCategoryList: false, // 右侧边栏显示文章分类列表
|
||||
showTagList: false // 右侧边栏显示标签分类列表
|
||||
},
|
||||
comment: {
|
||||
// support provider: gitalk, utterances, cusdis
|
||||
provider: 'cusdis', // leave it empty if you don't need any comment plugin
|
||||
socialLink: { // 社交链接,如不需要展示可以留空白,例如 weibo:''
|
||||
weibo: 'https://weibo.com/tangly1024',
|
||||
twitter: 'https://twitter.com/troy1024_1',
|
||||
github: 'https://github.com/tangly1024',
|
||||
telegram: 'https://t.me/tangly_1024'
|
||||
},
|
||||
comment: { // 评论插件,支持 gitalk, utterances, cusdis
|
||||
provider: 'gitalk', // 不需要则留空白
|
||||
gitalkConfig: {
|
||||
repo: 'NotionNext', // The repository of store comments
|
||||
owner: 'tangly1024',
|
||||
admin: ['tangly1024'],
|
||||
clientID: 'be7864a16b693e256f8f',
|
||||
clientSecret: 'dbd0f6d9ceea8940f6ed20936b415274b8fe66a2',
|
||||
clientID: process.env.GITALK_ID || 'be7864a16b693e256f8f',
|
||||
clientSecret: process.env.GITALK_SECRET || 'dbd0f6d9ceea8940f6ed20936b415274b8fe66a2',
|
||||
distractionFreeMode: false
|
||||
},
|
||||
cusdisConfig: {
|
||||
@@ -54,11 +72,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'
|
||||
}
|
||||
},
|
||||
isProd: process.env.VERCEL_ENV === 'production', // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables)
|
||||
googleAdsenseId: 'ca-pub-2708419466378217',
|
||||
DaoVoiceId: '' // DaoVoice http://dashboard.daovoice.io/get-started
|
||||
seo: {
|
||||
googleSiteVerification: '' // Remove the value or replace it with your own google site verification code
|
||||
},
|
||||
googleAdsenseId: 'ca-pub-2708419466378217', // 谷歌广告ID
|
||||
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,31 +0,0 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import TagItem from '@/components/TagItem'
|
||||
|
||||
const BlogPost = ({ post }) => {
|
||||
return (
|
||||
<article key={post.id}
|
||||
className='inline-block border dark:border-gray-600 my-2 shadow-card w-full md:max-w-md bg-white dark:bg-gray-700 dark:hover:bg-gray-600 overflow-hidden'>
|
||||
{/* 封面图 */}
|
||||
{post.page_cover && post.page_cover.length > 1 && (
|
||||
<a href={`${BLOG.path}/article/${post.slug}`} className='md:flex-shrink-0 md:w-52 md:h-52 rounded-lg'>
|
||||
<img className='w-full max-h-60 object-cover cursor-pointer transform hover:scale-110 duration-500' src={post.page_cover} alt={post.title} />
|
||||
</a>
|
||||
)}
|
||||
|
||||
<div className='px-8 py-6'>
|
||||
<a href={`${BLOG.path}/article/${post.slug}`}
|
||||
className='block my-3 text-2xl leading-tight font-semibold text-black dark:text-gray-100 hover:underline'>
|
||||
{post.title}
|
||||
</a>
|
||||
<p className='mt-2 text-gray-500 dark:text-gray-300 text-sm'>{post.summary}</p>
|
||||
<div className='flex flex-nowrap leading-8 py-2'>
|
||||
{post.tags.map(tag => (
|
||||
<TagItem key={tag} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogPost
|
||||
@@ -1,24 +0,0 @@
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
const BlogPostMini = ({ post }) => {
|
||||
return (
|
||||
<a key={post.id} href={`${BLOG.path}/article/${post.slug}`}
|
||||
className='sm:flex w-full border dark:border-gray-500 my-2 duration-200 transform hover:scale-105 hover:shadow-2xl bg-white dark:bg-gray-800 dark:hover:bg-gray-700'>
|
||||
{/* 封面图 */}
|
||||
{post.page_cover && post.page_cover.length > 1 && (
|
||||
<img className='sm:w-40 w-full object-cover cursor-pointer' src={post.page_cover} alt={post.title} />
|
||||
)}
|
||||
|
||||
<main className='px-2 py-1'>
|
||||
<a href={`${BLOG.path}/article/${post.slug}`}
|
||||
className='block my-3 leading-tight font-semibold text-black dark:text-gray-200 hover:underline'>
|
||||
{post.title}
|
||||
</a>
|
||||
{/* <p className='mt-2 text-gray-500 dark:text-gray-400 text-xs overflow-x-hidden'>{post.summary}</p> */}
|
||||
{/* <p className='mt-2 text-gray-500 dark:text-gray-400 text-xs overflow-x-hidden'>{BLOG.link}/article/{post.slug}</p> */}
|
||||
</main>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogPostMini
|
||||
17
components/Busuanzi.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import busuanzi from '@/lib/busuanzi'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function Busuanzi () {
|
||||
const router = useRouter()
|
||||
useEffect(() => {
|
||||
const busuanziRouteChange = url => {
|
||||
busuanzi.fetch()
|
||||
}
|
||||
router.events.on('routeChangeComplete', busuanziRouteChange)
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', busuanziRouteChange)
|
||||
}
|
||||
}, [router.events])
|
||||
return null
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTheme } from '@/lib/theme'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import 'gitalk/dist/gitalk.css'
|
||||
|
||||
const GitalkComponent = dynamic(
|
||||
() => {
|
||||
@@ -25,46 +25,48 @@ const CusdisComponent = dynamic(
|
||||
|
||||
const Comment = ({ frontMatter }) => {
|
||||
const router = useRouter()
|
||||
const { theme } = useTheme()
|
||||
const { theme } = useGlobal()
|
||||
|
||||
return <div className='comment text-gray-800 dark:text-gray-300'>
|
||||
<div className='font-bold pt-2 pb-4 '>留下评论</div>
|
||||
|
||||
{/* 评论插件 */}
|
||||
{BLOG.comment.provider === 'gitalk' && (
|
||||
<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
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{BLOG.comment.provider === 'utterances' && (
|
||||
<UtterancesComponent issueTerm={frontMatter.id} className='px-2' />
|
||||
)}
|
||||
{BLOG.comment.provider === 'cusdis' && (
|
||||
<>
|
||||
<script defer src='https://cusdis.com/js/widget/lang/zh-cn.js' />
|
||||
<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>
|
||||
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>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default Comment
|
||||
|
||||
@@ -2,12 +2,19 @@ import BLOG from '@/blog.config'
|
||||
import Head from 'next/head'
|
||||
|
||||
const CommonHead = ({ meta }) => {
|
||||
const url = BLOG.path.length ? `${BLOG.link}/${BLOG.path}` : BLOG.link
|
||||
let url = BLOG.path.length ? `${BLOG.link}/${BLOG.path}` : BLOG.link
|
||||
if (meta) {
|
||||
url = `${url}/${meta.slug}`
|
||||
}
|
||||
const title = meta?.title || BLOG.title
|
||||
const description = meta?.description || BLOG.description
|
||||
const type = meta?.type || 'website'
|
||||
const keywords = meta?.tags || BLOG.keywords
|
||||
|
||||
return <Head>
|
||||
<title>{meta.title}</title>
|
||||
<meta content={BLOG.darkBackground} name='theme-color' />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<title>{title}</title>
|
||||
<meta name='theme-color' content={BLOG.darkBackground} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
||||
<meta name='robots' content='follow, index' />
|
||||
<meta charSet='UTF-8' />
|
||||
{BLOG.seo.googleSiteVerification && (
|
||||
@@ -16,21 +23,19 @@ const CommonHead = ({ meta }) => {
|
||||
content={BLOG.seo.googleSiteVerification}
|
||||
/>
|
||||
)}
|
||||
{BLOG.seo.keywords && (
|
||||
<meta name='keywords' content={BLOG.seo.keywords.join(', ')} />
|
||||
{keywords && (
|
||||
<meta name='keywords' content={keywords.join(', ')} />
|
||||
)}
|
||||
<meta name='description' content={meta.description} />
|
||||
<meta name='description' content={description} />
|
||||
<meta property='og:locale' content={BLOG.lang} />
|
||||
<meta property='og:title' content={meta.title} />
|
||||
<meta property='og:description' content={meta.description} />
|
||||
<meta
|
||||
property='og:url'
|
||||
content={meta.slug ? `${url}/${meta.slug}` : url}
|
||||
<meta property='og:title' content={title} />
|
||||
<meta property='og:description' content={description} />
|
||||
<meta property='og:url' content={url}
|
||||
/>
|
||||
<meta property='og:type' content={meta.type} />
|
||||
<meta property='og:type' content={type} />
|
||||
<meta name='twitter:card' content='summary_large_image' />
|
||||
<meta name='twitter:description' content={meta.description} />
|
||||
<meta name='twitter:title' content={meta.title} />
|
||||
<meta name='twitter:description' content={description} />
|
||||
<meta name='twitter:title' content={title} />
|
||||
{meta.type === 'article' && (
|
||||
<>
|
||||
<meta
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
const ThirdPartyScript = () => {
|
||||
/**
|
||||
* 第三方代码 统计脚本
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const CommonScript = () => {
|
||||
return (<>
|
||||
{BLOG.DaoVoiceId && (<>
|
||||
{BLOG.comment?.DaoVoiceId && (<>
|
||||
{/* DaoVoice 反馈 */}
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
@@ -13,20 +18,39 @@ const ThirdPartyScript = () => {
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
daovoice('init', {
|
||||
app_id: "${BLOG.DaoVoiceId}"
|
||||
app_id: "${BLOG.comment.DaoVoiceId}"
|
||||
});
|
||||
daovoice('update');
|
||||
`
|
||||
}}
|
||||
/>
|
||||
</>)}
|
||||
|
||||
{/* GoogleAdsense 广告植入 */}
|
||||
{BLOG.googleAdsenseId && (<script data-ad-client={BLOG.googleAdsenseId} async
|
||||
src='https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js'/>)}
|
||||
|
||||
{BLOG.comment?.TidioId && (<>
|
||||
{/* Tidio在线反馈 */}
|
||||
<script async
|
||||
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 && (<>
|
||||
{/* GoogleAdsense */}
|
||||
{BLOG.googleAdsenseId && (
|
||||
<script data-ad-client={BLOG.googleAdsenseId} async
|
||||
src='https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js' />
|
||||
)}
|
||||
|
||||
{/* ackee统计脚本 */}
|
||||
{BLOG.analytics.provider === 'ackee' && (
|
||||
@@ -36,14 +60,14 @@ const ThirdPartyScript = () => {
|
||||
/>
|
||||
)}
|
||||
{/* 百度统计 */}
|
||||
{BLOG.analytics.baidyAnalytics && (
|
||||
{BLOG.analytics.baiduAnalytics && (
|
||||
<script async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?${BLOG.analytics.baidyAnalytics}";
|
||||
hm.src = "https://hm.baidu.com/hm.js?${BLOG.analytics.baiduAnalytics}";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
@@ -51,19 +75,13 @@ const ThirdPartyScript = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* 不蒜子 */}
|
||||
{BLOG.analytics.busuanzi && (
|
||||
<script async
|
||||
src={'//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 站长统计 */}
|
||||
{BLOG.isProd && (
|
||||
{BLOG.analytics.cnzzAnalytics && (
|
||||
<script async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
document.write(unescape("%3Cspan style='display:none' id='cnzz_stat_icon_1279970751'%3E%3C/span%3E%3Cscript src='https://s9.cnzz.com/z_stat.php%3Fid%3D1279970751' type='text/javascript'%3E%3C/script%3E"));
|
||||
document.write(unescape("%3Cspan style='display:none' id='cnzz_stat_icon_${BLOG.analytics.cnzzAnalytics}'%3E%3C/span%3E%3Cscript src='https://s9.cnzz.com/z_stat.php%3Fid%3D${BLOG.analytics.cnzzAnalytics}' type='text/javascript'%3E%3C/script%3E"));
|
||||
`
|
||||
}}
|
||||
/>
|
||||
@@ -91,4 +109,4 @@ const ThirdPartyScript = () => {
|
||||
</>)
|
||||
}
|
||||
|
||||
export default ThirdPartyScript
|
||||
export default CommonScript
|
||||
@@ -1,16 +0,0 @@
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const Container = ({ children, layout, fullWidth, ...customMeta }) => {
|
||||
return (
|
||||
<div>
|
||||
{/* 公共头 */}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Container.propTypes = {
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
export default Container
|
||||
@@ -1,15 +0,0 @@
|
||||
import { useTheme } from '@/lib/theme'
|
||||
import localStorage from 'localStorage'
|
||||
|
||||
const DarkModeButton = () => {
|
||||
const { theme, changeTheme } = useTheme()
|
||||
const handleChangeDarkMode = () => {
|
||||
const newTheme = (theme === 'light' ? 'dark' : 'light')
|
||||
changeTheme(newTheme)
|
||||
localStorage.setItem('theme', newTheme)
|
||||
}
|
||||
return <div className='z-10 p-1 border hover:shadow-xl duration-200 dark:border-gray-500 mr-2 h-12 my-2 bg-white dark:bg-gray-600 dark:bg-opacity-70 bg-opacity-70 dark:hover:bg-gray-100 text-xl cursor-pointer dark:text-gray-300 dark:hover:text-black'>
|
||||
<i className={'fa p-2.5 hover:scale-125 transform duration-200 ' + (theme === 'dark' ? ' fa-sun-o' : ' fa-moon-o') } onClick={handleChangeDarkMode} />
|
||||
</div>
|
||||
}
|
||||
export default DarkModeButton
|
||||
@@ -1,28 +0,0 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import React from 'react'
|
||||
|
||||
const Footer = ({ fullWidth = true }) => {
|
||||
const d = new Date()
|
||||
const y = d.getFullYear()
|
||||
const from = +BLOG.since
|
||||
return (
|
||||
<footer
|
||||
className='flex-shrink-0 m-auto w-full text-gray-500 dark:text-gray-400 text-sm text-gray-400'
|
||||
>
|
||||
<hr className='py-2'/>
|
||||
<span className='fa fa-shield leading-6'> <a href='https://beian.miit.gov.cn/' className='ml-1'>闽ICP备20010331号</a></span>
|
||||
<br />
|
||||
<span className='fa fa-copyright leading-6'> {from === y || !from ? y : `${from} - ${y}`} {BLOG.author} </span>
|
||||
<br />
|
||||
<span id='busuanzi_container_site_pv' className='hidden'>
|
||||
<a id='busuanzi_container_site_pv' href='https://www.cnzz.com/stat/website.php?web_id=1279970751' target='_blank'
|
||||
className='fa fa-user' rel='noreferrer'> pv <span id='busuanzi_value_site_pv'></span></a>
|
||||
</span>
|
||||
<span id='busuanzi_container_site_uv' className='hidden'><span className='s'> | </span>
|
||||
<a href='http://tongji.baidu.com/web/10000363165/overview/index?siteId=16809429' target='_blank' className='fa fa-eye' rel='noreferrer'> uv <span id='busuanzi_value_site_uv'></span></a>
|
||||
</span>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
||||
29
components/GoogleAdsense.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function GoogleAdsense () {
|
||||
const initGoogleAdsense = () => {
|
||||
const ads = document.getElementsByClassName('adsbygoogle').length
|
||||
const newAdsCount = ads
|
||||
if (newAdsCount > 0) {
|
||||
for (let i = 0; i <= newAdsCount; i++) {
|
||||
try {
|
||||
// eslint-disable-next-line no-undef
|
||||
(adsbygoogle = window.adsbygoogle || []).push({})
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
useEffect(() => {
|
||||
initGoogleAdsense()
|
||||
router.events.on('routeChangeComplete', initGoogleAdsense)
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', initGoogleAdsense)
|
||||
}
|
||||
}, [router.events])
|
||||
return null
|
||||
}
|
||||
@@ -5,12 +5,12 @@ import * as gtag from '@/lib/gtag'
|
||||
const Gtag = () => {
|
||||
const router = useRouter()
|
||||
useEffect(() => {
|
||||
const handleRouteChange = url => {
|
||||
const gtagRouteChange = url => {
|
||||
gtag.pageview(url)
|
||||
}
|
||||
router.events.on('routeChangeComplete', handleRouteChange)
|
||||
router.events.on('routeChangeComplete', gtagRouteChange)
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', handleRouteChange)
|
||||
router.events.off('routeChangeComplete', gtagRouteChange)
|
||||
}
|
||||
}, [router.events])
|
||||
return null
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useLocale } from '@/lib/locale'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
const Pagination = ({ page, showNext }) => {
|
||||
const locale = useLocale()
|
||||
const router = useRouter()
|
||||
const currentPage = +page
|
||||
return (
|
||||
<div className=' my-10 flex justify-between font-medium text-black dark:text-gray-100'>
|
||||
<Link
|
||||
href={
|
||||
{
|
||||
pathname: (currentPage - 1 === 1 ? `${BLOG.path || '/'}` : `/page/${currentPage - 1}`),
|
||||
query: router.query.s ? { s: router.query.s } : {}
|
||||
}
|
||||
}
|
||||
>
|
||||
<button
|
||||
rel='prev'
|
||||
className={`${currentPage === 1 ? 'invisible' : 'block'} duration-200 px-4 py-2 hover:border-black border-b-2 hover:font-bold`}
|
||||
>
|
||||
← {locale.PAGINATION.PREV}
|
||||
</button>
|
||||
</Link>
|
||||
<Link href={
|
||||
{
|
||||
pathname: `/page/${currentPage + 1}`,
|
||||
query: router.query.s ? { s: router.query.s } : {}
|
||||
}
|
||||
}>
|
||||
<button
|
||||
rel='next'
|
||||
className={`${+showNext ? 'block' : 'invisible'} duration-200 px-4 py-2 hover:border-black border-b-2 hover:font-bold`}
|
||||
>
|
||||
{locale.PAGINATION.NEXT} →
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Pagination
|
||||
@@ -1,29 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import throttle from 'lodash.throttle'
|
||||
|
||||
/**
|
||||
* 跳转到网页顶部;当屏幕下滑500像素后会出现该控件
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const Progress = ({ targetRef }) => {
|
||||
const [percent, changePercent] = useState(0)
|
||||
useEffect(() => {
|
||||
const scrollListener = throttle(() => {
|
||||
if (targetRef.current) {
|
||||
const fullHeight = targetRef.current.clientHeight
|
||||
const per = parseFloat(((window.scrollY / (fullHeight - 100) * 100)).toFixed(0))
|
||||
changePercent(per)
|
||||
}
|
||||
// console.log('滚动信息', window.scrollY, fullHeight, per)
|
||||
}, 1)
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [percent])
|
||||
|
||||
return (<div className='h-1.5 fixed top-0 w-full shadow-2xl z-40'>
|
||||
<div className='h-1 bg-blue-500 fixed top-0 w-1 duration-200' style={{ width: `${percent}%` }}/>
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default Progress
|
||||
@@ -1,59 +0,0 @@
|
||||
import React from 'react'
|
||||
import { createPopper } from '@popperjs/core'
|
||||
|
||||
/**
|
||||
* 赞赏模块
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const RewardButton = () => {
|
||||
const [popoverShow, setPopoverShow] = React.useState(false)
|
||||
const btnRef = React.createRef()
|
||||
const popoverRef = React.createRef()
|
||||
|
||||
const openPopover = () => {
|
||||
createPopper(btnRef.current, popoverRef.current, {
|
||||
placement: 'top'
|
||||
})
|
||||
setPopoverShow(true)
|
||||
}
|
||||
const closePopover = () => {
|
||||
setPopoverShow(false)
|
||||
}
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => {
|
||||
openPopover()
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
closePopover()
|
||||
}}>
|
||||
<div className='animate__jello animate__animated animate__faster'>
|
||||
<div
|
||||
ref={btnRef}
|
||||
className='bg-red-500 text-white hover:bg-green-400 hover:shadow-2xl duration-200 transform hover:scale-110 px-3 py-2 rounded cursor-pointer'>
|
||||
<span className='fa fa-qrcode mr-2' />
|
||||
<span>打赏</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={
|
||||
(popoverShow ? 'animate__animated animate__fadeIn ' : 'hidden ') +
|
||||
' animate__faster border-0 transform block z-50 font-normal'
|
||||
}
|
||||
ref={popoverRef}
|
||||
>
|
||||
<div
|
||||
className='border animate__animated animate__fadeIn hover:shadow-2xl duration-200 my-5 px-5 py-6 w-96 grid justify-center bg-white dark:bg-black dark:text-gray-200'>
|
||||
<span>
|
||||
<img className='md:w-72 m-auto' src='/reward_code.jpg' />
|
||||
</span>
|
||||
<br />
|
||||
<span className='text-center text-gray-500'>微信赞赏码或支付宝tlyong@126.com赞助</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default RewardButton
|
||||
@@ -1,61 +0,0 @@
|
||||
import React, { useState } from 'react'
|
||||
import TocBar from '@/components/TocBar'
|
||||
import throttle from 'lodash.throttle'
|
||||
import ShareButton from '@/components/ShareButton'
|
||||
import TopJumper from '@/components/TopJumper'
|
||||
|
||||
const RightAside = ({ toc, post }) => {
|
||||
// 无目录就直接返回空
|
||||
if (toc.length < 1) return <></>
|
||||
// 监听滚动事件
|
||||
React.useEffect(() => {
|
||||
window.addEventListener('resize', resizeWindowHideToc)
|
||||
return () => {
|
||||
window.removeEventListener('resize', resizeWindowHideToc)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const resizeWindowHideToc = throttle(() => {
|
||||
if (window.innerWidth > 1300) {
|
||||
changeHideAside(false)
|
||||
} else {
|
||||
changeHideAside(true)
|
||||
}
|
||||
}, 500)
|
||||
const [hideAside, changeHideAside] = useState(true)
|
||||
|
||||
return <aside className='dark:bg-gray-800'>
|
||||
{/* 上方菜单组 */}
|
||||
<div
|
||||
className={(hideAside ? 'right-0' : 'right-48') + ' z-20 space-x-2 fixed flex top-0 px-3 py-1 duration-500'}>
|
||||
{/* 目录按钮 */}
|
||||
<div
|
||||
className='border dark:border-gray-500 my-2 bg-white dark:bg-gray-600 bg-opacity-70 dark:hover:bg-gray-100 text-xl cursor-pointer dark:text-gray-300 dark:hover:text-black p-1'>
|
||||
<i className='fa fa-book p-2.5 hover:scale-125 transform duration-200'
|
||||
onClick={() => changeHideAside(!hideAside)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 下方菜单组 */}
|
||||
<div
|
||||
className={(hideAside ? 'right-0' : 'right-48') + ' space-x-2 fixed flex bottom-24 px-4 py-1 duration-500'}>
|
||||
<div className='flex-wrap'>
|
||||
{/* 分享按钮 */}
|
||||
<ShareButton post={post} />
|
||||
{/* 跳回顶部 */}
|
||||
<TopJumper />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 目录 */}
|
||||
<section
|
||||
className={(hideAside ? '-mr-48' : 'mr-0 shadow-xl xl:shadow-none') + ' md:static top-0 fixed h-full w-48 right-0 dark:bg-gray-800 duration-500 top-0'}>
|
||||
<div className='sticky top-0'>
|
||||
<TocBar toc={toc} />
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</aside>
|
||||
}
|
||||
export default RightAside
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react'
|
||||
import TopJumper from '@/components/TopJumper'
|
||||
import ShareButton from '@/components/ShareButton'
|
||||
|
||||
const RightWidget = ({ post }) => {
|
||||
return <div className='flex-wrap'>
|
||||
<ShareButton post={post} />
|
||||
<TopJumper />
|
||||
</div>
|
||||
}
|
||||
export default RightWidget
|
||||
@@ -1,86 +0,0 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
import { createPopper } from '@popperjs/core'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import QRCode from 'qrcode.react'
|
||||
|
||||
const ShareBar = ({ post }) => {
|
||||
const router = useRouter()
|
||||
const shareUrl = BLOG.link + router.asPath
|
||||
|
||||
// 二维码悬浮
|
||||
const [qrCodeShow, setQrCodeShow] = React.useState(false)
|
||||
const btnRef = React.createRef()
|
||||
const popoverRef = React.createRef()
|
||||
|
||||
const openPopover = () => {
|
||||
createPopper(btnRef.current, popoverRef.current, {
|
||||
placement: 'left'
|
||||
})
|
||||
setQrCodeShow(true)
|
||||
}
|
||||
const closePopover = () => {
|
||||
setQrCodeShow(false)
|
||||
}
|
||||
|
||||
const copyUrl = () => {
|
||||
copy(shareUrl)
|
||||
alert('当前链接已复制到剪贴板')
|
||||
}
|
||||
|
||||
return <>
|
||||
<div
|
||||
className='dark:border-gray-500 py-2 text-gray-500 flex-col text-center space-y-2 w-12 border my-1 bg-white dark:bg-gray-800 dark:text-white overflow-visible'>
|
||||
<div className='text-3xl cursor-pointer'>
|
||||
<a className='fa fa-facebook-square'
|
||||
href={`https://www.facebook.com/sharer.php?u=${shareUrl}`} />
|
||||
</div>
|
||||
<div className='text-3xl cursor-pointer'>
|
||||
<a className='fa fa-twitter-square' target='_blank' rel='noreferrer'
|
||||
href={`https://twitter.com/intent/tweet?title=${post.title}&url${shareUrl}`} />
|
||||
</div>
|
||||
<div className='text-3xl cursor-pointer'>
|
||||
<a className='fa fa-telegram' href={`https://telegram.me/share/url?url=${shareUrl}&text=${post.title}`} />
|
||||
</div>
|
||||
<div className='cursor-pointer text-2xl'>
|
||||
<a className='fa fa-wechat' ref={btnRef}
|
||||
onMouseEnter={() => {
|
||||
openPopover()
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
closePopover()
|
||||
}}>
|
||||
<div ref={popoverRef}
|
||||
className={(qrCodeShow ? 'animate__animated animate__fadeIn ' : 'hidden') + ' text-center py-2'}>
|
||||
<div className='p-2 bg-white border-0 duration-200 transform block z-50 font-normal shadow-xl mr-10'>
|
||||
<QRCode
|
||||
value={shareUrl}// 生成二维码的内容
|
||||
fgColor='#000000' // 二维码的颜色
|
||||
/>
|
||||
</div>
|
||||
<span className='bg-white text-black font-semibold p-1 mb-0 rounded-t-lg text-sm mx-auto mr-10'>
|
||||
扫一扫分享
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div className='cursor-pointer text-2xl'>
|
||||
<a className='fa fa-weibo' target='_blank' rel='noreferrer'
|
||||
href={`https://service.weibo.com/share/share.php?url=${shareUrl}&title=${post.title}`} />
|
||||
</div>
|
||||
<div className='cursor-pointer text-2xl'>
|
||||
<a className='fa fa-qq' target='_blank' rel='noreferrer'
|
||||
href={`http://connect.qq.com/widget/shareqq/index.html?url=${shareUrl}&sharesource=qzone&title=${post.title}&desc=${post.summary}`} />
|
||||
</div>
|
||||
<div className='cursor-pointer text-2xl'>
|
||||
<a className='fa fa-star' target='_blank' rel='noreferrer'
|
||||
href={`https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=${shareUrl}&sharesource=qzone&title=${post.title}&summary=${post.summary}`} />
|
||||
</div>
|
||||
<div className='cursor-pointer text-2xl'>
|
||||
<a className='fa fa-link' onClick={copyUrl} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
export default ShareBar
|
||||
@@ -1,134 +0,0 @@
|
||||
import Tags from '@/components/Tags'
|
||||
import { useLocale } from '@/lib/locale'
|
||||
import Link from 'next/link'
|
||||
import BLOG from '@/blog.config'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Router, { useRouter } from 'next/router'
|
||||
import DarkModeButton from '@/components/DarkModeButton'
|
||||
import Footer from '@/components/Footer'
|
||||
import throttle from 'lodash.throttle'
|
||||
import TocBar from '@/components/TocBar'
|
||||
import SocialButton from '@/components/SocialButton'
|
||||
|
||||
const SideBar = ({ tags, currentTag, post }) => {
|
||||
const locale = useLocale()
|
||||
const router = useRouter()
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
|
||||
const handleKeyUp = (e) => {
|
||||
if (e.keyCode === 13) {
|
||||
Router.push({ pathname: '/', query: { s: searchValue } })
|
||||
}
|
||||
}
|
||||
|
||||
// 监听resize事件
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', collapseSideBar)
|
||||
collapseSideBar()
|
||||
return () => {
|
||||
window.removeEventListener('resize', collapseSideBar)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const collapseSideBar = throttle(() => {
|
||||
if (window.innerWidth > 1300) {
|
||||
changeCollapse(false)
|
||||
} else {
|
||||
changeCollapse(true)
|
||||
}
|
||||
}, 500)
|
||||
const [collapse, changeCollapse] = useState(true)
|
||||
|
||||
return <aside className='z-10'>
|
||||
|
||||
<div
|
||||
className={(collapse ? '-ml-80 ' : 'shadow-2xl xl:shadow-none ') + ' dark:bg-gray-800 bg-white sidebar h-full w-72 duration-500 ease-in-out'}>
|
||||
|
||||
{/* Logo */}
|
||||
<section className='flex border-b px-5 pt-8 pb-6 flex-col sticky top-0 bg-white dark:bg-gray-800 z-10'>
|
||||
<Link href='/'>
|
||||
<div
|
||||
className='mx-auto text-center cursor-pointer text-3xl dark:bg-gray-900 dark:text-gray-300 font-semibold dark:hover:bg-gray-600 bg-gray-700 text-white p-2 hover:scale-105 hover:shadow-2xl duration-200 transform'>{BLOG.title}</div>
|
||||
</Link>
|
||||
|
||||
<i className='mx-auto fa fa-map-marker pl-1 dark:text-gray-300 mt-5' > Fuzhou, China</i>
|
||||
</section>
|
||||
|
||||
{/* 搜索框 */}
|
||||
<section className={ (post ? ' ' : ' sticky top-36 ') + ' z-20 border-t border-b flex justify-center items-center py-5 pr-5 pl-2 bg-gray-100 dark:bg-black'}>
|
||||
<input
|
||||
type='text'
|
||||
placeholder={
|
||||
currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`
|
||||
}
|
||||
className='shadow-inner duration-200 pl-2 rounded w-full py-2 border dark:border-gray-600 bg-white text-black dark:bg-gray-700 dark:text-white'
|
||||
onKeyUp={handleKeyUp}
|
||||
onChange={e => setSearchValue(e.target.value)}
|
||||
defaultValue={router.query.s ?? ''}
|
||||
/>
|
||||
<i className='fa fa-search text-gray-400 -ml-8' />
|
||||
</section>
|
||||
|
||||
{/* wrapper */}
|
||||
<div className={ (post ? ' ' : ' ') + 'px-6'}>
|
||||
|
||||
{/* 菜单 */}
|
||||
<nav className='mt-6'>
|
||||
<strong className='text-2xl text-gray-600 dark:text-gray-400'>菜单</strong>
|
||||
<ul className='mt-4 leading-8 text-gray-500 dark:text-gray-400'>
|
||||
<li><a className='fa fa-info hover:underline' href='/article/about' id='about'><span
|
||||
className='ml-2'>关于本站</span></a></li>
|
||||
<li><a className='fa fa-rss hover:underline' href='/feed' target='_blank' id='feed'><span
|
||||
className='ml-2'>RSS订阅</span></a></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/* 标签云 */}
|
||||
<section className='mt-6'>
|
||||
<strong className='text-2xl text-gray-600 dark:text-gray-400'>标签</strong>
|
||||
<div className='mt-4'>
|
||||
<Tags tags={tags} currentTag={currentTag} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 联系 */}
|
||||
<section>
|
||||
<div className='mt-6'>
|
||||
<strong className='text-2xl text-gray-600 dark:text-gray-400'>联系我</strong>
|
||||
<div className='mt-2 py-2'>
|
||||
<SocialButton />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 站点信息 */}
|
||||
<section className='my-3'>
|
||||
<Footer />
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
{post && (
|
||||
<div className='sticky top-36'>
|
||||
<TocBar toc={post.toc} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{/* 顶部菜单按钮 */}
|
||||
<div
|
||||
className={(collapse ? 'left-0' : 'left-72') + ' z-30 fixed flex flex-nowrap md:flex-col top-0 pl-4 py-1 duration-500 ease-in-out'}>
|
||||
{/* 菜单折叠 */}
|
||||
<div className='p-1 border hover:shadow-xl duration-200 dark:border-gray-500 h-12 bg-white dark:bg-gray-600 dark:bg-opacity-70 bg-opacity-70
|
||||
dark:hover:bg-gray-100 text-xl cursor-pointer mr-2 my-2 dark:text-gray-300 dark:hover:text-black'>
|
||||
<i className='fa fa-bars p-2.5 hover:scale-125 transform duration-200'
|
||||
onClick={() => changeCollapse(!collapse)} />
|
||||
</div>
|
||||
{/* 夜间模式 */}
|
||||
<DarkModeButton />
|
||||
</div>
|
||||
</aside>
|
||||
}
|
||||
export default SideBar
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
const SocialButton = () => {
|
||||
return <>
|
||||
<div className='space-x-3 text-2xl text-gray-500 dark:text-gray-400'>
|
||||
<a target='_blank' rel='noreferrer' title={'github'}
|
||||
href={'https://github.com/tangly1024'} >
|
||||
<div className='fa fa-github transform hover:scale-125 duration-150'/>
|
||||
</a>
|
||||
<a target='_blank' rel='noreferrer' title={'twitter'}
|
||||
href={'https://twitter.com/troy1024_1'} >
|
||||
<div className='fa fa-twitter transform hover:scale-125 duration-150'/>
|
||||
</a>
|
||||
<a href={'https://t.me/tangly_1024'} title={'telegram'} >
|
||||
<div className='fa fa-telegram transform hover:scale-125 duration-150'/>
|
||||
</a>
|
||||
<a target='_blank' rel='noreferrer' title={'weibo'}
|
||||
href={'https://weibo.com/tangly1024'} >
|
||||
<div className='fa fa-weibo transform hover:scale-125 duration-150'/>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
export default SocialButton
|
||||
@@ -1,14 +0,0 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
const TagItem = ({ tag }) => (
|
||||
<Link href={`/tag/${encodeURIComponent(tag)}`}>
|
||||
<a>
|
||||
<p className="hover:shadow hover:scale-105 hover:bg-gray-500 bg-gray-200 hover:text-white duration-200 mr-1 p-2 leading-none text-sm
|
||||
dark:bg-gray-500 dark:text-gray-100 dark:hover:bg-black">
|
||||
{tag}
|
||||
</p>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
|
||||
export default TagItem
|
||||
@@ -1,27 +0,0 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
const Tags = ({ tags, currentTag }) => {
|
||||
if (!tags) return <></>
|
||||
return (
|
||||
<ul className='flex flex-wrap py-1 max-w-full overflow-x-auto'>
|
||||
{Object.keys(tags).map(key => {
|
||||
const selected = key === currentTag
|
||||
return (
|
||||
<Link key={key} href={`/tag/${encodeURIComponent(key)}`}>
|
||||
<li
|
||||
className={`cursor-pointer hover:bg-gray-600 rounded-sm hover:text-white duration-200 mr-1 my-1 px-2 py-1 font-medium text-xs whitespace-nowrap
|
||||
dark:text-gray-300 dark:hover:bg-gray-600 ${selected ? 'text-white bg-black dark:border-gray-600' : 'bg-gray-200 text-gray-600 dark:bg-gray-900 dark:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
<a>
|
||||
{`${key} (${tags[key]})`}
|
||||
</a>
|
||||
</li>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tags
|
||||
@@ -1,39 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import throttle from 'lodash.throttle'
|
||||
import { useLocale } from '@/lib/locale'
|
||||
|
||||
/**
|
||||
* 跳转到网页顶部;当屏幕下滑500像素后会出现该控件
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const TopJumper = () => {
|
||||
const locale = useLocale()
|
||||
|
||||
const [show, switchShow] = useState(false)
|
||||
useEffect(() => {
|
||||
const scrollListener = throttle(() => {
|
||||
// 处理是否显示回到顶部按钮
|
||||
const shouldShow = window.scrollY > 100
|
||||
if (shouldShow !== show) {
|
||||
switchShow(shouldShow)
|
||||
}
|
||||
}, 500)
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={(show ? 'animate__fadeInUp' : 'animate__fadeOutUp') + ' animate__animated animate__faster'}>
|
||||
<div
|
||||
className='border dark:border-gray-500 dark:bg-gray-600 bg-white cursor-pointer hover:shadow-2xl'
|
||||
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}>
|
||||
<a className='dark:text-gray-200 fa fa-arrow-up p-4 transform hover:scale-150 duration-200' title={locale.POST.TOP}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default TopJumper
|
||||
@@ -1,76 +0,0 @@
|
||||
import Link from 'next/link'
|
||||
import BLOG from '@/blog.config'
|
||||
import { useState } from 'react'
|
||||
import { useLocale } from '@/lib/locale'
|
||||
import Router, { useRouter } from 'next/router'
|
||||
import DarkModeButton from '@/components/DarkModeButton'
|
||||
import SocialButton from '@/components/SocialButton'
|
||||
|
||||
const TopNav = ({ tags, currentTag }) => {
|
||||
const locale = useLocale()
|
||||
const [hiddenMenu, switchHiddenMenu] = useState(!currentTag)
|
||||
// 点击按钮更改菜单状态
|
||||
const handleMenuClick = () => {
|
||||
switchHiddenMenu(!hiddenMenu)
|
||||
}
|
||||
const router = useRouter()
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const handleKeyUp = (e) => {
|
||||
if (e.keyCode === 13) {
|
||||
Router.push({ pathname: '/', query: { s: searchValue } })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='bg-white dark:bg-gray-600'>
|
||||
{/* 隐藏的顶部菜单 */}
|
||||
<nav
|
||||
className={(hiddenMenu ? '-mt-10' : ' ') + ' py-1 overflow-hidden bg-gray-800 text-xl text-gray-200 w-full ease-in-out duration-500'}>
|
||||
<ul className='mx-5 duration-300'>
|
||||
<li>
|
||||
<SocialButton/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/* 导航栏 */}
|
||||
<div
|
||||
id='sticky-nav'
|
||||
className='text-sm ticky-nav m-auto w-full flex flex-row justify-between items-center px-5 '
|
||||
>
|
||||
<div>
|
||||
<Link href='/'>
|
||||
<a
|
||||
className='flex justify-center border-black border-2 bg-whitefont-semibold hover:bg-gray-800 hover:text-white p-2 duration-200
|
||||
dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-100 dark:hover:text-black
|
||||
'>{BLOG.title}</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{/* 搜索框 */}
|
||||
<div className='px-4 flex w-20'>
|
||||
<i className='py-3 fa fa-search text-gray-400 absolute cursor-pointer px-2' />
|
||||
<input
|
||||
type='text'
|
||||
placeholder={currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`}
|
||||
className={'transition duration-200 leading-10 pl-8 block border-gray-300 dark:border-gray-600 bg-white text-black dark:bg-gray-800 dark:text-white'}
|
||||
onKeyUp={handleKeyUp}
|
||||
onChange={e => setSearchValue(e.target.value)}
|
||||
defaultValue={router.query.s ?? ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-nowrap space-x-1'>
|
||||
<div className='z-10 p-1 border hover:shadow-xl duration-200 dark:border-gray-500 mr-2 h-12 my-2 bg-white dark:bg-gray-600 dark:bg-opacity-70 bg-opacity-70 dark:hover:bg-gray-100 text-xl cursor-pointer dark:text-gray-300 dark:hover:text-black'>
|
||||
<i className={'fa p-2.5 hover:scale-125 transform duration-200 ' + (hiddenMenu ? ' fa-bars ' : ' fa-times') } onClick={handleMenuClick} />
|
||||
</div>
|
||||
<DarkModeButton/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TopNav
|
||||
@@ -1,13 +1,21 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
/**
|
||||
* 评论插件
|
||||
* @param issueTerm
|
||||
* @param layout
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const Utterances = ({ issueTerm, layout }) => {
|
||||
useEffect(() => {
|
||||
const theme =
|
||||
BLOG.appearance === 'auto'
|
||||
? 'preferred-color-scheme'
|
||||
: BLOG.appearance === 'light'
|
||||
? 'github-light'
|
||||
: 'github-dark'
|
||||
? 'github-light'
|
||||
: 'github-dark'
|
||||
const script = document.createElement('script')
|
||||
const anchor = document.getElementById('comments')
|
||||
script.setAttribute('src', 'https://utteranc.es/client.js')
|
||||
|
||||
|
Before Width: | Height: | Size: 352 KiB After Width: | Height: | Size: 352 KiB |
BIN
docs/screenshot-youtube.png
Normal file
|
After Width: | Height: | Size: 767 KiB |
@@ -5,7 +5,7 @@
|
||||
"@/*": ["./*"],
|
||||
"@/components/*": ["components/*"],
|
||||
"@/data/*": ["data/*"],
|
||||
"@/layouts/*": ["layouts/*"],
|
||||
"@/layouts/*": ["theme/*"],
|
||||
"@/lib/*": ["lib/*"],
|
||||
"@/styles/*": ["styles/*"]
|
||||
}
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
import TagItem from '@/components/TagItem'
|
||||
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
|
||||
import BLOG from '@/blog.config'
|
||||
import formatDate from '@/lib/formatDate'
|
||||
import 'gitalk/dist/gitalk.css'
|
||||
import Comment from '@/components/Comment'
|
||||
import Progress from '@/components/Progress'
|
||||
import { useRef } from 'react'
|
||||
import Image from 'next/image'
|
||||
import RewardButton from '@/components/RewardButton'
|
||||
import { useTheme } from '@/lib/theme'
|
||||
import SideBar from '@/components/SideBar'
|
||||
import BlogPostMini from '@/components/BlogPostMini'
|
||||
import { useRouter } from 'next/router'
|
||||
import ShareButton from '@/components/ShareButton'
|
||||
import TopJumper from '@/components/TopJumper'
|
||||
import CommonHead from '@/components/CommonHead'
|
||||
|
||||
const mapPageUrl = id => {
|
||||
return 'https://www.notion.so/' + id.replace(/-/g, '')
|
||||
}
|
||||
|
||||
const ArticleLayout = ({
|
||||
children,
|
||||
blockMap,
|
||||
frontMatter,
|
||||
emailHash,
|
||||
fullWidth = true,
|
||||
tags,
|
||||
prev,
|
||||
next
|
||||
}) => {
|
||||
const meta = {
|
||||
title: frontMatter.title,
|
||||
type: 'article'
|
||||
}
|
||||
const targetRef = useRef(null)
|
||||
const { theme } = useTheme()
|
||||
const url = BLOG.link + useRouter().asPath
|
||||
|
||||
return (
|
||||
<div className={`${BLOG.font} ${theme}`}>
|
||||
<CommonHead meta={meta} />
|
||||
|
||||
{/* live2d 看板娘 */}
|
||||
<script async src='https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js' />
|
||||
|
||||
<Progress targetRef={targetRef} />
|
||||
|
||||
{/* Wrapper */}
|
||||
<div className='flex justify-between bg-gray-100 dark:bg-black'>
|
||||
|
||||
<SideBar tags={tags} post={frontMatter} />
|
||||
|
||||
{/* 主体区块 */}
|
||||
<main className='bg-gray-100 dark:bg-black w-full'>
|
||||
{/* 中央区域 wrapper */}
|
||||
<div>
|
||||
|
||||
<header
|
||||
className='hover:scale-105 hover:shadow-2xl duration-200 transform mx-auto max-w-5xl mt-20 md:flex-shrink-0 overflow-y-hidden animate__fadeIn animate__animated'>
|
||||
{/* 封面图 */}
|
||||
{frontMatter.page_cover && frontMatter.page_cover.length > 1 && (
|
||||
<img className='bg-center object-cover w-full' style={{ maxHeight: '40rem' }}
|
||||
src={frontMatter.page_cover} alt={frontMatter.title} />
|
||||
)}
|
||||
</header>
|
||||
|
||||
<article
|
||||
ref={targetRef}
|
||||
className='hover:shadow-2xl mb-20 overflow-x-auto px-10 py-10 max-w-5xl mx-auto bg-white dark:border-gray-700 dark:bg-gray-600'>
|
||||
{/* 文章标题 */}
|
||||
<h1 className='font-bold text-4xl text-black my-5 dark:text-white animate__animated animate__fadeIn'>
|
||||
{frontMatter.title}
|
||||
</h1>
|
||||
|
||||
{/* 文章信息 */}
|
||||
<div className='justify-between flex flex-wrap bg-gray-50 p-2 dark:bg-gray-700 dark:text-white'>
|
||||
<div className='flex-nowrap flex'>
|
||||
|
||||
{frontMatter.slug !== 'about' && (<>
|
||||
<a
|
||||
className='hidden md:block duration-200 px-1' href='/article/about'
|
||||
>
|
||||
<Image alt={BLOG.author} width={33} height={33} src='/avatar.svg'
|
||||
className='rounded-full cursor-pointer transform hover:scale-125 duration-200' />
|
||||
</a>
|
||||
</>)}
|
||||
{frontMatter.tags && (
|
||||
<div className='flex flex-nowrap leading-8 p-1'>
|
||||
{frontMatter.tags.map(tag => (
|
||||
<TagItem key={tag} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{frontMatter.type[0] !== 'Page' && (
|
||||
<div className='flex items-start text-gray-500 dark:text-gray-400 leading-10'>
|
||||
{formatDate(
|
||||
frontMatter?.date?.start_date || frontMatter.createdTime,
|
||||
BLOG.lang
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 不蒜子 */}
|
||||
<div id='busuanzi_container_page_pv' className='hidden'>
|
||||
<a href='https://analytics.google.com/analytics/web/#/p273013569/reports/reportinghub'
|
||||
className='fa fa-eye text-gray-500 text-sm leading-none py-1 px-2'>
|
||||
<span id='busuanzi_value_page_pv' className='leading-6'></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Notion文章主体 */}
|
||||
{blockMap && (
|
||||
<NotionRenderer recordMap={blockMap} mapPageUrl={mapPageUrl}
|
||||
components={{
|
||||
equation: Equation,
|
||||
code: Code,
|
||||
collectionRow: CollectionRow,
|
||||
collection: Collection
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='flex justify-center pt-5'>
|
||||
<RewardButton />
|
||||
</div>
|
||||
<p className='flex justify-center py-5'>
|
||||
- 💖 本 文 结 束 😚 感 谢 您 的 阅 读 💖 -
|
||||
</p>
|
||||
|
||||
{/* 版权声明 */}
|
||||
<section
|
||||
className='overflow-auto dark:bg-gray-700 dark:text-gray-300 bg-gray-100 p-5 leading-8 border-l-4 border-red-500'>
|
||||
<ul>
|
||||
<li><strong>本文作者:</strong>{BLOG.author}</li>
|
||||
<li><strong>本文链接:</strong> <a href={url}>{url}</a> 《{frontMatter.title}》</li>
|
||||
<li><strong>版权声明:</strong> 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div className='text-gray-800 my-5 dark:text-gray-300'>
|
||||
<div className='mt-4 my-2 font-bold'>继续阅读</div>
|
||||
<div className='flex flex-wrap lg:flex-nowrap lg:space-x-10 justify-between py-2'>
|
||||
<BlogPostMini post={prev} />
|
||||
<BlogPostMini post={next} />
|
||||
</div>
|
||||
</div>
|
||||
{/* 评论互动 */}
|
||||
<Comment frontMatter={frontMatter} />
|
||||
</article>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
{/* 下方菜单组 */}
|
||||
<div
|
||||
className='right-0 space-x-2 fixed flex bottom-24 px-5 py-1 duration-500'>
|
||||
<div className='flex-wrap'>
|
||||
{/* 分享按钮 */}
|
||||
<ShareButton post={frontMatter} />
|
||||
{/* 跳回顶部 */}
|
||||
<TopJumper />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ArticleLayout
|
||||
@@ -1,130 +0,0 @@
|
||||
import BlogPost from '@/components/BlogPost'
|
||||
import PropTypes from 'prop-types'
|
||||
import Pagination from '@/components/Pagination'
|
||||
import BLOG from '@/blog.config'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTheme } from '@/lib/theme'
|
||||
import { useEffect, useState } from 'react'
|
||||
import SideBar from '@/components/SideBar'
|
||||
import throttle from 'lodash.throttle'
|
||||
import CommonHead from '@/components/CommonHead'
|
||||
|
||||
const DefaultLayout = ({ tags, posts, page, currentTag, ...customMeta }) => {
|
||||
const meta = {
|
||||
title: BLOG.title,
|
||||
type: 'website',
|
||||
...customMeta
|
||||
}
|
||||
page = page ?? 1
|
||||
let postsToShow = []
|
||||
let filteredBlogPosts = posts ?? []
|
||||
let currentSearch = ''
|
||||
if (posts) {
|
||||
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)
|
||||
|
||||
if (posts) {
|
||||
postsToShow = filteredBlogPosts.slice(
|
||||
BLOG.postsPerPage * (page - 1),
|
||||
BLOG.postsPerPage * page
|
||||
)
|
||||
}
|
||||
let showNext = false
|
||||
if (filteredBlogPosts) {
|
||||
const totalPosts = filteredBlogPosts.length
|
||||
showNext = page * BLOG.postsPerPage < totalPosts
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// 首页隐藏看板娘
|
||||
// const ref = document.getElementById('waifu')
|
||||
// if (ref) {
|
||||
// ref.remove()
|
||||
// }
|
||||
window.addEventListener('resize', changeColumnCount)
|
||||
changeColumnCount()
|
||||
return () => {
|
||||
window.removeEventListener('resize', changeColumnCount)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const changeColumnCount = throttle(() => {
|
||||
if (window.innerWidth > 2500) {
|
||||
changeColumn(5)
|
||||
} else if (window.innerWidth > 1800) {
|
||||
changeColumn(4)
|
||||
} else if (window.innerWidth > 1300) {
|
||||
changeColumn(3)
|
||||
} else if (window.innerWidth > 900) {
|
||||
changeColumn(2)
|
||||
} else if (window.innerWidth <= 900) {
|
||||
changeColumn(1)
|
||||
}
|
||||
}, 500)
|
||||
|
||||
const [column, changeColumn] = useState(3)
|
||||
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<div id='wrapper' className={theme}>
|
||||
<CommonHead meta={meta} />
|
||||
<div className={`${BLOG.font} flex bg-gray-100 dark:bg-black min-h-screen`}>
|
||||
{/* 侧边菜单 */}
|
||||
<SideBar tags={tags} currentTag={currentTag} />
|
||||
<main className='md:px-24 p-5 flex-grow'>
|
||||
{(!page || page === 1) && (<div className='py-5' />)}
|
||||
|
||||
{/* 标签 */}
|
||||
{currentTag && (
|
||||
<div className='pb-5 dark:text-gray-200'>
|
||||
<div className='py-1'>标签: {currentTag}</div>
|
||||
<hr />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 当前搜索 */}
|
||||
{(currentSearch || (page && page !== 1)) && (
|
||||
<div className='pb-5'>
|
||||
<div className='dark:text-gray-200 flex justify-between py-1'>
|
||||
{currentSearch && (<span>搜索关键词: {currentSearch}</span>)}
|
||||
{page && page !== 1 && (<span>页 {page} / {totalPages}</span>)}
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='mx-auto'>
|
||||
{/* 文章列表 */}
|
||||
<div style={{ columnCount: column }}>
|
||||
{!postsToShow.length && (
|
||||
<p className='text-gray-500 dark:text-gray-300'>No posts found.</p>
|
||||
)}
|
||||
{postsToShow.map(post => (
|
||||
<BlogPost key={post.id} post={post} tags={tags} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Pagination page={page} showNext={showNext} />
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
DefaultLayout.propTypes = {
|
||||
posts: PropTypes.array.isRequired,
|
||||
tags: PropTypes.object.isRequired,
|
||||
currentTag: PropTypes.string
|
||||
}
|
||||
export default DefaultLayout
|
||||
104
lib/busuanzi.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/* eslint-disable */
|
||||
let bszCaller, bszTag, scriptTag, ready
|
||||
|
||||
let t; let e; let n; let a = !1
|
||||
let c = []
|
||||
|
||||
// 修复Node同构代码的问题
|
||||
if (typeof document !== 'undefined') {
|
||||
ready = function (t) {
|
||||
return a || document.readyState === 'interactive' || document.readyState === 'complete'
|
||||
? t.call(document)
|
||||
: c.push(function () {
|
||||
return t.call(this)
|
||||
}), this
|
||||
}, e = function () {
|
||||
for (let t = 0, e = c.length; t < e; t++) c[t].apply(document)
|
||||
c = []
|
||||
}, n = function () {
|
||||
a || (a = !0, e.call(window),
|
||||
document.removeEventListener ? document.removeEventListener('DOMContentLoaded', n, !1) : document.attachEvent && (document.detachEvent('onreadystatechange', n), window == window.top && (clearInterval(t), t = null)))
|
||||
}, document.addEventListener
|
||||
? document.addEventListener('DOMContentLoaded', n, !1)
|
||||
: document.attachEvent && (document.attachEvent('onreadystatechange', function () {
|
||||
/loaded|complete/.test(document.readyState) && n()
|
||||
}), window == window.top && (t = setInterval(function () {
|
||||
try {
|
||||
a || document.documentElement.doScroll('left')
|
||||
} catch (t) {
|
||||
return
|
||||
}
|
||||
n()
|
||||
}, 5)))
|
||||
}
|
||||
|
||||
bszCaller = {
|
||||
fetch: function (t, e) {
|
||||
const n = 'BusuanziCallback_' + Math.floor(1099511627776 * Math.random())
|
||||
t = t.replace('=BusuanziCallback', '=' + n)
|
||||
scriptTag = document.createElement('SCRIPT'), scriptTag.type = 'text/javascript', scriptTag.defer = !0, scriptTag.src = t, scriptTag.referrerPolicy = "no-referrer-when-downgrade", document.getElementsByTagName('HEAD')[0].appendChild(scriptTag)
|
||||
window[n] = this.evalCall(e)
|
||||
},
|
||||
evalCall: function (e) {
|
||||
return function (t) {
|
||||
ready(function () {
|
||||
try {
|
||||
e(t), scriptTag && scriptTag.parentElement && scriptTag.parentElement.removeChild && scriptTag.parentElement.removeChild(scriptTag)
|
||||
} catch (t) {
|
||||
// console.log(t), bszTag.hides()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fetch = () => {
|
||||
bszTag && bszTag.hides()
|
||||
bszCaller.fetch('//busuanzi.ibruce.info/busuanzi?jsonpCallback=BusuanziCallback', function (t) {
|
||||
// console.log('不蒜子请求结果',t)
|
||||
bszTag.texts(t), bszTag.shows()
|
||||
})
|
||||
}
|
||||
|
||||
bszTag = {
|
||||
bszs: ['site_pv', 'page_pv', 'site_uv'],
|
||||
texts: function (n) {
|
||||
this.bszs.map(function (t) {
|
||||
const e = document.getElementsByClassName('busuanzi_value_' + t)
|
||||
if(e){
|
||||
for (var element of e) {
|
||||
element.innerHTML = n[t]
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
hides: function () {
|
||||
this.bszs.map(function (t) {
|
||||
const e = document.getElementsByClassName('busuanzi_container_' + t)
|
||||
if(e){
|
||||
for (var element of e){
|
||||
element.style.display = 'none'
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
shows: function () {
|
||||
this.bszs.map(function (t) {
|
||||
const e = document.getElementsByClassName('busuanzi_container_' + t)
|
||||
if(e){
|
||||
for(var element of e){
|
||||
element.style.display = 'inline'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 修复Node同构代码的问题
|
||||
if (typeof document !== 'undefined') {
|
||||
fetch()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fetch
|
||||
}
|
||||
27
lib/cache/cache_manager.js
vendored
@@ -1,21 +1,26 @@
|
||||
import { getCacheFromFile, setCacheToFile } from '@/lib/cache/local_file_cache'
|
||||
import { getCacheFromMemory, setCacheToMemory } from '@/lib/cache/memory_cache'
|
||||
import BLOG from '@/blog.config'
|
||||
// import { getCacheFromFile, setCacheToFile } from './local_file_cache'
|
||||
const enableCache = true // 生产环境禁用
|
||||
|
||||
/**
|
||||
* 为减少频繁接口请求,notion数据将被缓存
|
||||
* @param {*} key
|
||||
* @returns
|
||||
*/
|
||||
export async function getDataFromCache (key) {
|
||||
let dataFromCache
|
||||
if (BLOG.isProd) {
|
||||
dataFromCache = await getCacheFromMemory(key)
|
||||
} else {
|
||||
dataFromCache = await getCacheFromFile(key)
|
||||
if (!enableCache) {
|
||||
return null
|
||||
}
|
||||
const dataFromCache = await getCacheFromMemory(key)
|
||||
if (JSON.stringify(dataFromCache) === '[]') {
|
||||
return null
|
||||
}
|
||||
return dataFromCache
|
||||
}
|
||||
|
||||
export async function setDataToCache (key, data) {
|
||||
if (BLOG.isProd) {
|
||||
await setCacheToMemory(key, data)
|
||||
} else {
|
||||
await setCacheToFile(key, data)
|
||||
if (!enableCache || !data) {
|
||||
return
|
||||
}
|
||||
await setCacheToMemory(key, data)
|
||||
}
|
||||
|
||||
10
lib/cache/local_file_cache.js
vendored
@@ -1,5 +1,4 @@
|
||||
import fs from 'fs'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
const path = require('path')
|
||||
// 文件缓存持续10秒
|
||||
@@ -11,7 +10,14 @@ export async function getCacheFromFile (key) {
|
||||
const exist = await fs.existsSync(jsonFile)
|
||||
if (!exist) return null
|
||||
const data = await fs.readFileSync(jsonFile)
|
||||
const json = data ? JSON.parse(data) : {}
|
||||
let json = null
|
||||
if (!data) return null
|
||||
try {
|
||||
json = JSON.parse(data)
|
||||
} catch (error) {
|
||||
console.error('读取JSON缓存文件失败', data)
|
||||
return null
|
||||
}
|
||||
// 缓存超过有效期就作废
|
||||
const cacheValidTime = new Date(parseInt(json[key + '_expire_time']) + cacheInvalidSeconds)
|
||||
const currentTime = new Date()
|
||||
|
||||
9
lib/cache/memory_cache.js
vendored
@@ -1,9 +1,12 @@
|
||||
import cache from 'memory-cache'
|
||||
import BLOG from 'blog.config'
|
||||
|
||||
export async function getCacheFromMemory (key, options) { // url为缓存标识
|
||||
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)
|
||||
}
|
||||
|
||||
export async function setCacheToMemory (key, data) { // url为缓存标识
|
||||
await cache.put(key, data, 60 * 1000)
|
||||
export async function setCacheToMemory (key, data) {
|
||||
await cache.put(key, data, cacheTime * 1000)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
export default function formatDate(date, local) {
|
||||
/**
|
||||
* 格式化日期
|
||||
* @param date
|
||||
* @param local
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function formatDate (date, local) {
|
||||
const d = new Date(date)
|
||||
const options = { year: 'numeric', month: 'short', day: 'numeric' }
|
||||
const res = d.toLocaleDateString(local, options)
|
||||
return local.slice(0, 2).toLowerCase() === 'zh'
|
||||
? res.replace('年', ' 年 ').replace('月', ' 月 ').replace('日', ' 日')
|
||||
? res.replace('年', '-').replace('月', '-').replace('日', '')
|
||||
: res
|
||||
}
|
||||
|
||||
export function formatDateFmt (timestamp, fmt) {
|
||||
const date = new Date(timestamp)
|
||||
const o = {
|
||||
'M+': date.getMonth() + 1, // 月份
|
||||
'd+': date.getDate(), // 日
|
||||
'h+': date.getHours(), // 小时
|
||||
'm+': date.getMinutes(), // 分
|
||||
's+': date.getSeconds(), // 秒
|
||||
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
S: date.getMilliseconds() // 毫秒
|
||||
}
|
||||
if (/(y+)/.test(fmt)) {
|
||||
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
|
||||
}
|
||||
for (const k in o) {
|
||||
if (new RegExp('(' + k + ')').test(fmt)) {
|
||||
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
|
||||
}
|
||||
}
|
||||
return fmt.trim()
|
||||
}
|
||||
|
||||
111
lib/global.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import lang from './lang'
|
||||
import { useContext, createContext, useState, useEffect } from 'react'
|
||||
import Router from 'next/router'
|
||||
import { initTheme, loadUserThemeFromCookies } from './theme'
|
||||
const GlobalContext = createContext()
|
||||
|
||||
/**
|
||||
* 全局变量Provider,包括语言本地化、样式主题、搜索词
|
||||
* @param children
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export function GlobalContextProvider ({ children }) {
|
||||
const [locale, changeLocale] = useState(generateLocaleDict('en-US'))
|
||||
const [theme, changeTheme] = useState(loadUserThemeFromCookies())
|
||||
const [onLoading, changeLoadingState] = useState(false)
|
||||
Router.events.on('routeChangeStart', (...args) => {
|
||||
changeLoadingState(true)
|
||||
})
|
||||
|
||||
Router.events.on('routeChangeComplete', (...args) => {
|
||||
changeLoadingState(false)
|
||||
})
|
||||
|
||||
// 服务端静态渲染,在渲染hooks后根据前端变量做初始化工作
|
||||
useEffect(() => {
|
||||
initTheme(theme, changeTheme)
|
||||
initLocale(locale, changeLocale)
|
||||
})
|
||||
|
||||
return (
|
||||
<GlobalContext.Provider value={{ onLoading, locale, theme, changeTheme }}>
|
||||
{children}
|
||||
</GlobalContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前语言字典
|
||||
* @returns 不同语言对应字典
|
||||
*/
|
||||
const generateLocaleDict = (langString) => {
|
||||
let userLocale = lang['en-US']
|
||||
if (!langString) {
|
||||
return userLocale
|
||||
}
|
||||
if (langString.slice(0, 2).toLowerCase() === 'zh') {
|
||||
switch (langString.toLowerCase()) {
|
||||
case 'zh-cn':
|
||||
case 'zh-sg':
|
||||
userLocale = lang['zh-CN']
|
||||
break
|
||||
case 'zh-hk':
|
||||
userLocale = lang['zh-HK']
|
||||
break
|
||||
case 'zh-tw':
|
||||
userLocale = lang['zh-TW']
|
||||
break
|
||||
default:
|
||||
userLocale = lang['zh-CN']
|
||||
}
|
||||
}
|
||||
const resLocale = mergeDeep({}, lang['en-US'], userLocale)
|
||||
return resLocale
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化语言
|
||||
* 根据用户当前浏览器语言进行切换
|
||||
*/
|
||||
const initLocale = (locale, changeLocale) => {
|
||||
if (window) {
|
||||
const targetLocale = generateLocaleDict(window.navigator.language)
|
||||
if (JSON.stringify(locale) !== JSON.stringify(targetLocale)) {
|
||||
changeLocale(targetLocale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度合并两个对象
|
||||
* @param target
|
||||
* @param sources
|
||||
*/
|
||||
export function mergeDeep (target, ...sources) {
|
||||
if (!sources.length) return target
|
||||
const source = sources.shift()
|
||||
|
||||
if (isObject(target) && isObject(source)) {
|
||||
for (const key in source) {
|
||||
if (isObject(source[key])) {
|
||||
if (!target[key]) Object.assign(target, { [key]: {} })
|
||||
mergeDeep(target[key], source[key])
|
||||
} else {
|
||||
Object.assign(target, { [key]: source[key] })
|
||||
}
|
||||
}
|
||||
}
|
||||
return mergeDeep(target, ...sources)
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象检查
|
||||
* @param item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isObject (item) {
|
||||
return (item && typeof item === 'object' && !Array.isArray(item))
|
||||
}
|
||||
|
||||
export const useGlobal = () => useContext(GlobalContext)
|
||||
89
lib/lang.js
@@ -1,83 +1,12 @@
|
||||
import zhCN from './lang/zh-CN'
|
||||
import enUS from './lang/en-US'
|
||||
import zhHK from './lang/zh-HK'
|
||||
import zhTW from './lang/zh-TW'
|
||||
const lang = {
|
||||
en: {
|
||||
NAV: {
|
||||
INDEX: 'Blog',
|
||||
RSS: 'RSS',
|
||||
SEARCH: 'Search',
|
||||
ABOUT: 'About',
|
||||
NAVGATION: 'NAVGATION'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: 'Prev',
|
||||
NEXT: 'Next'
|
||||
},
|
||||
SEARCH: {
|
||||
ARTICLES: 'Search Articles',
|
||||
TAGS: 'Search in'
|
||||
},
|
||||
POST: {
|
||||
BACK: 'Back',
|
||||
TOP: 'Top'
|
||||
}
|
||||
},
|
||||
'zh-CN': {
|
||||
NAV: {
|
||||
INDEX: '首页',
|
||||
RSS: '订阅',
|
||||
SEARCH: '搜索',
|
||||
ABOUT: '关于',
|
||||
NAVGATION: '导航'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: '上一页',
|
||||
NEXT: '下一页'
|
||||
},
|
||||
SEARCH: {
|
||||
ARTICLES: '搜索文章',
|
||||
TAGS: '搜索标签'
|
||||
},
|
||||
POST: {
|
||||
BACK: '返回上页',
|
||||
TOP: '回到顶部'
|
||||
}
|
||||
},
|
||||
'zh-HK': {
|
||||
NAV: {
|
||||
INDEX: '網誌',
|
||||
RSS: '訂閱',
|
||||
SEARCH: '搜尋',
|
||||
ABOUT: '關於',
|
||||
NAVGATION: '導航'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: '上一頁',
|
||||
NEXT: '下一頁'
|
||||
},
|
||||
SEARCH: {
|
||||
ARTICLES: '搜尋文章',
|
||||
TAGS: '搜尋標簽'
|
||||
},
|
||||
POST: {
|
||||
BACK: '返回',
|
||||
TOP: '回到頂端'
|
||||
}
|
||||
},
|
||||
'zh-TW': {
|
||||
NAV: {
|
||||
INDEX: '部落格',
|
||||
RSS: '訂閱',
|
||||
SEARCH: '搜尋',
|
||||
ABOUT: '關於',
|
||||
NAVGATION: '導航'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: '上一頁',
|
||||
NEXT: '下一頁'
|
||||
},
|
||||
POST: {
|
||||
BACK: '返回',
|
||||
TOP: '回到頂端'
|
||||
}
|
||||
}
|
||||
'en-US': enUS,
|
||||
'zh-CN': zhCN,
|
||||
'zh-HK': zhHK,
|
||||
'zh-TW': zhTW
|
||||
}
|
||||
|
||||
export default lang
|
||||
|
||||
45
lib/lang/en-US.js
Normal file
@@ -0,0 +1,45 @@
|
||||
export default {
|
||||
LOCALE: 'en-US',
|
||||
NAV: {
|
||||
INDEX: 'Blog',
|
||||
RSS: 'RSS',
|
||||
SEARCH: 'Search',
|
||||
ABOUT: 'About',
|
||||
MAIL: 'E-Mail',
|
||||
ARCHIVE: 'Archive'
|
||||
},
|
||||
COMMON: {
|
||||
MORE: 'More',
|
||||
NO_MORE: 'No More',
|
||||
LATEST_POSTS: 'Latest posts',
|
||||
TAGS: 'Tags',
|
||||
NO_TAG: 'NoTag',
|
||||
CATEGORY: 'Category',
|
||||
SHARE: 'Share',
|
||||
SCAN_QR_CODE: 'Scan QRCode',
|
||||
URL_COPIED: 'URL has copied!',
|
||||
TABLE_OF_CONTENTS: 'Table of Contents',
|
||||
RELATE_POSTS: 'Relate Posts',
|
||||
COPYRIGHT: 'Copyright',
|
||||
AUTHOR: 'Author',
|
||||
URL: 'URL',
|
||||
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!',
|
||||
RESULT_OF_SEARCH: 'Results Found',
|
||||
ARTICLE_DETAIL: 'Article Details'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: 'Prev',
|
||||
NEXT: 'Next'
|
||||
},
|
||||
SEARCH: {
|
||||
ARTICLES: 'Search Articles',
|
||||
TAGS: 'Search in'
|
||||
},
|
||||
POST: {
|
||||
BACK: 'Back',
|
||||
TOP: 'Top'
|
||||
}
|
||||
}
|
||||
47
lib/lang/zh-CN.js
Normal file
@@ -0,0 +1,47 @@
|
||||
export default {
|
||||
LOCALE: 'zh-CN',
|
||||
NAV: {
|
||||
INDEX: '首页',
|
||||
RSS: '订阅',
|
||||
SEARCH: '搜索',
|
||||
ABOUT: '关于',
|
||||
NAVIGATOR: '导航',
|
||||
MAIL: '邮箱',
|
||||
ARCHIVE: '归档'
|
||||
},
|
||||
COMMON: {
|
||||
MORE: '更多',
|
||||
NO_MORE: '没有更多了',
|
||||
LATEST_POSTS: '最新文章',
|
||||
TAGS: '标签',
|
||||
NO_TAG: 'NoTag',
|
||||
CATEGORY: '分类',
|
||||
SHARE: '分享',
|
||||
SCAN_QR_CODE: '扫一扫二维码',
|
||||
URL_COPIED: '链接已复制!',
|
||||
TABLE_OF_CONTENTS: '目录',
|
||||
RELATE_POSTS: '相关文章',
|
||||
COPYRIGHT: '声明',
|
||||
AUTHOR: '作者',
|
||||
URL: '链接',
|
||||
ANALYTICS: '统计',
|
||||
POSTS: '篇文章',
|
||||
VISITORS: '位访客',
|
||||
VIEWS: '次查看',
|
||||
COPYRIGHT_NOTICE: '本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。',
|
||||
RESULT_OF_SEARCH: '篇搜索到的结果',
|
||||
ARTICLE_DETAIL: '文章详情'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: '上一页',
|
||||
NEXT: '下一页'
|
||||
},
|
||||
SEARCH: {
|
||||
ARTICLES: '搜索文章',
|
||||
TAGS: '搜索标签'
|
||||
},
|
||||
POST: {
|
||||
BACK: '返回上页',
|
||||
TOP: '回到顶部'
|
||||
}
|
||||
}
|
||||
21
lib/lang/zh-HK.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
NAV: {
|
||||
INDEX: '網誌',
|
||||
RSS: '訂閱',
|
||||
SEARCH: '搜尋',
|
||||
ABOUT: '關於',
|
||||
MAIL: '電郵'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: '上一頁',
|
||||
NEXT: '下一頁'
|
||||
},
|
||||
SEARCH: {
|
||||
ARTICLES: '搜尋文章',
|
||||
TAGS: '搜尋標簽'
|
||||
},
|
||||
POST: {
|
||||
BACK: '返回',
|
||||
TOP: '回到頂端'
|
||||
}
|
||||
}
|
||||
21
lib/lang/zh-TW.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
NAV: {
|
||||
INDEX: '部落格',
|
||||
RSS: '訂閱',
|
||||
SEARCH: '搜尋',
|
||||
ABOUT: '關於',
|
||||
MAIL: '電郵'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: '上一頁',
|
||||
NEXT: '下一頁'
|
||||
},
|
||||
SEARCH: {
|
||||
ARTICLES: '搜尋文章',
|
||||
TAGS: '搜尋標簽'
|
||||
},
|
||||
POST: {
|
||||
BACK: '返回',
|
||||
TOP: '回到頂端'
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import lang from './lang'
|
||||
import { useContext, createContext } from 'react'
|
||||
|
||||
let locale = {}
|
||||
if (BLOG.lang.slice(0, 2).toLowerCase() === 'zh') {
|
||||
switch (BLOG.lang.toLowerCase()) {
|
||||
case 'zh-cn':
|
||||
case 'zh-sg':
|
||||
locale = lang['zh-CN']
|
||||
break
|
||||
case 'zh-hk':
|
||||
locale = lang['zh-HK']
|
||||
break
|
||||
case 'zh-tw':
|
||||
locale = lang['zh-TW']
|
||||
break
|
||||
default:
|
||||
locale = lang['zh-TW']
|
||||
break
|
||||
}
|
||||
} else {
|
||||
locale = lang.en
|
||||
}
|
||||
|
||||
const LocaleContext = createContext()
|
||||
|
||||
export function LocaleProvider({ children }) {
|
||||
return (
|
||||
<LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useLocale = () => useContext(LocaleContext)
|
||||
@@ -1,3 +1,4 @@
|
||||
export { getAllPosts } from './notion/getAllPosts'
|
||||
export { getAllTags } from './notion/getAllTags'
|
||||
export { getPostBlocks } from './notion/getPostBlocks'
|
||||
export { getAllCategories } from './notion/getAllCategories'
|
||||
|
||||
22
lib/notion/getAllCategories.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 获取所有文章的分类
|
||||
* @param allPosts
|
||||
* @returns {Promise<{}|*[]>}
|
||||
*/
|
||||
export async function getAllCategories (allPosts) {
|
||||
if (!allPosts) {
|
||||
return []
|
||||
}
|
||||
|
||||
let categories = allPosts.map(p => p.category)
|
||||
categories = [...categories.flat()]
|
||||
const categoryObj = {}
|
||||
categories.forEach(category => {
|
||||
if (category in categoryObj) {
|
||||
categoryObj[category]++
|
||||
} else {
|
||||
categoryObj[category] = 1
|
||||
}
|
||||
})
|
||||
return categoryObj
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { idToUuid } from 'notion-utils'
|
||||
|
||||
export default function getAllPageIds (collectionQuery, viewId) {
|
||||
if (!collectionQuery) {
|
||||
return []
|
||||
}
|
||||
const views = Object.values(collectionQuery)[0]
|
||||
if (!views) {
|
||||
return []
|
||||
@@ -12,7 +15,8 @@ export default function getAllPageIds (collectionQuery, viewId) {
|
||||
} else {
|
||||
const pageSet = new Set()
|
||||
Object.values(views).forEach(view => {
|
||||
view?.blockIds?.forEach(id => pageSet.add(id))
|
||||
view?.blockIds?.forEach(id => pageSet.add(id)) // group视图
|
||||
view?.collection_group_results?.blockIds?.forEach(id => pageSet.add(id)) // table视图
|
||||
})
|
||||
pageIds = [...pageSet]
|
||||
}
|
||||
|
||||
@@ -1,101 +1,80 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { idToUuid } from 'notion-utils'
|
||||
import getAllPageIds from './getAllPageIds'
|
||||
import getPageProperties from './getPageProperties'
|
||||
import { defaultMapImageUrl } from 'react-notion-x'
|
||||
import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
|
||||
import { getPostBlocks } from '@/lib/notion/getPostBlocks'
|
||||
import { getNotionPageData } from '@/lib/notion/getNotionData'
|
||||
|
||||
export async function getAllPosts () {
|
||||
const data = await getDataFromCache('posts_list')
|
||||
if (data) {
|
||||
return data
|
||||
/**
|
||||
* 获取所有文章列表
|
||||
* @param notionPageData
|
||||
* @param from
|
||||
* @param includePage 是否包含Page类型
|
||||
* @returns {Promise<*[]>}
|
||||
*/
|
||||
export async function getAllPosts ({ notionPageData, from, includePage = false }) {
|
||||
if (!notionPageData) {
|
||||
notionPageData = await getNotionPageData({ from })
|
||||
}
|
||||
let id = BLOG.notionPageId
|
||||
const pageRecordMap = await getPostBlocks(id)
|
||||
if (!pageRecordMap) {
|
||||
return <>获取数据异常</>
|
||||
if (!notionPageData) {
|
||||
return []
|
||||
}
|
||||
|
||||
id = idToUuid(id)
|
||||
const collection = Object.values(pageRecordMap.collection)[0]?.value
|
||||
const collectionQuery = pageRecordMap.collection_query
|
||||
const block = pageRecordMap.block
|
||||
const schema = collection?.schema
|
||||
const pageBlock = notionPageData.block
|
||||
const schema = notionPageData.schema
|
||||
const tagOptions = notionPageData.tagOptions
|
||||
const collectionQuery = notionPageData.collectionQuery
|
||||
|
||||
const rawMetadata = block[id].value
|
||||
const data = []
|
||||
const pageIds = getAllPageIds(collectionQuery)
|
||||
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
|
||||
properties.page_cover = getPostCover(id, pageBlock) ?? null
|
||||
properties.content = pageBlock[id].value?.content ?? []
|
||||
properties.tagItems = properties?.tags?.map(tag => {
|
||||
return { name: tag, color: tagOptions.find(t => t.value === tag)?.color || 'gray' }
|
||||
}) || []
|
||||
delete properties.content
|
||||
data.push(properties)
|
||||
}
|
||||
|
||||
// Check Type 兼容Page-Database和Inline-Database
|
||||
if (rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view') {
|
||||
console.warn(`pageId "${id}" is not a database`)
|
||||
return null
|
||||
} else {
|
||||
// Construct Data
|
||||
const pageIds = getAllPageIds(collectionQuery)
|
||||
const data = []
|
||||
for (let i = 0; i < pageIds.length; i++) {
|
||||
const id = pageIds[i]
|
||||
const properties = (await getPageProperties(id, block, schema)) || null
|
||||
|
||||
// Add fullwidth, createdtime to properties
|
||||
properties.createdTime = new Date(
|
||||
block[id].value?.created_time
|
||||
).toString()
|
||||
properties.fullWidth = block[id].value?.format?.page_full_width ?? false
|
||||
properties.page_cover = getPostCover(id, block, pageRecordMap) ?? getContentFirstImage(id, block, pageRecordMap)
|
||||
properties.content = block[id].value?.content ?? []
|
||||
data.push(properties)
|
||||
}
|
||||
// remove all the the items doesn't meet requirements
|
||||
const posts = data.filter(post => {
|
||||
// remove all the the items doesn't meet requirements
|
||||
const posts = data.filter(post => {
|
||||
if (includePage) {
|
||||
return (
|
||||
post.title &&
|
||||
post.slug &&
|
||||
post.title && post.slug &&
|
||||
post?.status?.[0] === 'Published' &&
|
||||
(post?.type?.[0] === 'Post' || post?.type?.[0] === 'Page')
|
||||
)
|
||||
})
|
||||
} else {
|
||||
return (
|
||||
post.title && post.slug &&
|
||||
post?.status?.[0] === 'Published' &&
|
||||
(post?.type?.[0] === 'Post')
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Sort by date
|
||||
if (BLOG.sortByDate) {
|
||||
posts.sort((a, b) => {
|
||||
const dateA = new Date(a?.date?.start_date || a.createdTime)
|
||||
const dateB = new Date(b?.date?.start_date || b.createdTime)
|
||||
return dateB - dateA
|
||||
})
|
||||
}
|
||||
if (posts) {
|
||||
await setDataToCache('posts_list', posts)
|
||||
}
|
||||
return posts
|
||||
// Sort by date
|
||||
if (BLOG.sortByDate) {
|
||||
posts.sort((a, b) => {
|
||||
const dateA = new Date(a?.date?.start_date || a.createdTime)
|
||||
const dateB = new Date(b?.date?.start_date || b.createdTime)
|
||||
return dateB - dateA
|
||||
})
|
||||
}
|
||||
return posts
|
||||
}
|
||||
|
||||
// 从Block获取封面图;优先取PageCover,否则取内容图片
|
||||
function getPostCover (id, block, pageRecordMap) {
|
||||
function getPostCover (id, block) {
|
||||
const pageCover = block[id].value?.format?.page_cover
|
||||
if (pageCover) {
|
||||
if (pageCover.startsWith('/')) return 'https://www.notion.so' + pageCover
|
||||
if (pageCover.startsWith('http')) return defaultMapImageUrl(pageCover, block[id].value)
|
||||
}
|
||||
}
|
||||
|
||||
// 取文章的第一个图片内容作为封面
|
||||
function getContentFirstImage (id, block, pageRecordMap) {
|
||||
const pageBlock = block[id]?.value
|
||||
|
||||
const contentBlockId = pageBlock?.content?.find((blockId) => {
|
||||
const block = pageRecordMap.block[blockId]?.value
|
||||
if (block?.type === 'image') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
if (contentBlockId) {
|
||||
const contentBlock = pageRecordMap.block[contentBlockId]?.value
|
||||
const source = contentBlock.properties?.source?.[0]?.[0] ??
|
||||
contentBlock.format?.display_source
|
||||
return defaultMapImageUrl(source, contentBlock)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { getAllPosts } from './getAllPosts'
|
||||
|
||||
export async function getAllTags (posts) {
|
||||
if (!posts) {
|
||||
const response = await getAllPosts()
|
||||
posts = response.filter(
|
||||
post =>
|
||||
post.status[0] === 'Published' && post.type[0] === 'Post' && post.tags
|
||||
)
|
||||
/**
|
||||
* 获取所有文章的标签
|
||||
* @param allPosts
|
||||
* @param sliceCount 默认截取数量为12,若为0则返回全部
|
||||
* @param tagOptions tags的下拉选项
|
||||
* @returns {Promise<{}|*[]>}
|
||||
*/
|
||||
export async function getAllTags ({ allPosts, sliceCount = 16, tagOptions }) {
|
||||
if (!allPosts) {
|
||||
return []
|
||||
}
|
||||
|
||||
let tags = posts.map(p => p.tags)
|
||||
let tags = allPosts.map(p => p.tags)
|
||||
tags = [...tags.flat()]
|
||||
|
||||
// 标签计数
|
||||
const tagObj = {}
|
||||
tags.forEach(tag => {
|
||||
if (tag in tagObj) {
|
||||
@@ -19,5 +23,16 @@ export async function getAllTags (posts) {
|
||||
tagObj[tag] = 1
|
||||
}
|
||||
})
|
||||
return tagObj
|
||||
|
||||
// 按照标签数量排序
|
||||
const list = Object.keys(tagObj).map((tag) => {
|
||||
const color = tagOptions.find(option => option.value === tag)?.color || 'gray'
|
||||
return { name: tag, count: tagObj[tag], color }
|
||||
})
|
||||
list.sort((a, b) => b.count - a.count)
|
||||
if (sliceCount && sliceCount > 0) {
|
||||
return list.slice(0, sliceCount)
|
||||
} else {
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default function getMetadata(rawMetadata) {
|
||||
export default function getMetadata (rawMetadata) {
|
||||
const metadata = {
|
||||
locked: rawMetadata?.format?.block_locked,
|
||||
page_full_width: rawMetadata?.format?.page_full_width,
|
||||
|
||||
121
lib/notion/getNotionData.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import BLOG from '@/blog.config'
|
||||
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
|
||||
* @param latestPostCount 截取最新文章数量
|
||||
* @param tagsCount 截取标签数量
|
||||
* @param includePage 是否包含PAGE类型
|
||||
* @returns { allPosts: '文章列表', latestPosts: ’最新文章, categories:‘分类列表’, postCount:'文章总数',tags:'标签列表' }
|
||||
* 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数据
|
||||
* @param pageId
|
||||
* @param from 请求来源
|
||||
* @returns {Promise<JSX.Element|*|*[]>}
|
||||
*/
|
||||
export async function getNotionPageData ({ pageId, from }) {
|
||||
// 尝试从缓存获取
|
||||
const cacheKey = 'page_record_map_' + pageId
|
||||
const data = await getDataFromCache(cacheKey)
|
||||
if (data) {
|
||||
console.log('[请求缓存]:', `from:${from}`, `id:${pageId}`)
|
||||
return data
|
||||
}
|
||||
const pageRecordMap = await getPageRecordMapByNotionAPI({ pageId, from })
|
||||
// 存入缓存
|
||||
if (pageRecordMap) {
|
||||
await setDataToCache(cacheKey, pageRecordMap)
|
||||
}
|
||||
return pageRecordMap
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签选项
|
||||
* @param schema
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function getTagOptions (schema) {
|
||||
const tagSchema = Object.values(schema).find(e => e.name === 'tags')
|
||||
return tagSchema?.options || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用NotionAPI获取Page数据
|
||||
* @returns {Promise<JSX.Element|null|*>}
|
||||
*/
|
||||
async function getPageRecordMapByNotionAPI ({ pageId, from }) {
|
||||
const pageRecordMap = await getPostBlocks(pageId, from)
|
||||
if (!pageRecordMap) {
|
||||
return []
|
||||
}
|
||||
|
||||
pageId = idToUuid(pageId)
|
||||
const collection = Object.values(pageRecordMap.collection)[0]?.value
|
||||
const collectionQuery = pageRecordMap.collection_query
|
||||
const block = pageRecordMap.block
|
||||
const schema = collection?.schema
|
||||
const rawMetadata = block[pageId].value
|
||||
const tagOptions = getTagOptions(schema)
|
||||
|
||||
// Check Type Page-Database和Inline-Database
|
||||
if (
|
||||
rawMetadata?.type !== 'collection_view_page' &&
|
||||
rawMetadata?.type !== 'collection_view'
|
||||
) {
|
||||
console.warn(`pageId "${pageId}" is not a database`)
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
collection,
|
||||
collectionQuery,
|
||||
block,
|
||||
schema,
|
||||
tagOptions,
|
||||
rawMetadata
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,7 @@ const indentLevels = {
|
||||
sub_sub_header: 2
|
||||
}
|
||||
|
||||
|
||||
export const getPageTableOfContents = (page,recordMap)=> {
|
||||
export const getPageTableOfContents = (page, recordMap) => {
|
||||
// 获取 header sub_header sub_sub_header
|
||||
const toc = (page.content ?? [])
|
||||
.map((blockId) => {
|
||||
@@ -66,4 +65,4 @@ export const getPageTableOfContents = (page,recordMap)=> {
|
||||
}
|
||||
|
||||
return toc
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,75 @@ import BLOG from '@/blog.config'
|
||||
import { NotionAPI } from 'notion-client'
|
||||
import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
|
||||
|
||||
export async function getPostBlocks (id) {
|
||||
let pageBlock = await getDataFromCache('page_block_' + id)
|
||||
export async function getPostBlocks (id, from, slice) {
|
||||
const cacheKey = 'page_block_' + id
|
||||
let pageBlock = await getDataFromCache(cacheKey)
|
||||
if (pageBlock) {
|
||||
return pageBlock
|
||||
console.log('[请求缓存]:', `from:${from}`, `id:${id}`)
|
||||
return filterPostBlocks(id, pageBlock, slice)
|
||||
}
|
||||
const authToken = BLOG.notionAccessToken || null
|
||||
const api = new NotionAPI({ authToken })
|
||||
pageBlock = await api.getPage(id)
|
||||
try {
|
||||
console.log('[请求API]:', `from:${from}`, `id:${id}`)
|
||||
pageBlock = await api.getPage(id)
|
||||
console.log('[请求成功]', `from:${from}`, `id:${id}`)
|
||||
} catch (error) {
|
||||
console.error('[请求失败]', `from:${from}`, `id:${id}`, `error:${error}`)
|
||||
return null
|
||||
}
|
||||
|
||||
if (pageBlock) {
|
||||
await setDataToCache('page_block_' + id, pageBlock)
|
||||
await setDataToCache(cacheKey, pageBlock)
|
||||
return filterPostBlocks(id, pageBlock, slice)
|
||||
}
|
||||
return pageBlock
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} id 页面ID
|
||||
* @param {*} pageBlock 页面元素
|
||||
* @param {*} slice 截取数量
|
||||
* @returns
|
||||
*/
|
||||
function filterPostBlocks (id, pageBlock, slice) {
|
||||
const clonePageBlock = deepClone(pageBlock)
|
||||
let count = 0
|
||||
|
||||
for (const i in clonePageBlock?.block) {
|
||||
const b = clonePageBlock?.block[i]
|
||||
if (slice && slice > 0 && count > slice) {
|
||||
delete clonePageBlock?.block[i]
|
||||
continue
|
||||
}
|
||||
count++
|
||||
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 (id === BLOG.notionPageId) {
|
||||
return clonePageBlock
|
||||
}
|
||||
return clonePageBlock
|
||||
}
|
||||
|
||||
function deepClone (obj) {
|
||||
const newObj = Array.isArray(obj) ? [] : {}
|
||||
if (obj && typeof obj === 'object') {
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
newObj[key] = (obj && typeof obj[key] === 'object') ? deepClone(obj[key]) : obj[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
return newObj
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Feed } from 'feed'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
export function generateRss(posts) {
|
||||
export function generateRss (posts) {
|
||||
const year = new Date().getFullYear()
|
||||
const feed = new Feed({
|
||||
title: BLOG.title,
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
57
lib/theme.js
@@ -1,19 +1,44 @@
|
||||
import { useContext, createContext, useState, useEffect } from 'react'
|
||||
import localStorage from 'localStorage'
|
||||
import cookie from 'react-cookies'
|
||||
|
||||
const ThemeContext = createContext()
|
||||
|
||||
export function ThemeProvider ({ children }) {
|
||||
// 初始值
|
||||
const defaultTheme = localStorage.getItem('theme')
|
||||
const [theme, changeTheme] = useState()
|
||||
useEffect(() => {
|
||||
changeTheme(defaultTheme)
|
||||
})
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, changeTheme }}>{children}</ThemeContext.Provider>
|
||||
)
|
||||
/**
|
||||
* 初始化主题
|
||||
* @param theme 用户默认主题state
|
||||
* @param changeTheme 更改主题ChangeState函数
|
||||
* @description 读取cookie中存的用户主题
|
||||
*/
|
||||
export const initTheme = (theme, changeTheme) => {
|
||||
// 若未指定主题,则从时间和浏览器偏好中决定初始主题
|
||||
if (!theme) {
|
||||
const date = new Date()
|
||||
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
const useDark = prefersDarkMode || (date.getHours() >= 18 || date.getHours() < 6)
|
||||
if (useDark) {
|
||||
theme = 'dark'
|
||||
} else {
|
||||
theme = 'light'
|
||||
}
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
const htmlElement = document.getElementsByTagName('html')
|
||||
htmlElement.className = ''
|
||||
changeTheme(theme)
|
||||
saveTheme(theme)
|
||||
htmlElement.classList?.add(theme)
|
||||
}
|
||||
}
|
||||
|
||||
export const useTheme = () => useContext(ThemeContext)
|
||||
/**
|
||||
* 读取默认主题
|
||||
* @returns {*}
|
||||
*/
|
||||
export const loadUserThemeFromCookies = () => {
|
||||
return cookie.load('theme')
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存默认主题
|
||||
* @param newTheme
|
||||
*/
|
||||
export const saveTheme = (newTheme) => {
|
||||
cookie.save('theme', newTheme, { path: '/' })
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ module.exports = {
|
||||
webpack5: true
|
||||
},
|
||||
images: {
|
||||
domains: ['gravatar.com']
|
||||
domains: ['gravatar.com', 'www.notion.so', 'avatars.githubusercontent.com']
|
||||
},
|
||||
async headers() {
|
||||
async headers () {
|
||||
return [
|
||||
{
|
||||
source: '/:path*{/}?',
|
||||
|
||||
27
package.json
@@ -13,30 +13,37 @@
|
||||
"url": "http://tangly1024.com"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "NODE_OPTIONS='--inspect' next dev",
|
||||
"build": "next build",
|
||||
"dev": "next dev",
|
||||
"build": "next build && next-sitemap --config next-sitemap.config.js",
|
||||
"start": "next start",
|
||||
"postbuild": "next-sitemap --config next-sitemap.config.js"
|
||||
"post-build": "next-sitemap --config next-sitemap.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.1.16",
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": ">=0.21.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"feed": "^4.2.2",
|
||||
"font-awesome": "^4.7.0",
|
||||
"gitalk": "^1.7.2",
|
||||
"localStorage": "^1.0.4",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"memory-cache": "^0.2.0",
|
||||
"next": "10.2.0",
|
||||
"notion-client": "^4.9.3",
|
||||
"notion-utils": "4.8.6",
|
||||
"preact": "^10.5.13",
|
||||
"next": "^12.0.5",
|
||||
"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.9.1",
|
||||
"react-notion-x": "4.13.0",
|
||||
"smoothscroll-polyfill": "^0.4.4",
|
||||
"typed.js": "^2.0.12",
|
||||
"use-ackee": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -48,7 +55,7 @@
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"next-sitemap": "^1.6.102",
|
||||
"next-sitemap": "^1.6.203",
|
||||
"postcss": "^8.2.15",
|
||||
"tailwindcss": "^2.1.2"
|
||||
},
|
||||
|
||||
BIN
pageid.png
|
Before Width: | Height: | Size: 130 KiB |
26
pages/404.js
@@ -1,31 +1,11 @@
|
||||
import { Layout404 } from '@/themes'
|
||||
|
||||
/**
|
||||
* 自定义404界面
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function Custom404 () {
|
||||
const route = useRouter()
|
||||
if (route.asPath.indexOf('/article') < 0 && route.asPath.indexOf('/404') < 0) {
|
||||
// article 重定向,处理旧文章链接迁移。
|
||||
const redirectUrl = '/article' + route.asPath
|
||||
route.push(redirectUrl)
|
||||
} else {
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
window.location.href = '/'
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
return <div
|
||||
className='text-black bg-white h-screen text-center justify-center content-center items-center flex flex-col'>
|
||||
<div>
|
||||
<h1 className='inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top'>404</h1>
|
||||
<div className='inline-block text-left h-32 leading-10 align-middle'>
|
||||
<h2 className='m-0 p-0'>页面丢失了,3秒后返回首页</h2></div>
|
||||
</div>
|
||||
</div>
|
||||
return <Layout404 />
|
||||
}
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
import '@/styles/notion.css'
|
||||
import 'rc-dropdown/assets/index.css'
|
||||
import 'katex/dist/katex.min.css'
|
||||
import '@/styles/globals.css'
|
||||
import 'prismjs'
|
||||
import 'prismjs/themes/prism-okaidia.css'
|
||||
import 'prismjs/components/prism-markup'
|
||||
import 'prismjs/components/prism-python'
|
||||
import 'prismjs/components/prism-bash'
|
||||
import BLOG from 'blog.config'
|
||||
import 'animate.css'
|
||||
import 'font-awesome/css/font-awesome.min.css'
|
||||
import BLOG from '@/blog.config'
|
||||
import '@/styles/globals.css'
|
||||
// custom
|
||||
// core styles shared by all of react-notion-x (required)
|
||||
import 'react-notion-x/src/styles.css'
|
||||
import '@/styles/notion.css' // 重写部分样式
|
||||
|
||||
// used for collection views (optional)
|
||||
import 'rc-dropdown/assets/index.css'
|
||||
// used for code syntax highlighting (optional)
|
||||
import 'prismjs/themes/prism-okaidia.css'
|
||||
// used for rendering equations (optional)
|
||||
import 'katex/dist/katex.min.css'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { LocaleProvider } from '@/lib/locale'
|
||||
import { ThemeProvider } from '@/lib/theme'
|
||||
import { GlobalContextProvider } from '@/lib/global'
|
||||
import { config } from '@fortawesome/fontawesome-svg-core'
|
||||
import '@fortawesome/fontawesome-svg-core/styles.css'
|
||||
config.autoAddCss = false
|
||||
|
||||
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
|
||||
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
|
||||
const Busuanzi = dynamic(() => import('@/components/Busuanzi'), { ssr: false })
|
||||
const GoogleAdsense = dynamic(() => import('@/components/GoogleAdsense'), { ssr: false })
|
||||
|
||||
function MyApp ({ Component, pageProps }) {
|
||||
const MyApp = ({ Component, pageProps }) => {
|
||||
return (
|
||||
<LocaleProvider>
|
||||
<ThemeProvider>
|
||||
<GlobalContextProvider>
|
||||
{BLOG.isProd && BLOG?.analytics?.provider === 'ackee' && (
|
||||
<Ackee
|
||||
ackeeServerUrl={BLOG.analytics.ackeeConfig.dataAckeeServer}
|
||||
@@ -28,9 +33,10 @@ function MyApp ({ Component, pageProps }) {
|
||||
/>
|
||||
)}
|
||||
{BLOG.isProd && BLOG?.analytics?.provider === 'ga' && <Gtag />}
|
||||
{BLOG.analytics.busuanzi && <Busuanzi/>}
|
||||
{BLOG.googleAdsenseId && <GoogleAdsense/>}
|
||||
<Component {...pageProps} />
|
||||
</ThemeProvider>
|
||||
</LocaleProvider>
|
||||
</GlobalContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// eslint-disable-next-line @next/next/no-document-import-in-page
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
import BLOG from '@/blog.config'
|
||||
import ThirdPartyScript from '@/components/ThirdPartyScript'
|
||||
import CommonScript from '@/components/CommonScript'
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps (ctx) {
|
||||
@@ -14,10 +15,10 @@ class MyDocument extends Document {
|
||||
<Head>
|
||||
<link rel='icon' href='/favicon.ico' />
|
||||
<link rel='icon' href='/favicon.svg' type='image/svg+xml' />
|
||||
<ThirdPartyScript />
|
||||
<CommonScript />
|
||||
</Head>
|
||||
|
||||
<body>
|
||||
<body className={`${BLOG.font} bg-day dark:bg-night duration-200`}>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
||||
60
pages/about.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import { getPostBlocks } from '@/lib/notion'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import Custom404 from '@/pages/404'
|
||||
import React from 'react'
|
||||
import { LayoutSlug } from '@/themes'
|
||||
|
||||
/**
|
||||
* 关于页面,默认取notion中slug为about的文章
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const About = (props) => {
|
||||
if (!props.post) {
|
||||
return <Custom404 />
|
||||
}
|
||||
return <LayoutSlug {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'about-props'
|
||||
const {
|
||||
allPosts,
|
||||
categories,
|
||||
tags,
|
||||
postCount,
|
||||
latestPosts
|
||||
} = await getGlobalNotionData({
|
||||
from,
|
||||
includePage: true
|
||||
})
|
||||
const post = allPosts.find(p => p.slug === 'about')
|
||||
|
||||
if (!post) {
|
||||
return {
|
||||
props: {},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
post.blockMap = await getPostBlocks(post.id, 'slug')
|
||||
|
||||
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,
|
||||
tags,
|
||||
prev,
|
||||
next,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
export default About
|
||||
24
pages/archive/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import React from 'react'
|
||||
import { LayoutArchive } from '@/themes'
|
||||
|
||||
export async function getStaticProps () {
|
||||
const { allPosts, categories, tags, postCount } =
|
||||
await getGlobalNotionData({ from: 'archive-index' })
|
||||
|
||||
return {
|
||||
props: {
|
||||
posts: allPosts,
|
||||
tags,
|
||||
categories,
|
||||
postCount
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
const ArchiveIndex = (props) => {
|
||||
return <LayoutArchive {...props}/>
|
||||
}
|
||||
|
||||
export default ArchiveIndex
|
||||
@@ -1,60 +1,112 @@
|
||||
import ArticleLayout from '@/layouts/ArticleLayout'
|
||||
import { getAllPosts, getAllTags, getPostBlocks } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import { createHash } from 'crypto'
|
||||
import { getPageTableOfContents } from 'notion-utils'
|
||||
import { getPostBlocks } from '@/lib/notion'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { LayoutSlug } from '@/themes'
|
||||
import Custom404 from '@/pages/404'
|
||||
|
||||
const BlogPost = ({ post, blockMap, emailHash, tags, prev, next }) => {
|
||||
if (!post) {
|
||||
return <Custom404/>
|
||||
/**
|
||||
* 根据notion的slug访问页面
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const Slug = (props) => {
|
||||
if (!props.post) {
|
||||
return <Custom404 />
|
||||
}
|
||||
return (
|
||||
<ArticleLayout
|
||||
blockMap={blockMap}
|
||||
frontMatter={post}
|
||||
emailHash={emailHash}
|
||||
tags={tags}
|
||||
prev={prev}
|
||||
next={next}
|
||||
></ArticleLayout>
|
||||
)
|
||||
return <LayoutSlug {...props}/>
|
||||
}
|
||||
|
||||
export async function getStaticPaths () {
|
||||
let posts = await getAllPosts()
|
||||
posts = posts.filter(post => post.status[0] === 'Published')
|
||||
if (!BLOG.isProd) {
|
||||
return {
|
||||
paths: [],
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
|
||||
const from = '[slug-paths'
|
||||
const { allPosts } = await getGlobalNotionData({ from, includePage: false })
|
||||
return {
|
||||
paths: posts.map(row => `${BLOG.path}/article/${row.slug}`),
|
||||
paths: allPosts.map(row => ({ params: { slug: row.slug } })),
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticProps ({ params: { slug } }) {
|
||||
let posts = await getAllPosts()
|
||||
posts = posts.filter(post => post.status[0] === 'Published')
|
||||
const post = posts.find(t => t.slug === slug)
|
||||
const from = `slug-props-${slug}`
|
||||
const { allPosts, categories, tags, postCount, latestPosts } =
|
||||
await getGlobalNotionData({ from, includePage: false })
|
||||
|
||||
const post = allPosts.find(p => p.slug === slug)
|
||||
|
||||
if (!post) {
|
||||
return {
|
||||
props: { },
|
||||
revalidate: 1
|
||||
}
|
||||
return { props: {}, revalidate: 1 }
|
||||
}
|
||||
|
||||
const blockMap = await getPostBlocks(post.id)
|
||||
const emailHash = createHash('md5').update(BLOG.email).digest('hex')
|
||||
post.toc = getPageTableOfContents(post, blockMap)
|
||||
posts = posts.filter(post => post.type[0] === 'Post')
|
||||
const tags = await getAllTags(posts)
|
||||
// 获取推荐文章
|
||||
const index = posts.indexOf(post)
|
||||
const prev = posts.slice(index - 1, index)[0] ?? posts.slice(-1)[0]
|
||||
const next = posts.slice(index + 1, index + 2)[0] ?? posts[0]
|
||||
post.blockMap = await getPostBlocks(post.id, 'slug')
|
||||
|
||||
// 上一篇、下一篇文章关联
|
||||
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]
|
||||
|
||||
const recommendPosts = getRecommendPost(post, allPosts)
|
||||
|
||||
return {
|
||||
props: { post, blockMap, emailHash, tags, prev, next },
|
||||
props: {
|
||||
post,
|
||||
tags,
|
||||
prev,
|
||||
next,
|
||||
recommendPosts,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
export default BlogPost
|
||||
/**
|
||||
*
|
||||
* @param post
|
||||
* @param {*} allPosts
|
||||
* @param {*} count
|
||||
* @returns
|
||||
*/
|
||||
function getRecommendPost (post, allPosts, count = 5) {
|
||||
let filteredPosts = Object.create(allPosts)
|
||||
// 筛选同标签
|
||||
if (post.tags && post.tags.length) {
|
||||
const currentTag = post.tags[0]
|
||||
filteredPosts = filteredPosts.filter(
|
||||
p => p && p.tags && p.tags.includes(currentTag) && p.slug !== post.slug
|
||||
)
|
||||
}
|
||||
shuffleSort(filteredPosts)
|
||||
|
||||
// 筛选前5个
|
||||
if (filteredPosts.length > count) {
|
||||
filteredPosts = filteredPosts.slice(0, count)
|
||||
}
|
||||
return filteredPosts
|
||||
}
|
||||
|
||||
/**
|
||||
* 洗牌乱序:从数组的最后位置开始,从前面随机一个位置,对两个数进行交换,直到循环完毕
|
||||
* @param arr
|
||||
* @returns {*}
|
||||
*/
|
||||
function shuffleSort (arr) {
|
||||
let i = arr.length - 1
|
||||
while (i > 0) {
|
||||
const rIndex = Math.floor(Math.random() * i)
|
||||
const temp = arr[rIndex]
|
||||
arr[rIndex] = arr[i]
|
||||
arr[i] = temp
|
||||
i--
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
export default Slug
|
||||
|
||||
42
pages/category/[category].js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import React from 'react'
|
||||
import { LayoutCategory } from '@/themes'
|
||||
|
||||
export default function Category (props) {
|
||||
return <LayoutCategory {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps ({ params }) {
|
||||
const from = 'category-props'
|
||||
const category = params.category
|
||||
const {
|
||||
allPosts,
|
||||
categories,
|
||||
tags,
|
||||
postCount,
|
||||
latestPosts
|
||||
} = await getGlobalNotionData({ from })
|
||||
const filteredPosts = allPosts.filter(
|
||||
post => post && post.category && post.category.includes(category)
|
||||
)
|
||||
return {
|
||||
props: {
|
||||
tags,
|
||||
posts: filteredPosts,
|
||||
category,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths () {
|
||||
const from = 'category-paths'
|
||||
const { categories } = await getGlobalNotionData({ from })
|
||||
return {
|
||||
paths: Object.keys(categories).map(category => ({ params: { category } })),
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
23
pages/category/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import React from 'react'
|
||||
import { LayoutCategoryIndex } from '@/themes'
|
||||
|
||||
export default function Category (props) {
|
||||
return <LayoutCategoryIndex {...props}/>
|
||||
}
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'category-index-props'
|
||||
const { allPosts, categories, tags, postCount, latestPosts } = await getGlobalNotionData({ from })
|
||||
|
||||
return {
|
||||
props: {
|
||||
tags,
|
||||
allPosts,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { generateRss } from '@/lib/rss'
|
||||
|
||||
export async function getServerSideProps ({ res }) {
|
||||
res.setHeader('Content-Type', 'text/xml')
|
||||
let posts = await getAllPosts()
|
||||
let posts = await getAllPosts({ from: 'feed' })
|
||||
posts = posts
|
||||
.filter(post => post.status[0] === 'Published' && post.type[0] === 'Post')
|
||||
.slice(0, 10)
|
||||
|
||||
@@ -1,27 +1,51 @@
|
||||
import { getAllPosts, getAllTags } from '@/lib/notion'
|
||||
import DefaultLayout from '@/layouts/DefaultLayout'
|
||||
import BLOG from '@/blog.config'
|
||||
import { getPostBlocks } from '@/lib/notion'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { LayoutIndex } from '@/themes'
|
||||
|
||||
const Index = (props) => {
|
||||
return <LayoutIndex {...props}/>
|
||||
}
|
||||
|
||||
export async function getStaticProps () {
|
||||
let posts = await getAllPosts()
|
||||
posts = posts.filter(
|
||||
post => post.status[0] === 'Published' && post.type[0] === 'Post'
|
||||
)
|
||||
const tags = await getAllTags(posts)
|
||||
const from = 'index'
|
||||
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', BLOG.home.previewLines)
|
||||
if (blockMap) {
|
||||
post.blockMap = blockMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
page: 1, // current page is 1
|
||||
posts,
|
||||
tags
|
||||
posts: postsToShow,
|
||||
latestPosts,
|
||||
postCount,
|
||||
tags,
|
||||
categories,
|
||||
meta
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
const blog = ({ posts, page, tags }) => {
|
||||
return (
|
||||
<DefaultLayout tags={tags} posts={posts} page={page} />
|
||||
)
|
||||
}
|
||||
|
||||
export default blog
|
||||
export default Index
|
||||
|
||||
@@ -1,63 +1,67 @@
|
||||
import { getAllPosts, getAllTags } from '@/lib/notion'
|
||||
import BLOG from '@/blog.config'
|
||||
import DefaultLayout from '@/layouts/DefaultLayout'
|
||||
import { useRouter } from 'next/router'
|
||||
import { getPostBlocks } from '@/lib/notion'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { LayoutPage } from '@/themes'
|
||||
import Custom404 from '@/pages/404'
|
||||
|
||||
const Page = ({ posts, tags, page }) => {
|
||||
let filteredBlogPosts = posts
|
||||
if (posts) {
|
||||
const router = useRouter()
|
||||
if (router.query && router.query.s) {
|
||||
filteredBlogPosts = posts.filter(post => {
|
||||
const tagContent = post.tags ? post.tags.join(' ') : ''
|
||||
const searchContent = post.title + post.summary + tagContent
|
||||
return searchContent.toLowerCase().includes(router.query.s.toLowerCase())
|
||||
})
|
||||
const Page = (props) => {
|
||||
if (!props?.meta) {
|
||||
return <Custom404 />
|
||||
}
|
||||
return <LayoutPage {...props} />
|
||||
}
|
||||
|
||||
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', BLOG.home.previewLines)
|
||||
if (blockMap) {
|
||||
post.blockMap = blockMap
|
||||
}
|
||||
}
|
||||
|
||||
return <DefaultLayout tags={tags} posts={filteredBlogPosts} page={page} />
|
||||
}
|
||||
|
||||
export async function getStaticProps (context) {
|
||||
const { page } = context.params // Get Current Page No.
|
||||
let posts = await getAllPosts()
|
||||
posts = posts.filter(
|
||||
post => post.status[0] === 'Published' && post.type[0] === 'Post'
|
||||
)
|
||||
const tags = await getAllTags(posts)
|
||||
return {
|
||||
props: {
|
||||
page,
|
||||
posts: postsToShow,
|
||||
postCount,
|
||||
latestPosts,
|
||||
tags,
|
||||
posts,
|
||||
page
|
||||
categories,
|
||||
meta
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths () {
|
||||
if (BLOG.isProd) {
|
||||
// 预渲染
|
||||
let posts = await getAllPosts()
|
||||
posts = posts.filter(
|
||||
post => post.status[0] === 'Published' && post.type[0] === 'Post'
|
||||
)
|
||||
const totalPosts = posts.length
|
||||
const totalPages = Math.ceil(totalPosts / 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
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
paths: [],
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Page
|
||||
|
||||
28
pages/search.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { LayoutSearch } from '@/themes'
|
||||
|
||||
export async function getStaticProps () {
|
||||
const {
|
||||
allPosts,
|
||||
categories,
|
||||
tags,
|
||||
postCount,
|
||||
latestPosts
|
||||
} = await getGlobalNotionData({ from: 'search-props' })
|
||||
return {
|
||||
props: {
|
||||
posts: allPosts,
|
||||
tags,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
const Search = (props) => {
|
||||
return <LayoutSearch {...props} />
|
||||
}
|
||||
|
||||
export default Search
|
||||
@@ -1,43 +1,65 @@
|
||||
import { getAllPosts, getAllTags } from '@/lib/notion'
|
||||
import DefaultLayout from '@/layouts/DefaultLayout'
|
||||
import BLOG from '@/blog.config'
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { LayoutTag } from '@/themes'
|
||||
|
||||
export default function Tag ({ tags, posts, currentTag }) {
|
||||
return <DefaultLayout tags={tags} posts={posts} currentTag={currentTag} />
|
||||
const Tag = (props) => {
|
||||
return <LayoutTag {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps ({ params }) {
|
||||
const currentTag = params.tag
|
||||
let posts = await getAllPosts()
|
||||
posts = posts.filter(
|
||||
post => post.status[0] === 'Published' && post.type[0] === 'Post'
|
||||
)
|
||||
const tags = await getAllTags(posts)
|
||||
const filteredPosts = posts.filter(
|
||||
post => post && post.tags && post.tags.includes(currentTag)
|
||||
const tag = params.tag
|
||||
const from = 'tag-props'
|
||||
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,
|
||||
posts: filteredPosts,
|
||||
currentTag
|
||||
tag,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的标签
|
||||
* @returns
|
||||
* @param tags
|
||||
*/
|
||||
function getTagNames (tags) {
|
||||
const tagNames = []
|
||||
tags.forEach(tag => {
|
||||
tagNames.push(tag.name)
|
||||
})
|
||||
return tagNames
|
||||
}
|
||||
|
||||
export async function getStaticPaths () {
|
||||
if (BLOG.isProd) {
|
||||
// 预渲染
|
||||
const tags = await getAllTags()
|
||||
return {
|
||||
paths: Object.keys(tags).map(tag => ({ params: { tag } })),
|
||||
fallback: true
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
paths: [],
|
||||
fallback: true
|
||||
}
|
||||
const from = 'tag-static-path'
|
||||
const { tags } = await getGlobalNotionData({
|
||||
from,
|
||||
tagsCount: 0
|
||||
})
|
||||
const tagNames = getTagNames(tags)
|
||||
|
||||
return {
|
||||
paths: Object.keys(tagNames).map(index => ({ params: { tag: tagNames[index] } })),
|
||||
fallback: true
|
||||
}
|
||||
}
|
||||
|
||||
export default Tag
|
||||
|
||||
33
pages/tag/index.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import React from 'react'
|
||||
import { LayoutTagIndex } from '@/themes'
|
||||
|
||||
const TagIndex = (props) => {
|
||||
return <LayoutTagIndex {...props} />
|
||||
}
|
||||
|
||||
export async function getStaticProps () {
|
||||
const from = 'tag-index-props'
|
||||
const {
|
||||
categories,
|
||||
tags,
|
||||
postCount,
|
||||
latestPosts
|
||||
} = await getGlobalNotionData({
|
||||
from,
|
||||
includePage: true,
|
||||
tagsCount: 0
|
||||
})
|
||||
|
||||
return {
|
||||
props: {
|
||||
tags,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
},
|
||||
revalidate: 1
|
||||
}
|
||||
}
|
||||
|
||||
export default TagIndex
|
||||
BIN
public/avatar.jpg
Executable file
|
After Width: | Height: | Size: 78 KiB |
@@ -1,898 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="256px" height="256px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve"> <image id="image0" width="256" height="256" x="0" y="0"
|
||||
href="data:image/png;base64,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
|
||||
YII=" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 68 KiB |
BIN
public/bg_image.jpg
Executable file
|
After Width: | Height: | Size: 261 KiB |
BIN
public/fonts/IBMPlexSansVar-Italic.woff2
Normal file
BIN
public/fonts/IBMPlexSansVar-Roman.woff2
Normal file
BIN
public/fonts/SourceSerif-Italic.var.woff2
Normal file
BIN
public/fonts/SourceSerif.var.woff2
Normal file
|
Before Width: | Height: | Size: 35 KiB |
BIN
public/reward_code_alipay.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
public/reward_code_wechat.png
Executable file
|
After Width: | Height: | Size: 120 KiB |
@@ -98,7 +98,7 @@ nav {
|
||||
}
|
||||
|
||||
.shadow-card{
|
||||
box-shadow: 0 2px 1px -1px rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 1px 3px 0 rgba(0,0,0,.12);
|
||||
box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px, rgba(0, 0, 0, 0.07) 0px 2px 4px, rgba(0, 0, 0, 0.07) 0px 4px 8px, rgba(0, 0, 0, 0.07) 0px 8px 16px, rgba(0, 0, 0, 0.07) 0px 16px 32px, rgba(0, 0, 0, 0.07) 0px 32px 64px;
|
||||
}
|
||||
|
||||
.gt-meta{
|
||||
@@ -109,12 +109,83 @@ nav {
|
||||
@apply right-auto left-0 hidden lg:block z-10 !important
|
||||
}
|
||||
|
||||
@media (max-width: 1300px){
|
||||
.sidebar{
|
||||
-ms-overflow-style: none;
|
||||
overflow: -moz-scrollbars-none;
|
||||
@apply border-r border-gray-200 h-screen overflow-y-scroll fixed left-0
|
||||
}
|
||||
.sidebar::-webkit-scrollbar { width: 0 !important }
|
||||
/* 隐藏滚动条 */
|
||||
.scroll-hidden{
|
||||
-ms-overflow-style: none;
|
||||
overflow: -moz-scrollbars-none;
|
||||
}
|
||||
|
||||
.scroll-hidden::-webkit-scrollbar { width: 0 !important }
|
||||
|
||||
.notion-collection{
|
||||
@apply max-w-0
|
||||
}
|
||||
|
||||
/* 渐变透明度 */
|
||||
.line-x-opacity{
|
||||
background: -webkit-linear-gradient(left, rgba(255,255,255,0), rgb(255, 255, 255)); /* Safari 5.1 - 6.0 */
|
||||
background: -o-linear-gradient(right, rgba(255, 255, 255,0), rgb(255, 255, 255)); /* Opera 11.1 - 12.0 */
|
||||
background: -moz-linear-gradient(right, rgba(255, 255, 255,0), rgba(255, 255, 255)); /* Firefox 3.6 - 15 */
|
||||
background: linear-gradient(to right, rgba(255, 255, 255,0), rgba(255, 255, 255)); /* 标准的语法(必须放在最后) */
|
||||
}
|
||||
|
||||
.-line-x-opacity{
|
||||
background: -webkit-linear-gradient(right, rgba(255,255,255,0), rgb(255, 255, 255)); /* Safari 5.1 - 6.0 */
|
||||
background: -o-linear-gradient(left, rgba(255, 255, 255,0), rgb(255, 255, 255)); /* Opera 11.1 - 12.0 */
|
||||
background: -moz-linear-gradient(left, rgba(255, 255, 255,0), rgba(255, 255, 255)); /* Firefox 3.6 - 15 */
|
||||
background: linear-gradient(to left, rgba(255, 255, 255,0), rgba(255, 255, 255)); /* 标准的语法(必须放在最后) */
|
||||
}
|
||||
.dark .line-x-opacity{
|
||||
background: -webkit-linear-gradient(left, rgba(31, 41, 55,0), rgb(31, 41, 55)); /* Safari 5.1 - 6.0 */
|
||||
background: -o-linear-gradient(right, rgba(31, 41, 55,0), rgb(31, 41, 55)); /* Opera 11.1 - 12.0 */
|
||||
background: -moz-linear-gradient(right, rgba(31, 41, 55,0), rgba(31, 41, 55)); /* Firefox 3.6 - 15 */
|
||||
background: linear-gradient(to right, rgba(31, 41, 55,0), rgba(31, 41, 55)); /* 标准的语法(必须放在最后) */
|
||||
}
|
||||
|
||||
.dark .-line-x-opacity{
|
||||
background: -webkit-linear-gradient(right, rgba(31, 41, 55,0), rgb(31, 41, 55)); /* Safari 5.1 - 6.0 */
|
||||
background: -o-linear-gradient(left, rgba(31, 41, 55,0), rgb(31, 41, 55)); /* Opera 11.1 - 12.0 */
|
||||
background: -moz-linear-gradient(left, rgba(31, 41, 55,0), rgb(31, 41, 55)); /* Firefox 3.6 - 15 */
|
||||
background: linear-gradient(to left, rgba(31, 41, 55,0), rgb(31, 41, 55)); /* 标准的语法(必须放在最后) */
|
||||
}
|
||||
|
||||
.glassmorphism{
|
||||
background: hsla(0, 0%, 100%, .75);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.dark .glassmorphism{
|
||||
background: rgba(31, 41, 55, .75);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.medium-zoom-overlay{
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.article-cover{
|
||||
-webkit-text-size-adjust: 100%;
|
||||
font-size: 14px;
|
||||
font-family: -apple-system,SF UI Text,Arial,PingFang SC,Hiragino Sans GB,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
color: rgba(0,0,0,.75);
|
||||
font-variant-ligatures: common-ligatures;
|
||||
line-height: 1.625;
|
||||
tab-size: 4;
|
||||
outline: 0;
|
||||
font-weight: normal;
|
||||
-webkit-box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
z-index: 998;
|
||||
padding-top: 160px;
|
||||
bottom: -1px;
|
||||
margin-top: -200px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,#fff 70%);
|
||||
padding-bottom: 34px;
|
||||
}
|
||||
@@ -37,6 +37,8 @@
|
||||
--notion-orange_background: rgb(250, 235, 221);
|
||||
--notion-brown_background: rgb(233, 229, 227);
|
||||
--notion-gray_background: rgb(235, 236, 237);
|
||||
--notion-green_background: rgb(219, 237, 219);
|
||||
--notion-default_background: rgba(227, 226, 224);
|
||||
|
||||
--notion-red_background_co: rgba(251, 228, 228, 0.3);
|
||||
--notion-pink_background_co: rgba(244, 223, 235, 0.3);
|
||||
@@ -47,6 +49,8 @@
|
||||
--notion-orange_background_co: rgba(250, 235, 221, 0.3);
|
||||
--notion-brown_background_co: rgba(233, 229, 227, 0.3);
|
||||
--notion-gray_background_co: rgba(235, 236, 237, 0.3);
|
||||
--notion-green_background_co: rgba(219, 237, 219, 0.3);
|
||||
--notion-default_background_co: rgba(227, 226, 224, 0.3);
|
||||
|
||||
--notion-item-blue: rgba(0, 120, 223, 0.2);
|
||||
--notion-item-orange: rgba(245, 93, 0, 0.2);
|
||||
@@ -64,10 +68,11 @@
|
||||
}
|
||||
|
||||
.notion {
|
||||
font-size: 16px;
|
||||
font-size: 17px;
|
||||
line-height: 1.5;
|
||||
color: var(--fg-color);
|
||||
caret-color: var(--fg-color);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.notion > * {
|
||||
@@ -186,10 +191,14 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -10;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.medium-zoom-overlay {
|
||||
z-index: 300;
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.medium-zoom-image {
|
||||
@@ -279,6 +288,13 @@
|
||||
.notion-gray_background {
|
||||
background-color: var(--notion-gray_background);
|
||||
}
|
||||
.notion-green_background {
|
||||
background-color: var(--notion-green_background);
|
||||
}
|
||||
.notion-default_background {
|
||||
background-color: var(--notion-default_background);
|
||||
}
|
||||
|
||||
.notion-red_background_co {
|
||||
background-color: var(--notion-red_background_co);
|
||||
}
|
||||
@@ -306,6 +322,12 @@
|
||||
.notion-gray_background_co {
|
||||
background-color: var(--notion-gray_background_co);
|
||||
}
|
||||
.notion-green_background_co {
|
||||
background-color: var(--notion-green_background_co);
|
||||
}
|
||||
.notion-default_background_co {
|
||||
background-color: var(--notion-default_background_co);
|
||||
}
|
||||
|
||||
.notion-item-blue {
|
||||
background-color: var(--notion-item-blue);
|
||||
@@ -365,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 {
|
||||
@@ -603,6 +626,7 @@ svg.notion-page-icon {
|
||||
margin: 0;
|
||||
margin-block-start: 0.6em;
|
||||
margin-block-end: 0.6em;
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
.notion-list-disc {
|
||||
@@ -627,7 +651,7 @@ svg.notion-page-icon {
|
||||
}
|
||||
|
||||
.notion-list li {
|
||||
padding: 6px 0;
|
||||
padding: 1px 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -689,12 +713,12 @@ svg.notion-page-icon {
|
||||
width: 100%;
|
||||
padding: 30px 16px 30px 20px;
|
||||
margin: 4px 0;
|
||||
border-radius: 3px;
|
||||
border-radius: 10px;
|
||||
tab-size: 2;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
background: var(--bg-color-1);
|
||||
background: #272822;
|
||||
font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier,
|
||||
monospace;
|
||||
}
|
||||
@@ -1059,7 +1083,7 @@ svg.notion-page-icon {
|
||||
|
||||
padding: 6px 2px;
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
line-height: 1.2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1095,7 +1119,7 @@ svg.notion-page-icon {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.notion-to-do-checked .notion-to-do-item {
|
||||
.notion-to-do-checked {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.375;
|
||||
}
|
||||
@@ -1107,6 +1131,11 @@ svg.notion-page-icon {
|
||||
|
||||
.notion-to-do-item .notion-property-checkbox {
|
||||
margin-right: 8px;
|
||||
/* @apply w-4 h-4 border-2 */
|
||||
}
|
||||
|
||||
.notion-property-checkbox-checked {
|
||||
/* @apply bg-white */
|
||||
}
|
||||
|
||||
.notion-google-drive {
|
||||
@@ -1556,7 +1585,7 @@ svg.notion-page-icon {
|
||||
/* NOTION CSS OVERRIDE */
|
||||
|
||||
.notion {
|
||||
@apply text-gray-900 dark:text-gray-300;
|
||||
@apply dark:text-gray-300;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.notion,
|
||||
@@ -1613,9 +1642,6 @@ pre[class*='language-'] {
|
||||
.notion-bookmark:hover {
|
||||
@apply border-blue-400;
|
||||
}
|
||||
.notion-viewport {
|
||||
z-index: -10;
|
||||
}
|
||||
.notion-asset-caption {
|
||||
@apply text-center;
|
||||
}
|
||||
|
||||
@@ -7,11 +7,22 @@ const fontSansCJK = !CJK()
|
||||
const fontSerifCJK = !CJK()
|
||||
? []
|
||||
: [`"Noto Serif CJK ${CJK()}"`, `"Noto Serif ${CJK()}"`]
|
||||
|
||||
module.exports = {
|
||||
purge: ['./pages/**/*.js', './components/**/*.js', './layouts/**/*.js'],
|
||||
purge: ['./pages/**/*.js', './components/**/*.js', './layouts/**/*.js', './themes/**/*.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: {
|
||||
@@ -20,18 +31,6 @@ module.exports = {
|
||||
night: {
|
||||
DEFAULT: BLOG.darkBackground || '#111827'
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['"IBM Plex Sans"', ...fontSansCJK, ...fontFamily.sans],
|
||||
serif: ['"Source Serif"', ...fontSerifCJK, ...fontFamily.serif],
|
||||
noEmoji: [
|
||||
'"IBM Plex Sans"',
|
||||
'ui-sans-serif',
|
||||
'system-ui',
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'sans-serif'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
35
themes/NEXT/Layout404.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import BLOG from '@/blog.config'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export const Layout404 = () => {
|
||||
const router = useRouter()
|
||||
useEffect(() => {
|
||||
// 延时3秒如果加载失败就返回首页
|
||||
setTimeout(() => {
|
||||
if (window) {
|
||||
const article = document.getElementById('container')
|
||||
if (!article) {
|
||||
router.push('/').then(() => {
|
||||
console.log('找不到页面', router.asPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
}, 30000000)
|
||||
})
|
||||
|
||||
return <LayoutBase meta={{ title: `${BLOG.title} | 页面找不到啦` }}>
|
||||
<div
|
||||
className='md:-mt-20 text-black w-full h-screen text-center justify-center content-center items-center flex flex-col'>
|
||||
<div className='dark:text-gray-200'>
|
||||
<h2 className='inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top'><FontAwesomeIcon icon={faSpinner} spin={true} className='mr-2'/>404</h2>
|
||||
<div className='inline-block text-left h-32 leading-10 items-center'>
|
||||
<h2 className='m-0 p-0'>页面无法加载,即将返回首页</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutBase>
|
||||
}
|
||||
65
themes/NEXT/LayoutArchive.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import BLOG from '@/blog.config'
|
||||
import React, { useEffect } from 'react'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import BlogPostArchive from './components/BlogPostArchive'
|
||||
import Live2D from './components/Live2D'
|
||||
|
||||
export const LayoutArchive = ({ posts, tags, categories, postCount }) => {
|
||||
const { locale } = useGlobal()
|
||||
// 深拷贝
|
||||
const postsSortByDate = Object.create(posts)
|
||||
|
||||
// 时间排序
|
||||
postsSortByDate.sort((a, b) => {
|
||||
const dateA = new Date(a?.date.start_date || a.createdTime)
|
||||
const dateB = new Date(b?.date.start_date || b.createdTime)
|
||||
return dateB - dateA
|
||||
})
|
||||
|
||||
const meta = {
|
||||
title: `${locale.NAV.ARCHIVE} | ${BLOG.title}`,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
|
||||
const archivePosts = {}
|
||||
|
||||
postsSortByDate.forEach(post => {
|
||||
const date = post.date.start_date.slice(0, 7)
|
||||
if (archivePosts[date]) {
|
||||
archivePosts[date].push(post)
|
||||
} else {
|
||||
archivePosts[date] = [post]
|
||||
}
|
||||
})
|
||||
|
||||
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 (
|
||||
<LayoutBase 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 />
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
112
themes/NEXT/LayoutBase.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import CommonHead from '@/components/CommonHead'
|
||||
import FloatDarkModeButton from './components/FloatDarkModeButton'
|
||||
import Footer from './components/Footer'
|
||||
import JumpToBottomButton from './components/JumpToBottomButton'
|
||||
import JumpToTopButton from './components/JumpToTopButton'
|
||||
import LoadingCover from './components/LoadingCover'
|
||||
import SideAreaLeft from './components/SideAreaLeft'
|
||||
import SideAreaRight from './components/SideAreaRight'
|
||||
import TopNav from './components/TopNav'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import smoothscroll from 'smoothscroll-polyfill'
|
||||
|
||||
/**
|
||||
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
|
||||
* @param children
|
||||
* @param layout
|
||||
* @param tags
|
||||
* @param meta
|
||||
* @param post
|
||||
* @param currentSearch
|
||||
* @param currentCategory
|
||||
* @param currentTag
|
||||
* @param categories
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const LayoutBase = ({
|
||||
children,
|
||||
headerSlot,
|
||||
tags,
|
||||
meta,
|
||||
post,
|
||||
postCount,
|
||||
sideBarSlot,
|
||||
floatSlot,
|
||||
rightAreaSlot,
|
||||
currentSearch,
|
||||
currentCategory,
|
||||
currentTag,
|
||||
categories
|
||||
}) => {
|
||||
const { onLoading } = useGlobal()
|
||||
const targetRef = useRef(null)
|
||||
|
||||
const [show, switchShow] = useState(false)
|
||||
const [percent, changePercent] = useState(0) // 页面阅读百分比
|
||||
const scrollListener = () => {
|
||||
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
|
||||
|
||||
if (shouldShow !== show) {
|
||||
switchShow(shouldShow)
|
||||
}
|
||||
changePercent(per)
|
||||
}
|
||||
useEffect(() => {
|
||||
smoothscroll.polyfill()
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
return (<>
|
||||
|
||||
<CommonHead meta={meta} />
|
||||
|
||||
<TopNav tags={tags} postCount={postCount} post={post} slot={sideBarSlot} currentSearch={currentSearch} categories={categories} currentCategory={currentCategory} />
|
||||
|
||||
<>{headerSlot}</>
|
||||
|
||||
<div className='h-0.5 w-full bg-gray-700 dark:bg-gray-600 hidden lg:block'/>
|
||||
|
||||
<main id='wrapper' className='flex justify-center flex-1 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' : ''} lg:max-w-3xl xl:max-w-4xl flex-grow md:mt-0 min-h-screen w-full`} ref={targetRef}>
|
||||
{onLoading
|
||||
? <LoadingCover/>
|
||||
: <>
|
||||
{children}
|
||||
</>
|
||||
}
|
||||
</section>
|
||||
<SideAreaRight targetRef={targetRef} post={post} slot={rightAreaSlot} postCount={postCount} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
|
||||
</main>
|
||||
|
||||
{/* 右下角悬浮 */}
|
||||
<div className='right-8 bottom-10 lg:right-2 lg:bottom-2 fixed justify-end z-20 font-sans'>
|
||||
<div className={(show ? 'animate__animated ' : 'hidden') + ' animate__fadeInUp rounded-md glassmorphism justify-center duration-500 animate__faster flex space-x-2 items-center cursor-pointer '}>
|
||||
<JumpToTopButton percent={percent}/>
|
||||
<JumpToBottomButton />
|
||||
<FloatDarkModeButton/>
|
||||
{floatSlot}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer title={meta.title}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
LayoutBase.propTypes = {
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
export default LayoutBase
|
||||
23
themes/NEXT/LayoutCategory.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import BLOG from '@/blog.config'
|
||||
import LayoutBase from '@/themes/NEXT/LayoutBase'
|
||||
import StickyBar from './components/StickyBar'
|
||||
import CategoryList from './components/CategoryList'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
|
||||
export const LayoutCategory = ({ tags, posts, category, categories, latestPosts, postCount }) => {
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${category} | ${locale.COMMON.CATEGORY} | ${BLOG.title}`,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
return <LayoutBase 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={posts} tags={tags} currentCategory={category}/>
|
||||
</div>
|
||||
</LayoutBase>
|
||||
}
|
||||
38
themes/NEXT/LayoutCategoryIndex.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import BLOG from '@/blog.config'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faFolder, faThList } from '@fortawesome/free-solid-svg-icons'
|
||||
import Link from 'next/link'
|
||||
|
||||
export const LayoutCategoryIndex = ({
|
||||
tags,
|
||||
allPosts,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
}) => {
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${locale.COMMON.CATEGORY} | ${BLOG.title}`,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
return <LayoutBase 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'>
|
||||
{Object.keys(categories).map(category => {
|
||||
return <Link key={category} href={`/category/${category}`} passHref>
|
||||
<div
|
||||
className={'hover:text-black dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600 px-5 cursor-pointer py-2 hover:bg-gray-100'}>
|
||||
<FontAwesomeIcon icon={faFolder} className='mr-4' />{category}({categories[category]})
|
||||
</div>
|
||||
</Link>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</LayoutBase>
|
||||
}
|
||||
29
themes/NEXT/LayoutIndex.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import LayoutBase from './LayoutBase'
|
||||
import Header from './components/Header'
|
||||
import LatestPostsGroup from './components/LatestPostsGroup'
|
||||
import Card from './components/Card'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import { CONFIG_NEXT } from './index'
|
||||
|
||||
export const LayoutIndex = ({ posts, tags, meta, categories, postCount, latestPosts }) => {
|
||||
return <LayoutBase
|
||||
headerSlot={CONFIG_NEXT.HOME_BANNER && <Header />}
|
||||
meta={meta}
|
||||
tags={tags}
|
||||
sideBarSlot={<LatestPostsGroup posts={latestPosts} />}
|
||||
rightAreaSlot={
|
||||
CONFIG_NEXT.RIGHT_LATEST_POSTS && <Card><LatestPostsGroup posts={latestPosts} /></Card>
|
||||
}
|
||||
postCount={postCount}
|
||||
categories={categories}
|
||||
>
|
||||
{CONFIG_NEXT.POSTS_LIST_TYPE !== 'page'
|
||||
? (
|
||||
<BlogPostListScroll posts={posts} tags={tags} showSummary={true} />
|
||||
)
|
||||
: (
|
||||
<BlogPostListPage posts={posts} tags={tags} postCount={postCount} />
|
||||
)}
|
||||
</LayoutBase>
|
||||
}
|
||||
19
themes/NEXT/LayoutPage.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import LayoutBase from './LayoutBase'
|
||||
import LatestPostsGroup from './components/LatestPostsGroup'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import { CONFIG_NEXT } from './index'
|
||||
|
||||
export const LayoutPage = ({ page, posts, tags, meta, categories, postCount, latestPosts }) => {
|
||||
return (
|
||||
<LayoutBase
|
||||
meta={meta}
|
||||
tags={tags}
|
||||
sideBarSlot={<LatestPostsGroup posts={latestPosts} />}
|
||||
rightAreaSlot={CONFIG_NEXT.RIGHT_LATEST_POSTS && <LatestPostsGroup posts={latestPosts} />}
|
||||
postCount={postCount}
|
||||
categories={categories}
|
||||
>
|
||||
<BlogPostListPage page={page} posts={posts} postCount={postCount} />
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
56
themes/NEXT/LayoutSearch.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import LayoutBase from './LayoutBase'
|
||||
import StickyBar from './components/StickyBar'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import BLOG from '@/blog.config'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export const LayoutSearch = ({ posts, tags, categories, postCount }) => {
|
||||
let filteredPosts
|
||||
const searchKey = getSearchKey()
|
||||
if (searchKey) {
|
||||
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: `${searchKey || ''} | ${locale.NAV.SEARCH} | ${BLOG.title} `,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
return (
|
||||
<LayoutBase
|
||||
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>
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
|
||||
function getSearchKey () {
|
||||
const router = useRouter()
|
||||
if (router.query && router.query.s) {
|
||||
return router.query.s
|
||||
}
|
||||
return null
|
||||
}
|
||||
76
themes/NEXT/LayoutSlug.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { getPageTableOfContents } from 'notion-utils'
|
||||
import TocDrawerButton from './components/TocDrawerButton'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import Card from './components/Card'
|
||||
import LatestPostsGroup from './components/LatestPostsGroup'
|
||||
import ArticleDetail from './components/ArticleDetail'
|
||||
import TocDrawer from './components/TocDrawer'
|
||||
import Live2D from './components/Live2D'
|
||||
import { useRef } from 'react'
|
||||
import 'prismjs'
|
||||
import 'prismjs/components/prism-bash'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/components/prism-markup'
|
||||
import 'prismjs/components/prism-python'
|
||||
import 'prismjs/components/prism-typescript'
|
||||
import { CONFIG_NEXT } from './index'
|
||||
|
||||
export const LayoutSlug = ({
|
||||
post,
|
||||
tags,
|
||||
prev,
|
||||
next,
|
||||
recommendPosts,
|
||||
categories,
|
||||
postCount,
|
||||
latestPosts
|
||||
}) => {
|
||||
const meta = {
|
||||
title: `${post.title} | ${BLOG.title}`,
|
||||
description: post.summary,
|
||||
type: 'article',
|
||||
tags: post.tags
|
||||
}
|
||||
|
||||
const drawerRight = useRef(null)
|
||||
const targetRef = typeof window !== 'undefined' ? document.getElementById('container') : null
|
||||
post.content = Object.keys(post?.blockMap?.block)
|
||||
post.toc = getPageTableOfContents(post, post.blockMap)
|
||||
const floatSlot = post?.toc?.length > 1
|
||||
? <div className='block lg:hidden'><TocDrawerButton onClick={() => {
|
||||
drawerRight?.current?.handleSwitchVisible()
|
||||
}} /></div>
|
||||
: null
|
||||
|
||||
return (
|
||||
<LayoutBase
|
||||
meta={meta}
|
||||
tags={tags}
|
||||
post={post}
|
||||
postCount={postCount}
|
||||
latestPosts={latestPosts}
|
||||
categories={categories}
|
||||
floatSlot={floatSlot}
|
||||
rightAreaSlot={
|
||||
CONFIG_NEXT.RIGHT_LATEST_POSTS && <Card><LatestPostsGroup posts={latestPosts} /></Card>
|
||||
}
|
||||
>
|
||||
<ArticleDetail
|
||||
post={post}
|
||||
recommendPosts={recommendPosts}
|
||||
prev={prev}
|
||||
next={next}
|
||||
/>
|
||||
|
||||
{/* 悬浮目录按钮 */}
|
||||
<div className='block lg:hidden'>
|
||||
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} />
|
||||
</div>
|
||||
|
||||
{/* 宠物 */}
|
||||
<Live2D />
|
||||
|
||||
</LayoutBase>
|
||||
)
|
||||
}
|
||||
30
themes/NEXT/LayoutTag.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import BLOG from '@/blog.config'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import StickyBar from './components/StickyBar'
|
||||
import TagList from './components/TagList'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
|
||||
export const LayoutTag = ({ tags, posts, tag, categories, postCount, latestPosts }) => {
|
||||
const { locale } = useGlobal()
|
||||
|
||||
const meta = {
|
||||
title: `${tag} | ${locale.COMMON.TAGS} | ${BLOG.title}`,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
|
||||
// 将当前选中的标签置顶🔝
|
||||
if (!tags) tags = []
|
||||
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 <LayoutBase 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={posts} tags={tags} currentTag={tag}/>
|
||||
</div>
|
||||
</LayoutBase>
|
||||
}
|
||||
25
themes/NEXT/LayoutTagIndex.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import BLOG from '@/blog.config'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faTags } from '@fortawesome/free-solid-svg-icons'
|
||||
import TagItem from './components/TagItem'
|
||||
|
||||
export const LayoutTagIndex = ({ tags, categories, postCount, latestPosts }) => {
|
||||
const { locale } = useGlobal()
|
||||
const meta = {
|
||||
title: `${locale.COMMON.TAGS} | ${BLOG.title}`,
|
||||
description: BLOG.description,
|
||||
type: 'website'
|
||||
}
|
||||
return <LayoutBase meta={meta} categories={categories} 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'>
|
||||
{ tags.map(tag => {
|
||||
return <div key={tag.name} className='p-2'><TagItem key={tag.name} tag={tag} /></div>
|
||||
}) }
|
||||
</div>
|
||||
</div>
|
||||
</LayoutBase>
|
||||
}
|
||||
30
themes/NEXT/components/ArticleCopyright.js
Normal file
@@ -0,0 +1,30 @@
|
||||
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">
|
||||
<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">
|
||||
<a className="hover:underline">{author}</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<strong className='mr-2'>{locale.COMMON.URL}:</strong>
|
||||
<a className="hover:underline" href={url}>
|
||||
{url}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<strong className='mr-2'>{locale.COMMON.COPYRIGHT}:</strong>
|
||||
{locale.COMMON.COPYRIGHT_NOTICE}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
}
|
||||
201
themes/NEXT/components/ArticleDetail.js
Normal file
@@ -0,0 +1,201 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogAround from '@/themes/NEXT/components/BlogAround'
|
||||
import Comment from '@/components/Comment'
|
||||
import RecommendPosts from '@/themes/NEXT/components/RecommendPosts'
|
||||
import ShareBar from '@/themes/NEXT/components/ShareBar'
|
||||
import TagItem from '@/themes/NEXT/components/TagItem'
|
||||
import formatDate from '@/lib/formatDate'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { faEye, faFolderOpen } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import mediumZoom from 'medium-zoom'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import 'prismjs'
|
||||
import 'prismjs/components/prism-bash'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/components/prism-markup'
|
||||
import 'prismjs/components/prism-python'
|
||||
import 'prismjs/components/prism-typescript'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
|
||||
import ArticleCopyright from './ArticleCopyright'
|
||||
import WordCount from './WordCount'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
|
||||
const url = BLOG.link + useRouter().asPath
|
||||
const { locale } = useGlobal()
|
||||
const date = formatDate(post?.date?.start_date || post.createdTime, locale.LOCALE)
|
||||
|
||||
const zoom = typeof window !== 'undefined' && mediumZoom({
|
||||
container: '.notion-viewport',
|
||||
background: 'rgba(0, 0, 0, 0.2)',
|
||||
margin: getMediumZoomMargin()
|
||||
})
|
||||
const zoomRef = useRef(zoom ? zoom.clone() : null)
|
||||
|
||||
useEffect(() => {
|
||||
// 将所有container下的所有图片添加medium-zoom
|
||||
const container = document.getElementById('container')
|
||||
const imgList = container.getElementsByTagName('img')
|
||||
if (imgList && zoomRef.current) {
|
||||
for (let i = 0; i < imgList.length; i++) {
|
||||
(zoomRef.current).attach(imgList[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (<div id="container" className="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="subpixel-antialiased py-10 px-5 lg:pt-24 md:px-24 dark:border-gray-700 bg-white dark:bg-gray-800"
|
||||
>
|
||||
|
||||
<header className='animate__slideInDown animate__animated'>
|
||||
{post.type && !post.type.includes('Page') && post?.page_cover && (
|
||||
<div className="w-full relative md:flex-shrink-0 overflow-hidden">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<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">
|
||||
{post.title}
|
||||
</div>
|
||||
|
||||
<section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8">
|
||||
<div>
|
||||
<Link href={`/category/${post.category}`} passHref>
|
||||
<a className="cursor-pointer text-md mr-2 hover:text-black dark:hover:text-white border-b dark:border-gray-500 border-dashed">
|
||||
<FontAwesomeIcon icon={faFolderOpen} className="mr-1" />
|
||||
{post.category}
|
||||
</a>
|
||||
</Link>
|
||||
<span className='mr-2'>|</span>
|
||||
|
||||
{post.type[0] !== 'Page' && (<>
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
>
|
||||
<a className="pl-1 mr-2 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 border-b dark:border-gray-500 border-dashed">
|
||||
{date}
|
||||
</a>
|
||||
</Link>
|
||||
<span className='mr-2'>|</span>
|
||||
</>)}
|
||||
|
||||
<div className="hidden busuanzi_container_page_pv font-light mr-2">
|
||||
<FontAwesomeIcon icon={faEye} className='mr-1'/>
|
||||
|
||||
<span className="mr-2 busuanzi_value_page_pv"
|
||||
></span>
|
||||
<span className='mr-2'>|</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-nowrap whitespace-nowrap items-center font-light text-md'>
|
||||
<WordCount/>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
{/* <hr className="mt-2" /> */}
|
||||
|
||||
</header>
|
||||
|
||||
{/* Notion文章主体 */}
|
||||
<section id='notion-article' className='px-1'>
|
||||
{post.blockMap && (
|
||||
<NotionRenderer
|
||||
recordMap={post.blockMap}
|
||||
mapPageUrl={mapPageUrl}
|
||||
components={{
|
||||
equation: Equation,
|
||||
code: Code,
|
||||
collectionRow: CollectionRow,
|
||||
collection: Collection
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
|
||||
{/* 文章内嵌广告 */}
|
||||
<ins className="adsbygoogle"
|
||||
style={{ display: 'block', textAlign: 'center' }}
|
||||
data-adtest="on"
|
||||
data-ad-layout="in-article"
|
||||
data-ad-format="fluid"
|
||||
data-ad-client="ca-pub-2708419466378217"
|
||||
data-ad-slot="3806269138"></ins>
|
||||
</section>
|
||||
|
||||
{/* 版权声明 */}
|
||||
<ArticleCopyright author={BLOG.author} url={url} />
|
||||
|
||||
{/* 推荐文章 */}
|
||||
<RecommendPosts currentPost={post} recommendPosts={recommendPosts} />
|
||||
|
||||
{/* 标签列表 */}
|
||||
<section className="md:flex md:justify-between">
|
||||
{post.tagItems && (
|
||||
<div className="flex flex-nowrap leading-8 p-1 py-4 overflow-x-auto">
|
||||
<div className="hidden md:block dark:text-gray-300 whitespace-nowrap">
|
||||
{locale.COMMON.TAGS}:
|
||||
</div>
|
||||
{post.tagItems.map(tag => (
|
||||
<TagItem key={tag.name} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<ShareBar post={post} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<BlogAround prev={prev} next={next} />
|
||||
|
||||
</article>
|
||||
|
||||
{/* 评论互动 */}
|
||||
<div className="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>)
|
||||
}
|
||||
|
||||
const mapPageUrl = id => {
|
||||
return 'https://www.notion.so/' + id.replace(/-/g, '')
|
||||
}
|
||||
|
||||
function getMediumZoomMargin () {
|
||||
const width = window.innerWidth
|
||||
|
||||
if (width < 500) {
|
||||
return 8
|
||||
} else if (width < 800) {
|
||||
return 20
|
||||
} else if (width < 1280) {
|
||||
return 30
|
||||
} else if (width < 1600) {
|
||||
return 40
|
||||
} else if (width < 1920) {
|
||||
return 48
|
||||
} else {
|
||||
return 72
|
||||
}
|
||||
}
|
||||
26
themes/NEXT/components/BlogAround.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import Link from 'next/link'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faAngleDoubleLeft, faAngleDoubleRight } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
/**
|
||||
* 上一篇,下一篇文章
|
||||
* @param {prev,next} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function BlogAround ({ prev, next }) {
|
||||
if (!prev || !next) {
|
||||
return <></>
|
||||
}
|
||||
return <section className='text-gray-800 border-t dark:text-gray-300 flex flex-wrap lg:flex-nowrap lg:space-x-10 justify-between py-2'>
|
||||
<Link href={`/article/${prev.slug}`} passHref>
|
||||
<a className='text-sm py-3 text-gray-400 hover:underline cursor-pointer'>
|
||||
<FontAwesomeIcon icon={faAngleDoubleLeft} className='mr-1' />{prev.title}
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/article/${next.slug}`} passHref>
|
||||
<a className='text-sm flex py-3 text-gray-400 hover:underline cursor-pointer'>{next.title}
|
||||
<FontAwesomeIcon icon={faAngleDoubleRight} className='ml-1 my-1' />
|
||||
</a>
|
||||
</Link>
|
||||
</section>
|
||||
}
|
||||