mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-13 23:16:47 +00:00
Merge branch 'main' into pr/emengweb/1007
This commit is contained in:
24
README.md
24
README.md
@@ -15,6 +15,11 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
中文文档 | [README in English](./README_EN.md)
|
||||
|
||||
<hr/>
|
||||
|
||||
一个使用 NextJS + Notion API 实现的,部署在 Vercel 上的静态博客系统。为Notion和所有创作者设计。
|
||||
|
||||
|
||||
@@ -35,7 +40,10 @@
|
||||
- [个性配置手册 - 如何配置功能插件](https://tangly1024.com/article/notion-next-guide)
|
||||
|
||||
- [二次开发指引 - 如何进行本地开发](https://tangly1024.com/article/how-to-develop-with-notion-next)
|
||||
|
||||
|
||||
- [更新操作指南 - 获取最新升级补丁](https://tangly1024.com/article/how-to-update-notionnext)
|
||||
|
||||
- [历史版本汇总 - 查询版本功能特性](https://tangly1024.com/article/notion-next-changelogs)
|
||||
|
||||
## 致谢
|
||||
感谢Craig Hart发起的Nobelium项目
|
||||
@@ -156,9 +164,23 @@
|
||||
<a href="https://github.com/hongzzz" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/25585061" width="64px;" alt="Hongzzz"/><br/><sub><b>Hongzzz</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=hongzzz" title="hongzzz" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/RedhairHambagu" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/129669334" width="64px;" alt="RedhairHambagu"/><br/><sub><b>RedhairHambagu</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=RedhairHambagu" title="RedhairHambagu" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/Allengl" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/58612763" width="64px;" alt="Allen"/><br/><sub><b>Allen</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=Allengl" title="Allengl" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/zdf1230" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/5999425" width="64px;" alt="zdf1230"/><br/><sub><b>zdf</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=zdf1230" title="zdf1230" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/emengweb" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/31469739" width="64px;" alt="emengweb"/><br/><sub><b>emengweb</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=emengweb" title="emengweb" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
187
README_EN.md
Normal file
187
README_EN.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# NotionNext
|
||||
|
||||
<p>
|
||||
<a aria-label="GitHub commit activity" href="https://github.com/tangly1024/NotionNext/commits/main" title="GitHub commit activity">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/tangly1024/NotionNext?style=for-the-badge"/>
|
||||
</a>
|
||||
<a aria-label="GitHub contributors" href="https://github.com/tangly1024/NotionNext/graphs/contributors" title="GitHub contributors">
|
||||
<img src="https://img.shields.io/github/contributors/tangly1024/NotionNext?color=orange&style=for-the-badge"/>
|
||||
</a>
|
||||
<a aria-label="Build status" href="#" title="Build status">
|
||||
<img src="https://img.shields.io/github/deployments/tangly1024/NotionNext/Production?logo=Vercel&style=for-the-badge"/>
|
||||
</a>
|
||||
<a aria-label="Powered by Vercel" href="https://vercel.com?utm_source=Craigary&utm_campaign=oss" title="Powered by Vercel">
|
||||
<img src="https://www.datocms-assets.com/31049/1618983297-powered-by-vercel.svg" height="28"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
[中文文档](./README.md) | README in English
|
||||
|
||||
<hr/>
|
||||
|
||||
A static blog system built with NextJS and Notion API, deployed on Vercel. Designed for Notion and all creators.
|
||||
|
||||
|
||||
## Preview
|
||||
|
||||
Live Demo:[https://preview.tangly1024.com/](https://preview.tangly1024.com/) ,Project supports switching between multiple themes. Can't find a theme you like? How about [contributing](/CONTRIBUTING.md) one?~
|
||||
|
||||
| Next | Medium | Hexo | Fukasawa |
|
||||
|--|--|--|--|
|
||||
| <img src='./docs/theme-next.png' width='300'/> [NEXT](https://preview.tangly1024.com/?theme=next) | <img src='./docs/theme-medium.png' width='300'/> [MEDIUM](https://preview.tangly1024.com/?theme=medium) | <img src='./docs/theme-hexo.png' width='300'/> [HEXO](https://preview.tangly1024.com/?theme=hexo) | <img src='./docs/theme-fukasawa.png' width='300'/> [FUKASAWA](https://preview.tangly1024.com/?theme=fukasawa) |
|
||||
|
||||
## Get Start!
|
||||
|
||||
It only takes a few minutes to set up your personal site:
|
||||
|
||||
- [Quick Deployment Tutorial - Multiple Options Available](https://tangly1024.com/article/notion-next)
|
||||
|
||||
- [Customization Guide - How to Configure Feature Plugins](https://tangly1024.com/article/notion-next-guide)
|
||||
|
||||
- [Development Guide - How to Conduct Local Development](https://tangly1024.com/article/how-to-develop-with-notion-next)
|
||||
|
||||
- [Update Guide - How to Get the Latest Upgrade Patch](https://tangly1024.com/article/how-to-update-notionnext)
|
||||
|
||||
- [Version History - Check Feature Highlights for Each Version](https://tangly1024.com/article/notion-next-changelogs)
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Special thanks to Craig Hart for initiating the Nobelium project.
|
||||
|
||||
<table><tr align="left">
|
||||
<td align="center"><a href="https://github.com/craigary" title="Craig Hart"><img src="https://avatars.githubusercontent.com/u/10571717" width="64px;"alt="Craig Hart"/></a><br/><a href="https://github.com/craigary" title="Craig Hart">Craig Hart</a></td>
|
||||
</tr></table>
|
||||
|
||||
## Contributors
|
||||
|
||||
<table>
|
||||
<tr align="left">
|
||||
<td align="center">
|
||||
<a href="https://github.com/tangly1024" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/15920488" width="64px;"alt="tangly1024"/><br/><sub><b>tangly1024</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=tangly1024" title="Owner" >🎫 🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/uWayLu" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/21689326" width="64px;" alt="uWayLu"/><br/><sub><b>uWayLu</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=uWayLu" title="uWayLu" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/txs" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/554329" width="64px;" alt="txs"/><br/><sub><b>txs</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=txs" title="txs" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/yuzhanglong" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/56540811" width="64px;" alt="yuzhanglong"/><br/><sub><b>yuzhanglong</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=yuzhanglong" title="yuzhanglong" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/Hscpro" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/13926044" width="64px;" alt="Hscpro"/><br/><sub><b>Hscpro</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=Hscpro" title="Hscpro" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/JensonMiao" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/46488783" width="64px;" alt="JensonMiao"/><br/><sub><b>JensonMiao</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=JensonMiao" title="JensonMiao" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/haixin1225" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/28828438" width="64px;" alt="haixin1225"/><br/><sub><b>haixin1225</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=haixin1225" title="haixin1225" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/mouyase" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/7844572" width="64px;" alt="mouyase"/><br/><sub><b>mouyase</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=mouyase" title="mouyase" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/qfdk" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/2404478" width="64px;" alt="qfdk"/><br/><sub><b>qfdk</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=qfdk" title="qfdk" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/ifyz" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/118271360" width="64px;" alt="ifyz"/><br/><sub><b>ifyz</b></sub></a><br><a href="https://github.com/tangly1024/NotionNext/commits?author=ifyz" title="ifyz" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/liqun98" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/71168966" width="64px;" alt="Liqun Zhao"/><br/><sub><b>Liqun Zhao</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=liqun98" title="liqun98" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/Ylarod" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/30978685" width="64px;" alt="Ylarod"/><br/><sub><b>Ylarod</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=Ylarod" title="Ylarod" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/lifeafter619" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/65111206" width="64px;" alt="Etherrreal."/><br/><sub><b>Etherrreal.</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=lifeafter619" title="Etherrreal." >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/ykxkykx" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/17985993" width="64px;" alt="Joshua Astray"/><br/><sub><b>Joshua Astray</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=ykxkykx" title="ykxkykx" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/Vixcity" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/57704177" width="64px;" alt="Vixcity"/><br/><sub><b>Vixcity</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=Vixcity" title="Vixcity" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/ipatpat" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/39089551" width="64px;" alt="ipatpat"/><br/><sub><b>ipatpat</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=ipatpat" title="ipatpat" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/xloong" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/8479955" width="64px;" alt="xloong"/><br/><sub><b>xloong</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=xloong" title="xloong" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/expoli" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/31023767" width="64px;" alt="expoli"/><br/><sub><b>expoli</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=expoli" title="expoli" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/SuperHuangXu" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/14816052" width="64px;" alt="SuperHuangXu"/><br/><sub><b>bUBBLE</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=SuperHuangXu" title="SuperHuangXu" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/Pylogmon" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/59004461" width="64px;" alt="Pylogmon"/><br/><sub><b>派了个萌 </b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=Pylogmon" title="Pylogmon" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/SkysCrystal" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/49473463" width="64px;" alt="SkysCrystal"/><br/><sub><b>Simon Shi</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=SkysCrystal" title="SkysCrystal" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/siygle" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/173408" width="64px;" alt="S.Y. Lee"/><br/><sub><b>S.Y. Lee</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=siygle" title="siygle" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/fighting-bug" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/56441589" width="64px;" alt="fighting-buf"/><br/><sub><b>fighting-buf</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=fighting-bug" title="fighting-buf" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/cliouo" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/71540889" width="64px;" alt="cliouo"/><br/><sub><b>cliouo</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=cliouo" title="cliouo" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/sudeakq" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/102792219" width="64px;" alt="Sude Akgün"/><br/><sub><b>Sude Akgün</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=sudeakq" title="sudeakq" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/fgprodigal" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/769910" width="64px;" alt="Ray"/><br/><sub><b>Ray</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=fgprodigal" title="Ray" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/hongzzz" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/25585061" width="64px;" alt="Hongzzz"/><br/><sub><b>Hongzzz</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=hongzzz" title="hongzzz" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
<td align="center">
|
||||
<a href="https://github.com/RedhairHambagu" style="display:inline-block;width:80px"><img src="https://avatars.githubusercontent.com/u/129669334" width="64px;" alt="RedhairHambagu"/><br/><sub><b>RedhairHambagu</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=RedhairHambagu" title="RedhairHambagu" >🔧 🐛</a>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## Technologies Used
|
||||
|
||||
- **Technical Framework**: [Next.js](https://nextjs.org)
|
||||
- **Styles**: [Tailwind CSS](https://www.tailwindcss.cn/)
|
||||
- **Rendering Tool**: [React-notion-x](https://github.com/NotionX/react-notion-x)
|
||||
- **COMMENT**: [Twikoo](https://github.com/imaegoo/twikoo), [Giscus](https://giscus.app/zh-CN), [Gitalk](https://gitalk.github.io), [Cusdis](https://cusdis.com), [Utterances](https://utteranc.es)
|
||||
- **ICON**: [Fontawesome](https://fontawesome.com/v6/icons/)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
The MIT License.
|
||||
@@ -1,5 +1,4 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { Analytics } from '@vercel/analytics/react'
|
||||
|
||||
/**
|
||||
* 第三方代码 统计脚本
|
||||
@@ -8,8 +7,6 @@ import { Analytics } from '@vercel/analytics/react'
|
||||
*/
|
||||
const CommonScript = () => {
|
||||
return (<>
|
||||
{BLOG.ANALYTICS_VERCEL && <Analytics />}
|
||||
|
||||
{BLOG.COMMENT_DAO_VOICE_ID && (<>
|
||||
{/* DaoVoice 反馈 */}
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
|
||||
@@ -58,7 +58,7 @@ export function DebugPanel() {
|
||||
<div>
|
||||
<div
|
||||
style={{ writingMode: 'vertical-lr' }}
|
||||
className={`bg-black text-xs text-white shadow-2xl p-1.5 rounded-l-xl cursor-pointer ${show ? 'right-96' : 'right-0'} fixed bottom-56 duration-200 z-50`}
|
||||
className={`bg-black text-xs text-white shadow-2xl p-1.5 rounded-l-xl cursor-pointer ${show ? 'right-96' : 'right-0'} fixed bottom-72 duration-200 z-50`}
|
||||
onClick={toggleShow}
|
||||
>
|
||||
{show
|
||||
|
||||
@@ -23,8 +23,10 @@ const PrismMac = () => {
|
||||
loadExternalResource('/css/prism-mac-style.css', 'css')
|
||||
}
|
||||
loadExternalResource(BLOG.PRISM_THEME_PATH, 'css')
|
||||
loadExternalResource(BLOG.PRISM_JS_AUTO_LOADER, 'js').then((e) => {
|
||||
Prism.plugins.autoloader.languages_path = BLOG.PRISM_JS_PATH
|
||||
loadExternalResource(BLOG.PRISM_JS_AUTO_LOADER, 'js').then((url) => {
|
||||
if (window?.Prism?.plugins?.autoloader) {
|
||||
window.Prism.plugins.autoloader.languages_path = BLOG.PRISM_JS_PATH
|
||||
}
|
||||
renderPrismMac()
|
||||
})
|
||||
}
|
||||
|
||||
29
components/ShareBar.js
Normal file
29
components/ShareBar.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
import ShareButtons from './ShareButtons'
|
||||
|
||||
const ShareBar = ({ post }) => {
|
||||
const router = useRouter()
|
||||
|
||||
if (!JSON.parse(BLOG.POST_SHARE_BAR_ENABLE) || !post || post?.type !== 'Post') {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const shareUrl = BLOG.LINK + router.asPath
|
||||
|
||||
return <div className='m-1 overflow-x-auto'>
|
||||
<div className='flex w-full md:justify-end'>
|
||||
<ShareButtons shareUrl={shareUrl} title={post.title} image={post.pageCover} body={
|
||||
post.title +
|
||||
' | ' +
|
||||
BLOG.TITLE +
|
||||
' ' +
|
||||
shareUrl +
|
||||
' ' +
|
||||
post.summary
|
||||
} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
export default ShareBar
|
||||
372
components/ShareButtons.js
Normal file
372
components/ShareButtons.js
Normal file
@@ -0,0 +1,372 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import QRCode from 'qrcode.react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import {
|
||||
FacebookShareButton,
|
||||
FacebookIcon,
|
||||
FacebookMessengerShareButton,
|
||||
FacebookMessengerIcon,
|
||||
RedditShareButton,
|
||||
RedditIcon,
|
||||
LineShareButton,
|
||||
LineIcon,
|
||||
EmailShareButton,
|
||||
EmailIcon,
|
||||
TwitterShareButton,
|
||||
TwitterIcon,
|
||||
TelegramShareButton,
|
||||
TelegramIcon,
|
||||
WhatsappShareButton,
|
||||
WhatsappIcon,
|
||||
LinkedinShareButton,
|
||||
LinkedinIcon,
|
||||
PinterestShareButton,
|
||||
PinterestIcon,
|
||||
VKIcon,
|
||||
VKShareButton,
|
||||
OKShareButton,
|
||||
OKIcon,
|
||||
TumblrShareButton,
|
||||
TumblrIcon,
|
||||
LivejournalIcon,
|
||||
LivejournalShareButton,
|
||||
MailruShareButton,
|
||||
MailruIcon,
|
||||
ViberIcon,
|
||||
ViberShareButton,
|
||||
WorkplaceShareButton,
|
||||
WorkplaceIcon,
|
||||
WeiboShareButton,
|
||||
WeiboIcon,
|
||||
PocketShareButton,
|
||||
PocketIcon,
|
||||
InstapaperShareButton,
|
||||
InstapaperIcon,
|
||||
HatenaShareButton,
|
||||
HatenaIcon
|
||||
} from 'react-share'
|
||||
|
||||
/**
|
||||
* @author https://github.com/txs
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const ShareButtons = ({ shareUrl, title, body, image }) => {
|
||||
const services = BLOG.POSTS_SHARE_SERVICES.split(',')
|
||||
const titleWithSiteInfo = title + ' | ' + BLOG.TITLE
|
||||
const { locale } = useGlobal()
|
||||
const [qrCodeShow, setQrCodeShow] = useState(false)
|
||||
|
||||
const copyUrl = () => {
|
||||
copy(shareUrl)
|
||||
alert(locale.COMMON.URL_COPIED)
|
||||
}
|
||||
|
||||
const openPopover = () => {
|
||||
setQrCodeShow(true)
|
||||
}
|
||||
const closePopover = () => {
|
||||
setQrCodeShow(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{services.map(singleService => {
|
||||
if (singleService === 'facebook') {
|
||||
return (
|
||||
<FacebookShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
className="mx-1"
|
||||
>
|
||||
<FacebookIcon size={32} round />
|
||||
</FacebookShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'messenger') {
|
||||
return (
|
||||
<FacebookMessengerShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
appId={BLOG.FACEBOOK_APP_ID}
|
||||
className="mx-1"
|
||||
>
|
||||
<FacebookMessengerIcon size={32} round />
|
||||
</FacebookMessengerShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'line') {
|
||||
return (
|
||||
<LineShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
className="mx-1"
|
||||
>
|
||||
<LineIcon size={32} round />
|
||||
</LineShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'reddit') {
|
||||
return (
|
||||
<RedditShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
windowWidth={660}
|
||||
windowHeight={460}
|
||||
className="mx-1"
|
||||
>
|
||||
<RedditIcon size={32} round />
|
||||
</RedditShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'email') {
|
||||
return (
|
||||
<EmailShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
subject={titleWithSiteInfo}
|
||||
body={body}
|
||||
className="mx-1"
|
||||
>
|
||||
<EmailIcon size={32} round />
|
||||
</EmailShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'twitter') {
|
||||
return (
|
||||
<TwitterShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<TwitterIcon size={32} round />
|
||||
</TwitterShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'telegram') {
|
||||
return (
|
||||
<TelegramShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<TelegramIcon size={32} round />
|
||||
</TelegramShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'whatsapp') {
|
||||
return (
|
||||
<WhatsappShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
separator=":: "
|
||||
className="mx-1"
|
||||
>
|
||||
<WhatsappIcon size={32} round />
|
||||
</WhatsappShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'linkedin') {
|
||||
return (
|
||||
<LinkedinShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
className="mx-1"
|
||||
>
|
||||
<LinkedinIcon size={32} round />
|
||||
</LinkedinShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'pinterest') {
|
||||
return (
|
||||
<PinterestShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
media={image}
|
||||
className="mx-1"
|
||||
>
|
||||
<PinterestIcon size={32} round />
|
||||
</PinterestShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'vkshare') {
|
||||
return (
|
||||
<VKShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
image={image}
|
||||
className="mx-1"
|
||||
>
|
||||
<VKIcon size={32} round />
|
||||
</VKShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'okshare') {
|
||||
return (
|
||||
<OKShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
image={image}
|
||||
className="mx-1"
|
||||
>
|
||||
<OKIcon size={32} round />
|
||||
</OKShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'tumblr') {
|
||||
return (
|
||||
<TumblrShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<TumblrIcon size={32} round />
|
||||
</TumblrShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'livejournal') {
|
||||
return (
|
||||
<LivejournalShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
description={shareUrl}
|
||||
className="mx-1"
|
||||
>
|
||||
<LivejournalIcon size={32} round />
|
||||
</LivejournalShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'mailru') {
|
||||
return (
|
||||
<MailruShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<MailruIcon size={32} round />
|
||||
</MailruShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'viber') {
|
||||
return (
|
||||
<ViberShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<ViberIcon size={32} round />
|
||||
</ViberShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'workplace') {
|
||||
return (
|
||||
<WorkplaceShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
quote={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<WorkplaceIcon size={32} round />
|
||||
</WorkplaceShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'weibo') {
|
||||
return (
|
||||
<WeiboShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
image={image}
|
||||
className="mx-1"
|
||||
>
|
||||
<WeiboIcon size={32} round />
|
||||
</WeiboShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'pocket') {
|
||||
return (
|
||||
<PocketShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<PocketIcon size={32} round />
|
||||
</PocketShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'instapaper') {
|
||||
return (
|
||||
<InstapaperShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
className="mx-1"
|
||||
>
|
||||
<InstapaperIcon size={32} round />
|
||||
</InstapaperShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'hatena') {
|
||||
return (
|
||||
<HatenaShareButton
|
||||
key={singleService}
|
||||
url={shareUrl}
|
||||
title={titleWithSiteInfo}
|
||||
windowWidth={660}
|
||||
windowHeight={460}
|
||||
className="mx-1"
|
||||
>
|
||||
<HatenaIcon size={32} round />
|
||||
</HatenaShareButton>
|
||||
)
|
||||
}
|
||||
if (singleService === 'qq') {
|
||||
return <button key={singleService} className='cursor-pointer bg-blue-600 text-white rounded-full mx-1'>
|
||||
<div target='_blank' rel='noreferrer' href={`http://connect.qq.com/widget/shareqq/index.html?url=${shareUrl}&sharesource=qzone&title=${title}&desc=${body}`} >
|
||||
<i className='fab fa-qq w-8' />
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
if (singleService === 'wechat') {
|
||||
return <button onMouseEnter={openPopover} onMouseLeave={closePopover} aria-label={singleService} key={singleService} className='cursor-pointer bg-green-600 text-white rounded-full mx-1'>
|
||||
<div id='wechat-button'>
|
||||
<i className='fab fa-weixin w-8' />
|
||||
</div>
|
||||
<div className='absolute'>
|
||||
<div id='pop' className={(qrCodeShow ? 'opacity-100 ' : ' invisible opacity-0') + ' z-40 absolute bottom-10 -left-10 bg-white shadow-xl transition-all duration-200 text-center'}>
|
||||
<div className='p-2 mt-1'>
|
||||
<QRCode value={shareUrl} fgColor='#000000' />
|
||||
</div>
|
||||
<span className='text-black font-semibold p-1 rounded-t-lg text-sm mx-auto mb-1'>
|
||||
{locale.COMMON.SCAN_QR_CODE}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
if (singleService === 'link') {
|
||||
return <button aria-label={singleService} key={singleService} className='cursor-pointer bg-yellow-500 text-white rounded-full mx-1'>
|
||||
<div alt={locale.COMMON.URL_COPIED} onClick={copyUrl} >
|
||||
<i className='fas fa-link w-8' />
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
return <></>
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShareButtons
|
||||
@@ -1,6 +1,7 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import React from 'react'
|
||||
import twikoo from 'twikoo'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
import { useEffect } from 'react'
|
||||
// import twikoo from 'twikoo'
|
||||
|
||||
/**
|
||||
* Giscus评论 @see https://giscus.app/zh-CN
|
||||
@@ -10,17 +11,48 @@ import twikoo from 'twikoo'
|
||||
*/
|
||||
|
||||
const Twikoo = ({ isDarkMode }) => {
|
||||
React.useEffect(() => {
|
||||
twikoo({
|
||||
envId: BLOG.COMMENT_TWIKOO_ENV_ID, // 腾讯云环境填 envId;Vercel 环境填地址(https://xxx.vercel.app)
|
||||
el: '#twikoo', // 容器元素
|
||||
lang: BLOG.LANG // 用于手动设定评论区语言,支持的语言列表 https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js
|
||||
// region: 'ap-guangzhou', // 环境地域,默认为 ap-shanghai,腾讯云环境填 ap-shanghai 或 ap-guangzhou;Vercel 环境不填
|
||||
// path: location.pathname, // 用于区分不同文章的自定义 js 路径,如果您的文章路径不是 location.pathname,需传此参数
|
||||
})
|
||||
})
|
||||
const loadTwikoo = async () => {
|
||||
try {
|
||||
const url = await loadExternalResource(BLOG.COMMENT_TWIKOO_CDN_URL, 'js')
|
||||
console.log('twikoo 加载成功', url)
|
||||
const twikoo = window.twikoo
|
||||
twikoo.init({
|
||||
envId: BLOG.COMMENT_TWIKOO_ENV_ID, // 腾讯云环境填 envId;Vercel 环境填地址(https://xxx.vercel.app)
|
||||
el: '#twikoo', // 容器元素
|
||||
lang: BLOG.LANG // 用于手动设定评论区语言,支持的语言列表 https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js
|
||||
// region: 'ap-guangzhou', // 环境地域,默认为 ap-shanghai,腾讯云环境填 ap-shanghai 或 ap-guangzhou;Vercel 环境不填
|
||||
// path: location.pathname, // 用于区分不同文章的自定义 js 路径,如果您的文章路径不是 location.pathname,需传此参数
|
||||
})
|
||||
|
||||
twikoo.getCommentsCount({
|
||||
envId: BLOG.COMMENT_TWIKOO_ENV_ID, // 环境 ID
|
||||
// region: 'ap-guangzhou', // 环境地域,默认为 ap-shanghai,如果您的环境地域不是上海,需传此参数
|
||||
urls: [ // 不包含协议、域名、参数的文章路径列表,必传参数
|
||||
'/article/notion-next',
|
||||
'/article/notion-next-guide'
|
||||
],
|
||||
includeReply: false // 评论数是否包括回复,默认:false
|
||||
}).then(function (res) {
|
||||
console.log(res)
|
||||
// 返回示例: [
|
||||
// { url: '/2020/10/post-1.html', count: 10 },
|
||||
// { url: '/2020/11/post-2.html', count: 0 },
|
||||
// { url: '/2020/12/post-3.html', count: 20 }
|
||||
// ]
|
||||
}).catch(function (err) {
|
||||
// 发生错误
|
||||
console.error(err)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('twikoo 加载失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadTwikoo()
|
||||
}, [])
|
||||
return (
|
||||
<div id="twikoo"></div>
|
||||
<div id="twikoo"></div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
22
components/TwikooCommentCount.js
Normal file
22
components/TwikooCommentCount.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import BLOG from '@/blog.config'
|
||||
// import twikoo from 'twikoo'
|
||||
|
||||
/**
|
||||
* 获取博客的评论数,用与在列表中展示
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
const TwikooCommentCount = ({ post, className }) => {
|
||||
if (!JSON.parse(BLOG.COMMENT_TWIKOO_COUNT_ENABLE)) {
|
||||
return null
|
||||
}
|
||||
return <a href={`${post.slug}?target=comment`} className={`mx-1 hidden comment-count-wrapper-${post.id} ${className || ''}`}>
|
||||
<i className="far fa-comment mr-1"></i>
|
||||
<span className={`comment-count-text-${post.id}`}>
|
||||
{/* <i className='fa-solid fa-spinner animate-spin' /> */}
|
||||
</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
export default TwikooCommentCount
|
||||
79
components/TwikooCommentCounter.js
Normal file
79
components/TwikooCommentCounter.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
/**
|
||||
* 获取博客的评论数,用与在列表中展示
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
const TwikooCommentCounter = (props) => {
|
||||
let commentsData = []
|
||||
const { theme } = useGlobal()
|
||||
|
||||
const fetchTwikooData = async (posts) => {
|
||||
posts.forEach(post => {
|
||||
post.slug = post.slug.startsWith('/') ? post.slug : `/${post.slug}`
|
||||
})
|
||||
try {
|
||||
await loadExternalResource(BLOG.COMMENT_TWIKOO_CDN_URL, 'js')
|
||||
const twikoo = window.twikoo
|
||||
twikoo.getCommentsCount({
|
||||
envId: BLOG.COMMENT_TWIKOO_ENV_ID, // 环境 ID
|
||||
// region: 'ap-guangzhou', // 环境地域,默认为 ap-shanghai,如果您的环境地域不是上海,需传此参数
|
||||
urls: posts.map(post => post.slug), // 不包含协议、域名、参数的文章路径列表,必传参数
|
||||
includeReply: true // 评论数是否包括回复,默认:false
|
||||
}).then(function (res) {
|
||||
console.log('查询', res)
|
||||
commentsData = res
|
||||
updateCommentCount()
|
||||
}).catch(function (err) {
|
||||
// 发生错误
|
||||
console.error(err)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('twikoo 加载失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateCommentCount = () => {
|
||||
if (commentsData.length === 0) {
|
||||
return
|
||||
}
|
||||
props.posts.forEach(post => {
|
||||
const matchingRes = commentsData.find(r => r.url === post.slug)
|
||||
if (matchingRes) {
|
||||
// 修改评论数量div
|
||||
const textElements = document.querySelectorAll(`.comment-count-text-${post.id}`)
|
||||
textElements.forEach(element => {
|
||||
element.innerHTML = matchingRes.count
|
||||
})
|
||||
// 取消隐藏
|
||||
const wrapperElements = document.querySelectorAll(`.comment-count-wrapper-${post.id}`)
|
||||
wrapperElements.forEach(element => {
|
||||
element.classList.remove('hidden')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
console.log('路由触发评论计数')
|
||||
if (props?.posts && props?.posts?.length > 0) {
|
||||
fetchTwikooData(props.posts)
|
||||
}
|
||||
}, [router.events])
|
||||
|
||||
// 监控主题变化时的的评论数
|
||||
useEffect(() => {
|
||||
console.log('主题触发评论计数', commentsData)
|
||||
updateCommentCount()
|
||||
}, [theme])
|
||||
return null
|
||||
}
|
||||
|
||||
export default TwikooCommentCounter
|
||||
12
components/TwikooRecentComments.js
Normal file
12
components/TwikooRecentComments.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
/**
|
||||
* 显示最近评论 TODO
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
const TwikooRecentComments = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
export default TwikooRecentComments
|
||||
53
lib/lang.js
53
lib/lang.js
@@ -4,47 +4,58 @@ import zhHK from './lang/zh-HK'
|
||||
import zhTW from './lang/zh-TW'
|
||||
import frFR from './lang/fr-FR'
|
||||
import trTR from './lang/tr-TR'
|
||||
import jaJP from './lang/ja-JP'
|
||||
import cookie from 'react-cookies'
|
||||
import { getQueryVariable, isBrowser, mergeDeep } from './utils'
|
||||
|
||||
/**
|
||||
* 在这里配置所有支持的语言
|
||||
* 国家-地区
|
||||
*/
|
||||
const lang = {
|
||||
'en-US': enUS,
|
||||
'zh-CN': zhCN,
|
||||
'zh-HK': zhHK,
|
||||
'zh-TW': zhTW,
|
||||
'fr-FR': frFR,
|
||||
'tr-TR': trTR
|
||||
'tr-TR': trTR,
|
||||
'ja-JP': jaJP
|
||||
}
|
||||
|
||||
export default lang
|
||||
|
||||
/**
|
||||
* 获取当前语言字典
|
||||
* 如果匹配到完整的“国家-地区”语言,则显示国家的语言
|
||||
* @returns 不同语言对应字典
|
||||
*/
|
||||
export function generateLocaleDict(langString) {
|
||||
let userLocale = lang['en-US']
|
||||
const supportedLocales = Object.keys(lang)
|
||||
let userLocale
|
||||
|
||||
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
|
||||
case 'fr-fr':
|
||||
userLocale = lang['fr-FR']
|
||||
break
|
||||
case 'tr-tr':
|
||||
userLocale = lang['tr-TR']
|
||||
break
|
||||
default:
|
||||
userLocale = lang['en-US']
|
||||
// 将语言字符串拆分为语言和地区代码,例如将 "zh-CN" 拆分为 "zh" 和 "CN"
|
||||
const [language, region] = langString.split(/[-_]/)
|
||||
|
||||
// 优先匹配语言和地区都匹配的情况
|
||||
const specificLocale = `${language}-${region}`
|
||||
if (supportedLocales.includes(specificLocale)) {
|
||||
userLocale = lang[specificLocale]
|
||||
}
|
||||
|
||||
// 然后尝试匹配只有语言匹配的情况
|
||||
if (!userLocale) {
|
||||
const languageOnlyLocales = supportedLocales.filter(locale => locale.startsWith(language))
|
||||
if (languageOnlyLocales.length > 0) {
|
||||
userLocale = lang[languageOnlyLocales[0]]
|
||||
}
|
||||
}
|
||||
|
||||
// 如果还没匹配到,则返回最接近的语言包
|
||||
if (!userLocale) {
|
||||
const fallbackLocale = supportedLocales.find(locale => locale.startsWith('en'))
|
||||
userLocale = lang[fallbackLocale]
|
||||
}
|
||||
|
||||
return mergeDeep({}, lang['en-US'], userLocale)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ export default {
|
||||
DEBUG_OPEN: 'Debug',
|
||||
DEBUG_CLOSE: 'Close',
|
||||
THEME_SWITCH: 'Theme Switch',
|
||||
ANNOUNCEMENT: 'Announcement'
|
||||
ANNOUNCEMENT: 'Announcement',
|
||||
START_READING: 'Start Reading'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: 'Prev',
|
||||
|
||||
59
lib/lang/ja-JP.js
Normal file
59
lib/lang/ja-JP.js
Normal file
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
LOCALE: 'ja-JP',
|
||||
NAV: {
|
||||
INDEX: 'ホーム',
|
||||
RSS: '購読',
|
||||
SEARCH: '検索',
|
||||
ABOUT: 'このサイトについて',
|
||||
NAVIGATOR: 'ナビゲーション',
|
||||
MAIL: 'メール',
|
||||
ARCHIVE: 'アーカイブ'
|
||||
},
|
||||
COMMON: {
|
||||
MORE: 'さらに',
|
||||
NO_MORE: 'それ以上ありません',
|
||||
LATEST_POSTS: '最新の記事',
|
||||
TAGS: 'タグ',
|
||||
NO_TAG: 'タグなし',
|
||||
CATEGORY: 'カテゴリ',
|
||||
SHARE: 'シェア',
|
||||
SCAN_QR_CODE: 'WeChatで共有',
|
||||
URL_COPIED: 'リンクがコピーされました!',
|
||||
TABLE_OF_CONTENTS: '目次',
|
||||
RELATE_POSTS: '関連する記事',
|
||||
COPYRIGHT: '免責事項',
|
||||
AUTHOR: '作成者',
|
||||
URL: 'リンク',
|
||||
ANALYTICS: '統計',
|
||||
POSTS: '記事',
|
||||
ARTICLE: '記事',
|
||||
VISITORS: '人の訪問者',
|
||||
VIEWS: '回の閲覧',
|
||||
COPYRIGHT_NOTICE: 'この記事はCC BY-NC-SA 4.0 ライセンスの下でライセンスされています。転載する場合には出典を明らかにしてください。',
|
||||
RESULT_OF_SEARCH: '個の検索結果',
|
||||
ARTICLE_DETAIL: '記事の詳細',
|
||||
PASSWORD_ERROR: 'パスワードが違います!',
|
||||
ARTICLE_LOCK_TIPS: 'この記事はロックされています。アクセスパスワードを入力してください。',
|
||||
SUBMIT: '送信',
|
||||
POST_TIME: '公開日',
|
||||
LAST_EDITED_TIME: '最終更新日',
|
||||
RECENT_COMMENTS: '最近のコメント',
|
||||
DEBUG_OPEN: 'デバッグをオンにする',
|
||||
DEBUG_CLOSE: 'デバッグをオフにする',
|
||||
THEME_SWITCH: 'テーマの切り替え',
|
||||
ANNOUNCEMENT: 'お知らせ',
|
||||
START_READING: '読み始める'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: '前のページ',
|
||||
NEXT: '次のページ'
|
||||
},
|
||||
SEARCH: {
|
||||
ARTICLES: '記事を検索',
|
||||
TAGS: 'タグを検索'
|
||||
},
|
||||
POST: {
|
||||
BACK: '前のページに戻る',
|
||||
TOP: '上に戻る'
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export default {
|
||||
NO_TAG: 'NoTag',
|
||||
CATEGORY: '分类',
|
||||
SHARE: '分享',
|
||||
SCAN_QR_CODE: '扫一扫二维码',
|
||||
SCAN_QR_CODE: '微信扫码分享',
|
||||
URL_COPIED: '链接已复制!',
|
||||
TABLE_OF_CONTENTS: '目录',
|
||||
RELATE_POSTS: '相关文章',
|
||||
@@ -41,7 +41,8 @@ export default {
|
||||
DEBUG_OPEN: '开启调试',
|
||||
DEBUG_CLOSE: '关闭调试',
|
||||
THEME_SWITCH: '切换主题',
|
||||
ANNOUNCEMENT: '公告'
|
||||
ANNOUNCEMENT: '公告',
|
||||
START_READING: '开始阅读'
|
||||
},
|
||||
PAGINATION: {
|
||||
PREV: '上一页',
|
||||
|
||||
@@ -65,10 +65,10 @@ export async function getNotionPageData({ pageId, from }) {
|
||||
const cacheKey = 'page_block_' + pageId
|
||||
const data = await getDataFromCache(cacheKey)
|
||||
if (data && data.pageIds?.length > 0) {
|
||||
console.log('[命中缓存]:', `from:${from}`, `root-page-id:${pageId}`)
|
||||
console.log('[缓存]:', `from:${from}`, `root-page-id:${pageId}`)
|
||||
return data
|
||||
}
|
||||
const pageRecordMap = await getPageRecordMapByNotionAPI({ pageId, from })
|
||||
const pageRecordMap = await getDataBaseInfoByNotionAPI({ pageId, from })
|
||||
// 存入缓存
|
||||
if (pageRecordMap) {
|
||||
await setDataToCache(cacheKey, pageRecordMap)
|
||||
@@ -177,24 +177,44 @@ async function getNotice(post) {
|
||||
* 调用NotionAPI获取Page数据
|
||||
* @returns {Promise<JSX.Element|null|*>}
|
||||
*/
|
||||
async function getPageRecordMapByNotionAPI({ pageId, from }) {
|
||||
async function getDataBaseInfoByNotionAPI({ pageId, from }) {
|
||||
const pageRecordMap = await getPostBlocks(pageId, from)
|
||||
if (!pageRecordMap) {
|
||||
return []
|
||||
console.error('can`t get Notion Data ; Which id is: ', pageId)
|
||||
return {}
|
||||
}
|
||||
pageId = idToUuid(pageId)
|
||||
const block = pageRecordMap.block
|
||||
const block = pageRecordMap.block || {}
|
||||
const rawMetadata = block[pageId]?.value
|
||||
// Check Type Page-Database和Inline-Database
|
||||
if (
|
||||
rawMetadata?.type !== 'collection_view_page' &&
|
||||
rawMetadata?.type !== 'collection_view'
|
||||
rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view'
|
||||
) {
|
||||
console.warn(`pageId "${pageId}" is not a database`)
|
||||
return null
|
||||
console.error(`pageId "${pageId}" is not a database`)
|
||||
return {
|
||||
notice: null,
|
||||
siteInfo: getBlogInfo({}),
|
||||
allPages: [{ id: 1, title: `无法获取Notion数据,请检查Notion_ID: \n 当前 ${pageId}`, summary: '访问文档获取帮助→ https://tangly1024.com/article/vercel-deploy-notion-next', status: 'Published', type: 'Post', slug: '13a171332816461db29d50e9f575b00d', date: { start_date: '2023-04-24', lastEditedTime: '2023-04-24', tagItems: [] } }],
|
||||
collection: [],
|
||||
collectionQuery: {},
|
||||
collectionId: null,
|
||||
collectionView: {},
|
||||
viewIds: [],
|
||||
block: {},
|
||||
schema: {},
|
||||
tagOptions: [],
|
||||
categoryOptions: [],
|
||||
rawMetadata: {},
|
||||
customNav: [],
|
||||
customMenu: [],
|
||||
postCount: 1,
|
||||
pageIds: [],
|
||||
latestPosts: []
|
||||
}
|
||||
}
|
||||
|
||||
const collection = Object.values(pageRecordMap.collection)[0]?.value
|
||||
const collection = Object.values(pageRecordMap.collection)[0]?.value || {}
|
||||
const siteInfo = getBlogInfo({ collection, block })
|
||||
const collectionId = rawMetadata?.collection_id
|
||||
const collectionQuery = pageRecordMap.collection_query
|
||||
const collectionView = pageRecordMap.collection_view
|
||||
@@ -222,7 +242,7 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
|
||||
let postCount = 0
|
||||
// 查找所有的Post和Page
|
||||
const allPages = collectionData.filter(post => {
|
||||
if (post.type === BLOG.NOTION_PROPERTY_NAME.type_post && post.status === BLOG.NOTION_PROPERTY_NAME.status_publish) {
|
||||
if (post.type === 'Post' && post.status === 'Published') {
|
||||
postCount++
|
||||
}
|
||||
return post && post?.slug &&
|
||||
@@ -242,7 +262,6 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
|
||||
const notice = await getNotice(collectionData.filter(post => { return post && post?.type && post?.type === 'Notice' && post.status === 'Published' })?.[0])
|
||||
const categoryOptions = getAllCategories({ allPages, categoryOptions: getCategoryOptions(schema) })
|
||||
const tagOptions = getAllTags({ allPages, tagOptions: getTagOptions(schema) })
|
||||
const siteInfo = getBlogInfo({ collection, block })
|
||||
const customNav = getCustomNav({ allPages: collectionData.filter(post => post.type === 'Page' && post.status === 'Published') })
|
||||
// 新的菜单
|
||||
const customMenu = await getCustomMenu({ collectionData })
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function getPostBlocks(id, from, slice) {
|
||||
const cacheKey = 'page_block_' + id
|
||||
let pageBlock = await getDataFromCache(cacheKey)
|
||||
if (pageBlock) {
|
||||
console.log('[命中缓存]:', `from:${from}`, cacheKey)
|
||||
console.log('[缓存]:', `from:${from}`, cacheKey)
|
||||
return filterPostBlocks(id, pageBlock, slice)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export async function getPageWithRetry(id, from, retryAttempts = 3) {
|
||||
console.log('[请求API]', `from:${from}`, `id:${id}`, retryAttempts < 3 ? `剩余重试次数:${retryAttempts}` : '')
|
||||
try {
|
||||
const authToken = BLOG.NOTION_ACCESS_TOKEN || null
|
||||
const api = new NotionAPI({ authToken, userTimeZone: 'Asia/ShangHai' })
|
||||
const api = new NotionAPI({ authToken, userTimeZone: Intl.DateTimeFormat().resolvedOptions().timeZone })
|
||||
const pageData = await api.getPage(id)
|
||||
console.info('[响应成功]:', `from:${from}`)
|
||||
return pageData
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "notion-next",
|
||||
"version": "3.13.4",
|
||||
"version": "3.13.6",
|
||||
"homepage": "https://github.com/tangly1024/NotionNext.git",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -22,7 +22,6 @@
|
||||
"dependencies": {
|
||||
"@giscus/react": "^2.2.6",
|
||||
"@next/bundle-analyzer": "^12.1.1",
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"@vercel/analytics": "^1.0.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"animejs": "^3.2.1",
|
||||
@@ -39,7 +38,7 @@
|
||||
"memory-cache": "^0.2.0",
|
||||
"mermaid": "9.2.2",
|
||||
"mongodb": "^4.6.0",
|
||||
"next": "^13.1.1",
|
||||
"next": "13.3.1",
|
||||
"notion-client": "6.15.6",
|
||||
"notion-utils": "6.15.6",
|
||||
"nprogress": "^0.2.0",
|
||||
@@ -53,10 +52,9 @@
|
||||
"react-facebook": "^8.1.4",
|
||||
"react-messenger-customer-chat": "^0.8.0",
|
||||
"react-notion-x": "6.16.0",
|
||||
"react-share": "^4.4.0",
|
||||
"react-share": "^4.4.1",
|
||||
"react-tweet-embed": "~2.0.0",
|
||||
"smoothscroll-polyfill": "^0.4.4",
|
||||
"twikoo": "^1.6.16",
|
||||
"typed.js": "^2.0.12",
|
||||
"use-ackee": "^3.0.0",
|
||||
"valine": "^1.4.18"
|
||||
|
||||
@@ -31,7 +31,7 @@ const Slug = props => {
|
||||
setLock(true)
|
||||
} else {
|
||||
if (!lock && post?.blockMap?.block) {
|
||||
post.content = Object.keys(post.blockMap.block)
|
||||
post.content = Object.keys(post.blockMap.block).filter(key => post.blockMap.block[key]?.value?.parent_id === post.id)
|
||||
post.toc = getPageTableOfContents(post, post.blockMap)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,12 @@ import { StarrySky } from '@/components/StarrySky'
|
||||
import MusicPlayer from '@/components/MusicPlayer'
|
||||
import ExternalScript from '@/components/ExternalScript'
|
||||
import smoothscroll from 'smoothscroll-polyfill'
|
||||
import { Analytics } from '@vercel/analytics/react'
|
||||
|
||||
import AOS from 'aos'
|
||||
import 'aos/dist/aos.css' // You can also use <link> for styles
|
||||
import { isMobile } from '@/lib/utils'
|
||||
import TwikooCommentCounter from '@/components/TwikooCommentCounter'
|
||||
|
||||
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
|
||||
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
|
||||
@@ -44,6 +46,7 @@ const MyApp = ({ Component, pageProps }) => {
|
||||
{JSON.parse(BLOG.DEBUG) && <DebugPanel />}
|
||||
{BLOG.ANALYTICS_ACKEE_TRACKER && <Ackee />}
|
||||
{BLOG.ANALYTICS_GOOGLE_ID && <Gtag />}
|
||||
{BLOG.ANALYTICS_VERCEL && <Analytics />}
|
||||
{JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && <Busuanzi />}
|
||||
{BLOG.ADSENSE_GOOGLE_ID && <GoogleAdsense />}
|
||||
{BLOG.FACEBOOK_APP_ID && BLOG.FACEBOOK_PAGE_ID && <Messenger />}
|
||||
@@ -53,6 +56,7 @@ const MyApp = ({ Component, pageProps }) => {
|
||||
{JSON.parse(BLOG.MUSIC_PLAYER) && <MusicPlayer />}
|
||||
{JSON.parse(BLOG.NEST) && <Nest />}
|
||||
{JSON.parse(BLOG.FLUTTERINGRIBBON) && <FlutteringRibbon />}
|
||||
{JSON.parse(BLOG.COMMENT_TWIKOO_COUNT_ENABLE) && <TwikooCommentCounter {...pageProps}/>}
|
||||
{JSON.parse(BLOG.RIBBON) && <Ribbon />}
|
||||
<ExternalScript/>
|
||||
</>
|
||||
|
||||
@@ -5,12 +5,22 @@ import * as ThemeMap from '@/themes'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { generateRss } from '@/lib/rss'
|
||||
import { generateRobotsTxt } from '@/lib/robots.txt'
|
||||
|
||||
/**
|
||||
* 首页布局
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const Index = props => {
|
||||
const { theme } = useGlobal()
|
||||
const ThemeComponents = ThemeMap[theme]
|
||||
return <ThemeComponents.LayoutIndex {...props} />
|
||||
}
|
||||
|
||||
/**
|
||||
* SSG 获取数据
|
||||
* @returns
|
||||
*/
|
||||
export async function getStaticProps() {
|
||||
const from = 'index'
|
||||
const props = await getGlobalNotionData({ from })
|
||||
|
||||
@@ -1,33 +1 @@
|
||||
#theme-fukasawa .grid-item {
|
||||
height: auto;
|
||||
break-inside: avoid-column;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
#theme-fukasawa .sideLeft hr{
|
||||
opacity: .04;
|
||||
}
|
||||
|
||||
/* 大屏幕(宽度≥1024px)下显示3列 */
|
||||
@media (min-width: 1024px) {
|
||||
#theme-fukasawa .grid-container {
|
||||
column-count: 3;
|
||||
column-gap: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏幕(宽度≥640px)下显示2列 */
|
||||
@media (min-width: 640px) and (max-width: 1023px) {
|
||||
#theme-fukasawa .grid-container {
|
||||
column-count: 2;
|
||||
column-gap: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端(宽度<640px)下显示1列 */
|
||||
@media (max-width: 639px) {
|
||||
#theme-fukasawa .grid-container {
|
||||
column-count: 1;
|
||||
column-gap: .5rem;
|
||||
}
|
||||
}
|
||||
/* fukasawa的主题相关 */
|
||||
@@ -201,8 +201,8 @@ nav {
|
||||
}
|
||||
|
||||
.next #announcement-content *{
|
||||
font-size:12px !important;
|
||||
line-height:1.5 !important;
|
||||
font-size:13px !important;
|
||||
line-height:1.7 !important;
|
||||
}
|
||||
|
||||
/* twikoo 评论区超链接样式 */
|
||||
@@ -310,4 +310,35 @@ a.avatar-wrapper {
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
/* fukasawa的首页响应式分栏 */
|
||||
#theme-fukasawa .grid-item {
|
||||
height: auto;
|
||||
break-inside: avoid-column;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
/* 大屏幕(宽度≥1024px)下显示3列 */
|
||||
@media (min-width: 1024px) {
|
||||
#theme-fukasawa .grid-container {
|
||||
column-count: 3;
|
||||
column-gap: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏幕(宽度≥640px)下显示2列 */
|
||||
@media (min-width: 640px) and (max-width: 1023px) {
|
||||
#theme-fukasawa .grid-container {
|
||||
column-count: 2;
|
||||
column-gap: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端(宽度<640px)下显示1列 */
|
||||
@media (max-width: 639px) {
|
||||
#theme-fukasawa .grid-container {
|
||||
column-count: 1;
|
||||
column-gap: .5rem;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@
|
||||
--notion-yellow_background: rgb(251, 243, 219);
|
||||
--notion-orange_background: rgb(250, 235, 221);
|
||||
--notion-brown_background: rgb(233, 229, 227);
|
||||
--notion-gray_background: rgb(235, 236, 237);
|
||||
--notion-gray_background: rgb(241 241 239);
|
||||
--notion-green_background: rgb(219, 237, 219);
|
||||
--notion-default_background: rgba(227, 226, 224);
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
--notion-yellow_background_co: rgba(251, 243, 219, 0.3);
|
||||
--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-gray_background_co: rgba(241, 241, 239, 0.3);
|
||||
--notion-green_background_co: rgba(219, 237, 219, 0.3);
|
||||
--notion-default_background_co: rgba(227, 226, 224, 0.3);
|
||||
|
||||
@@ -919,6 +919,7 @@ code[class*='language-'] {
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
color: var(--fg-color-3);
|
||||
@apply dark:text-gray-300
|
||||
}
|
||||
|
||||
.notion-callout {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ArticleLock } from './components/ArticleLock'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import { ArticleInfo } from './components/ArticleInfo'
|
||||
import Comment from '@/components/Comment'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
|
||||
export const LayoutSlug = props => {
|
||||
const { post, lock, validPassword } = props
|
||||
@@ -17,10 +18,10 @@ export const LayoutSlug = props => {
|
||||
{lock && <ArticleLock validPassword={validPassword} />}
|
||||
|
||||
{!lock && <div id="notion-article" className="px-2">
|
||||
|
||||
{post && <>
|
||||
<ArticleInfo post={post} />
|
||||
<NotionPage post={post} />
|
||||
<ShareBar post={post} />
|
||||
<Comment frontMatter={post}/>
|
||||
</>}
|
||||
</div>}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useGlobal } from '@/lib/global'
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
import CONFIG_EXAMPLE from '../config_example'
|
||||
import BlogPostCard from './BlogPostCard'
|
||||
|
||||
export const BlogListPage = props => {
|
||||
const { page = 1, posts, postCount } = props
|
||||
@@ -22,46 +23,8 @@ export const BlogListPage = props => {
|
||||
<div className={`w-full ${showPageCover ? 'md:pr-2' : 'md:pr-12'}} mb-12`}>
|
||||
|
||||
<div id="container">
|
||||
{posts?.map(p => (
|
||||
<article key={p.id} className={`mb-12 ${showPageCover ? 'flex md:flex-row flex-col-reverse' : ''}`}>
|
||||
<div className={`${showPageCover ? 'md:w-7/12' : ''}`}>
|
||||
<h2 className="mb-4">
|
||||
<Link
|
||||
href={`/${p.slug}`}
|
||||
className="text-black dark:text-gray-100 text-xl md:text-2xl no-underline hover:underline">
|
||||
{p.title}
|
||||
</Link>
|
||||
</h2>
|
||||
|
||||
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
|
||||
by <a href="#" className="text-gray-700 dark:text-gray-300">{BLOG.AUTHOR}</a> on {p.date?.start_date || p.createdTime}
|
||||
<span className="font-bold mx-1"> | </span>
|
||||
<a href={`/category${p.category}`} className="text-gray-700 dark:text-gray-300 hover:underline">{p.category}</a>
|
||||
{/* <span className="font-bold mx-1"> | </span> */}
|
||||
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700 dark:text-gray-400 leading-normal p-3-lines">
|
||||
{p.summary}
|
||||
</p>
|
||||
{/* 搜索结果 */}
|
||||
{p.results && (
|
||||
<p className="p-4-lines mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||
{p.results.map(r => (
|
||||
<span key={r}>{r}</span>
|
||||
))}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* 图片封面 */}
|
||||
{showPageCover && (
|
||||
<div className="md:w-5/12 w-full overflow-hidden p-1">
|
||||
<Link href={`${BLOG.SUB_PATH}/${p.slug}`} passHref legacyBehavior>
|
||||
<div className='h-44 bg-center bg-cover hover:scale-110 duration-200' style={{ backgroundImage: `url('${p?.page_cover}')` }} />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
{posts?.map(post => (
|
||||
<BlogPostCard key={post.id} post = {post}/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import throttle from 'lodash.throttle'
|
||||
import BlogPostCard from './BlogPostCard'
|
||||
import CONFIG_EXAMPLE from '../config_example'
|
||||
|
||||
export const BlogListScroll = props => {
|
||||
@@ -35,8 +35,9 @@ export const BlogListScroll = props => {
|
||||
handleGetMore()
|
||||
}
|
||||
}, 500))
|
||||
const showPageCover = CONFIG_EXAMPLE.POST_LIST_COVER
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', scrollTrigger)
|
||||
|
||||
return () => {
|
||||
@@ -44,60 +45,22 @@ export const BlogListScroll = props => {
|
||||
}
|
||||
})
|
||||
|
||||
const showPageCover = CONFIG_EXAMPLE.POST_LIST_COVER
|
||||
|
||||
return (
|
||||
<div id="container" className="w-full md:pr-12 mb-12" ref={targetRef}>
|
||||
{postsToShow.map(p => (
|
||||
<article key={p.id} className={`mb-12 ${showPageCover ? 'flex md:flex-row flex-col-reverse' : ''}`}>
|
||||
<div className={`${showPageCover ? 'md:w-7/12' : ''}`}>
|
||||
<h2 className="mb-4">
|
||||
<Link
|
||||
href={`/${p.slug}`}
|
||||
className="text-black dark:text-gray-100 text-xl md:text-2xl no-underline hover:underline">
|
||||
{p.title}
|
||||
</Link>
|
||||
</h2>
|
||||
|
||||
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
|
||||
by <a href="#" className="text-gray-700 dark:text-gray-300">{BLOG.AUTHOR}</a> on {p.date?.start_date || p.createdTime}
|
||||
<span className="font-bold mx-1"> | </span>
|
||||
<a href={`/category${p.category}`} className="text-gray-700 dark:text-gray-300 hover:underline">{p.category}</a>
|
||||
{/* <span className="font-bold mx-1"> | </span> */}
|
||||
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
|
||||
</div>
|
||||
<div className={`w-full ${showPageCover ? 'md:pr-2' : 'md:pr-12'}} mb-12`} ref={targetRef}>
|
||||
|
||||
<p className="text-gray-700 dark:text-gray-400 leading-normal p-3-lines">
|
||||
{p.summary}
|
||||
</p>
|
||||
{/* 搜索结果 */}
|
||||
{p.results && (
|
||||
<p className="p-4-lines mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||
{p.results.map(r => (
|
||||
<span key={r}>{r}</span>
|
||||
))}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* 图片封面 */}
|
||||
{showPageCover && (
|
||||
<div className="md:w-5/12 w-full overflow-hidden p-1">
|
||||
<Link href={`${BLOG.SUB_PATH}/${p.slug}`} passHref legacyBehavior>
|
||||
<div className='h-44 bg-center bg-cover hover:scale-110 duration-200' style={{ backgroundImage: `url('${p?.page_cover}')` }} />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
))}
|
||||
{postsToShow?.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} />
|
||||
))}
|
||||
|
||||
<div
|
||||
onClick={handleGetMore}
|
||||
className="w-full my-4 py-4 text-center cursor-pointer "
|
||||
>
|
||||
{' '}
|
||||
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
|
||||
</div>
|
||||
<div
|
||||
onClick={handleGetMore}
|
||||
className="w-full my-4 py-4 text-center cursor-pointer "
|
||||
>
|
||||
{' '}
|
||||
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
51
themes/example/components/BlogPostCard.js
Normal file
51
themes/example/components/BlogPostCard.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import CONFIG_EXAMPLE from '../config_example'
|
||||
import Link from 'next/link'
|
||||
import TwikooCommentCount from '@/components/TwikooCommentCount'
|
||||
|
||||
const BlogPostCard = ({ post }) => {
|
||||
const showPageCover = CONFIG_EXAMPLE.POST_LIST_COVER
|
||||
|
||||
return <article className={`mb-12 ${showPageCover ? 'flex md:flex-row flex-col-reverse' : ''}`}>
|
||||
<div className={`${showPageCover ? 'md:w-7/12' : ''}`}>
|
||||
<h2 className="mb-4">
|
||||
<Link
|
||||
href={`/${post.slug}`}
|
||||
className="text-black dark:text-gray-100 text-xl md:text-2xl no-underline hover:underline">
|
||||
{post.title}
|
||||
</Link>
|
||||
</h2>
|
||||
|
||||
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
|
||||
by <a href="#" className="text-gray-700 dark:text-gray-300">{BLOG.AUTHOR}</a> on {post.date?.start_date || post.createdTime}
|
||||
<TwikooCommentCount post={post} className='pl-1'/>
|
||||
<span className="font-bold mx-1"> | </span>
|
||||
<a href={`/category${post.category}`} className="text-gray-700 dark:text-gray-300 hover:underline">{post.category}</a>
|
||||
{/* <span className="font-bold mx-1"> | </span> */}
|
||||
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700 dark:text-gray-400 leading-normal p-3-lines">
|
||||
{post.summary}
|
||||
</p>
|
||||
{/* 搜索结果 */}
|
||||
{post.results && (
|
||||
<p className="p-4-lines mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||
{post.results.map(r => (
|
||||
<span key={r}>{r}</span>
|
||||
))}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* 图片封面 */}
|
||||
{showPageCover && (
|
||||
<div className="md:w-5/12 w-full overflow-hidden p-1">
|
||||
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
|
||||
<div className='h-44 bg-center bg-cover hover:scale-110 duration-200' style={{ backgroundImage: `url('${post?.page_cover}')` }} />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
}
|
||||
|
||||
export default BlogPostCard
|
||||
@@ -1,6 +1,7 @@
|
||||
import TagItemMini from './TagItemMini'
|
||||
import Comment from '@/components/Comment'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
import formatDate from '@/lib/formatDate'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Link from 'next/link'
|
||||
@@ -89,6 +90,11 @@ export default function ArticleDetail(props) {
|
||||
{post && <NotionPage post={post} />}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
{/* 分享 */}
|
||||
<ShareBar post={post} />
|
||||
</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"
|
||||
|
||||
@@ -47,7 +47,7 @@ const PaginationSimple = ({ page, showNext }) => {
|
||||
{locale.PAGINATION.NEXT}→
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default PaginationSimple
|
||||
|
||||
@@ -11,6 +11,7 @@ import ArticleAdjacent from './components/ArticleAdjacent'
|
||||
import ArticleCopyright from './components/ArticleCopyright'
|
||||
import ArticleRecommend from './components/ArticleRecommend'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
|
||||
export const LayoutSlug = props => {
|
||||
const { post, lock, validPassword } = props
|
||||
@@ -68,6 +69,8 @@ export const LayoutSlug = props => {
|
||||
data-ad-slot="3806269138" />
|
||||
</section>
|
||||
|
||||
{/* 分享 */}
|
||||
<ShareBar post={post} />
|
||||
{post.type === 'Post' && <ArticleCopyright {...props} /> }
|
||||
{post.type === 'Post' && <ArticleRecommend {...props} /> }
|
||||
{post.type === 'Post' && <ArticleAdjacent {...props} /> }
|
||||
|
||||
@@ -2,6 +2,7 @@ import BLOG from '@/blog.config'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import Link from 'next/link'
|
||||
import TagItemMini from './TagItemMini'
|
||||
import TwikooCommentCount from '@/components/TwikooCommentCount'
|
||||
|
||||
/**
|
||||
* 博客列表的文字内容
|
||||
@@ -26,7 +27,7 @@ export const BlogPostCardInfo = ({ post, showPreview, showPageCover, showSummary
|
||||
{/* 分类 */}
|
||||
<div
|
||||
className={`flex mt-2 items-center ${showPreview ? 'justify-center' : 'justify-start'
|
||||
} flex-wrap dark:text-gray-500 text-gray-400 hover:text-indigo-700 dark:hover:text-indigo-400`}
|
||||
} flex-wrap dark:text-gray-500 text-gray-400 `}
|
||||
>
|
||||
<Link
|
||||
href={`/category/${post.category}`}
|
||||
@@ -37,6 +38,8 @@ export const BlogPostCardInfo = ({ post, showPreview, showPageCover, showSummary
|
||||
{post.category}
|
||||
|
||||
</Link>
|
||||
|
||||
<TwikooCommentCount className='text-sm hover:text-indigo-700 dark:hover:text-indigo-400' post={post}/>
|
||||
</div>
|
||||
|
||||
{/* 摘要 */}
|
||||
@@ -80,7 +83,7 @@ export const BlogPostCardInfo = ({ post, showPreview, showPageCover, showSummary
|
||||
<div className="md:flex-nowrap flex-wrap md:justify-start inline-block">
|
||||
<div>
|
||||
{' '}
|
||||
{post.tagItems.map(tag => (
|
||||
{post.tagItems?.map(tag => (
|
||||
<TagItemMini key={tag.name} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Typed from 'typed.js'
|
||||
import CONFIG_HEXO from '../config_hexo'
|
||||
import NavButtonGroup from './NavButtonGroup'
|
||||
import throttle from 'lodash.throttle'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
let wrapperTop = 0
|
||||
let windowTop = 0
|
||||
@@ -17,6 +18,7 @@ const enableAutoScroll = false // 是否开启自动吸附滚动
|
||||
const Header = props => {
|
||||
const [typed, changeType] = useState()
|
||||
const { siteInfo } = props
|
||||
const { locale } = useGlobal()
|
||||
useEffect(() => {
|
||||
updateHeaderHeight()
|
||||
|
||||
@@ -103,7 +105,7 @@ const Header = props => {
|
||||
onClick={() => { window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) }}
|
||||
className="cursor-pointer w-full text-center py-4 text-3xl absolute bottom-10 text-white"
|
||||
>
|
||||
<div class="opacity-70 animate-bounce text-xs">点此继续</div>
|
||||
<div className="opacity-70 animate-bounce text-xs">{locale.COMMON.START_READING}</div>
|
||||
<i className='opacity-70 animate-bounce fas fa-angle-down' />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -25,21 +25,23 @@ const HexoRecentComments = (props) => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card >
|
||||
<div className=" mb-2 px-1 justify-between">
|
||||
<i className="mr-2 fas fas fa-comment" />
|
||||
{locale.COMMON.RECENT_COMMENTS}
|
||||
</div>
|
||||
<Card >
|
||||
<div className=" mb-2 px-1 justify-between">
|
||||
<i className="mr-2 fas fas fa-comment" />
|
||||
{locale.COMMON.RECENT_COMMENTS}
|
||||
</div>
|
||||
|
||||
{onLoading && <div>Loading...<i className='ml-2 fas fa-spinner animate-spin' /></div>}
|
||||
{!onLoading && comments && comments.length === 0 && <div>No Comments</div>}
|
||||
{!onLoading && comments && comments.length > 0 && comments.map((comment) => <div key={comment.objectId} className='pb-2 pl-1'>
|
||||
<div className='dark:text-gray-200 text-sm waline-recent-content wl-content' dangerouslySetInnerHTML={{ __html: comment.comment }} />
|
||||
<div className='dark:text-gray-400 text-gray-400 text-sm text-right cursor-pointer hover:text-red-500 hover:underline pt-1 pr-2'><Link href={{ pathname: comment.url, hash: comment.objectId, query: { target: 'comment' } }}>--{comment.nick}</Link></div>
|
||||
</div>)}
|
||||
{onLoading && <div>Loading...<i className='ml-2 fas fa-spinner animate-spin' /></div>}
|
||||
{!onLoading && comments && comments.length === 0 && <div>No Comments</div>}
|
||||
{!onLoading && comments && comments.length > 0 && comments.map((comment) => <div key={comment.objectId} className='pb-2 pl-1'>
|
||||
<div className='dark:text-gray-200 text-sm waline-recent-content wl-content' dangerouslySetInnerHTML={{ __html: comment.comment }} />
|
||||
<div className='dark:text-gray-400 text-gray-400 text-sm text-right cursor-pointer hover:text-red-500 hover:underline pt-1 pr-2'>
|
||||
<Link href={{ pathname: comment.url, hash: comment.objectId, query: { target: 'comment' } }}>--{comment.nick}</Link>
|
||||
</div>
|
||||
</div>)}
|
||||
|
||||
</Card>
|
||||
);
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default HexoRecentComments
|
||||
|
||||
@@ -47,7 +47,7 @@ const PaginationNumber = ({ page, totalPage }) => {
|
||||
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function getPageElement(page, currentPage, pagePrefix) {
|
||||
@@ -66,7 +66,7 @@ function getPageElement(page, currentPage, pagePrefix) {
|
||||
{page}
|
||||
|
||||
</Link>)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function generatePages(pagePrefix, page, currentPage, totalPage) {
|
||||
|
||||
@@ -51,9 +51,15 @@ const LayoutBase = props => {
|
||||
{headerSlot}
|
||||
|
||||
<main id="wrapper" className="flex-1 w-full py-8 md:px-8 lg:px-24 relative">
|
||||
{/* 嵌入区域 */}
|
||||
<div id="container-slot" className={`w-full max-w-6xl ${props?.post && ' lg:max-w-3xl 2xl:max-w-4xl '} mt-6 px-3 mx-auto lg:flex lg:space-x-4 justify-center relative z-10`}>
|
||||
{props.containerSlot}
|
||||
</div>
|
||||
|
||||
<div id="container-inner" className="w-full max-w-6xl mx-auto lg:flex lg:space-x-4 justify-center relative z-10">
|
||||
{onLoading ? <LoadingCover /> : children}
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
{/* 左下角悬浮 */}
|
||||
@@ -61,15 +67,12 @@ const LayoutBase = props => {
|
||||
<Live2D />
|
||||
</div>
|
||||
|
||||
<div className="bottom-40 right-2 fixed justify-end z-20">
|
||||
<FloatDarkModeButton />
|
||||
</div>
|
||||
|
||||
{/* 右下角悬浮 */}
|
||||
<div className={ (show ? ' opacity-100 fixed ' : ' hidden opacity-0 ') + ' transition-all duration-200 bottom-12 right-2 justify-end z-20' }>
|
||||
<div className= ' justify-center flex flex-col items-center cursor-pointer '>
|
||||
<JumpToTopButton />
|
||||
</div>
|
||||
<div className="bottom-40 right-2 fixed justify-end space-y-2 z-20">
|
||||
<FloatDarkModeButton />
|
||||
<JumpToTopButton />
|
||||
{/* 可扩展的右下角悬浮 */}
|
||||
{props.floatRightBottom}
|
||||
</div>
|
||||
|
||||
<Footer title={siteInfo?.title || BLOG.TITLE} />
|
||||
|
||||
@@ -5,9 +5,10 @@ import Header from './components/Header'
|
||||
import CONFIG_MATERY from './config_matery'
|
||||
import LayoutBase from './LayoutBase'
|
||||
import React from 'react'
|
||||
import Announcement from './components/Announcement'
|
||||
|
||||
export const LayoutIndex = (props) => {
|
||||
return <LayoutBase {...props} headerSlot={CONFIG_MATERY.HOME_BANNER_ENABLE && <Header {...props} />}>
|
||||
return <LayoutBase {...props} containerSlot={ <Announcement {...props}/>} headerSlot={CONFIG_MATERY.HOME_BANNER_ENABLE && <Header {...props} />}>
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import { ArticleInfo } from './components/ArticleInfo'
|
||||
import Catalog from './components/Catalog'
|
||||
import JumpToCommentButton from './components/JumpToCommentButton'
|
||||
import throttle from 'lodash.throttle'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
import Announcement from './components/Announcement'
|
||||
|
||||
export const LayoutSlug = props => {
|
||||
const { post, lock, validPassword } = props
|
||||
@@ -24,6 +26,7 @@ export const LayoutSlug = props => {
|
||||
switchShow(shouldShow)
|
||||
}
|
||||
}, throttleMs))
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
@@ -43,84 +46,82 @@ export const LayoutSlug = props => {
|
||||
{...props}
|
||||
showCategory={false}
|
||||
showTag={false}
|
||||
floatRightBottom={<JumpToCommentButton />}
|
||||
>
|
||||
|
||||
<div id='inner-wrapper'>
|
||||
<div className={'w-full lg:max-w-3xl 2xl:max-w-4xl'}>
|
||||
<div className="-mt-32 rounded-md mx-3 lg:border lg:rounded-xl lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
|
||||
{lock && <ArticleLock validPassword={validPassword} />}
|
||||
<div id='inner-wrapper' className={'w-full lg:max-w-3xl 2xl:max-w-4xl'} >
|
||||
{/* 文章主体卡片 */}
|
||||
<div className="-mt-32 transition-all duration-300 rounded-md mx-3 lg:border lg:rounded-xl lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
|
||||
{lock && <ArticleLock validPassword={validPassword} />}
|
||||
|
||||
{!lock && <div id="container" className="overflow-x-auto md:w-full px-3 ">
|
||||
{post?.type && post?.type === 'Post' && <>
|
||||
<div
|
||||
data-aos="fade-down"
|
||||
data-aos-duration="100"
|
||||
data-aos-once="false"
|
||||
data-aos-anchor-placement="top-center"
|
||||
className='px-10'>
|
||||
<ArticleInfo post={post} />
|
||||
</div>
|
||||
<hr />
|
||||
</>}
|
||||
|
||||
<div className='lg:px-10 subpixel-antialiased'>
|
||||
<article itemScope >
|
||||
{/* Notion文章主体 */}
|
||||
<section id='notion-article' className='justify-center mx-auto max-w-2xl lg:max-w-full'>
|
||||
{post && <NotionPage post={post} />}
|
||||
</section>
|
||||
|
||||
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
|
||||
{/* 文章内嵌广告 */}
|
||||
<ins className="adsbygoogle"
|
||||
style={{ display: 'block', textAlign: 'center' }}
|
||||
data-adtest="on"
|
||||
data-ad-layout="in-article"
|
||||
data-ad-format="fluid"
|
||||
data-ad-client="ca-pub-2708419466378217"
|
||||
data-ad-slot="3806269138" />
|
||||
</section>
|
||||
|
||||
{/* 文章版权说明 */}
|
||||
{post.type === 'Post' && <ArticleCopyright {...props} />}
|
||||
|
||||
</article>
|
||||
|
||||
<hr className='border-dashed' />
|
||||
|
||||
{/* 评论互动 */}
|
||||
<div className="overflow-x-auto dark:bg-hexo-black-gray px-3">
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
{!lock && <div id="container" className="overflow-x-auto md:w-full px-3 ">
|
||||
{post?.type && post?.type === 'Post' && <>
|
||||
<div
|
||||
data-aos="fade-down"
|
||||
data-aos-duration="100"
|
||||
data-aos-once="false"
|
||||
data-aos-anchor-placement="top-center"
|
||||
className='px-10'>
|
||||
<ArticleInfo post={post} />
|
||||
</div>
|
||||
<hr />
|
||||
</>}
|
||||
|
||||
</div>}
|
||||
</div>
|
||||
<div className='lg:px-10 subpixel-antialiased'>
|
||||
<article itemScope >
|
||||
{/* Notion文章主体 */}
|
||||
<section id='notion-article' className='justify-center mx-auto max-w-2xl lg:max-w-full'>
|
||||
{post && <NotionPage post={post} />}
|
||||
</section>
|
||||
|
||||
{/* 文章推荐 */}
|
||||
{post.type === 'Post' && <ArticleAdjacent {...props} />}
|
||||
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
|
||||
{/* 文章内嵌广告 */}
|
||||
<ins className="adsbygoogle"
|
||||
style={{ display: 'block', textAlign: 'center' }}
|
||||
data-adtest="on"
|
||||
data-ad-layout="in-article"
|
||||
data-ad-format="fluid"
|
||||
data-ad-client="ca-pub-2708419466378217"
|
||||
data-ad-slot="3806269138" />
|
||||
</section>
|
||||
{/* 分享 */}
|
||||
<ShareBar post={post} />
|
||||
{/* 文章版权说明 */}
|
||||
{post.type === 'Post' && <ArticleCopyright {...props} />}
|
||||
|
||||
{/* 文章目录 */}
|
||||
{post?.toc?.length > 0 && <div id='toc-wrapper' style={{ zIndex: '-1' }} className='absolute top-0 w-full h-full xl:block hidden lg:max-w-3xl 2xl:max-w-4xl' >
|
||||
<div data-aos-delay="200"
|
||||
data-aos="fade-down"
|
||||
data-aos-duration="200"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-center"
|
||||
className='relative h-full'>
|
||||
<div className='float-right xl:-mr-72 xl:w-72 w-56 -mr-56 h-full mt-40'>
|
||||
<div className='sticky top-24'>
|
||||
<Catalog toc={post.toc} />
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<hr className='border-dashed' />
|
||||
|
||||
{/* 评论互动 */}
|
||||
<div className="overflow-x-auto dark:bg-hexo-black-gray px-3">
|
||||
<Comment frontMatter={post} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
|
||||
<div className='fixed bottom-28 right-4'>
|
||||
<JumpToCommentButton />
|
||||
</div>
|
||||
{/* 底部文章推荐 */}
|
||||
{post.type === 'Post' && <ArticleAdjacent {...props} />}
|
||||
|
||||
<Announcement {...props}/>
|
||||
|
||||
{/* 右侧文章目录 */}
|
||||
{post?.toc?.length > 0 && <div id='toc-wrapper' style={{ zIndex: '-1' }} className='absolute top-0 w-full h-full xl:block hidden lg:max-w-3xl 2xl:max-w-4xl' >
|
||||
<div data-aos-delay="200"
|
||||
data-aos="fade-down"
|
||||
data-aos-duration="200"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-center"
|
||||
className='relative h-full'>
|
||||
<div className='float-right xl:-mr-72 xl:w-72 w-56 -mr-56 h-full mt-40'>
|
||||
<div className='sticky top-24'>
|
||||
<Catalog toc={post.toc} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
29
themes/matery/components/Announcement.js
Normal file
29
themes/matery/components/Announcement.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const NotionPage = dynamic(() => import('@/components/NotionPage'))
|
||||
|
||||
const Announcement = ({ notice }) => {
|
||||
const { locale } = useGlobal()
|
||||
if (!notice) {
|
||||
return <></>
|
||||
}
|
||||
return <div className="px-3 w-full">
|
||||
<div
|
||||
data-aos="zoom-in"
|
||||
data-aos-duration="500"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className="mb-4 p-2 overflow-auto shadow-md border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray">
|
||||
<div className="text-sm flex flex-nowrap justify-between">
|
||||
<div className="font-light text-gray-600 dark:text-gray-200">
|
||||
<i className="mx-2 fas fa-bullhorn" />{locale.COMMON.ANNOUNCEMENT}
|
||||
</div>
|
||||
</div>
|
||||
{notice && (<div id="announcement-content">
|
||||
<NotionPage post={notice} className='text-center ' />
|
||||
</div>)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
export default Announcement
|
||||
@@ -3,6 +3,7 @@ import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import TagItemMini from './TagItemMini'
|
||||
import CONFIG_MATERY from '../config_matery'
|
||||
import TwikooCommentCount from '@/components/TwikooCommentCount'
|
||||
// import Image from 'next/image'
|
||||
|
||||
const BlogPostCard = ({ index, post, showSummary, siteInfo }) => {
|
||||
@@ -14,84 +15,87 @@ const BlogPostCard = ({ index, post, showSummary, siteInfo }) => {
|
||||
const showPageCover = CONFIG_MATERY.POST_LIST_COVER && post?.page_cover
|
||||
const delay = (index % 3) * 300
|
||||
return (
|
||||
<div
|
||||
data-aos="zoom-in"
|
||||
data-aos-duration="500"
|
||||
data-aos-delay={delay}
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className="w-full mb-4 overflow-auto shadow-md border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray">
|
||||
<div
|
||||
data-aos="zoom-in"
|
||||
data-aos-duration="500"
|
||||
data-aos-delay={delay}
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className="w-full mb-4 overflow-auto shadow-md border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray">
|
||||
|
||||
{/* 固定高度 ,空白用图片拉升填充 */}
|
||||
<div className="flex flex-col h-80 justify-between">
|
||||
{/* 固定高度 ,空白用图片拉升填充 */}
|
||||
<div className="flex flex-col h-80 justify-between">
|
||||
|
||||
{/* 头部图片 填充卡片 */}
|
||||
{showPageCover && (
|
||||
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
|
||||
<div
|
||||
className="flex flex-grow w-full relative duration-200 bg-black rounded-t-md cursor-pointer transform overflow-hidden">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={post?.page_cover}
|
||||
alt={post.title}
|
||||
className="opacity-50 h-full w-full hover:scale-125 rounded-t-md transform object-cover duration-500"
|
||||
/>
|
||||
<span className='absolute bottom-0 left-0 text-white p-6 text-2xl replace break-words w-full' > {post.title}</span>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
{/* 头部图片 填充卡片 */}
|
||||
{showPageCover && (
|
||||
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
|
||||
<div
|
||||
className="flex flex-grow w-full relative duration-200 bg-black rounded-t-md cursor-pointer transform overflow-hidden">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={post?.page_cover}
|
||||
alt={post.title}
|
||||
className="opacity-50 h-full w-full hover:scale-125 rounded-t-md transform object-cover duration-500"
|
||||
/>
|
||||
<span className='absolute bottom-0 left-0 text-white p-6 text-2xl replace break-words w-full' > {post.title}</span>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{/* 文字描述 */}
|
||||
<div >
|
||||
{/* 描述 */}
|
||||
<div className="px-4 flex flex-col w-full text-gray-700 dark:text-gray-300">
|
||||
{/* 文字描述 */}
|
||||
<div >
|
||||
{/* 描述 */}
|
||||
<div className="px-4 flex flex-col w-full text-gray-700 dark:text-gray-300">
|
||||
|
||||
{(!showPreview || showSummary) && post.summary && (
|
||||
<p style={{ overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: '4', WebkitBoxOrient: 'vertical' }}
|
||||
className="replace my-2 text-sm font-light leading-7">
|
||||
{post.summary}
|
||||
</p>
|
||||
)}
|
||||
{(!showPreview || showSummary) && post.summary && (
|
||||
<p style={{ overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: '4', WebkitBoxOrient: 'vertical' }}
|
||||
className="replace my-2 text-sm font-light leading-7">
|
||||
{post.summary}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className='text-gray-800 justify-between flex my-2 dark:text-gray-300'>
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
className="font-light hover:underline cursor-pointer text-sm leading-4 mr-3">
|
||||
<div className='text-gray-800 justify-between flex my-2 dark:text-gray-300'>
|
||||
<div>
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
className="font-light hover:underline cursor-pointer text-sm leading-4 mr-3">
|
||||
|
||||
<i className="far fa-clock mr-1" />
|
||||
{post.date?.start_date || post.lastEditedTime}
|
||||
<i className="far fa-clock mr-1" />
|
||||
{post.date?.start_date || post.lastEditedTime}
|
||||
|
||||
</Link>
|
||||
<Link
|
||||
href={`/category/${post.category}`}
|
||||
passHref
|
||||
className="cursor-pointer font-light text-sm hover:underline hover:text-indigo-700 dark:hover:text-indigo-400 transform">
|
||||
</Link>
|
||||
<TwikooCommentCount post={post} className='hover:underline cursor-pointer text-sm'/>
|
||||
</div>
|
||||
<Link
|
||||
href={`/category/${post.category}`}
|
||||
passHref
|
||||
className="cursor-pointer font-light text-sm hover:underline hover:text-indigo-700 dark:hover:text-indigo-400 transform">
|
||||
|
||||
<i className="mr-1 far fa-folder" />
|
||||
{post.category}
|
||||
<i className="mr-1 far fa-folder" />
|
||||
{post.category}
|
||||
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{post?.tagItems && post?.tagItems.length > 0 && (<>
|
||||
<hr />
|
||||
<div className="text-gray-400 justify-between flex px-5 py-3">
|
||||
<div className="md:flex-nowrap flex-wrap md:justify-start inline-block">
|
||||
<div>
|
||||
{' '}
|
||||
{post.tagItems.map(tag => (
|
||||
<TagItemMini key={tag.name} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>)}
|
||||
</div>
|
||||
</div>
|
||||
{post?.tagItems && post?.tagItems.length > 0 && (<>
|
||||
<hr />
|
||||
<div className="text-gray-400 justify-between flex px-5 py-3">
|
||||
<div className="md:flex-nowrap flex-wrap md:justify-start inline-block">
|
||||
<div>
|
||||
{' '}
|
||||
{post.tagItems.map(tag => (
|
||||
<TagItemMini key={tag.name} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
|
||||
<div id="container" className='w-full'>
|
||||
<div className='pt-6'></div>
|
||||
{/* 文章列表 */}
|
||||
<div className="pt-4 flex flex-wrap pb-24" >
|
||||
<div className="pt-4 flex flex-wrap pb-12" >
|
||||
{posts.map(post => (
|
||||
<div key={post.id} className='xl:w-1/3 md:w-1/2 w-full p-4'> <BlogPostCard index={posts.indexOf(post)} post={post} siteInfo={siteInfo} /></div>
|
||||
))}
|
||||
|
||||
@@ -57,7 +57,7 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_MA
|
||||
return <div id='container' ref={targetRef} className='w-full'>
|
||||
|
||||
{/* 文章列表 */}
|
||||
<div className="pt-4 flex flex-wrap pb-24" >
|
||||
<div className="pt-4 flex flex-wrap pb-12" >
|
||||
{postsToShow.map(post => (
|
||||
<div key={post.id} className='xl:w-1/3 md:w-1/2 w-full p-4'>
|
||||
<BlogPostCard index={posts.indexOf(post)} post={post} siteInfo={siteInfo} />
|
||||
|
||||
@@ -21,9 +21,8 @@ export default function FloatDarkModeButton() {
|
||||
|
||||
return (
|
||||
<div className={'justify-center items-center text-center' } onClick={handleChangeDarkMode}>
|
||||
<i id="darkModeButton"
|
||||
className={`${isDarkMode ? 'fa-sun' : 'fa-moon'} fas transform hover:scale-105 duration-200
|
||||
text-2xl text-white bg-indigo-700 px-3 py-2.5 rounded-full dark:bg-black cursor-pointer`} />
|
||||
<i id="darkModeButton" className={`${isDarkMode ? 'fa-sun' : 'fa-moon'} fas transform hover:scale-105 duration-200
|
||||
text-white bg-indigo-700 w-10 h-10 py-2.5 rounded-full dark:bg-black cursor-pointer`} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react'
|
||||
import Typed from 'typed.js'
|
||||
import CONFIG_MATERY from '../config_matery'
|
||||
import throttle from 'lodash.throttle'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
let wrapperTop = 0
|
||||
let windowTop = 0
|
||||
@@ -17,6 +18,8 @@ const throttleMs = 200
|
||||
const Header = props => {
|
||||
const [typed, changeType] = useState()
|
||||
const { siteInfo } = props
|
||||
const { locale } = useGlobal()
|
||||
|
||||
useEffect(() => {
|
||||
scrollTrigger()
|
||||
updateHeaderHeight()
|
||||
@@ -96,8 +99,8 @@ const Header = props => {
|
||||
<span id='typed' />
|
||||
</div>
|
||||
<div onClick={() => { window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) }}
|
||||
className="mt-12 border cursor-pointer w-40 text-center pt-4 pb-3 text-md text-white hover:bg-orange-600 duration-300 rounded-3xl">
|
||||
<i className='animate-bounce fas fa-angle-double-down' /> <span>开始阅读</span>
|
||||
className="mt-12 border cursor-pointer w-40 text-center pt-4 pb-3 text-md text-white hover:bg-orange-600 duration-300 rounded-3xl z-40">
|
||||
<i className='animate-bounce fas fa-angle-double-down' /> <span>{locale.COMMON.START_READING}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,11 +17,10 @@ const JumpToCommentButton = () => {
|
||||
}
|
||||
}
|
||||
|
||||
return (<div
|
||||
onClick={navToComment}
|
||||
className='flex space-x-1 items-center justify-center cursor-pointer transform hover:scale-105 duration-200 w-7 h-7 text-center'>
|
||||
<i className='fas fa-comments text-xl text-white bg-indigo-700 py-3 px-2 rounded-full' />
|
||||
</div>)
|
||||
return <div className={'justify-center items-center text-center'} onClick={navToComment}>
|
||||
<i id="darkModeButton" className={`fas fa-comments transform hover:scale-105 duration-200 text-white
|
||||
text-sm bg-indigo-700 w-10 h-10 rounded-full dark:bg-black cursor-pointer py-3`} />
|
||||
</div>
|
||||
}
|
||||
|
||||
export default JumpToCommentButton
|
||||
|
||||
@@ -16,11 +16,14 @@ const JumpToTopButton = ({ showPercent = true, percent }) => {
|
||||
if (!CONFIG_MATERY.WIDGET_TO_TOP) {
|
||||
return <></>
|
||||
}
|
||||
return (<div className=' drop-shadow-md space-x-1 items-center justify-center transform hover:scale-105 duration-200 px-3 py-2 text-center text-white bg-indigo-700 dark:bg-hexo-black-gray rounded-full'
|
||||
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} >
|
||||
<div title={locale.POST.TOP} ><i className='fas fa-arrow-up text-2xl rounded-full' /></div>
|
||||
{showPercent && (<div className='text-md hidden lg:block'>{percent}</div>)}
|
||||
</div>)
|
||||
|
||||
return <div data-aos="fade-left"
|
||||
data-aos-duration="300"
|
||||
data-aos-anchor-placement="top-center"
|
||||
className={'justify-center items-center text-center'} onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} >
|
||||
<i id="darkModeButton" title={locale.POST.TOP} className={`fas fa-arrow-up transform hover:scale-105 duration-200 text-white
|
||||
bg-indigo-700 w-10 h-10 rounded-full dark:bg-black cursor-pointer py-2.5`} />
|
||||
</div>
|
||||
}
|
||||
|
||||
export default JumpToTopButton
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
@@ -13,6 +12,7 @@ const PaginationSimple = ({ page, totalPage }) => {
|
||||
const router = useRouter()
|
||||
const currentPage = +page
|
||||
const showNext = currentPage < totalPage
|
||||
const pagePrefix = router.asPath.replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
|
||||
|
||||
return (
|
||||
<div className="my-10 mx-6 flex justify-between font-medium text-black dark:text-gray-100 space-x-2">
|
||||
@@ -20,8 +20,8 @@ const PaginationSimple = ({ page, totalPage }) => {
|
||||
href={{
|
||||
pathname:
|
||||
currentPage - 1 === 1
|
||||
? `${BLOG.SUB_PATH || '/'}`
|
||||
: `/page/${currentPage - 1}`,
|
||||
? `${pagePrefix}/`
|
||||
: `${pagePrefix}/page/${currentPage - 1}`,
|
||||
query: router.query.s ? { s: router.query.s } : {}
|
||||
}}
|
||||
passHref
|
||||
@@ -36,7 +36,7 @@ const PaginationSimple = ({ page, totalPage }) => {
|
||||
</Link>
|
||||
<Link
|
||||
href={{
|
||||
pathname: `/page/${currentPage + 1}`,
|
||||
pathname: `${pagePrefix}/page/${currentPage + 1}`,
|
||||
query: router.query.s ? { s: router.query.s } : {}
|
||||
}}
|
||||
passHref
|
||||
|
||||
@@ -13,6 +13,7 @@ import ArticleAround from './components/ArticleAround'
|
||||
import TocDrawer from './components/TocDrawer'
|
||||
import CategoryItem from './components/CategoryItem'
|
||||
import TagItemMini from './components/TagItemMini'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
|
||||
export const LayoutSlug = props => {
|
||||
const { post, prev, next, siteInfo, lock, validPassword } = props
|
||||
@@ -85,12 +86,17 @@ export const LayoutSlug = props => {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
{/* 分享 */}
|
||||
<ShareBar post={post} />
|
||||
{/* 文章分类和标签信息 */}
|
||||
<div className='flex justify-between'>
|
||||
{CONFIG_MEDIUM.POST_DETAIL_CATEGORY && post.category && <CategoryItem category={post.category} />}
|
||||
<div>
|
||||
{CONFIG_MEDIUM.POST_DETAIL_TAG && post?.tagItems?.map(tag => <TagItemMini key={tag.name} tag={tag} />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{post.type === 'Post' && <ArticleAround prev={prev} next={next} />}
|
||||
<Comment frontMatter={post} />
|
||||
</section>
|
||||
|
||||
@@ -6,6 +6,7 @@ import React from 'react'
|
||||
import CONFIG_MEDIUM from '../config_medium'
|
||||
import CategoryItem from './CategoryItem'
|
||||
import TagItemMini from './TagItemMini'
|
||||
import TwikooCommentCount from '@/components/TwikooCommentCount'
|
||||
|
||||
const BlogPostCard = ({ post, showSummary }) => {
|
||||
const showPreview = CONFIG_MEDIUM.POST_LIST_PREVIEW && post.blockMap
|
||||
@@ -43,13 +44,9 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
}
|
||||
>
|
||||
<div className="text-sm py-1">{post.date?.start_date}</div>
|
||||
{CONFIG_MEDIUM.POST_LIST_CATEGORY && (
|
||||
<CategoryItem category={post.category} />
|
||||
)}
|
||||
{CONFIG_MEDIUM.POST_LIST_TAG &&
|
||||
post?.tagItems?.map(tag => (
|
||||
<TagItemMini key={tag.name} tag={tag} />
|
||||
))}
|
||||
{CONFIG_MEDIUM.POST_LIST_CATEGORY && <CategoryItem category={post.category} />}
|
||||
{CONFIG_MEDIUM.POST_LIST_TAG && post?.tagItems?.map(tag => <TagItemMini key={tag.name} tag={tag} />)}
|
||||
<TwikooCommentCount post={post} className='hover:underline'/>
|
||||
</div>
|
||||
|
||||
<div className="flex"></div>
|
||||
|
||||
@@ -48,7 +48,7 @@ const PaginationSimple = ({ page, totalPage }) => {
|
||||
{locale.PAGINATION.NEXT}→
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default PaginationSimple
|
||||
|
||||
@@ -8,8 +8,7 @@ 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 from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import CONFIG_NEXT from './config_next'
|
||||
import Live2D from '@/components/Live2D'
|
||||
import BLOG from '@/blog.config'
|
||||
@@ -22,12 +21,13 @@ import BLOG from '@/blog.config'
|
||||
const LayoutBase = (props) => {
|
||||
const { children, headerSlot, meta, sideBarSlot, floatSlot, rightAreaSlot, siteInfo } = props
|
||||
const { onLoading } = useGlobal()
|
||||
const targetRef = React.useRef(null)
|
||||
const floatButtonGroup = React.useRef(null)
|
||||
const targetRef = useRef(null)
|
||||
const floatButtonGroup = useRef(null)
|
||||
const leftAreaSlot = <Live2D/>
|
||||
|
||||
const [show, switchShow] = React.useState(false)
|
||||
const [percent, changePercent] = React.useState(0) // 页面阅读百分比
|
||||
const [showRightFloat, switchShow] = useState(false)
|
||||
const [percent, changePercent] = useState(0) // 页面阅读百分比
|
||||
|
||||
const scrollListener = () => {
|
||||
const targetRef = document.getElementById('wrapper')
|
||||
const clientHeight = targetRef?.clientHeight
|
||||
@@ -37,13 +37,13 @@ const LayoutBase = (props) => {
|
||||
if (per > 100) per = 100
|
||||
const shouldShow = scrollY > 100 && per > 0
|
||||
|
||||
if (shouldShow !== show) {
|
||||
if (shouldShow !== showRightFloat) {
|
||||
switchShow(shouldShow)
|
||||
}
|
||||
changePercent(per)
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
// facebook messenger 插件需要调整右下角悬浮按钮的高度
|
||||
const fb = document.getElementsByClassName('fb-customerchat')
|
||||
if (fb.length === 0) {
|
||||
@@ -54,7 +54,7 @@ const LayoutBase = (props) => {
|
||||
|
||||
document.addEventListener('scroll', scrollListener)
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
}, [showRightFloat])
|
||||
|
||||
return (<div id='theme-next'>
|
||||
|
||||
@@ -78,7 +78,7 @@ const LayoutBase = (props) => {
|
||||
|
||||
{/* 右下角悬浮 */}
|
||||
<div ref={floatButtonGroup} className='right-8 bottom-12 lg:right-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 '}>
|
||||
<div className={(showRightFloat ? '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/>
|
||||
@@ -91,8 +91,4 @@ const LayoutBase = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
LayoutBase.propTypes = {
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
export default LayoutBase
|
||||
|
||||
@@ -14,7 +14,11 @@ const Announcement = ({ post, className }) => {
|
||||
<i className="mr-2 fas fa-bullhorn" />{locale.COMMON.ANNOUNCEMENT}
|
||||
</div>
|
||||
</div>
|
||||
{post && (<div id="announcement-content">
|
||||
{post && (<div id="announcement-content" data-aos="fade-down"
|
||||
data-aos-duration="500"
|
||||
data-aos-delay="200"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom">
|
||||
<NotionPage post={post} className='text-center ' />
|
||||
</div>)}
|
||||
</>
|
||||
|
||||
@@ -2,7 +2,7 @@ import BLOG from '@/blog.config'
|
||||
import BlogAround from './BlogAround'
|
||||
import Comment from '@/components/Comment'
|
||||
import RecommendPosts from './RecommendPosts'
|
||||
import ShareBar from './ShareBar'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
import TagItem from './TagItem'
|
||||
import formatDate from '@/lib/formatDate'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
@@ -94,6 +94,10 @@ export default function ArticleDetail(props) {
|
||||
</section>
|
||||
|
||||
{showArticleInfo && <>
|
||||
|
||||
{/* 分享 */}
|
||||
<ShareBar post={post} />
|
||||
|
||||
{/* 版权声明 */}
|
||||
{post.type === 'Post' && <ArticleCopyright author={BLOG.AUTHOR} url={url} />}
|
||||
|
||||
@@ -124,7 +128,6 @@ export default function ArticleDetail(props) {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<ShareBar post={post} />
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -8,6 +8,7 @@ import TagItemMini from './TagItemMini'
|
||||
import CONFIG_NEXT from '../config_next'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import NotionIcon from '@/components/NotionIcon'
|
||||
import TwikooCommentCount from '@/components/TwikooCommentCount'
|
||||
|
||||
const BlogPostCard = ({ post, showSummary }) => {
|
||||
const { locale } = useGlobal()
|
||||
@@ -22,6 +23,10 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
<Link
|
||||
href={`${BLOG.SUB_PATH}/${post.slug}`}
|
||||
passHref
|
||||
data-aos="fade-down"
|
||||
data-aos-duration="500"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className={`cursor-pointer hover:underline text-3xl ${showPreview ? 'text-center' : ''
|
||||
} leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}>
|
||||
|
||||
@@ -29,17 +34,20 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
|
||||
</Link>
|
||||
|
||||
<div
|
||||
className={`flex mt-2 items-center ${showPreview ? 'justify-center' : 'justify-start'
|
||||
} flex-wrap dark:text-gray-500 text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 `}
|
||||
>
|
||||
<div data-aos="fade-down"
|
||||
data-aos-duration="500"
|
||||
data-aos-delay="100"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className={`flex mt-2 items-center ${showPreview ? 'justify-center' : 'justify-start'} flex-wrap dark:text-gray-500 text-gray-400 `}>
|
||||
|
||||
<div>
|
||||
{post.category && (
|
||||
<>
|
||||
<Link
|
||||
href={`/category/${post.category}`}
|
||||
passHref
|
||||
className="cursor-pointer font-light text-sm hover:underline transform">
|
||||
className="hover:text-blue-500 dark:hover:text-blue-400 cursor-pointer font-light text-sm hover:underline transform">
|
||||
|
||||
<i className="mr-1 fas fa-folder" />
|
||||
{post.category}
|
||||
@@ -48,27 +56,29 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
<span className="mx-2">|</span>
|
||||
</>
|
||||
)}
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
className="font-light hover:underline cursor-pointer text-sm leading-4 mr-3">
|
||||
|
||||
{post.date?.start_date}
|
||||
|
||||
</Link>
|
||||
<Link
|
||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||
passHref
|
||||
className="hover:text-blue-500 dark:hover:text-blue-400 font-light hover:underline cursor-pointer text-sm leading-4 mr-3">
|
||||
{post.date?.start_date}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="md:flex-nowrap flex-wrap md:justify-start inline-block">
|
||||
<div>
|
||||
{' '}
|
||||
{post.tagItems.map(tag => (
|
||||
<TwikooCommentCount post={post} className='hover:text-blue-500 dark:hover:text-blue-400 hover:underline text-sm'/>
|
||||
|
||||
<div className="hover:text-blue-500 dark:hover:text-blue-400 md:flex-nowrap flex-wrap md:justify-start inline-block">
|
||||
{post.tagItems?.map(tag => (
|
||||
<TagItemMini key={tag.name} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(!showPreview || showSummary) && !post.results && (
|
||||
<p className="mt-4 mb-24 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||
<p data-aos="fade-down"
|
||||
data-aos-duration="500"
|
||||
data-aos-delay="100"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className="mt-4 mb-12 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||
{post.summary}
|
||||
</p>
|
||||
)}
|
||||
@@ -83,7 +93,11 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
)}
|
||||
|
||||
{showPreview && post?.blockMap && (
|
||||
<div className="overflow-ellipsis truncate">
|
||||
<div data-aos="fade-down"
|
||||
data-aos-duration="500"
|
||||
data-aos-delay="100"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"className="overflow-ellipsis truncate">
|
||||
<NotionPage post={post} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
const Card = ({ children, headerSlot, className }) => {
|
||||
return <div
|
||||
data-aos="fade-down"
|
||||
data-aos-duration="300"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className={className}>
|
||||
const Card = (props) => {
|
||||
const { children, headerSlot } = props
|
||||
return <div {...props}>
|
||||
<>{headerSlot}</>
|
||||
<section className="shadow px-2 py-4 bg-white dark:bg-hexo-black-gray hover:shadow-xl duration-200">
|
||||
{children}
|
||||
|
||||
@@ -4,12 +4,21 @@ import React from 'react'
|
||||
const Logo = props => {
|
||||
const { siteInfo, className } = props
|
||||
return (
|
||||
<Link href='/' passHref legacyBehavior>
|
||||
<div className={'flex flex-col justify-center items-center cursor-pointer bg-black dark:bg-gray-800 space-y-3 font-bold ' + className}>
|
||||
<div className='font-serif text-xl text-white'> {siteInfo?.title}</div>
|
||||
<div className='text-sm text-gray-300 font-light text-center'> {siteInfo?.description}</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
<Link href='/' passHref legacyBehavior>
|
||||
<div className={'flex flex-col justify-center items-center cursor-pointer bg-black dark:bg-gray-800 space-y-3 font-bold ' + className}>
|
||||
<div data-aos="fade-down"
|
||||
data-aos-duration="500"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className='font-serif text-xl text-white'> {siteInfo?.title}</div>
|
||||
<div data-aos="fade-down"
|
||||
data-aos-duration="500"
|
||||
data-aos-delay="300"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className='text-sm text-gray-300 font-light text-center'> {siteInfo?.description}</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
export default Logo
|
||||
|
||||
@@ -34,7 +34,12 @@ export const MenuList = (props) => {
|
||||
return (
|
||||
<>
|
||||
{/* 大屏模式菜单 */}
|
||||
<nav id='nav' className='hidden md:block leading-8 text-gray-500 dark:text-gray-400 font-sans'>
|
||||
<nav id='nav' data-aos="fade-down"
|
||||
data-aos-duration="500"
|
||||
data-aos-delay="400"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className='hidden md:block leading-8 text-gray-500 dark:text-gray-400 font-sans'>
|
||||
{links.map(link => link && link.show && <MenuItemDrop key={link?.to} link={link} />)}
|
||||
</nav>
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
@@ -14,6 +13,8 @@ const PaginationSimple = ({ page, showNext }) => {
|
||||
const { locale } = useGlobal()
|
||||
const router = useRouter()
|
||||
const currentPage = +page
|
||||
const pagePrefix = router.asPath.replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
|
||||
|
||||
return (
|
||||
<div
|
||||
data-aos="fade-down"
|
||||
@@ -25,8 +26,8 @@ const PaginationSimple = ({ page, showNext }) => {
|
||||
href={{
|
||||
pathname:
|
||||
currentPage - 1 === 1
|
||||
? `${BLOG.SUB_PATH || '/'}`
|
||||
: `/page/${currentPage - 1}`,
|
||||
? `${pagePrefix}/`
|
||||
: `${pagePrefix}/page/${currentPage - 1}`,
|
||||
query: router.query.s ? { s: router.query.s } : {}
|
||||
}}
|
||||
passHref
|
||||
|
||||
@@ -41,11 +41,11 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
||||
searchInputRef.current.value = ''
|
||||
setShowClean(false)
|
||||
}
|
||||
function lockSearchInput () {
|
||||
function lockSearchInput() {
|
||||
lock = true
|
||||
}
|
||||
|
||||
function unLockSearchInput () {
|
||||
function unLockSearchInput() {
|
||||
lock = false
|
||||
}
|
||||
const [showClean, setShowClean] = useState(false)
|
||||
@@ -61,31 +61,36 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
||||
}
|
||||
}
|
||||
|
||||
return <div className='flex w-full bg-gray-100'>
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type='text'
|
||||
placeholder={currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`}
|
||||
className={'outline-none w-full text-sm pl-4 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-800 dark:text-white'}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={lockSearchInput}
|
||||
onCompositionUpdate={lockSearchInput}
|
||||
onCompositionEnd={unLockSearchInput}
|
||||
onChange={e => updateSearchKey(e.target.value)}
|
||||
defaultValue={currentSearch || ''}
|
||||
/>
|
||||
return <div data-aos="fade-down"
|
||||
data-aos-duration="500"
|
||||
data-aos-delay="200"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className='flex w-full bg-gray-100'>
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type='text'
|
||||
placeholder={currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`}
|
||||
className={'outline-none w-full text-sm pl-4 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-800 dark:text-white'}
|
||||
onKeyUp={handleKeyUp}
|
||||
onCompositionStart={lockSearchInput}
|
||||
onCompositionUpdate={lockSearchInput}
|
||||
onCompositionEnd={unLockSearchInput}
|
||||
onChange={e => updateSearchKey(e.target.value)}
|
||||
defaultValue={currentSearch || ''}
|
||||
/>
|
||||
|
||||
<div className='-ml-8 cursor-pointer float-right items-center justify-center py-2'
|
||||
onClick={handleSearch}>
|
||||
<i className={`hover:text-black transform duration-200 text-gray-500 cursor-pointer fas ${onLoading ? 'fa-spinner animate-spin' : 'fa-search'}`} />
|
||||
</div>
|
||||
<div className='-ml-8 cursor-pointer float-right items-center justify-center py-2'
|
||||
onClick={handleSearch}>
|
||||
<i className={`hover:text-black transform duration-200 text-gray-500 cursor-pointer fas ${onLoading ? 'fa-spinner animate-spin' : 'fa-search'}`} />
|
||||
</div>
|
||||
|
||||
{(showClean &&
|
||||
<div className='-ml-12 cursor-pointer dark:bg-gray-600 dark:hover:bg-gray-800 float-right items-center justify-center py-2'>
|
||||
<i className='hover:text-black transform duration-200 text-gray-400 cursor-pointer fas fa-times' onClick={cleanSearch} />
|
||||
{(showClean &&
|
||||
<div className='-ml-12 cursor-pointer dark:bg-gray-600 dark:hover:bg-gray-800 float-right items-center justify-center py-2'>
|
||||
<i className='hover:text-black transform duration-200 text-gray-400 cursor-pointer fas fa-times' onClick={cleanSearch} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default SearchInput
|
||||
|
||||
@@ -1,88 +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'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import CONFIG_NEXT from '../config_next'
|
||||
|
||||
const ShareBar = ({ post }) => {
|
||||
const router = useRouter()
|
||||
const [qrCodeShow, setQrCodeShow] = React.useState(false)
|
||||
const { locale } = useGlobal()
|
||||
if (!CONFIG_NEXT.ARTICLE_SHARE) {
|
||||
return <></>
|
||||
}
|
||||
const shareUrl = BLOG.LINK + router.asPath
|
||||
|
||||
// 二维码悬浮
|
||||
const btnRef = React.createRef()
|
||||
const popoverRef = React.createRef()
|
||||
|
||||
const openPopover = () => {
|
||||
createPopper(btnRef.current, popoverRef.current, {
|
||||
placement: 'top'
|
||||
})
|
||||
setQrCodeShow(true)
|
||||
}
|
||||
const closePopover = () => {
|
||||
setQrCodeShow(false)
|
||||
}
|
||||
|
||||
const copyUrl = () => {
|
||||
copy(shareUrl)
|
||||
alert(locale.COMMON.URL_COPIED)
|
||||
}
|
||||
|
||||
return <>
|
||||
<div
|
||||
className='py-2 text-gray-500 text-center space-x-2 flex my-1 dark:text-gray-200 overflow-visible'>
|
||||
<div className='hidden md:block text-gray-800 dark:text-gray-300 mr-2 my-2 whitespace-nowrap'>{locale.COMMON.SHARE}:</div>
|
||||
<div className='text-3xl cursor-pointer'>
|
||||
<a className='text-blue-700' href={`https://www.facebook.com/sharer.php?u=${shareUrl}`} >
|
||||
<i className='fab fa-facebook-square'/>
|
||||
</a>
|
||||
</div>
|
||||
<div className='text-3xl cursor-pointer'>
|
||||
<a className='text-blue-400' target='_blank' rel='noreferrer' href={`https://twitter.com/intent/tweet?title=${post.title}&url${shareUrl}`} >
|
||||
<i className='fab fa-twitter-square'/>
|
||||
</a>
|
||||
</div>
|
||||
<div className='text-3xl cursor-pointer'>
|
||||
<a className='text-blue-500' href={`https://telegram.me/share/url?url=${shareUrl}&text=${post.title}`} >
|
||||
<i className='fab fa-telegram'/>
|
||||
</a>
|
||||
</div>
|
||||
<div className='cursor-pointer text-2xl'>
|
||||
<a className='text-green-600' ref={btnRef} onMouseEnter={openPopover} onMouseLeave={closePopover}>
|
||||
<i className='fab fa-weixin'/>
|
||||
<div ref={popoverRef} className={(qrCodeShow ? 'opacity-100 ' : 'invisible opacity-0') + ' transition-all duration-200 text-center py-2'}>
|
||||
<div className='p-2 bg-white border-0 duration-200 transform block z-40 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'>
|
||||
{locale.COMMON.SCAN_QR_CODE}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div className='cursor-pointer text-2xl'>
|
||||
<a className='text-red-600' target='_blank' rel='noreferrer' href={`https://service.weibo.com/share/share.php?url=${shareUrl}&title=${post.title}`} >
|
||||
<i className='fab fa-weibo'/>
|
||||
</a>
|
||||
</div>
|
||||
<div className='cursor-pointer text-2xl'>
|
||||
<a className='text-blue-400' target='_blank' rel='noreferrer' href={`http://connect.qq.com/widget/shareqq/index.html?url=${shareUrl}&sharesource=qzone&title=${post.title}&desc=${post.summary}`} >
|
||||
<i className='fab fa-qq'/>
|
||||
</a>
|
||||
</div>
|
||||
<div className='cursor-pointer text-2xl'>
|
||||
<a className='text-yellow-600' onClick={copyUrl} >
|
||||
<i className='fas fa-link'/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
export default ShareBar
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react'
|
||||
import ShareBar from './ShareBar'
|
||||
|
||||
/**
|
||||
* 悬浮在屏幕右下角,分享按钮
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const ShareButton = ({ post }) => {
|
||||
const [popoverShow, setPopoverShow] = React.useState(false)
|
||||
const btnRef = React.createRef()
|
||||
|
||||
const openPopover = () => {
|
||||
setPopoverShow(true)
|
||||
}
|
||||
const closePopover = () => {
|
||||
setPopoverShow(false)
|
||||
}
|
||||
return (
|
||||
<div className='my-2'
|
||||
onMouseEnter={() => { openPopover() }}
|
||||
onMouseLeave={() => { closePopover() }}>
|
||||
<div className={(popoverShow ? 'opacity-100' : 'opacity-0') + ' duration-200 ease-in-out font-normal'}>
|
||||
<ShareBar post={post}/>
|
||||
</div>
|
||||
<div ref={btnRef}
|
||||
className='z-20 border dark:border-gray-500 dark:bg-gray-600 bg-white cursor-pointer text-md hover:shadow-2xl shadow-lg'>
|
||||
<i className='fas fa-share-alt-square transform duration-200 hover:scale-150 dark:text-gray-200 p-4' title='share' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShareButton
|
||||
@@ -26,14 +26,10 @@ const SideAreaLeft = props => {
|
||||
return <aside id='left' className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'ml-4' : 'mr-4') + ' hidden lg:block flex-col w-60 z-20 relative'}>
|
||||
|
||||
<section
|
||||
data-aos="fade-down"
|
||||
data-aos-duration="300"
|
||||
data-aos-once="false"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className='w-60'>
|
||||
{/* 菜单 */}
|
||||
<section className='shadow hidden lg:block mb-5 pb-4 bg-white dark:bg-hexo-black-gray hover:shadow-xl duration-200'>
|
||||
<Logo {...props} className='h-32' />
|
||||
<Logo className='h-32' {...props} />
|
||||
<div className='pt-2 px-2 font-sans'>
|
||||
<MenuList allowCollapse={true} {...props} />
|
||||
</div>
|
||||
|
||||
@@ -43,6 +43,7 @@ const SideAreaRight = (props) => {
|
||||
</Card>}
|
||||
|
||||
<div className="sticky top-0 space-y-4 w-full">
|
||||
|
||||
{announcementVisible && <Card>
|
||||
<Announcement post={notice} />
|
||||
</Card>}
|
||||
|
||||
@@ -30,7 +30,6 @@ const CONFIG_NEXT = {
|
||||
WIDGET_DARK_MODE: false, // 显示日间/夜间模式切换
|
||||
WIDGET_TOC: true, // 移动端显示悬浮目录
|
||||
|
||||
ARTICLE_SHARE: process.env.NEXT_PUBLIC_ARTICLE_SHARE || false, // 文章分享功能
|
||||
ARTICLE_RELATE_POSTS: true, // 相关文章推荐
|
||||
ARTICLE_COPYRIGHT: true // 文章版权声明
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
import BLOG from '@/blog.config'
|
||||
import Announcement from './components/Announcement'
|
||||
import { BlogListPage } from './components/BlogListPage'
|
||||
import { BlogListScroll } from './components/BlogListScroll'
|
||||
import LayoutBase from './LayoutBase'
|
||||
@@ -7,6 +8,7 @@ import LayoutBase from './LayoutBase'
|
||||
export const LayoutIndex = props => {
|
||||
return (
|
||||
<LayoutBase {...props}>
|
||||
<Announcement {...props} />
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
|
||||
</LayoutBase>
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import NotionPage from '@/components/NotionPage'
|
||||
import { ArticleInfo } from './components/ArticleInfo'
|
||||
import Comment from '@/components/Comment'
|
||||
import { ArticleFooter } from './components/ArticleFooter'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
|
||||
export const LayoutSlug = props => {
|
||||
const { post, lock, validPassword } = props
|
||||
@@ -21,6 +22,7 @@ export const LayoutSlug = props => {
|
||||
{post && <>
|
||||
<ArticleInfo post={post} />
|
||||
<NotionPage post={post} />
|
||||
<ShareBar post={post} />
|
||||
<Comment frontMatter={post}/>
|
||||
<ArticleFooter />
|
||||
</>}
|
||||
|
||||
18
themes/nobelium/components/Announcement.js
Normal file
18
themes/nobelium/components/Announcement.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const NotionPage = dynamic(() => import('@/components/NotionPage'))
|
||||
|
||||
const Announcement = ({ notice, className }) => {
|
||||
if (notice?.blockMap) {
|
||||
return <div className={className}>
|
||||
<section id='announcement-wrapper' className='mb-10'>
|
||||
{notice && (<div id="announcement-content">
|
||||
<NotionPage post={notice} className='text-center ' />
|
||||
</div>)}
|
||||
</section>
|
||||
</div>
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
export default Announcement
|
||||
@@ -4,6 +4,7 @@ import NotionPage from '@/components/NotionPage'
|
||||
import { ArticleInfo } from './components/ArticleInfo'
|
||||
import Comment from '@/components/Comment'
|
||||
import ArticleAround from './components/ArticleAround'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
|
||||
export const LayoutSlug = props => {
|
||||
const { post, lock, validPassword, prev, next } = props
|
||||
@@ -22,6 +23,8 @@ export const LayoutSlug = props => {
|
||||
{post && <>
|
||||
<ArticleInfo post={post} />
|
||||
<NotionPage post={post} />
|
||||
{/* 分享 */}
|
||||
<ShareBar post={post} />
|
||||
{post.type === 'Post' && <ArticleAround prev={prev} next={next} />}
|
||||
<Comment frontMatter={post}/>
|
||||
</>}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import Link from 'next/link'
|
||||
import CONFIG_SIMPLE from '../config_simple'
|
||||
import TwikooCommentCount from '@/components/TwikooCommentCount'
|
||||
|
||||
export const BlogItem = props => {
|
||||
const { post } = props
|
||||
@@ -18,6 +19,7 @@ export const BlogItem = props => {
|
||||
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
|
||||
<span> <i className="fa-regular fa-user"></i> <a href={CONFIG_SIMPLE.AUTHOR_LINK}>{BLOG.AUTHOR}</a></span>
|
||||
<span> - <i className="fa-regular fa-clock"></i> {post.date?.start_date || post.createdTime}</span>
|
||||
<span> - <TwikooCommentCount post={post}/></span>
|
||||
{post.category && <span> - <i className="fa-regular fa-folder"></i> <a href={`/category/${post.category}`} className="hover:text-red-400 transition-all duration-200">{post.category}</a></span>}
|
||||
{post.tags && post.tags?.length > 0 && post.tags.map(t => <span key={t}> / <Link href={`/tag/${t}`}><span className=' hover:text-red-400 transition-all duration-200'>{t}</span></Link></span>)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user