Merge pull request #1109 from tangly1024/fix/website-performance

Fix/website performance
This commit is contained in:
tangly1024
2023-06-07 15:58:10 +08:00
committed by GitHub
152 changed files with 883 additions and 599 deletions

View File

@@ -1,2 +1,2 @@
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
NEXT_PUBLIC_VERSION=3.14.0
NEXT_PUBLIC_VERSION=3.15.0

View File

@@ -12,6 +12,9 @@ const BLOG = {
APPEARANCE: process.env.NEXT_PUBLIC_APPEARANCE || 'light', // ['light', 'dark', 'auto'], // light 日间模式 dark夜间模式 auto根据时间和主题自动夜间模式
APPEARANCE_DARK_TIME: process.env.NEXT_PUBLIC_APPEARANCE_DARK_TIME || [18, 6], // 夜间模式起至时间false时关闭根据时间自动切换夜间模式
// 3.14.1版本后,欢迎语在此配置,英文逗号隔开 , 即可支持多个欢迎语打字效果。
GREETING_WORDS: process.env.NEXT_PUBLIC_GREETING_WORDS || 'Hi我是一个程序员, Hi我是一个打工人,Hi我是一个干饭人,欢迎来到我的博客🎉',
CUSTOM_MENU: process.env.NEXT_PUBLIC_CUSTOM_MENU || false, // 支持Menu 类型从3.12.0版本起各主题将逐步支持灵活的二级菜单配置替代了原来的Page类型此配置是试验功能、默认关闭。
AUTHOR: process.env.NEXT_PUBLIC_AUTHOR || 'NotionNext', // 您的昵称 例如 tangly1024
@@ -95,6 +98,9 @@ const BLOG = {
CODE_MAC_BAR: true, // 代码左上角显示mac的红黄绿图标
CODE_LINE_NUMBERS: process.env.NEXT_PUBLIC_CODE_LINE_NUMBERS || 'false', // 是否显示行号
// Mermaid 图表CDN
MERMAID_CDN: process.env.NEXT_PUBLIC_MERMAID_CDN || 'https://cdn.jsdelivr.net/npm/mermaid@10.2.2/dist/mermaid.min.js', // CDN
BACKGROUND_LIGHT: '#eeeeee', // use hex value, don't forget '#' e.g #fffefc
BACKGROUND_DARK: '#000000', // use hex value, don't forget '#'
SUB_PATH: '', // leave this empty unless you want to deploy in a folder
@@ -231,11 +237,14 @@ const BLOG = {
COMMENT_GITALK_CLIENT_SECRET:
process.env.NEXT_PUBLIC_COMMENT_GITALK_CLIENT_SECRET || '', // e.g 40位ID 在gitalk后台获取
COMMENT_GITALK_DISTRACTION_FREE_MODE: false, // 类似facebook的无干扰模式
COMMENT_GITALK_JS_CDN_URL: process.env.NEXT_PUBLIC_COMMENT_GITALK_JS_CDN_URL || 'https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js', // gitalk客户端 js cdn
COMMENT_GITALK_CSS_CDN_URL: process.env.NEXT_PUBLIC_COMMENT_GITALK_CSS_CDN_URL || 'https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css', // gitalk客户端 css cdn
COMMENT_GITTER_ROOM: process.env.NEXT_PUBLIC_COMMENT_GITTER_ROOM || '', // gitter聊天室 see https://gitter.im/ 不需要则留空
COMMENT_DAO_VOICE_ID: process.env.NEXT_PUBLIC_COMMENT_DAO_VOICE_ID || '', // DaoVoice http://dashboard.daovoice.io/get-started
COMMENT_TIDIO_ID: process.env.NEXT_PUBLIC_COMMENT_TIDIO_ID || '', // [tidio_id] -> //code.tidio.co/[tidio_id].js
COMMENT_VALINE_CDN: process.env.NEXT_PUBLIC_VALINE_CDN || 'https://unpkg.com/valine@1.5.1/dist/Valine.min.js',
COMMENT_VALINE_APP_ID: process.env.NEXT_PUBLIC_VALINE_ID || '', // Valine @see https://valine.js.org/quickstart.html 或 https://github.com/stonehank/react-valine#%E8%8E%B7%E5%8F%96app-id-%E5%92%8C-app-key
COMMENT_VALINE_APP_KEY: process.env.NEXT_PUBLIC_VALINE_KEY || '',
COMMENT_VALINE_SERVER_URLS: process.env.NEXT_PUBLIC_VALINE_SERVER_URLS || '', // 该配置适用于国内自定义域名用户, 海外版本会自动检测(无需手动填写) @see https://valine.js.org/configuration.html#serverURLs
@@ -314,7 +323,7 @@ const BLOG = {
// 作废配置
AVATAR: process.env.NEXT_PUBLIC_AVATAR || '/avatar.svg', // 作者头像被notion中的ICON覆盖。若无ICON则取public目录下的avatar.png
TITLE: process.env.NEXT_PUBLIC_TITLE || 'NotionNext BLOG', // 站点标题 被notion中的页面标题覆盖
TITLE: process.env.NEXT_PUBLIC_TITLE || 'NotionNext BLOG', // 站点标题 被notion中的页面标题覆盖;此处请勿留空白,否则服务器无法编译
HOME_BANNER_IMAGE:
process.env.NEXT_PUBLIC_HOME_BANNER_IMAGE || './bg_image.jpg', // 首页背景大图, 会被notion中的封面图覆盖若无封面图则会使用代码中的 /public/bg_image.jpg 文件
DESCRIPTION:

View File

@@ -1,18 +1,18 @@
import BLOG from '@/blog.config'
import * as ThemeMap from '@/themes'
import { useEffect, useState } from 'react'
import Select from './Select'
import { ALL_THEME } from '@/themes'
import { useGlobal } from '@/lib/global'
import { ALL_THEME } from '@/lib/theme'
/**
*
* @returns 调试面板
*/
export function DebugPanel() {
const DebugPanel = () => {
const [show, setShow] = useState(false)
const { theme, changeTheme, switchTheme, locale } = useGlobal()
const { changeTheme, switchTheme, locale } = useGlobal()
const [siteConfig, updateSiteConfig] = useState({})
const [themeConfig, updateThemeConfig] = useState({})
// const [themeConfig, updateThemeConfig] = useState({})
const [debugTheme, updateDebugTheme] = useState(BLOG.THEME)
// 主题下拉框
@@ -21,7 +21,7 @@ export function DebugPanel() {
useEffect(() => {
changeTheme(BLOG.THEME)
updateSiteConfig(Object.assign({}, BLOG))
updateThemeConfig(Object.assign({}, ThemeMap[BLOG.THEME].THEME_CONFIG))
// updateThemeConfig(Object.assign({}, ThemeMap[BLOG.THEME].THEME_CONFIG))
}, [])
function toggleShow() {
@@ -30,13 +30,13 @@ export function DebugPanel() {
function handleChangeDebugTheme() {
const newTheme = switchTheme()
updateThemeConfig(Object.assign({}, ThemeMap[newTheme].THEME_CONFIG))
// updateThemeConfig(Object.assign({}, ThemeMap[newTheme].THEME_CONFIG))
updateDebugTheme(newTheme)
}
function handleUpdateDebugTheme(e) {
changeTheme(e)
updateThemeConfig(Object.assign({}, ThemeMap[theme].THEME_CONFIG))
// updateThemeConfig(Object.assign({}, ThemeMap[theme].THEME_CONFIG))
updateDebugTheme(e)
}
@@ -90,7 +90,7 @@ export function DebugPanel() {
</div>
<div>
<div>
{/* <div>
<div className="font-bold w-18 border-b my-2">
主题配置{`config_${debugTheme}.js`}:
</div>
@@ -106,7 +106,7 @@ export function DebugPanel() {
</div>
))}
</div>
</div>
</div> */}
<div className="font-bold w-18 border-b my-2">
站点配置[blog.config.js]
</div>
@@ -128,3 +128,4 @@ export function DebugPanel() {
</>
)
}
export default DebugPanel

View File

@@ -0,0 +1,58 @@
import BLOG from 'blog.config'
import dynamic from 'next/dynamic'
// import TwikooCommentCounter from '@/components/TwikooCommentCounter'
// import { DebugPanel } from '@/components/DebugPanel'
// import { ThemeSwitch } from '@/components/ThemeSwitch'
// import { Fireworks } from '@/components/Fireworks'
// import { Nest } from '@/components/Nest'
// import { FlutteringRibbon } from '@/components/FlutteringRibbon'
// import { Ribbon } from '@/components/Ribbon'
// import { Sakura } from '@/components/Sakura'
// import { StarrySky } from '@/components/StarrySky'
// import { Analytics } from '@vercel/analytics/react'
const TwikooCommentCounter = dynamic(() => import('@/components/TwikooCommentCounter'), { ssr: false })
const DebugPanel = dynamic(() => import('@/components/DebugPanel'), { ssr: false })
const ThemeSwitch = dynamic(() => import('@/components/ThemeSwitch'), { ssr: false })
const Fireworks = dynamic(() => import('@/components/Fireworks'), { ssr: false })
const Nest = dynamic(() => import('@/components/Nest'), { ssr: false })
const FlutteringRibbon = dynamic(() => import('@/components/FlutteringRibbon'), { ssr: false })
const Ribbon = dynamic(() => import('@/components/Ribbon'), { ssr: false })
const Sakura = dynamic(() => import('@/components/Sakura'), { ssr: false })
const StarrySky = dynamic(() => import('@/components/StarrySky'), { ssr: false })
const Analytics = dynamic(() => import('@vercel/analytics/react').then(async (m) => { return m.Analytics }), { ssr: false })
const MusicPlayer = dynamic(() => import('@/components/Player'), { ssr: false })
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
const Busuanzi = dynamic(() => import('@/components/Busuanzi'), { ssr: false })
const GoogleAdsense = dynamic(() => import('@/components/GoogleAdsense'), { ssr: false })
const Messenger = dynamic(() => import('@/components/FacebookMessenger'), { ssr: false })
/**
* 各种第三方组件
* @param {*} props
* @returns
*/
const ExternalPlugin = (props) => {
return <>
{JSON.parse(BLOG.THEME_SWITCH) && <ThemeSwitch />}
{JSON.parse(BLOG.DEBUG) && <DebugPanel />}
{BLOG.ANALYTICS_ACKEE_TRACKER && <Ackee />}
{BLOG.ANALYTICS_GOOGLE_ID && <Gtag />}
{BLOG.ANALYTICS_VERCEL && <Analytics />}
{JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && <Busuanzi />}
{BLOG.ADSENSE_GOOGLE_ID && <GoogleAdsense />}
{BLOG.FACEBOOK_APP_ID && BLOG.FACEBOOK_PAGE_ID && <Messenger />}
{JSON.parse(BLOG.FIREWORKS) && <Fireworks />}
{JSON.parse(BLOG.SAKURA) && <Sakura />}
{JSON.parse(BLOG.STARRY_SKY) && <StarrySky />}
{JSON.parse(BLOG.MUSIC_PLAYER) && <MusicPlayer />}
{JSON.parse(BLOG.NEST) && <Nest />}
{JSON.parse(BLOG.FLUTTERINGRIBBON) && <FlutteringRibbon />}
{JSON.parse(BLOG.COMMENT_TWIKOO_COUNT_ENABLE) && <TwikooCommentCounter {...props}/>}
{JSON.parse(BLOG.RIBBON) && <Ribbon />}
</>
}
export default ExternalPlugin

View File

@@ -2,16 +2,17 @@
* https://codepen.io/juliangarnier/pen/gmOwJX
* custom by hexo-theme-yun @YunYouJun
*/
import React from 'react'
import { useEffect } from 'react'
import anime from 'animejs'
import BLOG from 'blog.config'
export const Fireworks = () => {
React.useEffect(() => {
const Fireworks = () => {
useEffect(() => {
createFireworks({})
}, [])
return <canvas id='fireworks' className='fireworks'></canvas>
}
export default Fireworks
/**
* 创建烟花

View File

@@ -1,5 +1,5 @@
/* eslint-disable */
import React from 'react'
import { useEffect } from 'react'
const id = 'canvasFlutteringRibbon'
export const FlutteringRibbon = () => {
const destroyRibbon = ()=>{
@@ -9,15 +9,17 @@ export const FlutteringRibbon = () => {
}
}
React.useEffect(() => {
useEffect(() => {
createFlutteringRibbon()
return () => destroyRibbon()
}, [])
return <></>
}
export default FlutteringRibbon
/**
* 创建连接点
* @param config

View File

@@ -1,18 +1,44 @@
import 'gitalk/dist/gitalk.css'
// import 'gitalk/dist/gitalk.css'
import BLOG from '@/blog.config'
import GitalkComponent from 'gitalk/dist/gitalk-component'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
// import GitalkComponent from 'gitalk/dist/gitalk-component'
const Gitalk = ({ frontMatter }) => {
return <GitalkComponent options={{
id: frontMatter.id,
title: frontMatter.title,
clientID: BLOG.COMMENT_GITALK_CLIENT_ID,
clientSecret: BLOG.COMMENT_GITALK_CLIENT_SECRET,
repo: BLOG.COMMENT_GITALK_REPO,
owner: BLOG.COMMENT_GITALK_OWNER,
admin: BLOG.COMMENT_GITALK_ADMIN.split(','),
distractionFreeMode: JSON.parse(BLOG.COMMENT_GITALK_DISTRACTION_FREE_MODE)
}} />
// return <GitalkComponent options={{
// id: frontMatter.id,
// title: frontMatter.title,
// clientID: BLOG.COMMENT_GITALK_CLIENT_ID,
// clientSecret: BLOG.COMMENT_GITALK_CLIENT_SECRET,
// repo: BLOG.COMMENT_GITALK_REPO,
// owner: BLOG.COMMENT_GITALK_OWNER,
// admin: BLOG.COMMENT_GITALK_ADMIN.split(','),
// distractionFreeMode: JSON.parse(BLOG.COMMENT_GITALK_DISTRACTION_FREE_MODE)
// }} />
const loadGitalk = async() => {
const css = await loadExternalResource(BLOG.COMMENT_GITALK_CSS_CDN_URL, 'css')
const js = await loadExternalResource(BLOG.COMMENT_GITALK_JS_CDN_URL, 'js')
console.log('gitalk 加载成功', css, js)
const Gitalk = window.Gitalk
const gitalk = new Gitalk({
clientID: BLOG.COMMENT_GITALK_CLIENT_ID,
clientSecret: BLOG.COMMENT_GITALK_CLIENT_SECRET,
repo: BLOG.COMMENT_GITALK_REPO,
owner: BLOG.COMMENT_GITALK_OWNER,
admin: BLOG.COMMENT_GITALK_ADMIN.split(','),
id: frontMatter.id, // Ensure uniqueness and length less than 50
distractionFreeMode: JSON.parse(BLOG.COMMENT_GITALK_DISTRACTION_FREE_MODE) // Facebook-like distraction free mode
})
gitalk.render('gitalk-container')
}
useEffect(() => {
loadGitalk()
}, [])
return <div id="gitalk-container"></div>
}
export default Gitalk

View File

@@ -6,17 +6,20 @@ import { useEffect } from 'react'
export default function Live2D() {
const { theme, switchTheme } = useGlobal()
const showPet = JSON.parse(BLOG.WIDGET_PET)
useEffect(() => {
if (BLOG.WIDGET_PET) {
// setLive2DLoaded(true)
// console.log('加载宠物挂件')
if (showPet) {
Promise.all([
loadExternalResource('https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/live2d.min.js', 'js')
]).then((e) => {
if (window?.loadlive2d) {
if (typeof window?.loadlive2d !== 'undefined') {
// https://github.com/xiazeyu/live2d-widget-models
loadlive2d('live2d', BLOG.WIDGET_PET_LINK)
try {
loadlive2d('live2d', BLOG.WIDGET_PET_LINK)
} catch (error) {
console.error('读取PET模型', error)
}
}
})
}
@@ -28,7 +31,7 @@ export default function Live2D() {
}
}
if (!BLOG.WIDGET_PET || !JSON.parse(BLOG.WIDGET_PET)) {
if (!showPet) {
return <></>
}

25
components/Loading.js Normal file
View File

@@ -0,0 +1,25 @@
/**
* 主题文件被加载出之前的占位符
* @returns
*/
const Loading = (props) => {
// const { theme, setOnReading } = useGlobal()
// console.log('开启遮罩')
// setOnReading(true)
// useEffect(() => {
// // 返回一个函数,在组件销毁时设置 onReading 为 false
// return () => {
// setTimeout(() => {
// console.log('关闭遮罩')
// setOnReading(false)
// }, 500)
// }
// })
return <div className="w-screen h-screen flex justify-center items-center bg-black">
<i className='mr-5 fas fa-spinner animate-spin text-2xl' />
</div>
}
export default Loading

View File

@@ -0,0 +1,13 @@
/**
* 加载文件时的全局遮罩
* @returns
*/
const LoadingCover = (props) => {
const { onReading } = props
return <div className={`${onReading ? 'opacity-90' : 'opacity-0'} transition-all fixed top-0 left-0 pointer-events-none duration-1000 z-50 shadow-inner w-screen h-screen flex justify-center items-center bg-gray-400 dark:bg-black text-white shadow-text`}>
<i className='text-2xl mr-5 fas fa-spinner animate-spin' />
</div>
}
export default LoadingCover

View File

@@ -1,7 +0,0 @@
import dynamic from 'next/dynamic'
const MusicPlayer = dynamic(
() => import('@/components/Player'),
{ ssr: false }
)
export default MusicPlayer

View File

@@ -1,7 +1,7 @@
/* eslint-disable */
import { useEffect } from 'react'
const id = 'canvasNestCreated'
export const Nest = () => {
const Nest = () => {
const destroyNest = ()=>{
const nest = document.getElementById(id)
if(nest && nest.parentNode){
@@ -16,6 +16,8 @@ export const Nest = () => {
return <></>
}
export default Nest
/**
* 创建连接点
* @param config

View File

@@ -13,7 +13,7 @@ const Equation = dynamic(() =>
// 化学方程式
await import('@/lib/mhchem')
return m.Equation
})
}), { ssr: false }
)
const Pdf = dynamic(
() => import('react-notion-x/build/third-party/pdf').then((m) => m.Pdf),

View File

@@ -25,7 +25,7 @@ const PrismMac = () => {
}
loadExternalResource(BLOG.PRISM_THEME_PATH, 'css')
loadExternalResource(BLOG.PRISM_JS_AUTO_LOADER, 'js').then((url) => {
console.log('渲染公式图表')
// console.log('渲染公式图表')
if (window?.Prism?.plugins?.autoloader) {
window.Prism.plugins.autoloader.languages_path = BLOG.PRISM_JS_PATH
}
@@ -64,8 +64,11 @@ const renderMermaid = async() => {
}
}
if (needLoad) {
const asyncMermaid = await import('mermaid')
asyncMermaid.default.contentLoaded()
// const asyncMermaid = await import('mermaid')
const url = await loadExternalResource(BLOG.MERMAID_CDN, 'js')
const mermaid = window.mermaid
console.log('mermaid加载成功', url, mermaid)
mermaid.contentLoaded()
}
}
}

View File

@@ -2,7 +2,7 @@
import { useEffect } from 'react'
const id = 'canvasRibbon'
export const Ribbon = () => {
const Ribbon = () => {
const destroyRibbon = ()=>{
const ribbon = document.getElementById(id)
if(ribbon && ribbon.parentNode){
@@ -17,6 +17,8 @@ export const Ribbon = () => {
return <></>
}
export default Ribbon
/**
* 创建连接点
* @param config

View File

@@ -1,7 +1,7 @@
/* eslint-disable */
import { useEffect } from 'react'
const id = 'canvas_sakura'
export const Sakura = () => {
const Sakura = () => {
const destroySakura = ()=>{
const sakura = document.getElementById(id)
if(sakura && sakura.parentNode){
@@ -16,6 +16,8 @@ export const Sakura = () => {
return <></>
}
export default Sakura
/**
* 创建樱花雨
* @param config

View File

@@ -29,11 +29,11 @@ const SideBarDrawer = ({ children, isOpen, onOpen, onClose, className }) => {
const sideBarDrawerBackground = window.document.getElementById('sidebar-drawer-background')
if (showStatus) {
sideBarDrawer.classList.replace('-ml-60', 'ml-0')
sideBarDrawerBackground.classList.replace('hidden', 'block')
sideBarDrawer?.classList.replace('-ml-60', 'ml-0')
sideBarDrawerBackground?.classList.replace('hidden', 'block')
} else {
sideBarDrawer.classList.replace('ml-0', '-ml-60')
sideBarDrawerBackground.classList.replace('block', 'hidden')
sideBarDrawer?.classList.replace('ml-0', '-ml-60')
sideBarDrawerBackground?.classList.replace('block', 'hidden')
}
}

View File

@@ -1,7 +1,7 @@
/* eslint-disable */
import React from 'react'
export const StarrySky = () => {
const StarrySky = () => {
React.useEffect(() => {
dark()
}, [])
@@ -12,6 +12,7 @@ export const StarrySky = () => {
)
}
export default StarrySky
/**
* 创建星空雨
* @param config

View File

@@ -1,12 +1,12 @@
import { useGlobal } from '@/lib/global'
import { ALL_THEME } from '@/themes'
import React from 'react'
import { Draggable } from './Draggable'
import { ALL_THEME } from '@/lib/theme'
/**
*
* @returns 主题切换
*/
export function ThemeSwitch() {
const ThemeSwitch = () => {
const { theme, changeTheme } = useGlobal()
const onSelectChange = (e) => {
@@ -30,3 +30,5 @@ export function ThemeSwitch() {
</>
)
}
export default ThemeSwitch

View File

@@ -24,7 +24,7 @@ const TwikooCommentCounter = (props) => {
twikoo.getCommentsCount({
envId: BLOG.COMMENT_TWIKOO_ENV_ID, // 环境 ID
// region: 'ap-guangzhou', // 环境地域,默认为 ap-shanghai如果您的环境地域不是上海需传此参数
urls: posts.map(post => post.slug), // 不包含协议、域名、参数的文章路径列表,必传参数
urls: posts?.map(post => post.slug), // 不包含协议、域名、参数的文章路径列表,必传参数
includeReply: true // 评论数是否包括回复默认false
}).then(function (res) {
// console.log('查询', res)
@@ -62,7 +62,7 @@ const TwikooCommentCounter = (props) => {
const router = useRouter()
useEffect(() => {
console.log('路由触发评论计数')
// console.log('路由触发评论计数')
if (props?.posts && props?.posts?.length > 0) {
fetchTwikooData(props.posts)
}
@@ -70,7 +70,7 @@ const TwikooCommentCounter = (props) => {
// 监控主题变化时的的评论数
useEffect(() => {
console.log('主题触发评论计数', commentsData)
// console.log('主题触发评论计数', commentsData)
updateCommentCount()
}, [theme])
return null

View File

@@ -1,3 +0,0 @@
import { Valine } from 'react-valine'
export default Valine

View File

@@ -1,49 +1,61 @@
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import React from 'react'
import Valine from 'valine'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
const ValineComponent = (props) => {
const router = useRouter()
const initValine = (url) => {
const valine = new Valine({
el: '#v-comments',
appId: BLOG.COMMENT_VALINE_APP_ID,
appKey: BLOG.COMMENT_VALINE_APP_KEY,
avatar: '',
path: url || router.asPath,
recordIP: true,
placeholder: BLOG.COMMENT_VALINE_PLACEHOLDER,
serverURLs: BLOG.COMMENT_VALINE_SERVER_URLS,
visitor: true
})
if (!valine) {
console.error('valine错误')
const ValineComponent = ({ path }) => {
const loadValine = async () => {
try {
const url = await loadExternalResource(BLOG.COMMENT_VALINE_CDN, 'js')
console.log('valine 加载成功', url)
const Valine = window.Valine
// eslint-disable-next-line no-unused-vars
const valine = new Valine({
el: '#valine', // 容器元素
lang: BLOG.LANG, // 用于手动设定评论区语言,支持的语言列表 https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js
appId: BLOG.COMMENT_VALINE_APP_ID,
appKey: BLOG.COMMENT_VALINE_APP_KEY,
avatar: '',
path,
recordIP: true,
placeholder: BLOG.COMMENT_VALINE_PLACEHOLDER,
serverURLs: BLOG.COMMENT_VALINE_SERVER_URLS,
visitor: true
})
console.log('初始化valine成功')
} catch (error) {
console.error('twikoo 加载失败', error)
}
}
const updateValine = url => {
// 移除旧的评论区,否则会重复渲染。
const wrapper = document.getElementById('v-wrapper')
const comments = document.getElementById('v-comments')
wrapper.removeChild(comments)
const newComments = document.createElement('div')
newComments.id = 'v-comments'
newComments.name = new Date()
wrapper.appendChild(newComments)
initValine(url)
}
React.useEffect(() => {
initValine()
router.events.on('routeChangeComplete', updateValine)
return () => {
router.events.off('routeChangeComplete', updateValine)
}
useEffect(() => {
loadValine()
}, [])
return <div id='v-wrapper'>
<div id='v-comments'></div>
</div>
return <div id="valine"></div>
// const updateValine = url => {
// // 移除旧的评论区,否则会重复渲染。
// const wrapper = document.getElementById('v-wrapper')
// const comments = document.getElementById('v-comments')
// wrapper.removeChild(comments)
// const newComments = document.createElement('div')
// newComments.id = 'v-comments'
// newComments.name = new Date()
// wrapper.appendChild(newComments)
// initValine(url)
// }
// useEffect(() => {
// initValine()
// router.events.on('routeChangeComplete', updateValine)
// return () => {
// router.events.off('routeChangeComplete', updateValine)
// }
// }, [])
// return <div id='v-wrapper'>
// <div id='v-comments'></div>
// </div>
}
export default ValineComponent

View File

@@ -4,6 +4,7 @@
"paths": {
"@/*": ["./*"],
"@/components/*": ["components/*"],
"@/theme/*": ["theme/*"],
"@/data/*": ["data/*"],
"@/lib/*": ["lib/*"],
"@/styles/*": ["styles/*"]

View File

@@ -2,9 +2,10 @@ import { generateLocaleDict, initLocale } from './lang'
import { createContext, useContext, useEffect, useState } from 'react'
import Router, { useRouter } from 'next/router'
import BLOG from '@/blog.config'
import { initDarkMode, initTheme, saveThemeToCookies } from '@/lib/theme'
import { ALL_THEME } from '@/themes'
import { ALL_THEME, initDarkMode, initTheme, saveThemeToCookies } from '@/lib/theme'
import NProgress from 'nprogress'
import LoadingCover from '@/components/LoadingCover'
import { isBrowser } from './utils'
const GlobalContext = createContext()
@@ -15,27 +16,40 @@ const GlobalContext = createContext()
* @constructor
*/
export function GlobalContextProvider({ children }) {
const [lang, updateLang] = useState(BLOG.LANG)
const [locale, updateLocale] = useState(generateLocaleDict(BLOG.LANG))
const [theme, setTheme] = useState(BLOG.THEME)
const [isDarkMode, updateDarkMode] = useState(BLOG.APPEARANCE === 'dark')
const [onLoading, changeLoadingState] = useState(false)
const [lang, updateLang] = useState(BLOG.LANG) // 默认语言
const [locale, updateLocale] = useState(generateLocaleDict(BLOG.LANG)) // 默认语言
const [theme, setTheme] = useState(BLOG.THEME) // 默认博客主题
const [isDarkMode, updateDarkMode] = useState(BLOG.APPEARANCE === 'dark') // 默认深色模式
const [onLoading, setOnLoading] = useState(false) // 抓取文章数据
const [onReading, setOnReading] = useState(true) // 网页资源加载
const router = useRouter()
useEffect(() => {
initLocale(lang, locale, updateLang, updateLocale)
initDarkMode(isDarkMode, updateDarkMode)
initTheme(theme, changeTheme)
if (isBrowser()) {
// 监听用户刷新页面
const handleBeforeUnload = (event) => {
setOnReading(true)
}
// 监听页面元素加载完
setOnReading(false)
window.addEventListener('beforeunload', handleBeforeUnload)
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload)
}
}
}, [])
useEffect(() => {
const handleStart = (url) => {
NProgress.start()
changeLoadingState(true)
setOnLoading(true)
}
const handleStop = () => {
NProgress.done()
changeLoadingState(false)
setOnLoading(false)
}
router.events.on('routeChangeStart', handleStart)
@@ -67,20 +81,20 @@ export function GlobalContextProvider({ children }) {
}
return (
<GlobalContext.Provider value={
{
onLoading,
changeLoadingState,
locale,
updateLocale,
isDarkMode,
updateDarkMode,
theme,
setTheme,
switchTheme,
changeTheme
}
}>
<GlobalContext.Provider value={{
onLoading,
setOnLoading,
locale,
updateLocale,
isDarkMode,
updateDarkMode,
theme,
setTheme,
switchTheme,
changeTheme,
setOnReading
}}>
<LoadingCover onReading={onReading} setOnReading={setOnReading}/>
{children}
</GlobalContext.Provider>
)

0
lib/memorize.js Normal file
View File

View File

@@ -19,7 +19,7 @@ export function getAllCategories({ allPages, categoryOptions, sliceCount = 0 })
return []
}
// 计数
let categories = allPosts.map(p => p.category)
let categories = allPosts?.map(p => p.category)
categories = [...categories.flat()]
const categoryObj = {}
categories.forEach(category => {

View File

@@ -14,7 +14,7 @@ export function getAllTags({ allPages, sliceCount = 0, tagOptions }) {
return []
}
// 计数
let tags = allPosts.map(p => p.tags)
let tags = allPosts?.map(p => p.tags)
tags = [...tags.flat()]
const tagObj = {}
tags.forEach(tag => {

View File

@@ -172,5 +172,9 @@ function generateCustomizeUrl(postProperties) {
fullSlug += '/'
}
})
if (fullSlug.startsWith('/')) {
fullSlug = fullSlug.substring(1) // 去掉头部的"/"
}
return `${fullSlug}/${(postProperties.slug ?? postProperties.id)}`
}

View File

@@ -1,8 +1,21 @@
import cookie from 'react-cookies'
import BLOG from '@/blog.config'
import { ALL_THEME } from '@/themes'
import { isBrowser, getQueryVariable } from './utils'
/**
* 所有主题枚举
*/
export const ALL_THEME = [
'hexo',
'matery',
'next',
'medium',
'fukasawa',
'nobelium',
'example',
'simple'
]
/**
* 初始化主题 , 优先级 query > cookies > systemPrefer
* @param isDarkMode

View File

@@ -1,5 +1,15 @@
// 封装异步加载资源的方法
import { memo } from 'react'
/**
* 组件持久化
*/
export const memorize = (Component) => {
const MemoizedComponent = (props) => {
return <Component {...props} />
}
return memo(MemoizedComponent)
}
/**
* 加载外部资源
* @param url 地址 例如 https://xx.com/xx.js

View File

@@ -1,6 +1,6 @@
{
"name": "notion-next",
"version": "3.14.0",
"version": "3.15.0",
"homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT",
"repository": {
@@ -30,13 +30,11 @@
"copy-to-clipboard": "^3.3.1",
"eslint-plugin-react-hooks": "^4.6.0",
"feed": "^4.2.2",
"gitalk": "^1.7.2",
"js-md5": "^0.7.3",
"localStorage": "^1.0.4",
"lodash.throttle": "^4.1.1",
"mark.js": "^8.11.1",
"memory-cache": "^0.2.0",
"mermaid": "9.2.2",
"mongodb": "^4.6.0",
"next": "13.3.1",
"notion-client": "6.15.6",
@@ -56,8 +54,7 @@
"react-tweet-embed": "~2.0.0",
"smoothscroll-polyfill": "^0.4.4",
"typed.js": "^2.0.12",
"use-ackee": "^3.0.0",
"valine": "^1.4.18"
"use-ackee": "^3.0.0"
},
"devDependencies": {
"@waline/client": "^2.5.1",

View File

@@ -1,6 +1,6 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import * as ThemeMap from '@/themes'
import { useGlobal } from '@/lib/global'
import dynamic from 'next/dynamic'
/**
* 404
@@ -9,9 +9,9 @@ import { useGlobal } from '@/lib/global'
*/
const NoFound = props => {
const { theme, siteInfo } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const meta = { title: `${props?.siteInfo?.title} | 页面找不到啦`, image: siteInfo?.pageCover }
return <ThemeComponents.Layout404 {...props} meta={meta}/>
const Layout404 = dynamic(() => import(`@/themes/${theme}/Layout404`))
return <Layout404 {...props} meta={meta}/>
}
export async function getStaticProps () {

View File

@@ -2,14 +2,20 @@ import BLOG from '@/blog.config'
import { getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import { useEffect, useState } from 'react'
import { Suspense, useEffect, useState } from 'react'
import { idToUuid } from 'notion-utils'
import Router, { useRouter } from 'next/router'
import { useRouter } from 'next/router'
import { isBrowser } from '@/lib/utils'
import { getNotion } from '@/lib/notion/getNotion'
import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents'
import md5 from 'js-md5'
import { isBrowser } from '@/lib/utils'
import dynamic from 'next/dynamic'
import Loading from '@/components/Loading'
/**
* 懒加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutSlug`), { ssr: true })
/**
* 根据notion的slug访问页面
@@ -17,54 +23,28 @@ import { isBrowser } from '@/lib/utils'
* @returns
*/
const Slug = props => {
const { theme, changeLoadingState } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { theme, setOnLoading } = useGlobal()
const { post, siteInfo } = props
const router = useRouter()
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutSlug`)))
}
loadLayout()
}, [theme])
// 文章锁🔐
const [lock, setLock] = useState(post?.password && post?.password !== '')
useEffect(() => {
if (post) {
changeLoadingState(false)
} else {
changeLoadingState(true)
}
if (post?.password && post?.password !== '') {
setLock(true)
} else {
if (!lock && post?.blockMap?.block) {
post.content = Object.keys(post.blockMap.block).filter(key => post.blockMap.block[key]?.value?.parent_id === post.id)
post.toc = getPageTableOfContents(post, post.blockMap)
}
setLock(false)
}
}, [post])
if (!post) {
setTimeout(() => {
if (isBrowser()) {
const article = document.getElementById('notion-article')
if (!article) {
router.push('/404').then(() => {
console.warn('找不到页面', router.asPath)
})
}
}
}, 8 * 1000) // 404时长 8秒
const meta = { title: `${props?.siteInfo?.title || BLOG.TITLE} | loading`, image: siteInfo?.pageCover || BLOG.HOME_BANNER_IMAGE }
return <ThemeComponents.LayoutSlug {...props} showArticleInfo={true} meta={meta} />
}
/**
* 验证文章密码
* @param {*} result
*/
* 验证文章密码
* @param {*} result
*/
const validPassword = passInput => {
const encrypt = md5(post.slug + passInput)
if (passInput && encrypt === post.password) {
setLock(false)
return true
@@ -72,25 +52,52 @@ const Slug = props => {
return false
}
props = { ...props, lock, setLock, validPassword }
// 文章加载
useEffect(() => {
setOnLoading(false)
// 404
if (!post) {
setTimeout(() => {
if (isBrowser()) {
const article = document.getElementById('notion-article')
if (!article) {
router.push('/404').then(() => {
console.warn('找不到页面', router.asPath)
})
}
}
}, 8 * 1000) // 404时长 8秒
}
// 文章加密
if (post?.password && post?.password !== '') {
setLock(true)
} else {
if (!lock && post?.blockMap?.block) {
post.content = Object.keys(post.blockMap.block).filter(key => post.blockMap.block[key]?.value?.parent_id === post.id)
post.toc = getPageTableOfContents(post, post.blockMap)
}
setLock(false)
}
router.events.on('routeChangeComplete', () => {
window.scrollTo({ top: 0, behavior: 'smooth' })
})
}, [post])
const meta = {
title: `${post?.title} | ${siteInfo?.title}`,
title: post ? `${post?.title} | ${siteInfo?.title}` : `${props?.siteInfo?.title || BLOG.TITLE} | loading`,
description: post?.summary,
type: post?.type,
slug: post?.slug,
image: post?.page_cover,
image: post?.page_cover || (siteInfo?.pageCover || BLOG.HOME_BANNER_IMAGE),
category: post?.category?.[0],
tags: post?.tags
}
props = { ...props, lock, meta, setLock, validPassword }
Router.events.on('routeChangeComplete', () => {
window.scrollTo({ top: 0, behavior: 'smooth' })
})
return (
<ThemeComponents.LayoutSlug {...props} showArticleInfo={true} meta={meta} />
)
return <Suspense fallback={<Loading />}>
<Layout {...props} />
</Suspense>
}
export async function getStaticPaths() {
@@ -119,7 +126,7 @@ export async function getStaticProps({ params: { slug } }) {
const from = `slug-props-${fullSlug}`
const props = await getGlobalNotionData({ from })
// 在列表内查找文章
props.post = props.allPages.find((p) => {
props.post = props?.allPages?.find((p) => {
return p.slug === fullSlug || p.id === idToUuid(fullSlug)
})

View File

@@ -1,6 +1,4 @@
import BLOG from 'blog.config'
import React, { useEffect } from 'react'
import dynamic from 'next/dynamic'
import { useEffect } from 'react'
import 'animate.css'
import '@/styles/globals.css'
@@ -11,56 +9,21 @@ import 'react-notion-x/src/styles.css'
import '@/styles/notion.css' // 重写部分样式
import { GlobalContextProvider } from '@/lib/global'
import { DebugPanel } from '@/components/DebugPanel'
import { ThemeSwitch } from '@/components/ThemeSwitch'
import { Fireworks } from '@/components/Fireworks'
import { Nest } from '@/components/Nest'
import { FlutteringRibbon } from '@/components/FlutteringRibbon'
import { Ribbon } from '@/components/Ribbon'
import { Sakura } from '@/components/Sakura'
import { StarrySky } from '@/components/StarrySky'
import MusicPlayer from '@/components/MusicPlayer'
import ExternalScript from '@/components/ExternalScript'
import smoothscroll from 'smoothscroll-polyfill'
import { Analytics } from '@vercel/analytics/react'
import { isMobile } from '@/lib/utils'
import AOS from 'aos'
import 'aos/dist/aos.css' // You can also use <link> for styles
import { isMobile } from '@/lib/utils'
import TwikooCommentCounter from '@/components/TwikooCommentCounter'
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
const Busuanzi = dynamic(() => import('@/components/Busuanzi'), { ssr: false })
const GoogleAdsense = dynamic(() => import('@/components/GoogleAdsense'), {
ssr: false
})
const Messenger = dynamic(() => import('@/components/FacebookMessenger'), {
ssr: false
})
import smoothscroll from 'smoothscroll-polyfill'
import dynamic from 'next/dynamic'
// 自定义样式css和js引入
import ExternalScript from '@/components/ExternalScript'
// 各种扩展插件 动画等
const ExternalPlugins = dynamic(() => import('@/components/ExternalPlugins'))
const MyApp = ({ Component, pageProps }) => {
// 外部插件
const externalPlugins = <>
{JSON.parse(BLOG.THEME_SWITCH) && <ThemeSwitch />}
{JSON.parse(BLOG.DEBUG) && <DebugPanel />}
{BLOG.ANALYTICS_ACKEE_TRACKER && <Ackee />}
{BLOG.ANALYTICS_GOOGLE_ID && <Gtag />}
{BLOG.ANALYTICS_VERCEL && <Analytics />}
{JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && <Busuanzi />}
{BLOG.ADSENSE_GOOGLE_ID && <GoogleAdsense />}
{BLOG.FACEBOOK_APP_ID && BLOG.FACEBOOK_PAGE_ID && <Messenger />}
{JSON.parse(BLOG.FIREWORKS) && <Fireworks />}
{JSON.parse(BLOG.SAKURA) && <Sakura />}
{JSON.parse(BLOG.STARRY_SKY) && <StarrySky />}
{JSON.parse(BLOG.MUSIC_PLAYER) && <MusicPlayer />}
{JSON.parse(BLOG.NEST) && <Nest />}
{JSON.parse(BLOG.FLUTTERINGRIBBON) && <FlutteringRibbon />}
{JSON.parse(BLOG.COMMENT_TWIKOO_COUNT_ENABLE) && <TwikooCommentCounter {...pageProps}/>}
{JSON.parse(BLOG.RIBBON) && <Ribbon />}
<ExternalScript/>
</>
useEffect(() => {
AOS.init()
if (isMobile()) {
@@ -71,7 +34,8 @@ const MyApp = ({ Component, pageProps }) => {
return (
<GlobalContextProvider>
<Component {...pageProps} />
{externalPlugins}
<ExternalPlugins {...pageProps}/>
<ExternalScript/>
</GlobalContextProvider>
)
}

View File

@@ -1,13 +1,28 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import React from 'react'
import React, { Suspense, useEffect, useState } from 'react'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import dynamic from 'next/dynamic'
import BLOG from '@/blog.config'
import Loading from '@/components/Loading'
/**
* 加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutArchive`), { ssr: true })
const ArchiveIndex = props => {
const { theme, locale } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { siteInfo } = props
const { theme, locale } = useGlobal()
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutArchive`)))
}
loadLayout()
}, [theme])
const meta = {
title: `${locale.NAV.ARCHIVE} | ${siteInfo?.title}`,
description: siteInfo?.description,
@@ -16,7 +31,11 @@ const ArchiveIndex = props => {
type: 'website'
}
return <ThemeComponents.LayoutArchive {...props} meta={meta} />
props = { ...props, meta }
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
export async function getStaticProps() {

View File

@@ -1,22 +1,33 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import React from 'react'
import React, { Suspense, useEffect, useState } from 'react'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import dynamic from 'next/dynamic'
import BLOG from '@/blog.config'
import Loading from '@/components/Loading'
/**
* 加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutCategory`), { ssr: true })
/**
* 分类页
* @param {*} props
* @returns
*/
export default function Category(props) {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { siteInfo, posts } = props
const { locale } = useGlobal()
if (!posts) {
return <ThemeComponents.Layout404 {...props} />
}
const { siteInfo } = props
const { locale, theme } = useGlobal()
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutCategory`)))
}
loadLayout()
}, [theme])
const meta = {
title: `${props.category} | ${locale.COMMON.CATEGORY} | ${
siteInfo?.title || ''
@@ -26,7 +37,12 @@ export default function Category(props) {
image: siteInfo?.pageCover,
type: 'website'
}
return <ThemeComponents.LayoutCategory {...props} meta={meta} />
props = { ...props, meta }
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
export async function getStaticProps({ params: { category } }) {

View File

@@ -1,9 +1,16 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import React from 'react'
import React, { Suspense, useEffect, useState } from 'react'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import dynamic from 'next/dynamic'
import BLOG from '@/blog.config'
import Loading from '@/components/Loading'
/**
* 加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutCategory`), { ssr: true })
/**
* 分类页
* @param {*} props
@@ -11,12 +18,18 @@ import BLOG from '@/blog.config'
*/
export default function Category(props) {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { siteInfo, posts } = props
const { siteInfo } = props
const { locale } = useGlobal()
if (!posts) {
return <ThemeComponents.Layout404 {...props} />
}
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutCategory`)))
}
loadLayout()
}, [theme])
const meta = {
title: `${props.category} | ${locale.COMMON.CATEGORY} | ${
siteInfo?.title || ''
@@ -26,7 +39,12 @@ export default function Category(props) {
image: siteInfo?.pageCover,
type: 'website'
}
return <ThemeComponents.LayoutCategory {...props} meta={meta} />
props = { ...props, meta }
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
export async function getStaticProps({ params: { category, page } }) {

View File

@@ -1,9 +1,16 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import React from 'react'
import React, { Suspense, useEffect, useState } from 'react'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import dynamic from 'next/dynamic'
import BLOG from '@/blog.config'
import Loading from '@/components/Loading'
/**
* 加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutCategoryIndex`), { ssr: true })
/**
* 分类首页
* @param {*} props
@@ -11,9 +18,18 @@ import BLOG from '@/blog.config'
*/
export default function Category(props) {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { locale } = useGlobal()
const { siteInfo } = props
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutCategoryIndex`)))
}
loadLayout()
}, [theme])
const meta = {
title: `${locale.COMMON.CATEGORY} | ${siteInfo?.title}`,
description: siteInfo?.description,
@@ -21,7 +37,11 @@ export default function Category(props) {
slug: 'category',
type: 'website'
}
return <ThemeComponents.LayoutCategoryIndex {...props} meta={meta} />
props = { ...props, meta }
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
export async function getStaticProps() {

View File

@@ -1,10 +1,17 @@
import BLOG from '@/blog.config'
import { getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import * as ThemeMap from '@/themes'
import { useGlobal } from '@/lib/global'
import { generateRss } from '@/lib/rss'
import { generateRobotsTxt } from '@/lib/robots.txt'
import dynamic from 'next/dynamic'
import { Suspense, useEffect, useState } from 'react'
import Loading from '@/components/Loading'
/**
* 懒加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutIndex`), { ssr: true })
/**
* 首页布局
@@ -12,9 +19,20 @@ import { generateRobotsTxt } from '@/lib/robots.txt'
* @returns
*/
const Index = props => {
// 动态切换主题
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
return <ThemeComponents.LayoutIndex {...props} />
const [Layout, setLayoutIndex] = useState(DefaultLayout)
useEffect(() => {
const loadLayout = async () => {
setLayoutIndex(dynamic(() => import(`@/themes/${theme}/LayoutIndex`)))
}
console.log(loadLayout)
// loadLayout()
}, [theme])
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
/**

View File

@@ -2,23 +2,44 @@ import BLOG from '@/blog.config'
import { getPostBlocks } from '@/lib/notion'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import dynamic from 'next/dynamic'
import { Suspense, useEffect, useState } from 'react'
import Loading from '@/components/Loading'
/**
* 加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutPage`), { ssr: true })
/**
* 文章列表分页
* @param {*} props
* @returns
*/
const Page = props => {
const { theme } = useGlobal()
const { siteInfo } = props
const ThemeComponents = ThemeMap[theme]
if (!siteInfo) {
return <></>
}
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutPage`)))
}
loadLayout()
}, [theme])
const meta = {
title: `${props.page} | Page | ${siteInfo?.title}`,
title: `${props?.page} | Page | ${siteInfo?.title}`,
description: siteInfo?.description,
image: siteInfo?.pageCover,
slug: 'page/' + props.page,
type: 'website'
}
return <ThemeComponents.LayoutPage {...props} meta={meta} />
props = { ...props, meta }
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
export async function getStaticPaths() {

View File

@@ -1,12 +1,28 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global'
import { getDataFromCache } from '@/lib/cache/cache_manager'
import * as ThemeMap from '@/themes'
import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
import { Suspense, useEffect, useState } from 'react'
import Loading from '@/components/Loading'
/**
* 加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutSearch`), { ssr: true })
const Index = props => {
const { keyword, siteInfo } = props
const { locale } = useGlobal()
const { locale, theme } = useGlobal()
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutSearch`)))
}
loadLayout()
}, [theme])
const meta = {
title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`,
description: siteInfo?.title,
@@ -14,15 +30,12 @@ const Index = props => {
slug: 'search/' + (keyword || ''),
type: 'website'
}
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
return (
<ThemeComponents.LayoutSearch
{...props}
meta={meta}
currentSearch={keyword}
/>
)
props = { ...props, meta }
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
/**

View File

@@ -1,12 +1,27 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global'
import { getDataFromCache } from '@/lib/cache/cache_manager'
import * as ThemeMap from '@/themes'
import dynamic from 'next/dynamic'
import BLOG from '@/blog.config'
import { Suspense, useEffect, useState } from 'react'
import Loading from '@/components/Loading'
/**
* 加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutSearch`), { ssr: true })
const Index = props => {
const { keyword, siteInfo } = props
const { locale } = useGlobal()
const { locale, theme } = useGlobal()
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutSearch`)))
}
loadLayout()
}, [theme])
const meta = {
title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`,
description: siteInfo?.title,
@@ -14,15 +29,12 @@ const Index = props => {
slug: 'search/' + (keyword || ''),
type: 'website'
}
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
return (
<ThemeComponents.LayoutSearch
{...props}
meta={meta}
currentSearch={keyword}
/>
)
props = { ...props, meta, currentSearch: keyword }
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
/**

View File

@@ -1,22 +1,39 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import * as ThemeMap from '@/themes'
import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
import { Suspense, useEffect, useState } from 'react'
import Loading from '@/components/Loading'
/**
* 加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutSearch`), { ssr: true })
const Search = props => {
const { posts, siteInfo } = props
const { theme } = useGlobal()
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutSearch`)))
}
loadLayout()
}, [theme])
const router = useRouter()
let filteredPosts
const searchKey = getSearchKey(router)
const keyword = getSearchKey(router)
// 静态过滤
if (searchKey) {
if (keyword) {
filteredPosts = posts.filter(post => {
const tagContent = post.tags ? post.tags.join(' ') : ''
const categoryContent = post.category ? post.category.join(' ') : ''
const searchContent =
post.title + post.summary + tagContent + categoryContent
return searchContent.toLowerCase().includes(searchKey.toLowerCase())
post.title + post.summary + tagContent + categoryContent
return searchContent.toLowerCase().includes(keyword.toLowerCase())
})
} else {
filteredPosts = []
@@ -24,26 +41,18 @@ const Search = props => {
const { locale } = useGlobal()
const meta = {
title: `${searchKey || ''}${searchKey ? ' | ' : ''}${locale.NAV.SEARCH} | ${
siteInfo?.title
}`,
title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`,
description: siteInfo?.description,
image: siteInfo?.pageCover,
slug: 'search',
type: 'website'
}
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
props = { ...props, meta, posts: { filteredPosts } }
return (
<ThemeComponents.LayoutSearch
{...props}
posts={filteredPosts}
currentSearch={searchKey}
meta={meta}
/>
)
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
/**

View File

@@ -1,17 +1,28 @@
import { useGlobal } from '@/lib/global'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import * as ThemeMap from '@/themes'
import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
import { Suspense, useEffect, useState } from 'react'
import Loading from '@/components/Loading'
/**
* 加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutTag`), { ssr: true })
const Tag = props => {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { locale } = useGlobal()
const { tag, siteInfo, posts } = props
const { tag, siteInfo } = props
const [Layout, setLayout] = useState(DefaultLayout)
if (!posts) {
return <ThemeComponents.Layout404 {...props} />
}
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutTag`)))
}
loadLayout()
}, [theme])
const meta = {
title: `${tag} | ${locale.COMMON.TAGS} | ${siteInfo?.title}`,
@@ -20,7 +31,11 @@ const Tag = props => {
slug: 'tag/' + tag,
type: 'website'
}
return <ThemeComponents.LayoutTag {...props} meta={meta} />
props = { ...props, meta }
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
export async function getStaticProps({ params: { tag } }) {

View File

@@ -1,17 +1,27 @@
import { useGlobal } from '@/lib/global'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import * as ThemeMap from '@/themes'
import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
import { Suspense, useEffect, useState } from 'react'
import Loading from '@/components/Loading'
/**
* 加载默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutTag`), { ssr: true })
const Tag = props => {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { locale } = useGlobal()
const { tag, siteInfo, posts } = props
if (!posts) {
return <ThemeComponents.Layout404 {...props} />
}
const { tag, siteInfo } = props
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutTag`)))
}
loadLayout()
}, [theme])
const meta = {
title: `${tag} | ${locale.COMMON.TAGS} | ${siteInfo?.title}`,
@@ -20,7 +30,11 @@ const Tag = props => {
slug: 'tag/' + tag,
type: 'website'
}
return <ThemeComponents.LayoutTag {...props} meta={meta} />
props = { ...props, meta }
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
export async function getStaticProps({ params: { tag, page } }) {

View File

@@ -1,8 +1,13 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import React from 'react'
import { Suspense, useEffect, useState } from 'react'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
import Loading from '@/components/Loading'
/**
* 默认主题
*/
const DefaultLayout = dynamic(() => import(`@/themes/${BLOG.THEME}/LayoutTagIndex`), { ssr: true })
/**
* 标签首页
@@ -10,10 +15,18 @@ import BLOG from '@/blog.config'
* @returns
*/
const TagIndex = props => {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { locale } = useGlobal()
const { siteInfo } = props
const { theme } = useGlobal()
const [Layout, setLayout] = useState(DefaultLayout)
// 切换主题
useEffect(() => {
const loadLayout = async () => {
setLayout(dynamic(() => import(`@/themes/${theme}/LayoutTagIndex`)))
}
loadLayout()
}, [theme])
const meta = {
title: `${locale.COMMON.TAGS} | ${siteInfo?.title}`,
description: siteInfo?.description,
@@ -21,7 +34,11 @@ const TagIndex = props => {
slug: 'tag',
type: 'website'
}
return <ThemeComponents.LayoutTagIndex {...props} meta={meta} />
props = { ...props, meta }
return <Suspense fallback={<Loading/>}>
<Layout {...props} />
</Suspense>
}
export async function getStaticProps() {

View File

@@ -5,3 +5,5 @@ export const Layout404 = (props) => {
404 Not found.
</LayoutBase>
}
export default Layout404

View File

@@ -43,3 +43,5 @@ export const LayoutArchive = props => {
</LayoutBase>
)
}
export default LayoutArchive

View File

@@ -8,3 +8,5 @@ export const LayoutCategory = props => {
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</LayoutBase >
}
export default LayoutCategory

View File

@@ -24,3 +24,5 @@ export const LayoutCategoryIndex = props => {
</LayoutBase>
)
}
export default LayoutCategoryIndex

View File

@@ -11,3 +11,5 @@ export const LayoutIndex = props => {
</LayoutBase>
)
}
export default LayoutIndex

View File

@@ -8,3 +8,5 @@ export const LayoutPage = props => {
</LayoutBase>
)
}
export default LayoutPage

View File

@@ -29,3 +29,5 @@ export const LayoutSlug = props => {
</LayoutBase>
)
}
export default LayoutSlug

View File

@@ -8,3 +8,5 @@ export const LayoutTag = props => {
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</LayoutBase >
}
export default LayoutTag

View File

@@ -27,3 +27,5 @@ export const LayoutTagIndex = (props) => {
</div> </LayoutBase>
)
}
export default LayoutTagIndex

View File

@@ -1,25 +0,0 @@
import CONFIG_EXAMPLE from './config_example'
import { LayoutIndex } from './LayoutIndex'
import { LayoutSearch } from './LayoutSearch'
import { LayoutArchive } from './LayoutArchive'
import { LayoutSlug } from './LayoutSlug'
import { Layout404 } from './Layout404'
import { LayoutCategory } from './LayoutCategory'
import { LayoutCategoryIndex } from './LayoutCategoryIndex'
import { LayoutPage } from './LayoutPage'
import { LayoutTag } from './LayoutTag'
import { LayoutTagIndex } from './LayoutTagIndex'
export {
CONFIG_EXAMPLE as THEME_CONFIG,
LayoutIndex,
LayoutSearch,
LayoutArchive,
LayoutSlug,
Layout404,
LayoutCategory,
LayoutCategoryIndex,
LayoutPage,
LayoutTag,
LayoutTagIndex
}

View File

@@ -3,3 +3,5 @@ import LayoutBase from './LayoutBase'
export const Layout404 = props => {
return <LayoutBase {...props}>404</LayoutBase>
}
export default Layout404

View File

@@ -28,3 +28,5 @@ export const LayoutArchive = (props) => {
</div>
</LayoutBase>
}
export default LayoutArchive

View File

@@ -8,3 +8,5 @@ export const LayoutCategory = props => {
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
</LayoutBase>
}
export default LayoutCategory

View File

@@ -31,3 +31,5 @@ export const LayoutCategoryIndex = (props) => {
</LayoutBase>
)
}
export default LayoutCategoryIndex

View File

@@ -8,3 +8,5 @@ export const LayoutIndex = (props) => {
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
</LayoutBase>
}
export default LayoutIndex

View File

@@ -8,3 +8,5 @@ export const LayoutPage = (props) => {
</LayoutBase>
}
export default LayoutPage

View File

@@ -28,3 +28,5 @@ export const LayoutSearch = (props) => {
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
</LayoutBase>
}
export default LayoutSearch

View File

@@ -11,3 +11,5 @@ export const LayoutSlug = (props) => {
</LayoutBase>
)
}
export default LayoutSlug

View File

@@ -1,7 +1,6 @@
import BLOG from '@/blog.config'
import BlogListPage from './components/BlogListPage'
import BlogListScroll from './components/BlogListScroll'
import TagItemMini from './components/TagItemMini'
import LayoutBase from './LayoutBase'
export const LayoutTag = (props) => {
@@ -9,3 +8,5 @@ export const LayoutTag = (props) => {
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
</LayoutBase>
}
export default LayoutTag

View File

@@ -1,5 +1,4 @@
import { useGlobal } from '@/lib/global'
import TagItem from './components/TagItem'
import TagItemMini from './components/TagItemMini'
import LayoutBase from './LayoutBase'
@@ -21,3 +20,5 @@ export const LayoutTagIndex = (props) => {
</div>
</LayoutBase>
}
export default LayoutTagIndex

View File

@@ -21,7 +21,7 @@ const BlogArchiveItem = ({ posts = [], archiveTitle }) => {
{archiveTitle}
</div>
<ul>
{posts.map(post => (
{posts?.map(post => (
<li
key={post.id}
className="border-l-2 p-1 text-xs md:text-base items-center hover:scale-x-105 hover:border-gray-500 dark:hover:border-gray-300 dark:border-gray-400 transform duration-500"

View File

@@ -1,25 +0,0 @@
import CONFIG_FUKA from './config_fuka'
import { LayoutIndex } from './LayoutIndex'
import { LayoutSearch } from './LayoutSearch'
import { LayoutArchive } from './LayoutArchive'
import { LayoutSlug } from './LayoutSlug'
import { Layout404 } from './Layout404'
import { LayoutCategory } from './LayoutCategory'
import { LayoutCategoryIndex } from './LayoutCategoryIndex'
import { LayoutPage } from './LayoutPage'
import { LayoutTag } from './LayoutTag'
import { LayoutTagIndex } from './LayoutTagIndex'
export {
CONFIG_FUKA as THEME_CONFIG,
LayoutIndex,
LayoutSearch,
LayoutArchive,
LayoutSlug,
Layout404,
LayoutCategory,
LayoutCategoryIndex,
LayoutPage,
LayoutTag,
LayoutTagIndex
}

View File

@@ -30,3 +30,4 @@ export const Layout404 = props => {
</LayoutBase>
)
}
export default Layout404

View File

@@ -32,3 +32,5 @@ export const LayoutArchive = (props) => {
</Card>
</LayoutBase>
}
export default LayoutArchive

View File

@@ -13,3 +13,5 @@ export const LayoutCategory = props => {
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase>
}
export default LayoutCategory

View File

@@ -37,3 +37,5 @@ export const LayoutCategoryIndex = props => {
</LayoutBase>
)
}
export default LayoutCategoryIndex

View File

@@ -12,3 +12,5 @@ export const LayoutIndex = (props) => {
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase>
}
export default LayoutIndex

View File

@@ -6,3 +6,5 @@ export const LayoutPage = (props) => {
<BlogPostListPage {...props}/>
</LayoutBase>
}
export default LayoutPage

View File

@@ -97,3 +97,5 @@ export const LayoutSearch = props => {
</LayoutBase>
)
}
export default LayoutSearch

View File

@@ -93,3 +93,5 @@ export const LayoutSlug = props => {
</LayoutBase>
)
}
export default LayoutSlug

View File

@@ -6,7 +6,7 @@ import React from 'react'
import Link from 'next/link'
export const LayoutTag = (props) => {
const tag = props.tagOptions.find((t) => {
const tag = props?.tagOptions?.find((t) => {
return t.name === props.tag
})
@@ -30,3 +30,5 @@ export const LayoutTag = (props) => {
</LayoutBase>
)
}
export default LayoutTag

View File

@@ -26,3 +26,5 @@ export const LayoutTagIndex = props => {
</LayoutBase>
)
}
export default LayoutTagIndex

View File

@@ -21,7 +21,7 @@ const BlogPostArchive = ({ posts = [], archiveTitle }) => {
{archiveTitle}
</div>
<ul>
{posts.map(post => (
{posts?.map(post => (
<li
key={post.id}
className="border-l-2 p-1 text-xs md:text-base items-center hover:scale-x-105 hover:border-indigo-500 dark:hover:border-indigo-300 dark:border-indigo-400 transform duration-500"
@@ -42,7 +42,7 @@ const BlogPostArchive = ({ posts = [], archiveTitle }) => {
))}
</ul>
</div>
);
)
}
}

View File

@@ -2,6 +2,7 @@ import Link from 'next/link'
import React from 'react'
import CONFIG_HEXO from '../config_hexo'
import { BlogPostCardInfo } from './BlogPostCardInfo'
import BLOG from '@/blog.config'
// import Image from 'next/image'
const BlogPostCard = ({ index, post, showSummary, siteInfo }) => {
@@ -34,7 +35,7 @@ const BlogPostCard = ({ index, post, showSummary, siteInfo }) => {
{/* 图片封面 */}
{showPageCover && (
<div className="md:w-5/12 overflow-hidden">
<Link href={`/${post.slug}`} passHref legacyBehavior>
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
<div className='h-56 bg-center bg-cover hover:scale-110 duration-200' style={{ backgroundImage: `url('${post?.page_cover}')` }} />
</Link>
</div>

View File

@@ -2,6 +2,7 @@ import NotionPage from '@/components/NotionPage'
import Link from 'next/link'
import TagItemMini from './TagItemMini'
import TwikooCommentCount from '@/components/TwikooCommentCount'
import BLOG from '@/blog.config'
/**
* 博客列表的文字内容
@@ -13,7 +14,7 @@ export const BlogPostCardInfo = ({ post, showPreview, showPageCover, showSummary
<div>
{/* 标题 */}
<Link
href={`/${post.slug}`}
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
className={`replace cursor-pointer hover:underline text-2xl ${showPreview ? 'text-center' : ''
} leading-tight font-normal text-gray-600 dark:text-gray-100 hover:text-indigo-700 dark:hover:text-indigo-400`}>

View File

@@ -21,7 +21,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
<div id="container" className='w-full'>
{/* 文章列表 */}
<div className="space-y-6 px-2">
{posts.map(post => (
{posts?.map(post => (
<BlogPostCard index={posts.indexOf(post)} key={post.id} post={post} siteInfo={siteInfo}/>
))}
</div>

View File

@@ -5,6 +5,7 @@ import CONFIG_HEXO from '../config_hexo'
import NavButtonGroup from './NavButtonGroup'
import throttle from 'lodash.throttle'
import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
let wrapperTop = 0
let windowTop = 0
@@ -25,7 +26,7 @@ const Header = props => {
if (!typed && window && document.getElementById('typed')) {
changeType(
new Typed('#typed', {
strings: CONFIG_HEXO.HOME_BANNER_GREETINGS,
strings: BLOG.GREETING_WORDS.split(','),
typeSpeed: 200,
backSpeed: 100,
backDelay: 400,

View File

@@ -21,7 +21,11 @@ export default function HeaderArticle({ post, siteInfo }) {
return (
<div
id="header"
className="w-full h-96 relative md:flex-shrink-0 overflow-hidden bg-cover bg-center bg-no-repeat animate__animated animate__fadeIn z-10"
data-aos="fade-down"
data-aos-duration="300"
data-aos-once="true"
data-aos-anchor-placement="top-bottom"
className="w-full h-96 relative md:flex-shrink-0 overflow-hidden bg-cover bg-center bg-no-repeat z-10"
style={{ backgroundImage: headerImage }}
>
<header id='article-header-cover'

View File

@@ -1,5 +1,5 @@
import { useRouter } from 'next/router'
import React from 'react'
import { useEffect } from 'react'
/**
* 侧边栏抽屉面板,可以从侧面拉出
@@ -8,7 +8,7 @@ import React from 'react'
*/
const SideBarDrawer = ({ children, isOpen, onOpen, onClose, className }) => {
const router = useRouter()
React.useEffect(() => {
useEffect(() => {
const sideBarDrawerRouteListener = () => {
switchSideDrawerVisible(false)
}
@@ -29,11 +29,11 @@ const SideBarDrawer = ({ children, isOpen, onOpen, onClose, className }) => {
const sideBarDrawerBackground = window.document.getElementById('sidebar-drawer-background')
if (showStatus) {
sideBarDrawer.classList.replace('-mr-72', 'mr-0')
sideBarDrawerBackground.classList.replace('hidden', 'block')
sideBarDrawer?.classList.replace('-mr-72', 'mr-0')
sideBarDrawerBackground?.classList.replace('hidden', 'block')
} else {
sideBarDrawer.classList.replace('mr-0', '-mr-72')
sideBarDrawerBackground.classList.replace('block', 'hidden')
sideBarDrawer?.classList.replace('mr-0', '-mr-72')
sideBarDrawerBackground?.classList.replace('block', 'hidden')
}
}

View File

@@ -1,5 +1,6 @@
const CONFIG_HEXO = {
HOME_BANNER_ENABLE: true,
// 3.14.1以后的版本中欢迎语在blog.config.js中配置用英文逗号','隔开多个。
HOME_BANNER_GREETINGS: ['Hi我是一个程序员', 'Hi我是一个打工人', 'Hi我是一个干饭人', '欢迎来到我的博客🎉'], // 首页大图标语文字
HOME_NAV_BUTTONS: true, // 首页是否显示分类大图标按钮

View File

@@ -1,25 +0,0 @@
import CONFIG_HEXO from './config_hexo'
import { LayoutIndex } from './LayoutIndex'
import { LayoutSearch } from './LayoutSearch'
import { LayoutArchive } from './LayoutArchive'
import { LayoutSlug } from './LayoutSlug'
import { Layout404 } from './Layout404'
import { LayoutCategory } from './LayoutCategory'
import { LayoutCategoryIndex } from './LayoutCategoryIndex'
import { LayoutPage } from './LayoutPage'
import { LayoutTag } from './LayoutTag'
import { LayoutTagIndex } from './LayoutTagIndex'
export {
CONFIG_HEXO as THEME_CONFIG,
LayoutIndex,
LayoutSearch,
LayoutArchive,
LayoutSlug,
Layout404,
LayoutCategory,
LayoutCategoryIndex,
LayoutPage,
LayoutTag,
LayoutTagIndex
}

View File

@@ -1,32 +0,0 @@
/**
* 切换主题请修改 blog.config.js 中的 THEME 字段
*/
import * as next from './next'
import * as fukasawa from './fukasawa'
import * as hexo from './hexo'
import * as medium from './medium'
import * as nobelium from './nobelium'
import * as matery from './matery'
import * as example from './example'
import * as simple from './simple'
export const ALL_THEME = [
'hexo',
'matery',
'next',
'medium',
'fukasawa',
'nobelium',
'example',
'simple'
]
export {
hexo,
next,
medium,
fukasawa,
nobelium,
matery,
example,
simple
}

View File

@@ -30,3 +30,5 @@ export const Layout404 = props => {
</LayoutBase>
)
}
export default Layout404

View File

@@ -32,3 +32,5 @@ export const LayoutArchive = (props) => {
</Card>
</LayoutBase>
}
export default LayoutArchive

View File

@@ -36,3 +36,5 @@ export const LayoutCategory = props => {
</LayoutBase>
)
}
export default LayoutCategory

View File

@@ -30,3 +30,5 @@ export const LayoutCategoryIndex = props => {
</LayoutBase>
)
}
export default LayoutCategoryIndex

View File

@@ -12,3 +12,5 @@ export const LayoutIndex = (props) => {
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase>
}
export default LayoutIndex

View File

@@ -6,3 +6,5 @@ export const LayoutPage = (props) => {
<BlogPostListPage {...props}/>
</LayoutBase>
}
export default LayoutPage

View File

@@ -97,3 +97,5 @@ export const LayoutSearch = props => {
</LayoutBase>
)
}
export default LayoutSearch

View File

@@ -128,3 +128,5 @@ export const LayoutSlug = props => {
</LayoutBase>
)
}
export default LayoutSlug

View File

@@ -6,7 +6,6 @@ import React from 'react'
import HeaderArticle from './components/HeaderArticle'
import { useGlobal } from '@/lib/global'
import TagItemMiddle from './components/TagItemMiddle'
import TagItemMini from './components/TagItemMini'
export const LayoutTag = (props) => {
const { tagOptions, tag } = props
@@ -41,3 +40,5 @@ export const LayoutTag = (props) => {
</LayoutBase>
}
export default LayoutTag

View File

@@ -1,7 +1,6 @@
import { useGlobal } from '@/lib/global'
import HeaderArticle from './components/HeaderArticle'
import TagItemMiddle from './components/TagItemMiddle'
import TagItemMini from './components/TagItemMini'
import LayoutBase from './LayoutBase'
export const LayoutTagIndex = props => {
@@ -31,3 +30,5 @@ export const LayoutTagIndex = props => {
</LayoutBase>
)
}
export default LayoutTagIndex

View File

@@ -21,7 +21,7 @@ const BlogPostArchive = ({ posts = [], archiveTitle }) => {
{archiveTitle}
</div>
<ul>
{posts.map(post => (
{posts?.map(post => (
<li
key={post.id}
className="border-l-2 p-1 text-xs md:text-base items-center hover:scale-x-105 hover:border-indigo-500 dark:hover:border-indigo-300 dark:border-indigo-400 transform duration-500"

View File

@@ -22,7 +22,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
<div className='pt-6'></div>
{/* 文章列表 */}
<div className="pt-4 flex flex-wrap pb-12" >
{posts.map(post => (
{posts?.map(post => (
<div key={post.id} className='xl:w-1/3 md:w-1/2 w-full p-4'> <BlogPostCard index={posts.indexOf(post)} post={post} siteInfo={siteInfo} /></div>
))}
</div>

Some files were not shown because too many files have changed in this diff Show More