Merge branch 'main' into pr/emengweb/1007

This commit is contained in:
tangly1024.com
2023-05-23 16:03:18 +08:00
69 changed files with 1422 additions and 592 deletions

View File

@@ -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
View 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.

View File

@@ -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={{

View File

@@ -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

View File

@@ -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
View 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
View 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

View File

@@ -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, // 腾讯云环境填 envIdVercel 环境填地址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-guangzhouVercel 环境不填
// 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, // 腾讯云环境填 envIdVercel 环境填地址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-guangzhouVercel 环境不填
// 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>
)
}

View 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

View 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

View File

@@ -0,0 +1,12 @@
/**
* 显示最近评论 TODO
* @returns {JSX.Element}
* @constructor
*/
const TwikooRecentComments = (props) => {
return null
}
export default TwikooRecentComments

View File

@@ -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)
}

View File

@@ -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
View 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: '上に戻る'
}
}

View File

@@ -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: '上一页',

View File

@@ -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 })

View File

@@ -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

View File

@@ -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"

View File

@@ -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)
}

View File

@@ -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/>
</>

View File

@@ -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 })

View File

@@ -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的主题相关 */

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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>}

View File

@@ -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>

View File

@@ -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>
)
}

View 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

View File

@@ -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"

View File

@@ -47,7 +47,7 @@ const PaginationSimple = ({ page, showNext }) => {
{locale.PAGINATION.NEXT}
</Link>
</div>
);
)
}
export default PaginationSimple

View File

@@ -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} /> }

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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) {

View File

@@ -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} />

View File

@@ -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>
}

View File

@@ -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>

View 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

View File

@@ -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>
)
}

View File

@@ -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>
))}

View File

@@ -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} />

View File

@@ -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>
)
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -48,7 +48,7 @@ const PaginationSimple = ({ page, totalPage }) => {
{locale.PAGINATION.NEXT}
</Link>
</div>
);
)
}
export default PaginationSimple

View File

@@ -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

View File

@@ -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>)}
</>

View File

@@ -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>

View File

@@ -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>
)}

View File

@@ -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}

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -43,6 +43,7 @@ const SideAreaRight = (props) => {
</Card>}
<div className="sticky top-0 space-y-4 w-full">
{announcementVisible && <Card>
<Announcement post={notice} />
</Card>}

View File

@@ -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 // 文章版权声明

View File

@@ -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>
)

View File

@@ -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 />
</>}

View 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

View File

@@ -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}/>
</>}

View File

@@ -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>