mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-31 07:26:43 +00:00
Merge branch 'main' into pr/sebastian0619/1657
This commit is contained in:
12
components/AOSAnimation.js
Normal file
12
components/AOSAnimation.js
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import dynamic from 'next/dynamic'
|
||||
import Tabs from '@/components/Tabs'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import { isBrowser, isSearchEngineBot } from '@/lib/utils'
|
||||
import { useRouter } from 'next/router'
|
||||
import Artalk from './Artalk'
|
||||
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_WEBMENTION_ENABLE = siteConfig('COMMENT_WEBMENTION_ENABLE')
|
||||
|
||||
if (isSearchEngineBot()) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 当连接中有特殊参数时跳转到评论区
|
||||
if (isBrowser && ('giscus' in router.query || router.query.target === 'comment')) {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -162,10 +162,12 @@ export default function CustomContextMenu(props) {
|
||||
{isDarkMode ? <i className="fa-regular fa-sun mr-2" /> : <i className="fa-regular fa-moon mr-2" />}
|
||||
<div className='whitespace-nowrap'> {isDarkMode ? locale.MENU.LIGHT_MODE : locale.MENU.DARK_MODE}</div>
|
||||
</div>
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH') && (
|
||||
<div onClick={handeChangeTheme} title={locale.MENU.THEME_SWITCH} className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className="fa-solid fa-palette mr-2" />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.THEME_SWITCH}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,12 @@ const katexSettings = {
|
||||
strict: false
|
||||
}
|
||||
|
||||
/**
|
||||
* 数学公式
|
||||
* @param {} param0
|
||||
* @returns
|
||||
*/
|
||||
export const Equation = ({ block, math, inline = false, className, ...rest }) => {
|
||||
// const { recordMap } = useNotionContext()
|
||||
math = math || getBlockTitle(block, null)
|
||||
if (!math) return null
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import dynamic from 'next/dynamic'
|
||||
import LA51 from './LA51'
|
||||
import WebWhiz from './Webwhiz'
|
||||
|
||||
const TwikooCommentCounter = dynamic(() => import('@/components/TwikooCommentCounter'), { ssr: false })
|
||||
@@ -11,7 +12,7 @@ const FlutteringRibbon = dynamic(() => import('@/components/FlutteringRibbon'),
|
||||
const Ribbon = dynamic(() => import('@/components/Ribbon'), { ssr: false })
|
||||
const Sakura = dynamic(() => import('@/components/Sakura'), { ssr: false })
|
||||
const StarrySky = dynamic(() => import('@/components/StarrySky'), { ssr: false })
|
||||
const DifyChatbot = dynamic(() => import('./DifyChatbot'), { ssr: false });
|
||||
const DifyChatbot = dynamic(() => import('@/components/DifyChatbot'), { ssr: false });
|
||||
const Analytics = dynamic(() => import('@vercel/analytics/react').then(async (m) => { return m.Analytics }), { ssr: false })
|
||||
const MusicPlayer = dynamic(() => import('@/components/Player'), { ssr: false })
|
||||
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
|
||||
@@ -23,6 +24,8 @@ 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 })
|
||||
const LoadingProgress = dynamic(() => import('@/components/LoadingProgress'), { ssr: false })
|
||||
const AosAnimation = dynamic(() => import('@/components/AOSAnimation'), { ssr: false })
|
||||
|
||||
/**
|
||||
* 各种插件脚本
|
||||
@@ -30,146 +33,201 @@ const AdBlockDetect = dynamic(() => import('@/components/AdBlockDetect'), { ssr:
|
||||
* @returns
|
||||
*/
|
||||
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')
|
||||
const DIFY_CHATBOT_ENABLED = siteConfig('DIFY_CHATBOT_ENABLED')
|
||||
|
||||
if (DISABLE_PLUGIN) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>
|
||||
{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('DIFY_CHATBOT_ENABLED')) && <DifyChatbot />}
|
||||
{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 />
|
||||
{THEME_SWITCH && <ThemeSwitch />}
|
||||
{DEBUG && <DebugPanel />}
|
||||
{ANALYTICS_ACKEE_TRACKER && <Ackee />}
|
||||
{ANALYTICS_GOOGLE_ID && <Gtag />}
|
||||
{ANALYTICS_VERCEL && <Analytics />}
|
||||
{ANALYTICS_BUSUANZI_ENABLE && <Busuanzi />}
|
||||
{ADSENSE_GOOGLE_ID && <GoogleAdsense />}
|
||||
{FACEBOOK_APP_ID && FACEBOOK_PAGE_ID && <Messenger />}
|
||||
{FIREWORKS && <Fireworks />}
|
||||
{SAKURA && <Sakura />}
|
||||
{STARRY_SKY && <StarrySky />}
|
||||
{MUSIC_PLAYER && <MusicPlayer />}
|
||||
{NEST && <Nest />}
|
||||
{FLUTTERINGRIBBON && <FlutteringRibbon />}
|
||||
{COMMENT_TWIKOO_COUNT_ENABLE && <TwikooCommentCounter {...props} />}
|
||||
{RIBBON && <Ribbon />}
|
||||
{DIFY_CHATBOT_ENABLED && <DifyChatbot />}
|
||||
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU && <CustomContextMenu {...props} />}
|
||||
{!CAN_COPY && <DisableCopy />}
|
||||
{WEB_WHIZ_ENABLED && <WebWhiz />}
|
||||
{AD_WWADS_BLOCK_DETECT && <AdBlockDetect />}
|
||||
<VConsole />
|
||||
<LoadingProgress />
|
||||
<AosAnimation />
|
||||
{ANALYTICS_51LA_ID && ANALYTICS_51LA_CK && <LA51/>}
|
||||
|
||||
{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')}",
|
||||
}
|
||||
`
|
||||
}} />
|
||||
</>)}
|
||||
{ANALYTICS_51LA_ID && ANALYTICS_51LA_CK && (<>
|
||||
<script id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js" defer/>
|
||||
{/* <script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
LA.init({id:"${ANALYTICS_51LA_ID}",ck:"${ANALYTICS_51LA_CK}",hashMode:true,autoTrack:true})
|
||||
`
|
||||
}} /> */}
|
||||
</>)}
|
||||
|
||||
{siteConfig('COMMENT_DAO_VOICE_ID') && (<>
|
||||
{/* DaoVoice 反馈 */}
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{CHATBASE_ID && (<>
|
||||
<script id={CHATBASE_ID} src="https://www.chatbase.co/embed.min.js" defer />
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__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")
|
||||
`
|
||||
}}
|
||||
/>
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
}}
|
||||
/>
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
daovoice('init', {
|
||||
app_id: "${siteConfig('COMMENT_DAO_VOICE_ID')}"
|
||||
app_id: "${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>}
|
||||
{AD_WWADS_ID && <script type="text/javascript" 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聊天室 */}
|
||||
{siteConfig('COMMENT_GITTER_ROOM') && (<>
|
||||
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer />
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{/* gitter聊天室 */}
|
||||
{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')}'
|
||||
room: '${COMMENT_GITTER_ROOM}'
|
||||
};
|
||||
`
|
||||
}} />
|
||||
</>)}
|
||||
}} />
|
||||
</>)}
|
||||
|
||||
{/* 百度统计 */}
|
||||
{siteConfig('ANALYTICS_BAIDU_ID') && (
|
||||
<script async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{/* 百度统计 */}
|
||||
{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')}";
|
||||
hm.src = "https://hm.baidu.com/hm.js?${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"));
|
||||
{/* 站长统计 */}
|
||||
{ANALYTICS_CNZZ_ID && (
|
||||
<script async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
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') && (<>
|
||||
<script async
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${siteConfig('ANALYTICS_GOOGLE_ID')}`}
|
||||
/>
|
||||
<script async
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{/* 谷歌统计 */}
|
||||
{ANALYTICS_GOOGLE_ID && (<>
|
||||
<script async
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${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')}', {
|
||||
gtag('config', '${ANALYTICS_GOOGLE_ID}', {
|
||||
page_path: window.location.pathname,
|
||||
});
|
||||
`
|
||||
}}
|
||||
/>
|
||||
</>)}
|
||||
}}
|
||||
/>
|
||||
</>)}
|
||||
|
||||
{/* Matomo 统计 */}
|
||||
{siteConfig('MATOMO_HOST_URL') && siteConfig('MATOMO_SITE_ID') && (
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{/* Matomo 统计 */}
|
||||
{MATOMO_HOST_URL && MATOMO_SITE_ID && (
|
||||
<script async dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
var _paq = window._paq = window._paq || [];
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="//${siteConfig('MATOMO_HOST_URL')}/";
|
||||
var u="//${MATOMO_HOST_URL}/";
|
||||
_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];
|
||||
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
`
|
||||
}}/>
|
||||
)}
|
||||
}} />
|
||||
)}
|
||||
|
||||
</>
|
||||
</>
|
||||
}
|
||||
|
||||
export default ExternalPlugin
|
||||
|
||||
@@ -1,14 +1,41 @@
|
||||
import { Component } from 'react'
|
||||
import { Component, useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
|
||||
export default function Messenger() {
|
||||
return <MessengerCustomerChat
|
||||
pageId={siteConfig('FACEBOOK_PAGE_ID')}
|
||||
appId={siteConfig('FACEBOOK_APP_ID')}
|
||||
language={siteConfig('LANG').replace('-', '_')}
|
||||
shouldShowDialog={true}
|
||||
/>
|
||||
const pageId = siteConfig('FACEBOOK_PAGE_ID')
|
||||
const appId = siteConfig('FACEBOOK_APP_ID')
|
||||
const language = siteConfig('LANG').replace('-', '_')
|
||||
|
||||
// 新增一个状态变量用于追踪是否已经滚动过
|
||||
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() {
|
||||
this.setFbAsyncInit()
|
||||
this.reloadSDKAsynchronously()
|
||||
@@ -34,19 +61,19 @@ class MessengerCustomerChat extends Component {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
prevProps.pageId !== this.props.pageId ||
|
||||
prevProps.appId !== this.props.appId ||
|
||||
prevProps.shouldShowDialog !== this.props.shouldShowDialog ||
|
||||
prevProps.htmlRef !== this.props.htmlRef ||
|
||||
prevProps.minimized !== this.props.minimized ||
|
||||
prevProps.themeColor !== this.props.themeColor ||
|
||||
prevProps.loggedInGreeting !== this.props.loggedInGreeting ||
|
||||
prevProps.loggedOutGreeting !== this.props.loggedOutGreeting ||
|
||||
prevProps.greetingDialogDisplay !== this.props.greetingDialogDisplay ||
|
||||
prevProps.greetingDialogDelay !== this.props.greetingDialogDelay ||
|
||||
prevProps.autoLogAppEvents !== this.props.autoLogAppEvents ||
|
||||
prevProps.xfbml !== this.props.xfbml ||
|
||||
prevProps.version !== this.props.version ||
|
||||
prevProps.language !== this.props.language
|
||||
prevProps.appId !== this.props.appId ||
|
||||
prevProps.shouldShowDialog !== this.props.shouldShowDialog ||
|
||||
prevProps.htmlRef !== this.props.htmlRef ||
|
||||
prevProps.minimized !== this.props.minimized ||
|
||||
prevProps.themeColor !== this.props.themeColor ||
|
||||
prevProps.loggedInGreeting !== this.props.loggedInGreeting ||
|
||||
prevProps.loggedOutGreeting !== this.props.loggedOutGreeting ||
|
||||
prevProps.greetingDialogDisplay !== this.props.greetingDialogDisplay ||
|
||||
prevProps.greetingDialogDelay !== this.props.greetingDialogDelay ||
|
||||
prevProps.autoLogAppEvents !== this.props.autoLogAppEvents ||
|
||||
prevProps.xfbml !== this.props.xfbml ||
|
||||
prevProps.version !== this.props.version ||
|
||||
prevProps.language !== this.props.language
|
||||
) {
|
||||
this.setFbAsyncInit()
|
||||
this.reloadSDKAsynchronously()
|
||||
@@ -60,8 +87,8 @@ class MessengerCustomerChat extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
* 初始化
|
||||
*/
|
||||
setFbAsyncInit() {
|
||||
const { appId, autoLogAppEvents, xfbml, version } = this.props
|
||||
|
||||
@@ -80,18 +107,18 @@ class MessengerCustomerChat extends Component {
|
||||
loadSDKAsynchronously() {
|
||||
const { language } = this.props;
|
||||
/* eslint-disable */
|
||||
(function (d, s, id) {
|
||||
var js,
|
||||
fjs = d.getElementsByTagName(s)[0];
|
||||
if (d.getElementById(id)) {
|
||||
return;
|
||||
}
|
||||
js = d.createElement(s);
|
||||
js.id = id;
|
||||
js.src = `https://connect.facebook.net/${language}/sdk/xfbml.customerchat.js`;
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
})(document, 'script', 'facebook-jssdk');
|
||||
/* eslint-enable */
|
||||
(function (d, s, id) {
|
||||
var js,
|
||||
fjs = d.getElementsByTagName(s)[0];
|
||||
if (d.getElementById(id)) {
|
||||
return;
|
||||
}
|
||||
js = d.createElement(s);
|
||||
js.id = id;
|
||||
js.src = `https://connect.facebook.net/${language}/sdk/xfbml.customerchat.js`;
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
})(document, 'script', 'facebook-jssdk');
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
removeFacebookSDK() {
|
||||
@@ -147,25 +174,25 @@ class MessengerCustomerChat extends Component {
|
||||
|
||||
const refAttribute = htmlRef !== undefined ? `ref="${htmlRef}"` : ''
|
||||
const minimizedAttribute =
|
||||
minimized !== undefined ? `minimized="${minimized}"` : ''
|
||||
minimized !== undefined ? `minimized="${minimized}"` : ''
|
||||
const themeColorAttribute =
|
||||
themeColor !== undefined ? `theme_color="${themeColor}"` : ''
|
||||
themeColor !== undefined ? `theme_color="${themeColor}"` : ''
|
||||
const loggedInGreetingAttribute =
|
||||
loggedInGreeting !== undefined
|
||||
? `logged_in_greeting="${loggedInGreeting}"`
|
||||
: ''
|
||||
loggedInGreeting !== undefined
|
||||
? `logged_in_greeting="${loggedInGreeting}"`
|
||||
: ''
|
||||
const loggedOutGreetingAttribute =
|
||||
loggedOutGreeting !== undefined
|
||||
? `logged_out_greeting="${loggedOutGreeting}"`
|
||||
: ''
|
||||
loggedOutGreeting !== undefined
|
||||
? `logged_out_greeting="${loggedOutGreeting}"`
|
||||
: ''
|
||||
const greetingDialogDisplayAttribute =
|
||||
greetingDialogDisplay !== undefined
|
||||
? `greeting_dialog_display="${greetingDialogDisplay}"`
|
||||
: ''
|
||||
greetingDialogDisplay !== undefined
|
||||
? `greeting_dialog_display="${greetingDialogDisplay}"`
|
||||
: ''
|
||||
const greetingDialogDelayAttribute =
|
||||
greetingDialogDelay !== undefined
|
||||
? `greeting_dialog_delay="${greetingDialogDelay}"`
|
||||
: ''
|
||||
greetingDialogDelay !== undefined
|
||||
? `greeting_dialog_delay="${greetingDialogDelay}"`
|
||||
: ''
|
||||
|
||||
return {
|
||||
__html: `<div
|
||||
@@ -192,8 +219,8 @@ class MessengerCustomerChat extends Component {
|
||||
const element = event.target
|
||||
if (
|
||||
element.className &&
|
||||
typeof element.className === 'string' &&
|
||||
element.className.includes('fb_dialog')
|
||||
typeof element.className === 'string' &&
|
||||
element.className.includes('fb_dialog')
|
||||
) {
|
||||
this.controlPlugin()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import KaTeX from 'katex'
|
||||
import React from 'react'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
|
||||
/**
|
||||
* 数学公式
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const TeX = ({
|
||||
children,
|
||||
math,
|
||||
@@ -13,9 +18,9 @@ const TeX = ({
|
||||
}) => {
|
||||
const Component = asComponent || (block ? 'div' : 'span')
|
||||
const content = (children ?? math)
|
||||
const [state, setState] = React.useState({ innerHtml: '' })
|
||||
const [state, setState] = useState({ innerHtml: '' })
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
try {
|
||||
const innerHtml = KaTeX.renderToString(content, {
|
||||
displayMode: true,
|
||||
@@ -50,4 +55,4 @@ const TeX = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(TeX)
|
||||
export default memo(TeX)
|
||||
|
||||
18
components/LA51.js
Normal file
18
components/LA51.js
Normal 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 <></>
|
||||
}
|
||||
@@ -4,6 +4,10 @@ import { useGlobal } from '@/lib/global'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
/**
|
||||
* 网页动画
|
||||
* @returns
|
||||
*/
|
||||
export default function Live2D() {
|
||||
const { theme, switchTheme } = useGlobal()
|
||||
const showPet = JSON.parse(siteConfig('WIDGET_PET'))
|
||||
|
||||
29
components/LoadingProgress.js
Normal file
29
components/LoadingProgress.js
Normal 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])
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
import { NotionRenderer } from 'react-notion-x'
|
||||
import dynamic from 'next/dynamic'
|
||||
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 { useEffect, useRef } from 'react'
|
||||
import 'katex/dist/katex.min.css'
|
||||
import { mapImgUrl } from '@/lib/notion/mapImage'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
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(() =>
|
||||
import('react-notion-x/build/third-party/code').then(async (m) => {
|
||||
@@ -15,6 +20,7 @@ const Code = dynamic(() =>
|
||||
}), { ssr: false }
|
||||
)
|
||||
|
||||
// 公式
|
||||
const Equation = dynamic(() =>
|
||||
import('@/components/Equation').then(async (m) => {
|
||||
// 化学方程式
|
||||
@@ -36,6 +42,13 @@ const PrismMac = dynamic(() => import('@/components/PrismMac'), {
|
||||
ssr: false
|
||||
})
|
||||
|
||||
/**
|
||||
* tweet嵌入
|
||||
*/
|
||||
const TweetEmbed = dynamic(() => import('react-tweet-embed'), {
|
||||
ssr: false
|
||||
})
|
||||
|
||||
const Collection = dynamic(() =>
|
||||
import('react-notion-x/build/third-party/collection').then((m) => m.Collection), { ssr: true }
|
||||
)
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
import ShareButtons from './ShareButtons'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const ShareButtons = dynamic(() => import('@/components/ShareButtons'), { ssr: false })
|
||||
|
||||
/**
|
||||
* 分享栏
|
||||
* @param {} param0
|
||||
* @returns
|
||||
*/
|
||||
const ShareBar = ({ post }) => {
|
||||
const router = useRouter()
|
||||
|
||||
if (!JSON.parse(siteConfig('POST_SHARE_BAR_ENABLE')) || !post || post?.type !== 'Post') {
|
||||
return <></>
|
||||
}
|
||||
|
||||
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 +
|
||||
' | ' +
|
||||
siteConfig('TITLE') +
|
||||
' ' +
|
||||
shareUrl +
|
||||
' ' +
|
||||
post?.summary
|
||||
} />
|
||||
<ShareButtons post={post} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
|
||||
import {
|
||||
@@ -56,7 +57,13 @@ const QrCode = dynamic(() => import('@/components/QrCode'), { ssr: false })
|
||||
* @param {*} param0
|
||||
* @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 titleWithSiteInfo = title + ' | ' + siteConfig('TITLE')
|
||||
const { locale } = useGlobal()
|
||||
@@ -336,7 +343,7 @@ const ShareButtons = ({ shareUrl, title, body, image }) => {
|
||||
}
|
||||
if (singleService === 'qq') {
|
||||
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' />
|
||||
</a>
|
||||
</button>
|
||||
@@ -349,7 +356,7 @@ const ShareButtons = ({ shareUrl, title, body, image }) => {
|
||||
<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 className='p-2 mt-1 w-28 h-28'>
|
||||
<QrCode value={shareUrl}/>
|
||||
{ qrCodeShow && <QrCode value={shareUrl}/> }
|
||||
</div>
|
||||
<span className='text-black font-semibold p-1 rounded-t-lg text-sm mx-auto mb-1'>
|
||||
{locale.COMMON.SCAN_QR_CODE}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useState } from 'react';
|
||||
|
||||
/**
|
||||
* Tabs切换标签
|
||||
@@ -6,59 +6,37 @@ import React, { useState } from 'react'
|
||||
* @returns
|
||||
*/
|
||||
const Tabs = ({ className, children }) => {
|
||||
const [currentTab, setCurrentTab] = useState(0)
|
||||
const [currentTab, setCurrentTab] = useState(0);
|
||||
|
||||
if (!children) {
|
||||
return <></>
|
||||
const validChildren = children.filter(c => c);
|
||||
|
||||
if (validChildren.length === 0) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
children = children.filter(c => c && c !== '')
|
||||
|
||||
let count = 0
|
||||
children.forEach(e => {
|
||||
if (e) {
|
||||
count++
|
||||
}
|
||||
})
|
||||
|
||||
if (count === 0) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
if (count === 1) {
|
||||
return <section className={'duration-200 ' + className}>
|
||||
{children}
|
||||
</section>
|
||||
}
|
||||
|
||||
function tabClickHandle(i) {
|
||||
setCurrentTab(i)
|
||||
}
|
||||
|
||||
return <div className={'mb-5 duration-200 ' + className}>
|
||||
<ul className='flex justify-center space-x-5 pb-4 dark:text-gray-400 text-gray-600 overflow-auto'>
|
||||
{children.map((item, 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 '}
|
||||
onClick={() => {
|
||||
tabClickHandle(index)
|
||||
}}>
|
||||
{item?.key}
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
<div>
|
||||
{children.map((item, index) => {
|
||||
return <section key={index}
|
||||
data-aos="fade-up"
|
||||
data-aos-duration="300"
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom">
|
||||
{currentTab === index && item}
|
||||
</section>
|
||||
})}
|
||||
</div>
|
||||
return (
|
||||
<div className={`mb-5 duration-200 ${className}`}>
|
||||
<ul className="flex justify-center space-x-5 pb-4 dark:text-gray-400 text-gray-600 overflow-auto">
|
||||
{validChildren.map((item, index) => (
|
||||
<li key={index}
|
||||
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={() => setCurrentTab(index)}>
|
||||
{item.key}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{/* 标签切换的时候不销毁 DOM 元素,使用 CSS 样式进行隐藏 */}
|
||||
<div>
|
||||
{validChildren.map((item, index) => (
|
||||
<section
|
||||
key={index}
|
||||
className={`${currentTab === index ? 'opacity-100 static h-auto' : 'opacity-0 absolute h-0'}`}>
|
||||
{item}
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Tabs
|
||||
export default Tabs;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useEffect } from 'react'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
/**
|
||||
* 评论插件
|
||||
@@ -9,28 +10,48 @@ import { useEffect } from 'react'
|
||||
* @constructor
|
||||
*/
|
||||
const Utterances = ({ issueTerm, layout }) => {
|
||||
const { isDarkMode } = useGlobal()
|
||||
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const theme =
|
||||
siteConfig('APPEARANCE') === 'auto'
|
||||
? 'preferred-color-scheme'
|
||||
: siteConfig('APPEARANCE') === 'light'
|
||||
? 'github-light'
|
||||
: 'github-dark'
|
||||
const script = document.createElement('script')
|
||||
const anchor = document.getElementById('comments')
|
||||
script.setAttribute('src', 'https://utteranc.es/client.js')
|
||||
script.setAttribute('crossorigin', 'anonymous')
|
||||
script.setAttribute('async', true)
|
||||
script.setAttribute('repo', siteConfig('COMMENT_UTTERRANCES_REPO'))
|
||||
script.setAttribute('issue-term', 'title')
|
||||
script.setAttribute('theme', theme)
|
||||
anchor.appendChild(script)
|
||||
const script = document.createElement('script');
|
||||
const anchor = document.getElementById('comments');
|
||||
script.onload = () => setLoading(false);
|
||||
script.setAttribute('src', 'https://utteranc.es/client.js');
|
||||
script.setAttribute('crossorigin', 'anonymous');
|
||||
script.setAttribute('async', true);
|
||||
script.setAttribute('repo', siteConfig('COMMENT_UTTERRANCES_REPO'));
|
||||
script.setAttribute('issue-term', 'title');
|
||||
// 初始主题
|
||||
script.setAttribute('theme', isDarkMode ? 'github-dark' : 'github-light');
|
||||
anchor.appendChild(script);
|
||||
|
||||
return () => {
|
||||
anchor.innerHTML = ''
|
||||
// anchor.innerHTML = ''
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// 直接设置 iframe 的类来改变主题,不重新加载脚本
|
||||
const iframe = document.querySelector('iframe.utterances-frame');
|
||||
if (iframe) {
|
||||
iframe.contentWindow.postMessage({
|
||||
type: 'set-theme',
|
||||
theme: isDarkMode ? 'github-dark' : 'github-light'
|
||||
}, 'https://utteranc.es');
|
||||
}
|
||||
})
|
||||
return <div id="comments" className='utterances' >
|
||||
</div>
|
||||
}, [isDarkMode]);
|
||||
|
||||
return (
|
||||
<div id="comments" className='utterances'>
|
||||
{isLoading && (
|
||||
<div className="flex justify-center items-center m-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-2 border-indigo-400 border-t-transparent"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Utterances
|
||||
|
||||
Reference in New Issue
Block a user