Merge pull request #1619 from tangly1024/release/4.1.0

Release/4.1.0
This commit is contained in:
tangly1024
2023-11-10 18:09:37 +08:00
committed by GitHub
358 changed files with 2382 additions and 2167 deletions

View File

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

View File

@@ -5,7 +5,7 @@ const BLOG = {
process.env.NOTION_PAGE_ID || '02ab3b8678004aa69e9e415905ef32a5',
PSEUDO_STATIC: process.env.NEXT_PUBLIC_PSEUDO_STATIC || false, // 伪静态路径开启后所有文章URL都以 .html 结尾。
NEXT_REVALIDATE_SECOND: process.env.NEXT_PUBLIC_REVALIDATE_SECOND || 5, // 更新内容缓存间隔 单位(秒)即每个页面有5秒的纯静态期、此期间无论多少次访问都不会抓取notion数据调大该值有助于节省Vercel资源、同时提升访问速率但也会使文章更新有延迟。
THEME: process.env.NEXT_PUBLIC_THEME || 'hexo', // 当前主题在themes文件夹下可找到所有支持的主题主题名称就是文件夹名例如 example,fukasawa,gitbook,heo,hexo,landing,matery,medium,next,nobelium,plog,simple
THEME: process.env.NEXT_PUBLIC_THEME || 'simple', // 当前主题在themes文件夹下可找到所有支持的主题主题名称就是文件夹名例如 example,fukasawa,gitbook,heo,hexo,landing,matery,medium,next,nobelium,plog,simple
THEME_SWITCH: process.env.NEXT_PUBLIC_THEME_SWITCH || false, // 是否显示切换主题按钮
LANG: process.env.NEXT_PUBLIC_LANG || 'zh-CN', // e.g 'zh-CN','en-US' see /lib/lang.js for more.
SINCE: 2021, // e.g if leave this empty, current year will be used.
@@ -304,13 +304,11 @@ const BLOG = {
// HOSTNAME: Webmention绑定之网域通常即为本站网址
// TWITTER_USERNAME: 评论显示区域需要的资讯
// TOKEN: Webmention的API token
COMMENT_WEBMENTION: {
ENABLE: process.env.NEXT_PUBLIC_WEBMENTION_ENABLE || false,
AUTH: process.env.NEXT_PUBLIC_WEBMENTION_AUTH || '',
HOSTNAME: process.env.NEXT_PUBLIC_WEBMENTION_HOSTNAME || '',
TWITTER_USERNAME: process.env.NEXT_PUBLIC_TWITTER_USERNAME || '',
TOKEN: process.env.NEXT_PUBLIC_WEBMENTION_TOKEN || ''
},
COMMENT_WEBMENTION_ENABLE: process.env.NEXT_PUBLIC_WEBMENTION_ENABLE || false,
COMMENT_WEBMENTION_AUTH: process.env.NEXT_PUBLIC_WEBMENTION_AUTH || '',
COMMENT_WEBMENTION_HOSTNAME: process.env.NEXT_PUBLIC_WEBMENTION_HOSTNAME || '',
COMMENT_WEBMENTION_TWITTER_USERNAME: process.env.NEXT_PUBLIC_TWITTER_USERNAME || '',
COMMENT_WEBMENTION_TOKEN: process.env.NEXT_PUBLIC_WEBMENTION_TOKEN || '',
// <---- 评论插件

View File

@@ -1,45 +1,47 @@
'use strict'
import { useEffect } from 'react'
import BLOG from '@/blog.config'
import { loadExternalResource } from '@/lib/utils'
import { useRouter } from 'next/router'
import { siteConfig } from '@/lib/config'
const Ackee = () => {
const router = useRouter()
const server = siteConfig('ANALYTICS_ACKEE_DATA_SERVER')
const domainId = siteConfig('ANALYTICS_ACKEE_DOMAIN_ID')
// 或者使用其他依赖数组,根据需要执行 handleAckee
useEffect(() => {
handleAckeeCallback()
}, [router])
// handleAckee 函数
const handleAckeeCallback = () => {
handleAckee(
router.asPath,
{
server: BLOG.ANALYTICS_ACKEE_DATA_SERVER,
domainId: BLOG.ANALYTICS_ACKEE_DOMAIN_ID
server: server,
domainId: domainId
},
{
/*
* Enable or disable tracking of personal data.
* We recommend to ask the user for permission before turning this option on.
*/
* Enable or disable tracking of personal data.
* We recommend to ask the user for permission before turning this option on.
*/
detailed: true,
/*
* Enable or disable tracking when on localhost.
*/
* Enable or disable tracking when on localhost.
*/
ignoreLocalhost: false,
/*
* Enable or disable the tracking of your own visits.
* This is enabled by default, but should be turned off when using a wildcard Access-Control-Allow-Origin header.
* Some browsers strictly block third-party cookies. The option won't have an impact when this is the case.
*/
* Enable or disable the tracking of your own visits.
* This is enabled by default, but should be turned off when using a wildcard Access-Control-Allow-Origin header.
* Some browsers strictly block third-party cookies. The option won't have an impact when this is the case.
*/
ignoreOwnVisits: false
}
)
}
// 或者使用其他依赖数组,根据需要执行 handleAckee
useEffect(() => {
handleAckeeCallback()
}, [router])
return null
}
@@ -53,8 +55,8 @@ export default Ackee
* @param {Object} environment - Object containing the URL of the Ackee server and the domain id.
* @param {?Object} options - Ackee options.
*/
const handleAckee = async function(pathname, environment, options = {}) {
await loadExternalResource(BLOG.ANALYTICS_ACKEE_TRACKER, 'js')
const handleAckee = async function (pathname, environment, options = {}) {
await loadExternalResource(siteConfig('ANALYTICS_ACKEE_TRACKER'), 'js')
const ackeeTracker = window.ackeeTracker
const instance = ackeeTracker?.create(environment.server, options)

View File

@@ -1,10 +1,10 @@
import { useState, useImperativeHandle, useRef } from 'react'
import BLOG from '@/blog.config'
import algoliasearch from 'algoliasearch'
import replaceSearchResult from '@/components/Mark'
import Link from 'next/link'
import { useGlobal } from '@/lib/global'
import throttle from 'lodash/throttle'
import { siteConfig } from '@/lib/config'
/**
* 结合 Algolia 实现的弹出式搜索框
@@ -31,8 +31,8 @@ export default function AlgoliaSearchModal({ cRef }) {
}
})
const client = algoliasearch(BLOG.ALGOLIA_APP_ID, BLOG.ALGOLIA_SEARCH_ONLY_APP_KEY)
const index = client.initIndex(BLOG.ALGOLIA_INDEX)
const client = algoliasearch(siteConfig('ALGOLIA_APP_ID'), siteConfig('ALGOLIA_SEARCH_ONLY_APP_KEY'))
const index = client.initIndex(siteConfig('ALGOLIA_INDEX'))
/**
* 搜索
@@ -97,7 +97,7 @@ export default function AlgoliaSearchModal({ cRef }) {
setIsModalOpen(false)
}
if (!BLOG.ALGOLIA_APP_ID) {
if (!siteConfig('ALGOLIA_APP_ID')) {
return <></>
}
@@ -123,7 +123,7 @@ export default function AlgoliaSearchModal({ cRef }) {
<ul>
{searchResults.map((result) => (
<li key={result.objectID} className="replace my-2">
<a href={`${BLOG.SUB_PATH}/${result.slug}`} className="font-bold hover:text-blue-600 text-black dark:text-gray-200">
<a href={`${siteConfig('SUB_PATH')}/${result.slug}`} className="font-bold hover:text-blue-600 text-black dark:text-gray-200">
{result.title}
</a>
</li>

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils'
// import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
@@ -11,17 +11,26 @@ import { useEffect } from 'react'
*/
const Artalk = ({ siteInfo }) => {
const artalkCss = siteConfig('COMMENT_ARTALK_CSS')
const artalkServer = siteConfig('COMMENT_ARTALK_SERVER')
const artalkLocale = siteConfig('LANG')
const site = siteConfig('TITLE')
useEffect(() => {
loadExternalResource(BLOG.COMMENT_ARTALK_CSS, 'css')
initArtalk()
}, [])
const initArtalk = async () => {
await loadExternalResource(artalkCss, 'css')
window?.Artalk?.init({
server: BLOG.COMMENT_ARTALK_SERVER, // 后端地址
server: artalkServer, // 后端地址
el: '#artalk', // 容器元素
locale: BLOG.LANG,
locale: artalkLocale,
// pageKey: '/post/1', // 固定链接 (留空自动获取)
// pageTitle: '关于引入 Artalk 的这档子事', // 页面标题 (留空自动获取)
site: siteInfo?.title // 你的站点名
site: site // 你的站点名
})
}, [])
}
return (
<div id="artalk"></div>
)

View File

@@ -1,17 +1,17 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
/**
* 这是一个嵌入组件可以在任意位置全屏显示您的chat-base对话框
* 暂时没有页面引用
* 因为您可以直接用内嵌网页的方式放入您的notion中 https://www.chatbase.co/chatbot-iframe/${BLOG.CHATBASE_ID}
* 因为您可以直接用内嵌网页的方式放入您的notion中 https://www.chatbase.co/chatbot-iframe/${siteConfig('CHATBASE_ID')}
*/
export default function ChatBase() {
if (!BLOG.CHATBASE_ID) {
if (!siteConfig('CHATBASE_ID')) {
return <></>
}
return <iframe
src={`https://www.chatbase.co/chatbot-iframe/${BLOG.CHATBASE_ID}`}
src={`https://www.chatbase.co/chatbot-iframe/${siteConfig('CHATBASE_ID')}`}
width="100%"
style={{ height: '100%', minHeight: '700px' }}
frameborder="0"

View File

@@ -1,9 +1,9 @@
import BLOG from '@/blog.config'
import dynamic from 'next/dynamic'
import Tabs from '@/components/Tabs'
import { isBrowser } from '@/lib/utils'
import { useRouter } from 'next/router'
import Artalk from './Artalk'
import { siteConfig } from '@/lib/config'
const WalineComponent = dynamic(
() => {
@@ -55,13 +55,6 @@ const ValineComponent = dynamic(() => import('@/components/ValineComponent'), {
ssr: false
})
/**
* 是否有评论
*/
export const commentEnable = BLOG.COMMENT_TWIKOO_ENV_ID || BLOG.COMMENT_WALINE_SERVER_URL || BLOG.COMMENT_VALINE_APP_ID ||
BLOG.COMMENT_GISCUS_REPO || BLOG.COMMENT_CUSDIS_APP_ID || BLOG.COMMENT_UTTERRANCES_REPO ||
BLOG.COMMENT_GITALK_CLIENT_ID || BLOG.COMMENT_WEBMENTION.ENABLE
/**
* 评论组件
* @param {*} param0
@@ -70,6 +63,7 @@ BLOG.COMMENT_GISCUS_REPO || BLOG.COMMENT_CUSDIS_APP_ID || BLOG.COMMENT_UTTERRANC
const Comment = ({ siteInfo, frontMatter, className }) => {
const router = useRouter()
// 当连接中有特殊参数时跳转到评论区
if (isBrowser && ('giscus' in router.query || router.query.target === 'comment')) {
setTimeout(() => {
const url = router.asPath.replace('?target=comment', '')
@@ -85,41 +79,41 @@ const Comment = ({ siteInfo, frontMatter, className }) => {
return (
<div key={frontMatter?.id} id='comment' className={`comment mt-5 text-gray-800 dark:text-gray-300 ${className || ''}`}>
<Tabs>
{BLOG.COMMENT_ARTALK_SERVER && (<div key='Artalk'>
<Artalk siteInfo={siteInfo} />
{siteConfig('COMMENT_ARTALK_SERVER') && (<div key='Artalk'>
<Artalk />
</div>)}
{BLOG.COMMENT_TWIKOO_ENV_ID && (<div key='Twikoo'>
{siteConfig('COMMENT_TWIKOO_ENV_ID') && (<div key='Twikoo'>
<TwikooCompenent />
</div>)}
{BLOG.COMMENT_WALINE_SERVER_URL && (<div key='Waline'>
{siteConfig('COMMENT_WALINE_SERVER_URL') && (<div key='Waline'>
<WalineComponent />
</div>)}
{BLOG.COMMENT_VALINE_APP_ID && (<div key='Valine' name='reply'>
{siteConfig('COMMENT_VALINE_APP_ID') && (<div key='Valine' name='reply'>
<ValineComponent path={frontMatter.id} />
</div>)}
{BLOG.COMMENT_GISCUS_REPO && (
{siteConfig('COMMENT_GISCUS_REPO') && (
<div key="Giscus">
<GiscusComponent className="px-2" />
</div>
)}
{BLOG.COMMENT_CUSDIS_APP_ID && (<div key='Cusdis'>
{siteConfig('COMMENT_CUSDIS_APP_ID') && (<div key='Cusdis'>
<CusdisComponent frontMatter={frontMatter} />
</div>)}
{BLOG.COMMENT_UTTERRANCES_REPO && (<div key='Utterance'>
{siteConfig('COMMENT_UTTERRANCES_REPO') && (<div key='Utterance'>
<UtterancesComponent issueTerm={frontMatter.id} className='px-2' />
</div>)}
{BLOG.COMMENT_GITALK_CLIENT_ID && (<div key='GitTalk'>
{siteConfig('COMMENT_GITALK_CLIENT_ID') && (<div key='GitTalk'>
<GitalkComponent frontMatter={frontMatter} />
</div>)}
{BLOG.COMMENT_WEBMENTION.ENABLE && (<div key='WebMention'>
{siteConfig('COMMENT_WEBMENTION_ENABLE') && (<div key='WebMention'>
<WebMentionComponent frontMatter={frontMatter} className="px-2" />
</div>)}
</Tabs>

View File

@@ -1,34 +1,34 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import Head from 'next/head'
const CommonHead = ({ meta, children }) => {
let url = BLOG?.PATH?.length ? `${BLOG.LINK}/${BLOG.SUB_PATH}` : BLOG.LINK
let url = siteConfig('PATH')?.length ? `${siteConfig('LINK')}/${siteConfig('SUB_PATH', '')}` : siteConfig('LINK')
let image
if (meta) {
url = `${url}/${meta.slug}`
image = meta.image || '/bg_image.jpg'
}
const title = meta?.title || BLOG.TITLE
const description = meta?.description || BLOG.DESCRIPTION
const title = meta?.title || siteConfig('TITLE')
const description = meta?.description || siteConfig('DESCRIPTION')
const type = meta?.type || 'website'
const keywords = meta?.tags || BLOG.KEYWORDS
const lang = BLOG.LANG.replace('-', '_') // Facebook OpenGraph 要 zh_CN 這樣的格式才抓得到語言
const category = meta?.category || BLOG.KEYWORDS || '軟體科技' // section 主要是像是 category 這樣的分類Facebook 用這個來抓連結的分類
const keywords = meta?.tags || siteConfig('KEYWORDS')
const lang = siteConfig('LANG').replace('-', '_') // Facebook OpenGraph 要 zh_CN 這樣的格式才抓得到語言
const category = meta?.category || siteConfig('KEYWORDS') // section 主要是像是 category 這樣的分類Facebook 用這個來抓連結的分類
return (
<Head>
<title>{title}</title>
<meta name="theme-color" content={BLOG.BACKGROUND_DARK} />
<meta name="theme-color" content={siteConfig('BACKGROUND_DARK')} />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0" />
<meta name="robots" content="follow, index" />
<meta charSet="UTF-8" />
{BLOG.SEO_GOOGLE_SITE_VERIFICATION && (
{siteConfig('SEO_GOOGLE_SITE_VERIFICATION') && (
<meta
name="google-site-verification"
content={BLOG.SEO_GOOGLE_SITE_VERIFICATION}
content={siteConfig('SEO_GOOGLE_SITE_VERIFICATION')}
/>
)}
{BLOG.SEO_BAIDU_SITE_VERIFICATION && (<meta name="baidu-site-verification" content={BLOG.SEO_BAIDU_SITE_VERIFICATION} />)}
{siteConfig('SEO_BAIDU_SITE_VERIFICATION') && (<meta name="baidu-site-verification" content={siteConfig('SEO_BAIDU_SITE_VERIFICATION')} />)}
<meta name="keywords" content={keywords} />
<meta name="description" content={description} />
<meta property="og:locale" content={lang} />
@@ -36,33 +36,33 @@ const CommonHead = ({ meta, children }) => {
<meta property="og:description" content={description} />
<meta property="og:url" content={url} />
<meta property="og:image" content={image} />
<meta property="og:site_name" content={BLOG.TITLE} />
<meta property="og:site_name" content={siteConfig('TITLE')} />
<meta property="og:type" content={type} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:description" content={description} />
<meta name="twitter:title" content={title} />
{BLOG.COMMENT_WEBMENTION.ENABLE && (
{siteConfig('COMMENT_WEBMENTION_ENABLE') && (
<>
<link rel="webmention" href={`https://webmention.io/${BLOG.COMMENT_WEBMENTION.HOSTNAME}/webmention`} />
<link rel="pingback" href={`https://webmention.io/${BLOG.COMMENT_WEBMENTION.HOSTNAME}/xmlrpc`} />
<link rel="webmention" href={`https://webmention.io/${siteConfig('COMMENT_WEBMENTION_HOSTNAME')}/webmention`} />
<link rel="pingback" href={`https://webmention.io/${siteConfig('COMMENT_WEBMENTION_HOSTNAME')}/xmlrpc`} />
</>
)}
{BLOG.COMMENT_WEBMENTION.ENABLE && BLOG.COMMENT_WEBMENTION.AUTH !== '' && (
<link href={BLOG.COMMENT_WEBMENTION.AUTH} rel="me" />
{siteConfig('COMMENT_WEBMENTION_ENABLE') && siteConfig('COMMENT_WEBMENTION_AUTH') !== '' && (
<link href={siteConfig('COMMENT_WEBMENTION_AUTH')} rel="me" />
)}
{JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && <meta name="referrer" content="no-referrer-when-downgrade" />}
{JSON.parse(siteConfig('ANALYTICS_BUSUANZI_ENABLE')) && <meta name="referrer" content="no-referrer-when-downgrade" />}
{meta?.type === 'Post' && (
<>
<meta
property="article:published_time"
content={meta.publishDay}
/>
<meta property="article:author" content={BLOG.AUTHOR} />
<meta property="article:author" content={siteConfig('AUTHOR')} />
<meta property="article:section" content={category} />
<meta property="article:publisher" content={BLOG.FACEBOOK_PAGE} />
<meta property="article:publisher" content={siteConfig('FACEBOOK_PAGE')} />
</>
)}
{children}

View File

@@ -1,125 +0,0 @@
import BLOG from '@/blog.config'
/**
* 第三方代码 统计脚本
* @returns {JSX.Element}
* @constructor
*/
const CommonScript = () => {
return (<>
{BLOG.CHATBASE_ID && (<>
<script id={BLOG.CHATBASE_ID} src="https://www.chatbase.co/embed.min.js" defer/>
<script async dangerouslySetInnerHTML={{
__html: `
window.chatbaseConfig = {
chatbotId: "${BLOG.CHATBASE_ID}",
}
`
}}/>
</>)}
{BLOG.COMMENT_DAO_VOICE_ID && (<>
{/* DaoVoice 反馈 */}
<script async dangerouslySetInnerHTML={{
__html: `
(function(i,s,o,g,r,a,m){i["DaoVoiceObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;a.charset="utf-8";m.parentNode.insertBefore(a,m)})(window,document,"script",('https:' == document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/daf1a94b.js","daovoice")
`
}}
/>
<script async dangerouslySetInnerHTML={{
__html: `
daovoice('init', {
app_id: "${BLOG.COMMENT_DAO_VOICE_ID}"
});
daovoice('update');
`
}}
/>
</>)}
{BLOG.AD_WWADS_ID && <script type="text/javascript" charSet="UTF-8" src="https://cdn.wwads.cn/js/makemoney.js" async></script>}
{BLOG.COMMENT_CUSDIS_APP_ID && <script defer src={`https://cusdis.com/js/widget/lang/${BLOG.LANG.toLowerCase()}.js`} />}
{BLOG.COMMENT_TWIKOO_ENV_ID && <script defer src={BLOG.COMMENT_TWIKOO_CDN_URL}/> }
{BLOG.COMMENT_ARTALK_SERVER && <script defer src={BLOG.COMMENT_ARTALK_JS}/> }
{BLOG.COMMENT_TIDIO_ID && <script async src={`//code.tidio.co/${BLOG.COMMENT_TIDIO_ID}.js`} />}
{/* gitter聊天室 */}
{BLOG.COMMENT_GITTER_ROOM && (<>
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer/>
<script async dangerouslySetInnerHTML={{
__html: `
((window.gitter = {}).chat = {}).options = {
room: '${BLOG.COMMENT_GITTER_ROOM}'
};
`
}}/>
</>)}
{/* 代码统计 */}
{/* ackee统计脚本 */}
{/* {BLOG.ANALYTICS_ACKEE_TRACKER && (
<script async src={BLOG.ANALYTICS_ACKEE_TRACKER}
data-ackee-server={BLOG.ANALYTICS_ACKEE_DATA_SERVER}
data-ackee-domain-id={BLOG.ANALYTICS_ACKEE_DOMAIN_ID}
/>
)} */}
{/* 百度统计 */}
{BLOG.ANALYTICS_BAIDU_ID && (
<script async
dangerouslySetInnerHTML={{
__html: `
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?${BLOG.ANALYTICS_BAIDU_ID}";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
`
}}
/>
)}
{/* 站长统计 */}
{BLOG.ANALYTICS_CNZZ_ID && (
<script async
dangerouslySetInnerHTML={{
__html: `
document.write(unescape("%3Cspan style='display:none' id='cnzz_stat_icon_${BLOG.ANALYTICS_CNZZ_ID}'%3E%3C/span%3E%3Cscript src='https://s9.cnzz.com/z_stat.php%3Fid%3D${BLOG.ANALYTICS_CNZZ_ID}' type='text/javascript'%3E%3C/script%3E"));
`
}}
/>
)}
{/* 谷歌统计 */}
{BLOG.ANALYTICS_GOOGLE_ID && (<>
<script async
src={`https://www.googletagmanager.com/gtag/js?id=${BLOG.ANALYTICS_GOOGLE_ID}`}
/>
<script async
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${BLOG.ANALYTICS_GOOGLE_ID}', {
page_path: window.location.pathname,
});
`
}}
/>
</>)}
{/* 引入音乐播放 */}
{JSON.parse(BLOG.MUSIC_PLAYER) && <script async src={BLOG.MUSIC_PLAYER_CDN_URL} />}
{JSON.parse(BLOG.MUSIC_PLAYER) && JSON.parse(BLOG.MUSIC_PLAYER_METING) && <script async src="https://cdnjs.cloudflare.com/ajax/libs/meting/2.0.1/Meting.min.js" />}
</>)
}
export default CommonScript

View File

@@ -1,28 +1,34 @@
import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { loadExternalResource } from '@/lib/utils'
import { siteConfig } from '@/lib/config'
const CusdisComponent = ({ frontMatter }) => {
const { locale } = useGlobal()
const router = useRouter()
const { isDarkMode } = useGlobal()
const { isDarkMode, lang } = useGlobal()
const src = siteConfig('COMMENT_CUSDIS_SCRIPT_SRC')
const i18nForCusdis = siteConfig('LANG').toLowerCase().indexOf('zh') === 0 ? siteConfig('LANG').toLowerCase() : siteConfig('LANG').toLowerCase().substring(0, 2)
const langCDN = siteConfig('COMMENT_CUSDIS_LANG_SRC', `https://cusdis.com/js/widget/lang/${i18nForCusdis}.js`)
// 处理cusdis主题
useEffect(() => {
loadExternalResource(BLOG.COMMENT_CUSDIS_SCRIPT_SRC, 'js').then(url => {
const CUSDIS = window.CUSDIS
CUSDIS?.initial()
})
}, [isDarkMode])
loadCusdis()
}, [isDarkMode, lang])
const loadCusdis = async () => {
await loadExternalResource(langCDN, 'js')
await loadExternalResource(src, 'js')
window?.CUSDIS?.initial()
}
return <div id="cusdis_thread"
lang={locale.LOCALE.toLowerCase()}
data-host={BLOG.COMMENT_CUSDIS_HOST}
data-app-id={BLOG.COMMENT_CUSDIS_APP_ID}
lang={lang.toLowerCase()}
data-host={siteConfig('COMMENT_CUSDIS_HOST')}
data-app-id={siteConfig('COMMENT_CUSDIS_APP_ID')}
data-page-id={frontMatter.id}
data-page-url={BLOG.LINK + router.asPath}
data-page-url={siteConfig('LINK') + router.asPath}
data-page-title={frontMatter.title}
data-theme={isDarkMode ? 'dark' : 'light'}
></div>

View File

@@ -3,8 +3,8 @@ import { useRouter } from 'next/router'
import { useEffect, useState, useRef, useLayoutEffect } from 'react'
import { useGlobal } from '@/lib/global'
import { saveDarkModeToCookies, THEMES } from '@/themes/theme'
import BLOG from '@/blog.config'
import useWindowSize from '@/hooks/useWindowSize'
import { siteConfig } from '@/lib/config'
/**
* 自定义右键菜单
@@ -28,7 +28,7 @@ export default function CustomContextMenu(props) {
function handleJumpToRandomPost() {
const randomIndex = Math.floor(Math.random() * latestPosts.length)
const randomPost = latestPosts[randomIndex]
router.push(`${BLOG.SUB_PATH}/${randomPost?.slug}`)
router.push(`${siteConfig('SUB_PATH', '')}/${randomPost?.slug}`)
}
useLayoutEffect(() => {

View File

@@ -1,5 +1,4 @@
import { useGlobal } from '@/lib/global'
import { saveDarkModeToCookies } from '@/themes/theme'
import { Moon, Sun } from './HeroIcons'
import { useImperativeHandle } from 'react'
@@ -8,7 +7,7 @@ import { useImperativeHandle } from 'react'
*/
const DarkModeButton = (props) => {
const { cRef, className } = props
const { isDarkMode, updateDarkMode } = useGlobal()
const { isDarkMode, toggleDarkMode } = useGlobal()
/**
* 对外暴露方法
@@ -16,22 +15,12 @@ const DarkModeButton = (props) => {
useImperativeHandle(cRef, () => {
return {
handleChangeDarkMode: () => {
handleChangeDarkMode()
toggleDarkMode()
}
}
})
// 用户手动设置主题
const handleChangeDarkMode = () => {
const newStatus = !isDarkMode
saveDarkModeToCookies(newStatus)
updateDarkMode(newStatus)
const htmlElement = document.getElementsByTagName('html')[0]
htmlElement.classList?.remove(newStatus ? 'light' : 'dark')
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
}
return <div onClick={handleChangeDarkMode} className={`${className || ''} flex justify-center dark:text-gray-200 text-gray-800`}>
return <div onClick={toggleDarkMode} className={`${className || ''} flex justify-center dark:text-gray-200 text-gray-800`}>
<div id='darkModeButton' className=' hover:scale-110 cursor-pointer transform duration-200 w-5 h-5'> {isDarkMode ? <Sun /> : <Moon />}</div>
</div>
}

View File

@@ -1,9 +1,10 @@
import BLOG from '@/blog.config'
import { useEffect, useState } from 'react'
import Select from './Select'
import { useGlobal } from '@/lib/global'
import { THEMES } from '@/themes/theme'
import { useRouter } from 'next/router'
import { siteConfigMap } from '@/lib/config'
import { getQueryParam } from '@/lib/utils'
/**
*
@@ -13,14 +14,14 @@ const DebugPanel = () => {
const [show, setShow] = useState(false)
const { theme, switchTheme, locale } = useGlobal()
const router = useRouter()
const currentTheme = getQueryParam(router.asPath, 'theme') || theme
const [siteConfig, updateSiteConfig] = useState({})
// 主题下拉框
const themeOptions = THEMES?.map(t => ({ value: t, text: t }))
useEffect(() => {
updateSiteConfig(Object.assign({}, BLOG))
// updateThemeConfig(Object.assign({}, ThemeMap[BLOG.THEME].THEME_CONFIG))
updateSiteConfig(Object.assign({}, siteConfigMap()))
}, [])
function toggleShow() {
@@ -71,7 +72,7 @@ const DebugPanel = () => {
<div className='flex'>
<Select
label={locale.COMMON.THEME_SWITCH}
value={theme}
value={currentTheme}
options={themeOptions}
onChange={handleUpdateDebugTheme}
/>

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { useEffect } from 'react'
/**
@@ -6,7 +6,7 @@ import { useEffect } from 'react'
*/
export default function DisableCopy() {
useEffect(() => {
if (!JSON.parse(BLOG.CAN_COPY)) {
if (!JSON.parse(siteConfig('CAN_COPY'))) {
// 全栈添加禁止复制的样式
document.getElementsByTagName('html')[0].classList.add('forbid-copy')
// 监听复制事件

View File

@@ -1,18 +1,7 @@
import BLOG from 'blog.config'
import { siteConfig } from '@/lib/config'
import dynamic from 'next/dynamic'
import WebWhiz from './Webwhiz'
// 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 })
@@ -33,35 +22,134 @@ const VConsole = dynamic(() => import('@/components/VConsole'), { ssr: false })
const CustomContextMenu = dynamic(() => import('@/components/CustomContextMenu'), { ssr: false })
const DisableCopy = dynamic(() => import('@/components/DisableCopy'), { ssr: false })
const AdBlockDetect = dynamic(() => import('@/components/AdBlockDetect'), { 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 />}
{JSON.parse(BLOG.CUSTOM_RIGHT_CLICK_CONTEXT_MENU) && <CustomContextMenu {...props} />}
{!JSON.parse(BLOG.CAN_COPY) && <DisableCopy/>}
{JSON.parse(BLOG.WEB_WHIZ_ENABLED) && <WebWhiz/>}
{JSON.parse(BLOG.AD_WWADS_BLOCK_DETECT) && <AdBlockDetect/>}
<VConsole/>
</>
{JSON.parse(siteConfig('THEME_SWITCH')) && <ThemeSwitch />}
{JSON.parse(siteConfig('DEBUG')) && <DebugPanel />}
{siteConfig('ANALYTICS_ACKEE_TRACKER') && <Ackee />}
{siteConfig('ANALYTICS_GOOGLE_ID') && <Gtag />}
{siteConfig('ANALYTICS_VERCEL') && <Analytics />}
{JSON.parse(siteConfig('ANALYTICS_BUSUANZI_ENABLE')) && <Busuanzi />}
{siteConfig('ADSENSE_GOOGLE_ID') && <GoogleAdsense />}
{siteConfig('FACEBOOK_APP_ID') && siteConfig('FACEBOOK_PAGE_ID') && <Messenger />}
{JSON.parse(siteConfig('FIREWORKS')) && <Fireworks />}
{JSON.parse(siteConfig('SAKURA')) && <Sakura />}
{JSON.parse(siteConfig('STARRY_SKY')) && <StarrySky />}
{JSON.parse(siteConfig('MUSIC_PLAYER')) && <MusicPlayer />}
{JSON.parse(siteConfig('NEST')) && <Nest />}
{JSON.parse(siteConfig('FLUTTERINGRIBBON')) && <FlutteringRibbon />}
{JSON.parse(siteConfig('COMMENT_TWIKOO_COUNT_ENABLE')) && <TwikooCommentCounter {...props} />}
{JSON.parse(siteConfig('RIBBON')) && <Ribbon />}
{JSON.parse(siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU')) && <CustomContextMenu {...props} />}
{!JSON.parse(siteConfig('CAN_COPY')) && <DisableCopy />}
{JSON.parse(siteConfig('WEB_WHIZ_ENABLED')) && <WebWhiz />}
{JSON.parse(siteConfig('AD_WWADS_BLOCK_DETECT')) && <AdBlockDetect />}
<VConsole />
{siteConfig('CHATBASE_ID') && (<>
<script id={siteConfig('CHATBASE_ID')} src="https://www.chatbase.co/embed.min.js" defer />
<script async dangerouslySetInnerHTML={{
__html: `
window.chatbaseConfig = {
chatbotId: "${siteConfig('CHATBASE_ID')}",
}
`
}} />
</>)}
{siteConfig('COMMENT_DAO_VOICE_ID') && (<>
{/* DaoVoice 反馈 */}
<script async dangerouslySetInnerHTML={{
__html: `
(function(i,s,o,g,r,a,m){i["DaoVoiceObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;a.charset="utf-8";m.parentNode.insertBefore(a,m)})(window,document,"script",('https:' == document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/daf1a94b.js","daovoice")
`
}}
/>
<script async dangerouslySetInnerHTML={{
__html: `
daovoice('init', {
app_id: "${siteConfig('COMMENT_DAO_VOICE_ID')}"
});
daovoice('update');
`
}}
/>
</>)}
{siteConfig('AD_WWADS_ID') && <script type="text/javascript" charSet="UTF-8" src="https://cdn.wwads.cn/js/makemoney.js" async></script>}
{siteConfig('COMMENT_TWIKOO_ENV_ID') && <script defer src={siteConfig('COMMENT_TWIKOO_CDN_URL')} />}
{siteConfig('COMMENT_ARTALK_SERVER') && <script defer src={siteConfig('COMMENT_ARTALK_JS')} />}
{siteConfig('COMMENT_TIDIO_ID') && <script async src={`//code.tidio.co/${siteConfig('COMMENT_TIDIO_ID')}.js`} />}
{/* gitter聊天室 */}
{siteConfig('COMMENT_GITTER_ROOM') && (<>
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer />
<script async dangerouslySetInnerHTML={{
__html: `
((window.gitter = {}).chat = {}).options = {
room: '${siteConfig('COMMENT_GITTER_ROOM')}'
};
`
}} />
</>)}
{/* 百度统计 */}
{siteConfig('ANALYTICS_BAIDU_ID') && (
<script async
dangerouslySetInnerHTML={{
__html: `
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?${siteConfig('ANALYTICS_BAIDU_ID')}";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
`
}}
/>
)}
{/* 站长统计 */}
{siteConfig('ANALYTICS_CNZZ_ID') && (
<script async
dangerouslySetInnerHTML={{
__html: `
document.write(unescape("%3Cspan style='display:none' id='cnzz_stat_icon_${siteConfig('ANALYTICS_CNZZ_ID')}'%3E%3C/span%3E%3Cscript src='https://s9.cnzz.com/z_stat.php%3Fid%3D${siteConfig('ANALYTICS_CNZZ_ID')}' type='text/javascript'%3E%3C/script%3E"));
`
}}
/>
)}
{/* 谷歌统计 */}
{siteConfig('ANALYTICS_GOOGLE_ID') && (<>
<script async
src={`https://www.googletagmanager.com/gtag/js?id=${siteConfig('ANALYTICS_GOOGLE_ID')}`}
/>
<script async
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${siteConfig('ANALYTICS_GOOGLE_ID')}', {
page_path: window.location.pathname,
});
`
}}
/>
</>)}
</>
}
export default ExternalPlugin

View File

@@ -1,12 +1,12 @@
import BLOG from '@/blog.config'
import { Component } from 'react'
import PropTypes from 'prop-types'
import { siteConfig } from '@/lib/config'
export default function Messenger() {
return <MessengerCustomerChat
pageId={BLOG.FACEBOOK_PAGE_ID}
appId={BLOG.FACEBOOK_APP_ID}
language={BLOG.LANG.replace('-', '_')}
pageId={siteConfig('FACEBOOK_PAGE_ID')}
appId={siteConfig('FACEBOOK_APP_ID')}
language={siteConfig('LANG').replace('-', '_')}
shouldShowDialog={true}
/>
}

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { FacebookProvider, Page } from 'react-facebook'
import { FacebookIcon } from 'react-share'
@@ -7,27 +7,27 @@ import { FacebookIcon } from 'react-share'
* @returns
*/
const FacebookPage = () => {
if (!BLOG.FACEBOOK_APP_ID || !BLOG.FACEBOOK_PAGE) {
if (!siteConfig('FACEBOOK_APP_ID') || !siteConfig('FACEBOOK_PAGE')) {
return <></>
}
return <div className="shadow-md hover:shadow-xl dark:text-gray-300 border dark:border-black rounded-xl px-2 py-4 bg-white dark:bg-hexo-black-gray lg:duration-100 justify-center">
{BLOG.FACEBOOK_PAGE && (
{siteConfig('FACEBOOK_PAGE') && (
<div className="flex items-center pb-2">
<a
href={BLOG.FACEBOOK_PAGE}
href={siteConfig('FACEBOOK_PAGE')}
target="_blank"
rel="noopener noreferrer"
className="p-1 pr-2 pt-0"
>
<FacebookIcon size={28} round />
</a>
<a href={BLOG.FACEBOOK_PAGE} rel="noopener noreferrer" target="_blank">
{BLOG.FACEBOOK_PAGE_TITLE}
<a href={siteConfig('FACEBOOK_PAGE')} rel="noopener noreferrer" target="_blank">
{siteConfig('FACEBOOK_PAGE_TITLE')}
</a>
</div>
)}
{BLOG.FACEBOOK_APP_ID && <FacebookProvider appId={BLOG.FACEBOOK_APP_ID}>
<Page href={BLOG.FACEBOOK_PAGE} tabs="timeline" />
{siteConfig('FACEBOOK_APP_ID') && <FacebookProvider appId={siteConfig('FACEBOOK_APP_ID')}>
<Page href={siteConfig('FACEBOOK_PAGE')} tabs="timeline" />
</FacebookProvider>}
</div>
}

View File

@@ -4,11 +4,17 @@
*/
import { useEffect } from 'react'
import anime from 'animejs'
import BLOG from 'blog.config'
import { siteConfig } from '@/lib/config'
/**
* 鼠标点击烟花特效
* @returns
*/
const Fireworks = () => {
const fireworksColor = siteConfig('FIREWORKS_COLOR')
useEffect(() => {
createFireworks({})
createFireworks({ colors: fireworksColor })
}, [])
return <canvas id='fireworks' className='fireworks'></canvas>
}
@@ -20,7 +26,7 @@ export default Fireworks
*/
function createFireworks(config) {
const defaultConfig = {
colors: BLOG.FIREWORKS_COLOR,
colors: config?.colors,
numberOfParticules: 20,
orbitRadius: {
min: 50,

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import Giscus from '@giscus/react'
@@ -15,17 +15,17 @@ const GiscusComponent = () => {
return (
<Giscus
repo={BLOG.COMMENT_GISCUS_REPO}
repoId={BLOG.COMMENT_GISCUS_REPO_ID}
categoryId={BLOG.COMMENT_GISCUS_CATEGORY_ID}
mapping={BLOG.COMMENT_GISCUS_MAPPING}
reactionsEnabled={BLOG.COMMENT_GISCUS_REACTIONS_ENABLED}
emitMetadata={BLOG.COMMENT_GISCUS_EMIT_METADATA}
repo={siteConfig('COMMENT_GISCUS_REPO')}
repoId={siteConfig('COMMENT_GISCUS_REPO_ID')}
categoryId={siteConfig('COMMENT_GISCUS_CATEGORY_ID')}
mapping={siteConfig('COMMENT_GISCUS_MAPPING')}
reactionsEnabled={siteConfig('COMMENT_GISCUS_REACTIONS_ENABLED')}
emitMetadata={siteConfig('COMMENT_GISCUS_EMIT_METADATA')}
theme={theme}
inputPosition={BLOG.COMMENT_GISCUS_INPUT_POSITION}
lang={BLOG.COMMENT_GISCUS_LANG}
loading={BLOG.COMMENT_GISCUS_LOADING}
crossorigin={BLOG.COMMENT_GISCUS_CROSSORIGIN}
inputPosition={siteConfig('COMMENT_GISCUS_INPUT_POSITION')}
lang={siteConfig('COMMENT_GISCUS_LANG')}
loading={siteConfig('COMMENT_GISCUS_LOADING')}
crossorigin={siteConfig('COMMENT_GISCUS_CROSSORIGIN')}
/>
)
}

View File

@@ -1,37 +1,39 @@
// import 'gitalk/dist/gitalk.css'
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
// import GitalkComponent from 'gitalk/dist/gitalk-component'
/**
* gitalk评论插件
* @param {*} param0
* @returns
*/
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)
// }} />
const loadGitalk = async() => {
await loadExternalResource(BLOG.COMMENT_GITALK_CSS_CDN_URL, 'css')
await loadExternalResource(BLOG.COMMENT_GITALK_JS_CDN_URL, 'js')
const Gitalk = window.Gitalk
const gitalkCSSCDN = siteConfig('COMMENT_GITALK_CSS_CDN_URL')
const gitalkJSCDN = siteConfig('COMMENT_GITALK_JS_CDN_URL')
const clientId = siteConfig('COMMENT_GITALK_CLIENT_ID')
const clientSecret = siteConfig('COMMENT_GITALK_CLIENT_SECRET')
const repo = siteConfig('COMMENT_GITALK_REPO')
const owner = siteConfig('COMMENT_GITALK_OWNER')
const admin = siteConfig('COMMENT_GITALK_ADMIN').split(',')
const distractionFreeMode = siteConfig('COMMENT_GITALK_DISTRACTION_FREE_MODE')
const loadGitalk = async() => {
await loadExternalResource(gitalkCSSCDN, 'css')
await loadExternalResource(gitalkJSCDN, '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(','),
clientID: clientId,
clientSecret: clientSecret,
repo: repo,
owner: owner,
admin: admin,
id: frontMatter.id, // Ensure uniqueness and length less than 50
distractionFreeMode: JSON.parse(BLOG.COMMENT_GITALK_DISTRACTION_FREE_MODE) // Facebook-like distraction free mode
distractionFreeMode: distractionFreeMode // Facebook-like distraction free mode
})
gitalk.render('gitalk-container')
}
useEffect(() => {
loadGitalk()
}, [])

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
@@ -9,7 +9,7 @@ import { useEffect } from 'react'
*/
export default function GoogleAdsense() {
const initGoogleAdsense = () => {
loadExternalResource(`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${BLOG.ADSENSE_GOOGLE_ID}`, 'js').then(url => {
loadExternalResource(`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${siteConfig('ADSENSE_GOOGLE_ID')}`, 'js').then(url => {
setTimeout(() => {
const ads = document.getElementsByClassName('adsbygoogle')
const adsbygoogle = window.adsbygoogle
@@ -44,7 +44,7 @@ export default function GoogleAdsense() {
* 添加 可以在本地调试
*/
const AdSlot = ({ type = 'show' }) => {
if (!BLOG.ADSENSE_GOOGLE_ID) {
if (!siteConfig('ADSENSE_GOOGLE_ID')) {
return null
}
// 文章内嵌广告
@@ -53,9 +53,9 @@ const AdSlot = ({ type = 'show' }) => {
style={{ display: 'block', textAlign: 'center' }}
data-ad-layout="in-article"
data-ad-format="fluid"
data-adtest={BLOG.ADSENSE_GOOGLE_TEST ? 'on' : 'off'}
data-ad-client={BLOG.ADSENSE_GOOGLE_ID}
data-ad-slot={BLOG.ADSENSE_GOOGLE_SLOT_IN_ARTICLE}></ins>
data-adtest={siteConfig('ADSENSE_GOOGLE_TEST') ? 'on' : 'off'}
data-ad-client={siteConfig('ADSENSE_GOOGLE_ID')}
data-ad-slot={siteConfig('ADSENSE_GOOGLE_SLOT_IN_ARTICLE')}></ins>
}
// 信息流广告
@@ -64,9 +64,9 @@ const AdSlot = ({ type = 'show' }) => {
data-ad-format="fluid"
data-ad-layout-key="-5j+cz+30-f7+bf"
style={{ display: 'block' }}
data-adtest={BLOG.ADSENSE_GOOGLE_TEST ? 'on' : 'off'}
data-ad-client={BLOG.ADSENSE_GOOGLE_ID}
data-ad-slot={BLOG.ADSENSE_GOOGLE_SLOT_FLOW}></ins>
data-adtest={siteConfig('ADSENSE_GOOGLE_TEST') ? 'on' : 'off'}
data-ad-client={siteConfig('ADSENSE_GOOGLE_ID')}
data-ad-slot={siteConfig('ADSENSE_GOOGLE_SLOT_FLOW')}></ins>
}
// 原生广告
@@ -74,17 +74,17 @@ const AdSlot = ({ type = 'show' }) => {
return <ins className="adsbygoogle"
style={{ display: 'block', textAlign: 'center' }}
data-ad-format="autorelaxed"
data-adtest={BLOG.ADSENSE_GOOGLE_TEST ? 'on' : 'off'}
data-ad-client={BLOG.ADSENSE_GOOGLE_ID}
data-ad-slot={BLOG.ADSENSE_GOOGLE_SLOT_NATIVE}></ins>
data-adtest={siteConfig('ADSENSE_GOOGLE_TEST') ? 'on' : 'off'}
data-ad-client={siteConfig('ADSENSE_GOOGLE_ID')}
data-ad-slot={siteConfig('ADSENSE_GOOGLE_SLOT_NATIVE')}></ins>
}
// 展示广告
return <ins className="adsbygoogle"
style={{ display: 'block' }}
data-ad-client={BLOG.ADSENSE_GOOGLE_ID}
data-adtest={BLOG.ADSENSE_GOOGLE_TEST ? 'on' : 'off'}
data-ad-slot={BLOG.ADSENSE_GOOGLE_SLOT_AUTO}
data-ad-client={siteConfig('ADSENSE_GOOGLE_ID')}
data-adtest={siteConfig('ADSENSE_GOOGLE_TEST') ? 'on' : 'off'}
data-ad-slot={siteConfig('ADSENSE_GOOGLE_SLOT_AUTO')}
data-ad-format="auto"
data-full-width-responsive="true"></ins>
}

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import Head from 'next/head'
import React, { useEffect, useRef, useState } from 'react'
@@ -12,7 +12,7 @@ export default function LazyImage({
id,
src,
alt,
placeholderSrc = BLOG.IMG_LAZY_LOAD_PLACEHOLDER,
placeholderSrc,
className,
width,
height,
@@ -22,6 +22,9 @@ export default function LazyImage({
}) {
const imageRef = useRef(null)
const [imageLoaded, setImageLoaded] = useState(false)
if (!placeholderSrc) {
placeholderSrc = siteConfig('IMG_LAZY_LOAD_PLACEHOLDER')
}
const handleImageLoad = () => {
setImageLoaded(true)

View File

@@ -1,12 +1,13 @@
/* eslint-disable no-undef */
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
export default function Live2D() {
const { theme, switchTheme } = useGlobal()
const showPet = JSON.parse(BLOG.WIDGET_PET)
const showPet = JSON.parse(siteConfig('WIDGET_PET'))
const petLink = siteConfig('WIDGET_PET_LINK')
useEffect(() => {
if (showPet) {
@@ -16,7 +17,7 @@ export default function Live2D() {
if (typeof window?.loadlive2d !== 'undefined') {
// https://github.com/xiazeyu/live2d-widget-models
try {
loadlive2d('live2d', BLOG.WIDGET_PET_LINK)
loadlive2d('live2d', petLink)
} catch (error) {
console.error('读取PET模型', error)
}
@@ -26,7 +27,7 @@ export default function Live2D() {
}, [theme])
function handleClick() {
if (JSON.parse(BLOG.WIDGET_PET_SWITCH_THEME)) {
if (JSON.parse(siteConfig('WIDGET_PET_SWITCH_THEME'))) {
switchTheme()
}
}

View File

@@ -4,11 +4,10 @@ import mediumZoom from '@fisch0920/medium-zoom'
import React, { useEffect, useRef } from 'react'
// import { Code } from 'react-notion-x/build/third-party/code'
import TweetEmbed from 'react-tweet-embed'
import BLOG from '@/blog.config'
import 'katex/dist/katex.min.css'
import { mapImgUrl } from '@/lib/notion/mapImage'
import { isBrowser } from '@/lib/utils'
import { siteConfig } from '@/lib/config'
const Code = dynamic(() =>
import('react-notion-x/build/third-party/code').then(async (m) => {
@@ -63,7 +62,7 @@ const NotionPage = ({ post, className }) => {
useEffect(() => {
// 将相册gallery下的图片加入放大功能
if (JSON.parse(BLOG.POST_DISABLE_GALLERY_CLICK)) {
if (siteConfig('POST_DISABLE_GALLERY_CLICK')) {
setTimeout(() => {
if (isBrowser) {
const imgList = document?.querySelectorAll('.notion-collection-card-cover img')

View File

@@ -1,27 +1,54 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils'
import { useEffect, useRef, useState } from 'react'
/**
* 音乐播放器
* @returns
*/
const Player = () => {
const [player, setPlayer] = useState()
const ref = useRef(null)
const lrcType = JSON.parse(siteConfig('MUSIC_PLAYER_LRC_TYPE'))
const playerVisible = JSON.parse(siteConfig('MUSIC_PLAYER_VISIBLE'))
const autoPlay = JSON.parse(siteConfig('MUSIC_PLAYER_AUTO_PLAY'))
const meting = JSON.parse(siteConfig('MUSIC_PLAYER_METING'))
const order = siteConfig('MUSIC_PLAYER_ORDER')
const audio = siteConfig('MUSIC_PLAYER_AUDIO_LIST')
const lrcType = JSON.parse(BLOG.MUSIC_PLAYER_LRC_TYPE)
const playerVisible = JSON.parse(BLOG.MUSIC_PLAYER_VISIBLE)
const autoPlay = JSON.parse(BLOG.MUSIC_PLAYER_AUTO_PLAY)
const musicPlayerEnable = siteConfig('MUSIC_PLAYER')
const musicPlayerCDN = siteConfig('MUSIC_PLAYER_CDN_URL')
const musicMetingEnable = siteConfig('MUSIC_PLAYER_METING')
const musicMetingCDNUrl = siteConfig('MUSIC_PLAYER_METING_CDN_URL', 'https://cdnjs.cloudflare.com/ajax/libs/meting/2.0.1/Meting.min.js')
const meting = JSON.parse(BLOG.MUSIC_PLAYER_METING)
const initMusicPlayer = async () => {
if (!musicPlayerEnable) {
return
}
try {
await loadExternalResource(musicPlayerCDN, 'js')
} catch (error) {
console.error('音乐组件异常', error)
}
if (musicMetingEnable) {
await loadExternalResource(musicMetingCDNUrl, 'js')
}
useEffect(() => {
if (!meting && window.APlayer) {
setPlayer(new window.APlayer({
container: ref.current,
fixed: true,
lrcType: lrcType,
autoplay: autoPlay,
order: BLOG.MUSIC_PLAYER_ORDER,
audio: BLOG.MUSIC_PLAYER_AUDIO_LIST
order: order,
audio: audio
}))
}
}
useEffect(() => {
initMusicPlayer()
return () => {
setPlayer(undefined)
}
@@ -39,11 +66,11 @@ const Player = () => {
fixed="true"
type="playlist"
preload="auto"
lrc-type={BLOG.MUSIC_PLAYER_METING_LRC_TYPE}
lrc-type={siteConfig('MUSIC_PLAYER_METING_LRC_TYPE')}
autoplay={autoPlay}
order={BLOG.MUSIC_PLAYER_ORDER}
server={BLOG.MUSIC_PLAYER_METING_SERVER}
id={BLOG.MUSIC_PLAYER_METING_ID}
order={siteConfig('MUSIC_PLAYER_ORDER')}
server={siteConfig('MUSIC_PLAYER_METING_SERVER')}
id={siteConfig('MUSIC_PLAYER_METING_ID')}
/>
: <div ref={ref} data-player={player} />
}

View File

@@ -10,10 +10,10 @@ import 'prismjs/plugins/line-numbers/prism-line-numbers'
import 'prismjs/plugins/line-numbers/prism-line-numbers.css'
// mermaid图
import BLOG from '@/blog.config'
import { loadExternalResource } from '@/lib/utils'
import { useRouter } from 'next/navigation'
import { useGlobal } from '@/lib/global'
import { siteConfig } from '@/lib/config'
/**
* 代码美化相关
@@ -23,22 +23,36 @@ import { useGlobal } from '@/lib/global'
const PrismMac = () => {
const router = useRouter()
const { isDarkMode } = useGlobal()
const codeMacBar = siteConfig('CODE_MAC_BAR')
const prismjsAutoLoader = siteConfig('PRISM_JS_AUTO_LOADER')
const prismjsPath = siteConfig('PRISM_JS_PATH')
const prismThemeSwitch = siteConfig('PRISM_THEME_SWITCH')
const prismThemeDarkPath = siteConfig('PRISM_THEME_DARK_PATH')
const prismThemeLightPath = siteConfig('PRISM_THEME_LIGHT_PATH')
const prismThemePrefixPath = siteConfig('PRISM_THEME_PREFIX_PATH')
const mermaidCDN = siteConfig('MERMAID_CDN')
const codeLineNumbers = siteConfig('CODE_LINE_NUMBERS')
const codeCollapse = siteConfig('CODE_COLLAPSE')
const codeCollapseExpandDefault = siteConfig('CODE_COLLAPSE_EXPAND_DEFAULT')
useEffect(() => {
if (JSON.parse(BLOG.CODE_MAC_BAR)) {
if (codeMacBar) {
loadExternalResource('/css/prism-mac-style.css', 'css')
}
// 加载prism样式
loadPrismThemeCSS(isDarkMode)
loadPrismThemeCSS(isDarkMode, prismThemeSwitch, prismThemeDarkPath, prismThemeLightPath, prismThemePrefixPath)
// 折叠代码
loadExternalResource(BLOG.PRISM_JS_AUTO_LOADER, 'js').then((url) => {
loadExternalResource(prismjsAutoLoader, 'js').then((url) => {
if (window?.Prism?.plugins?.autoloader) {
window.Prism.plugins.autoloader.languages_path = BLOG.PRISM_JS_PATH
window.Prism.plugins.autoloader.languages_path = prismjsPath
}
renderPrismMac()
renderMermaid()
renderCollapseCode()
renderPrismMac(codeLineNumbers)
renderMermaid(mermaidCDN)
renderCollapseCode(codeCollapse, codeCollapseExpandDefault)
})
}, [router, isDarkMode])
@@ -46,18 +60,18 @@ const PrismMac = () => {
}
/**
* 加载样式
* 加载Prism主题样式
*/
const loadPrismThemeCSS = (isDarkMode) => {
const loadPrismThemeCSS = (isDarkMode, prismThemeSwitch, prismThemeDarkPath, prismThemeLightPath, prismThemePrefixPath) => {
let PRISM_THEME
let PRISM_PREVIOUS
if (JSON.parse(BLOG.PRISM_THEME_SWITCH)) {
if (prismThemeSwitch) {
if (isDarkMode) {
PRISM_THEME = BLOG.PRISM_THEME_DARK_PATH
PRISM_PREVIOUS = BLOG.PRISM_THEME_LIGHT_PATH
PRISM_THEME = prismThemeDarkPath
PRISM_PREVIOUS = prismThemeLightPath
} else {
PRISM_THEME = BLOG.PRISM_THEME_LIGHT_PATH
PRISM_PREVIOUS = BLOG.PRISM_THEME_DARK_PATH
PRISM_THEME = prismThemeLightPath
PRISM_PREVIOUS = prismThemeDarkPath
}
const previousTheme = document.querySelector(`link[href="${PRISM_PREVIOUS}"]`)
if (previousTheme) {
@@ -65,15 +79,15 @@ const loadPrismThemeCSS = (isDarkMode) => {
}
loadExternalResource(PRISM_THEME, 'css')
} else {
loadExternalResource(BLOG.PRISM_THEME_PREFIX_PATH, 'css')
loadExternalResource(prismThemePrefixPath, 'css')
}
}
/*
* 将代码块转为可折叠对象
*/
const renderCollapseCode = () => {
if (!JSON.parse(BLOG.CODE_COLLAPSE)) {
const renderCollapseCode = (codeCollapse, codeCollapseExpandDefault) => {
if (!codeCollapse) {
return
}
const codeBlocks = document.querySelectorAll('.code-toolbar')
@@ -116,7 +130,7 @@ const renderCollapseCode = () => {
// 点击后折叠展开代码
header.addEventListener('click', collapseCode)
// 是否自动展开
if (JSON.parse(BLOG.CODE_COLLAPSE_EXPAND_DEFAULT)) {
if (codeCollapseExpandDefault) {
header.click()
}
}
@@ -125,7 +139,7 @@ const renderCollapseCode = () => {
/**
* 将mermaid语言 渲染成图片
*/
const renderMermaid = async() => {
const renderMermaid = async(mermaidCDN) => {
const observer = new MutationObserver(async mutationsList => {
for (const m of mutationsList) {
if (m.target.className === 'notion-code language-mermaid') {
@@ -146,7 +160,7 @@ const renderMermaid = async() => {
}
}
if (needLoad) {
loadExternalResource(BLOG.MERMAID_CDN, 'js').then(url => {
loadExternalResource(mermaidCDN, 'js').then(url => {
setTimeout(() => {
const mermaid = window.mermaid
mermaid?.contentLoaded()
@@ -162,11 +176,11 @@ const renderMermaid = async() => {
}
}
function renderPrismMac() {
function renderPrismMac(codeLineNumbers) {
const container = document?.getElementById('notion-article')
// Add line numbers
if (JSON.parse(BLOG.CODE_LINE_NUMBERS)) {
if (codeLineNumbers) {
const codeBlocks = container?.getElementsByTagName('pre')
if (codeBlocks) {
Array.from(codeBlocks).forEach(item => {
@@ -200,7 +214,7 @@ function renderPrismMac() {
}
// 折叠代码行号bug
if (JSON.parse(BLOG.CODE_LINE_NUMBERS)) {
if (codeLineNumbers) {
fixCodeLineStyle()
}
}

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
@@ -6,12 +6,14 @@ import { useEffect } from 'react'
* 二维码生成
*/
export default function QrCode({ value }) {
const qrCodeCDN = siteConfig('QR_CODE_CDN')
useEffect(() => {
let qrcode
if (!value) {
return
}
loadExternalResource(BLOG.QR_CODE_CDN, 'js').then(url => {
loadExternalResource(qrCodeCDN, 'js').then(url => {
const QRCode = window.QRCode
qrcode = new QRCode(document.getElementById('qrcode'), {
text: value,

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { useRouter } from 'next/router'
import React from 'react'
import ShareButtons from './ShareButtons'
@@ -6,18 +6,18 @@ import ShareButtons from './ShareButtons'
const ShareBar = ({ post }) => {
const router = useRouter()
if (!JSON.parse(BLOG.POST_SHARE_BAR_ENABLE) || !post || post?.type !== 'Post') {
if (!JSON.parse(siteConfig('POST_SHARE_BAR_ENABLE')) || !post || post?.type !== 'Post') {
return <></>
}
const shareUrl = BLOG.LINK + router.asPath
const shareUrl = siteConfig('LINK') + router.asPath
return <div className='m-1 overflow-x-auto'>
<div className='flex w-full md:justify-end'>
<ShareButtons shareUrl={shareUrl} title={post.title} image={post.pageCover} body={
post?.title +
' | ' +
BLOG.TITLE +
siteConfig('TITLE') +
' ' +
shareUrl +
' ' +

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import copy from 'copy-to-clipboard'
import dynamic from 'next/dynamic'
@@ -57,8 +57,8 @@ const QrCode = dynamic(() => import('@/components/QrCode'), { ssr: false })
* @returns
*/
const ShareButtons = ({ shareUrl, title, body, image }) => {
const services = BLOG.POSTS_SHARE_SERVICES.split(',')
const titleWithSiteInfo = title + ' | ' + BLOG.TITLE
const services = siteConfig('POSTS_SHARE_SERVICES').split(',')
const titleWithSiteInfo = title + ' | ' + siteConfig('TITLE')
const { locale } = useGlobal()
const [qrCodeShow, setQrCodeShow] = useState(false)
@@ -93,7 +93,7 @@ const ShareButtons = ({ shareUrl, title, body, image }) => {
<FacebookMessengerShareButton
key={singleService}
url={shareUrl}
appId={BLOG.FACEBOOK_APP_ID}
appId={siteConfig('FACEBOOK_APP_ID')}
className="mx-1"
>
<FacebookMessengerIcon size={32} round />

View File

@@ -1,21 +1,25 @@
import { useGlobal } from '@/lib/global'
import React, { useState } from 'react'
import { useState } from 'react'
import { Draggable } from './Draggable'
import { THEMES } from '@/themes/theme'
import { useRouter } from 'next/router'
import DarkModeButton from './DarkModeButton'
import { getQueryParam } from '@/lib/utils'
import LANGS from '@/lib/lang'
/**
*
* @returns 主题切换
*/
const ThemeSwitch = () => {
const { theme } = useGlobal()
const { theme, lang, changeLang, locale, isDarkMode, toggleDarkMode } = useGlobal()
const router = useRouter()
const currentTheme = getQueryParam(router.asPath, 'theme') || theme
// const currentLang = getQueryParam(router.asPath, 'lang') || lang
const [isLoading, setIsLoading] = useState(false)
// 修改当前路径url中的 theme 参数
// 例如 http://localhost?theme=hexo 跳转到 http://localhost?theme=newTheme
const onSelectChange = (e) => {
const onThemeSelectChange = (e) => {
setIsLoading(true)
const newTheme = e.target.value
const query = router.query
@@ -25,27 +29,52 @@ const ThemeSwitch = () => {
})
}
const onLangSelectChange = (e) => {
const newLang = e.target.value
changeLang(newLang)
}
return (<>
<Draggable>
<div id="draggableBox" style={{ left: '10px', top: '80vh' }} className="fixed z-50 dark:text-white bg-gray-50 dark:bg-black rounded-2xl drop-shadow-lg">
<div className="p-3 w-full flex items-center text-sm group duration-200 transition-all">
<DarkModeButton className='mr-2' />
<div className='w-0 group-hover:w-20 transition-all duration-200 overflow-hidden'>
<select value={theme} onChange={onSelectChange} name="themes" className='appearance-none outline-none dark:text-white bg-gray-50 dark:bg-black uppercase cursor-pointer'>
<div id="draggableBox" style={{ left: '0px', top: '80vh' }} className="fixed group space-y-2 overflow-hidden z-50 p-3 flex flex-col items-start dark:text-white bg-gray-50 dark:bg-black rounded-xl shadow-lg border dark:border-gray-800">
{/* 深色按钮 */}
<div className="text-sm flex items-center w-0 group-hover:w-32 transition-all duration-200">
<DarkModeButton />
<div onClick={toggleDarkMode} className='cursor-pointer w-0 group-hover:w-24 transition-all duration-200 overflow-hidden whitespace-nowrap pl-1 h-auto'>{isDarkMode ? locale.MENU.DARK_MODE : locale.MENU.LIGHT_MODE}</div>
</div>
{/* 翻译按钮 */}
<div className="text-sm flex items-center group-hover:w-32 transition-all duration-200">
<i className="fa-solid fa-language w-5" />
<div className='w-0 group-hover:w-24 transition-all duration-200 overflow-hidden'>
<select value={lang} onChange={onLangSelectChange} name="themes" className='pl-1 bg-gray-50 dark:bg-black appearance-none outline-none dark:text-white uppercase cursor-pointer'>
{Object.keys(LANGS)?.map(t => {
return <option key={t} value={t}>{LANGS[t].LOCALE}</option>
})}
</select>
</div>
</div>
{/* 主题切换按钮 */}
<div className="text-sm flex items-center group-hover:w-32 transition-all duration-200">
<i className="fa-solid fa-palette w-5" />
<div className='w-0 group-hover:w-24 transition-all duration-200 overflow-hidden'>
<select value={currentTheme} onChange={onThemeSelectChange} name="themes" className='pl-1 bg-gray-50 dark:bg-black appearance-none outline-none dark:text-white uppercase cursor-pointer'>
{THEMES?.map(t => {
return <option key={t} value={t}>{t}</option>
})}
</select>
</div>
<i className="fa-solid fa-palette pl-2"></i>
</div>
</div>
{/* 切换主题加载时的全屏遮罩 */}
<div className={`${isLoading ? 'opacity-50 ' : 'opacity-0'} w-screen h-screen bg-black text-white shadow-text flex justify-center items-center
transition-all fixed top-0 left-0 pointer-events-none duration-1000 z-50 shadow-inner`}>
<i className='text-3xl mr-5 fas fa-spinner animate-spin' />
</div>
</Draggable>
{/* 切换主题加载时的全屏遮罩 */}
<div className={`${isLoading ? 'opacity-50 ' : 'opacity-0'}
w-screen h-screen bg-black text-white shadow-text flex justify-center items-center
transition-all fixed top-0 left-0 pointer-events-none duration-1000 z-50 shadow-inner`}>
<i className='text-3xl mr-5 fas fa-spinner animate-spin' />
</div>
</>
)
}

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
// import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
@@ -10,11 +10,15 @@ import { useEffect } from 'react'
*/
const Twikoo = ({ isDarkMode }) => {
const envId = siteConfig('COMMENT_TWIKOO_ENV_ID')
const el = siteConfig('COMMENT_TWIKOO_ELEMENT_ID', '#twikoo')
const lang = siteConfig('LANG')
useEffect(() => {
window?.twikoo?.init({
envId: BLOG.COMMENT_TWIKOO_ENV_ID, // 腾讯云环境填 envIdVercel 环境填地址https://xxx.vercel.app
el: '#twikoo', // 容器元素
lang: BLOG.LANG // 用于手动设定评论区语言,支持的语言列表 https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js
envId: envId, // 腾讯云环境填 envIdVercel 环境填地址https://xxx.vercel.app
el: el, // 容器元素
lang: lang // 用于手动设定评论区语言,支持的语言列表 https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js
// region: 'ap-guangzhou', // 环境地域,默认为 ap-shanghai腾讯云环境填 ap-shanghai 或 ap-guangzhouVercel 环境不填
// path: location.pathname, // 用于区分不同文章的自定义 js 路径,如果您的文章路径不是 location.pathname需传此参数
})

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
// import twikoo from 'twikoo'
/**
@@ -8,7 +8,7 @@ import BLOG from '@/blog.config'
*/
const TwikooCommentCount = ({ post, className }) => {
if (!JSON.parse(BLOG.COMMENT_TWIKOO_COUNT_ENABLE)) {
if (!JSON.parse(siteConfig('COMMENT_TWIKOO_COUNT_ENABLE'))) {
return null
}
return <a href={`${post.slug}?target=comment`} className={`mx-1 hidden comment-count-wrapper-${post.id} ${className || ''}`}>

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { loadExternalResource } from '@/lib/utils'
import { useRouter } from 'next/router'
@@ -13,16 +13,37 @@ import { useEffect } from 'react'
const TwikooCommentCounter = (props) => {
let commentsData = []
const { theme } = useGlobal()
const router = useRouter()
useEffect(() => {
// console.log('路由触发评论计数')
if (props?.posts && props?.posts?.length > 0) {
fetchTwikooData(props.posts)
}
}, [router.events])
// 监控主题变化时的的评论数
useEffect(() => {
// console.log('主题触发评论计数', commentsData)
updateCommentCount()
}, [theme])
const twikooCDNURL = siteConfig('COMMENT_TWIKOO_CDN_URL')
const twikooENVID = siteConfig('COMMENT_TWIKOO_ENV_ID')
/**
* 加载外部twikoojs
* @param {*} posts
*/
const fetchTwikooData = async (posts) => {
posts.forEach(post => {
post.slug = post.slug.startsWith('/') ? post.slug : `/${post.slug}`
})
try {
await loadExternalResource(BLOG.COMMENT_TWIKOO_CDN_URL, 'js')
await loadExternalResource(twikooCDNURL, 'js')
const twikoo = window.twikoo
twikoo.getCommentsCount({
envId: BLOG.COMMENT_TWIKOO_ENV_ID, // 环境 ID
envId: twikooENVID, // 环境 ID
// region: 'ap-guangzhou', // 环境地域,默认为 ap-shanghai如果您的环境地域不是上海需传此参数
urls: posts?.map(post => post.slug), // 不包含协议、域名、参数的文章路径列表,必传参数
includeReply: true // 评论数是否包括回复默认false
@@ -58,20 +79,7 @@ const TwikooCommentCounter = (props) => {
}
})
}
const router = useRouter()
useEffect(() => {
// console.log('路由触发评论计数')
if (props?.posts && props?.posts?.length > 0) {
fetchTwikooData(props.posts)
}
}, [router.events])
// 监控主题变化时的的评论数
useEffect(() => {
// console.log('主题触发评论计数', commentsData)
updateCommentCount()
}, [theme])
return null
}

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { useEffect } from 'react'
/**
@@ -11,9 +11,9 @@ import { useEffect } from 'react'
const Utterances = ({ issueTerm, layout }) => {
useEffect(() => {
const theme =
BLOG.APPEARANCE === 'auto'
siteConfig('APPEARANCE') === 'auto'
? 'preferred-color-scheme'
: BLOG.APPEARANCE === 'light'
: siteConfig('APPEARANCE') === 'light'
? 'github-light'
: 'github-dark'
const script = document.createElement('script')
@@ -21,7 +21,7 @@ const Utterances = ({ issueTerm, layout }) => {
script.setAttribute('src', 'https://utteranc.es/client.js')
script.setAttribute('crossorigin', 'anonymous')
script.setAttribute('async', true)
script.setAttribute('repo', BLOG.COMMENT_UTTERRANCES_REPO)
script.setAttribute('repo', siteConfig('COMMENT_UTTERRANCES_REPO'))
script.setAttribute('issue-term', 'title')
script.setAttribute('theme', theme)
anchor.appendChild(script)

View File

@@ -1,23 +1,23 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
const ValineComponent = ({ path }) => {
const loadValine = async () => {
try {
await loadExternalResource(BLOG.COMMENT_VALINE_CDN, 'js')
await loadExternalResource(siteConfig('COMMENT_VALINE_CDN'), 'js')
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,
lang: siteConfig('LANG'), // 用于手动设定评论区语言,支持的语言列表 https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js
appId: siteConfig('COMMENT_VALINE_APP_ID'),
appKey: siteConfig('COMMENT_VALINE_APP_KEY'),
avatar: '',
path,
recordIP: true,
placeholder: BLOG.COMMENT_VALINE_PLACEHOLDER,
serverURLs: BLOG.COMMENT_VALINE_SERVER_URLS,
placeholder: siteConfig('COMMENT_VALINE_PLACEHOLDER'),
serverURLs: siteConfig('COMMENT_VALINE_SERVER_URLS'),
visitor: true
})
} catch (error) {

View File

@@ -1,5 +1,4 @@
import React from 'react'
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
/**
* 万维广告插件
@@ -8,11 +7,13 @@ import BLOG from '@/blog.config'
* @returns {JSX.Element | null} - 返回渲染的 JSX 元素或 null
*/
export default function WWAds({ orientation = 'vertical', sticky = false, className }) {
if (!JSON.parse(BLOG.AD_WWADS_ID)) {
const adWWADSId = siteConfig('AD_WWADS_ID')
if (!adWWADSId) {
return null
}
return (
<div className={`wwads-cn ${orientation === 'vertical' ? 'wwads-vertical' : 'wwads-horizontal'} ${sticky ? 'wwads-sticky' : ''} z-10 ${className || ''}`} data-id={BLOG.AD_WWADS_ID}></div>
)
return <div data-id={adWWADSId} className={`wwads-cn
${orientation === 'vertical' ? 'wwads-vertical' : 'wwads-horizontal'}
${sticky ? 'wwads-sticky' : ''} z-10 ${className || ''}`} />
}

View File

@@ -1,8 +1,8 @@
import React from 'react'
import React, { createRef } from 'react'
import { init } from '@waline/client'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import '@waline/client/dist/waline.css'
import { siteConfig } from '@/lib/config'
const path = ''
let waline = null
@@ -12,7 +12,7 @@ let waline = null
* @returns
*/
const WalineComponent = (props) => {
const containerRef = React.createRef()
const containerRef = createRef()
const router = useRouter()
const updateWaline = url => {
@@ -26,8 +26,8 @@ const WalineComponent = (props) => {
waline = init({
...props,
el: containerRef.current,
serverURL: BLOG.COMMENT_WALINE_SERVER_URL,
lang: BLOG.lang,
serverURL: siteConfig('COMMENT_WALINE_SERVER_URL'),
lang: siteConfig('LANG'),
reaction: true,
dark: 'html.dark',
emoji: [

View File

@@ -1,7 +1,7 @@
import BLOG from '@/blog.config'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import Image from 'next/image'
import { siteConfig } from '@/lib/config'
/**
* 评论插件
@@ -78,7 +78,7 @@ const WebmentionReplies = ({ target }) => {
const [mentions, setMentions] = useState([])
const fetchMentions = async (target) =>
fetch(
`https://webmention.io/api/mentions.jf2?per-page=500&target=${encodeURIComponent(target)}&token=${BLOG.COMMENT_WEBMENTION.TOKEN}`
`https://webmention.io/api/mentions.jf2?per-page=500&target=${encodeURIComponent(target)}&token=${siteConfig('COMMENT_WEBMENTION_TOKEN')}`
).then((response) => (response.json ? response.json() : response))
useEffect(() => {
async function getMentions() {
@@ -137,8 +137,8 @@ const WebmentionReplies = ({ target }) => {
const WebMentionBlock = ({ frontMatter }) => {
const router = useRouter()
const url = `https://${BLOG.COMMENT_WEBMENTION.HOSTNAME}${router.asPath}`
const tweet = `${frontMatter.title} by @${BLOG.COMMENT_WEBMENTION.TWITTER_USERNAME} ${url}`
const url = `https://${siteConfig('COMMENT_WEBMENTION_HOSTNAME')}${router.asPath}`
const tweet = `${frontMatter.title} by @${siteConfig('COMMENT_WEBMENTION_TWITTER_USERNAME')} ${url}`
return (
<div className='webmention-block'>

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import ExternalScript from './ExternalScript'
/**
@@ -10,8 +10,8 @@ export default function WebWhiz() {
const props = {
id: '__webwhizSdk__',
src: 'https://www.unpkg.com/webwhiz@1.0.0/dist/sdk.js',
baseUrl: BLOG.WEB_WHIZ_BASE_URL,
chatbotId: BLOG.WEB_WHIZ_CHAT_BOT_ID
baseUrl: siteConfig('WEB_WHIZ_BASE_URL'),
chatbotId: siteConfig('WEB_WHIZ_CHAT_BOT_ID')
}
return <ExternalScript {...props}/>
}

View File

@@ -3,15 +3,6 @@ import FileCache from './local_file_cache'
import MongoCache from './mongo_db_cache'
import BLOG from '@/blog.config'
let api
if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) {
api = MongoCache
} else if (process.env.ENABLE_FILE_CACHE) {
api = FileCache
} else {
api = MemoryCache
}
/**
* 为减少频繁接口请求notion数据将被缓存
* @param {*} key
@@ -19,11 +10,11 @@ if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) {
*/
export async function getDataFromCache(key, force) {
if (BLOG.ENABLE_CACHE || force) {
const dataFromCache = await api.getCache(key)
const dataFromCache = await getApi().getCache(key)
if (JSON.stringify(dataFromCache) === '[]') {
return null
}
return api.getCache(key)
return getApi().getCache(key)
} else {
return null
}
@@ -33,12 +24,26 @@ export async function setDataToCache(key, data) {
if (!data) {
return
}
await api.setCache(key, data)
await getApi().setCache(key, data)
}
export async function delCacheData(key) {
if (!BLOG.ENABLE_CACHE) {
return
}
await api.delCache(key)
await getApi().delCache(key)
}
/**
* 缓存实现类
* @returns
*/
function getApi() {
if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) {
return MongoCache
} else if (process.env.ENABLE_FILE_CACHE) {
return FileCache
} else {
return MemoryCache
}
}

View File

@@ -41,7 +41,7 @@ export async function setCache (key, data) {
fs.writeFileSync(jsonFile, JSON.stringify(json))
}
export async function delCache (key, data) {
export async function delCache (key) {
const exist = await fs.existsSync(jsonFile)
const json = exist ? JSON.parse(await fs.readFileSync(jsonFile)) : {}
delete json.key
@@ -49,4 +49,12 @@ export async function delCache (key, data) {
fs.writeFileSync(jsonFile, JSON.stringify(json))
}
/**
* 清理缓存
*/
export async function cleanCache() {
const json = {}
fs.writeFileSync(jsonFile, JSON.stringify(json))
}
export default { getCache, setCache, delCache }

93
lib/config.js Normal file
View File

@@ -0,0 +1,93 @@
'use client'
import BLOG from '@/blog.config'
import { useGlobal } from './global'
import { deepClone } from './utils'
/**
* 读取配置顺序
* 1. 优先读取NotionConfig表
* 2. 其次读取环境变量
* 3. 再读取blog.config.js / 或各个主题的CONFIG文件
* @param {*} key 参数名
* @param {*} defaultVal ; 参数不存在默认返回值
* @param {*} extendConfig ; 参考配置对象{key:val}如果notion中找不到优先尝试在这里面查找
* @returns
*/
export const siteConfig = (key, defaultVal = null, extendConfig = null) => {
let global = null
try {
// eslint-disable-next-line react-hooks/rules-of-hooks
global = useGlobal()
} catch (error) {}
// 首先 配置最优先读取NOTION中的表格配置
let val = null
let siteInfo = null
if (global) {
val = global.NOTION_CONFIG?.[key]
siteInfo = global.siteInfo
// console.log('当前变量', key, val)
}
if (!val) {
// 这里针对部分key做一些兼容处理
switch (key) {
case 'HOME_BANNER_IMAGE':
val = siteInfo?.pageCover // 封面图取Notion的封面
break
case 'AVATAR':
val = siteInfo?.icon // 封面图取Notion的头像
break
case 'TITLE':
val = siteInfo?.title // 标题取Notion中的标题
break
case 'DESCRIPTION':
val = siteInfo?.description // 标题取Notion中的标题
break
}
}
// 其次 有传入的配置参考,则尝试读取
if (!val && extendConfig) {
val = extendConfig[key]
}
// 其次 NOTION没有找到配置则会读取blog.config.js文件
if (!val) {
val = BLOG[key]
}
if (!val) {
return defaultVal
} else {
if (typeof val === 'string') {
return val;
} else {
try {
return JSON.parse(val);
} catch (error) {
// 如果值是一个字符串但不是有效的 JSON 格式,直接返回字符串
return val;
}
}
}
}
/**
* 读取所有配置
* 1. 优先读取NotionConfig表
* 2. 其次读取环境变量
* 3. 再读取blog.config.js文件
* @param {*} key
* @returns
*/
export const siteConfigMap = () => {
const val = deepClone(BLOG)
for (const key in val) {
val[key] = siteConfig(key)
// console.log('site', key, val[key], siteConfig(key))
}
return val
}

View File

@@ -1,34 +1,65 @@
import { generateLocaleDict, initLocale } from './lang'
import { createContext, useContext, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { THEMES, initDarkMode, saveDarkModeToCookies } from '@/themes/theme'
import BLOG from '@/blog.config'
import { THEMES, initDarkMode } from '@/themes/theme'
import NProgress from 'nprogress'
import { getQueryVariable, isBrowser } from './utils'
const GlobalContext = createContext()
/**
* 全局变量Provider包括语言本地化、样式主题、搜索词
* 定义全局变量,包括语言、主题、深色模式、加载状态
* @param children
* @returns {JSX.Element}
* @constructor
*/
export function GlobalContextProvider(props) {
const { children, siteInfo, categoryOptions, tagOptions } = props
const { children, siteInfo, categoryOptions, tagOptions, NOTION_CONFIG } = props
const router = useRouter()
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 [lang, updateLang] = useState(NOTION_CONFIG?.LANG || BLOG.LANG) // 默认语言
const [locale, updateLocale] = useState(generateLocaleDict(NOTION_CONFIG?.LANG || BLOG.LANG)) // 默认语言
const [theme, setTheme] = useState(NOTION_CONFIG?.THEME || BLOG.THEME) // 默认博客主题
const [isDarkMode, updateDarkMode] = useState(NOTION_CONFIG?.APPEARANCE || BLOG.APPEARANCE === 'dark') // 默认深色模式
const [onLoading, setOnLoading] = useState(false) // 抓取文章数据
// 切换主题
function switchTheme() {
const currentIndex = THEMES.indexOf(theme)
const newIndex = currentIndex < THEMES.length - 1 ? currentIndex + 1 : 0
const newTheme = THEMES[newIndex]
const query = router.query
query.theme = newTheme
router.push({ pathname: router.pathname, query })
return newTheme
}
// 切换深色模式
const toggleDarkMode = () => {
const newStatus = !isDarkMode
saveDarkModeToCookies(newStatus)
updateDarkMode(newStatus)
const htmlElement = document.getElementsByTagName('html')[0]
htmlElement.classList?.remove(newStatus ? 'light' : 'dark')
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
}
/**
* 更新语言
*/
function changeLang(lang) {
if (lang) {
updateLang(lang)
updateLocale(generateLocaleDict(lang))
}
}
useEffect(() => {
initLocale(lang, locale, updateLang, updateLocale)
initDarkMode(updateDarkMode)
initTheme()
initLocale(lang, locale, updateLang, updateLocale)
}, [])
// 加载进度条
useEffect(() => {
const handleStart = (url) => {
NProgress.start()
@@ -43,8 +74,7 @@ export function GlobalContextProvider(props) {
NProgress.done()
setOnLoading(false)
}
const queryTheme = getQueryVariable('theme') || BLOG.THEME
setTheme(queryTheme)
router.events.on('routeChangeStart', handleStart)
router.events.on('routeChangeError', handleStop)
router.events.on('routeChangeComplete', handleStop)
@@ -55,21 +85,14 @@ export function GlobalContextProvider(props) {
}
}, [router])
// 切换主题
function switchTheme() {
const currentIndex = THEMES.indexOf(theme)
const newIndex = currentIndex < THEMES.length - 1 ? currentIndex + 1 : 0
const newTheme = THEMES[newIndex]
const query = router.query
query.theme = newTheme
router.push({ pathname: router.pathname, query })
return newTheme
}
return (
<GlobalContext.Provider value={{
NOTION_CONFIG,
toggleDarkMode,
onLoading,
setOnLoading,
lang,
changeLang,
locale,
updateLocale,
isDarkMode,
@@ -86,23 +109,4 @@ export function GlobalContextProvider(props) {
)
}
/**
* 切换主题时的特殊处理
* @param {*} setTheme
*/
const initTheme = () => {
if (isBrowser) {
setTimeout(() => {
const elements = document.querySelectorAll('[id^="theme-"]')
if (elements?.length > 1) {
elements[elements.length - 1].scrollIntoView()
// 删除前面的元素,只保留最后一个元素
for (let i = 0; i < elements.length - 1; i++) {
elements[i].parentNode.removeChild(elements[i])
}
}
}, 500)
}
}
export const useGlobal = () => useContext(GlobalContext)

View File

@@ -12,7 +12,7 @@ import { getQueryVariable, isBrowser, mergeDeep } from './utils'
* 在这里配置所有支持的语言
* 国家-地区
*/
const lang = {
const LANGS = {
'en-US': enUS,
'zh-CN': zhCN,
'zh-HK': zhHK,
@@ -22,7 +22,7 @@ const lang = {
'ja-JP': jaJP
}
export default lang
export default LANGS
/**
* 获取当前语言字典
@@ -30,33 +30,33 @@ export default lang
* @returns 不同语言对应字典
*/
export function generateLocaleDict(langString) {
const supportedLocales = Object.keys(lang)
const supportedLocales = Object.keys(LANGS)
let userLocale
// 将语言字符串拆分为语言和地区代码,例如将 "zh-CN" 拆分为 "zh" 和 "CN"
const [language, region] = langString.split(/[-_]/)
const [language, region] = langString?.split(/[-_]/)
// 优先匹配语言和地区都匹配的情况
const specificLocale = `${language}-${region}`
if (supportedLocales.includes(specificLocale)) {
userLocale = lang[specificLocale]
userLocale = LANGS[specificLocale]
}
// 然后尝试匹配只有语言匹配的情况
if (!userLocale) {
const languageOnlyLocales = supportedLocales.filter(locale => locale.startsWith(language))
if (languageOnlyLocales.length > 0) {
userLocale = lang[languageOnlyLocales[0]]
userLocale = LANGS[languageOnlyLocales[0]]
}
}
// 如果还没匹配到,则返回最接近的语言包
if (!userLocale) {
const fallbackLocale = supportedLocales.find(locale => locale.startsWith('en'))
userLocale = lang[fallbackLocale]
userLocale = LANGS[fallbackLocale]
}
return mergeDeep({}, lang['en-US'], userLocale)
return mergeDeep({}, LANGS['en-US'], userLocale)
}
/**
@@ -65,11 +65,12 @@ export function generateLocaleDict(langString) {
*/
export function initLocale(lang, locale, changeLang, changeLocale) {
if (isBrowser) {
const queryLang = getQueryVariable('lang') || loadLangFromCookies() || window.navigator.language
const queryLang = getQueryVariable('lang') || loadLangFromCookies()
let currentLang = lang
if (queryLang !== lang) {
if (queryLang && queryLang !== 'undefined' && queryLang !== lang) {
currentLang = queryLang
}
changeLang(currentLang)
saveLangToCookies(currentLang)

View File

@@ -1,5 +1,5 @@
export default {
LOCALE: 'en-US',
LOCALE: 'English',
MENU: {
WALK_AROUND: 'Walk Around',
CATEGORY: 'Category',

View File

@@ -1,5 +1,5 @@
export default {
LOCALE: 'fr-FR',
LOCALE: 'français',
NAV: {
INDEX: 'Accueil',
RSS: 'RSS',

View File

@@ -1,5 +1,5 @@
export default {
LOCALE: 'ja-JP',
LOCALE: '日本語',
NAV: {
INDEX: 'ホーム',
RSS: '購読',

View File

@@ -1,5 +1,5 @@
export default {
LOCALE: 'tr-TR',
LOCALE: 'Türkçe',
NAV: {
INDEX: 'Blog',
RSS: 'RSS',

View File

@@ -1,5 +1,5 @@
export default {
LOCALE: 'zh-CN',
LOCALE: '中文(简体)',
MENU: {
WALK_AROUND: '随便逛逛',
CATEGORY: '博客分类',

View File

@@ -1,4 +1,5 @@
export default {
LOCALE: '中文(繁体香港)',
NAV: {
INDEX: '網誌',
RSS: '訂閱',

View File

@@ -1,5 +1,5 @@
export default {
LOCALE: 'zh-TW',
LOCALE: '中文(繁体台湾)',
NAV: {
INDEX: '部落格',
RSS: '訂閱',

View File

@@ -0,0 +1,136 @@
/**
* 从Notion中读取站点配置;
* 在Notion模板中创建一个类型为CONFIG的页面再添加一个数据库表格即可用于填写配置
* Notion数据库配置优先级最高将覆盖vercel环境变量以及blog.config.js中的配置
* --注意--
* 数据库请从模板复制 https://www.notion.so/tanghh/287869a92e3d4d598cf366bd6994755e
*
*/
import { getDateValue, getTextContent } from 'notion-utils'
import { getPostBlocks } from './getPostBlocks'
import getAllPageIds from './getAllPageIds'
/**
* 从Notion中读取Config配置表
* @param {*} allPages
* @returns
*/
export async function getConfigMapFromConfigPage(allPages) {
// 默认返回配置文件
const notionConfig = {}
if (!allPages || !Array.isArray(allPages) || allPages.length === 0) {
console.warn('[Notion配置] 忽略的配置')
return null
}
const configPage = allPages?.find(post => {
return post && post?.type && post?.type === 'CONFIG'
})
if (!configPage) {
console.warn('[Notion配置] 未找到配置页面')
return null
}
const configPageId = configPage.id
// console.log('[Notion配置]请求配置数据 ', configPage.id)
const pageRecordMap = await getPostBlocks(configPageId, 'config-table')
// console.log('配置中心Page', configPageId, pageRecordMap)
const content = pageRecordMap.block[configPageId].value.content
if (!content) {
console.warn('[Notion配置] 未找到配置表格', pageRecordMap.block[configPageId], pageRecordMap.block[configPageId].value)
return null
}
// 找到配置文件中的database
// for (const contentId of content) {
// console.log('内容', contentId, configPageRecordMap.block[contentId].value.type === 'collection_view')
// }
const configTableId = content?.find(contentId => {
return pageRecordMap.block[contentId].value.type === 'collection_view'
})
// eslint-disable-next-line no-constant-condition, no-self-compare
if (!configTableId) {
console.warn('[Notion配置]未找到配置表格数据', pageRecordMap.block[configPageId], pageRecordMap.block[configPageId].value)
return null
}
// 页面查找
const databaseRecordMap = pageRecordMap.block[configTableId]
const block = pageRecordMap.block || {}
const rawMetadata = databaseRecordMap.value
// Check Type Page-Database和Inline-Database
if (
rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view'
) {
console.error(`pageId "${configTableId}" is not a database`)
return null
}
// console.log('表格', databaseRecordMap, block, rawMetadata)
const collectionId = rawMetadata?.collection_id
const collection = pageRecordMap.collection[collectionId].value
const collectionQuery = pageRecordMap.collection_query
const collectionView = pageRecordMap.collection_view
const schema = collection?.schema
const viewIds = rawMetadata?.view_ids
const pageIds = getAllPageIds(collectionQuery, collectionId, collectionView, viewIds)
if (pageIds?.length === 0) {
console.error('[Notion配置]获取到的文章列表为空请检查notion模板', collectionQuery, collection, collectionView, viewIds, databaseRecordMap)
}
// 遍历用户的表格
for (let i = 0; i < pageIds.length; i++) {
const id = pageIds[i]
const value = block[id]?.value
if (!value) {
continue
}
const rawProperties = Object.entries(block?.[id]?.value?.properties || [])
const excludeProperties = ['date', 'select', 'multi_select', 'person']
const properties = {}
for (let i = 0; i < rawProperties.length; i++) {
const [key, val] = rawProperties[i]
properties.id = id
if (schema[key]?.type && !excludeProperties.includes(schema[key].type)) {
properties[schema[key].name] = getTextContent(val)
} else {
switch (schema[key]?.type) {
case 'date': {
const dateProperty = getDateValue(val)
delete dateProperty.type
properties[schema[key].name] = dateProperty
break
}
case 'select':
case 'multi_select': {
const selects = getTextContent(val)
if (selects[0]?.length) {
properties[schema[key].name] = selects.split(',')
}
break
}
default:
break
}
}
}
if (properties) {
// 将表格中的字段映射成 英文
const config = {
enable: properties['启用'] === 'Yes',
key: properties['配置名'],
value: properties['配置值']
}
// 只导入生效的配置
if (config.enable) {
// console.log('[Notion配置]', config.key, config.value)
notionConfig[config.key] = config.value
}
}
}
return notionConfig
}

View File

@@ -8,6 +8,7 @@ import getAllPageIds from './getAllPageIds'
import { getAllTags } from './getAllTags'
import getPageProperties from './getPageProperties'
import { mapImgUrl, compressImage } from './mapImage'
import { getConfigMapFromConfigPage } from './getNotionConfig'
/**
* 获取博客数据
@@ -272,6 +273,7 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) {
// 文章计数
let postCount = 0
// 查找所有的Post和Page
const allPages = collectionData.filter(post => {
if (post?.type === 'Post' && post.status === 'Published') {
@@ -282,6 +284,9 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) {
(post?.status === 'Invisible' || post?.status === 'Published')
})
// 站点配置优先读取配置表格否则读取blog.config.js 文件
const NOTION_CONFIG = await getConfigMapFromConfigPage(collectionData) || {}
// Sort by date
if (BLOG.POSTS_SORT_BY === 'date') {
allPages.sort((a, b) => {
@@ -300,6 +305,7 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) {
const allNavPages = getNavPages({ allPages })
return {
NOTION_CONFIG,
notice,
siteInfo,
allPages,

View File

@@ -6,6 +6,15 @@ import formatDate from '../formatDate'
import md5 from 'js-md5'
import { mapImgUrl } from './mapImage'
/**
* 获取页面元素成员属性
* @param {*} id
* @param {*} block
* @param {*} schema
* @param {*} authToken
* @param {*} tagOptions
* @returns
*/
export default async function getPageProperties(id, block, schema, authToken, tagOptions) {
const rawProperties = Object.entries(block?.[id]?.value?.properties || [])
const excludeProperties = ['date', 'select', 'multi_select', 'person']
@@ -108,6 +117,7 @@ export default async function getPageProperties(id, block, schema, authToken, ta
properties.slug += '.html'
}
}
// 密码字段md5
properties.password = properties.password ? md5(properties.slug + properties.password) : ''
return properties
}

View File

@@ -75,7 +75,6 @@ export function getQueryVariable(key) {
}
return (false)
}
/**
* 获取 URL 中指定参数的值
* @param {string} url
@@ -83,8 +82,10 @@ export function getQueryVariable(key) {
* @returns {string|null}
*/
export function getQueryParam(url, param) {
const searchParams = new URLSearchParams(url.split('?')[1])
return searchParams.get(param)
// 移除哈希部分
const urlWithoutHash = url.split('#')[0];
const searchParams = new URLSearchParams(urlWithoutHash.split('?')[1]);
return searchParams.get(param);
}
/**
@@ -202,3 +203,46 @@ export const isMobile = () => {
return isMobile
}
/**
* 扫描页面上的所有文本节点将url格式的文本转为可点击链接
* @param {*} node
*/
export const scanAndConvertToLinks = (node) => {
if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent;
const urlRegex = /https?:\/\/[^\s]+/g;
let lastIndex = 0;
let match;
const newNode = document.createElement('span');
while ((match = urlRegex.exec(text)) !== null) {
const beforeText = text.substring(lastIndex, match.index);
const url = match[0];
if (beforeText) {
newNode.appendChild(document.createTextNode(beforeText));
}
const link = document.createElement('a');
link.href = url;
link.target = '_blank'
link.textContent = url;
newNode.appendChild(link);
lastIndex = urlRegex.lastIndex;
}
if (lastIndex < text.length) {
newNode.appendChild(document.createTextNode(text.substring(lastIndex)));
}
node.parentNode.replaceChild(newNode, node);
} else if (node.nodeType === Node.ELEMENT_NODE) {
for (const childNode of node.childNodes) {
scanAndConvertToLinks(childNode);
}
}
}

View File

@@ -1,5 +1,8 @@
const BLOG = require('./blog.config')
/**
* 通常没啥用sitemap交给 /pages/sitemap.xml.js 动态生成
*/
module.exports = {
siteUrl: BLOG.LINK,
changefreq: 'daily',

View File

@@ -91,6 +91,7 @@ module.exports = withBundleAnalyzer({
// })
// }
// 动态主题:添加 resolve.alias 配置,将动态路径映射到实际路径
console.log('加载默认主题', path.resolve(__dirname, 'themes', THEME))
config.resolve.alias['@theme-components'] = path.resolve(__dirname, 'themes', THEME)
return config
},

View File

@@ -1,6 +1,6 @@
{
"name": "notion-next",
"version": "4.0.18",
"version": "4.1.0",
"homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT",
"repository": {

View File

@@ -1,7 +1,7 @@
import { getGlobalData } from '@/lib/notion/getNotionData'
import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
/**
* 404
@@ -9,13 +9,12 @@ import { getLayoutByTheme } from '@/themes/theme'
* @returns
*/
const NoFound = props => {
const { siteInfo } = useGlobal()
const meta = { title: `${props?.siteInfo?.title} | 页面找不到啦`, image: siteInfo?.pageCover }
const meta = { title: `${siteConfig('TITLE')} | 页面找不到啦`, image: siteConfig('HOME_BANNER_IMAGE') }
props = { ...props, meta }
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
return <Layout {...props} />
}

View File

@@ -10,6 +10,7 @@ import { getLayoutByTheme } from '@/themes/theme'
import md5 from 'js-md5'
import { isBrowser } from '@/lib/utils'
import { uploadDataToAlgolia } from '@/lib/algolia'
import { siteConfig } from '@/lib/config'
/**
* 根据notion的slug访问页面
@@ -66,7 +67,7 @@ const Slug = props => {
}, [post])
const meta = {
title: post ? `${post?.title} | ${siteInfo?.title}` : `${props?.siteInfo?.title || BLOG.TITLE} | loading`,
title: post ? `${post?.title} | ${siteConfig('TITLE')}` : `${siteConfig('TITLE')} | loading`,
description: post?.summary,
type: post?.type,
slug: post?.slug,
@@ -76,7 +77,7 @@ const Slug = props => {
}
props = { ...props, lock, meta, setLock, validPassword }
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
return <Layout {...props} />
}

View File

@@ -11,12 +11,11 @@ import { GlobalContextProvider } from '@/lib/global'
import AOS from 'aos'
import 'aos/dist/aos.css' // You can also use <link> for styles
import dynamic from 'next/dynamic'
import { isBrowser, loadExternalResource } from '@/lib/utils'
import BLOG from '@/blog.config'
// 各种扩展插件 动画等
const ExternalPlugins = dynamic(() => import('@/components/ExternalPlugins'))
import ExternalPlugins from '@/components/ExternalPlugins'
// const ExternalPlugins = dynamic(() => import('@/components/ExternalPlugins'))
const MyApp = ({ Component, pageProps }) => {
// 自定义样式css和js引入

View File

@@ -1,7 +1,6 @@
// eslint-disable-next-line @next/next/no-document-import-in-page
import Document, { Html, Head, Main, NextScript } from 'next/document'
import BLOG from '@/blog.config'
import CommonScript from '@/components/CommonScript'
class MyDocument extends Document {
static async getInitialProps(ctx) {
@@ -14,7 +13,6 @@ class MyDocument extends Document {
<Html lang={BLOG.LANG}>
<Head>
<link rel='icon' href= {`${BLOG.BLOG_FAVICON}`} />
<CommonScript />
{/* 预加载字体 */}
{BLOG.FONT_AWESOME && <>
<link rel='preload' href={BLOG.FONT_AWESOME} as="style" crossOrigin="anonymous" />

15
pages/api/cache.js Normal file
View File

@@ -0,0 +1,15 @@
import { cleanCache } from '@/lib/cache/local_file_cache'
/**
* 清理缓存
* @param {*} req
* @param {*} res
*/
export default async function handler(req, res) {
try {
await cleanCache()
res.status(200).json({ status: 'success', message: 'Clean cache successful!' })
} catch (error) {
res.status(400).json({ status: 'error', message: 'Clean cache failed!', error })
}
}

View File

@@ -6,13 +6,14 @@ import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { isBrowser } from '@/lib/utils'
import { formatDateFmt } from '@/lib/formatDate'
import { siteConfig } from '@/lib/config'
const ArchiveIndex = props => {
const { siteInfo } = props
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
useEffect(() => {
if (isBrowser) {
@@ -29,8 +30,8 @@ const ArchiveIndex = props => {
}, [])
const meta = {
title: `${locale.NAV.ARCHIVE} | ${siteInfo?.title}`,
description: siteInfo?.description,
title: `${locale.NAV.ARCHIVE} | ${siteConfig('TITLE')}`,
description: siteConfig('HOME_BANNER_IMAGE'),
image: siteInfo?.pageCover,
slug: 'archive',
type: 'website'

View File

@@ -4,6 +4,7 @@ import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
/**
* 分类页
@@ -15,13 +16,13 @@ export default function Category(props) {
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${props.category} | ${locale.COMMON.CATEGORY} | ${
siteInfo?.title || ''
siteConfig('TITLE') || ''
}`,
description: siteInfo?.description,
description: siteConfig('HOME_BANNER_IMAGE'),
slug: 'category/' + props.category,
image: siteInfo?.pageCover,
type: 'website'

View File

@@ -4,6 +4,7 @@ import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
/**
* 分类页
@@ -15,13 +16,13 @@ export default function Category(props) {
const { siteInfo } = props
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${props.category} | ${locale.COMMON.CATEGORY} | ${
siteInfo?.title || ''
siteConfig('TITLE') || ''
}`,
description: siteInfo?.description,
description: siteConfig('HOME_BANNER_IMAGE'),
slug: 'category/' + props.category,
image: siteInfo?.pageCover,
type: 'website'

View File

@@ -4,6 +4,7 @@ import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
/**
* 分类首页
@@ -15,11 +16,11 @@ export default function Category(props) {
const { siteInfo } = props
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${locale.COMMON.CATEGORY} | ${siteInfo?.title}`,
description: siteInfo?.description,
title: `${locale.COMMON.CATEGORY} | ${siteConfig('TITLE')}`,
description: siteConfig('HOME_BANNER_IMAGE'),
image: siteInfo?.pageCover,
slug: 'category',
type: 'website'

View File

@@ -3,8 +3,9 @@ import { getPostBlocks } from '@/lib/notion'
import { getGlobalData } from '@/lib/notion/getNotionData'
import { generateRss } from '@/lib/rss'
import { generateRobotsTxt } from '@/lib/robots.txt'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
import { useRouter } from 'next/router'
/**
* 首页布局
@@ -13,8 +14,16 @@ import { getLayoutByTheme } from '@/themes/theme'
*/
const Index = props => {
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
return <Layout {...props} />
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${siteConfig('TITLE')} | ${siteConfig('DESCRIPTION')}`,
description: siteConfig('DESCRIPTION'),
image: siteConfig('HOME_BANNER_IMAGE'),
slug: '',
type: 'website'
}
return <Layout meta={meta} {...props} />
}
/**
@@ -25,16 +34,8 @@ export async function getStaticProps() {
const from = 'index'
const props = await getGlobalData({ from })
const { siteInfo } = props
props.posts = props.allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
const meta = {
title: `${siteInfo?.title} | ${siteInfo?.description}`,
description: siteInfo?.description,
image: siteInfo?.pageCover,
slug: '',
type: 'website'
}
// 处理分页
if (BLOG.POST_LIST_STYLE === 'scroll') {
// 滚动列表默认给前端返回所有数据
@@ -65,10 +66,7 @@ export async function getStaticProps() {
delete props.allPages
return {
props: {
meta,
...props
},
props,
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
}
}

View File

@@ -3,6 +3,7 @@ import { getPostBlocks } from '@/lib/notion'
import { getGlobalData } from '@/lib/notion/getNotionData'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
/**
* 文章列表分页
@@ -13,11 +14,11 @@ const Page = props => {
const { siteInfo } = props
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${props?.page} | Page | ${siteInfo?.title}`,
description: siteInfo?.description,
title: `${props?.page} | Page | ${siteConfig('TITLE')}`,
description: siteConfig('HOME_BANNER_IMAGE'),
image: siteInfo?.pageCover,
slug: 'page/' + props.page,
type: 'website'

View File

@@ -4,17 +4,18 @@ import { getDataFromCache } from '@/lib/cache/cache_manager'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
const Index = props => {
const { keyword, siteInfo } = props
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`,
description: siteInfo?.title,
title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`,
description: siteConfig('TITLE'),
image: siteInfo?.pageCover,
slug: 'search/' + (keyword || ''),
type: 'website'

View File

@@ -4,17 +4,18 @@ import { getDataFromCache } from '@/lib/cache/cache_manager'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
const Index = props => {
const { keyword, siteInfo } = props
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`,
description: siteInfo?.title,
title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`,
description: siteConfig('TITLE'),
image: siteInfo?.pageCover,
slug: 'search/' + (keyword || ''),
type: 'website'

View File

@@ -3,6 +3,7 @@ import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import BLOG from '@/blog.config'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
/**
* 搜索路由
@@ -14,7 +15,7 @@ const Search = props => {
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const router = useRouter()
const keyword = getSearchKey(router)
@@ -34,8 +35,8 @@ const Search = props => {
}
const meta = {
title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`,
description: siteInfo?.description,
title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`,
description: siteConfig('HOME_BANNER_IMAGE'),
image: siteInfo?.pageCover,
slug: 'search',
type: 'website'

View File

@@ -3,6 +3,7 @@ import { getGlobalData } from '@/lib/notion/getNotionData'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
/**
* 标签下的文章列表
@@ -14,11 +15,11 @@ const Tag = props => {
const { tag, siteInfo } = props
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${tag} | ${locale.COMMON.TAGS} | ${siteInfo?.title}`,
description: siteInfo?.description,
title: `${tag} | ${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`,
description: siteConfig('HOME_BANNER_IMAGE'),
image: siteInfo?.pageCover,
slug: 'tag/' + tag,
type: 'website'

View File

@@ -3,17 +3,18 @@ import { getGlobalData } from '@/lib/notion/getNotionData'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
const Tag = props => {
const { locale } = useGlobal()
const { tag, siteInfo } = props
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${tag} | ${locale.COMMON.TAGS} | ${siteInfo?.title}`,
description: siteInfo?.description,
title: `${tag} | ${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`,
description: siteConfig('HOME_BANNER_IMAGE'),
image: siteInfo?.pageCover,
slug: 'tag/' + tag,
type: 'website'

View File

@@ -3,6 +3,7 @@ import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { siteConfig } from '@/lib/config'
/**
* 标签首页
@@ -14,11 +15,11 @@ const TagIndex = props => {
const { siteInfo } = props
// 根据页面路径加载不同Layout文件
const Layout = getLayoutByTheme(useRouter())
const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${locale.COMMON.TAGS} | ${siteInfo?.title}`,
description: siteInfo?.description,
title: `${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`,
description: siteConfig('HOME_BANNER_IMAGE'),
image: siteInfo?.pageCover,
slug: 'tag',
type: 'website'

View File

@@ -724,7 +724,6 @@ svg.notion-page-icon {
.notion-text {
width: 100%;
white-space: pre-wrap;
line-height: 1.5rem !important;
word-break: break-word;
padding: 3px 2px !important;
margin: 1px 0 !important;

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import Link from 'next/link'
/**
@@ -24,7 +24,7 @@ export default function BlogListGroupByDate({ archiveTitle, archivePosts }) {
{post?.publishDay}
</span>{' '}
&nbsp;
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">
<Link href={`${siteConfig('SUB_PATH', '')}/${post.slug}`} className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">
{post.title}
</Link>
</div>

View File

@@ -1,5 +1,5 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import Link from 'next/link'
@@ -10,14 +10,14 @@ export const BlogListPage = props => {
const { page = 1, posts, postCount } = props
const { locale } = useGlobal()
const router = useRouter()
const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
const totalPage = Math.ceil(postCount / parseInt(siteConfig('POSTS_PER_PAGE')))
const currentPage = +page
const showPrev = currentPage > 1
const showNext = page < totalPage
const pagePrefix = router.asPath.split('?')[0].replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
const showPageCover = CONFIG.POST_LIST_COVER
const showPageCover = siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG)
return (
<div className={`w-full ${showPageCover ? 'md:pr-2' : 'md:pr-12'} mb-12`}>

View File

@@ -1,6 +1,6 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import React, { useEffect } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import throttle from 'lodash.throttle'
import BlogPostCard from './BlogPostCard'
import CONFIG from '../config'
@@ -9,33 +9,33 @@ export const BlogListScroll = props => {
const { posts } = props
const { locale } = useGlobal()
const [page, updatePage] = React.useState(1)
const [page, updatePage] = useState(1)
let hasMore = false
const postsToShow = posts
? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page)
? Object.assign(posts).slice(0, parseInt(siteConfig('POSTS_PER_PAGE')) * page)
: []
if (posts) {
const totalCount = posts.length
hasMore = page * BLOG.POSTS_PER_PAGE < totalCount
hasMore = page * parseInt(siteConfig('POSTS_PER_PAGE')) < totalCount
}
const handleGetMore = () => {
if (!hasMore) return
updatePage(page + 1)
}
const targetRef = React.useRef(null)
const targetRef = useRef(null)
// 监听滚动自动分页加载
const scrollTrigger = React.useCallback(throttle(() => {
const scrollTrigger = useCallback(throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500))
const showPageCover = CONFIG.POST_LIST_COVER
const showPageCover = siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG)
useEffect(() => {
window.addEventListener('scroll', scrollTrigger)

View File

@@ -1,11 +1,11 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import CONFIG from '../config'
import Link from 'next/link'
import TwikooCommentCount from '@/components/TwikooCommentCount'
import LazyImage from '@/components/LazyImage'
const BlogPostCard = ({ post }) => {
const showPageCover = CONFIG.POST_LIST_COVER && post?.pageCoverThumbnail
const showPageCover = siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG) && post?.pageCoverThumbnail
return <article className={`${showPageCover ? 'flex md:flex-row flex-col-reverse' : ''} replace mb-12 `}>
<div className={`${showPageCover ? 'md:w-7/12' : ''}`}>
@@ -18,7 +18,7 @@ const BlogPostCard = ({ post }) => {
</h2>
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300">
by <a href="#" className="text-gray-700 dark:text-gray-300">{BLOG.AUTHOR}</a> on {post.date?.start_date || post.createdTime}
by <a href="#" className="text-gray-700 dark:text-gray-300">{siteConfig('AUTHOR')}</a> on {post.date?.start_date || post.createdTime}
<TwikooCommentCount post={post} className='pl-1'/>
<span className="font-bold mx-1"> | </span>
<a href={`/category${post.category}`} className="text-gray-700 dark:text-gray-300 hover:underline">{post.category}</a>
@@ -40,9 +40,9 @@ const BlogPostCard = ({ post }) => {
</div>
{/* 图片封面 */}
{showPageCover && (
<div className="md:w-5/12 w-full overflow-hidden p-1">
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
<LazyImage src={post?.pageCoverThumbnail} className='h-44 bg-center bg-cover hover:scale-110 duration-200' />
<div className="md:w-5/12 w-full h-44 overflow-hidden p-1">
<Link href={`${siteConfig('SUB_PATH', '')}/${post.slug}`} passHref legacyBehavior>
<LazyImage src={post?.pageCoverThumbnail} className='w-full bg-cover hover:scale-110 duration-200' />
</Link>
</div>
)}

View File

@@ -1,5 +1,5 @@
import React from 'react'
import BLOG from '@/blog.config'
import { useEffect, useState } from 'react'
import { siteConfig } from '@/lib/config'
import Link from 'next/link'
import { RecentComments } from '@waline/client'
@@ -9,11 +9,11 @@ import { RecentComments } from '@waline/client'
* @returns
*/
const ExampleRecentComments = (props) => {
const [comments, updateComments] = React.useState([])
const [onLoading, changeLoading] = React.useState(true)
React.useEffect(() => {
const [comments, updateComments] = useState([])
const [onLoading, changeLoading] = useState(true)
useEffect(() => {
RecentComments({
serverURL: BLOG.COMMENT_WALINE_SERVER_URL,
serverURL: siteConfig('COMMENT_WALINE_SERVER_URL'),
count: 5
}).then(({ comments }) => {
changeLoading(false)

View File

@@ -1,28 +1,24 @@
import BLOG from '@/blog.config'
import DarkModeButton from '@/components/DarkModeButton'
import { siteConfig } from '@/lib/config'
export const Footer = (props) => {
const d = new Date()
const currentYear = d.getFullYear()
const copyrightDate = (function() {
if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
return BLOG.SINCE + '-' + currentYear
}
return currentYear
})()
const since = siteConfig('SINCE')
const copyrightDate = parseInt(since) < currentYear ? since + '-' + currentYear : currentYear
return <footer className="z-10 relative w-full bg-white px-6 border-t dark:border-hexo-black-gray dark:bg-hexo-black-gray ">
<DarkModeButton className='text-center pt-4'/>
<div className="container mx-auto max-w-4xl py-6 md:flex flex-wrap md:flex-no-wrap md:justify-between items-center text-sm">
<div className='text-center'> &copy;{`${copyrightDate}`} {BLOG.AUTHOR}. All rights reserved.</div>
<div className='text-center'> &copy;{`${copyrightDate}`} {siteConfig('AUTHOR')}. All rights reserved.</div>
<div className="md:p-0 text-center md:text-right text-xs">
{/* 右侧链接 */}
{/* <a href="#" className="text-black no-underline hover:underline">Privacy Policy</a> */}
{BLOG.BEI_AN && (<a href="https://beian.miit.gov.cn/" className="text-black dark:text-gray-200 no-underline hover:underline ml-4">{BLOG.BEI_AN} </a>)}
{siteConfig('BEI_AN') && (<a href="https://beian.miit.gov.cn/" className="text-black dark:text-gray-200 no-underline hover:underline ml-4">{siteConfig('BEI_AN')} </a>)}
<span className='dark:text-gray-200 no-underline ml-4'>
Powered by
<a href="https://github.com/tangly1024/NotionNext" className=' hover:underline'> NotionNext {BLOG.VERSION} </a>
<a href="https://github.com/tangly1024/NotionNext" className=' hover:underline'> NotionNext {siteConfig('VERSION')} </a>
</span>
</div>
</div>

View File

@@ -1,12 +1,11 @@
import Link from 'next/link'
import { siteConfig } from '@/lib/config'
/**
* 网站顶部
* @returns
*/
export const Header = (props) => {
const { siteInfo } = props
return (
<header className="w-full px-6 bg-white dark:bg-black relative z-10">
<div className="container mx-auto max-w-4xl md:flex justify-between items-center">
@@ -14,7 +13,7 @@ export const Header = (props) => {
href='/'
className="py-6 w-full text-center md:text-left md:w-auto text-gray-dark no-underline flex justify-center items-center">
{siteInfo?.title}
{siteConfig('TITLE')}
</Link>
<div className="w-full md:w-auto text-center md:text-right">
{/* 右侧文字 */}

View File

@@ -1,5 +1,4 @@
import { useGlobal } from '@/lib/global'
import React from 'react'
/**
* 跳转到网页顶部

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import CONFIG from '../config'
import { MenuItemDrop } from './MenuItemDrop'
@@ -13,10 +13,10 @@ export const Nav = (props) => {
const { locale } = useGlobal()
let links = [
{ id: 1, icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG.MENU_SEARCH },
{ id: 2, icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG.MENU_ARCHIVE },
{ id: 3, icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG.MENU_CATEGORY },
{ id: 4, icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG.MENU_TAG }
{ id: 1, icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: siteConfig('EXAMPLE_MENU_SEARCH', null, CONFIG) },
{ id: 2, icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: siteConfig('EXAMPLE_MENU_ARCHIVE', null, CONFIG) },
{ id: 3, icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: siteConfig('EXAMPLE_MENU_CATEGORY', null, CONFIG) },
{ id: 4, icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: siteConfig('EXAMPLE_MENU_TAG', null, CONFIG) }
]
if (customNav) {
@@ -24,7 +24,7 @@ export const Nav = (props) => {
}
// 如果 开启自定义菜单,则不再使用 Page生成菜单。
if (BLOG.CUSTOM_MENU) {
if (siteConfig('CUSTOM_MENU')) {
links = customMenu
}

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import Live2D from '@/components/Live2D'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
@@ -51,7 +51,7 @@ export const SideBar = (props) => {
<Announcement post={notice}/>
{BLOG.COMMENT_WALINE_SERVER_URL && BLOG.COMMENT_WALINE_RECENT && <aside className="rounded shadow overflow-hidden mb-6">
{siteConfig('COMMENT_WALINE_SERVER_URL') && siteConfig('COMMENT_WALINE_RECENT') && <aside className="rounded shadow overflow-hidden mb-6">
<h3 className="text-sm bg-gray-100 text-gray-700 dark:bg-hexo-black-gray dark:text-gray-200 py-3 px-4 dark:border-hexo-black-gray border-b">{locale.COMMON.RECENT_COMMENTS}</h3>
<div className="p-4">

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
/**
* 标题栏
@@ -6,9 +6,9 @@ import BLOG from '@/blog.config'
* @returns
*/
export const Title = (props) => {
const { siteInfo, post } = props
const title = post?.title || siteInfo?.description
const description = post?.description || BLOG.AUTHOR
const { post } = props
const title = post?.title || siteConfig('TITLE')
const description = post?.description || siteConfig('AUTHOR')
return <div className="text-center px-6 py-12 mb-6 bg-gray-100 dark:bg-hexo-black-gray dark:border-hexo-black-gray border-b">
<h1 className=" text-xl md:text-4xl pb-4">{title}</h1>

View File

@@ -3,12 +3,12 @@
*/
const CONFIG = {
// 菜单配置
MENU_CATEGORY: true, // 显示分类
MENU_TAG: true, // 显示标签
MENU_ARCHIVE: true, // 显示归档
MENU_SEARCH: true, // 显示搜索
EXAMPLE_MENU_CATEGORY: true, // 显示分类
EXAMPLE_MENU_TAG: true, // 显示标签
EXAMPLE_MENU_ARCHIVE: true, // 显示归档
EXAMPLE_MENU_SEARCH: true, // 显示搜索
POST_LIST_COVER: true // 列表显示文章封面
EXAMPLE_POST_LIST_COVER: true // 列表显示文章封面
}
export default CONFIG

View File

@@ -1,6 +1,5 @@
'use client'
import BLOG from '@/blog.config'
import CONFIG from './config'
import { useEffect } from 'react'
import { Header } from './components/Header'
@@ -27,6 +26,7 @@ import { useRouter } from 'next/router'
import { Transition } from '@headlessui/react'
import { Style } from './style'
import CommonHead from '@/components/CommonHead'
import { siteConfig } from '@/lib/config'
/**
* 基础布局框架
@@ -67,7 +67,7 @@ const LayoutBase = props => {
{/* 标题栏 */}
<Title {...props} />
<div id='container-wrapper' className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + 'relative container mx-auto justify-center md:flex items-start py-8 px-2'}>
<div id='container-wrapper' className={(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE')) ? 'flex-row-reverse' : '') + 'relative container mx-auto justify-center md:flex items-start py-8 px-2'}>
{/* 内容 */}
<div className='w-full max-w-3xl xl:px-14 lg:px-4 '>
@@ -134,7 +134,7 @@ const LayoutPostList = props => {
}
return (
<LayoutBase {...props} slotTop={slotTop}>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</LayoutBase>
)
}

View File

@@ -12,9 +12,7 @@ import SocialButton from './SocialButton'
import { useFukasawaGlobal } from '..'
import CONFIG from '@/themes/fukasawa/config'
import { AdSlot } from '@/components/GoogleAdsense'
// import { debounce } from 'lodash'
// import { useEffect } from 'react'
import { siteConfig } from '@/lib/config'
/**
* 侧边栏
@@ -22,7 +20,7 @@ import { AdSlot } from '@/components/GoogleAdsense'
* @returns
*/
function AsideLeft(props) {
const { tagOptions, currentTag, categoryOptions, currentCategory, post, slot, siteInfo, notice } = props
const { tagOptions, currentTag, categoryOptions, currentCategory, post, slot, notice } = props
const router = useRouter()
const { isCollapsed, setIsCollapse } = useFukasawaGlobal()
// 折叠侧边栏
@@ -56,7 +54,7 @@ function AsideLeft(props) {
return <div className={`sideLeft relative ${isCollapsed ? 'w-0' : 'w-80'} duration-150 transition-all bg-white dark:bg-hexo-black-gray min-h-screen hidden lg:block z-20`}>
{/* 折叠按钮 */}
{CONFIG.SIDEBAR_COLLAPSE_BUTTON && <div className={`${isCollapsed ? '' : 'ml-80'} hidden lg:block sticky top-0 mx-2 cursor-pointer hover:scale-110 duration-150 px-3 py-2`} onClick={toggleOpen}>
{siteConfig('FUKASAWA_SIDEBAR_COLLAPSE_BUTTON', null, CONFIG) && <div className={`${isCollapsed ? '' : 'ml-80'} hidden lg:block sticky top-0 mx-2 cursor-pointer hover:scale-110 duration-150 px-3 py-2`} onClick={toggleOpen}>
{isCollapsed ? <i className="fa-solid fa-indent text-xl"></i> : <i className='fas fa-bars text-xl'></i>}
</div>}
@@ -65,7 +63,7 @@ function AsideLeft(props) {
<Logo {...props} />
<section className='siteInfo flex flex-col dark:text-gray-300 pt-8'>
{siteInfo?.description}
{siteConfig('DESCRIPTION')}
</section>
<section className='flex flex-col text-gray-600'>

View File

@@ -1,17 +1,16 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import Link from 'next/link'
import TagItemMini from './TagItemMini'
import React from 'react'
import CONFIG_FUKA from '../config'
import CONFIG from '../config'
import LazyImage from '@/components/LazyImage'
const BlogCard = ({ index, post, showSummary, siteInfo }) => {
const showPreview = CONFIG_FUKA.POST_LIST_PREVIEW && post.blockMap
const showPreview = siteConfig('FUKASAWA_POST_LIST_PREVIEW', null, CONFIG) && post.blockMap
// fukasawa 强制显示图片
if (CONFIG_FUKA.POST_LIST_COVER_FORCE && post && !post.pageCover) {
if (siteConfig('FUKASAWA_POST_LIST_COVER_FORCE', null, CONFIG) && post && !post.pageCover) {
post.pageCoverThumbnail = siteInfo?.pageCover
}
const showPageCover = CONFIG_FUKA.POST_LIST_COVER && post?.pageCoverThumbnail
const showPageCover = siteConfig('FUKASAWA_POST_LIST_COVER', null, CONFIG) && post?.pageCoverThumbnail
return (
<div
@@ -26,10 +25,10 @@ const BlogCard = ({ index, post, showSummary, siteInfo }) => {
{/* 封面图 */}
{showPageCover && (
<div className="flex-grow mb-3 w-full duration-200 cursor-pointer transform overflow-hidden">
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
<Link href={`${siteConfig('SUB_PATH', '')}/${post.slug}`} passHref legacyBehavior>
<LazyImage
src={post?.pageCoverThumbnail}
alt={post?.title || BLOG.TITLE}
alt={post?.title || siteConfig('TITLE')}
className="object-cover w-full h-full hover:scale-125 transform duration-500"
/>
</Link>
@@ -38,7 +37,7 @@ const BlogCard = ({ index, post, showSummary, siteInfo }) => {
{/* 文字部分 */}
<div className="flex flex-col w-full">
<Link passHref href={`${BLOG.SUB_PATH}/${post.slug}`}
<Link passHref href={`${siteConfig('SUB_PATH', '')}/${post.slug}`}
className={`break-words cursor-pointer font-bold hover:underline text-xl ${showPreview ? 'justify-center' : 'justify-start'} leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}
>
{post.title}

View File

@@ -1,4 +1,4 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
import { deepClone, isBrowser } from '@/lib/utils'
import BlogCard from './BlogCard'
import BlogPostListEmpty from './BlogListEmpty'
@@ -15,7 +15,7 @@ import { AdSlot } from '@/components/GoogleAdsense'
* @constructor
*/
const BlogListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
const totalPage = Math.ceil(postCount / parseInt(siteConfig('POSTS_PER_PAGE')))
const showNext = page < totalPage
const [columns, setColumns] = useState(calculateColumns())
@@ -59,7 +59,7 @@ const BlogListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
<BlogCard index={posts.indexOf(post)} key={post.id} post={post} siteInfo={siteInfo} />
</div>
))}
{BLOG.ADSENSE_GOOGLE_ID && (
{siteConfig('ADSENSE_GOOGLE_ID') && (
<div className='p-3'>
<AdSlot type='flow'/>
</div>

View File

@@ -1,5 +1,5 @@
import BLOG from '@/blog.config'
import React from 'react'
import { siteConfig } from '@/lib/config'
import { useEffect, useRef, useState } from 'react'
import BlogCard from './BlogCard'
import BlogPostListEmpty from './BlogListEmpty'
import { useGlobal } from '@/lib/global'
@@ -15,18 +15,18 @@ import { useGlobal } from '@/lib/global'
const BlogListScroll = props => {
const { posts = [], siteInfo } = props
const { locale } = useGlobal()
const targetRef = React.useRef(null)
const targetRef = useRef(null)
const [page, updatePage] = React.useState(1)
const [page, updatePage] = useState(1)
let hasMore = false
const postsToShow = posts
? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page)
? Object.assign(posts).slice(0, parseInt(siteConfig('POSTS_PER_PAGE')) * page)
: []
if (posts) {
const totalCount = posts.length
hasMore = page * BLOG.POSTS_PER_PAGE < totalCount
hasMore = page * parseInt(siteConfig('POSTS_PER_PAGE')) < totalCount
}
const handleGetMore = () => {
if (!hasMore) return
@@ -44,7 +44,7 @@ const BlogListScroll = props => {
})
}
React.useEffect(() => {
useEffect(() => {
window.addEventListener('scroll', scrollTrigger)
return () => {
window.removeEventListener('scroll', scrollTrigger)

View File

@@ -1,6 +1,6 @@
import React from 'react'
import Link from 'next/link'
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
/**
* 博客归档
* @param posts 所有文章
@@ -30,7 +30,7 @@ const BlogArchiveItem = ({ posts = [], archiveTitle }) => {
<span className="text-gray-400">{post.date?.start_date}</span>{' '}
&nbsp;
<Link
href={`${BLOG.SUB_PATH}/${post.slug}`}
href={`${siteConfig('SUB_PATH', '')}/${post.slug}`}
passHref
className="dark:text-gray-400 dark:hover:text-gray-300 overflow-x-hidden hover:underline cursor-pointer text-gray-600">

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import throttle from 'lodash.throttle'
import { uuidToId } from 'notion-utils'
import { useGlobal } from '@/lib/global'

View File

@@ -1,5 +1,4 @@
import Link from 'next/link'
import React from 'react'
function GroupCategory ({ currentCategory, categories }) {
if (!categories) {

View File

@@ -1,13 +1,13 @@
import Link from 'next/link'
import { siteConfig } from '@/lib/config'
const Logo = props => {
const { siteInfo } = props
return (
<section className='flex'>
<Link
href='/'
className='hover:bg-black hover:text-white border-black border-2 duration-500 px-4 py-2 cursor-pointer dark:text-gray-300 dark:border-gray-300 font-black'>
{siteInfo?.title}
{siteConfig('TITLE')}
</Link>
</section>
);

View File

@@ -48,8 +48,8 @@ export const MenuItemCollapse = (props) => {
{/* 折叠子菜单 */}
{hasSubMenu && <Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
{link.subMenus.map(sLink => {
return <div key={sLink.id} className='whitespace-nowrap dark:text-gray-200
{link.subMenus.map((sLink, index) => {
return <div key={index} className='whitespace-nowrap dark:text-gray-200
not:last-child:border-b-0 border-b dark:border-gray-800 py-2 px-14 cursor-pointer hover:bg-gray-100
font-extralight dark:bg-black text-left justify-start text-gray-600 bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200'>
<Link href={sLink.to} target={link?.to?.indexOf('http') === 0 ? '_blank' : '_self'}>

View File

@@ -1,6 +1,6 @@
import { useGlobal } from '@/lib/global'
import CONFIG_FUKA from '../config'
import BLOG from '@/blog.config'
import CONFIG from '../config'
import { siteConfig } from '@/lib/config'
import { MenuItemDrop } from './MenuItemDrop'
import { MenuItemCollapse } from './MenuItemCollapse'
@@ -10,10 +10,10 @@ export const MenuList = (props) => {
let links = [
{ name: locale.NAV.INDEX, to: '/' || '/', show: true },
{ name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_FUKA.MENU_CATEGORY },
{ name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_FUKA.MENU_TAG },
{ name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_FUKA.MENU_ARCHIVE },
{ name: locale.NAV.SEARCH, to: '/search', show: CONFIG_FUKA.MENU_SEARCH }
{ name: locale.COMMON.CATEGORY, to: '/category', show: siteConfig('FUKASAWA_MENU_CATEGORY', null, CONFIG) },
{ name: locale.COMMON.TAGS, to: '/tag', show: siteConfig('FUKASAWA_MENU_TAG', null, CONFIG) },
{ name: locale.NAV.ARCHIVE, to: '/archive', show: siteConfig('FUKASAWA_MENU_ARCHIVE', null, CONFIG) },
{ name: locale.NAV.SEARCH, to: '/search', show: siteConfig('FUKASAWA_MENU_SEARCH', null, CONFIG) }
]
if (customNav) {
@@ -21,7 +21,7 @@ export const MenuList = (props) => {
}
// 如果 开启自定义菜单则覆盖Page生成的菜单
if (BLOG.CUSTOM_MENU) {
if (siteConfig('CUSTOM_MENU')) {
links = customMenu
}

View File

@@ -1,27 +1,23 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
function SiteInfo ({ title }) {
const d = new Date()
const currentYear = d.getFullYear()
const copyrightDate = (function() {
if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
return BLOG.SINCE + '-' + currentYear
}
return currentYear
})()
const since = siteConfig('SINCE')
const copyrightDate = parseInt(since) < currentYear ? since + '-' + currentYear : currentYear
return (
<footer
className='relative leading-6 justify-start w-full text-gray-600 dark:text-gray-300 text-xs font-sans'
>
<span> © {`${copyrightDate}`} <span> <a href={BLOG.LINK}> <i className='mx-1 animate-pulse fas fa-heart'/> {BLOG.AUTHOR}</a>. <br /></span>
<span> © {`${copyrightDate}`} <span> <a href={siteConfig('LINK')}> <i className='mx-1 animate-pulse fas fa-heart'/> {siteConfig('AUTHOR')}</a>. <br /></span>
{BLOG.BEI_AN && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</a><br/></>}
{siteConfig('BEI_AN') && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{siteConfig('BEI_AN')}</a><br/></>}
<span className='hidden busuanzi_container_site_pv'> <i className='fas fa-eye' /><span className='px-1 busuanzi_value_site_pv'> </span> </span>
<span className='pl-2 hidden busuanzi_container_site_uv'> <i className='fas fa-users' /> <span className='px-1 busuanzi_value_site_uv'> </span> </span>
<br />
<span className='text-xs font-serif'> Powered by <a href='https://github.com/tangly1024/NotionNext' className='underline'>NotionNext {BLOG.VERSION}</a></span><br /></span>
<span className='text-xs font-serif'> Powered by <a href='https://github.com/tangly1024/NotionNext' className='underline'>NotionNext {siteConfig('VERSION')}</a></span><br /></span>
<h1>{title}</h1>
</footer>
)

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