Skip to content

Commit a33bf9f

Browse files
authored
Merge pull request tangly1024#2145 from tangly1024/feat/theme-simple-gitbook
Feat/theme simple gitbook
2 parents 02f26f9 + 0d1004a commit a33bf9f

File tree

17 files changed

+145
-52
lines changed

17 files changed

+145
-52
lines changed

blog.config.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,15 @@ const BLOG = {
438438
MAILCHIMP_LIST_ID: process.env.MAILCHIMP_LIST_ID || null, // 开启mailichimp邮件订阅 客户列表ID ,具体使用方法参阅文档
439439
MAILCHIMP_API_KEY: process.env.MAILCHIMP_API_KEY || null, // 开启mailichimp邮件订阅 APIkey
440440

441+
// ANIMATE.css 动画
442+
ANIMATE_CSS_URL: process.env.NEXT_PUBLIC_ANIMATE_CSS_URL || 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css', // 动画CDN
443+
444+
// 网站图片
445+
IMG_LAZY_LOAD_PLACEHOLDER: process.env.NEXT_PUBLIC_IMG_LAZY_LOAD_PLACEHOLDER || 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', // 懒加载占位图片地址,支持base64或url
446+
IMG_URL_TYPE: process.env.NEXT_PUBLIC_IMG_TYPE || 'Notion', // 此配置已失效,请勿使用;AMAZON方案不再支持,仅支持Notion方案。 ['Notion','AMAZON'] 站点图片前缀 默认 Notion:(https://notion.so/images/xx) , AMAZON(https://s3.us-west-2.amazonaws.com/xxx)
447+
IMG_SHADOW: process.env.NEXT_PUBLIC_IMG_SHADOW || false, // 文章图片是否自动添加阴影
448+
IMG_COMPRESS_WIDTH: process.env.NEXT_PUBLIC_IMG_COMPRESS_WIDTH || 800, // Notion图片压缩宽度
449+
441450
// 作废配置
442451
AVATAR: process.env.NEXT_PUBLIC_AVATAR || '/avatar.svg', // 作者头像,被notion中的ICON覆盖。若无ICON则取public目录下的avatar.png
443452
TITLE: process.env.NEXT_PUBLIC_TITLE || 'NotionNext BLOG', // 站点标题 ,被notion中的页面标题覆盖;此处请勿留空白,否则服务器无法编译
@@ -446,12 +455,6 @@ const BLOG = {
446455
DESCRIPTION:
447456
process.env.NEXT_PUBLIC_DESCRIPTION || '这是一个由NotionNext生成的站点', // 站点描述,被notion中的页面描述覆盖
448457

449-
// 网站图片
450-
IMG_LAZY_LOAD_PLACEHOLDER: process.env.NEXT_PUBLIC_IMG_LAZY_LOAD_PLACEHOLDER || 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', // 懒加载占位图片地址,支持base64或url
451-
IMG_URL_TYPE: process.env.NEXT_PUBLIC_IMG_TYPE || 'Notion', // 此配置已失效,请勿使用;AMAZON方案不再支持,仅支持Notion方案。 ['Notion','AMAZON'] 站点图片前缀 默认 Notion:(https://notion.so/images/xx) , AMAZON(https://s3.us-west-2.amazonaws.com/xxx)
452-
IMG_SHADOW: process.env.NEXT_PUBLIC_IMG_SHADOW || false, // 文章图片是否自动添加阴影
453-
IMG_COMPRESS_WIDTH: process.env.NEXT_PUBLIC_IMG_COMPRESS_WIDTH || 800, // Notion图片压缩宽度
454-
455458
// 开发相关
456459
NOTION_ACCESS_TOKEN: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public
457460
DEBUG: process.env.NEXT_PUBLIC_DEBUG || false, // 是否显示调试按钮

components/Badge.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* 红点
3+
*/
4+
export default function Badge() {
5+
return <>
6+
{/* 红点 */}
7+
<span class="absolute right-1 top-1 flex h-2 w-2">
8+
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
9+
<span class="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
10+
</span></>
11+
}

components/ExternalPlugins.js

+5
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const ExternalPlugin = (props) => {
8080
const GLOBAL_JS = siteConfig('GLOBAL_JS')
8181
const CLARITY_ID = siteConfig('CLARITY_ID')
8282
const IMG_SHADOW = siteConfig('IMG_SHADOW')
83+
const ANIMATE_CSS_URL = siteConfig('ANIMATE_CSS_URL')
8384

8485
// 自定义样式css和js引入
8586
if (isBrowser) {
@@ -93,6 +94,10 @@ const ExternalPlugin = (props) => {
9394
loadExternalResource('/css/img-shadow.css', 'css')
9495
}
9596

97+
if (ANIMATE_CSS_URL) {
98+
loadExternalResource(ANIMATE_CSS_URL, 'css')
99+
}
100+
96101
// 导入外部自定义脚本
97102
if (CUSTOM_EXTERNAL_JS && CUSTOM_EXTERNAL_JS.length > 0) {
98103
for (const url of CUSTOM_EXTERNAL_JS) {

lib/wow.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { loadExternalResource } = require('./utils');
55
* 是data-aos的平替 aos ≈ wowjs + animate
66
*/
77
export const loadWowJS = async () => {
8+
await loadExternalResource('/css/wow/animate.css', 'css');
89
await loadExternalResource('https://cdnjs.cloudflare.com/ajax/libs/wow/1.1.2/wow.min.js', 'js');
910
// 配合animatecss 实现延时滚动动画,和AOS动画相似
1011
const WOW = window.WOW;

pages/_app.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import '@/styles/animate.css' // @see https://animate.style/
1+
// import '@/styles/animate.css' // @see https://animate.style/
22
import '@/styles/globals.css'
33
import '@/styles/nprogress.css'
44
import '@/styles/utility-patterns.css'

styles/animate.css public/css/wow/animate.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/*!
44
* animate.css -https://daneden.github.io/animate.css/
5-
* Version - 3.7.2
5+
* Version - 3.7.2 适配wowjs
66
* Licensed under the MIT license - http://opensource.org/licenses/MIT
77
*
88
* Copyright (c) 2019 Daniel Eden

themes/gitbook/components/BlogPostCard.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@ import Link from 'next/link'
33
import { useRouter } from 'next/router'
44
import { checkContainHttp, sliceUrlFromHttp } from '@/lib/utils'
55
import NotionIcon from '@/components/NotionIcon'
6+
import Badge from '@/components/Badge'
7+
import CONFIG from '../config'
68

79
const BlogPostCard = ({ post, className }) => {
810
const router = useRouter()
911
const currentSelected = router.asPath.split('?')[0] === '/' + post.slug
1012
const url = checkContainHttp(post.slug) ? sliceUrlFromHttp(post.slug) : `${siteConfig('SUB_PATH', '')}/${post.slug}`
1113
return (
12-
<Link href={url} passHref> <div key={post.id} className={`${className} py-1.5 cursor-pointer px-1.5 hover:bg-gray-50 rounded-md dark:hover:bg-gray-600 ${currentSelected ? 'bg-green-50 text-green-500' : ''}`}>
13-
<div className="w-full select-none">
14-
<NotionIcon icon={post?.pageIcon}/> {post.title}
14+
<Link href={url} passHref>
15+
<div key={post.id} className={`${className} relative py-1.5 cursor-pointer px-1.5 hover:bg-gray-50 rounded-md dark:hover:bg-gray-600 ${currentSelected ? 'bg-green-50 text-green-500 dark:bg-yellow-100 dark:text-yellow-600' : ''}`}>
16+
<div className="w-full select-none">
17+
<NotionIcon icon={post?.pageIcon}/> {post.title}
18+
</div>
19+
{/* 最新文章加个红点 */}
20+
{post?.isLatest && siteConfig('GITBOOK_LATEST_POST_RED_BADGE', false, CONFIG) && <Badge/>}
1521
</div>
16-
</div>
1722
</Link>
1823
)
1924
}

themes/gitbook/components/NavPostItem.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import BlogPostCard from './BlogPostCard'
22
import { useState } from 'react'
33
import Collapse from '@/components/Collapse'
4+
import Badge from '@/components/Badge'
5+
import { siteConfig } from '@/lib/config'
6+
import CONFIG from '../config'
47

58
/**
69
* 导航列表
@@ -17,12 +20,15 @@ const NavPostItem = (props) => {
1720
changeIsOpen(!isOpen)
1821
}
1922

23+
const groupHasLatest = group?.items?.some(post => post.isLatest)
24+
2025
if (group?.category) {
2126
return <>
2227
<div onClick={toggleOpenSubMenu}
23-
className='select-none flex justify-between text-sm cursor-pointer p-2 hover:bg-gray-50 rounded-md dark:hover:bg-gray-600' key={group?.category}>
28+
className='select-none relative flex justify-between text-sm cursor-pointer p-2 hover:bg-gray-50 rounded-md dark:hover:bg-gray-600' key={group?.category}>
2429
<span>{group?.category}</span>
25-
<div className='inline-flex items-center select-none pointer-events-none '><i className={`px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''}`}></i></div>
30+
<div className='inline-flex items-center select-none pointer-events-none '><i className={`px-2 fas fa-chevron-left transition-all opacity-50 duration-200 ${isOpen ? '-rotate-90' : ''}`}></i></div>
31+
{groupHasLatest && siteConfig('GITBOOK_LATEST_POST_RED_BADGE', false, CONFIG) && !isOpen && <Badge/>}
2632
</div>
2733
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
2834
{group?.items?.map(post => (<div key={post.id} className='ml-3 border-l'>

themes/gitbook/components/SearchInput.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const SearchInput = ({ currentSearch, cRef, className }) => {
103103
<input
104104
ref={searchInputRef}
105105
type='text'
106-
className={`${className} outline-none w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-900 dark:text-white`}
106+
className={`${className} outline-none w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-800 dark:text-white`}
107107
onFocus={handleFocus}
108108
onKeyUp={handleKeyUp}
109109
onCompositionStart={lockSearchInput}

themes/gitbook/config.js

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const CONFIG = {
44

55
GITBOOK_AUTO_SORT: process.env.NEXT_PUBLIC_GITBOOK_AUTO_SORT || true, // 是否自动按分类名 归组排序文章;自动归组可能会打乱您Notion中的文章顺序
66

7+
GITBOOK_LATEST_POST_RED_BADGE: process.env.NEXT_PUBLIC_GITBOOK_LATEST_POST_RED_BADGE || true, // 是否给最新文章显示红点
8+
79
// 菜单
810
GITBOOK_MENU_CATEGORY: true, // 显示分类
911
GITBOOK_BOOK_MENU_TAG: true, // 显示标签

themes/gitbook/index.js

+37-5
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,46 @@ const WWAds = dynamic(() => import('@/components/WWAds'), { ssr: false })
4242
const ThemeGlobalGitbook = createContext()
4343
export const useGitBookGlobal = () => useContext(ThemeGlobalGitbook)
4444

45+
/**
46+
* 给最新的文章标一个红点
47+
*/
48+
function getNavPagesWithLatest(allNavPages, latestPosts, post) {
49+
// 检测需要去除红点的文章 ; localStorage 的 posts_read = {"${post.id}":"Date()"} 保存了所有已读的页面id,和阅读时间;
50+
// 如果页面在这里面则不显示红点
51+
const postRead = JSON.parse(localStorage.getItem('post_read') || '[]');
52+
if (post && !postRead.includes(post.id)) {
53+
postRead.push(post.id);
54+
}
55+
localStorage.setItem('post_read', JSON.stringify(postRead));
56+
57+
return allNavPages?.map(item => {
58+
const res = {
59+
id: item.id,
60+
title: item.title || '',
61+
pageCoverThumbnail: item.pageCoverThumbnail || '',
62+
category: item.category || null,
63+
tags: item.tags || null,
64+
summary: item.summary || null,
65+
slug: item.slug,
66+
pageIcon: item.pageIcon || '',
67+
lastEditedDate: item.lastEditedDate
68+
}
69+
if (latestPosts.some(post => post.id === item.id) && !postRead.includes(item.id)) {
70+
return { ...res, isLatest: true };
71+
} else {
72+
return res;
73+
}
74+
})
75+
}
76+
4577
/**
4678
* 基础布局
4779
* 采用左右两侧布局,移动端使用顶部导航栏
4880
* @returns {JSX.Element}
4981
* @constructor
5082
*/
5183
const LayoutBase = (props) => {
52-
const { children, post, allNavPages, slotLeft, slotRight, slotTop } = props
84+
const { children, post, allNavPages, latestPosts, slotLeft, slotRight, slotTop } = props
5385
const { onLoading, fullWidth } = useGlobal()
5486
const router = useRouter()
5587
const [tocVisible, changeTocVisible] = useState(false)
@@ -60,8 +92,8 @@ const LayoutBase = (props) => {
6092
const searchModal = useRef(null)
6193

6294
useEffect(() => {
63-
setFilteredNavPages(allNavPages)
64-
}, [post])
95+
setFilteredNavPages(getNavPagesWithLatest(allNavPages, latestPosts, post))
96+
}, [router])
6597

6698
return (
6799
<ThemeGlobalGitbook.Provider value={{ searchModal, tocVisible, changeTocVisible, filteredNavPages, setFilteredNavPages, allNavPages, pageNavVisible, changePageNavVisible }}>
@@ -78,7 +110,7 @@ const LayoutBase = (props) => {
78110
{/* 左侧推拉抽屉 */}
79111
{fullWidth
80112
? null
81-
: (<div className={'hidden md:block border-r dark:border-transparent relative z-10 '}>
113+
: (<div className={'hidden md:block border-r dark:border-transparent relative z-10 dark:bg-hexo-black-gray'}>
82114
<div className='w-72 py-14 px-6 sticky top-0 overflow-y-scroll h-screen scroll-hidden'>
83115
{slotLeft}
84116
<SearchInput className='my-3 rounded-md' />
@@ -94,7 +126,7 @@ const LayoutBase = (props) => {
94126
</div>
95127
</div>) }
96128

97-
<div id='center-wrapper' className='flex flex-col justify-between w-full relative z-10 pt-14 min-h-screen'>
129+
<div id='center-wrapper' className='flex flex-col justify-between w-full relative z-10 pt-14 min-h-screen dark:bg-black'>
98130

99131
<div id='container-inner' className={`w-full px-7 ${fullWidth ? 'px-10' : 'max-w-3xl'} justify-center mx-auto`}>
100132
{slotTop}

themes/heo/components/Hero.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ const Hero = props => {
2424
<div
2525
id="hero"
2626
style={{ zIndex: 1 }}
27-
className={
28-
`animate__animated animate__fadeIn animate__fast
29-
${HEO_HERO_REVERSE ? 'xl:flex-row-reverse' : ''}
27+
className={`${HEO_HERO_REVERSE ? 'xl:flex-row-reverse' : ''}
3028
recent-post-top rounded-[12px] 2xl:px-5 recent-top-post-group max-w-[86rem] overflow-x-scroll w-full mx-auto flex-row flex-nowrap flex relative`
3129
}
3230
>
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Link from 'next/link'
2+
import { useGlobal } from '@/lib/global'
3+
import CONFIG from '../config'
4+
import { siteConfig } from '@/lib/config'
5+
6+
/**
7+
* 展示文章推荐
8+
*/
9+
const RecommendPosts = ({ recommendPosts }) => {
10+
const { locale } = useGlobal()
11+
if (!siteConfig('SIMPLE_ARTICLE_RECOMMEND_POSTS', null, CONFIG) || !recommendPosts || recommendPosts.length < 1) {
12+
return <></>
13+
}
14+
15+
return (
16+
<div className="pt-2 border pl-4 py-2 my-4 dark:text-gray-300 ">
17+
<div className="mb-2 font-bold text-lg">{locale.COMMON.RELATE_POSTS} :</div>
18+
<ul className="font-light text-sm">
19+
{recommendPosts.map(post => (
20+
<li className="py-1" key={post.id}>
21+
<Link href={`/${post.slug}`} className="cursor-pointer hover:underline">
22+
23+
{post.title}
24+
25+
</Link>
26+
</li>
27+
))}
28+
</ul>
29+
</div>
30+
)
31+
}
32+
export default RecommendPosts

themes/simple/components/SideBar.js

-8
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,14 @@ export default function SideBar (props) {
1313
const { notice } = props
1414
return (<>
1515

16-
<aside>
1716
<Catalog {...props} />
18-
</aside>
1917

20-
<aside>
2118
<Live2D />
22-
</aside>
2319

24-
<aside>
2520
<Announcement post={notice} />
26-
</aside>
2721

28-
<aside>
2922
<AdSlot/>
3023
<WWAds orientation="vertical" className="w-full" />
31-
</aside>
3224

3325
</>)
3426
}

themes/simple/config.js

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const CONFIG = {
1111

1212
SIMPLE_POST_COVER_ENABLE: process.env.NEXT_PUBLIC_SIMPLE_POST_COVER_ENABLE || false, // 是否展示博客封面
1313

14+
SIMPLE_ARTICLE_RECOMMEND_POSTS: process.env.NEXT_PUBLIC_SIMPLE_ARTICLE_RECOMMEND_POSTS || true, // 文章详情底部显示推荐
15+
1416
// 菜单配置
1517
SIMPLE_MENU_CATEGORY: true, // 显示分类
1618
SIMPLE_MENU_TAG: true, // 显示标签

themes/simple/index.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const Footer = dynamic(() => import('./components/Footer'), { ssr: false });
3131
const SearchInput = dynamic(() => import('./components/SearchInput'), { ssr: false });
3232
const WWAds = dynamic(() => import('@/components/WWAds'), { ssr: false });
3333
const BlogListPage = dynamic(() => import('./components/BlogListPage'), { ssr: false })
34+
const RecommendPosts = dynamic(() => import('./components/RecommendPosts'), { ssr: false })
3435

3536
// 主题全局状态
3637
const ThemeGlobalSimple = createContext()
@@ -175,7 +176,7 @@ const LayoutArchive = props => {
175176
* @returns
176177
*/
177178
const LayoutSlug = props => {
178-
const { post, lock, validPassword, prev, next } = props
179+
const { post, lock, validPassword, prev, next, recommendPosts } = props
179180
const { fullWidth } = useGlobal()
180181

181182
return (
@@ -201,7 +202,10 @@ const LayoutSlug = props => {
201202
{/* 广告嵌入 */}
202203
<AdSlot type={'in-article'} />
203204

204-
{post?.type === 'Post' && <ArticleAround prev={prev} next={next} />}
205+
{post?.type === 'Post' && <>
206+
<ArticleAround prev={prev} next={next} />
207+
<RecommendPosts recommendPosts={recommendPosts}/>
208+
</>}
205209

206210
{/* 评论区 */}
207211
<Comment frontMatter={post} />

0 commit comments

Comments
 (0)