This commit is contained in:
tangly1024
2023-12-10 10:31:19 +08:00
34 changed files with 545 additions and 307 deletions

View File

@@ -201,6 +201,9 @@
- **图标**: [Fontawesome](https://fontawesome.com/v6/icons/) - **图标**: [Fontawesome](https://fontawesome.com/v6/icons/)
## 🔗 友情链接
- [Elog](https://github.com/LetTTGACO/elog) Markdown 批量导出工具、开放式跨平台博客解决方案,随意组合写作平台(语雀/Notion/FlowUs/飞书)和博客平台(Hexo/Vitepress/Halo/Confluence/WordPress等)
## License ## License
The MIT License. The MIT License.

View File

@@ -324,6 +324,10 @@ const BLOG = {
ANALYTICS_CNZZ_ID: process.env.NEXT_PUBLIC_ANALYTICS_CNZZ_ID || '', // 只需要填写站长统计的id, [cnzz_id] -> https://s9.cnzz.com/z_stat.php?id=[cnzz_id]&web_id=[cnzz_id] ANALYTICS_CNZZ_ID: process.env.NEXT_PUBLIC_ANALYTICS_CNZZ_ID || '', // 只需要填写站长统计的id, [cnzz_id] -> https://s9.cnzz.com/z_stat.php?id=[cnzz_id]&web_id=[cnzz_id]
ANALYTICS_GOOGLE_ID: process.env.NEXT_PUBLIC_ANALYTICS_GOOGLE_ID || '', // 谷歌Analytics的id e.g: G-XXXXXXXXXX ANALYTICS_GOOGLE_ID: process.env.NEXT_PUBLIC_ANALYTICS_GOOGLE_ID || '', // 谷歌Analytics的id e.g: G-XXXXXXXXXX
// 51la 站点统计 https://www.51.la/
ANALYTICS_51LA_ID: process.env.NEXT_PUBLIC_ANALYTICS_51LA_ID || '', // id在51la后台获取 参阅 https://docs.tangly1024.com/article/notion-next-51-la
ANALYTICS_51LA_CK: process.env.NEXT_PUBLIC_ANALYTICS_51LA_CK || '', // ck在51la后台获取
// Matomo 网站统计 // Matomo 网站统计
MATOMO_HOST_URL: process.env.NEXT_PUBLIC_MATOMO_HOST_URL || '', // Matomo服务器地址不带斜杠 MATOMO_HOST_URL: process.env.NEXT_PUBLIC_MATOMO_HOST_URL || '', // Matomo服务器地址不带斜杠
MATOMO_SITE_ID: process.env.NEXT_PUBLIC_MATOMO_SITE_ID || '', // Matomo网站ID MATOMO_SITE_ID: process.env.NEXT_PUBLIC_MATOMO_SITE_ID || '', // Matomo网站ID
@@ -398,12 +402,14 @@ const BLOG = {
IMG_LAZY_LOAD_PLACEHOLDER: process.env.NEXT_PUBLIC_IMG_LAZY_LOAD_PLACEHOLDER || 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', // 懒加载占位图片地址支持base64或url IMG_LAZY_LOAD_PLACEHOLDER: process.env.NEXT_PUBLIC_IMG_LAZY_LOAD_PLACEHOLDER || 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', // 懒加载占位图片地址支持base64或url
IMG_URL_TYPE: process.env.NEXT_PUBLIC_IMG_TYPE || 'Notion', // 此配置已失效请勿使用AMAZON方案不再支持仅支持Notion方案。 ['Notion','AMAZON'] 站点图片前缀 默认 Notion:(https://notion.so/images/xx) AMAZON(https://s3.us-west-2.amazonaws.com/xxx) IMG_URL_TYPE: process.env.NEXT_PUBLIC_IMG_TYPE || 'Notion', // 此配置已失效请勿使用AMAZON方案不再支持仅支持Notion方案。 ['Notion','AMAZON'] 站点图片前缀 默认 Notion:(https://notion.so/images/xx) AMAZON(https://s3.us-west-2.amazonaws.com/xxx)
IMG_SHADOW: process.env.NEXT_PUBLIC_IMG_SHADOW || false, // 文章图片是否自动添加阴影 IMG_SHADOW: process.env.NEXT_PUBLIC_IMG_SHADOW || false, // 文章图片是否自动添加阴影
IMG_COMPRESS_WIDTH: process.env.NEXT_PUBLIC_IMG_COMPRESS_WIDTH || 800, // Notion图片压缩宽度
// 开发相关 // 开发相关
NOTION_ACCESS_TOKEN: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public NOTION_ACCESS_TOKEN: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public
DEBUG: process.env.NEXT_PUBLIC_DEBUG || false, // 是否显示调试按钮 DEBUG: process.env.NEXT_PUBLIC_DEBUG || false, // 是否显示调试按钮
ENABLE_CACHE: process.env.ENABLE_CACHE || process.env.npm_lifecycle_event === 'build', // 缓存在开发调试和打包过程中选择性开启,正式部署开启此功能意义不大。 ENABLE_CACHE: process.env.ENABLE_CACHE || process.env.npm_lifecycle_event === 'build', // 缓存在开发调试和打包过程中选择性开启,正式部署开启此功能意义不大。
isProd: process.env.VERCEL_ENV === 'production', // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) isProd: process.env.VERCEL_ENV === 'production' // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) isProd: process.env.VERCEL_ENV === 'production', // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) isProd: process.env.VERCEL_ENV === 'production' // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables)
BUNDLE_ANALYZER: process.env.ANALYZE === 'true' || false, // 是否展示编译依赖内容与大小
VERSION: process.env.NEXT_PUBLIC_VERSION // 版本号 VERSION: process.env.NEXT_PUBLIC_VERSION // 版本号
} }

View File

@@ -0,0 +1,12 @@
import AOS from 'aos'
import { isBrowser } from 'react-notion-x'
/**
* 加载滚动动画
* https://michalsnik.github.io/aos/
*/
export default function AOSAnimation() {
if (isBrowser) {
AOS.init()
}
}

View File

@@ -1,6 +1,6 @@
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import Tabs from '@/components/Tabs' import Tabs from '@/components/Tabs'
import { isBrowser } from '@/lib/utils' import { isBrowser, isSearchEngineBot } from '@/lib/utils'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import Artalk from './Artalk' import Artalk from './Artalk'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
@@ -73,6 +73,10 @@ const Comment = ({ siteInfo, frontMatter, className }) => {
const COMMENT_GITALK_CLIENT_ID = siteConfig('COMMENT_GITALK_CLIENT_ID') const COMMENT_GITALK_CLIENT_ID = siteConfig('COMMENT_GITALK_CLIENT_ID')
const COMMENT_WEBMENTION_ENABLE = siteConfig('COMMENT_WEBMENTION_ENABLE') const COMMENT_WEBMENTION_ENABLE = siteConfig('COMMENT_WEBMENTION_ENABLE')
if (isSearchEngineBot()) {
return null
}
// 当连接中有特殊参数时跳转到评论区 // 当连接中有特殊参数时跳转到评论区
if (isBrowser && ('giscus' in router.query || router.query.target === 'comment')) { if (isBrowser && ('giscus' in router.query || router.query.target === 'comment')) {
setTimeout(() => { setTimeout(() => {

View File

@@ -8,8 +8,12 @@ const katexSettings = {
strict: false strict: false
} }
/**
* 数学公式
* @param {} param0
* @returns
*/
export const Equation = ({ block, math, inline = false, className, ...rest }) => { export const Equation = ({ block, math, inline = false, className, ...rest }) => {
// const { recordMap } = useNotionContext()
math = math || getBlockTitle(block, null) math = math || getBlockTitle(block, null)
if (!math) return null if (!math) return null

View File

@@ -1,5 +1,6 @@
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import LA51 from './LA51'
import WebWhiz from './Webwhiz' import WebWhiz from './Webwhiz'
const TwikooCommentCounter = dynamic(() => import('@/components/TwikooCommentCounter'), { ssr: false }) const TwikooCommentCounter = dynamic(() => import('@/components/TwikooCommentCounter'), { ssr: false })
@@ -22,6 +23,8 @@ const VConsole = dynamic(() => import('@/components/VConsole'), { ssr: false })
const CustomContextMenu = dynamic(() => import('@/components/CustomContextMenu'), { ssr: false }) const CustomContextMenu = dynamic(() => import('@/components/CustomContextMenu'), { ssr: false })
const DisableCopy = dynamic(() => import('@/components/DisableCopy'), { ssr: false }) const DisableCopy = dynamic(() => import('@/components/DisableCopy'), { ssr: false })
const AdBlockDetect = dynamic(() => import('@/components/AdBlockDetect'), { ssr: false }) const AdBlockDetect = dynamic(() => import('@/components/AdBlockDetect'), { ssr: false })
const LoadingProgress = dynamic(() => import('@/components/LoadingProgress'), { ssr: false })
const AosAnimation = dynamic(() => import('@/components/AOSAnimation'), { ssr: false })
/** /**
* 各种插件脚本 * 各种插件脚本
@@ -29,145 +32,199 @@ const AdBlockDetect = dynamic(() => import('@/components/AdBlockDetect'), { ssr:
* @returns * @returns
*/ */
const ExternalPlugin = (props) => { const ExternalPlugin = (props) => {
const DISABLE_PLUGIN = siteConfig('DISABLE_PLUGIN')
const THEME_SWITCH = siteConfig('THEME_SWITCH')
const DEBUG = siteConfig('DEBUG')
const ANALYTICS_ACKEE_TRACKER = siteConfig('ANALYTICS_ACKEE_TRACKER')
const ANALYTICS_VERCEL = siteConfig('ANALYTICS_VERCEL')
const ANALYTICS_BUSUANZI_ENABLE = siteConfig('ANALYTICS_BUSUANZI_ENABLE')
const ADSENSE_GOOGLE_ID = siteConfig('ADSENSE_GOOGLE_ID')
const FACEBOOK_APP_ID = siteConfig('FACEBOOK_APP_ID')
const FACEBOOK_PAGE_ID = siteConfig('FACEBOOK_PAGE_ID')
const FIREWORKS = siteConfig('FIREWORKS')
const SAKURA = siteConfig('SAKURA')
const STARRY_SKY = siteConfig('STARRY_SKY')
const MUSIC_PLAYER = siteConfig('MUSIC_PLAYER')
const NEST = siteConfig('NEST')
const FLUTTERINGRIBBON = siteConfig('FLUTTERINGRIBBON')
const COMMENT_TWIKOO_COUNT_ENABLE = siteConfig('COMMENT_TWIKOO_COUNT_ENABLE')
const RIBBON = siteConfig('RIBBON')
const CUSTOM_RIGHT_CLICK_CONTEXT_MENU = siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU')
const CAN_COPY = siteConfig('CAN_COPY')
const WEB_WHIZ_ENABLED = siteConfig('WEB_WHIZ_ENABLED')
const AD_WWADS_BLOCK_DETECT = siteConfig('AD_WWADS_BLOCK_DETECT')
const CHATBASE_ID = siteConfig('CHATBASE_ID')
const COMMENT_DAO_VOICE_ID = siteConfig('COMMENT_DAO_VOICE_ID')
const AD_WWADS_ID = siteConfig('AD_WWADS_ID')
const COMMENT_TWIKOO_ENV_ID = siteConfig('COMMENT_TWIKOO_ENV_ID')
const COMMENT_TWIKOO_CDN_URL = siteConfig('COMMENT_TWIKOO_CDN_URL')
const COMMENT_ARTALK_SERVER = siteConfig('COMMENT_ARTALK_SERVER')
const COMMENT_ARTALK_JS = siteConfig('COMMENT_ARTALK_JS')
const COMMENT_TIDIO_ID = siteConfig('COMMENT_TIDIO_ID')
const COMMENT_GITTER_ROOM = siteConfig('COMMENT_GITTER_ROOM')
const ANALYTICS_BAIDU_ID = siteConfig('ANALYTICS_BAIDU_ID')
const ANALYTICS_CNZZ_ID = siteConfig('ANALYTICS_CNZZ_ID')
const ANALYTICS_GOOGLE_ID = siteConfig('ANALYTICS_GOOGLE_ID')
const MATOMO_HOST_URL = siteConfig('MATOMO_HOST_URL')
const MATOMO_SITE_ID = siteConfig('MATOMO_SITE_ID')
const ANALYTICS_51LA_ID = siteConfig('ANALYTICS_51LA_ID')
const ANALYTICS_51LA_CK = siteConfig('ANALYTICS_51LA_CK')
if (DISABLE_PLUGIN) {
return null
}
return <> return <>
{JSON.parse(siteConfig('THEME_SWITCH')) && <ThemeSwitch />} {THEME_SWITCH && <ThemeSwitch />}
{JSON.parse(siteConfig('DEBUG')) && <DebugPanel />} {DEBUG && <DebugPanel />}
{siteConfig('ANALYTICS_ACKEE_TRACKER') && <Ackee />} {ANALYTICS_ACKEE_TRACKER && <Ackee />}
{siteConfig('ANALYTICS_GOOGLE_ID') && <Gtag />} {ANALYTICS_GOOGLE_ID && <Gtag />}
{siteConfig('ANALYTICS_VERCEL') && <Analytics />} {ANALYTICS_VERCEL && <Analytics />}
{JSON.parse(siteConfig('ANALYTICS_BUSUANZI_ENABLE')) && <Busuanzi />} {ANALYTICS_BUSUANZI_ENABLE && <Busuanzi />}
{siteConfig('ADSENSE_GOOGLE_ID') && <GoogleAdsense />} {ADSENSE_GOOGLE_ID && <GoogleAdsense />}
{siteConfig('FACEBOOK_APP_ID') && siteConfig('FACEBOOK_PAGE_ID') && <Messenger />} {FACEBOOK_APP_ID && FACEBOOK_PAGE_ID && <Messenger />}
{JSON.parse(siteConfig('FIREWORKS')) && <Fireworks />} {FIREWORKS && <Fireworks />}
{JSON.parse(siteConfig('SAKURA')) && <Sakura />} {SAKURA && <Sakura />}
{JSON.parse(siteConfig('STARRY_SKY')) && <StarrySky />} {STARRY_SKY && <StarrySky />}
{JSON.parse(siteConfig('MUSIC_PLAYER')) && <MusicPlayer />} {MUSIC_PLAYER && <MusicPlayer />}
{JSON.parse(siteConfig('NEST')) && <Nest />} {NEST && <Nest />}
{JSON.parse(siteConfig('FLUTTERINGRIBBON')) && <FlutteringRibbon />} {FLUTTERINGRIBBON && <FlutteringRibbon />}
{JSON.parse(siteConfig('COMMENT_TWIKOO_COUNT_ENABLE')) && <TwikooCommentCounter {...props} />} {COMMENT_TWIKOO_COUNT_ENABLE && <TwikooCommentCounter {...props} />}
{JSON.parse(siteConfig('RIBBON')) && <Ribbon />} {RIBBON && <Ribbon />}
{JSON.parse(siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU')) && <CustomContextMenu {...props} />} {CUSTOM_RIGHT_CLICK_CONTEXT_MENU && <CustomContextMenu {...props} />}
{!JSON.parse(siteConfig('CAN_COPY')) && <DisableCopy />} {!CAN_COPY && <DisableCopy />}
{JSON.parse(siteConfig('WEB_WHIZ_ENABLED')) && <WebWhiz />} {WEB_WHIZ_ENABLED && <WebWhiz />}
{JSON.parse(siteConfig('AD_WWADS_BLOCK_DETECT')) && <AdBlockDetect />} {AD_WWADS_BLOCK_DETECT && <AdBlockDetect />}
<VConsole /> <VConsole />
<LoadingProgress />
<AosAnimation />
{ANALYTICS_51LA_ID && ANALYTICS_51LA_CK && <LA51/>}
{siteConfig('CHATBASE_ID') && (<> {ANALYTICS_51LA_ID && ANALYTICS_51LA_CK && (<>
<script id={siteConfig('CHATBASE_ID')} src="https://www.chatbase.co/embed.min.js" defer /> <script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js" defer/>
<script async dangerouslySetInnerHTML={{ {/* <script async dangerouslySetInnerHTML={{
__html: ` __html: `
window.chatbaseConfig = { LA.init({id:"${ANALYTICS_51LA_ID}",ck:"${ANALYTICS_51LA_CK}",hashMode:true,autoTrack:true})
chatbotId: "${siteConfig('CHATBASE_ID')}", `
} }} /> */}
` </>)}
}} />
</>)}
{siteConfig('COMMENT_DAO_VOICE_ID') && (<> {CHATBASE_ID && (<>
{/* DaoVoice 反馈 */} <script id={CHATBASE_ID} src="https://www.chatbase.co/embed.min.js" defer />
<script async dangerouslySetInnerHTML={{ <script async dangerouslySetInnerHTML={{
__html: ` __html: `
window.chatbaseConfig = {
chatbotId: "${CHATBASE_ID}",
}
`
}} />
</>)}
{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") (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={{ <script async dangerouslySetInnerHTML={{
__html: ` __html: `
daovoice('init', { daovoice('init', {
app_id: "${siteConfig('COMMENT_DAO_VOICE_ID')}" app_id: "${COMMENT_DAO_VOICE_ID}"
}); });
daovoice('update'); daovoice('update');
` `
}} }}
/> />
</>)} </>)}
{siteConfig('AD_WWADS_ID') && <script type="text/javascript" charSet="UTF-8" src="https://cdn.wwads.cn/js/makemoney.js" async></script>} {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')} />} {COMMENT_TWIKOO_ENV_ID && <script defer src={COMMENT_TWIKOO_CDN_URL} />}
{siteConfig('COMMENT_ARTALK_SERVER') && <script defer src={siteConfig('COMMENT_ARTALK_JS')} />} {COMMENT_ARTALK_SERVER && <script defer src={COMMENT_ARTALK_JS} />}
{siteConfig('COMMENT_TIDIO_ID') && <script async src={`//code.tidio.co/${siteConfig('COMMENT_TIDIO_ID')}.js`} />} {COMMENT_TIDIO_ID && <script async src={`//code.tidio.co/${COMMENT_TIDIO_ID}.js`} />}
{/* gitter聊天室 */} {/* gitter聊天室 */}
{siteConfig('COMMENT_GITTER_ROOM') && (<> {COMMENT_GITTER_ROOM && (<>
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer /> <script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer />
<script async dangerouslySetInnerHTML={{ <script async dangerouslySetInnerHTML={{
__html: ` __html: `
((window.gitter = {}).chat = {}).options = { ((window.gitter = {}).chat = {}).options = {
room: '${siteConfig('COMMENT_GITTER_ROOM')}' room: '${COMMENT_GITTER_ROOM}'
}; };
` `
}} /> }} />
</>)} </>)}
{/* 百度统计 */} {/* 百度统计 */}
{siteConfig('ANALYTICS_BAIDU_ID') && ( {ANALYTICS_BAIDU_ID && (
<script async <script async
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: ` __html: `
var _hmt = _hmt || []; var _hmt = _hmt || [];
(function() { (function() {
var hm = document.createElement("script"); var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?${siteConfig('ANALYTICS_BAIDU_ID')}"; hm.src = "https://hm.baidu.com/hm.js?${ANALYTICS_BAIDU_ID}";
var s = document.getElementsByTagName("script")[0]; var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s); s.parentNode.insertBefore(hm, s);
})(); })();
` `
}} }}
/> />
)} )}
{/* 站长统计 */} {/* 站长统计 */}
{siteConfig('ANALYTICS_CNZZ_ID') && ( {ANALYTICS_CNZZ_ID && (
<script async <script async
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: ` __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")); document.write(unescape("%3Cspan style='display:none' id='cnzz_stat_icon_${ANALYTICS_CNZZ_ID}'%3E%3C/span%3E%3Cscript src='https://s9.cnzz.com/z_stat.php%3Fid%3D${ANALYTICS_CNZZ_ID}' type='text/javascript'%3E%3C/script%3E"));
` `
}} }}
/> />
)} )}
{/* 谷歌统计 */} {/* 谷歌统计 */}
{siteConfig('ANALYTICS_GOOGLE_ID') && (<> {ANALYTICS_GOOGLE_ID && (<>
<script async <script async
src={`https://www.googletagmanager.com/gtag/js?id=${siteConfig('ANALYTICS_GOOGLE_ID')}`} src={`https://www.googletagmanager.com/gtag/js?id=${ANALYTICS_GOOGLE_ID}`}
/> />
<script async <script async
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: ` __html: `
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag(){dataLayer.push(arguments);}
gtag('js', new Date()); gtag('js', new Date());
gtag('config', '${siteConfig('ANALYTICS_GOOGLE_ID')}', { gtag('config', '${ANALYTICS_GOOGLE_ID}', {
page_path: window.location.pathname, page_path: window.location.pathname,
}); });
` `
}} }}
/> />
</>)} </>)}
{/* Matomo 统计 */} {/* Matomo 统计 */}
{siteConfig('MATOMO_HOST_URL') && siteConfig('MATOMO_SITE_ID') && ( {MATOMO_HOST_URL && MATOMO_SITE_ID && (
<script async dangerouslySetInnerHTML={{ <script async dangerouslySetInnerHTML={{
__html: ` __html: `
var _paq = window._paq = window._paq || []; var _paq = window._paq = window._paq || [];
_paq.push(['trackPageView']); _paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']); _paq.push(['enableLinkTracking']);
(function() { (function() {
var u="//${siteConfig('MATOMO_HOST_URL')}/"; var u="//${MATOMO_HOST_URL}/";
_paq.push(['setTrackerUrl', u+'matomo.php']); _paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '${siteConfig('MATOMO_SITE_ID')}']); _paq.push(['setSiteId', '${MATOMO_SITE_ID}']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})(); })();
` `
}}/> }} />
)} )}
</> </>
} }
export default ExternalPlugin export default ExternalPlugin

