Merge branch 'main' into feat/codeQL2

This commit is contained in:
bhwa23
2024-03-06 15:25:26 +08:00
committed by GitHub
17 changed files with 145 additions and 52 deletions

View File

@@ -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, // 是否显示调试按钮

11
components/Badge.js Normal file
View File

@@ -0,0 +1,11 @@
/**
* 红点
*/
export default function Badge() {
return <>
{/* 红点 */}
<span class="absolute right-1 top-1 flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
</span></>
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 (
<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' : ''}`}>
<div className="w-full select-none">
<NotionIcon icon={post?.pageIcon}/> {post.title}
<Link href={url} passHref>
<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' : ''}`}>
<div className="w-full select-none">
<NotionIcon icon={post?.pageIcon}/> {post.title}
</div>
{/* 最新文章加个红点 */}
{post?.isLatest && siteConfig('GITBOOK_LATEST_POST_RED_BADGE', false, CONFIG) && <Badge/>}
</div>
</div>
</Link>
)
}

View File

@@ -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 <>
<div onClick={toggleOpenSubMenu}
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}>
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}>
<span>{group?.category}</span>
<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>
<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>
{groupHasLatest && siteConfig('GITBOOK_LATEST_POST_RED_BADGE', false, CONFIG) && !isOpen && <Badge/>}
</div>
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
{group?.items?.map(post => (<div key={post.id} className='ml-3 border-l'>

View File

@@ -103,7 +103,7 @@ const SearchInput = ({ currentSearch, cRef, className }) => {
<input
ref={searchInputRef}
type='text'
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`}
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`}
onFocus={handleFocus}
onKeyUp={handleKeyUp}
onCompositionStart={lockSearchInput}

View File

@@ -4,6 +4,8 @@ const CONFIG = {
GITBOOK_AUTO_SORT: process.env.NEXT_PUBLIC_GITBOOK_AUTO_SORT || true, // 是否自动按分类名 归组排序文章自动归组可能会打乱您Notion中的文章顺序
GITBOOK_LATEST_POST_RED_BADGE: process.env.NEXT_PUBLIC_GITBOOK_LATEST_POST_RED_BADGE || true, // 是否给最新文章显示红点
// 菜单
GITBOOK_MENU_CATEGORY: true, // 显示分类
GITBOOK_BOOK_MENU_TAG: true, // 显示标签

View File

@@ -42,6 +42,38 @@ const WWAds = dynamic(() => 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 (
<ThemeGlobalGitbook.Provider value={{ searchModal, tocVisible, changeTocVisible, filteredNavPages, setFilteredNavPages, allNavPages, pageNavVisible, changePageNavVisible }}>
@@ -78,7 +110,7 @@ const LayoutBase = (props) => {
{/* 左侧推拉抽屉 */}
{fullWidth
? null
: (<div className={'hidden md:block border-r dark:border-transparent relative z-10 '}>
: (<div className={'hidden md:block border-r dark:border-transparent relative z-10 dark:bg-hexo-black-gray'}>
<div className='w-72 py-14 px-6 sticky top-0 overflow-y-scroll h-screen scroll-hidden'>
{slotLeft}
<SearchInput className='my-3 rounded-md' />
@@ -94,7 +126,7 @@ const LayoutBase = (props) => {
</div>
</div>) }
<div id='center-wrapper' className='flex flex-col justify-between w-full relative z-10 pt-14 min-h-screen'>
<div id='center-wrapper' className='flex flex-col justify-between w-full relative z-10 pt-14 min-h-screen dark:bg-black'>
<div id='container-inner' className={`w-full px-7 ${fullWidth ? 'px-10' : 'max-w-3xl'} justify-center mx-auto`}>
{slotTop}

View File

@@ -24,9 +24,7 @@ const Hero = props => {
<div
id="hero"
style={{ zIndex: 1 }}
className={
`animate__animated animate__fadeIn animate__fast
${HEO_HERO_REVERSE ? 'xl:flex-row-reverse' : ''}
className={`${HEO_HERO_REVERSE ? 'xl:flex-row-reverse' : ''}
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`
}
>

View File

@@ -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 (
<div className="pt-2 border pl-4 py-2 my-4 dark:text-gray-300 ">
<div className="mb-2 font-bold text-lg">{locale.COMMON.RELATE_POSTS} :</div>
<ul className="font-light text-sm">
{recommendPosts.map(post => (
<li className="py-1" key={post.id}>
<Link href={`/${post.slug}`} className="cursor-pointer hover:underline">
{post.title}
</Link>
</li>
))}
</ul>
</div>
)
}
export default RecommendPosts

View File

@@ -13,22 +13,14 @@ export default function SideBar (props) {
const { notice } = props
return (<>
<aside>
<Catalog {...props} />
</aside>
<aside>
<Live2D />
</aside>
<aside>
<Announcement post={notice} />
</aside>
<aside>
<AdSlot/>
<WWAds orientation="vertical" className="w-full" />
</aside>
</>)
}

View File

@@ -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, // 显示标签

View File

@@ -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 => {
{/* 广告嵌入 */}
<AdSlot type={'in-article'} />
{post?.type === 'Post' && <ArticleAround prev={prev} next={next} />}
{post?.type === 'Post' && <>
<ArticleAround prev={prev} next={next} />
<RecommendPosts recommendPosts={recommendPosts}/>
</>}
{/* 评论区 */}
<Comment frontMatter={post} />

View File

@@ -8,7 +8,7 @@
const Style = () => {
return <style jsx global>{`
.sticky{
#theme-starter .sticky{
position: fixed;
z-index: 9999;
background-color: rgb(255 255 255 / 0.8);
@@ -19,48 +19,48 @@ const Style = () => {
transition-duration: 150ms;
}
:is(.dark .sticky){
:is(.dark #theme-starter .sticky){
background-color: rgb(17 25 40 / 0.8);
}
.sticky {
#theme-starter .sticky {
-webkit-backdrop-filter: blur(5px);
backdrop-filter: blur(5px);
box-shadow: inset 0 -1px 0 0 rgba(0, 0, 0, 0.1);
}
.sticky .navbar-logo{
#theme-starter .sticky .navbar-logo{
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.sticky #navbarToggler span{
#theme-starter .sticky #navbarToggler span{
--tw-bg-opacity: 1;
background-color: rgb(17 25 40 / var(--tw-bg-opacity));
}
:is(.dark .sticky #navbarToggler span){
:is(.dark #theme-starter .sticky #navbarToggler span){
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.sticky #navbarCollapse li > a{
#theme-starter .sticky #navbarCollapse li > a{
--tw-text-opacity: 1;
color: rgb(17 25 40 / var(--tw-text-opacity));
}
.sticky #navbarCollapse li > a:hover{
#theme-starter .sticky #navbarCollapse li > a:hover{
--tw-text-opacity: 1;
color: rgb(55 88 249 / var(--tw-text-opacity));
opacity: 1;
}
:is(.dark .sticky #navbarCollapse li > a){
:is(.dark #theme-starter .sticky #navbarCollapse li > a){
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
:is(.dark .sticky #navbarCollapse li > a:hover){
:is(.dark #theme-starter .sticky #navbarCollapse li > a:hover){
--tw-text-opacity: 1;
color: rgb(55 88 249 / var(--tw-text-opacity));
}
@@ -69,53 +69,53 @@ const Style = () => {
opacity: 0.7;
}
.sticky #navbarCollapse li .ud-menu-scroll.active{
#theme-starter .sticky #navbarCollapse li .ud-menu-scroll.active{
--tw-text-opacity: 1;
color: rgb(55 88 249 / var(--tw-text-opacity));
opacity: 1;
}
.sticky .loginBtn{
#theme-starter .sticky .loginBtn{
--tw-text-opacity: 1;
color: rgb(17 25 40 / var(--tw-text-opacity));
}
.sticky .loginBtn:hover{
#theme-starter .sticky .loginBtn:hover{
--tw-text-opacity: 1;
color: rgb(55 88 249 / var(--tw-text-opacity));
opacity: 1;
}
:is(.dark .sticky .loginBtn){
:is(.dark #theme-starter .sticky .loginBtn){
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
:is(.dark .sticky .loginBtn:hover){
:is(.dark #theme-starter .sticky .loginBtn:hover){
--tw-text-opacity: 1;
color: rgb(55 88 249 / var(--tw-text-opacity));
}
.sticky .signUpBtn{
#theme-starter .sticky .signUpBtn{
--tw-bg-opacity: 1;
background-color: rgb(55 88 249 / var(--tw-bg-opacity));
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.sticky .signUpBtn:hover{
#theme-starter .sticky .signUpBtn:hover{
--tw-bg-opacity: 1;
background-color: rgb(27 68 200 / var(--tw-bg-opacity));
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.sticky #themeSwitcher ~ span{
#theme-starter .sticky #themeSwitcher ~ span{
--tw-text-opacity: 1;
color: rgb(17 25 40 / var(--tw-text-opacity));
}
:is(.dark .sticky #themeSwitcher ~ span){
:is(.dark #theme-starter .sticky #themeSwitcher ~ span){
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}