diff --git a/blog.config.js b/blog.config.js
index fcaee9bd..2b57efcd 100644
--- a/blog.config.js
+++ b/blog.config.js
@@ -438,6 +438,15 @@ const BLOG = {
MAILCHIMP_LIST_ID: process.env.MAILCHIMP_LIST_ID || null, // 开启mailichimp邮件订阅 客户列表ID ,具体使用方法参阅文档
MAILCHIMP_API_KEY: process.env.MAILCHIMP_API_KEY || null, // 开启mailichimp邮件订阅 APIkey
+ // ANIMATE.css 动画
+ 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
+
+ // 网站图片
+ IMG_LAZY_LOAD_PLACEHOLDER: process.env.NEXT_PUBLIC_IMG_LAZY_LOAD_PLACEHOLDER || 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', // 懒加载占位图片地址,支持base64或url
+ 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)
+ IMG_SHADOW: process.env.NEXT_PUBLIC_IMG_SHADOW || false, // 文章图片是否自动添加阴影
+ IMG_COMPRESS_WIDTH: process.env.NEXT_PUBLIC_IMG_COMPRESS_WIDTH || 800, // Notion图片压缩宽度
+
// 作废配置
AVATAR: process.env.NEXT_PUBLIC_AVATAR || '/avatar.svg', // 作者头像,被notion中的ICON覆盖。若无ICON则取public目录下的avatar.png
TITLE: process.env.NEXT_PUBLIC_TITLE || 'NotionNext BLOG', // 站点标题 ,被notion中的页面标题覆盖;此处请勿留空白,否则服务器无法编译
@@ -446,12 +455,6 @@ const BLOG = {
DESCRIPTION:
process.env.NEXT_PUBLIC_DESCRIPTION || '这是一个由NotionNext生成的站点', // 站点描述,被notion中的页面描述覆盖
- // 网站图片
- IMG_LAZY_LOAD_PLACEHOLDER: process.env.NEXT_PUBLIC_IMG_LAZY_LOAD_PLACEHOLDER || 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', // 懒加载占位图片地址,支持base64或url
- 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)
- IMG_SHADOW: process.env.NEXT_PUBLIC_IMG_SHADOW || false, // 文章图片是否自动添加阴影
- IMG_COMPRESS_WIDTH: process.env.NEXT_PUBLIC_IMG_COMPRESS_WIDTH || 800, // Notion图片压缩宽度
-
// 开发相关
NOTION_ACCESS_TOKEN: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public
DEBUG: process.env.NEXT_PUBLIC_DEBUG || false, // 是否显示调试按钮
diff --git a/components/Badge.js b/components/Badge.js
new file mode 100644
index 00000000..9eab49a2
--- /dev/null
+++ b/components/Badge.js
@@ -0,0 +1,11 @@
+/**
+ * 红点
+ */
+export default function Badge() {
+ return <>
+ {/* 红点 */}
+
+
+
+ >
+}
diff --git a/components/ExternalPlugins.js b/components/ExternalPlugins.js
index 78da3094..3df09e6c 100644
--- a/components/ExternalPlugins.js
+++ b/components/ExternalPlugins.js
@@ -80,6 +80,7 @@ const ExternalPlugin = (props) => {
const GLOBAL_JS = siteConfig('GLOBAL_JS')
const CLARITY_ID = siteConfig('CLARITY_ID')
const IMG_SHADOW = siteConfig('IMG_SHADOW')
+ const ANIMATE_CSS_URL = siteConfig('ANIMATE_CSS_URL')
// 自定义样式css和js引入
if (isBrowser) {
@@ -93,6 +94,10 @@ const ExternalPlugin = (props) => {
loadExternalResource('/css/img-shadow.css', 'css')
}
+ if (ANIMATE_CSS_URL) {
+ loadExternalResource(ANIMATE_CSS_URL, 'css')
+ }
+
// 导入外部自定义脚本
if (CUSTOM_EXTERNAL_JS && CUSTOM_EXTERNAL_JS.length > 0) {
for (const url of CUSTOM_EXTERNAL_JS) {
diff --git a/lib/wow.js b/lib/wow.js
index e4682e46..7b09d2c3 100644
--- a/lib/wow.js
+++ b/lib/wow.js
@@ -5,6 +5,7 @@ const { loadExternalResource } = require('./utils');
* 是data-aos的平替 aos ≈ wowjs + animate
*/
export const loadWowJS = async () => {
+ await loadExternalResource('/css/wow/animate.css', 'css');
await loadExternalResource('https://cdnjs.cloudflare.com/ajax/libs/wow/1.1.2/wow.min.js', 'js');
// 配合animatecss 实现延时滚动动画,和AOS动画相似
const WOW = window.WOW;
diff --git a/pages/_app.js b/pages/_app.js
index 9b6a74bc..cf71fe18 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -1,4 +1,4 @@
-import '@/styles/animate.css' // @see https://animate.style/
+// import '@/styles/animate.css' // @see https://animate.style/
import '@/styles/globals.css'
import '@/styles/nprogress.css'
import '@/styles/utility-patterns.css'
diff --git a/styles/animate.css b/public/css/wow/animate.css
similarity index 99%
rename from styles/animate.css
rename to public/css/wow/animate.css
index c83e40e1..ae252ed1 100644
--- a/styles/animate.css
+++ b/public/css/wow/animate.css
@@ -2,7 +2,7 @@
/*!
* animate.css -https://daneden.github.io/animate.css/
- * Version - 3.7.2
+ * Version - 3.7.2 适配wowjs
* Licensed under the MIT license - http://opensource.org/licenses/MIT
*
* Copyright (c) 2019 Daniel Eden
diff --git a/themes/gitbook/components/BlogPostCard.js b/themes/gitbook/components/BlogPostCard.js
index 891e3ea7..a2870973 100644
--- a/themes/gitbook/components/BlogPostCard.js
+++ b/themes/gitbook/components/BlogPostCard.js
@@ -3,17 +3,22 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { checkContainHttp, sliceUrlFromHttp } from '@/lib/utils'
import NotionIcon from '@/components/NotionIcon'
+import Badge from '@/components/Badge'
+import CONFIG from '../config'
const BlogPostCard = ({ post, className }) => {
const router = useRouter()
const currentSelected = router.asPath.split('?')[0] === '/' + post.slug
const url = checkContainHttp(post.slug) ? sliceUrlFromHttp(post.slug) : `${siteConfig('SUB_PATH', '')}/${post.slug}`
return (
-
-
-
{post.title}
+
+
+
+ {post.title}
+
+ {/* 最新文章加个红点 */}
+ {post?.isLatest && siteConfig('GITBOOK_LATEST_POST_RED_BADGE', false, CONFIG) &&
}
-
)
}
diff --git a/themes/gitbook/components/NavPostItem.js b/themes/gitbook/components/NavPostItem.js
index b357b1af..97ab3425 100644
--- a/themes/gitbook/components/NavPostItem.js
+++ b/themes/gitbook/components/NavPostItem.js
@@ -1,6 +1,9 @@
import BlogPostCard from './BlogPostCard'
import { useState } from 'react'
import Collapse from '@/components/Collapse'
+import Badge from '@/components/Badge'
+import { siteConfig } from '@/lib/config'
+import CONFIG from '../config'
/**
* 导航列表
@@ -17,12 +20,15 @@ const NavPostItem = (props) => {
changeIsOpen(!isOpen)
}
+ const groupHasLatest = group?.items?.some(post => post.isLatest)
+
if (group?.category) {
return <>
+ 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}>
{group?.category}
-
+
+ {groupHasLatest && siteConfig('GITBOOK_LATEST_POST_RED_BADGE', false, CONFIG) && !isOpen &&
}
{group?.items?.map(post => (
diff --git a/themes/gitbook/components/SearchInput.js b/themes/gitbook/components/SearchInput.js
index cab041fe..e1560a51 100644
--- a/themes/gitbook/components/SearchInput.js
+++ b/themes/gitbook/components/SearchInput.js
@@ -103,7 +103,7 @@ const SearchInput = ({ currentSearch, cRef, className }) => {
import('@/components/WWAds'), { ssr: false })
const ThemeGlobalGitbook = createContext()
export const useGitBookGlobal = () => useContext(ThemeGlobalGitbook)
+/**
+ * 给最新的文章标一个红点
+ */
+function getNavPagesWithLatest(allNavPages, latestPosts, post) {
+ // 检测需要去除红点的文章 ; localStorage 的 posts_read = {"${post.id}":"Date()"} 保存了所有已读的页面id,和阅读时间;
+ // 如果页面在这里面则不显示红点
+ const postRead = JSON.parse(localStorage.getItem('post_read') || '[]');
+ if (post && !postRead.includes(post.id)) {
+ postRead.push(post.id);
+ }
+ localStorage.setItem('post_read', JSON.stringify(postRead));
+
+ return allNavPages?.map(item => {
+ const res = {
+ id: item.id,
+ title: item.title || '',
+ pageCoverThumbnail: item.pageCoverThumbnail || '',
+ category: item.category || null,
+ tags: item.tags || null,
+ summary: item.summary || null,
+ slug: item.slug,
+ pageIcon: item.pageIcon || '',
+ lastEditedDate: item.lastEditedDate
+ }
+ if (latestPosts.some(post => post.id === item.id) && !postRead.includes(item.id)) {
+ return { ...res, isLatest: true };
+ } else {
+ return res;
+ }
+ })
+}
+
/**
* 基础布局
* 采用左右两侧布局,移动端使用顶部导航栏
@@ -49,7 +81,7 @@ export const useGitBookGlobal = () => useContext(ThemeGlobalGitbook)
* @constructor
*/
const LayoutBase = (props) => {
- const { children, post, allNavPages, slotLeft, slotRight, slotTop } = props
+ const { children, post, allNavPages, latestPosts, slotLeft, slotRight, slotTop } = props
const { onLoading, fullWidth } = useGlobal()
const router = useRouter()
const [tocVisible, changeTocVisible] = useState(false)
@@ -60,8 +92,8 @@ const LayoutBase = (props) => {
const searchModal = useRef(null)
useEffect(() => {
- setFilteredNavPages(allNavPages)
- }, [post])
+ setFilteredNavPages(getNavPagesWithLatest(allNavPages, latestPosts, post))
+ }, [router])
return (
@@ -78,7 +110,7 @@ const LayoutBase = (props) => {
{/* 左侧推拉抽屉 */}
{fullWidth
? null
- : (
+ : (
{slotLeft}
@@ -94,7 +126,7 @@ const LayoutBase = (props) => {
) }
-
+
{slotTop}
diff --git a/themes/heo/components/Hero.js b/themes/heo/components/Hero.js
index 23edfc8b..9143e064 100644
--- a/themes/heo/components/Hero.js
+++ b/themes/heo/components/Hero.js
@@ -24,9 +24,7 @@ const Hero = props => {
diff --git a/themes/simple/components/RecommendPosts.js b/themes/simple/components/RecommendPosts.js
new file mode 100644
index 00000000..fd214fa0
--- /dev/null
+++ b/themes/simple/components/RecommendPosts.js
@@ -0,0 +1,32 @@
+import Link from 'next/link'
+import { useGlobal } from '@/lib/global'
+import CONFIG from '../config'
+import { siteConfig } from '@/lib/config'
+
+/**
+ * 展示文章推荐
+ */
+const RecommendPosts = ({ recommendPosts }) => {
+ const { locale } = useGlobal()
+ if (!siteConfig('SIMPLE_ARTICLE_RECOMMEND_POSTS', null, CONFIG) || !recommendPosts || recommendPosts.length < 1) {
+ return <>>
+ }
+
+ return (
+
+
{locale.COMMON.RELATE_POSTS} :
+
+ {recommendPosts.map(post => (
+ -
+
+
+ {post.title}
+
+
+
+ ))}
+
+
+ )
+}
+export default RecommendPosts
diff --git a/themes/simple/components/SideBar.js b/themes/simple/components/SideBar.js
index 3f109cb9..1f4fa2e3 100644
--- a/themes/simple/components/SideBar.js
+++ b/themes/simple/components/SideBar.js
@@ -13,22 +13,14 @@ export default function SideBar (props) {
const { notice } = props
return (<>
-
-
-
-
>)
}
diff --git a/themes/simple/config.js b/themes/simple/config.js
index bbacb396..bc0b8fc4 100644
--- a/themes/simple/config.js
+++ b/themes/simple/config.js
@@ -11,6 +11,8 @@ const CONFIG = {
SIMPLE_POST_COVER_ENABLE: process.env.NEXT_PUBLIC_SIMPLE_POST_COVER_ENABLE || false, // 是否展示博客封面
+ SIMPLE_ARTICLE_RECOMMEND_POSTS: process.env.NEXT_PUBLIC_SIMPLE_ARTICLE_RECOMMEND_POSTS || true, // 文章详情底部显示推荐
+
// 菜单配置
SIMPLE_MENU_CATEGORY: true, // 显示分类
SIMPLE_MENU_TAG: true, // 显示标签
diff --git a/themes/simple/index.js b/themes/simple/index.js
index 02cb8dd4..60867451 100644
--- a/themes/simple/index.js
+++ b/themes/simple/index.js
@@ -31,6 +31,7 @@ const Footer = dynamic(() => import('./components/Footer'), { ssr: false });
const SearchInput = dynamic(() => import('./components/SearchInput'), { ssr: false });
const WWAds = dynamic(() => import('@/components/WWAds'), { ssr: false });
const BlogListPage = dynamic(() => import('./components/BlogListPage'), { ssr: false })
+const RecommendPosts = dynamic(() => import('./components/RecommendPosts'), { ssr: false })
// 主题全局状态
const ThemeGlobalSimple = createContext()
@@ -175,7 +176,7 @@ const LayoutArchive = props => {
* @returns
*/
const LayoutSlug = props => {
- const { post, lock, validPassword, prev, next } = props
+ const { post, lock, validPassword, prev, next, recommendPosts } = props
const { fullWidth } = useGlobal()
return (
@@ -201,7 +202,10 @@ const LayoutSlug = props => {
{/* 广告嵌入 */}
- {post?.type === 'Post' &&
}
+ {post?.type === 'Post' && <>
+
+
+ >}
{/* 评论区 */}
diff --git a/themes/starter/style.js b/themes/starter/style.js
index c176c16d..cfe51df6 100644
--- a/themes/starter/style.js
+++ b/themes/starter/style.js
@@ -8,7 +8,7 @@
const Style = () => {
return