This commit is contained in:
tangly1024
2022-01-15 17:21:47 +08:00
40 changed files with 590 additions and 457 deletions

View File

@@ -44,20 +44,21 @@
- 使用 Tailwind CSS轻松实现二次开发
**🕸  网址美观、搜索引擎优化**
- 更多特性、欢迎移步[我的博客](https://tangly1024.com/article/notion-next)查看
## 更新日志
请移步 [更新文档](https://docs.tangly1024.com/zh/changelog)查看
## 更多特性
欢迎移步[我的博客](https://tangly1024.com/article/notion-next)查看
## 快速起步
- 给这个项目点个小星星 😉
- 将 [这个 Notion 模板](https://tanghh.notion.site/02ab3b8678004aa69e9e415905ef32a5) 制作副本,并分享这个页面给所有人
- [Fork](https://github.com/tangly1024/NotionNext/fork) 这个项目
-`blog.config.js` 配置相关选项,更多关于配置的说明,请移步[NotionNext文档](https://docs.tangly1024.com/zh)
- _(可选)_ 用自己的图片替换 `/public` 文件夹里的 `avatar.jpg``favicon.svg``favicon.ico`
- 在 [Vercel](https://vercel.com) 上部署这个项目, 设定一下环境变量:
- `NOTION_PAGE_ID`: 你刚刚分享出去的 Notion 页面网址中的页面 ID通常是网址中工作区地址后的 32 位字符串
- `NOTION_ACCESS_TOKEN`(可选): 如果你决定不分享你的数据库,你可以使用 token 来让网页从 Notion 数据库中抓取数据。你可以在你的浏览器 cookies 中找到它,名称是 `token_v2'。
- Notion token 的有效期只有 180 天,请确保在 Vercel Dashboard 上手动更新,我们可能会在未来切换到官方 API 来解决这个问题。此外如果数据库是非公开到Notion 中的图片可能无法正常显示到网页上。
- `blog.config.js` 配置相关选项,更多关于配置的说明,请移步[NotionNext文档](https://docs.tangly1024.com/zh)
- **稍微等等就可以访问了!** 简单吗?
@@ -76,10 +77,10 @@ yarn run start # 本地启动NextJS服务
## 引用技术
- **生成**: Next.js SSG 和 Incremental Static Regeneration
- **页面渲染**: [React-notion-x](https://github.com/NotionX/react-notion-x)
- **样式**: Tailwind CSS 和 `@tailwindcss/jit` compiler
- **评论**: Gitalk,Cusdis,Utterances
- **框架**: Next.js
- **渲染**: [React-notion-x](https://github.com/NotionX/react-notion-x)
- **样式**: [Tailwind CSS](https://www.tailwindcss.cn/)`@tailwindcss/jit` compiler
- **评论**: Gitalk, Cusdis, Utterances
- **图标**[fontawesome](https://fontawesome.com/v5.15/icons?d=gallery)
## 页面样式主题

View File

@@ -12,6 +12,7 @@ const BLOG = {
homeBannerImage: './bg_image.jpg', // 背景图地址
showPostCover: false, // 文章列表显示封面图
showPreview: true, // 列表展示文章预览
previewLines: 12, // 预览文章的篇幅
showSummary: false // 显示用户自定义摘要
},
lang: 'zh-CN', // ['zh-CN','en-US'] default lang => see /lib/lang.js for more.
@@ -38,8 +39,8 @@ const BLOG = {
showPet: false, // 是否显示宠物挂件
petLink: 'https://cdn.jsdelivr.net/npm/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json', // 挂件模型地址 @see https://github.com/xiazeyu/live2d-widget-models
showToTop: true, // 是否显示回顶
showToBottom: true, // 显示回底
showDarkMode: true, // 显示日间/夜间模式切换
showToBottom: false, // 显示回底
showDarkMode: false, // 显示日间/夜间模式切换
showToc: true, // 移动端显示悬浮目录
showShareBar: false, // 文章分享功能
showRelatePosts: true, // 相关文章推荐
@@ -55,13 +56,13 @@ const BLOG = {
telegram: 'https://t.me/tangly_1024'
},
comment: { // 评论插件,支持 gitalk, utterances, cusdis
provider: '', // 不需要则留空白
provider: 'gitalk', // 不需要则留空白
gitalkConfig: {
repo: 'NotionNext', // The repository of store comments
owner: 'tangly1024',
admin: ['tangly1024'],
clientID: 'be7864a16b693e256f8f',
clientSecret: 'dbd0f6d9ceea8940f6ed20936b415274b8fe66a2',
clientID: process.env.GITALK_ID || 'be7864a16b693e256f8f',
clientSecret: process.env.GITALK_SECRET || 'dbd0f6d9ceea8940f6ed20936b415274b8fe66a2',
distractionFreeMode: false
},
cusdisConfig: {

View File

@@ -13,10 +13,10 @@ export default function Analytics ({ postCount }) {
<div className='mt-2 text-center dark:text-gray-300 font-light text-xs'>
<span className='px-1 '>
<strong className='font-medium'>{postCount}</strong>{locale.COMMON.POSTS}</span>
{/* <span className='px-1 busuanzi_container_site_uv hidden'> */}
{/* | <strong className='pl-1 busuanzi_value_site_uv font-medium'></strong>{locale.COMMON.VISITORS}</span> */}
<span className='px-1 busuanzi_container_site_pv hidden'>
| <strong className='pl-1 busuanzi_value_site_pv font-medium'></strong>{locale.COMMON.VIEWS}</span>
<span className='px-1 busuanzi_container_site_uv hidden'>
| <strong className='pl-1 busuanzi_value_site_uv font-medium'></strong>{locale.COMMON.VISITORS}</span>
{/* <span className='px-1 busuanzi_container_site_pv hidden'>
| <strong className='pl-1 busuanzi_value_site_pv font-medium'></strong>{locale.COMMON.VIEWS}</span> */}
</div>
</>
}

View File

@@ -4,8 +4,6 @@ import Comment from '@/components/Comment'
import RecommendPosts from '@/components/RecommendPosts'
import ShareBar from '@/components/ShareBar'
import TagItem from '@/components/TagItem'
import TocDrawer from '@/components/TocDrawer'
import TocDrawerButton from '@/components/TocDrawerButton'
import formatDate from '@/lib/formatDate'
import { useGlobal } from '@/lib/global'
import { faEye, faFolderOpen } from '@fortawesome/free-solid-svg-icons'
@@ -22,7 +20,6 @@ import 'prismjs/components/prism-typescript'
import { useEffect, useRef } from 'react'
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
import ArticleCopyright from './ArticleCopyright'
import Live2D from './Live2D'
import WordCount from './WordCount'
/**
@@ -31,8 +28,6 @@ import WordCount from './WordCount'
* @returns
*/
export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
const targetRef = useRef(null)
const drawerRight = useRef(null)
const url = BLOG.link + useRouter().asPath
const { locale } = useGlobal()
const date = formatDate(post?.date?.start_date || post.createdTime, locale.LOCALE)
@@ -55,144 +50,132 @@ export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
}
})
return (<>
<div id="container" ref={targetRef} className="shadow md:hover:shadow-2xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
<article itemScope itemType="https://schema.org/Movie"
className="subpixel-antialiased py-10 px-5 lg:pt-24 md:px-24 dark:border-gray-700 bg-white dark:bg-gray-800"
>
return (<div id="container" className="shadow md:hover:shadow-2xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
<article itemScope itemType="https://schema.org/Movie"
className="subpixel-antialiased py-10 px-5 lg:pt-24 md:px-24 dark:border-gray-700 bg-white dark:bg-gray-800"
>
<header className='animate__slideInDown animate__animated'>
{post.type && !post.type.includes('Page') && post?.page_cover && (
<div className="w-full relative md:flex-shrink-0 overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img alt={post.title} src={post?.page_cover} className='object-center w-full' />
{/* <div className="w-full h-60 relative lg:h-96 transform duration-200 md:flex-shrink-0 overflow-hidden">
<Image
src={post?.page_cover}
loading="eager"
objectFit="cover"
layout="fill"
alt={post.title}
/>
</div> */}
</div>
)}
{/* 文章Title */}
<div className="font-bold text-3xl text-black dark:text-white font-serif pt-10">
{post.title}
</div>
<section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8">
<div>
<Link href={`/category/${post.category}`} passHref>
<a className="cursor-pointer text-md mr-2 hover:text-black dark:hover:text-white border-b dark:border-gray-500 border-dashed">
<FontAwesomeIcon icon={faFolderOpen} className="mr-1" />
{post.category}
</a>
</Link>
<span className='mr-2'>|</span>
{post.type[0] !== 'Page' && (<>
<Link
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
passHref
>
<a className="pl-1 mr-2 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 border-b dark:border-gray-500 border-dashed">
{date}
</a>
</Link>
<span className='mr-2'>|</span>
</>)}
<div className="hidden busuanzi_container_page_pv font-light mr-2">
<FontAwesomeIcon icon={faEye} className='mr-1'/>
&nbsp;
<span className="mr-2 busuanzi_value_page_pv"
></span>
<span className='mr-2'>|</span>
</div>
</div>
<div className='flex flex-nowrap whitespace-nowrap items-center font-light text-md'>
<WordCount/>
</div>
</section>
{/* <hr className="mt-2" /> */}
</header>
{/* Notion文章主体 */}
<section id='notion-article' className='px-1'>
{post.blockMap && (
<NotionRenderer
recordMap={post.blockMap}
mapPageUrl={mapPageUrl}
components={{
equation: Equation,
code: Code,
collectionRow: CollectionRow,
collection: Collection
}}
<header className='animate__slideInDown animate__animated'>
{post.type && !post.type.includes('Page') && post?.page_cover && (
<div className="w-full relative md:flex-shrink-0 overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img alt={post.title} src={post?.page_cover} className='object-center w-full' />
{/* <div className="w-full h-60 relative lg:h-96 transform duration-200 md:flex-shrink-0 overflow-hidden">
<Image
src={post?.page_cover}
loading="eager"
objectFit="cover"
layout="fill"
alt={post.title}
/>
)}
</section>
</div> */}
</div>
)}
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
{/* 文章内嵌广告 */}
<ins className="adsbygoogle"
style={{ display: 'block', textAlign: 'center' }}
data-adtest="on"
data-ad-layout="in-article"
data-ad-format="fluid"
data-ad-client="ca-pub-2708419466378217"
data-ad-slot="3806269138"></ins>
</section>
{/* 版权声明 */}
<ArticleCopyright author={BLOG.author} url={url} />
{/* 推荐文章 */}
<RecommendPosts currentPost={post} recommendPosts={recommendPosts} />
{/* 标签列表 */}
<section className="md:flex md:justify-between">
{post.tagItems && (
<div className="flex flex-nowrap leading-8 p-1 py-4 overflow-x-auto">
<div className="hidden md:block dark:text-gray-300 whitespace-nowrap">
{locale.COMMON.TAGS}
</div>
{post.tagItems.map(tag => (
<TagItem key={tag.name} tag={tag} />
))}
</div>
)}
<div>
<ShareBar post={post} />
</div>
</section>
<BlogAround prev={prev} next={next} />
</article>
{/* 评论互动 */}
<div className="lg:px-40 md:hover:shadow-2xl duration-200 shadow w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-800">
<Comment frontMatter={post} />
{/* 文章Title */}
<div className="font-bold text-3xl text-black dark:text-white font-serif pt-10">
{post.title}
</div>
</div>
{/* 悬浮目录按钮 */}
<div className="block lg:hidden">
<TocDrawerButton onClick={() => { drawerRight.current.handleSwitchVisible() }} />
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} />
</div>
<section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8">
<div>
<Link href={`/category/${post.category}`} passHref>
<a className="cursor-pointer text-md mr-2 hover:text-black dark:hover:text-white border-b dark:border-gray-500 border-dashed">
<FontAwesomeIcon icon={faFolderOpen} className="mr-1" />
{post.category}
</a>
</Link>
<span className='mr-2'>|</span>
{/* 宠物 */}
<Live2D/>
{post.type[0] !== 'Page' && (<>
<Link
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
passHref
>
<a className="pl-1 mr-2 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 border-b dark:border-gray-500 border-dashed">
{date}
</a>
</Link>
<span className='mr-2'>|</span>
</>)}
</>)
<div className="hidden busuanzi_container_page_pv font-light mr-2">
<FontAwesomeIcon icon={faEye} className='mr-1'/>
&nbsp;
<span className="mr-2 busuanzi_value_page_pv"
></span>
<span className='mr-2'>|</span>
</div>
</div>
<div className='flex flex-nowrap whitespace-nowrap items-center font-light text-md'>
<WordCount/>
</div>
</section>
{/* <hr className="mt-2" /> */}
</header>
{/* Notion文章主体 */}
<section id='notion-article' className='px-1'>
{post.blockMap && (
<NotionRenderer
recordMap={post.blockMap}
mapPageUrl={mapPageUrl}
components={{
equation: Equation,
code: Code,
collectionRow: CollectionRow,
collection: Collection
}}
/>
)}
</section>
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
{/* 文章内嵌广告 */}
<ins className="adsbygoogle"
style={{ display: 'block', textAlign: 'center' }}
data-adtest="on"
data-ad-layout="in-article"
data-ad-format="fluid"
data-ad-client="ca-pub-2708419466378217"
data-ad-slot="3806269138"></ins>
</section>
{/* 版权声明 */}
<ArticleCopyright author={BLOG.author} url={url} />
{/* 推荐文章 */}
<RecommendPosts currentPost={post} recommendPosts={recommendPosts} />
{/* 标签列表 */}
<section className="md:flex md:justify-between">
{post.tagItems && (
<div className="flex flex-nowrap leading-8 p-1 py-4 overflow-x-auto">
<div className="hidden md:block dark:text-gray-300 whitespace-nowrap">
{locale.COMMON.TAGS}
</div>
{post.tagItems.map(tag => (
<TagItem key={tag.name} tag={tag} />
))}
</div>
)}
<div>
<ShareBar post={post} />
</div>
</section>
<BlogAround prev={prev} next={next} />
</article>
{/* 评论互动 */}
<div className="duration-200 shadow w-screen md:w-full overflow-x-auto dark:border-gray-700 bg-white dark:bg-gray-800">
<Comment frontMatter={post} />
</div>
</div>)
}
const mapPageUrl = id => {

View File

@@ -1,27 +1,29 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { faAngleRight, faFolder } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Image from 'next/image'
import Link from 'next/link'
import React from 'react'
import Image from 'next/image'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faAngleDoubleRight, faFolder } from '@fortawesome/free-solid-svg-icons'
import TagItemMini from './TagItemMini'
import { Code, Collection, CollectionRow, Equation, NotionRenderer } from 'react-notion-x'
import { useGlobal } from '@/lib/global'
import Card from './Card'
import TagItemMini from './TagItemMini'
const BlogPostCard = ({ post, showSummary }) => {
const { locale } = useGlobal()
const showPreview = BLOG.home?.showPreview && post.blockMap
return (
<div key={post.id} className='shadow border animate__animated animate__fadeIn flex flex-col-reverse justify-between md:hover:shadow-xl duration-300
w-full bg-white dark:bg-gray-800 dark:hover:bg-gray-700 dark:border-gray-600'>
<Card className='w-full'>
<div key={post.id} className='animate__animated animate__fadeIn flex flex-col-reverse justify-between duration-300'>
<div className='lg:p-8 p-4 flex flex-col justify-between w-full'>
<div className='lg:p-8 p-4 flex flex-col w-full'>
<Link href={`${BLOG.path}/article/${post.slug}`} passHref>
<a className='cursor-pointer font-bold text-3xl text-center leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400'>
<a className={`cursor-pointer font-bold hover:underline text-3xl flex ${showPreview ? 'justify-center' : 'justify-start'} leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}>
{post.title}
</a>
</Link>
<div className='flex mt-2 items-center justify-center flex-wrap dark:text-gray-500 text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 '>
<div 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>
<Link href={`/category/${post.category}`} passHref>
<a className='cursor-pointer font-light text-sm hover:underline transform'>
@@ -38,31 +40,31 @@ const BlogPostCard = ({ post, showSummary }) => {
</div>
</div>
{showSummary && <p className='mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7'>
{(!showPreview || showSummary) && <p className='mt-4 mb-24 text-gray-700 dark:text-gray-300 text-sm font-light leading-7'>
{post.summary}
</p>}
{BLOG.home?.showPreview && post?.blockMap && <div className='max-h-screen overflow-hidden truncate max-w-full'>
{showPreview && post?.blockMap && <div className='overflow-ellipsis truncate'>
<NotionRenderer
recordMap={post.blockMap}
mapPageUrl={mapPageUrl}
components={{
equation: Equation,
code: Code,
collectionRow: CollectionRow,
collection: Collection
}}
/>
bodyClassName='max-h-full'
recordMap={post.blockMap}
mapPageUrl={mapPageUrl}
components={{
equation: Equation,
code: Code,
collectionRow: CollectionRow,
collection: Collection
}}
/>
</div> }
<div className='border-b-2 w-full border-dashed py-2'></div>
<Link href={`${BLOG.path}/article/${post.slug}`} passHref>
<div className='flex items-center cursor-pointer pt-6 justify-end leading-tight'>
<a className='bg-black p-2 text-white'>{locale.COMMON.ARTICLE_DETAIL}
<FontAwesomeIcon icon={faAngleDoubleRight} /></a>
</div>
</Link>
<div className='article-cover pointer-events-none'>
<Link href={`${BLOG.path}/article/${post.slug}`} passHref>
<a className='hover:bg-opacity-100 hover:scale-105 pointer-events-auto transform duration-300 rounded-md p-2 text-red-500 cursor-pointer'>
{locale.COMMON.ARTICLE_DETAIL}
<FontAwesomeIcon className='ml-1' icon={faAngleRight} /></a>
</Link>
</div>
</div>
{BLOG.home?.showPostCover && post?.page_cover && (
@@ -73,6 +75,8 @@ const BlogPostCard = ({ post, showSummary }) => {
</Link>
)}
</div >
</Card>
)
}

View File

@@ -55,7 +55,7 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = BLOG.home
return <div id='container' ref={targetRef}>
{/* 文章列表 */}
<div className='flex flex-wrap space-y-8'>
<div className='flex flex-wrap space-y-1 lg:space-y-4'>
{postsToShow.map(post => (
<BlogPostCard key={post.id} post={post} showSummary={showSummary}/>
))}
@@ -65,7 +65,7 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = BLOG.home
<div onClick={() => {
handleGetMore()
}}
className='w-full my-4 py-4 text-center cursor-pointer glassmorphism shadow-xl rounded-xl dark:text-gray-200'
className='w-full my-4 py-4 text-center cursor-pointer glassmorphism shadow-xl rounded-xl dark:text-gray-200'
> {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`} </div>
</div>
</div>

9
components/Card.js Normal file
View File

@@ -0,0 +1,9 @@
const Card = ({ children, headerSlot, className }) => {
return <div className={className}>
<>{headerSlot}</>
<section className="shadow px-2 py-4 bg-white dark:bg-gray-800 hover:shadow-xl duration-200">
{children}
</section>
</div>
}
export default Card

View File

@@ -12,7 +12,7 @@ const CategoryGroup = ({ currentCategory, categories }) => {
<a className={(selected
? 'hover:text-white dark:hover:text-white bg-gray-600 text-white '
: 'dark:text-gray-400 text-gray-500 hover:text-white hover:bg-gray-500 dark:hover:text-white') +
' text-sm w-full items-center duration-300 px-3 mx-2 cursor-pointer py-1 font-light'}>
' text-sm w-full items-center duration-300 px-2 cursor-pointer py-1 font-light'}>
<FontAwesomeIcon icon={selected ? faFolderOpen : faFolder} className={`${selected ? 'text-white' : 'text-gray-400'} mr-2`} />{category}({categories[category]})
</a>
</Link>

38
components/Collapse.js Normal file
View File

@@ -0,0 +1,38 @@
import React, { useEffect, useRef } from 'react'
const Collapse = props => {
const collapseRef = useRef(null)
const collapseSection = element => {
const sectionHeight = element.scrollHeight
requestAnimationFrame(function () {
element.style.height = sectionHeight + 'px'
requestAnimationFrame(function () {
element.style.height = 0 + 'px'
})
})
}
const expandSection = element => {
const sectionHeight = element.scrollHeight
element.style.height = sectionHeight + 'px'
const clearTime = setTimeout(() => {
element.style.height = 'auto'
}, 400)
clearTimeout(clearTime)
}
useEffect(() => {
const element = collapseRef.current
if (props.isOpen) {
expandSection(element)
} else {
collapseSection(element)
}
}, [props.isOpen])
return (
<div ref={collapseRef} style={{ height: '0px' }} className='overflow-hidden duration-200'>
{props.children}
</div>
)
}
Collapse.defaultProps = { isOpen: false }
export default Collapse

View File

@@ -2,6 +2,7 @@ import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { useGlobal } from '@/lib/global'
import 'gitalk/dist/gitalk.css'
const GitalkComponent = dynamic(
() => {

View File

@@ -1,27 +1,13 @@
import { useEffect, useState } from 'react'
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { loadUserThemeFromCookies, saveTheme } from '@/lib/theme'
import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import BLOG from '@/blog.config'
import { loadUserThemeFromCookies, saveTheme } from '@/lib/theme'
export default function FloatDarkModeButton () {
if (!BLOG.widget?.showDarkMode) {
return <></>
}
const [show, switchShow] = useState(false)
const scrollListener = () => {
const scrollY = window.pageYOffset
const shouldShow = scrollY > 100
if (shouldShow !== show) {
switchShow(shouldShow)
}
}
useEffect(() => {
scrollListener()
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
const { changeTheme } = useGlobal()
const userTheme = loadUserThemeFromCookies()
@@ -37,12 +23,8 @@ export default function FloatDarkModeButton () {
return (
<div
id='float-dark-mode-button'
onClick={handleChangeDarkMode}
className={
(show ? '' : ' hidden ') +
' animate__fadeInRight animate__animated animate__faster fixed right-1 bottom-28 z-10 duration-500 text-xs cursor-pointer ' +
' text-black dark:border-gray-500 flex justify-center items-center w-8 h-8 glassmorphism dark:bg-gray-700 dark:text-gray-200'
className={ ' text-black dark:border-gray-500 flex justify-center items-center dark:text-gray-200 py-2 px-3'
}
>
<FontAwesomeIcon

View File

@@ -1,26 +1,27 @@
import { faCopyright, faEye, faShieldAlt, faUsers } from '@fortawesome/free-solid-svg-icons'
import { faCopyright, faEye, faShieldAlt, faUsers, faHeart } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React from 'react'
import Link from 'next/link'
import BLOG from '@/blog.config'
const Footer = ({ title }) => {
const d = new Date()
const y = d.getFullYear()
const currentYear = d.getFullYear()
const startYear = BLOG.since && BLOG.since !== currentYear && BLOG.since + '-'
return (
<footer
className='dark:bg-gray-900 flex-shrink-0 justify-center text-center m-auto w-full text-gray-400 text-sm p-6'
className='dark:bg-gray-900 flex-shrink-0 justify-center text-center m-auto w-full leading-6 text-gray-400 text-sm p-6'
>
<FontAwesomeIcon icon={faCopyright} /> {` ${y}`} <span> <a href={BLOG.link} className='underline font-bold text-gray-500 dark:text-gray-300 '>{BLOG.author}</a>. Powered by <a href='https://notion.so' className='underline font-bold text-gray-500 dark:text-gray-300'>Notion</a> & <a href='https://github.com/tangly1024/NotionNext' className='underline font-bold text-gray-500 dark:text-gray-300'>NotionNext</a>.</span>
<br />
<FontAwesomeIcon icon={faShieldAlt} /> <a href='https://beian.miit.gov.cn/' className='ml-1 font-bold'>闽ICP备20010331号</a>
<span > <Link href='/article/privacy-policy' ><a className='ml-1 mr-1 font-bold underline'>隐私政策</a></Link></span>
<FontAwesomeIcon icon={faCopyright} /> {`${startYear}${currentYear}`} <span><FontAwesomeIcon icon={faHeart} className='mx-1 animate-pulse'/> <a href={BLOG.link} className='underline font-bold text-gray-500 dark:text-gray-300 '>{BLOG.author}</a>.
<br/>
<span>Powered by <a href='https://notion.so' className='underline font-bold text-gray-500 dark:text-gray-300'>Notion</a> & <a href='https://github.com/tangly1024/NotionNext' className='underline font-bold text-gray-500 dark:text-gray-300'>NotionNext</a>.</span></span>
<br />
<FontAwesomeIcon icon={faShieldAlt} /> <a href='https://beian.miit.gov.cn/' className='mr-2 font-bold'>闽ICP备20010331号</a>
<span className='hidden busuanzi_container_site_pv'>
<FontAwesomeIcon icon={faEye}/><span className='px-1 busuanzi_value_site_pv'> </span>pv
</span>
<FontAwesomeIcon icon={faEye}/><span className='px-1 busuanzi_value_site_pv'> </span> </span>
<span className='pl-2 hidden busuanzi_container_site_uv'>
<FontAwesomeIcon icon={faUsers}/> <span className='px-1 busuanzi_value_site_uv'> </span>uv </span>
<FontAwesomeIcon icon={faUsers}/> <span className='px-1 busuanzi_value_site_uv'> </span> </span>
<br/>
<h1>{title}</h1>
</footer>

View File

@@ -1,5 +1,4 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { faArrowDown } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React, { useEffect, useState } from 'react'
@@ -18,7 +17,6 @@ const JumpToBottomButton = ({ showPercent = false }) => {
return <></>
}
const { locale } = useGlobal()
const [show, switchShow] = useState(false)
const [percent, changePercent] = useState(0)
const scrollListener = () => {
@@ -47,15 +45,12 @@ const JumpToBottomButton = ({ showPercent = false }) => {
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
return (<div id='jump-to-top' className='right-1 fixed flex bottom-36 z-20'>
<div onClick={() => scrollToBottom()}
className={(show ? '' : 'hidden') + ' animate__fadeInRight duration-500 animate__animated animate__faster glassmorphism flex justify-center items-center w-8 h-8 cursor-pointer '}>
<div className='dark:text-gray-200 transform hover:scale-150 text-xs duration-200' title={locale.POST.TOP} >
<FontAwesomeIcon icon={faArrowDown} />
</div>
{showPercent && (<div className='w-10 text-xs dark:text-gray-200'>{percent}</div>)}
</div>
</div>)
return (<div className='flex space-x-1 transform hover:scale-105 duration-200 py-2 px-3' onClick={scrollToBottom} >
<div className='dark:text-gray-200' >
<FontAwesomeIcon icon={faArrowDown} />
</div>
{showPercent && (<div className='dark:text-gray-200 block lg:hidden'>{percent}%</div>)}
</div>)
}
export default JumpToBottomButton

View File

@@ -1,9 +1,8 @@
import React, { useEffect, useState } from 'react'
import { useGlobal } from '@/lib/global'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowUp } from '@fortawesome/free-solid-svg-icons'
import smoothscroll from 'smoothscroll-polyfill'
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { faArrowUp } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React from 'react'
/**
* 跳转到网页顶部
@@ -13,41 +12,16 @@ import BLOG from '@/blog.config'
* @returns {JSX.Element}
* @constructor
*/
const JumpToTopButton = ({ showPercent = false }) => {
const JumpToTopButton = ({ showPercent = true, percent }) => {
if (!BLOG.widget?.showToTop) {
return <></>
}
const { locale } = useGlobal()
const [show, switchShow] = useState(false)
const [percent, changePercent] = useState(0)
const scrollListener = () => {
const targetRef = document.getElementById('wrapper')
const clientHeight = targetRef?.clientHeight
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
if (per > 100) per = 100
const shouldShow = scrollY > 100 && per > 0
if (shouldShow !== show) {
switchShow(shouldShow)
}
changePercent(per)
}
useEffect(() => {
smoothscroll.polyfill()
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
return (<div id='jump-to-top' className='right-1 fixed flex bottom-44 z-20'>
<div onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
className={(show ? '' : 'hidden') + ' animate__fadeInRight duration-500 animate__animated animate__faster flex justify-center items-center w-8 h-8 glassmorphism cursor-pointer '}>
<div className='dark:text-gray-200 transform hover:scale-150 text-xs duration-200' title={locale.POST.TOP} >
return (<div className='flex space-x-1 items-center transform hover:scale-105 duration-200 py-2 px-3' onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} >
<div className='dark:text-gray-200' title={locale.POST.TOP} >
<FontAwesomeIcon icon={faArrowUp} />
</div>
{showPercent && (<div className='w-10 text-xs dark:text-gray-200'>{percent}</div>)}
</div>
{showPercent && (<div className='text-xs dark:text-gray-200 block lg:hidden'>{percent}%</div>)}
</div>)
}

View File

@@ -19,16 +19,16 @@ const LatestPostsGroup = ({ posts }) => {
const currentPath = useRouter().asPath
const { locale } = useGlobal()
return <section className='mt-8'>
<div className='text-sm pb-4 px-5 flex flex-nowrap justify-between'>
return <>
<div className='text-sm pb-1 px-2 flex flex-nowrap justify-between'>
<div className='font-light text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faArchive} className='mr-2' />{locale.COMMON.LATEST_POSTS}</div>
</div>
{posts.map(post => {
const selected = currentPath === `${BLOG.path}/article/${post.slug}`
return (
<Link key={post.id} title={post.title} href={`${BLOG.path}/article/${post.slug}`} passHref>
<a className={ 'my-1 px-2 flex font-light'}>
<div className={ (selected ? 'text-white bg-gray-600 ' : 'text-gray-500 dark:text-gray-400 ') + ' text-xs py-1.5 flex overflow-x-hidden whitespace-nowrap hover:bg-gray-500 px-3 w-full ' +
<a className={ 'my-1 flex font-light'}>
<div className={ (selected ? 'text-white bg-gray-600 ' : 'text-gray-500 dark:text-gray-400 ') + ' text-xs py-1.5 flex overflow-x-hidden whitespace-nowrap hover:bg-gray-500 px-2 duration-200 w-full ' +
'hover:text-white dark:hover:text-white cursor-pointer' }>
<FontAwesomeIcon icon={faFileAlt} className='mr-2'/>
<div className='truncate'>{post.title}</div>
@@ -37,6 +37,6 @@ const LatestPostsGroup = ({ posts }) => {
</Link>
)
})}
</section>
</>
}
export default LatestPostsGroup

View File

@@ -6,27 +6,30 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArchive, faHome, faTag, faTh, faUser } from '@fortawesome/free-solid-svg-icons'
import BLOG from 'blog.config'
const MenuButtonGroup = ({ allowCollapse = false }) => {
const MenuButtonGroup = ({ allowCollapse = false, postCount }) => {
const { locale } = useGlobal()
const router = useRouter()
const archiveSlot = <div className='bg-gray-300 dark:bg-gray-500 rounded-md text-gray-50 px-1 text-xs'>{postCount}</div>
const links = [
{ id: 0, icon: faHome, name: locale.NAV.INDEX, to: '/' || '/', show: true },
{ id: 1, icon: faArchive, name: locale.NAV.ARCHIVE, to: '/archive', show: BLOG.menu.showArchive },
{ id: 2, icon: faTh, name: locale.COMMON.CATEGORY, to: '/category', show: BLOG.menu.showCategory },
{ id: 3, icon: faTag, name: locale.COMMON.TAGS, to: '/tag', show: BLOG.menu.showTag },
{ id: 1, icon: faTh, name: locale.COMMON.CATEGORY, to: '/category', show: BLOG.menu.showCategory },
{ id: 2, icon: faTag, name: locale.COMMON.TAGS, to: '/tag', show: BLOG.menu.showTag },
{ id: 3, icon: faArchive, name: locale.NAV.ARCHIVE, to: '/archive', slot: archiveSlot, show: BLOG.menu.showArchive },
{ id: 4, icon: faUser, name: locale.NAV.ABOUT, to: '/about', show: BLOG.menu.showAbout }
]
return <nav id='nav' className='leading-8 text-gray-500 dark:text-gray-400 '>
return <nav id='nav' className='leading-8 text-gray-500 dark:text-gray-400 font-sans'>
{links.map(link => {
if (link.show) {
const selected = (router.pathname === link.to) || (router.asPath === link.to)
return <Link key={`${link.id}-${link.to}`} title={link.to} href={link.to} >
<a className={'py-1 my-1 px-5 mx-2 duration-300 text-base hover:bg-gray-700 hover:text-white hover:shadow-lg cursor-pointer font-light flex flex-nowrap items-center ' +
<a className={'py-1.5 px-5 duration-300 text-base justify-between hover:bg-gray-700 hover:text-white hover:shadow-lg cursor-pointer font-light flex flex-nowrap items-center ' +
(selected ? 'bg-gray-200 text-black' : ' ')} >
<div className='my-auto justify-center flex '>
<div className='my-auto items-center justify-center flex '>
<FontAwesomeIcon icon={link.icon} />
<div className={'ml-4'}>{link.name}</div>
</div>
<div className={'ml-4'}>{link.name}</div>
{link.slot}
</a>
</Link>
} else {

View File

@@ -6,13 +6,15 @@ import React, { useEffect, useState } from 'react'
* @constructor
*/
const Progress = ({ targetRef, showPercent = true }) => {
const currentRef = targetRef?.current || targetRef
const [percent, changePercent] = useState(0)
const scrollListener = () => {
if (targetRef?.current) {
const clientHeight = targetRef ? (targetRef.current.clientHeight) : 0
const target = currentRef || document.getElementById('container')
if (target) {
const clientHeight = target.clientHeight
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
if (per > 100) per = 100
if (per < 0) per = 0
changePercent(per)
@@ -24,11 +26,18 @@ const Progress = ({ targetRef, showPercent = true }) => {
return () => document.removeEventListener('scroll', scrollListener)
}, [percent])
return (<div className='h-4 w-full shadow-2xl bg-gray-400'>
<div className='h-4 bg-gray-600 duration-200' style={{ width: `${percent}%` }}>
{showPercent && <div className='text-right text-white text-xs'>{percent}%</div>}
return (
<div className="h-4 w-full shadow-2xl bg-gray-400 font-sans">
<div
className="h-4 bg-gray-600 duration-200"
style={{ width: `${percent}%` }}
>
{showPercent && (
<div className="text-right text-white text-xs">{percent}%</div>
)}
</div>
</div>)
</div>
)
}
export default Progress

View File

@@ -1,7 +1,7 @@
import { Router } from 'next/router'
import { useImperativeHandle, useRef } from 'react'
import SearchInput from './SearchInput'
const SearchDrawer = ({ cRef }) => {
const SearchDrawer = ({ cRef, slot }) => {
const searchDrawer = useRef()
const searchInputRef = useRef()
useImperativeHandle(cRef, () => {
@@ -20,14 +20,15 @@ const SearchDrawer = ({ cRef }) => {
})
return (
<div id='search-drawer-wrapper' ref={searchDrawer} className='hidden'>
<div className='flex absolute px-5 w-full h-full left-0 top-14 z-50 justify-center'>
<div className='flex-col fixed px-5 w-full left-0 top-14 z-50 justify-center'>
<div className='md:max-w-3xl w-full mx-auto animate__animated animate__faster animate__fadeIn'>
<SearchInput cRef={searchInputRef} />
<SearchInput cRef={searchInputRef} />
{slot}
</div>
</div>
{/* 背景蒙版 */}
<div id='search-drawer-background' onClick={hidden} className='animate__animated animate__faster animate__fadeIn fixed glassmorphism top-0 left-0 z-30 w-full h-full' />
<div id='search-drawer-background' onClick={hidden} className='animate__animated animate__faster animate__fadeIn fixed bg-day dark:bg-night top-0 left-0 z-40 w-full h-full' />
</div>
)
}

View File

@@ -1,15 +1,22 @@
import { useRouter } from 'next/router'
import { useGlobal } from '@/lib/global'
import { useRef, useState } from 'react'
import { useImperativeHandle, useRef, useState } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons'
const SearchInput = ({ currentTag, currentSearch }) => {
const SearchInput = ({ currentTag, currentSearch, cRef }) => {
const { locale } = useGlobal()
const [searchKey, setSearchKey] = useState(currentSearch)
const [searchKey, setSearchKey] = useState(currentSearch || '')
const [onLoading, setLoadingState] = useState(false)
const router = useRouter()
const searchInputRef = useRef()
useImperativeHandle(cRef, () => {
return {
focus: () => {
searchInputRef?.current?.focus()
}
}
})
const handleSearch = (key) => {
if (key && key !== '') {
setLoadingState(true)
@@ -45,7 +52,7 @@ const SearchInput = ({ currentTag, currentSearch }) => {
className={'w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 border-gray-300 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
onKeyUp={handleKeyUp}
onChange={e => updateSearchKey(e.target.value)}
defaultValue={currentSearch}
defaultValue={searchKey}
/>
{(searchKey && searchKey.length && <FontAwesomeIcon icon={faTimes} className='text-gray-300 float-right m-3 cursor-pointer' onClick={cleanSearch} />)}

View File

@@ -8,6 +8,7 @@ import Analytics from './Analytics'
import Tabs from '@/components/Tabs'
import BLOG from '@/blog.config'
import Logo from './Logo'
import Card from './Card'
/**
* 侧边平铺
@@ -30,21 +31,20 @@ const SideAreaLeft = ({ title, tags, currentTag, post, postCount, categories, cu
{/* 菜单 */}
<section className='shadow hidden lg:block mb-5 pb-4 bg-white dark:bg-gray-800 hover:shadow-xl duration-200'>
<Logo/>
<div className='pt-2'>
<MenuButtonGroup allowCollapse={true} />
<div className='pt-2 px-2 font-sans'>
<MenuButtonGroup allowCollapse={true} postCount={postCount} />
</div>
{BLOG.menu.showSearch && <div className='px-5 pt-2'>
{BLOG.menu.showSearch && <div className='px-2 pt-2 font-sans'>
<SearchInput currentTag={currentTag} currentSearch={currentSearch} />
</div>}
</section>
</section>
<section className='sticky top-4'>
<Card className='sticky top-4'>
<Tabs>
{showToc && (
<div key={locale.COMMON.TABLE_OF_CONTENTS} className='dark:text-gray-400 text-gray-600 bg-white dark:bg-gray-800 duration-200'>
<Toc toc={post.toc} targetRef={targetRef} />
<Toc toc={post.toc}/>
</div>
)}
@@ -53,7 +53,7 @@ const SideAreaLeft = ({ title, tags, currentTag, post, postCount, categories, cu
<Analytics postCount={postCount}/>
</div>
</Tabs>
</section>
</Card>
</aside>
}

View File

@@ -1,9 +1,10 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { faAngleDoubleRight, faAngleRight, faTags, faThList } from '@fortawesome/free-solid-svg-icons'
import { faAngleDoubleRight, faAngleRight, faTag, faThList } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Link from 'next/link'
import React from 'react'
import Card from './Card'
import CategoryGroup from './CategoryGroup'
import TagGroups from './TagGroups'
@@ -38,25 +39,25 @@ const SideAreaRight = ({
return (<aside id='right' className='hidden 2xl:block flex-col w-60 ml-4'>
<section className="shadow mb-5 py-4 px-2 bg-white dark:bg-gray-800 hover:shadow-xl duration-200">
{/* 展示广告 */}
<ins
className="adsbygoogle"
style={{ display: 'block' }}
data-adtest="on"
data-ad-client="ca-pub-2708419466378217"
data-ad-slot="8807314373"
data-ad-format="auto"
data-full-width-responsive="true"
></ins>
</section>
<Card className='mb-2'>
{/* 展示广告 */}
<ins
className="adsbygoogle"
style={{ display: 'block' }}
data-adtest="on"
data-ad-client="ca-pub-2708419466378217"
data-ad-slot="8807314373"
data-ad-format="auto"
data-full-width-responsive="true"
></ins>
</Card>
<div className="sticky top-8">
<div className="sticky top-4">
{/* 分类 */}
{widget?.showCategoryList && categories && (
<section className='shadow py-4 mb-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200'>
<div className='text-sm px-5 mb-2 flex flex-nowrap justify-between font-light'>
<Card>
<div className='text-sm px-2 flex flex-nowrap justify-between font-light'>
<div className='pb-1 text-gray-600 dark:text-gray-300'><FontAwesomeIcon icon={faThList} className='mr-2' />{locale.COMMON.CATEGORY}</div>
<Link href='/category' passHref>
<a className='text-gray-400 hover:text-black dark:text-gray-400 dark:hover:text-white hover:underline cursor-pointer'>
@@ -65,16 +66,16 @@ const SideAreaRight = ({
</Link>
</div>
<CategoryGroup currentCategory={currentCategory} categories={categories} />
</section>
</Card>
)}
{slot}
{widget?.showTagList && tags && (
<section className="shadow py-4 mb-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200">
<div className="text-sm pb-1 px-5 flex flex-nowrap justify-between font-light dark:text-gray-200">
<Card>
<div className="text-sm pb-1 px-2 flex flex-nowrap justify-between font-light dark:text-gray-200">
<div className="text-gray-600 dark:text-gray-200">
<FontAwesomeIcon icon={faTags} className="mr-2" />
<FontAwesomeIcon icon={faTag} className="mr-2" />
{locale.COMMON.TAGS}
</div>
<Link href="/tag" passHref>
@@ -84,10 +85,10 @@ const SideAreaRight = ({
</a>
</Link>
</div>
<div className="px-5 pt-2">
<div className="px-2 pt-2">
<TagGroups tags={tags} currentTag={currentTag} />
</div>
</section>
</Card>
)}
</div>

View File

@@ -1,3 +1,4 @@
import BLOG from '@/blog.config'
import throttle from 'lodash.throttle'
import { useCallback, useEffect } from 'react'
@@ -13,6 +14,9 @@ let windowTop = 0
const StickyBar = ({ children }) => {
if (!children) return <></>
const scrollTrigger = useCallback(throttle(() => {
if (BLOG.topNavType === 'normal') {
return
}
const scrollS = window.scrollY
if (scrollS >= windowTop && scrollS > 10) {
const stickyBar = document.querySelector('#sticky-bar')
@@ -35,7 +39,7 @@ const StickyBar = ({ children }) => {
}, [])
return (
<div id='sticky-bar' className='sticky flex-grow justify-center top-14 md:top-0 duration-500 z-10 pb-16'>
<div id='sticky-bar' className='sticky flex-grow justify-center top-0 duration-500 z-10 pb-16'>
<div className='glassmorphism dark:border-gray-600 px-5 absolute shadow-md border w-full hidden-scroll'>
<div id='tag-container' className="md:pl-3 overflow-x-auto">
{ children }

View File

@@ -18,7 +18,7 @@ const Tabs = ({ children }) => {
})
if (count === 1) {
return <section className='shadow hover:shadow-xl duration-200'>
return <section className='duration-200'>
{children}
</section>
}
@@ -29,11 +29,11 @@ const Tabs = ({ children }) => {
}
return (
<section >
{<div className='shadow hidden lg:block mb-5 py-4 px-5 bg-white dark:bg-gray-800 hover:shadow-xl duration-200'>
< >
{<div className='hidden lg:block mb-5 bg-white dark:bg-gray-800 duration-200'>
<ul className='flex justify-center space-x-5 pb-4 dark:text-gray-400 text-gray-600'>
{children.map((item, index) => {
return <li key={index} className={currentTab === index ? 'font-black border-b-2 border-red-400 text-red-400 animate__animated animate__jello ' : 'font-extralight cursor-pointer'} onClick={() => { tabClickHandle(index) }}>
return <li key={index} className={(currentTab === index ? 'font-black border-b-2 border-red-400 text-red-400 animate__animated animate__jello ' : 'font-extralight cursor-pointer') + ' text-sm font-sans '} onClick={() => { tabClickHandle(index) }}>
{item?.key}
</li>
})}
@@ -45,7 +45,7 @@ const Tabs = ({ children }) => {
})}
</div>}
</section>)
</>)
}
export default Tabs

View File

@@ -8,7 +8,7 @@ const TagItemMini = ({ tag, selected = false }) => {
mr-2 py-0.5 px-1 text-xs whitespace-nowrap dark:hover:text-white
${selected
? 'text-white dark:text-gray-300 bg-black dark:bg-black dark:hover:bg-gray-900'
: `text-gray-600 hover:shadow-xl dark:border-gray-400 notion-${tag.color}_background dark:bg-gray-900`}` }>
: `text-gray-600 hover:shadow-xl dark:border-gray-400 notion-${tag.color}_background dark:bg-gray-800`}` }>
<div className='font-light dark:text-gray-400'>{selected && <FontAwesomeIcon icon={faTag} className='mr-1'/>} {tag.name + (tag.count ? `(${tag.count})` : '')} </div>
</a>
</Link>

View File

@@ -10,7 +10,7 @@ import Progress from './Progress'
* @returns {JSX.Element}
* @constructor
*/
const Toc = ({ toc, targetRef }) => {
const Toc = ({ toc }) => {
// 无目录就直接返回空
if (!toc || toc.length < 1) {
return <></>
@@ -52,11 +52,11 @@ const Toc = ({ toc, targetRef }) => {
setActiveSection(currentSectionId)
}, throttleMs))
return <>
<div className='w-full'>
<Progress targetRef={targetRef}/>
return <div className='px-3'>
<div className='w-full pb-1'>
<Progress/>
</div>
<nav className=' overflow-y-auto scroll-hidden'>
<nav className='font-sans overflow-y-auto scroll-hidden'>
{toc.map((tocItem) => {
const id = uuidToId(tocItem.id)
return (
@@ -79,7 +79,7 @@ const Toc = ({ toc, targetRef }) => {
)
})}
</nav>
</>
</div>
}
export default Toc

View File

@@ -9,7 +9,7 @@ import { useGlobal } from '@/lib/global'
* @returns {JSX.Element}
* @constructor
*/
const TocDrawer = ({ post, cRef, targetRef }) => {
const TocDrawer = ({ post, cRef }) => {
// 暴露给父组件 通过cRef.current.handleMenuClick 调用
useImperativeHandle(cRef, () => {
return {
@@ -22,18 +22,18 @@ const TocDrawer = ({ post, cRef, targetRef }) => {
}
const { locale } = useGlobal()
return <>
<div className='fixed top-0 right-0 z-40'>
<div className='fixed top-0 right-0 z-40 '>
{/* 侧边菜单 */}
<div
className={(showDrawer ? 'animate__slideInRight ' : ' -mr-72 animate__slideOutRight') +
' shadow-card animate__animated animate__faster max-h-96 ' +
' w-60 duration-200 fixed right-4 top-16 rounded overflow-y-auto'}>
' shadow-card animate__animated animate__faster max-h-96 ' +
' w-60 duration-200 fixed right-4 top-16 rounded overflow-y-auto py-2 bg-white dark:bg-gray-600'}>
{post && <>
<div className='text-xl font-bold text-center text-black dark:text-white bg-white dark:bg-gray-600 py-2 px-6'>
<div className='font-bold pb-2 text-center text-black dark:text-white '>
{locale.COMMON.TABLE_OF_CONTENTS}
</div>
<div className='p-6 dark:text-gray-400 text-gray-600 bg-white dark:bg-gray-800'>
<Toc toc={post.toc} targetRef={targetRef}/>
<div className='dark:text-gray-400 text-gray-600 dark:bg-gray-800'>
<Toc toc={post.toc}/>
</div>
</>
}

View File

@@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react'
import { useGlobal } from '@/lib/global'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faListOl } from '@fortawesome/free-solid-svg-icons'
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import { faListOl } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React from 'react'
/**
* 点击召唤目录抽屉
@@ -16,30 +16,9 @@ const TocDrawerButton = (props) => {
return <></>
}
const { locale } = useGlobal()
const [show, switchShow] = useState(false)
const scrollListener = () => {
const scrollY = window.pageYOffset
const shouldShow = scrollY > 100
if (shouldShow !== show) {
switchShow(shouldShow)
}
}
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
})
return (
<div id='toc-drawer-button' className='right-1 fixed bottom-52 z-20'>
<div onClick={props.onClick} className={(show ? 'animate__fadeInRight' : 'hidden') + ' animate__animated animate__faster glassmorphism cursor-pointer' }>
<div className='dark:text-gray-200 text-center transform hover:scale-150 duration-200 text-xs flex justify-center items-center w-8 h-8' title={locale.POST.TOP} >
<FontAwesomeIcon icon={faListOl}/>
</div>
</div>
</div>
)
return (<div onClick={props.onClick} className='py-2 px-3 cursor-pointer dark:text-gray-200 text-center transform hover:scale-150 duration-200 flex justify-center items-center' title={locale.POST.TOP} >
<FontAwesomeIcon icon={faListOl}/>
</div>)
}
export default TocDrawerButton

View File

@@ -1,12 +1,16 @@
import BLOG from '@/blog.config'
import SideBarDrawer from '@/components/SideBarDrawer'
import { useGlobal } from '@/lib/global'
import { faBars, faSearch } from '@fortawesome/free-solid-svg-icons'
import { faAngleDoubleRight, faBars, faSearch, faTag, faThList, faTimes } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import throttle from 'lodash.throttle'
import { useCallback, useEffect, useRef } from 'react'
import Link from 'next/link'
import { useCallback, useEffect, useRef, useState } from 'react'
import CategoryGroup from './CategoryGroup'
import Collapse from './Collapse'
import Logo from './Logo'
import MenuButtonGroup from './MenuButtonGroup'
import SearchDrawer from './SearchDrawer'
import TagGroups from './TagGroups'
let windowTop = 0
@@ -15,8 +19,7 @@ let windowTop = 0
* @param {*} param0
* @returns
*/
const TopNav = ({ tags, currentTag, post, slot, categories, currentCategory, autoHide = true }) => {
const drawer = useRef()
const TopNav = ({ tags, currentTag, post, slot, categories, currentCategory, autoHide = true, postCount }) => {
const { locale } = useGlobal()
const searchDrawer = useRef()
@@ -43,19 +46,55 @@ const TopNav = ({ tags, currentTag, post, slot, categories, currentCategory, aut
BLOG.autoCollapsedNavBar && window.removeEventListener('scroll', scrollTrigger)
}
}, [])
const [isOpen, changeShow] = useState(false)
const toggleMenuOpen = () => {
changeShow(!isOpen)
}
const searchDrawerSlot = <>
{ categories && (
<section className='mt-8'>
<div className='text-sm flex flex-nowrap justify-between font-light px-2'>
<div className='text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faThList} className='mr-2' />{locale.COMMON.CATEGORY}</div>
<Link href='/category' passHref>
<a className='mb-3 text-gray-400 hover:text-black dark:text-gray-400 dark:hover:text-white hover:underline cursor-pointer'>
{locale.COMMON.MORE} <FontAwesomeIcon icon={faAngleDoubleRight} />
</a>
</Link>
</div>
<CategoryGroup currentCategory={currentCategory} categories={categories} />
</section>
) }
{ tags && (
<section className='mt-4'>
<div className='text-sm py-2 px-2 flex flex-nowrap justify-between font-light dark:text-gray-200'>
<div className='text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faTag} className='mr-2'/>{locale.COMMON.TAGS}</div>
<Link href='/tag' passHref>
<a className='text-gray-400 hover:text-black dark:hover:text-white hover:underline cursor-pointer'>
{locale.COMMON.MORE} <FontAwesomeIcon icon={faAngleDoubleRight} />
</a>
</Link>
</div>
<div className='p-2'>
<TagGroups tags={tags} currentTag={currentTag} />
</div>
</section>
) }
</>
return (<div id='top-nav' className='z-40 block lg:hidden'>
{/* 侧面抽屉 */}
<SideBarDrawer post={post} currentTag={currentTag} cRef={drawer} tags={tags} slot={slot} categories={categories} currentCategory={currentCategory}/>
<SearchDrawer cRef={searchDrawer}/>
<SearchDrawer cRef={searchDrawer} slot={searchDrawerSlot}/>
{/* 导航栏 */}
<div id='sticky-nav' className={`${BLOG.topNavType !== 'normal' ? 'fixed' : ''} flex animate__animated animate__fadeIn lg:relative w-full top-0 z-20 transform duration-500`}>
<div id='sticky-nav' className={`${BLOG.topNavType !== 'normal' ? 'fixed' : ''} lg:relative w-full top-0 z-20 transform duration-500`}>
<div className='w-full flex justify-between items-center p-4 bg-black text-white'>
{/* 左侧LOGO 标题 */}
<div className='flex flex-none flex-grow-0'>
<div onClick={() => { drawer.current.handleSwitchSideDrawerVisible() }}
className='w-8 cursor-pointer'>
<FontAwesomeIcon icon={faBars} size={'lg'}/>
<div onClick={toggleMenuOpen} className='w-8 cursor-pointer'>
{ isOpen ? <FontAwesomeIcon icon={faTimes} size={'lg'}/> : <FontAwesomeIcon icon={faBars} size={'lg'}/> }
</div>
</div>
@@ -70,6 +109,12 @@ const TopNav = ({ tags, currentTag, post, slot, categories, currentCategory, aut
</div>
</div>
</div>
<Collapse isOpen={isOpen}>
<div className='bg-white py-1 px-5'>
<MenuButtonGroup postCount={postCount}/>
</div>
</Collapse>
</div>
</div>)

View File

@@ -10,7 +10,9 @@ import SideAreaRight from '@/components/SideAreaRight'
import TopNav from '@/components/TopNav'
import { useGlobal } from '@/lib/global'
import PropTypes from 'prop-types'
import React, { useRef } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import smoothscroll from 'smoothscroll-polyfill'
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
* @param children
@@ -38,6 +40,7 @@ const BaseLayout = ({
post,
postCount,
sideBarSlot,
floatSlot,
rightAreaSlot,
currentSearch,
currentCategory,
@@ -48,19 +51,41 @@ const BaseLayout = ({
const { onLoading } = useGlobal()
const targetRef = useRef(null)
const [show, switchShow] = useState(false)
const [percent, changePercent] = useState(0) // 页面阅读百分比
const scrollListener = () => {
const targetRef = document.getElementById('wrapper')
const clientHeight = targetRef?.clientHeight
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
if (per > 100) per = 100
const shouldShow = scrollY > 100 && per > 0
if (shouldShow !== show) {
switchShow(shouldShow)
}
changePercent(per)
}
useEffect(() => {
smoothscroll.polyfill()
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
return (<>
<CommonHead meta={meta} />
<TopNav tags={tags} post={post} slot={sideBarSlot} currentSearch={currentSearch} categories={categories} currentCategory={currentCategory} />
<TopNav tags={tags} postCount={postCount} post={post} slot={sideBarSlot} currentSearch={currentSearch} categories={categories} currentCategory={currentCategory} />
<>{headerSlot}</>
<div className='h-0.5 w-full bg-gray-700 dark:bg-gray-600 hidden lg:block'></div>
<main id='wrapper' className='flex justify-center flex-1 mx-auto pb-12'>
<main id='wrapper' className='flex justify-center flex-1 pb-12'>
<SideAreaLeft targetRef={targetRef} post={post} postCount={postCount} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
<section id='center' className={`${BLOG.topNavType !== 'normal' ? 'mt-14' : ''} flex-grow md:mt-0 max-w-5xl min-h-screen w-full`} ref={targetRef}>
<section id='center' className={`${BLOG.topNavType !== 'normal' ? 'mt-14' : ''} lg:max-w-3xl xl:max-w-4xl flex-grow md:mt-0 min-h-screen w-full`} ref={targetRef}>
{onLoading
? <LoadingCover/>
: <>
@@ -71,10 +96,17 @@ const BaseLayout = ({
<SideAreaRight targetRef={targetRef} post={post} slot={rightAreaSlot} postCount={postCount} tags={tags} currentSearch={currentSearch} currentTag={currentTag} categories={categories} currentCategory={currentCategory}/>
</main>
{/* 右下角悬浮 */}
<div className='right-8 bottom-10 lg:right-2 lg:bottom-2 fixed justify-end z-20 font-sans'>
<div className={(show ? 'animate__animated ' : 'hidden') + ' animate__fadeInUp rounded-md glassmorphism justify-center duration-500 animate__faster flex space-x-2 items-center cursor-pointer '}>
<JumpToTopButton percent={percent}/>
<JumpToBottomButton />
<FloatDarkModeButton/>
{floatSlot}
</div>
</div>
<Footer title={meta.title}/>
<JumpToTopButton showPercent={false} />
<JumpToBottomButton showPercent={false}/>
<FloatDarkModeButton/>
</>
)
}

View File

@@ -2,12 +2,12 @@ import BLOG from '@/blog.config'
import { NotionAPI } from 'notion-client'
import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
export async function getPostBlocks (id, from) {
export async function getPostBlocks (id, from, slice) {
const cacheKey = 'page_block_' + id
let pageBlock = await getDataFromCache(cacheKey)
if (pageBlock) {
console.log('[请求缓存]:', `from:${from}`, `id:${id}`)
return pageBlock
return filterPostBlocks(id, pageBlock, slice)
}
const authToken = BLOG.notionAccessToken || null
const api = new NotionAPI({ authToken })
@@ -20,24 +20,57 @@ export async function getPostBlocks (id, from) {
return null
}
// 去掉不用的字段
for (const j in pageBlock?.block) {
const b = pageBlock?.block[j]
if (b) {
delete b.role
delete b.value?.version
delete b.value?.created_time
delete b.value?.last_edited_time
delete b.value?.created_by_table
delete b.value?.created_by_id
delete b.value?.last_edited_by_table
delete b.value?.last_edited_by_id
delete b.value?.space_id
}
}
if (pageBlock) {
await setDataToCache(cacheKey, pageBlock)
return filterPostBlocks(id, pageBlock, slice)
}
return pageBlock
}
/**
*
* @param {*} id 页面ID
* @param {*} pageBlock 页面元素
* @param {*} slice 截取数量
* @returns
*/
function filterPostBlocks (id, pageBlock, slice) {
const clonePageBlock = deepClone(pageBlock)
let count = 0
for (const i in clonePageBlock?.block) {
const b = clonePageBlock?.block[i]
if (slice && slice > 0 && count > slice) {
delete clonePageBlock?.block[i]
continue
}
count++
delete b?.role
delete b?.value?.version
delete b?.value?.created_time
delete b?.value?.last_edited_time
delete b?.value?.created_by_table
delete b?.value?.created_by_id
delete b?.value?.last_edited_by_table
delete b?.value?.last_edited_by_id
delete b?.value?.space_id
}
// 去掉不用的字段
if (id === BLOG.notionPageId) {
return clonePageBlock
}
return clonePageBlock
}
function deepClone (obj) {
const newObj = Array.isArray(obj) ? [] : {}
if (obj && typeof obj === 'object') {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
newObj[key] = (obj && typeof obj[key] === 'object') ? deepClone(obj[key]) : obj[key]
}
}
}
return newObj
}

View File

@@ -7,7 +7,6 @@ import {
} from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import Custom404 from '@/pages/404'
import { getPageTableOfContents } from 'notion-utils'
import 'prismjs'
import 'prismjs/components/prism-bash'
import 'prismjs/components/prism-javascript'
@@ -21,7 +20,7 @@ import React from 'react'
* @param {*} param0
* @returns
*/
const About = ({ post, blockMap, tags, prev, next, allPosts, categories }) => {
const About = ({ post, tags, prev, next, postCount, categories }) => {
if (!post) {
return <Custom404 />
}
@@ -39,10 +38,10 @@ const About = ({ post, blockMap, tags, prev, next, allPosts, categories }) => {
meta={meta}
tags={tags}
post={post}
totalPosts={allPosts}
postCount={postCount}
categories={categories}
>
<ArticleDetail post={post} blockMap={blockMap} allPosts={allPosts} prev={prev} next={next} />
<ArticleDetail post={post} prev={prev} next={next} />
</BaseLayout>
)
}
@@ -56,20 +55,14 @@ export async function getStaticProps () {
return { props: {}, revalidate: 1 }
}
const blockMap = await getPostBlocks(post.id, 'slug')
post.toc = []
if (blockMap) {
post.blockMap = blockMap
post.content = Object.keys(blockMap.block)
post.toc = getPageTableOfContents(post, blockMap)
}
post.blockMap = await getPostBlocks(post.id, 'slug')
const index = allPosts.indexOf(post)
const prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
const next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
return {
props: { post, blockMap, tags, prev, next, allPosts, categories, postCount, latestPosts },
props: { post, tags, prev, next, categories, postCount, latestPosts },
revalidate: 1
}
}

View File

@@ -9,7 +9,7 @@ import React, { useEffect } from 'react'
export async function getStaticProps () {
const from = 'index'
const { allPosts, categories, tags, postCount } =
await getGlobalNotionData({ from, includePage: true })
await getGlobalNotionData({ from })
return {
props: {

View File

@@ -1,10 +1,16 @@
import BLOG from '@/blog.config'
import ArticleDetail from '@/components/ArticleDetail'
import Card from '@/components/Card'
import LatestPostsGroup from '@/components/LatestPostsGroup'
import Live2D from '@/components/Live2D'
import TocDrawer from '@/components/TocDrawer'
import TocDrawerButton from '@/components/TocDrawerButton'
import BaseLayout from '@/layouts/BaseLayout'
import { getAllPosts, getPostBlocks } from '@/lib/notion'
import { getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import Custom404 from '@/pages/404'
import { getPageTableOfContents } from 'notion-utils'
import { useRef } from 'react'
/**
* 根据notion的slug访问页面
@@ -16,7 +22,6 @@ const Slug = ({
tags,
prev,
next,
allPosts,
recommendPosts,
categories,
postCount,
@@ -32,6 +37,12 @@ const Slug = ({
tags: post.tags
}
const drawerRight = useRef(null)
const targetRef = typeof window !== 'undefined' ? document.getElementById('container') : null
post.content = Object.keys(post?.blockMap?.block)
post.toc = getPageTableOfContents(post, post.blockMap)
const floatSlot = post?.toc?.length > 1 ? <div className="block lg:hidden"><TocDrawerButton onClick={() => { drawerRight?.current?.handleSwitchVisible() }} /></div> : null
return (
<BaseLayout
meta={meta}
@@ -39,8 +50,11 @@ const Slug = ({
post={post}
postCount={postCount}
latestPosts={latestPosts}
totalPosts={allPosts}
categories={categories}
floatSlot={floatSlot}
rightAreaSlot={
BLOG.widget?.showLatestPost && <Card><LatestPostsGroup posts={latestPosts} /></Card>
}
>
<ArticleDetail
post={post}
@@ -48,17 +62,31 @@ const Slug = ({
prev={prev}
next={next}
/>
{/* 悬浮目录按钮 */}
<div className="block lg:hidden">
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} />
</div>
{/* 宠物 */}
<Live2D/>
</BaseLayout>
)
}
export async function getStaticPaths () {
let posts = []
if (BLOG.isProd) {
posts = await getAllPosts({ from: 'slug - paths', includePage: false })
if (!BLOG.isProd) {
return {
paths: [],
fallback: true
}
}
const from = '[slug-paths'
const { allPosts } = await getGlobalNotionData({ from, includePage: false })
return {
paths: posts.map(row => ({ params: { slug: row.slug } })),
paths: allPosts.map(row => ({ params: { slug: row.slug } })),
fallback: true
}
}
@@ -74,13 +102,7 @@ export async function getStaticProps ({ params: { slug } }) {
return { props: {}, revalidate: 1 }
}
const blockMap = await getPostBlocks(post.id, 'slug')
if (blockMap) {
post.blockMap = blockMap
post.content = Object.keys(blockMap.block)
post.toc = getPageTableOfContents(post, blockMap)
delete post.content
}
post.blockMap = await getPostBlocks(post.id, 'slug')
// 上一篇、下一篇文章关联
const index = allPosts.indexOf(post)
@@ -95,7 +117,6 @@ export async function getStaticProps ({ params: { slug } }) {
tags,
prev,
next,
allPosts,
recommendPosts,
categories,
postCount,

View File

@@ -1,6 +1,7 @@
import BLOG from '@/blog.config'
import BlogPostListPage from '@/components/BlogPostListPage'
import BlogPostListScroll from '@/components/BlogPostListScroll'
import Card from '@/components/Card'
import Header from '@/components/Header'
import LatestPostsGroup from '@/components/LatestPostsGroup'
import BaseLayout from '@/layouts/BaseLayout'
@@ -28,12 +29,11 @@ export async function getStaticProps () {
)
for (const i in postsToShow) {
const post = postsToShow[i]
const blockMap = await getPostBlocks(post.id, 'slug')
const blockMap = await getPostBlocks(post.id, 'slug', BLOG.home.previewLines)
if (blockMap) {
post.blockMap = blockMap
}
}
console.log('加载文章预览完成')
}
return {
@@ -57,7 +57,7 @@ const Index = ({ posts, tags, meta, categories, postCount, latestPosts }) => {
tags={tags}
sideBarSlot={<LatestPostsGroup posts={latestPosts} />}
rightAreaSlot={
BLOG.widget?.showLatestPost && <LatestPostsGroup posts={latestPosts} />
BLOG.widget?.showLatestPost && <Card><LatestPostsGroup posts={latestPosts} /></Card>
}
postCount={postCount}
categories={categories}

View File

@@ -54,12 +54,11 @@ export async function getStaticProps ({ params: { page } }) {
for (const i in postsToShow) {
const post = postsToShow[i]
const blockMap = await getPostBlocks(post.id, 'slug')
const blockMap = await getPostBlocks(post.id, 'slug', BLOG.home.previewLines)
if (blockMap) {
post.blockMap = blockMap
}
}
console.log('加载文章预览完成')
return {
props: {

View File

@@ -25,7 +25,7 @@ export async function getStaticProps () {
}
}
const Search = ({ posts, tags, categories, postCount, latestPosts }) => {
const Search = ({ posts, tags, categories, postCount }) => {
let filteredPosts = []
const searchKey = getSearchKey()
if (searchKey) {

View File

@@ -4,7 +4,6 @@ import StickyBar from '@/components/StickyBar'
import TagList from '@/components/TagList'
import BaseLayout from '@/layouts/BaseLayout'
import { useGlobal } from '@/lib/global'
import { getAllPosts } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
export default function Tag ({ tags, posts, tag, categories, postCount, latestPosts }) {
@@ -56,27 +55,21 @@ export async function getStaticProps ({ params }) {
* @param {*}} allPosts
* @returns
*/
function getTagNames (allPosts) {
const tags = allPosts.map(p => p.tags).flat()
const tagObj = {}
function getTagNames (tags) {
const tagNames = []
tags.forEach(tag => {
if (tag in tagObj) {
tagObj[tag]++
} else {
tagObj[tag] = 1
}
tagNames.push(tag.name)
})
return tagObj
return tagNames
}
export async function getStaticPaths () {
const from = 'tag-static-path'
const posts = await getAllPosts({ from })
const tagNames = getTagNames(posts)
const { tags } = await getGlobalNotionData({ from, tagsCount: 0 })
const tagNames = getTagNames(tags)
return {
paths: Object.keys(tagNames).map(tag => ({ params: { tag } })),
paths: Object.keys(tagNames).map(index => ({ params: { tag: tagNames[index] } })),
fallback: true
}
}

View File

@@ -7,14 +7,14 @@ import { faTags } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React from 'react'
export default function Tag ({ tags, allPosts, categories, postCount, latestPosts }) {
export default function Tag ({ tags, categories, postCount, latestPosts }) {
const { locale } = useGlobal()
const meta = {
title: `${locale.COMMON.TAGS} | ${BLOG.title}`,
description: BLOG.description,
type: 'website'
}
return <BaseLayout meta={meta} categories={categories} totalPosts={allPosts} postCount={postCount} latestPosts={latestPosts}>
return <BaseLayout meta={meta} categories={categories} postCount={postCount} latestPosts={latestPosts}>
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
<div className='dark:text-gray-200 mb-5'><FontAwesomeIcon icon={faTags} className='mr-4'/>{locale.COMMON.TAGS}:</div>
<div id='tags-list' className='duration-200 flex flex-wrap'>
@@ -28,12 +28,11 @@ export default function Tag ({ tags, allPosts, categories, postCount, latestPost
export async function getStaticProps () {
const from = 'tag-index-props'
const { allPosts, categories, tags, postCount, latestPosts } = await getGlobalNotionData({ from, includePage: true, tagsCount: 0 })
const { categories, tags, postCount, latestPosts } = await getGlobalNotionData({ from, includePage: true, tagsCount: 0 })
return {
props: {
tags,
posts: allPosts,
categories,
postCount,
latestPosts

BIN
public/avatar.jpg Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -163,4 +163,29 @@ nav {
.medium-zoom-overlay{
background: none !important;
}
.article-cover{
-webkit-text-size-adjust: 100%;
font-size: 14px;
font-family: -apple-system,SF UI Text,Arial,PingFang SC,Hiragino Sans GB,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif;
-webkit-font-smoothing: antialiased;
color: rgba(0,0,0,.75);
font-variant-ligatures: common-ligatures;
line-height: 1.625;
tab-size: 4;
outline: 0;
font-weight: normal;
-webkit-box-sizing: border-box;
padding: 0;
margin: 0;
position: relative;
z-index: 998;
padding-top: 160px;
bottom: -1px;
margin-top: -200px;
text-align: center;
width: 100%;
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,#fff 70%);
padding-bottom: 34px;
}