View File

@@ -1,14 +1,41 @@
import { Component } from 'react' import { Component, useEffect, useState } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
export default function Messenger() { export default function Messenger() {
return <MessengerCustomerChat const pageId = siteConfig('FACEBOOK_PAGE_ID')
pageId={siteConfig('FACEBOOK_PAGE_ID')} const appId = siteConfig('FACEBOOK_APP_ID')
appId={siteConfig('FACEBOOK_APP_ID')} const language = siteConfig('LANG').replace('-', '_')
language={siteConfig('LANG').replace('-', '_')}
shouldShowDialog={true} // 新增一个状态变量用于追踪是否已经滚动过
/> const [showMessenger, setShowMessenger] = useState(false);
const showTheComponent = () => {
window.removeEventListener('scroll', showTheComponent);
if (!showMessenger) {
setShowMessenger(true);
}
};
// 延时7秒或页面滚动时加载该组件
useEffect(() => {
window.addEventListener('scroll', showTheComponent);
setTimeout(() => {
showTheComponent()
}, 7000);
return () => {
window.removeEventListener('scroll', showTheComponent);
};
}, []);
return <>
{showMessenger && <MessengerCustomerChat
pageId={pageId}
appId={appId}
language={language}
shouldShowDialog={true}
/>}
</>
} }
/** /**
@@ -24,8 +51,8 @@ class MessengerCustomerChat extends Component {
} }
/** /**
* 初始化 * 初始化
*/ */
componentDidMount() { componentDidMount() {
this.setFbAsyncInit() this.setFbAsyncInit()
this.reloadSDKAsynchronously() this.reloadSDKAsynchronously()
@@ -34,19 +61,19 @@ class MessengerCustomerChat extends Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if ( if (
prevProps.pageId !== this.props.pageId || prevProps.pageId !== this.props.pageId ||
prevProps.appId !== this.props.appId || prevProps.appId !== this.props.appId ||
prevProps.shouldShowDialog !== this.props.shouldShowDialog || prevProps.shouldShowDialog !== this.props.shouldShowDialog ||
prevProps.htmlRef !== this.props.htmlRef || prevProps.htmlRef !== this.props.htmlRef ||
prevProps.minimized !== this.props.minimized || prevProps.minimized !== this.props.minimized ||
prevProps.themeColor !== this.props.themeColor || prevProps.themeColor !== this.props.themeColor ||
prevProps.loggedInGreeting !== this.props.loggedInGreeting || prevProps.loggedInGreeting !== this.props.loggedInGreeting ||
prevProps.loggedOutGreeting !== this.props.loggedOutGreeting || prevProps.loggedOutGreeting !== this.props.loggedOutGreeting ||
prevProps.greetingDialogDisplay !== this.props.greetingDialogDisplay || prevProps.greetingDialogDisplay !== this.props.greetingDialogDisplay ||
prevProps.greetingDialogDelay !== this.props.greetingDialogDelay || prevProps.greetingDialogDelay !== this.props.greetingDialogDelay ||
prevProps.autoLogAppEvents !== this.props.autoLogAppEvents || prevProps.autoLogAppEvents !== this.props.autoLogAppEvents ||
prevProps.xfbml !== this.props.xfbml || prevProps.xfbml !== this.props.xfbml ||
prevProps.version !== this.props.version || prevProps.version !== this.props.version ||
prevProps.language !== this.props.language prevProps.language !== this.props.language
) { ) {
this.setFbAsyncInit() this.setFbAsyncInit()
this.reloadSDKAsynchronously() this.reloadSDKAsynchronously()
@@ -60,8 +87,8 @@ class MessengerCustomerChat extends Component {
} }
/** /**
* 初始化 * 初始化
*/ */
setFbAsyncInit() { setFbAsyncInit() {
const { appId, autoLogAppEvents, xfbml, version } = this.props const { appId, autoLogAppEvents, xfbml, version } = this.props
@@ -80,18 +107,18 @@ class MessengerCustomerChat extends Component {
loadSDKAsynchronously() { loadSDKAsynchronously() {
const { language } = this.props; const { language } = this.props;
/* eslint-disable */ /* eslint-disable */
(function (d, s, id) { (function (d, s, id) {
var js, var js,
fjs = d.getElementsByTagName(s)[0]; fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) { if (d.getElementById(id)) {
return; return;
} }
js = d.createElement(s); js = d.createElement(s);
js.id = id; js.id = id;
js.src = `https://connect.facebook.net/${language}/sdk/xfbml.customerchat.js`; js.src = `https://connect.facebook.net/${language}/sdk/xfbml.customerchat.js`;
fjs.parentNode.insertBefore(js, fjs); fjs.parentNode.insertBefore(js, fjs);
})(document, 'script', 'facebook-jssdk'); })(document, 'script', 'facebook-jssdk');
/* eslint-enable */ /* eslint-enable */
} }
removeFacebookSDK() { removeFacebookSDK() {
@@ -147,25 +174,25 @@ class MessengerCustomerChat extends Component {
const refAttribute = htmlRef !== undefined ? `ref="${htmlRef}"` : '' const refAttribute = htmlRef !== undefined ? `ref="${htmlRef}"` : ''
const minimizedAttribute = const minimizedAttribute =
minimized !== undefined ? `minimized="${minimized}"` : '' minimized !== undefined ? `minimized="${minimized}"` : ''
const themeColorAttribute = const themeColorAttribute =
themeColor !== undefined ? `theme_color="${themeColor}"` : '' themeColor !== undefined ? `theme_color="${themeColor}"` : ''
const loggedInGreetingAttribute = const loggedInGreetingAttribute =
loggedInGreeting !== undefined loggedInGreeting !== undefined
? `logged_in_greeting="${loggedInGreeting}"` ? `logged_in_greeting="${loggedInGreeting}"`
: '' : ''
const loggedOutGreetingAttribute = const loggedOutGreetingAttribute =
loggedOutGreeting !== undefined loggedOutGreeting !== undefined
? `logged_out_greeting="${loggedOutGreeting}"` ? `logged_out_greeting="${loggedOutGreeting}"`
: '' : ''
const greetingDialogDisplayAttribute = const greetingDialogDisplayAttribute =
greetingDialogDisplay !== undefined greetingDialogDisplay !== undefined
? `greeting_dialog_display="${greetingDialogDisplay}"` ? `greeting_dialog_display="${greetingDialogDisplay}"`
: '' : ''
const greetingDialogDelayAttribute = const greetingDialogDelayAttribute =
greetingDialogDelay !== undefined greetingDialogDelay !== undefined
? `greeting_dialog_delay="${greetingDialogDelay}"` ? `greeting_dialog_delay="${greetingDialogDelay}"`
: '' : ''
return { return {
__html: `<div __html: `<div
@@ -192,8 +219,8 @@ class MessengerCustomerChat extends Component {
const element = event.target const element = event.target
if ( if (
element.className && element.className &&
typeof element.className === 'string' && typeof element.className === 'string' &&
element.className.includes('fb_dialog') element.className.includes('fb_dialog')
) { ) {
this.controlPlugin() this.controlPlugin()
} }

View File

@@ -1,6 +1,11 @@
import KaTeX from 'katex' import KaTeX from 'katex'
import React from 'react' import { memo, useEffect, useState } from 'react'
/**
* 数学公式
* @param {*} param0
* @returns
*/
const TeX = ({ const TeX = ({
children, children,
math, math,
@@ -13,9 +18,9 @@ const TeX = ({
}) => { }) => {
const Component = asComponent || (block ? 'div' : 'span') const Component = asComponent || (block ? 'div' : 'span')
const content = (children ?? math) const content = (children ?? math)
const [state, setState] = React.useState({ innerHtml: '' }) const [state, setState] = useState({ innerHtml: '' })
React.useEffect(() => { useEffect(() => {
try { try {
const innerHtml = KaTeX.renderToString(content, { const innerHtml = KaTeX.renderToString(content, {
displayMode: true, displayMode: true,
@@ -50,4 +55,4 @@ const TeX = ({
) )
} }
export default React.memo(TeX) export default memo(TeX)

18
components/LA51.js Normal file
View File

@@ -0,0 +1,18 @@
import { siteConfig } from '@/lib/config'
import { useEffect } from 'react'
/**
* 51LA统计
*/
export default function LA51() {
const ANALYTICS_51LA_ID = siteConfig('ANALYTICS_51LA_ID')
const ANALYTICS_51LA_CK = siteConfig('ANALYTICS_51LA_CK')
useEffect(() => {
const LA = window.LA
if (LA) {
LA.init({ id: `${ANALYTICS_51LA_ID}`, ck: `${ANALYTICS_51LA_CK}`, hashMode: true, autoTrack: true })
}
}, [])
return <></>
}

View File

@@ -4,6 +4,10 @@ import { useGlobal } from '@/lib/global'
import { loadExternalResource } from '@/lib/utils' import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react' import { useEffect } from 'react'
/**
* 网页动画
* @returns
*/
export default function Live2D() { export default function Live2D() {
const { theme, switchTheme } = useGlobal() const { theme, switchTheme } = useGlobal()
const showPet = JSON.parse(siteConfig('WIDGET_PET')) const showPet = JSON.parse(siteConfig('WIDGET_PET'))

View File

@@ -0,0 +1,29 @@
import { useRouter } from 'next/router'
import NProgress from 'nprogress'
import { useEffect } from 'react'
/**
* 出现页面加载进度条
*/
export default function LoadingProgress() {
const router = useRouter()
// 加载进度条
useEffect(() => {
const handleStart = (url) => {
NProgress.start()
}
const handleStop = () => {
NProgress.done()
}
router.events.on('routeChangeStart', handleStart)
router.events.on('routeChangeError', handleStop)
router.events.on('routeChangeComplete', handleStop)
return () => {
router.events.off('routeChangeStart', handleStart)
router.events.off('routeChangeComplete', handleStop)
router.events.off('routeChangeError', handleStop)
}
}, [router])
}

View File

@@ -1,13 +1,18 @@
import { NotionRenderer } from 'react-notion-x'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import mediumZoom from '@fisch0920/medium-zoom' import mediumZoom from '@fisch0920/medium-zoom'
import React, { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
// import { Code } from 'react-notion-x/build/third-party/code'
import TweetEmbed from 'react-tweet-embed'
import 'katex/dist/katex.min.css' import 'katex/dist/katex.min.css'
import { mapImgUrl } from '@/lib/notion/mapImage' import { mapImgUrl } from '@/lib/notion/mapImage'
import { isBrowser } from '@/lib/utils' import { isBrowser } from '@/lib/utils'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { NotionRenderer } from 'react-notion-x'
// Notion渲染
// const NotionRenderer = dynamic(() => import('react-notion-x').then(async (m) => {
// return m.NotionRenderer
// }), {
// ssr: false
// })
const Code = dynamic(() => const Code = dynamic(() =>
import('react-notion-x/build/third-party/code').then(async (m) => { import('react-notion-x/build/third-party/code').then(async (m) => {
@@ -15,6 +20,7 @@ const Code = dynamic(() =>
}), { ssr: false } }), { ssr: false }
) )
// 公式
const Equation = dynamic(() => const Equation = dynamic(() =>
import('@/components/Equation').then(async (m) => { import('@/components/Equation').then(async (m) => {
// 化学方程式 // 化学方程式
@@ -36,6 +42,13 @@ const PrismMac = dynamic(() => import('@/components/PrismMac'), {
ssr: false ssr: false
}) })
/**
* tweet嵌入
*/
const TweetEmbed = dynamic(() => import('react-tweet-embed'), {
ssr: false
})
const Collection = dynamic(() => const Collection = dynamic(() =>
import('react-notion-x/build/third-party/collection').then((m) => m.Collection), { ssr: true } import('react-notion-x/build/third-party/collection').then((m) => m.Collection), { ssr: true }
) )

View File

@@ -1,28 +1,21 @@
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { useRouter } from 'next/router' import dynamic from 'next/dynamic'
import React from 'react'
import ShareButtons from './ShareButtons'
const ShareButtons = dynamic(() => import('@/components/ShareButtons'), { ssr: false })
/**
* 分享栏
* @param {} param0
* @returns
*/
const ShareBar = ({ post }) => { const ShareBar = ({ post }) => {
const router = useRouter()
if (!JSON.parse(siteConfig('POST_SHARE_BAR_ENABLE')) || !post || post?.type !== 'Post') { if (!JSON.parse(siteConfig('POST_SHARE_BAR_ENABLE')) || !post || post?.type !== 'Post') {
return <></> return <></>
} }
const shareUrl = siteConfig('LINK') + router.asPath
return <div className='m-1 overflow-x-auto'> return <div className='m-1 overflow-x-auto'>
<div className='flex w-full md:justify-end'> <div className='flex w-full md:justify-end'>
<ShareButtons shareUrl={shareUrl} title={post.title} image={post.pageCover} body={ <ShareButtons post={post} />
post?.title +
' | ' +
siteConfig('TITLE') +
' ' +
shareUrl +
' ' +
post?.summary
} />
</div> </div>
</div> </div>
} }

View File

@@ -2,6 +2,7 @@ import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { useState } from 'react' import { useState } from 'react'
import { import {
@@ -56,7 +57,13 @@ const QrCode = dynamic(() => import('@/components/QrCode'), { ssr: false })
* @param {*} param0 * @param {*} param0
* @returns * @returns
*/ */
const ShareButtons = ({ shareUrl, title, body, image }) => { const ShareButtons = ({ post }) => {
const router = useRouter()
const shareUrl = siteConfig('LINK') + router.asPath
const title = post.title || siteConfig('TITLE')
const image = post.pageCover
const body = post?.title + ' | ' + title + ' ' + shareUrl + ' ' + post?.summary
const services = siteConfig('POSTS_SHARE_SERVICES').split(',') const services = siteConfig('POSTS_SHARE_SERVICES').split(',')
const titleWithSiteInfo = title + ' | ' + siteConfig('TITLE') const titleWithSiteInfo = title + ' | ' + siteConfig('TITLE')
const { locale } = useGlobal() const { locale } = useGlobal()
@@ -336,7 +343,7 @@ const ShareButtons = ({ shareUrl, title, body, image }) => {
} }
if (singleService === 'qq') { if (singleService === 'qq') {
return <button key={singleService} className='cursor-pointer bg-blue-600 text-white rounded-full mx-1'> return <button key={singleService} className='cursor-pointer bg-blue-600 text-white rounded-full mx-1'>
<a target='_blank' rel='noreferrer' href={`http://connect.qq.com/widget/shareqq/index.html?url=${shareUrl}&sharesource=qzone&title=${title}&desc=${body}`} > <a target='_blank' rel='noreferrer' aria-label="Share by QQ" href={`http://connect.qq.com/widget/shareqq/index.html?url=${shareUrl}&sharesource=qzone&title=${title}&desc=${body}`} >
<i className='fab fa-qq w-8' /> <i className='fab fa-qq w-8' />
</a> </a>
</button> </button>
@@ -349,7 +356,7 @@ const ShareButtons = ({ shareUrl, title, body, image }) => {
<div className='absolute'> <div className='absolute'>
<div id='pop' className={(qrCodeShow ? 'opacity-100 ' : ' invisible opacity-0') + ' z-40 absolute bottom-10 -left-10 bg-white shadow-xl transition-all duration-200 text-center'}> <div id='pop' className={(qrCodeShow ? 'opacity-100 ' : ' invisible opacity-0') + ' z-40 absolute bottom-10 -left-10 bg-white shadow-xl transition-all duration-200 text-center'}>
<div className='p-2 mt-1 w-28 h-28'> <div className='p-2 mt-1 w-28 h-28'>
<QrCode value={shareUrl}/> { qrCodeShow && <QrCode value={shareUrl}/> }
</div> </div>
<span className='text-black font-semibold p-1 rounded-t-lg text-sm mx-auto mb-1'> <span className='text-black font-semibold p-1 rounded-t-lg text-sm mx-auto mb-1'>
{locale.COMMON.SCAN_QR_CODE} {locale.COMMON.SCAN_QR_CODE}

View File

@@ -39,7 +39,7 @@ const Tabs = ({ className, children }) => {
<ul className='flex justify-center space-x-5 pb-4 dark:text-gray-400 text-gray-600 overflow-auto'> <ul className='flex justify-center space-x-5 pb-4 dark:text-gray-400 text-gray-600 overflow-auto'>
{children.map((item, index) => { {children.map((item, index) => {
return <li key={index} return <li key={index}
className={(currentTab === index ? 'font-black border-b-2 border-red-400 text-red-400 animate__animated animate__jello ' : 'font-extralight cursor-pointer') + ' text-sm font-sans '} className={(currentTab === index ? 'font-black border-b-2 border-red-600 text-red-600 animate__animated animate__jello ' : 'font-extralight cursor-pointer') + ' text-sm font-sans '}
onClick={() => { onClick={() => {
tabClickHandle(index) tabClickHandle(index)
}}> }}>

View File

@@ -2,10 +2,7 @@ import { generateLocaleDict, initLocale } from './lang'
import { createContext, useContext, useEffect, useState } from 'react' import { createContext, useContext, useEffect, useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { THEMES, initDarkMode, saveDarkModeToCookies } from '@/themes/theme' import { THEMES, initDarkMode, saveDarkModeToCookies } from '@/themes/theme'
import { APPEARANCE, LANG, THEME } from 'blog.config'
import BLOG from '@/blog.config'
import NProgress from 'nprogress'
const GlobalContext = createContext() const GlobalContext = createContext()
/** /**
@@ -17,10 +14,10 @@ const GlobalContext = createContext()
export function GlobalContextProvider(props) { export function GlobalContextProvider(props) {
const { children, siteInfo, categoryOptions, tagOptions, NOTION_CONFIG } = props const { children, siteInfo, categoryOptions, tagOptions, NOTION_CONFIG } = props
const router = useRouter() const router = useRouter()
const [lang, updateLang] = useState(NOTION_CONFIG?.LANG || BLOG.LANG) // 默认语言 const [lang, updateLang] = useState(NOTION_CONFIG?.LANG || LANG) // 默认语言
const [locale, updateLocale] = useState(generateLocaleDict(NOTION_CONFIG?.LANG || BLOG.LANG)) // 默认语言 const [locale, updateLocale] = useState(generateLocaleDict(NOTION_CONFIG?.LANG || LANG)) // 默认语言
const [theme, setTheme] = useState(NOTION_CONFIG?.THEME || BLOG.THEME) // 默认博客主题 const [theme, setTheme] = useState(NOTION_CONFIG?.THEME || THEME) // 默认博客主题
const [isDarkMode, updateDarkMode] = useState(NOTION_CONFIG?.APPEARANCE || BLOG.APPEARANCE === 'dark') // 默认深色模式 const [isDarkMode, updateDarkMode] = useState(NOTION_CONFIG?.APPEARANCE || APPEARANCE === 'dark') // 默认深色模式
const [onLoading, setOnLoading] = useState(false) // 抓取文章数据 const [onLoading, setOnLoading] = useState(false) // 抓取文章数据
// 切换主题 // 切换主题
@@ -62,7 +59,6 @@ export function GlobalContextProvider(props) {
// 加载进度条 // 加载进度条
useEffect(() => { useEffect(() => {
const handleStart = (url) => { const handleStart = (url) => {
NProgress.start()
const { theme } = router.query const { theme } = router.query
if (theme && !url.includes(`theme=${theme}`)) { if (theme && !url.includes(`theme=${theme}`)) {
const newUrl = `${url}${url.includes('?') ? '&' : '?'}theme=${theme}` const newUrl = `${url}${url.includes('?') ? '&' : '?'}theme=${theme}`
@@ -71,7 +67,6 @@ export function GlobalContextProvider(props) {
setOnLoading(true) setOnLoading(true)
} }
const handleStop = () => { const handleStop = () => {
NProgress.done()
setOnLoading(false) setOnLoading(false)
} }

View File

@@ -7,6 +7,20 @@ import { memo } from 'react'
*/ */
export const isBrowser = typeof window !== 'undefined' export const isBrowser = typeof window !== 'undefined'
/**
* google机器人
* @returns
*/
export const isSearchEngineBot = () => {
if (typeof navigator === 'undefined') {
return false
}
// 获取用户代理字符串
const userAgent = navigator.userAgent;
// 使用正则表达式检测是否包含搜索引擎爬虫关键字
return /Googlebot|bingbot|Baidu/.test(userAgent)
}
/** /**
* 组件持久化 * 组件持久化
*/ */

View File

@@ -1,10 +1,11 @@
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
const { THEME } = require('./blog.config') const { THEME } = require('./blog.config')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const BLOG = require('./blog.config')
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: BLOG.BUNDLE_ANALYZER
})
/** /**
* 扫描指定目录下的文件夹名,用于获取当前有几个主题 * 扫描指定目录下的文件夹名,用于获取当前有几个主题

View File

@@ -6,41 +6,38 @@ import '@/styles/utility-patterns.css'
// core styles shared by all of react-notion-x (required) // core styles shared by all of react-notion-x (required)
import 'react-notion-x/src/styles.css' import 'react-notion-x/src/styles.css'
import '@/styles/notion.css' // 重写部分样式 import '@/styles/notion.css' // 重写部分样式
import 'aos/dist/aos.css' // You can also use <link> for styles
import { GlobalContextProvider } from '@/lib/global' import { GlobalContextProvider } from '@/lib/global'
import AOS from 'aos'
import 'aos/dist/aos.css' // You can also use <link> for styles
import { isBrowser, loadExternalResource } from '@/lib/utils' import { isBrowser, loadExternalResource } from '@/lib/utils'
import BLOG from '@/blog.config'
// 各种扩展插件 动画等 // 各种扩展插件 这个要阻塞引入
import ExternalPlugins from '@/components/ExternalPlugins' import ExternalPlugins from '@/components/ExternalPlugins'
// const ExternalPlugins = dynamic(() => import('@/components/ExternalPlugins')) import { CUSTOM_EXTERNAL_CSS, CUSTOM_EXTERNAL_JS, IMG_SHADOW } from '@/blog.config'
const MyApp = ({ Component, pageProps }) => { const MyApp = ({ Component, pageProps }) => {
// 自定义样式css和js引入 // 自定义样式css和js引入
if (isBrowser) { if (isBrowser) {
// 初始化AOS动画 // 初始化AOS动画
AOS.init()
// 静态导入本地自定义样式 // 静态导入本地自定义样式
loadExternalResource('/css/custom.css', 'css') loadExternalResource('/css/custom.css', 'css')
loadExternalResource('/js/custom.js', 'js') loadExternalResource('/js/custom.js', 'js')
// 自动添加图片阴影 // 自动添加图片阴影
if (BLOG.IMG_SHADOW) { if (IMG_SHADOW) {
loadExternalResource('/css/img-shadow.css', 'css') loadExternalResource('/css/img-shadow.css', 'css')
} }
// 导入外部自定义脚本 // 导入外部自定义脚本
if (BLOG.CUSTOM_EXTERNAL_JS && BLOG.CUSTOM_EXTERNAL_JS.length > 0) { if (CUSTOM_EXTERNAL_JS && CUSTOM_EXTERNAL_JS.length > 0) {
for (const url of BLOG.CUSTOM_EXTERNAL_JS) { for (const url of CUSTOM_EXTERNAL_JS) {
loadExternalResource(url, 'js') loadExternalResource(url, 'js')
} }
} }
// 导入外部自定义样式 // 导入外部自定义样式
if (BLOG.CUSTOM_EXTERNAL_CSS && BLOG.CUSTOM_EXTERNAL_CSS.length > 0) { if (CUSTOM_EXTERNAL_CSS && CUSTOM_EXTERNAL_CSS.length > 0) {
for (const url of BLOG.CUSTOM_EXTERNAL_CSS) { for (const url of CUSTOM_EXTERNAL_CSS) {
loadExternalResource(url, 'css') loadExternalResource(url, 'css')
} }
} }

View File

@@ -1130,9 +1130,10 @@ code[class*='language-'] {
transition: background 20ms ease-in 0s; transition: background 20ms ease-in 0s;
cursor: pointer; cursor: pointer;
width: 100%; width: 100%;
opacity: 0.9;
padding: 6px 2px; padding: 6px 2px;
font-size: 14px; font-size: 15px;
line-height: 1.2; line-height: 1.2;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@@ -4,29 +4,33 @@ import CONFIG from '../config'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { formatDateFmt } from '@/lib/formatDate' import { formatDateFmt } from '@/lib/formatDate'
export const ArticleInfo = (props) => { /**
* 文章描述
* @param {*} props
* @returns
*/
export default function ArticleInfo (props) {
const { post } = props const { post } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return ( return (
<section className="flex-wrap flex mt-2 text-gray-400 dark:text-gray-400 font-light leading-8"> <section className="mt-2 text-gray-600 dark:text-gray-400 leading-8">
<div> <h2
<h2 className="blog-item-title mb-5 font-bold text-black text-xl md:text-2xl no-underline">
className="blog-item-title mb-5 font-bold text-black text-xl md:text-2xl no-underline"> {post?.title}
{post?.title} </h2>
</h2>
{post?.type !== 'Page' && (<> <div className='flex flex-wrap text-gray-700 dark:text-gray-300'>
<div className="mb-4 text-sm text-gray-700 dark:text-gray-300"> {post?.type !== 'Page' && (
<div className="space-x-3 mr-4">
<span> <i className="fa-regular fa-user"></i> <a href={siteConfig('SIMPLE_AUTHOR_LINK', null, CONFIG)}>{siteConfig('AUTHOR')}</a></span> <span> <i className="fa-regular fa-user"></i> <a href={siteConfig('SIMPLE_AUTHOR_LINK', null, CONFIG)}>{siteConfig('AUTHOR')}</a></span>
<span> - <i className="fa-regular fa-clock"></i> {post?.publishDay}</span> <span> <i className="fa-regular fa-clock"></i> {post?.publishDay}</span>
{post?.category && <span> - <i className="fa-regular fa-folder"></i> <a href={`/category/${post?.category}`} className="hover:text-red-400 transition-all duration-200">{post?.category}</a></span>} {post?.category && <span> <i className="fa-regular fa-folder"></i> <a href={`/category/${post?.category}`} className="hover:text-red-400 transition-all duration-200">{post?.category}</a></span>}
{post?.tags && post?.tags?.length > 0 && post?.tags.map(t => <span key={t}> / <Link href={`/tag/${t}`}><span className=' hover:text-red-400 transition-all duration-200'>{t}</span></Link></span>)} {post?.tags && post?.tags?.length > 0 && post?.tags.map(t => <span key={t}> / <Link href={`/tag/${t}`}><span className=' hover:text-red-400 transition-all duration-200'>{t}</span></Link></span>)}
</div> </div>)}
</>)}
{post?.type !== 'Page' && (<> {post?.type !== 'Page' && (<div className=''>
<span>{locale.COMMON.POST_TIME}: <span>{locale.COMMON.POST_TIME}:
<Link <Link
href={`/archive#${formatDateFmt(post?.publishDate, 'yyyy-MM')}`} href={`/archive#${formatDateFmt(post?.publishDate, 'yyyy-MM')}`}
@@ -36,7 +40,7 @@ export const ArticleInfo = (props) => {
</Link> </Link>
</span> </span>
<span className='mr-2'>|</span> <span className='mr-2'>|</span>
<span className='mx-2 text-gray-400 dark:text-gray-500'> <span className='mx-2 dark:text-gray-500'>
{locale.COMMON.LAST_EDITED_TIME}: {post?.lastEditedDay} {locale.COMMON.LAST_EDITED_TIME}: {post?.lastEditedDay}
</span> </span>
<span className='mr-2'>|</span> <span className='mr-2'>|</span>
@@ -45,10 +49,9 @@ export const ArticleInfo = (props) => {
&nbsp; &nbsp;
<span className="mr-2 busuanzi_value_page_pv" /> <span className="mr-2 busuanzi_value_page_pv" />
</span> </span>
</>)} </div>)}
</div> </div>
</section> </section>
) )
} }

View File

@@ -8,7 +8,7 @@ import { useEffect, useRef } from 'react'
* @param validPassword(bool) 回调函数校验正确回调入参为true * @param validPassword(bool) 回调函数校验正确回调入参为true
* @returns * @returns
*/ */
export const ArticleLock = props => { export default function ArticleLock (props) {
const { validPassword } = props const { validPassword } = props
const { locale } = useGlobal() const { locale } = useGlobal()

View File

@@ -3,43 +3,60 @@ import CONFIG from '../config'
import TwikooCommentCount from '@/components/TwikooCommentCount' import TwikooCommentCount from '@/components/TwikooCommentCount'
import { formatDateFmt } from '@/lib/formatDate' import { formatDateFmt } from '@/lib/formatDate'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import LazyImage from '@/components/LazyImage'
export const BlogItem = props => { export const BlogItem = props => {
const { post } = props const { post } = props
const showPageCover = siteConfig('SIMPLE_POST_COVER_ENABLE', false, CONFIG)
return <div key={post.id} className="h-42 my-6 pb-12 border-b dark:border-gray-800" > return <div key={post.id} className="h-42 my-6 pb-12 border-b dark:border-gray-800" >
{/* 文章标题 */} {/* 文章标题 */}
<h2 className="mb-2"> <div className='flex'>
<Link <div className='article-cover h-full'>
href={`${siteConfig('SUB_PATH', '')}/${post.slug}`} {/* 图片封面 */}
className="blog-item-title font-bold text-black text-2xl menu-link"> {showPageCover && (
{post.title} <div className="overflow-hidden mr-2 w-56 h-full">
</Link> <Link href={`${siteConfig('SUB_PATH', '')}/${post.slug}`} passHref legacyBehavior>
</h2> <LazyImage src={post?.pageCoverThumbnail} className='w-56 h-full object-cover object-center group-hover:scale-110 duration-500' />
</Link>
</div>
)}
</div>
{/* 文章信息 */} <div className='article-info'>
<h2 className="mb-2">
<div className="mb-5 text-md text-gray-700 dark:text-gray-300 flex-wrap flex leading-6"> <Link
<div className='space-x-2'> href={`${siteConfig('SUB_PATH', '')}/${post.slug}`}
<span> <a href={siteConfig('SIMPLE_AUTHOR_LINK', null, CONFIG)} className='p-1 hover:text-red-400 transition-all duration-200'><i className="fa-regular fa-user"></i> {siteConfig('AUTHOR')}</a></span> className="blog-item-title font-bold text-black text-2xl menu-link">
<span> {post.title}
<Link className='p-1 hover:text-red-400 transition-all duration-200' href={`/archive#${formatDateFmt(post?.publishDate, 'yyyy-MM')}`}>
<i className="fa-regular fa-clock" /> {post.date?.start_date || post.createdTime}
</Link> </Link>
</span> </h2>
<span><TwikooCommentCount post={post} /></span>
</div>
<div> {/* 文章信息 */}
{post.category && <Link href={`/category/${post.category}`} className='p-1'> <span className="hover:text-red-400 transition-all duration-200"><i className="fa-regular fa-folder mr-0.5"/>{post.category}</span></Link>}
{post?.tags && post?.tags?.length > 0 && post?.tags.map(t => <Link key={t} href={`/tag/${t}`} className=' hover:text-red-400 transition-all duration-200'><span > /{t}</span></Link>)}
</div>
</div>
<div className="text-gray-700 dark:text-gray-300 leading-normal mb-6"> <div className="mb-5 text-md text-gray-700 dark:text-gray-300 flex-wrap flex leading-6">
{post.summary} <div className='space-x-2'>
{post.summary && <span>...</span>} <span> <a href={siteConfig('SIMPLE_AUTHOR_LINK', null, CONFIG)} className='p-1 hover:text-red-400 transition-all duration-200'><i className="fa-regular fa-user"></i> {siteConfig('AUTHOR')}</a></span>
<span>
<Link className='p-1 hover:text-red-400 transition-all duration-200' href={`/archive#${formatDateFmt(post?.publishDate, 'yyyy-MM')}`}>
<i className="fa-regular fa-clock" /> {post.date?.start_date || post.createdTime}
</Link>
</span>
<span><TwikooCommentCount post={post} /></span>
</div>
<div>
{post.category && <Link href={`/category/${post.category}`} className='p-1'> <span className="hover:text-red-400 transition-all duration-200"><i className="fa-regular fa-folder mr-0.5" />{post.category}</span></Link>}
{post?.tags && post?.tags?.length > 0 && post?.tags.map(t => <Link key={t} href={`/tag/${t}`} className=' hover:text-red-400 transition-all duration-200'><span > /{t}</span></Link>)}
</div>
</div>
<div className="text-gray-700 dark:text-gray-300 leading-normal mb-6">
{post.summary}
{post.summary && <span>...</span>}
</div>
</div>
</div> </div>
<div className='block'> <div className='block'>

View File

@@ -6,7 +6,12 @@ import { AdSlot } from '@/components/GoogleAdsense'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import CONFIG from '../config' import CONFIG from '../config'
export const BlogListPage = props => { /**
* 博客列表
* @param {*} props
* @returns
*/
export default function BlogListPage(props) {
const { page = 1, posts, postCount } = props const { page = 1, posts, postCount } = props
const router = useRouter() const router = useRouter()
const totalPage = Math.ceil(postCount / parseInt(siteConfig('POSTS_PER_PAGE'))) const totalPage = Math.ceil(postCount / parseInt(siteConfig('POSTS_PER_PAGE')))

View File

@@ -4,7 +4,12 @@ import throttle from 'lodash.throttle'
import { BlogItem } from './BlogItem' import { BlogItem } from './BlogItem'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
export const BlogListScroll = props => { /**
* 滚动博客列表
* @param {*} props
* @returns
*/
export default function BlogListScroll (props) {
const { posts } = props const { posts } = props
const { locale } = useGlobal() const { locale } = useGlobal()

View File

@@ -57,31 +57,33 @@ const Catalog = ({ post }) => {
} }
return <div className='px-3 '> return <div className='px-3 '>
<div className='dark:text-white'><i className='mr-1 fas fa-stream' />{locale.COMMON.TABLE_OF_CONTENTS}</div> <div className='dark:text-white mb-2'>
<i className='mr-1 fas fa-stream' />{locale.COMMON.TABLE_OF_CONTENTS}
</div>
<div className='overflow-y-auto overscroll-none max-h-36 lg:max-h-96 scroll-hidden' ref={tRef}> <div className='overflow-y-auto overscroll-none max-h-36 lg:max-h-96 scroll-hidden' ref={tRef}>
<nav className='h-full text-black'> <nav className='h-full text-black'>
{post?.toc?.map((tocItem) => { {post?.toc?.map((tocItem) => {
const id = uuidToId(tocItem.id) const id = uuidToId(tocItem.id)
return ( return (
<a <a
key={id} key={id}
href={`#${id}`} href={`#${id}`}
className={`notion-table-of-contents-item duration-300 transform font-light dark:text-gray-200 className={`notion-table-of-contents-item duration-300 transform dark:text-gray-200
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `} notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
> >
<span style={{ display: 'inline-block', marginLeft: tocItem.indentLevel * 16 }} <span style={{ display: 'inline-block', marginLeft: tocItem.indentLevel * 16 }}
className={`${activeSection === id && ' font-bold text-red-500 underline overflow-ellipsis truncate'}`} className={`${activeSection === id && ' font-bold text-red-600 underline overflow-ellipsis truncate'}`}
> >
{tocItem.text} {tocItem.text}
</span> </span>
</a> </a>
) )
})} })}
</nav> </nav>
</div>
</div> </div>
</div>
} }
export default Catalog export default Catalog

View File

@@ -1,7 +1,12 @@
import DarkModeButton from '@/components/DarkModeButton' import DarkModeButton from '@/components/DarkModeButton'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
export const Footer = (props) => { /**
* 页脚
* @param {*} props
* @returns
*/
export default function Footer (props) {
const d = new Date() const d = new Date()
const currentYear = d.getFullYear() const currentYear = d.getFullYear()
const since = siteConfig('SINCE') const since = siteConfig('SINCE')

View File

@@ -3,14 +3,15 @@ import Link from 'next/link'
import CONFIG from '../config' import CONFIG from '../config'
import SocialButton from './SocialButton' import SocialButton from './SocialButton'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { compressImage } from '@/lib/notion/mapImage'
/** /**
* 网站顶部 * 网站顶部
* @returns * @returns
*/ */
export const Header = (props) => { export default function Header (props) {
const { siteInfo } = props const { siteInfo } = props
const avatar = siteInfo?.icon || siteConfig('AVATAR') const avatar = compressImage(siteInfo?.icon || siteConfig('AVATAR'), 200)
return ( return (
<header className="text-center justify-between items-center px-6 bg-white h-80 dark:bg-black relative z-10"> <header className="text-center justify-between items-center px-6 bg-white h-80 dark:bg-black relative z-10">
@@ -18,8 +19,8 @@ export const Header = (props) => {
<Link href='/'> <Link href='/'>
{/* 可使用一张单图作为logo */} {/* 可使用一张单图作为logo */}
<div className='flex space-x-6'> <div className='flex space-x-6'>
<div className='hover:rotate-45 hover:scale-125 transform duration-200 cursor-pointer'> <div className='hover:rotate-45 hover:scale-125 transform duration-200 cursor-pointer justify-center items-center flex'>
<LazyImage src={avatar} className='rounded-full' width={130} height={130} alt={siteConfig('AUTHOR')} /> <LazyImage priority={true} src={avatar} className='rounded-full' width={100} height={100} alt={siteConfig('AUTHOR')} />
</div> </div>
<div className='flex-col flex justify-center'> <div className='flex-col flex justify-center'>

View File

@@ -7,7 +7,7 @@ import { MenuList } from './MenuList'
* @param {*} props * @param {*} props
* @returns * @returns
*/ */
export const NavBar = (props) => { export default function NavBar (props) {
const [showSearchInput, changeShowSearchInput] = useState(false) const [showSearchInput, changeShowSearchInput] = useState(false)
const router = useRouter() const router = useRouter()

View File

@@ -3,7 +3,12 @@ import Live2D from '@/components/Live2D'
import Announcement from './Announcement' import Announcement from './Announcement'
import Catalog from './Catalog' import Catalog from './Catalog'
export const SideBar = (props) => { /**
* 侧边栏
* @param {*} props
* @returns
*/
export default function SideBar (props) {
const { notice } = props const { notice } = props
return (<> return (<>

View File

@@ -6,8 +6,8 @@ import { siteConfig } from '@/lib/config'
* @constructor * @constructor
*/ */
const SocialButton = () => { const SocialButton = () => {
return <div className='w-52 justify-center flex-wrap flex'> return <div className='w-52 justify-center flex-wrap flex my-2'>
<div className='space-x-3 md:text-xl text-3xl text-gray-600 dark:text-gray-400 text-center'> <div className='space-x-5 md:text-xl text-3xl text-gray-600 dark:text-gray-400 text-center'>
{siteConfig('CONTACT_GITHUB') && <a target='_blank' rel='noreferrer' title={'github'} href={siteConfig('CONTACT_GITHUB')} > {siteConfig('CONTACT_GITHUB') && <a target='_blank' rel='noreferrer' title={'github'} href={siteConfig('CONTACT_GITHUB')} >
<i className='fab fa-github transform hover:scale-125 duration-150'/> <i className='fab fa-github transform hover:scale-125 duration-150'/>
</a>} </a>}

View File

@@ -5,11 +5,13 @@ import { siteConfig } from '@/lib/config'
* 网站顶部 提示栏 * 网站顶部 提示栏
* @returns * @returns
*/ */
export const TopBar = (props) => { export default function TopBar (props) {
if (siteConfig('SIMPLE_TOP_BAR_CONTENT', null, CONFIG)) { const content = siteConfig('SIMPLE_TOP_BAR_CONTENT', null, CONFIG)
if (content) {
return <header className="flex justify-center items-center bg-black dark:bg-hexo-black-gray"> return <header className="flex justify-center items-center bg-black dark:bg-hexo-black-gray">
<div id='top-bar-inner' className='max-w-9/10 w-full z-20'> <div id='top-bar-inner' className='max-w-9/10 w-full z-20'>
<div className='text-xs text-center float-left text-white z-50 leading-5 py-2.5' dangerouslySetInnerHTML={{ __html: siteConfig('SIMPLE_TOP_BAR_CONTENT', null, CONFIG) }}/> <div className='text-xs text-center float-left text-white z-50 leading-5 py-2.5' dangerouslySetInnerHTML={{ __html: content }}/>
</div> </div>
</header> </header>
} }

View File

@@ -9,6 +9,8 @@ const CONFIG = {
SIMPLE_POST_AD_ENABLE: process.env.NEXT_PUBLIC_SIMPLE_POST_AD_ENABLE || false, // 文章列表是否插入广告 SIMPLE_POST_AD_ENABLE: process.env.NEXT_PUBLIC_SIMPLE_POST_AD_ENABLE || false, // 文章列表是否插入广告
SIMPLE_POST_COVER_ENABLE: process.env.NEXT_PUBLIC_SIMPLE_POST_COVER_ENABLE || false, // 是否展示博客封面
// 菜单配置 // 菜单配置
SIMPLE_MENU_CATEGORY: true, // 显示分类 SIMPLE_MENU_CATEGORY: true, // 显示分类
SIMPLE_MENU_TAG: true, // 显示标签 SIMPLE_MENU_TAG: true, // 显示标签

View File

@@ -1,31 +1,35 @@
import CONFIG from './config' import CONFIG from './config'
import { BlogListPage } from './components/BlogListPage'
import { BlogListScroll } from './components/BlogListScroll'
import { useEffect } from 'react' import { useEffect } from 'react'
import { isBrowser, loadExternalResource } from '@/lib/utils' import { isBrowser } from '@/lib/utils'
import BlogArchiveItem from './components/BlogArchiveItem'
import { ArticleLock } from './components/ArticleLock'
import NotionPage from '@/components/NotionPage'
import { ArticleInfo } from './components/ArticleInfo'
import Comment from '@/components/Comment'
import ArticleAround from './components/ArticleAround'
import ShareBar from '@/components/ShareBar'
import { AdSlot } from '@/components/GoogleAdsense'
import Link from 'next/link'
import { TopBar } from './components/TopBar'
import { Header } from './components/Header'
import { NavBar } from './components/NavBar'
import { siteConfig } from '@/lib/config'
import { SideBar } from './components/SideBar'
import JumpToTopButton from './components/JumpToTopButton'
import { Footer } from './components/Footer'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import SearchInput from './components/SearchInput' import { AdSlot } from '@/components/GoogleAdsense'
import { siteConfig } from '@/lib/config'
import { Transition } from '@headlessui/react' import { Transition } from '@headlessui/react'
import Link from 'next/link'
import { Style } from './style' import { Style } from './style'
import replaceSearchResult from '@/components/Mark' import replaceSearchResult from '@/components/Mark'
import CommonHead from '@/components/CommonHead' import dynamic from 'next/dynamic'
import WWAds from '@/components/WWAds' import NotionPage from '@/components/NotionPage'
// const NotionPage = dynamic(() => import('@/components/NotionPage'), { ssr: false });
// 主题组件
const BlogListScroll = dynamic(() => import('./components/BlogListScroll'), { ssr: false });
const BlogArchiveItem = dynamic(() => import('./components/BlogArchiveItem'), { ssr: false });
const ArticleLock = dynamic(() => import('./components/ArticleLock'), { ssr: false });
const ArticleInfo = dynamic(() => import('./components/ArticleInfo'), { ssr: false });
const Comment = dynamic(() => import('@/components/Comment'), { ssr: false });
const ArticleAround = dynamic(() => import('./components/ArticleAround'), { ssr: false });
const ShareBar = dynamic(() => import('@/components/ShareBar'), { ssr: false });
const TopBar = dynamic(() => import('./components/TopBar'), { ssr: false });
const Header = dynamic(() => import('./components/Header'), { ssr: false });
const NavBar = dynamic(() => import('./components/NavBar'), { ssr: false });
const SideBar = dynamic(() => import('./components/SideBar'), { ssr: false });
const JumpToTopButton = dynamic(() => import('./components/JumpToTopButton'), { ssr: false });
const Footer = dynamic(() => import('./components/Footer'), { ssr: false });
const SearchInput = dynamic(() => import('./components/SearchInput'), { ssr: false });
const CommonHead = dynamic(() => import('@/components/CommonHead'), { ssr: false });
const WWAds = dynamic(() => import('@/components/WWAds'), { ssr: false });
const BlogListPage = dynamic(() => import('./components/BlogListPage'), { ssr: false })
/** /**
* 基础布局 * 基础布局
@@ -37,9 +41,6 @@ const LayoutBase = props => {
const { children, slotTop, meta } = props const { children, slotTop, meta } = props
const { onLoading } = useGlobal() const { onLoading } = useGlobal()
if (isBrowser) {
loadExternalResource('/css/theme-simple.css', 'css')
}
return ( return (
<div id='theme-simple' className='min-h-screen flex flex-col dark:text-gray-300 bg-white dark:bg-black'> <div id='theme-simple' className='min-h-screen flex flex-col dark:text-gray-300 bg-white dark:bg-black'>
{/* SEO相关 */} {/* SEO相关 */}