Merge branch 'main' into pr/Olimiya/1723

This commit is contained in:
tangly1024.com
2024-01-30 18:34:19 +08:00
60 changed files with 985 additions and 684 deletions

View File

@@ -1,5 +1,5 @@
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables # 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
NEXT_PUBLIC_VERSION=4.1.2 NEXT_PUBLIC_VERSION=4.2.0
# 可在此添加环境变量,去掉最左边的(# )注释即可 # 可在此添加环境变量,去掉最左边的(# )注释即可

View File

@@ -90,6 +90,7 @@ const BLOG = {
// END ************网站字体***************** // END ************网站字体*****************
CAN_COPY: process.env.NEXT_PUBLIC_CAN_COPY || true, // 是否允许复制页面内容 默认允许如果设置为false、则全栈禁止复制内容。 CAN_COPY: process.env.NEXT_PUBLIC_CAN_COPY || true, // 是否允许复制页面内容 默认允许如果设置为false、则全栈禁止复制内容。
CUSTOM_RIGHT_CLICK_CONTEXT_MENU: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU || true, // 自定义右键菜单,覆盖系统菜单 CUSTOM_RIGHT_CLICK_CONTEXT_MENU: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU || true, // 自定义右键菜单,覆盖系统菜单
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH || true,
// 自定义外部脚本,外部样式 // 自定义外部脚本,外部样式
CUSTOM_EXTERNAL_JS: [''], // e.g. ['http://xx.com/script.js','http://xx.com/script.js'] CUSTOM_EXTERNAL_JS: [''], // e.g. ['http://xx.com/script.js','http://xx.com/script.js']
@@ -184,13 +185,20 @@ const BLOG = {
STARRY_SKY: process.env.NEXT_PUBLIC_STARRY_SKY || false, // 开关 STARRY_SKY: process.env.NEXT_PUBLIC_STARRY_SKY || false, // 开关
// ********挂件组件相关******** // ********挂件组件相关********
// AI 文章摘要生成 @see https://docs_s.tianli0.top/
TianliGPT_CSS: process.env.NEXT_PUBLIC_TIANLI_GPT_CSS || 'https://cdn1.tianli0.top/gh/zhheo/Post-Abstract-AI@0.15.2/tianli_gpt.css',
TianliGPT_JS: process.env.NEXT_PUBLIC_TIANLI_GPT_JS || 'https://cdn1.tianli0.top/gh/zhheo/Post-Abstract-AI@0.15.2/tianli_gpt.js',
TianliGPT_KEY: process.env.NEXT_PUBLIC_TIANLI_GPT_KEY || '',
// Chatbase 是否显示chatbase机器人 https://www.chatbase.co/ // Chatbase 是否显示chatbase机器人 https://www.chatbase.co/
CHATBASE_ID: process.env.NEXT_PUBLIC_CHATBASE_ID || null, CHATBASE_ID: process.env.NEXT_PUBLIC_CHATBASE_ID || null,
// WebwhizAI 机器人 @see https://github.com/webwhiz-ai/webwhiz // WebwhizAI 机器人 @see https://github.com/webwhiz-ai/webwhiz
WEB_WHIZ_ENABLED: process.env.NEXT_PUBLIC_WEB_WHIZ_ENABLED || false, // 是否显示 WEB_WHIZ_ENABLED: process.env.NEXT_PUBLIC_WEB_WHIZ_ENABLED || false, // 是否显示
WEB_WHIZ_BASE_URL: process.env.NEXT_PUBLIC_WEB_WHIZ_BASE_URL || 'https://api.webwhiz.ai', // 可以自建服务器 WEB_WHIZ_BASE_URL: process.env.NEXT_PUBLIC_WEB_WHIZ_BASE_URL || 'https://api.webwhiz.ai', // 可以自建服务器
WEB_WHIZ_CHAT_BOT_ID: process.env.NEXT_PUBLIC_WEB_WHIZ_CHAT_BOT_ID || null, // 在后台获取ID WEB_WHIZ_CHAT_BOT_ID: process.env.NEXT_PUBLIC_WEB_WHIZ_CHAT_BOT_ID || null, // 在后台获取ID
DIFY_CHATBOT_ENABLED: process.env.NEXT_PUBLIC_DIFY_CHATBOT_ENABLED || false,
DIFY_CHATBOT_BASE_URL: process.env.NEXT_PUBLIC_DIFY_CHATBOT_BASE_URL || '',
DIFY_CHATBOT_TOKEN: process.env.NEXT_PUBLIC_DIFY_CHATBOT_TOKEN || '',
// 悬浮挂件 // 悬浮挂件
WIDGET_PET: process.env.NEXT_PUBLIC_WIDGET_PET || true, // 是否显示宠物挂件 WIDGET_PET: process.env.NEXT_PUBLIC_WIDGET_PET || true, // 是否显示宠物挂件
WIDGET_PET_LINK: WIDGET_PET_LINK:
@@ -236,6 +244,8 @@ const BLOG = {
// ********挂件组件相关******** // ********挂件组件相关********
// ----> 评论互动 可同时开启多个支持 WALINE VALINE GISCUS CUSDIS UTTERRANCES GITALK // ----> 评论互动 可同时开启多个支持 WALINE VALINE GISCUS CUSDIS UTTERRANCES GITALK
COMMENT_HIDE_SINGLE_TAB: process.env.NEXT_PUBLIC_COMMENT_HIDE_SINGLE_TAB || false, //Whether hide the tab when there's no tabs. 只有一个评论组件时是否隐藏切换组件的标签页
// artalk 评论插件 // artalk 评论插件
COMMENT_ARTALK_SERVER: process.env.NEXT_PUBLIC_COMMENT_ARTALK_SERVER || '', // ArtalkServert后端地址 https://artalk.js.org/guide/deploy.html COMMENT_ARTALK_SERVER: process.env.NEXT_PUBLIC_COMMENT_ARTALK_SERVER || '', // ArtalkServert后端地址 https://artalk.js.org/guide/deploy.html
COMMENT_ARTALK_JS: process.env.NEXT_PUBLIC_COMMENT_ARTALK_JS || 'https://cdnjs.cloudflare.com/ajax/libs/artalk/2.5.5/Artalk.js', // ArtalkServert js cdn COMMENT_ARTALK_JS: process.env.NEXT_PUBLIC_COMMENT_ARTALK_JS || 'https://cdnjs.cloudflare.com/ajax/libs/artalk/2.5.5/Artalk.js', // ArtalkServert js cdn
@@ -342,6 +352,9 @@ const BLOG = {
SEO_BAIDU_SITE_VERIFICATION: SEO_BAIDU_SITE_VERIFICATION:
process.env.NEXT_PUBLIC_SEO_BAIDU_SITE_VERIFICATION || '', // Remove the value or replace it with your own google site verification code process.env.NEXT_PUBLIC_SEO_BAIDU_SITE_VERIFICATION || '', // Remove the value or replace it with your own google site verification code
// 微软 Clarity 站点分析
CLARITY_ID: process.env.NEXT_PUBLIC_CLARITY_ID || null , // 只需要复制Clarity脚本中的ID部分ID是一个十位的英文数字组合
// <---- 站点统计 // <---- 站点统计
// START---->营收相关 // START---->营收相关

View File

@@ -1,11 +1,9 @@
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils' import { loadExternalResource } from '@/lib/utils'
// import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react' import { useEffect } from 'react'
/** /**
* Giscus评论 @see https://giscus.app/zh-CN * Artalk 自托管评论系统 @see https://artalk.js.org/
* Contribute by @txs https://github.com/txs/NotionNext/commit/1bf7179d0af21fb433e4c7773504f244998678cb
* @returns {JSX.Element} * @returns {JSX.Element}
* @constructor * @constructor
*/ */

View File

@@ -2,14 +2,14 @@ import busuanzi from '@/lib/busuanzi'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
// import { useRouter } from 'next/router' // import { useRouter } from 'next/router'
import React from 'react' import { useEffect } from 'react'
let path = '' let path = ''
export default function Busuanzi () { export default function Busuanzi () {
const { theme } = useGlobal() const { theme } = useGlobal()
const Router = useRouter() const router = useRouter()
Router.events.on('routeChangeComplete', (url, option) => { router.events.on('routeChangeComplete', (url, option) => {
if (url !== path) { if (url !== path) {
path = url path = url
busuanzi.fetch() busuanzi.fetch()
@@ -17,7 +17,7 @@ export default function Busuanzi () {
}) })
// 更换主题时更新 // 更换主题时更新
React.useEffect(() => { useEffect(() => {
if (theme) { if (theme) {
busuanzi.fetch() busuanzi.fetch()
} }

View File

@@ -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" />} {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 className='whitespace-nowrap'> {isDarkMode ? locale.MENU.LIGHT_MODE : locale.MENU.DARK_MODE}</div>
</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'> <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" /> <i className="fa-solid fa-palette mr-2" />
<div className='whitespace-nowrap'>{locale.MENU.THEME_SWITCH}</div> <div className='whitespace-nowrap'>{locale.MENU.THEME_SWITCH}</div>
</div> </div>
)}
</div> </div>
</div> </div>

32
components/DifyChatbot.js Normal file
View File

@@ -0,0 +1,32 @@
import { useEffect } from 'react';
import { siteConfig } from '@/lib/config';
export default function DifyChatbot() {
useEffect(() => {
// 这里使用 siteConfig() 函数调用来获取配置值
if (!siteConfig('DIFY_CHATBOT_ENABLED')) {
return;
}
// 配置 DifyChatbot同样需要调用 siteConfig() 获取相应的配置值
window.difyChatbotConfig = {
token: siteConfig('DIFY_CHATBOT_TOKEN'),
baseUrl: siteConfig('DIFY_CHATBOT_BASE_URL')
};
// 加载 DifyChatbot 脚本
const script = document.createElement('script');
script.src = `${siteConfig('DIFY_CHATBOT_BASE_URL')}/embed.min.js`; // 注意调用 siteConfig()
script.id = siteConfig('DIFY_CHATBOT_TOKEN'); // 注意调用 siteConfig()
script.defer = true;
document.body.appendChild(script);
return () => {
// 在组件卸载时清理 script 标签
const existingScript = document.getElementById(siteConfig('DIFY_CHATBOT_TOKEN')); // 注意调用 siteConfig()
if (existingScript) document.body.removeChild(existingScript);
};
}, []); // 注意依赖数组为空,意味着脚本将仅在加载页面时执行一次
return null;
}

View File

@@ -2,6 +2,11 @@ import { siteConfig } from '@/lib/config'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import LA51 from './LA51' import LA51 from './LA51'
import WebWhiz from './Webwhiz' import WebWhiz from './Webwhiz'
import TianLiGPT from './TianliGPT'
import { GlobalStyle } from './GlobalStyle'
import { CUSTOM_EXTERNAL_CSS, CUSTOM_EXTERNAL_JS, IMG_SHADOW } from '@/blog.config'
import { isBrowser, loadExternalResource } from '@/lib/utils'
const TwikooCommentCounter = dynamic(() => import('@/components/TwikooCommentCounter'), { ssr: false }) const TwikooCommentCounter = dynamic(() => import('@/components/TwikooCommentCounter'), { ssr: false })
const DebugPanel = dynamic(() => import('@/components/DebugPanel'), { ssr: false }) const DebugPanel = dynamic(() => import('@/components/DebugPanel'), { ssr: false })
@@ -12,6 +17,7 @@ const FlutteringRibbon = dynamic(() => import('@/components/FlutteringRibbon'),
const Ribbon = dynamic(() => import('@/components/Ribbon'), { ssr: false }) const Ribbon = dynamic(() => import('@/components/Ribbon'), { ssr: false })
const Sakura = dynamic(() => import('@/components/Sakura'), { ssr: false }) const Sakura = dynamic(() => import('@/components/Sakura'), { ssr: false })
const StarrySky = dynamic(() => import('@/components/StarrySky'), { ssr: false }) const StarrySky = dynamic(() => import('@/components/StarrySky'), { 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 Analytics = dynamic(() => import('@vercel/analytics/react').then(async (m) => { return m.Analytics }), { ssr: false })
const MusicPlayer = dynamic(() => import('@/components/Player'), { ssr: false }) const MusicPlayer = dynamic(() => import('@/components/Player'), { ssr: false })
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false }) const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
@@ -69,12 +75,47 @@ const ExternalPlugin = (props) => {
const MATOMO_SITE_ID = siteConfig('MATOMO_SITE_ID') const MATOMO_SITE_ID = siteConfig('MATOMO_SITE_ID')
const ANALYTICS_51LA_ID = siteConfig('ANALYTICS_51LA_ID') const ANALYTICS_51LA_ID = siteConfig('ANALYTICS_51LA_ID')
const ANALYTICS_51LA_CK = siteConfig('ANALYTICS_51LA_CK') const ANALYTICS_51LA_CK = siteConfig('ANALYTICS_51LA_CK')
const DIFY_CHATBOT_ENABLED = siteConfig('DIFY_CHATBOT_ENABLED')
const TIANLI_KEY = siteConfig('TianliGPT_KEY')
const GLOBAL_JS = siteConfig('GLOBAL_JS')
const CLARITY_ID = siteConfig('CLARITY_ID')
// 自定义样式css和js引入
if (isBrowser) {
// 初始化AOS动画
// 静态导入本地自定义样式
loadExternalResource('/css/custom.css', 'css')
loadExternalResource('/js/custom.js', 'js')
// 自动添加图片阴影
if (IMG_SHADOW) {
loadExternalResource('/css/img-shadow.css', 'css')
}
// 导入外部自定义脚本
if (CUSTOM_EXTERNAL_JS && CUSTOM_EXTERNAL_JS.length > 0) {
for (const url of CUSTOM_EXTERNAL_JS) {
loadExternalResource(url, 'js')
}
}
// 导入外部自定义样式
if (CUSTOM_EXTERNAL_CSS && CUSTOM_EXTERNAL_CSS.length > 0) {
for (const url of CUSTOM_EXTERNAL_CSS) {
loadExternalResource(url, 'css')
}
}
}
if (DISABLE_PLUGIN) { if (DISABLE_PLUGIN) {
return null return null
} }
return <> return <>
{/* 全局样式嵌入 */}
<GlobalStyle/>
{THEME_SWITCH && <ThemeSwitch />} {THEME_SWITCH && <ThemeSwitch />}
{DEBUG && <DebugPanel />} {DEBUG && <DebugPanel />}
{ANALYTICS_ACKEE_TRACKER && <Ackee />} {ANALYTICS_ACKEE_TRACKER && <Ackee />}
@@ -91,10 +132,12 @@ const ExternalPlugin = (props) => {
{FLUTTERINGRIBBON && <FlutteringRibbon />} {FLUTTERINGRIBBON && <FlutteringRibbon />}
{COMMENT_TWIKOO_COUNT_ENABLE && <TwikooCommentCounter {...props} />} {COMMENT_TWIKOO_COUNT_ENABLE && <TwikooCommentCounter {...props} />}
{RIBBON && <Ribbon />} {RIBBON && <Ribbon />}
{DIFY_CHATBOT_ENABLED && <DifyChatbot />}
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU && <CustomContextMenu {...props} />} {CUSTOM_RIGHT_CLICK_CONTEXT_MENU && <CustomContextMenu {...props} />}
{!CAN_COPY && <DisableCopy />} {!CAN_COPY && <DisableCopy />}
{WEB_WHIZ_ENABLED && <WebWhiz />} {WEB_WHIZ_ENABLED && <WebWhiz />}
{AD_WWADS_BLOCK_DETECT && <AdBlockDetect />} {AD_WWADS_BLOCK_DETECT && <AdBlockDetect />}
{TIANLI_KEY && <TianLiGPT/>}
<VConsole /> <VConsole />
<LoadingProgress /> <LoadingProgress />
<AosAnimation /> <AosAnimation />
@@ -109,6 +152,11 @@ const ExternalPlugin = (props) => {
}} /> */} }} /> */}
</>)} </>)}
{/* 注入JS脚本 */}
{GLOBAL_JS && <script async dangerouslySetInnerHTML={{
__html: GLOBAL_JS
}} />}
{CHATBASE_ID && (<> {CHATBASE_ID && (<>
<script id={CHATBASE_ID} src="https://www.chatbase.co/embed.min.js" defer /> <script id={CHATBASE_ID} src="https://www.chatbase.co/embed.min.js" defer />
<script async dangerouslySetInnerHTML={{ <script async dangerouslySetInnerHTML={{
@@ -120,6 +168,18 @@ const ExternalPlugin = (props) => {
}} /> }} />
</>)} </>)}
{CLARITY_ID && (<>
<script async dangerouslySetInnerHTML={{
__html: `
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "${CLARITY_ID}");
`
}} />
</>)}
{COMMENT_DAO_VOICE_ID && (<> {COMMENT_DAO_VOICE_ID && (<>
{/* DaoVoice 反馈 */} {/* DaoVoice 反馈 */}
<script async dangerouslySetInnerHTML={{ <script async dangerouslySetInnerHTML={{

20
components/GlobalStyle.js Normal file
View File

@@ -0,0 +1,20 @@
/* eslint-disable react/no-unknown-property */
import { siteConfig } from '@/lib/config'
/**
* 这里的css样式对全局生效
* 主题客制化css
* @returns
*/
const GlobalStyle = () => {
// 从NotionConfig中读取样式
const GLOBAL_CSS = siteConfig('GLOBAL_CSS')
return (<style jsx global>{`
${GLOBAL_CSS}
`}</style>)
}
export { GlobalStyle }

View File

@@ -1,4 +1,5 @@
import React, { useState } from 'react' import { useState } from 'react';
import { siteConfig } from '@/lib/config'
/** /**
* Tabs切换标签 * Tabs切换标签
@@ -6,59 +7,39 @@ import React, { useState } from 'react'
* @returns * @returns
*/ */
const Tabs = ({ className, children }) => { const Tabs = ({ className, children }) => {
const [currentTab, setCurrentTab] = useState(0) const [currentTab, setCurrentTab] = useState(0);
if (!children) { const validChildren = children.filter(c => c);
return <></>
if (validChildren.length === 0) {
return <></>;
} }
children = children.filter(c => c && c !== '') return (
<div className={`mb-5 duration-200 ${className}`}>
let count = 0 {!(validChildren.length === 1 && siteConfig('COMMENT_HIDE_SINGLE_TAB')) && (
children.forEach(e => { <ul className="flex justify-center space-x-5 pb-4 dark:text-gray-400 text-gray-600 overflow-auto">
if (e) { {validChildren.map((item, index) => (
count++ <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}
if (count === 0) { </li>
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-600 text-red-600 animate__animated animate__jello ' : 'font-extralight cursor-pointer') + ' text-sm font-sans '}
onClick={() => {
tabClickHandle(index)
}}>
{item?.key}
</li>
})}
</ul> </ul>
<div> )}
{children.map((item, index) => { {/* 标签切换的时候不销毁 DOM 元素,使用 CSS 样式进行隐藏 */}
return <section key={index} <div>
data-aos="fade-up" {validChildren.map((item, index) => (
data-aos-duration="300" <section
data-aos-once="true" key={index}
data-aos-anchor-placement="top-bottom"> className={`${currentTab === index ? 'opacity-100 static h-auto' : 'opacity-0 absolute h-0 pointer-events-none overflow-hidden'}`}>
{currentTab === index && item} {item}
</section> </section>
})} ))}
</div> </div>
</div> </div>
} );
};
export default Tabs export default Tabs;

41
components/TianliGPT.js Normal file
View File

@@ -0,0 +1,41 @@
/* eslint-disable no-unused-vars */
/* eslint-disable camelcase */
import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
/**
* TianliGpt AI文章摘要生成工具 @see https://docs_s.tianli0.top/
* @returns {JSX.Element}
* @constructor
*/
const TianLiGPT = () => {
const tianliKey = siteConfig('TianliGPT_KEY')
const tianliCss = siteConfig('TianliGPT_CSS')
const tianliJs = siteConfig('TianliGPT_JS')
useEffect(() => {
initArtalk()
}, [])
if (!tianliKey) {
return null
}
const initArtalk = async () => {
console.log('loading tianliGPT', tianliKey, tianliCss, tianliJs)
if (!tianliKey) {
return
}
await loadExternalResource(tianliCss, 'css')
window.tianliGPT_postSelector = '#notion-article';
window.tianliGPT_key = tianliKey;
await loadExternalResource(tianliJs, 'js')
}
return <></>
}
export default TianLiGPT

View File

@@ -1,6 +1,6 @@
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
// import { loadExternalResource } from '@/lib/utils' import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react' import { useEffect, useRef, useState } from 'react'
/** /**
* Giscus评论 @see https://giscus.app/zh-CN * Giscus评论 @see https://giscus.app/zh-CN
@@ -12,23 +12,46 @@ import { useEffect } from 'react'
const Twikoo = ({ isDarkMode }) => { const Twikoo = ({ isDarkMode }) => {
const envId = siteConfig('COMMENT_TWIKOO_ENV_ID') const envId = siteConfig('COMMENT_TWIKOO_ENV_ID')
const el = siteConfig('COMMENT_TWIKOO_ELEMENT_ID', '#twikoo') const el = siteConfig('COMMENT_TWIKOO_ELEMENT_ID', '#twikoo')
const twikooCDNURL = siteConfig('COMMENT_TWIKOO_CDN_URL')
const lang = siteConfig('LANG') const lang = siteConfig('LANG')
useEffect(() => { const [isInit] = useState(useRef(false))
const twikoo = window?.twikoo
if (typeof twikoo !== 'undefined' && twikoo && typeof twikoo.init === 'function') { const loadTwikoo = async () => {
twikoo.init({ try {
envId: envId, // 腾讯云环境填 envIdVercel 环境填地址https://xxx.vercel.app await loadExternalResource(twikooCDNURL, 'js')
el: el, // 容器元素 const twikoo = window?.twikoo
lang: lang // 用于手动设定评论区语言,支持的语言列表 https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js if (
// region: 'ap-guangzhou', // 环境地域,默认为 ap-shanghai腾讯云环境填 ap-shanghai 或 ap-guangzhouVercel 环境不填 typeof twikoo !== 'undefined' &&
// path: location.pathname, // 用于区分不同文章的自定义 js 路径,如果您的文章路径不是 location.pathname需传此参数 twikoo &&
}) typeof twikoo.init === 'function'
) {
twikoo.init({
envId: envId, // 腾讯云环境填 envIdVercel 环境填地址https://xxx.vercel.app
el: el, // 容器元素
lang: lang // 用于手动设定评论区语言,支持的语言列表 https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js
// region: 'ap-guangzhou', // 环境地域,默认为 ap-shanghai腾讯云环境填 ap-shanghai 或 ap-guangzhouVercel 环境不填
// path: location.pathname, // 用于区分不同文章的自定义 js 路径,如果您的文章路径不是 location.pathname需传此参数
})
console.log('twikoo init', twikoo)
isInit.current = true
}
} catch (error) {
console.error('twikoo 加载失败', error)
} }
}
useEffect(() => {
const interval = setInterval(() => {
if (isInit.current) {
console.log('twioo init! clear interval')
clearInterval(interval)
} else {
loadTwikoo()
}
}, 1000)
return () => clearInterval(interval)
}, [isDarkMode]) }, [isDarkMode])
return ( return <div id="twikoo"></div>
<div id="twikoo"></div>
)
} }
export default Twikoo export default Twikoo

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { useEffect } from 'react' import { useGlobal } from '@/lib/global'
/** /**
* 评论插件 * 评论插件
@@ -9,28 +10,48 @@ import { useEffect } from 'react'
* @constructor * @constructor
*/ */
const Utterances = ({ issueTerm, layout }) => { const Utterances = ({ issueTerm, layout }) => {
const { isDarkMode } = useGlobal()
const [isLoading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
const theme = const script = document.createElement('script');
siteConfig('APPEARANCE') === 'auto' const anchor = document.getElementById('comments');
? 'preferred-color-scheme' script.onload = () => setLoading(false);
: siteConfig('APPEARANCE') === 'light' script.setAttribute('src', 'https://utteranc.es/client.js');
? 'github-light' script.setAttribute('crossorigin', 'anonymous');
: 'github-dark' script.setAttribute('async', true);
const script = document.createElement('script') script.setAttribute('repo', siteConfig('COMMENT_UTTERRANCES_REPO'));
const anchor = document.getElementById('comments') script.setAttribute('issue-term', 'title');
script.setAttribute('src', 'https://utteranc.es/client.js') // 初始主题
script.setAttribute('crossorigin', 'anonymous') script.setAttribute('theme', isDarkMode ? 'github-dark' : 'github-light');
script.setAttribute('async', true) anchor.appendChild(script);
script.setAttribute('repo', siteConfig('COMMENT_UTTERRANCES_REPO'))
script.setAttribute('issue-term', 'title')
script.setAttribute('theme', theme)
anchor.appendChild(script)
return () => { 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');
} }
}) }, [isDarkMode]);
return <div id="comments" className='utterances' >
</div> 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 export default Utterances

33
hooks/useAdjustStyle.js Normal file
View File

@@ -0,0 +1,33 @@
import { isBrowser } from '@/lib/utils';
import { useEffect } from 'react';
const useAdjustStyle = () => {
/**
* 避免 callout 含有图片时溢出撑开父容器
*/
const adjustCalloutImg = () => {
const callOuts = document.querySelectorAll('.notion-callout-text');
callOuts.forEach((callout) => {
const images = callout.querySelectorAll('figure.notion-asset-wrapper.notion-asset-wrapper-image > div');
const calloutWidth = callout.offsetWidth;
images.forEach((container) => {
const imageWidth = container.offsetWidth;
if (imageWidth + 50 > calloutWidth) {
container.style.setProperty('width', '100%');
}
});
});
};
useEffect(() => {
if (isBrowser) {
adjustCalloutImg();
window.addEventListener('resize', adjustCalloutImg);
return () => {
window.removeEventListener('resize', adjustCalloutImg);
};
}
}, []);
};
export default useAdjustStyle;

View File

@@ -9,7 +9,7 @@ import BLOG from '@/blog.config'
* @returns * @returns
*/ */
export async function getDataFromCache(key, force) { export async function getDataFromCache(key, force) {
if (BLOG.ENABLE_CACHE || force) { if (JSON.parse(BLOG.ENABLE_CACHE) || force) {
const dataFromCache = await getApi().getCache(key) const dataFromCache = await getApi().getCache(key)
if (JSON.stringify(dataFromCache) === '[]') { if (JSON.stringify(dataFromCache) === '[]') {
return null return null
@@ -28,7 +28,7 @@ export async function setDataToCache(key, data) {
} }
export async function delCacheData(key) { export async function delCacheData(key) {
if (!BLOG.ENABLE_CACHE) { if (!JSON.parse(BLOG.ENABLE_CACHE)) {
return return
} }
await getApi().delCache(key) await getApi().delCache(key)

View File

@@ -1,4 +1,4 @@
import { generateLocaleDict, initLocale } from './lang' import { generateLocaleDict, initLocale, saveLangToCookies } 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'
@@ -12,7 +12,7 @@ const GlobalContext = createContext()
* @constructor * @constructor
*/ */
export function GlobalContextProvider(props) { export function GlobalContextProvider(props) {
const { children, siteInfo, categoryOptions, tagOptions, NOTION_CONFIG } = props const { post, children, siteInfo, categoryOptions, tagOptions, NOTION_CONFIG } = props
const router = useRouter() const router = useRouter()
const [lang, updateLang] = useState(NOTION_CONFIG?.LANG || LANG) // 默认语言 const [lang, updateLang] = useState(NOTION_CONFIG?.LANG || LANG) // 默认语言
const [locale, updateLocale] = useState(generateLocaleDict(NOTION_CONFIG?.LANG || LANG)) // 默认语言 const [locale, updateLocale] = useState(generateLocaleDict(NOTION_CONFIG?.LANG || LANG)) // 默认语言
@@ -20,6 +20,9 @@ export function GlobalContextProvider(props) {
const [isDarkMode, updateDarkMode] = useState(NOTION_CONFIG?.APPEARANCE || APPEARANCE === 'dark') // 默认深色模式 const [isDarkMode, updateDarkMode] = useState(NOTION_CONFIG?.APPEARANCE || APPEARANCE === 'dark') // 默认深色模式
const [onLoading, setOnLoading] = useState(false) // 抓取文章数据 const [onLoading, setOnLoading] = useState(false) // 抓取文章数据
// 是否全屏
const fullWidth = post?.fullWidth ?? false
// 切换主题 // 切换主题
function switchTheme() { function switchTheme() {
const currentIndex = THEMES.indexOf(theme) const currentIndex = THEMES.indexOf(theme)
@@ -46,6 +49,7 @@ export function GlobalContextProvider(props) {
*/ */
function changeLang(lang) { function changeLang(lang) {
if (lang) { if (lang) {
saveLangToCookies(lang)
updateLang(lang) updateLang(lang)
updateLocale(generateLocaleDict(lang)) updateLocale(generateLocaleDict(lang))
} }
@@ -82,6 +86,7 @@ export function GlobalContextProvider(props) {
return ( return (
<GlobalContext.Provider value={{ <GlobalContext.Provider value={{
fullWidth,
NOTION_CONFIG, NOTION_CONFIG,
toggleDarkMode, toggleDarkMode,
onLoading, onLoading,

View File

@@ -89,9 +89,9 @@ export const loadLangFromCookies = () => {
} }
/** /**
* 保存语言 * 保存语言
* @param newTheme * @param newTheme
*/ */
export const saveLangToCookies = (lang) => { export const saveLangToCookies = (lang) => {
cookie.save('lang', lang, { path: '/' }) cookie.save('lang', lang, { path: '/' })
} }

View File

@@ -16,7 +16,6 @@ export async function getNotion(pageId) {
} }
const postInfo = blockMap?.block?.[idToUuid(pageId)].value const postInfo = blockMap?.block?.[idToUuid(pageId)].value
return { return {
id: pageId, id: pageId,
type: postInfo, type: postInfo,
@@ -26,7 +25,7 @@ export async function getNotion(pageId) {
status: 'Published', status: 'Published',
createdTime: formatDate(new Date(postInfo.created_time).toString(), BLOG.LANG), createdTime: formatDate(new Date(postInfo.created_time).toString(), BLOG.LANG),
lastEditedDay: formatDate(new Date(postInfo?.last_edited_time).toString(), BLOG.LANG), lastEditedDay: formatDate(new Date(postInfo?.last_edited_time).toString(), BLOG.LANG),
fullWidth: false, fullWidth: postInfo?.fullWidth,
page_cover: getPageCover(postInfo), page_cover: getPageCover(postInfo),
date: { start_date: formatDate(new Date(postInfo?.last_edited_time).toString(), BLOG.LANG) }, date: { start_date: formatDate(new Date(postInfo?.last_edited_time).toString(), BLOG.LANG) },
blockMap blockMap

View File

@@ -100,11 +100,11 @@ export default async function getPageProperties(id, value, schema, authToken, ta
delete properties.content delete properties.content
// 处理URL // 处理URL
if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_post) { if (properties.type === 'Post') {
properties.slug = (BLOG.POST_URL_PREFIX) ? generateCustomizeUrl(properties) : (properties.slug ?? properties.id) properties.slug = (BLOG.POST_URL_PREFIX) ? generateCustomizeUrl(properties) : (properties.slug ?? properties.id)
} else if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_page) { } else if (properties.type === 'Page') {
properties.slug = properties.slug ?? properties.id properties.slug = properties.slug ?? properties.id
} else if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_menu || properties.type === BLOG.NOTION_PROPERTY_NAME.type_sub_menu) { } else if (properties.type === 'Menu' || properties.type === 'SubMenu') {
// 菜单路径为空、作为可展开菜单使用 // 菜单路径为空、作为可展开菜单使用
properties.to = properties.slug ?? '#' properties.to = properties.slug ?? '#'
properties.name = properties.title ?? '' properties.name = properties.title ?? ''

View File

@@ -81,8 +81,10 @@ export async function getPageWithRetry(id, from, retryAttempts = 3) {
} }
/** /**
* 获取到的blockMap删除不需要的字段 * 获取到的页面BLOCK特殊处理
* 并且对于页面内容进行特殊处理比如文件url格式化 * 1.删除冗余字段
* 2.比如文件、视频、音频、url格式化
* 3.代码块等元素兼容
* @param {*} id 页面ID * @param {*} id 页面ID
* @param {*} pageBlock 页面元素 * @param {*} pageBlock 页面元素
* @param {*} slice 截取数量 * @param {*} slice 截取数量
@@ -121,7 +123,10 @@ function filterPostBlocks(id, pageBlock, slice) {
} }
// 如果是文件或嵌入式PDF需要重新加密签名 // 如果是文件或嵌入式PDF需要重新加密签名
if ((b?.value?.type === 'file' || b?.value?.type === 'pdf' || b?.value?.type === 'video') && b?.value?.properties?.source?.[0][0]) { if ((b?.value?.type === 'file' || b?.value?.type === 'pdf' || b?.value?.type === 'video' || b?.value?.type === 'audio') &&
b?.value?.properties?.source?.[0][0] &&
b?.value?.properties?.source?.[0][0].indexOf('amazonaws.com') > 0
) {
const oldUrl = b?.value?.properties?.source?.[0][0] const oldUrl = b?.value?.properties?.source?.[0][0]
const newUrl = `https://notion.so/signed/${encodeURIComponent(oldUrl)}?table=block&id=${b?.value?.id}` const newUrl = `https://notion.so/signed/${encodeURIComponent(oldUrl)}?table=block&id=${b?.value?.id}`
b.value.properties.source[0][0] = newUrl b.value.properties.source[0][0] = newUrl

View File

@@ -1,45 +1,5 @@
import BLOG from '@/blog.config' import BLOG from '@/blog.config'
/**
* 压缩图片
* 1. Notion图床可以通过指定url-query参数来压缩裁剪图片 例如 ?xx=xx&width=400
* 2. UnPlash 图片可以通过api q=50 控制压缩质量 width=400 控制图片尺寸
* @param {*} image
*/
const compressImage = (image, width = 800, quality = 50, fmt = 'webp') => {
if (!image) {
return null
}
if (image.indexOf(BLOG.NOTION_HOST) === 0 && image.indexOf('amazonaws.com') > 0) {
return `${image}&width=${width}`
}
// 压缩unsplash图片
if (image.indexOf('https://images.unsplash.com/') === 0) {
// 将URL解析为一个对象
const urlObj = new URL(image)
// 获取URL参数
const params = new URLSearchParams(urlObj.search)
// 将q参数的值替换
params.set('q', quality)
// 尺寸
params.set('width', width)
// 格式
params.set('fmt', fmt)
params.set('fm', fmt)
// 生成新的URL
urlObj.search = params.toString()
return urlObj.toString()
}
// 此处还可以添加您的自定义图传的封面图压缩参数。
// .e.g
if (image.indexOf('https://your_picture_bed') === 0) {
return 'do_somethin_here'
}
return image
}
/** /**
* 图片映射 * 图片映射
* 1. 如果是 /xx.xx 相对路径格式,则转化为 完整notion域名图片 * 1. 如果是 /xx.xx 相对路径格式,则转化为 完整notion域名图片
@@ -61,9 +21,9 @@ const mapImgUrl = (img, block, type = 'block', from) => {
} }
// Notion 图床转换为永久地址 // Notion 图床转换为永久地址
const isNotionImg = ret.indexOf('secure.notion-static.com') > 0 || ret.indexOf('prod-files-secure') > 0 const isNotionSignImg = ret.indexOf('https://www.notion.so/image') !== 0 && (ret.indexOf('secure.notion-static.com') > 0 || ret.indexOf('prod-files-secure') > 0)
const isImgBlock = BLOG.IMG_URL_TYPE === 'Notion' || type !== 'block' const isImgBlock = BLOG.IMG_URL_TYPE === 'Notion' || type !== 'block'
if (isNotionImg && isImgBlock) { if (isNotionSignImg && isImgBlock) {
ret = BLOG.NOTION_HOST + '/image/' + encodeURIComponent(ret) + '?table=' + type + '&id=' + block.id ret = BLOG.NOTION_HOST + '/image/' + encodeURIComponent(ret) + '?table=' + type + '&id=' + block.id
} }
@@ -96,9 +56,11 @@ const mapImgUrl = (img, block, type = 'block', from) => {
} }
} }
// 文章封面 // 文章封面压缩
if (from === 'pageCoverThumbnail') { if (from === 'pageCoverThumbnail' || block.type === 'image') {
ret = compressImage(ret) // 统一压缩图片
const width = block?.format?.block_width
ret = compressImage(ret, width)
} }
return ret return ret
@@ -114,4 +76,44 @@ function isEmoji(str) {
return emojiRegex.test(str); return emojiRegex.test(str);
} }
/**
* 压缩图片
* 1. Notion图床可以通过指定url-query参数来压缩裁剪图片 例如 ?xx=xx&width=400
* 2. UnPlash 图片可以通过api q=50 控制压缩质量 width=400 控制图片尺寸
* @param {*} image
*/
const compressImage = (image, width = 800, quality = 50, fmt = 'webp') => {
if (!image) {
return null
}
if (image.indexOf(BLOG.NOTION_HOST) === 0 && image.indexOf('amazonaws.com') > 0) {
return `${image}&width=${width}&cache=v2`
}
// 压缩unsplash图片
if (image.indexOf('https://images.unsplash.com/') === 0) {
// 将URL解析为一个对象
const urlObj = new URL(image)
// 获取URL参数
const params = new URLSearchParams(urlObj.search)
// 将q参数的值替换
params.set('q', quality)
// 尺寸
params.set('width', width)
// 格式
params.set('fmt', fmt)
params.set('fm', fmt)
// 生成新的URL
urlObj.search = params.toString()
return urlObj.toString()
}
// 此处还可以添加您的自定义图传的封面图压缩参数。
// .e.g
if (image.indexOf('https://your_picture_bed') === 0) {
return 'do_somethin_here'
}
return image
}
export { mapImgUrl, compressImage } export { mapImgUrl, compressImage }

View File

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

View File

@@ -9,45 +9,41 @@ import '@/styles/notion.css' // 重写部分样式
import 'aos/dist/aos.css' // You can also use <link> for styles import 'aos/dist/aos.css' // You can also use <link> for styles
import { GlobalContextProvider } from '@/lib/global' import { GlobalContextProvider } from '@/lib/global'
import { isBrowser, loadExternalResource } from '@/lib/utils' import { getGlobalLayoutByTheme } from '@/themes/theme'
import { useRouter } from 'next/router'
import { useCallback, useMemo } from 'react'
import { getQueryParam } from '../lib/utils'
import useAdjustStyle from '@/hooks/useAdjustStyle'
// 各种扩展插件 这个要阻塞引入 // 各种扩展插件 这个要阻塞引入
import ExternalPlugins from '@/components/ExternalPlugins' import ExternalPlugins from '@/components/ExternalPlugins'
import { CUSTOM_EXTERNAL_CSS, CUSTOM_EXTERNAL_JS, IMG_SHADOW } from '@/blog.config' import { THEME } from '@/blog.config'
const MyApp = ({ Component, pageProps }) => { const MyApp = ({ Component, pageProps }) => {
// 自定义样式css和js引入 // 一些可能出现 bug 的样式,可以统一放入该钩子进行调整
if (isBrowser) { useAdjustStyle();
// 初始化AOS动画
// 静态导入本地自定义样式
loadExternalResource('/css/custom.css', 'css')
loadExternalResource('/js/custom.js', 'js')
// 自动添加图片阴影 const route = useRouter()
if (IMG_SHADOW) { const queryParam = useMemo(() => {
loadExternalResource('/css/img-shadow.css', 'css') return getQueryParam(route.asPath, 'theme') || THEME
} }, [route])
// 导入外部自定义脚本 const GLayout = useCallback(
if (CUSTOM_EXTERNAL_JS && CUSTOM_EXTERNAL_JS.length > 0) { props => {
for (const url of CUSTOM_EXTERNAL_JS) { // 根据页面路径加载不同Layout文件
loadExternalResource(url, 'js') const Layout = getGlobalLayoutByTheme(queryParam)
} return <Layout {...props} />
} },
[queryParam]
// 导入外部自定义样式 )
if (CUSTOM_EXTERNAL_CSS && CUSTOM_EXTERNAL_CSS.length > 0) {
for (const url of CUSTOM_EXTERNAL_CSS) {
loadExternalResource(url, 'css')
}
}
}
return ( return (
<GlobalContextProvider {...pageProps}> <GlobalContextProvider {...pageProps}>
<Component {...pageProps} /> <GLayout {...pageProps}>
<ExternalPlugins {...pageProps} /> <Component {...pageProps} />
</GlobalContextProvider> </GLayout>
<ExternalPlugins {...pageProps} />
</GlobalContextProvider>
) )
} }

View File

@@ -20,6 +20,7 @@ class MyDocument extends Document {
</>} </>}
{BLOG.FONT_URL?.map((fontUrl, index) => { {BLOG.FONT_URL?.map((fontUrl, index) => {
console.log(fontUrl)
if (fontUrl.endsWith('.css')) { if (fontUrl.endsWith('.css')) {
return <link key={index} rel="stylesheet" href={fontUrl} /> return <link key={index} rel="stylesheet" href={fontUrl} />
} else { } else {

View File

@@ -160,6 +160,11 @@ nav {
@apply text-blue-700 @apply text-blue-700
} }
/* twikoo 内置的 element-ui 加载样式 */
.el-loading-spinner {
@apply flex justify-center items-center;
}
/* Webmention style */ /* Webmention style */
.webmention-block { .webmention-block {
background: rgba(0, 116, 222, .2); background: rgba(0, 116, 222, .2);

View File

@@ -21,7 +21,7 @@ const BlogPostCard = ({ post }) => {
by <a href="#" className="text-gray-700 dark:text-gray-300">{siteConfig('AUTHOR')}</a> on {post.date?.start_date || post.createdTime} by <a href="#" className="text-gray-700 dark:text-gray-300">{siteConfig('AUTHOR')}</a> on {post.date?.start_date || post.createdTime}
<TwikooCommentCount post={post} className='pl-1'/> <TwikooCommentCount post={post} className='pl-1'/>
<span className="font-bold mx-1"> | </span> <span className="font-bold mx-1"> | </span>
<a href={`/category${post.category}`} className="text-gray-700 dark:text-gray-300 hover:underline">{post.category}</a> <Link href={`/category/${post.category}`} className="text-gray-700 dark:text-gray-300 hover:underline">{post.category}</Link>
{/* <span className="font-bold mx-1"> | </span> */} {/* <span className="font-bold mx-1"> | </span> */}
{/* <a href="#" className="text-gray-700">2 Comments</a> */} {/* <a href="#" className="text-gray-700">2 Comments</a> */}
</div> </div>

View File

@@ -36,8 +36,23 @@ import { siteConfig } from '@/lib/config'
* @constructor * @constructor
*/ */
const LayoutBase = props => { const LayoutBase = props => {
const { children, slotTop, meta } = props const { children, meta } = props
const { onLoading } = useGlobal() const { onLoading, fullWidth } = useGlobal()
const router = useRouter()
const { category, tag } = props
// 顶部如果是按照分类或标签查看文章列表,列表顶部嵌入一个横幅
// 如果是搜索,则列表顶部嵌入 搜索框
let slotTop = null
if (category) {
slotTop = <div className='pb-12'><i className="mr-1 fas fa-folder-open" />{category}</div>
} else if (tag) {
slotTop = <div className='pb-12'>#{tag}</div>
} else if (props.slotTop) {
slotTop = props.slotTop
} else if (router.route==='/search'){
// 嵌入一个搜索框在顶部
slotTop = <div className='pb-12'><SearchInput {...props} /></div>
}
// 增加一个状态以触发 Transition 组件的动画 // 增加一个状态以触发 Transition 组件的动画
// const [showTransition, setShowTransition] = useState(true) // const [showTransition, setShowTransition] = useState(true)
@@ -65,12 +80,12 @@ const LayoutBase = props => {
<div id='container-inner' className="w-full relative z-10"> <div id='container-inner' className="w-full relative z-10">
{/* 标题栏 */} {/* 标题栏 */}
<Title {...props} /> {fullWidth ? null : <Title {...props} />}
<div id='container-wrapper' className={(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE')) ? 'flex-row-reverse' : '') + 'relative container mx-auto justify-center md:flex items-start py-8 px-2'}> <div id='container-wrapper' className={(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE')) ? 'flex-row-reverse' : '') + 'relative container mx-auto justify-center md:flex items-start py-8 px-2'}>
{/* 内容 */} {/* 内容 */}
<div className='w-full max-w-3xl xl:px-14 lg:px-4 '> <div className={`w-full ${fullWidth ? '' : 'max-w-3xl'} xl:px-14 lg:px-4`}>
<Transition <Transition
show={!onLoading} show={!onLoading}
appear={true} appear={true}
@@ -89,7 +104,7 @@ const LayoutBase = props => {
</div> </div>
{/* 侧边栏 */} {/* 侧边栏 */}
<SideBar {...props} /> {!fullWidth && <SideBar {...props} />}
</div> </div>
@@ -121,21 +136,11 @@ const LayoutIndex = props => {
* @returns * @returns
*/ */
const LayoutPostList = props => { const LayoutPostList = props => {
const { category, tag } = props
// 顶部如果是按照分类或标签查看文章列表,列表顶部嵌入一个横幅
// 如果是搜索,则列表顶部嵌入 搜索框
let slotTop = null
if (category) {
slotTop = <div className='pb-12'><i className="mr-1 fas fa-folder-open" />{category}</div>
} else if (tag) {
slotTop = <div className='pb-12'>#{tag}</div>
} else if (props.slotTop) {
slotTop = props.slotTop
}
return ( return (
<LayoutBase {...props} slotTop={slotTop}> <>
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</LayoutBase> </>
) )
} }
@@ -147,7 +152,7 @@ const LayoutPostList = props => {
const LayoutSlug = props => { const LayoutSlug = props => {
const { post, lock, validPassword } = props const { post, lock, validPassword } = props
return ( return (
<LayoutBase {...props}> <>
{lock {lock
? <ArticleLock validPassword={validPassword} /> ? <ArticleLock validPassword={validPassword} />
: <div id="article-wrapper" className="px-2"> : <div id="article-wrapper" className="px-2">
@@ -156,7 +161,7 @@ const LayoutSlug = props => {
<ShareBar post={post} /> <ShareBar post={post} />
<Comment frontMatter={post} /> <Comment frontMatter={post} />
</div>} </div>}
</LayoutBase> </>
) )
} }
@@ -166,7 +171,7 @@ const LayoutSlug = props => {
* @returns * @returns
*/ */
const Layout404 = (props) => { const Layout404 = (props) => {
return <LayoutBase {...props}>404 Not found.</LayoutBase> return <>404 Not found.</>
} }
/** /**
@@ -176,8 +181,6 @@ const Layout404 = (props) => {
*/ */
const LayoutSearch = props => { const LayoutSearch = props => {
const { keyword } = props const { keyword } = props
// 嵌入一个搜索框在顶部
const slotTop = <div className='pb-12'><SearchInput {...props} /></div>
const router = useRouter() const router = useRouter()
useEffect(() => { useEffect(() => {
if (isBrowser) { if (isBrowser) {
@@ -196,7 +199,7 @@ const LayoutSearch = props => {
} }
}, [router]) }, [router])
return <LayoutPostList slotTop={slotTop} {...props} /> return <LayoutPostList {...props} />
} }
/** /**
@@ -206,15 +209,13 @@ const LayoutSearch = props => {
*/ */
const LayoutArchive = props => { const LayoutArchive = props => {
const { archivePosts } = props const { archivePosts } = props
return ( return (<>
<LayoutBase {...props}>
<div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full"> <div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full">
{Object.keys(archivePosts).map(archiveTitle => ( {Object.keys(archivePosts).map(archiveTitle => (
<BlogListGroupByDate key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} /> <BlogListGroupByDate key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />
))} ))}
</div> </div>
</LayoutBase> </>)
)
} }
/** /**
@@ -225,11 +226,11 @@ const LayoutArchive = props => {
const LayoutCategoryIndex = props => { const LayoutCategoryIndex = props => {
const { categoryOptions } = props const { categoryOptions } = props
return ( return (
<LayoutBase {...props}> <>
<div id='category-list' className='duration-200 flex flex-wrap'> <div id='category-list' className='duration-200 flex flex-wrap'>
{categoryOptions?.map(category => <CategoryItem key={category.name} category={category} />)} {categoryOptions?.map(category => <CategoryItem key={category.name} category={category} />)}
</div> </div>
</LayoutBase> </>
) )
} }
@@ -241,16 +242,17 @@ const LayoutCategoryIndex = props => {
const LayoutTagIndex = (props) => { const LayoutTagIndex = (props) => {
const { tagOptions } = props const { tagOptions } = props
return ( return (
<LayoutBase {...props}> <>
<div id='tags-list' className='duration-200 flex flex-wrap'> <div id='tags-list' className='duration-200 flex flex-wrap'>
{tagOptions.map(tag => <TagItem key={tag.name} tag={tag} />)} {tagOptions.map(tag => <TagItem key={tag.name} tag={tag} />)}
</div> </div>
</LayoutBase> </>
) )
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutPostList, LayoutPostList,
LayoutSearch, LayoutSearch,

View File

@@ -17,13 +17,13 @@ import WWAds from '@/components/WWAds'
*/ */
export default function ArticleDetail(props) { export default function ArticleDetail(props) {
const { post, prev, next } = props const { post, prev, next } = props
const { locale } = useGlobal() const { locale, fullWidth } = useGlobal()
if (!post) { if (!post) {
return <></> return <></>
} }
return ( return (
<div id="container" className="max-w-5xl overflow-x-auto flex-grow mx-auto w-screen md:w-full "> <div id="container" className={`${fullWidth ? 'px-10' : 'max-w-5xl '} overflow-x-auto flex-grow mx-auto w-screen md:w-full`}>
{post?.type && !post?.type !== 'Page' && post?.pageCover && ( {post?.type && !post?.type !== 'Page' && post?.pageCover && (
<div className="w-full relative md:flex-shrink-0 overflow-hidden"> <div className="w-full relative md:flex-shrink-0 overflow-hidden">
<LazyImage alt={post.title} src={post?.pageCover} className='object-center w-full' /> <LazyImage alt={post.title} src={post?.pageCover} className='object-center w-full' />

View File

@@ -46,9 +46,9 @@ export const useFukasawaGlobal = () => useContext(ThemeGlobalFukasawa)
const LayoutBase = (props) => { const LayoutBase = (props) => {
const { children, headerSlot, meta } = props const { children, headerSlot, meta } = props
const leftAreaSlot = <Live2D /> const leftAreaSlot = <Live2D />
const { onLoading } = useGlobal() const { onLoading, fullWidth } = useGlobal()
const FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT = siteConfig('FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT', null, CONFIG) const FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT = fullWidth || siteConfig('FUKASAWA_SIDEBAR_COLLAPSE_SATUS_DEFAULT', null, CONFIG)
// 侧边栏折叠从 本地存储中获取 open 状态的初始值 // 侧边栏折叠从 本地存储中获取 open 状态的初始值
const [isCollapsed, setIsCollapse] = useState(() => { const [isCollapsed, setIsCollapse] = useState(() => {
@@ -80,7 +80,7 @@ const LayoutBase = (props) => {
<AsideLeft {...props} slot={leftAreaSlot} /> <AsideLeft {...props} slot={leftAreaSlot} />
<main id='wrapper' className='relative flex w-full py-8 justify-center bg-day dark:bg-night'> <main id='wrapper' className='relative flex w-full py-8 justify-center bg-day dark:bg-night'>
<div id='container-inner' className='2xl:max-w-6xl md:max-w-4xl w-full relative z-10'> <div id='container-inner' className={`${fullWidth ? '' : '2xl:max-w-6xl md:max-w-4xl'} w-full relative z-10`}>
<Transition <Transition
show={!onLoading} show={!onLoading}
appear={true} appear={true}
@@ -126,12 +126,10 @@ const LayoutIndex = (props) => {
* @param {*} props * @param {*} props
*/ */
const LayoutPostList = (props) => { const LayoutPostList = (props) => {
return <LayoutBase {...props}> return <>
<div className='w-full p-2'><WWAds className='w-full' orientation='horizontal'/></div> <div className='w-full p-2'><WWAds className='w-full' orientation='horizontal'/></div>
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</LayoutBase> </>
} }
/** /**
@@ -142,9 +140,9 @@ const LayoutPostList = (props) => {
const LayoutSlug = (props) => { const LayoutSlug = (props) => {
const { lock, validPassword } = props const { lock, validPassword } = props
return ( return (
<LayoutBase {...props} > <>
{lock ? <ArticleLock validPassword={validPassword} /> : <ArticleDetail {...props} />} {lock ? <ArticleLock validPassword={validPassword} /> : <ArticleDetail {...props} />}
</LayoutBase> </>
) )
} }
@@ -174,7 +172,7 @@ const LayoutSearch = props => {
*/ */
const LayoutArchive = (props) => { const LayoutArchive = (props) => {
const { archivePosts } = props const { archivePosts } = props
return <LayoutBase {...props}> return <>
<div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 shadow-md min-h-full"> <div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-gray-800 shadow-md min-h-full">
{Object.keys(archivePosts).map(archiveTitle => ( {Object.keys(archivePosts).map(archiveTitle => (
<BlogArchiveItem <BlogArchiveItem
@@ -184,7 +182,7 @@ const LayoutArchive = (props) => {
/> />
))} ))}
</div> </div>
</LayoutBase> </>
} }
/** /**
@@ -193,7 +191,7 @@ const LayoutArchive = (props) => {
* @returns * @returns
*/ */
const Layout404 = props => { const Layout404 = props => {
return <LayoutBase {...props}>404</LayoutBase> return <>404</>
} }
/** /**
@@ -205,7 +203,7 @@ const LayoutCategoryIndex = (props) => {
const { locale } = useGlobal() const { locale } = useGlobal()
const { categoryOptions } = props const { categoryOptions } = props
return ( return (
<LayoutBase {...props}> <>
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'> <div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
<div className='dark:text-gray-200 mb-5'> <div className='dark:text-gray-200 mb-5'>
<i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}: <i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}:
@@ -227,7 +225,7 @@ const LayoutCategoryIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
) )
} }
@@ -239,7 +237,7 @@ const LayoutCategoryIndex = (props) => {
const LayoutTagIndex = (props) => { const LayoutTagIndex = (props) => {
const { locale } = useGlobal() const { locale } = useGlobal()
const { tagOptions } = props const { tagOptions } = props
return <LayoutBase {...props} > return <>
<div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'> <div className='bg-white dark:bg-gray-700 px-10 py-10 shadow'>
<div className='dark:text-gray-200 mb-5'><i className='mr-4 fas fa-tag' />{locale.COMMON.TAGS}:</div> <div className='dark:text-gray-200 mb-5'><i className='mr-4 fas fa-tag' />{locale.COMMON.TAGS}:</div>
<div id="tags-list" className="duration-200 flex flex-wrap ml-8"> <div id="tags-list" className="duration-200 flex flex-wrap ml-8">
@@ -252,11 +250,12 @@ const LayoutTagIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutSearch, LayoutSearch,
LayoutArchive, LayoutArchive,

View File

@@ -17,7 +17,7 @@ const Footer = ({ siteInfo }) => {
© {`${copyrightDate}`} © {`${copyrightDate}`}
</div> </div>
<div className='text-xs font-serif'>Powered By <a href='https://github.com/tangly1024/NotionNext' className='underline text-gray-500 dark:text-gray-300'>NotionNext</a></div> <div className='text-xs font-serif'>Powered By <a href='https://github.com/tangly1024/NotionNext' className='underline text-gray-500 dark:text-gray-300'>NotionNext {siteConfig('VERSION')}</a></div>
{siteConfig('BEI_AN') && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{siteConfig('BEI_AN')}</a><br /></>} {siteConfig('BEI_AN') && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{siteConfig('BEI_AN')}</a><br /></>}

View File

@@ -49,7 +49,7 @@ export const useGitBookGlobal = () => useContext(ThemeGlobalGitbook)
*/ */
const LayoutBase = (props) => { const LayoutBase = (props) => {
const { children, post, allNavPages, slotLeft, slotRight, slotTop, meta } = props const { children, post, allNavPages, slotLeft, slotRight, slotTop, meta } = props
const { onLoading } = useGlobal() const { onLoading, fullWidth } = useGlobal()
const router = useRouter() const router = useRouter()
const [tocVisible, changeTocVisible] = useState(false) const [tocVisible, changeTocVisible] = useState(false)
const [pageNavVisible, changePageNavVisible] = useState(false) const [pageNavVisible, changePageNavVisible] = useState(false)
@@ -73,7 +73,9 @@ const LayoutBase = (props) => {
<main id='wrapper' className={(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE')) ? 'flex-row-reverse' : '') + 'relative flex justify-between w-full h-full mx-auto'}> <main id='wrapper' className={(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE')) ? 'flex-row-reverse' : '') + 'relative flex justify-between w-full h-full mx-auto'}>
{/* 左侧推拉抽屉 */} {/* 左侧推拉抽屉 */}
<div className={'font-sans hidden md:block border-r dark:border-transparent relative z-10 '}> {fullWidth
? null
: (<div className={'font-sans hidden md:block border-r dark:border-transparent relative z-10 '}>
<div className='w-72 py-14 px-6 sticky top-0 overflow-y-scroll h-screen scroll-hidden'> <div className='w-72 py-14 px-6 sticky top-0 overflow-y-scroll h-screen scroll-hidden'>
{slotLeft} {slotLeft}
<SearchInput className='my-3 rounded-md' /> <SearchInput className='my-3 rounded-md' />
@@ -87,11 +89,11 @@ const LayoutBase = (props) => {
<div className='w-72 fixed left-0 bottom-0 z-20 bg-white'> <div className='w-72 fixed left-0 bottom-0 z-20 bg-white'>
<Footer {...props} /> <Footer {...props} />
</div> </div>
</div> </div>) }
<div id='center-wrapper' className='flex flex-col justify-between w-full relative z-10 pt-14 min-h-screen'> <div id='center-wrapper' className='flex flex-col justify-between w-full relative z-10 pt-14 min-h-screen'>
<div id='container-inner' className='w-full px-7 max-w-3xl justify-center mx-auto'> <div id='container-inner' className={`w-full px-7 ${fullWidth ? 'px-10' : 'max-w-3xl'} justify-center mx-auto`}>
{slotTop} {slotTop}
<WWAds className='w-full' orientation='horizontal'/> <WWAds className='w-full' orientation='horizontal'/>
@@ -124,27 +126,29 @@ const LayoutBase = (props) => {
</div> </div>
{/* 右侧侧推拉抽屉 */} {/* 右侧侧推拉抽屉 */}
<div style={{ width: '32rem' }} className={'hidden xl:block dark:border-transparent relative z-10 '}> {fullWidth
<div className='py-14 px-6 sticky top-0'> ? null
<ArticleInfo post={props?.post ? props?.post : props.notice} /> : <div style={{ width: '32rem' }} className={'hidden xl:block dark:border-transparent relative z-10 '}>
<div className='py-14 px-6 sticky top-0'>
<ArticleInfo post={props?.post ? props?.post : props.notice} />
<div className='py-4'> <div className='py-4'>
<Catalog {...props} /> <Catalog {...props} />
{slotRight} {slotRight}
{router.route === '/' && <> {router.route === '/' && <>
<InfoCard {...props} /> <InfoCard {...props} />
{siteConfig('GITBOOK_WIDGET_REVOLVER_MAPS', null, CONFIG) === 'true' && <RevolverMaps />} {siteConfig('GITBOOK_WIDGET_REVOLVER_MAPS', null, CONFIG) === 'true' && <RevolverMaps />}
<Live2D /> <Live2D />
</>} </>}
{/* gitbook主题首页只显示公告 */} {/* gitbook主题首页只显示公告 */}
<Announcement {...props} /> <Announcement {...props} />
</div> </div>
<AdSlot type='in-article' /> <AdSlot type='in-article' />
<Live2D /> <Live2D />
</div> </div>
</div> </div>}
</main> </main>
@@ -189,7 +193,7 @@ const LayoutIndex = (props) => {
}) })
}, []) }, [])
return <LayoutBase {...props} /> return <></>
} }
/** /**
@@ -199,9 +203,7 @@ const LayoutIndex = (props) => {
* @returns * @returns
*/ */
const LayoutPostList = (props) => { const LayoutPostList = (props) => {
return <LayoutBase {...props} > return <></>
<div className='mt-10'><BlogPostListPage {...props} /></div>
</LayoutBase>
} }
/** /**
@@ -213,7 +215,7 @@ const LayoutSlug = (props) => {
const { post, prev, next, lock, validPassword } = props const { post, prev, next, lock, validPassword } = props
return ( return (
<LayoutBase {...props} > <>
{/* 文章锁 */} {/* 文章锁 */}
{lock && <ArticleLock validPassword={validPassword} />} {lock && <ArticleLock validPassword={validPassword} />}
@@ -246,7 +248,7 @@ const LayoutSlug = (props) => {
<TocDrawer {...props} /> <TocDrawer {...props} />
</div>} </div>}
</LayoutBase> </>
) )
} }
@@ -257,7 +259,7 @@ const LayoutSlug = (props) => {
* @returns * @returns
*/ */
const LayoutSearch = (props) => { const LayoutSearch = (props) => {
return <LayoutBase {...props}></LayoutBase> return <></>
} }
/** /**
@@ -269,20 +271,20 @@ const LayoutSearch = (props) => {
const LayoutArchive = (props) => { const LayoutArchive = (props) => {
const { archivePosts } = props const { archivePosts } = props
return <LayoutBase {...props}> return <>
<div className="mb-10 pb-20 md:py-12 py-3 min-h-full"> <div className="mb-10 pb-20 md:py-12 py-3 min-h-full">
{Object.keys(archivePosts)?.map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />)} {Object.keys(archivePosts)?.map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />)}
</div> </div>
</LayoutBase> </>
} }
/** /**
* 404 * 404
*/ */
const Layout404 = props => { const Layout404 = props => {
return <LayoutBase {...props}> return <>
<div className='w-full h-96 py-80 flex justify-center items-center'>404 Not found.</div> <div className='w-full h-96 py-80 flex justify-center items-center'>404 Not found.</div>
</LayoutBase> </>
} }
/** /**
@@ -291,7 +293,7 @@ const Layout404 = props => {
const LayoutCategoryIndex = (props) => { const LayoutCategoryIndex = (props) => {
const { categoryOptions } = props const { categoryOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return <LayoutBase {...props}> return <>
<div className='bg-white dark:bg-gray-700 py-10'> <div className='bg-white dark:bg-gray-700 py-10'>
<div className='dark:text-gray-200 mb-5'> <div className='dark:text-gray-200 mb-5'>
<i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}: <i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}:
@@ -313,7 +315,7 @@ const LayoutCategoryIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
} }
/** /**
@@ -323,7 +325,7 @@ const LayoutTagIndex = (props) => {
const { tagOptions } = props const { tagOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return <LayoutBase {...props}> return <>
<div className="bg-white dark:bg-gray-700 py-10"> <div className="bg-white dark:bg-gray-700 py-10">
<div className="dark:text-gray-200 mb-5"> <div className="dark:text-gray-200 mb-5">
<i className="mr-4 fas fa-tag" /> <i className="mr-4 fas fa-tag" />
@@ -339,11 +341,12 @@ const LayoutTagIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutSearch, LayoutSearch,
LayoutArchive, LayoutArchive,

View File

@@ -15,6 +15,7 @@ import CONFIG from '../config'
* @returns * @returns
*/ */
const Hero = props => { const Hero = props => {
const HEO_HERO_REVERSE = siteConfig('HEO_HERO_REVERSE', false, CONFIG)
return ( return (
<div <div
id="hero-wrapper" id="hero-wrapper"
@@ -24,12 +25,17 @@ const Hero = props => {
id="hero" id="hero"
style={{ zIndex: 1 }} style={{ zIndex: 1 }}
className={ className={
'animate__animated animate__fadeIn animate__fast recent-post-top rounded-[12px] 2xl:px-5 recent-top-post-group max-w-[86rem] overflow-x-scroll w-full mx-auto flex-row flex-nowrap flex xl:space-x-3 relative' `animate__animated animate__fadeIn animate__fast
${HEO_HERO_REVERSE ? 'xl:flex-row-reverse' : ''}
recent-post-top rounded-[12px] 2xl:px-5 recent-top-post-group max-w-[86rem] overflow-x-scroll w-full mx-auto flex-row flex-nowrap flex relative`
} }
> >
{/* 左侧banner组 */} {/* 左侧banner组 */}
<BannerGroup {...props} /> <BannerGroup {...props} />
{/* 中间留白 */}
<div className='px-1.5 h-full'></div>
{/* 右侧置顶文章组 */} {/* 右侧置顶文章组 */}
<TopGroup {...props} /> <TopGroup {...props} />
</div> </div>

View File

@@ -9,6 +9,11 @@ const CONFIG = {
{ title: '访问文档中心获取更多帮助', url: 'https://docs.tangly1024.com' } { title: '访问文档中心获取更多帮助', url: 'https://docs.tangly1024.com' }
], ],
// 英雄区左右侧组件颠倒位置
HEO_HERO_REVERSE: false,
// 博客主体区左右侧组件颠倒位置
HEO_HERO_BODY_REVERSE: false,
// 英雄区(首页顶部大卡) // 英雄区(首页顶部大卡)
HEO_HERO_TITLE_1: '分享编程', HEO_HERO_TITLE_1: '分享编程',
HEO_HERO_TITLE_2: '与思维认知', HEO_HERO_TITLE_2: '与思维认知',

View File

@@ -50,13 +50,42 @@ import { siteConfig } from '@/lib/config'
const LayoutBase = props => { const LayoutBase = props => {
const { const {
children, children,
headerSlot,
slotTop, slotTop,
slotRight,
className, className,
meta meta
} = props } = props
// 全屏模式下的最大宽度
const { fullWidth } = useGlobal()
const router = useRouter()
console.log(router)
const headerSlot = (
<header>
{/* 顶部导航 */}
<div id="nav-bar-wrapper" className="h-16">
<NavBar {...props} />
</div>
{/* 通知横幅 */}
{router.route==='/' ? <>
<NoticeBar />
<Hero {...props} />
</>
: null}
<div className="max-w-[86rem] mx-auto px-3">
<WWAds className="w-full" orientation="horizontal" />
</div>
{fullWidth ? null : <PostHeader {...props} />}
</header>
)
// 右侧栏 用户信息+标签列表
const slotRight = fullWidth ? null : <SideRight {...props} />
const maxWidth = fullWidth ? 'max-w-[96rem] mx-auto' : 'max-w-[86rem]' // 普通最大宽度是86rem和顶部菜单栏对齐留空则与窗口对齐
const HEO_HERO_BODY_REVERSE = siteConfig('HEO_HERO_BODY_REVERSE', false, CONFIG)
return ( return (
<div <div
id="theme-heo" id="theme-heo"
@@ -72,12 +101,12 @@ const LayoutBase = props => {
{/* 主区块 */} {/* 主区块 */}
<main <main
id="wrapper-outer" id="wrapper-outer"
className={'flex-grow w-full max-w-[86rem] mx-auto relative md:px-5'} className={`flex-grow w-full ${maxWidth} mx-auto relative md:px-5`}
> >
<div <div
id="container-inner" id="container-inner"
className={ className={
'w-full mx-auto lg:flex lg:space-x-4 justify-center relative z-10' `${HEO_HERO_BODY_REVERSE ? 'flex-row-reverse' : ''} w-full mx-auto lg:flex justify-center relative z-10`
} }
> >
<div className={`w-full h-auto ${className || ''}`}> <div className={`w-full h-auto ${className || ''}`}>
@@ -86,11 +115,14 @@ const LayoutBase = props => {
{children} {children}
</div> </div>
<div className='lg:px-2'></div>
<div className="hidden xl:block"> <div className="hidden xl:block">
{/* 主区快右侧 */} {/* 主区快右侧 */}
{slotRight} {slotRight}
</div> </div>
</div> </div>
</main> </main>
{/* 页脚 */} {/* 页脚 */}
@@ -106,38 +138,18 @@ const LayoutBase = props => {
* @returns * @returns
*/ */
const LayoutIndex = props => { const LayoutIndex = props => {
const headerSlot = (
<header>
{/* 顶部导航 */}
<div id="nav-bar-wrapper" className="h-16">
<NavBar {...props} />
</div>
{/* 通知横幅 */}
<NoticeBar />
<Hero {...props} />
<div className="max-w-[86rem] mx-auto px-3">
<WWAds className="w-full" orientation="horizontal" />
</div>
</header>
)
// 右侧栏 用户信息+标签列表
const slotRight = <SideRight {...props} />
return ( return (
<LayoutBase {...props} slotRight={slotRight} headerSlot={headerSlot}>
<div id="post-outer-wrapper" className="px-5 md:px-0"> <div id="post-outer-wrapper" className="px-5 md:px-0">
{/* 文章分类条 */} {/* 文章分类条 */}
<CategoryBar {...props} /> <CategoryBar {...props} />
{siteConfig('POST_LIST_STYLE') === 'page' {siteConfig('POST_LIST_STYLE') === 'page'
? ( ? (
<BlogPostListPage {...props} /> <BlogPostListPage {...props} />
) )
: ( : (
<BlogPostListScroll {...props} /> <BlogPostListScroll {...props} />
)} )}
</div> </div>
</LayoutBase>
) )
} }
@@ -147,31 +159,19 @@ const LayoutIndex = props => {
* @returns * @returns
*/ */
const LayoutPostList = props => { const LayoutPostList = props => {
// 右侧栏
const slotRight = <SideRight {...props} />
const headerSlot = (
<header>
{/* 顶部导航 */}
<div id="nav-bar-wrapper" className="h-16">
<NavBar {...props} />
</div>
</header>
)
return ( return (
<LayoutBase {...props} slotRight={slotRight} headerSlot={headerSlot}>
<div id="post-outer-wrapper" className="px-5 md:px-0"> <div id="post-outer-wrapper" className="px-5 md:px-0">
{/* 文章分类条 */} {/* 文章分类条 */}
<CategoryBar {...props} /> <CategoryBar {...props} />
{siteConfig('POST_LIST_STYLE') === 'page' {siteConfig('POST_LIST_STYLE') === 'page'
? ( ? (
<BlogPostListPage {...props} /> <BlogPostListPage {...props} />
) )
: ( : (
<BlogPostListScroll {...props} /> <BlogPostListScroll {...props} />
)} )}
</div> </div>
</LayoutBase>
) )
} }
@@ -184,15 +184,6 @@ const LayoutSearch = props => {
const { keyword } = props const { keyword } = props
const router = useRouter() const router = useRouter()
const currentSearch = keyword || router?.query?.s const currentSearch = keyword || router?.query?.s
const headerSlot = (
<header className="post-bg">
{/* 顶部导航 */}
<div id="nav-bar-wrapper">
<NavBar {...props} />
</div>
<PostHeader {...props} />
</header>
)
useEffect(() => { useEffect(() => {
// 高亮搜索结果 // 高亮搜索结果
@@ -210,29 +201,28 @@ const LayoutSearch = props => {
} }
}, []) }, [])
return ( return (
<LayoutBase <div
{...props} {...props}
currentSearch={currentSearch} currentSearch={currentSearch}
headerSlot={headerSlot}
> >
<div id="post-outer-wrapper" className="px-5 md:px-0"> <div id="post-outer-wrapper" className="px-5 md:px-0">
{!currentSearch {!currentSearch
? ( ? (
<SearchNav {...props} /> <SearchNav {...props} />
) )
: ( : (
<div id="posts-wrapper"> <div id="posts-wrapper">
{siteConfig('POST_LIST_STYLE') === 'page' {siteConfig('POST_LIST_STYLE') === 'page'
? ( ? (
<BlogPostListPage {...props} /> <BlogPostListPage {...props} />
) )
: ( : (
<BlogPostListScroll {...props} /> <BlogPostListScroll {...props} />
)} )}
</div> </div>
)} )}
</div> </div>
</LayoutBase> </div>
) )
} }
@@ -244,21 +234,9 @@ const LayoutSearch = props => {
const LayoutArchive = props => { const LayoutArchive = props => {
const { archivePosts } = props const { archivePosts } = props
// 右侧栏
const slotRight = <SideRight {...props} />
const headerSlot = (
<header>
{/* 顶部导航 */}
<div id="nav-bar-wrapper" className="h-16">
<NavBar {...props} />
</div>
</header>
)
// 归档页顶部显示条,如果是默认归档则不显示。分类详情页显示分类列表,标签详情页显示当前标签 // 归档页顶部显示条,如果是默认归档则不显示。分类详情页显示分类列表,标签详情页显示当前标签
return ( return (
<LayoutBase {...props} slotRight={slotRight} headerSlot={headerSlot}>
<div className="p-5 rounded-xl border dark:border-gray-600 max-w-6xl w-full bg-white dark:bg-[#1e1e1e]"> <div className="p-5 rounded-xl border dark:border-gray-600 max-w-6xl w-full bg-white dark:bg-[#1e1e1e]">
{/* 文章分类条 */} {/* 文章分类条 */}
<CategoryBar {...props} border={false} /> <CategoryBar {...props} border={false} />
@@ -273,7 +251,6 @@ const LayoutArchive = props => {
))} ))}
</div> </div>
</div> </div>
</LayoutBase>
) )
} }
@@ -284,7 +261,7 @@ const LayoutArchive = props => {
*/ */
const LayoutSlug = props => { const LayoutSlug = props => {
const { post, lock, validPassword } = props const { post, lock, validPassword } = props
const { locale } = useGlobal() const { locale, fullWidth } = useGlobal()
const [hasCode, setHasCode] = useState(false) const [hasCode, setHasCode] = useState(false)
@@ -293,36 +270,18 @@ const LayoutSlug = props => {
setHasCode(hasCode) setHasCode(hasCode)
}, []) }, [])
// 右侧栏
const slotRight = <SideRight {...props} />
const headerSlot = (
<header
data-aos="fade-up"
data-aos-duration="300"
data-aos-once="false"
data-aos-anchor-placement="top-bottom"
className="post-bg"
>
{/* 顶部导航 */}
<div id="nav-bar-wrapper">
<NavBar {...props} />
</div>
<PostHeader {...props} />
</header>
)
const commentEnable = siteConfig('COMMENT_TWIKOO_ENV_ID') || siteConfig('COMMENT_WALINE_SERVER_URL') || siteConfig('COMMENT_VALINE_APP_ID') || const commentEnable = siteConfig('COMMENT_TWIKOO_ENV_ID') || siteConfig('COMMENT_WALINE_SERVER_URL') || siteConfig('COMMENT_VALINE_APP_ID') ||
siteConfig('COMMENT_GISCUS_REPO') || siteConfig('COMMENT_CUSDIS_APP_ID') || siteConfig('COMMENT_UTTERRANCES_REPO') || siteConfig('COMMENT_GISCUS_REPO') || siteConfig('COMMENT_CUSDIS_APP_ID') || siteConfig('COMMENT_UTTERRANCES_REPO') ||
siteConfig('COMMENT_GITALK_CLIENT_ID') || siteConfig('COMMENT_WEBMENTION_ENABLE') siteConfig('COMMENT_GITALK_CLIENT_ID') || siteConfig('COMMENT_WEBMENTION_ENABLE')
return ( return (
<LayoutBase <div
{...props} {...props}
headerSlot={headerSlot}
showCategory={false} showCategory={false}
showTag={false} showTag={false}
slotRight={slotRight}
> >
<div className={`w-full xl:max-w-5xl ${hasCode ? 'xl:w-[73.15vw]' : ''} lg:hover:shadow lg:border rounded-2xl lg:px-2 lg:py-4 bg-white dark:bg-[#18171d] dark:border-gray-600 article`}> <div className={`w-full ${fullWidth ? '' : 'xl:max-w-5xl'} ${hasCode ? 'xl:w-[73.15vw]' : ''} lg:hover:shadow lg:border rounded-2xl lg:px-2 lg:py-4 bg-white dark:bg-[#18171d] dark:border-gray-600 article`}>
{lock && <ArticleLock validPassword={validPassword} />} {lock && <ArticleLock validPassword={validPassword} />}
{!lock && ( {!lock && (
@@ -360,26 +319,28 @@ const LayoutSlug = props => {
)} )}
</article> </article>
<div className={`${commentEnable && post ? '' : 'hidden'}`}> {fullWidth
<hr className="my-4 border-dashed" /> ? null
: <div className={`${commentEnable && post ? '' : 'hidden'}`}>
<hr className="my-4 border-dashed" />
{/* 评论互动 */} {/* 评论互动 */}
<div className="duration-200 overflow-x-auto px-5"> <div className="duration-200 overflow-x-auto px-5">
<div className="text-2xl dark:text-white"> <div className="text-2xl dark:text-white">
<i className="fas fa-comment mr-1" /> <i className="fas fa-comment mr-1" />
{locale.COMMON.COMMENTS} {locale.COMMON.COMMENTS}
</div>
<Comment frontMatter={post} className="" />
<div className="py-2">
<AdSlot />
</div>
</div> </div>
<Comment frontMatter={post} className="" /> </div>}
<div className="py-2">
<AdSlot />
</div>
</div>
</div>
</div> </div>
)} )}
</div> </div>
<FloatTocButton {...props} /> <FloatTocButton {...props} />
</LayoutBase> </div>
) )
} }
@@ -390,7 +351,7 @@ const LayoutSlug = props => {
*/ */
const Layout404 = props => { const Layout404 = props => {
const { meta, siteInfo } = props const { meta, siteInfo } = props
const { onLoading } = useGlobal() const { onLoading, fullWidth } = useGlobal()
return ( return (
<div <div
id="theme-heo" id="theme-heo"
@@ -411,7 +372,7 @@ const Layout404 = props => {
{/* 主区块 */} {/* 主区块 */}
<main <main
id="wrapper-outer" id="wrapper-outer"
className={'flex-grow max-w-4xl w-screen mx-auto px-5'} className={`flex-grow ${fullWidth ? '' : 'max-w-4xl'} w-screen mx-auto px-5`}
> >
<div id="error-wrapper" className={'w-full mx-auto justify-center'}> <div id="error-wrapper" className={'w-full mx-auto justify-center'}>
<Transition <Transition
@@ -466,18 +427,10 @@ const Layout404 = props => {
const LayoutCategoryIndex = props => { const LayoutCategoryIndex = props => {
const { categoryOptions } = props const { categoryOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
const headerSlot = (
<header>
{/* 顶部导航 */}
<div id="nav-bar-wrapper" className="h-16">
<NavBar {...props} />
</div>
</header>
)
return ( return (
<LayoutBase {...props} className="mt-8" headerSlot={headerSlot}> <div id="category-outer-wrapper" className="mt-8 px-5 md:px-0">
<div id="category-outer-wrapper" className="px-5 md:px-0">
<div className="text-4xl font-extrabold dark:text-gray-200 mb-5"> <div className="text-4xl font-extrabold dark:text-gray-200 mb-5">
{locale.COMMON.CATEGORY} {locale.COMMON.CATEGORY}
</div> </div>
@@ -509,7 +462,6 @@ const LayoutCategoryIndex = props => {
})} })}
</div> </div>
</div> </div>
</LayoutBase>
) )
} }
@@ -521,17 +473,9 @@ const LayoutCategoryIndex = props => {
const LayoutTagIndex = props => { const LayoutTagIndex = props => {
const { tagOptions } = props const { tagOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
const headerSlot = (
<header>
{/* 顶部导航 */}
<div id="nav-bar-wrapper" className="h-16">
<NavBar {...props} />
</div>
</header>
)
return ( return (
<LayoutBase {...props} className="mt-8" headerSlot={headerSlot}> <div id="tag-outer-wrapper" className="px-5 mt-8 md:px-0">
<div id="tag-outer-wrapper" className="px-5 md:px-0">
<div className="text-4xl font-extrabold dark:text-gray-200 mb-5"> <div className="text-4xl font-extrabold dark:text-gray-200 mb-5">
{locale.COMMON.TAGS} {locale.COMMON.TAGS}
</div> </div>
@@ -563,12 +507,12 @@ const LayoutTagIndex = props => {
})} })}
</div> </div>
</div> </div>
</LayoutBase>
) )
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutSearch, LayoutSearch,
LayoutArchive, LayoutArchive,

View File

@@ -7,11 +7,17 @@ import { formatDateFmt } from '@/lib/formatDate'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
export default function PostHeader({ post, siteInfo }) { export default function PostHeader({ post, siteInfo }) {
const { locale } = useGlobal() const { locale, fullWidth } = useGlobal()
if (!post) { if (!post) {
return <></> return <></>
} }
// 文章全屏隐藏标头
if (fullWidth) {
return <div className='my-8'/>
}
const headerImage = post?.pageCover ? post.pageCover : siteInfo?.pageCover const headerImage = post?.pageCover ? post.pageCover : siteInfo?.pageCover
return ( return (

View File

@@ -0,0 +1,30 @@
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import AlgoliaSearchModal from '@/components/AlgoliaSearchModal'
import { useRef } from 'react'
import { useHexoGlobal } from '..'
/**
* 搜索按钮
* @returns
*/
export default function SearchButton(props) {
const { locale } = useGlobal()
const router = useRouter()
const { searchModal} = useHexoGlobal()
function handleSearch() {
if (siteConfig('ALGOLIA_APP_ID')) {
searchModal.current.openSearch()
} else {
router.push('/search')
}
}
return <>
<div onClick={handleSearch} title={locale.NAV.SEARCH} alt={locale.NAV.SEARCH} className='cursor-pointer hover:bg-black hover:bg-opacity-10 rounded-full w-10 h-10 flex justify-center items-center duration-200 transition-all'>
<i title={locale.NAV.SEARCH} className="fa-solid fa-magnifying-glass" />
</div>
</>
}

View File

@@ -38,6 +38,12 @@ export default function SideRight(props) {
} = props } = props
const { locale } = useGlobal() const { locale } = useGlobal()
// 文章全屏处理
if (post && post?.fullWidth) {
return null
}
return ( return (
<div id='sideRight' className={'space-y-4 lg:w-80 lg:pt-0 px-2 pt-4'}> <div id='sideRight' className={'space-y-4 lg:w-80 lg:pt-0 px-2 pt-4'}>
<InfoCard {...props} /> <InfoCard {...props} />

View File

@@ -10,6 +10,9 @@ import { useRouter } from 'next/router'
import throttle from 'lodash.throttle' import throttle from 'lodash.throttle'
import SideBar from './SideBar' import SideBar from './SideBar'
import SideBarDrawer from './SideBarDrawer' import SideBarDrawer from './SideBarDrawer'
import { siteConfig } from '@/lib/config'
import SearchButton from './SearchButton'
import CONFIG from '../config'
let windowTop = 0 let windowTop = 0
@@ -26,6 +29,7 @@ const TopNav = props => {
const router = useRouter() const router = useRouter()
const [isOpen, changeShow] = useState(false) const [isOpen, changeShow] = useState(false)
const showSearchButton = siteConfig('HEXO_MENU_SEARCH',false,CONFIG)
const toggleMenuOpen = () => { const toggleMenuOpen = () => {
changeShow(!isOpen) changeShow(!isOpen)
@@ -140,11 +144,12 @@ const TopNav = props => {
</div> </div>
{/* 右侧功能 */} {/* 右侧功能 */}
<div className='mr-1 justify-end items-center '> <div className='mr-1 flex justify-end items-center '>
<div className='hidden lg:flex'> <MenuListTop {...props} /></div> <div className='hidden lg:flex'> <MenuListTop {...props} /></div>
<div onClick={toggleMenuOpen} className='w-8 justify-center items-center h-8 cursor-pointer flex lg:hidden'> <div onClick={toggleMenuOpen} className='w-8 justify-center items-center h-8 cursor-pointer flex lg:hidden'>
{isOpen ? <i className='fas fa-times' /> : <i className='fas fa-bars' />} {isOpen ? <i className='fas fa-times' /> : <i className='fas fa-bars' />}
</div> </div>
{showSearchButton && <SearchButton />}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import CONFIG from './config' import CONFIG from './config'
import CommonHead from '@/components/CommonHead' import CommonHead from '@/components/CommonHead'
import { useEffect, useRef } from 'react' import { createContext, useContext, useEffect, useRef } from 'react'
import Footer from './components/Footer' import Footer from './components/Footer'
import SideRight from './components/SideRight' import SideRight from './components/SideRight'
import TopNav from './components/TopNav' import TopNav from './components/TopNav'
@@ -32,6 +32,12 @@ import { Transition } from '@headlessui/react'
import { Style } from './style' import { Style } from './style'
import replaceSearchResult from '@/components/Mark' import replaceSearchResult from '@/components/Mark'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import AlgoliaSearchModal from '@/components/AlgoliaSearchModal'
// 主题全局状态
const ThemeGlobalHexo = createContext()
export const useHexoGlobal = () => useContext(ThemeGlobalHexo)
/** /**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏 * 基础布局 采用左右两侧布局,移动端使用顶部导航栏
@@ -40,10 +46,30 @@ import { siteConfig } from '@/lib/config'
* @constructor * @constructor
*/ */
const LayoutBase = props => { const LayoutBase = props => {
const { children, headerSlot, floatSlot, slotTop, meta, className } = props const { post , children, slotTop, meta, className } = props
const { onLoading } = useGlobal() const { onLoading, fullWidth } = useGlobal()
const router = useRouter()
const headerSlot = post
? <PostHeader {...props} /> : (router.route==='/' && siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG)
? <Hero {...props} /> : null)
const floatSlot = <>
{post?.toc?.length > 1 && <div className="block lg:hidden">
<TocDrawerButton
onClick={() => {
drawerRight?.current?.handleSwitchVisible()
}}
/>
</div>}
<JumpToCommentButton />
</>
// Algolia搜索框
const searchModal = useRef(null)
return ( return (
<ThemeGlobalHexo.Provider value={{ searchModal }}>
<div id='theme-hexo'> <div id='theme-hexo'>
{/* 网页SEO */} {/* 网页SEO */}
<CommonHead meta={meta}/> <CommonHead meta={meta}/>
@@ -70,7 +96,7 @@ const LayoutBase = props => {
{/* 主区块 */} {/* 主区块 */}
<main id="wrapper" className={`${siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG) ? '' : 'pt-16'} bg-hexo-background-gray dark:bg-black w-full py-8 md:px-8 lg:px-24 min-h-screen relative`}> <main id="wrapper" className={`${siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG) ? '' : 'pt-16'} bg-hexo-background-gray dark:bg-black w-full py-8 md:px-8 lg:px-24 min-h-screen relative`}>
<div id="container-inner" className={(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE')) ? 'flex-row-reverse' : '') + ' w-full mx-auto lg:flex lg:space-x-4 justify-center relative z-10'} > <div id="container-inner" className={(JSON.parse(siteConfig('LAYOUT_SIDEBAR_REVERSE')) ? 'flex-row-reverse' : '') + ' w-full mx-auto lg:flex lg:space-x-4 justify-center relative z-10'} >
<div className={`${className || ''} w-full max-w-4xl h-full overflow-hidden`}> <div className={`${className || ''} w-full ${fullWidth ? '' : 'max-w-4xl'} h-full overflow-hidden`}>
<Transition <Transition
show={!onLoading} show={!onLoading}
@@ -98,9 +124,13 @@ const LayoutBase = props => {
{/* 悬浮菜单 */} {/* 悬浮菜单 */}
<RightFloatArea floatSlot={floatSlot} /> <RightFloatArea floatSlot={floatSlot} />
{/* 全文搜索 */}
<AlgoliaSearchModal cRef={searchModal} {...props}/>
{/* 页脚 */} {/* 页脚 */}
<Footer title={siteConfig('TITLE') } /> <Footer title={siteConfig('TITLE') } />
</div> </div>
</ThemeGlobalHexo.Provider>
) )
} }
@@ -111,8 +141,7 @@ const LayoutBase = props => {
* @returns * @returns
*/ */
const LayoutIndex = (props) => { const LayoutIndex = (props) => {
const headerSlot = siteConfig('HEXO_HOME_BANNER_ENABLE', null, CONFIG) && <Hero {...props} /> return <LayoutPostList {...props} className='pt-8' />
return <LayoutPostList {...props} headerSlot={headerSlot} className='pt-8' />
} }
/** /**
@@ -121,10 +150,10 @@ const LayoutIndex = (props) => {
* @returns * @returns
*/ */
const LayoutPostList = (props) => { const LayoutPostList = (props) => {
return <LayoutBase {...props} className='pt-8'> return <div className='pt-8'>
<SlotBar {...props} /> <SlotBar {...props} />
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase> </div>
} }
/** /**
@@ -151,11 +180,11 @@ const LayoutSearch = props => {
}) })
return ( return (
<LayoutBase {...props} currentSearch={currentSearch} className='pt-8'> <div className='pt-8'>
{!currentSearch {!currentSearch
? <SearchNav {...props} /> ? <SearchNav {...props} />
: <div id="posts-wrapper"> {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />} </div>} : <div id="posts-wrapper"> {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />} </div>}
</LayoutBase> </div>
) )
} }
@@ -166,7 +195,7 @@ const LayoutSearch = props => {
*/ */
const LayoutArchive = (props) => { const LayoutArchive = (props) => {
const { archivePosts } = props const { archivePosts } = props
return <LayoutBase {...props} className='pt-8'> return <div className='pt-8'>
<Card className='w-full'> <Card className='w-full'>
<div className="mb-10 pb-20 bg-white md:p-12 p-3 min-h-full dark:bg-hexo-black-gray"> <div className="mb-10 pb-20 bg-white md:p-12 p-3 min-h-full dark:bg-hexo-black-gray">
{Object.keys(archivePosts).map(archiveTitle => ( {Object.keys(archivePosts).map(archiveTitle => (
@@ -178,7 +207,7 @@ const LayoutArchive = (props) => {
))} ))}
</div> </div>
</Card> </Card>
</LayoutBase> </div>
} }
/** /**
@@ -189,22 +218,10 @@ const LayoutArchive = (props) => {
const LayoutSlug = props => { const LayoutSlug = props => {
const { post, lock, validPassword } = props const { post, lock, validPassword } = props
const drawerRight = useRef(null) const drawerRight = useRef(null)
const tocRef = isBrowser ? document.getElementById('article-wrapper') : null
const targetRef = isBrowser ? document.getElementById('article-wrapper') : null
const floatSlot = <>
{post?.toc?.length > 1 && <div className="block lg:hidden">
<TocDrawerButton
onClick={() => {
drawerRight?.current?.handleSwitchVisible()
}}
/>
</div>}
<JumpToCommentButton />
</>
return ( return (
<LayoutBase {...props} headerSlot={<PostHeader {...props} />} showCategory={false} showTag={false} floatSlot={floatSlot} > <>
<div className="w-full lg:hover:shadow lg:border rounded-t-xl lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black article"> <div className="w-full lg:hover:shadow lg:border rounded-t-xl lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black article">
{lock && <ArticleLock validPassword={validPassword} />} {lock && <ArticleLock validPassword={validPassword} />}
@@ -236,10 +253,10 @@ const LayoutSlug = props => {
</div> </div>
<div className='block lg:hidden'> <div className='block lg:hidden'>
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} /> <TocDrawer post={post} cRef={drawerRight} targetRef={tocRef} />
</div> </div>
</LayoutBase> </>
) )
} }
@@ -264,7 +281,7 @@ const Layout404 = props => {
}, 3000) }, 3000)
}) })
return ( return (
<LayoutBase {...props}> <>
<div className="text-black w-full h-screen text-center justify-center content-center items-center flex flex-col"> <div className="text-black w-full h-screen text-center justify-center content-center items-center flex flex-col">
<div className="dark:text-gray-200"> <div className="dark:text-gray-200">
<h2 className="inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top"> <h2 className="inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top">
@@ -275,7 +292,7 @@ const Layout404 = props => {
</div> </div>
</div> </div>
</div> </div>
</LayoutBase> </>
) )
} }
@@ -288,7 +305,7 @@ const LayoutCategoryIndex = props => {
const { categoryOptions } = props const { categoryOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return ( return (
<LayoutBase {...props} className='mt-8'> <div className='mt-8'>
<Card className="w-full min-h-screen"> <Card className="w-full min-h-screen">
<div className="dark:text-gray-200 mb-5 mx-3"> <div className="dark:text-gray-200 mb-5 mx-3">
<i className="mr-4 fas fa-th" /> {locale.COMMON.CATEGORY}: <i className="mr-4 fas fa-th" /> {locale.COMMON.CATEGORY}:
@@ -305,7 +322,7 @@ const LayoutCategoryIndex = props => {
})} })}
</div> </div>
</Card> </Card>
</LayoutBase> </div>
) )
} }
@@ -318,7 +335,7 @@ const LayoutTagIndex = props => {
const { tagOptions } = props const { tagOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return ( return (
<LayoutBase {...props} className='mt-8'> <div className='mt-8'>
<Card className='w-full'> <Card className='w-full'>
<div className="dark:text-gray-200 mb-5 ml-4"> <div className="dark:text-gray-200 mb-5 ml-4">
<i className="mr-4 fas fa-tag" /> {locale.COMMON.TAGS}: <i className="mr-4 fas fa-tag" /> {locale.COMMON.TAGS}:
@@ -329,12 +346,13 @@ const LayoutTagIndex = props => {
</div>)} </div>)}
</div> </div>
</Card> </Card>
</LayoutBase> </div>
) )
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutSearch, LayoutSearch,
LayoutArchive, LayoutArchive,

View File

@@ -36,12 +36,12 @@ export default function Header() {
{/* Desktop sign in links */} {/* Desktop sign in links */}
<ul className="flex grow justify-end flex-wrap items-center"> <ul className="flex grow justify-end flex-wrap items-center">
<li> <li>
<Link href={siteConfig('LANDING_HEDEAR_BUTTON_1_URL', null, CONFIG)} target='_blank' className="font-medium hover:font-bold text-gray-600 hover:text-gray-900 px-5 py-3 flex items-center transition duration-150 ease-in-out"> <Link href={siteConfig('LANDING_HEADER_BUTTON_1_URL', null, CONFIG)} target='_blank' className="font-medium hover:font-bold text-gray-600 hover:text-gray-900 px-5 py-3 flex items-center transition duration-150 ease-in-out">
<div>{siteConfig('LANDING_HEADER_BUTTON_1_TITLE', null, CONFIG)}</div> <div>{siteConfig('LANDING_HEADER_BUTTON_1_TITLE', null, CONFIG)}</div>
</Link> </Link>
</li> </li>
<li> <li>
<Link href={siteConfig('LANDING_HEDEAR_BUTTON_2_URL', null, CONFIG)} target='_blank' className="btn-sm text-gray-200 bg-gray-900 hover:bg-gray-800 ml-3"> <Link href={siteConfig('LANDING_HEADER_BUTTON_2_URL', null, CONFIG)} target='_blank' className="btn-sm text-gray-200 bg-gray-900 hover:bg-gray-800 ml-3">
<span>{siteConfig('LANDING_HEADER_BUTTON_2_TITLE', null, CONFIG)}</span> <span>{siteConfig('LANDING_HEADER_BUTTON_2_TITLE', null, CONFIG)}</span>
<svg className="w-3 h-3 fill-current text-gray-400 shrink-0 ml-2 -mr-1" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"> <svg className="w-3 h-3 fill-current text-gray-400 shrink-0 ml-2 -mr-1" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
<path d="M11.707 5.293L7 .586 5.586 2l3 3H0v2h8.586l-3 3L7 11.414l4.707-4.707a1 1 0 000-1.414z" fillRule="nonzero" /> <path d="M11.707 5.293L7 .586 5.586 2l3 3H0v2h8.586l-3 3L7 11.414l4.707-4.707a1 1 0 000-1.414z" fillRule="nonzero" />

View File

@@ -67,12 +67,12 @@ export default function MobileMenu() {
> >
<ul className="px-5 py-2"> <ul className="px-5 py-2">
<li> <li>
<Link href={siteConfig('LANDING_HEDEAR_BUTTON_1_URL', null, CONFIG)} className="flex font-medium w-full text-gray-600 hover:text-gray-900 py-2 justify-center" onClick={() => setMobileNavOpen(false)}> <Link href={siteConfig('LANDING_HEADER_BUTTON_1_URL', null, CONFIG)} className="flex font-medium w-full text-gray-600 hover:text-gray-900 py-2 justify-center" onClick={() => setMobileNavOpen(false)}>
<div>{siteConfig('LANDING_HEADER_BUTTON_1_TITLE', null, CONFIG)}</div> <div>{siteConfig('LANDING_HEADER_BUTTON_1_TITLE', null, CONFIG)}</div>
</Link> </Link>
</li> </li>
<li> <li>
<Link href={siteConfig('LANDING_HEDEAR_BUTTON_2_URL', null, CONFIG)} className="btn-sm text-gray-200 bg-gray-900 hover:bg-gray-800 w-full my-2" onClick={() => setMobileNavOpen(false)}> <Link href={siteConfig('LANDING_HEADER_BUTTON_2_URL', null, CONFIG)} className="btn-sm text-gray-200 bg-gray-900 hover:bg-gray-800 w-full my-2" onClick={() => setMobileNavOpen(false)}>
<span>{siteConfig('LANDING_HEADER_BUTTON_2_TITLE', null, CONFIG)}</span> <span>{siteConfig('LANDING_HEADER_BUTTON_2_TITLE', null, CONFIG)}</span>
<svg className="w-3 h-3 fill-current text-gray-400 shrink-0 ml-2 -mr-1" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"> <svg className="w-3 h-3 fill-current text-gray-400 shrink-0 ml-2 -mr-1" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
<path d="M11.707 5.293L7 .586 5.586 2l3 3H0v2h8.586l-3 3L7 11.414l4.707-4.707a1 1 0 000-1.414z" fill="#999" fillRule="nonzero" /> <path d="M11.707 5.293L7 .586 5.586 2l3 3H0v2h8.586l-3 3L7 11.414l4.707-4.707a1 1 0 000-1.414z" fill="#999" fillRule="nonzero" />

View File

@@ -1,10 +1,10 @@
const CONFIG = { const CONFIG = {
LANDING_HEADER_BUTTON_1_TITLE: 'Github开源', LANDING_HEADER_BUTTON_1_TITLE: 'Github开源',
LANDING_HEDEAR_BUTTON_1_URL: 'https://github.com/tangly1024/NotionNext', LANDING_HEADER_BUTTON_1_URL: 'https://github.com/tangly1024/NotionNext',
LANDING_HEADER_BUTTON_2_TITLE: '作者博客', LANDING_HEADER_BUTTON_2_TITLE: '作者博客',
LANDING_HEDEAR_BUTTON_2_URL: 'https://blog.tangly1024.com/', LANDING_HEADER_BUTTON_2_URL: 'https://blog.tangly1024.com/',
// 首页大图英雄板块 // 首页大图英雄板块
LANDING_HERO_TITLE_1: 'NotionNext', LANDING_HERO_TITLE_1: 'NotionNext',

View File

@@ -57,13 +57,13 @@ const LayoutBase = (props) => {
*/ */
const LayoutIndex = (props) => { const LayoutIndex = (props) => {
return ( return (
<LayoutBase {...props}> <>
<Hero /> <Hero />
<Features /> <Features />
<FeaturesBlocks /> <FeaturesBlocks />
<Testimonials /> <Testimonials />
<Newsletter /> <Newsletter />
</LayoutBase> </>
) )
} }
@@ -81,24 +81,24 @@ const LayoutSlug = (props) => {
return <div id='theme-landing'><Loading /></div> return <div id='theme-landing'><Loading /></div>
} }
return <LayoutBase {...props}> return <>
<div id='container-inner' className='mx-auto max-w-screen-lg p-12'> <div id='container-inner' className='mx-auto max-w-screen-lg p-12'>
<NotionPage {...props} /> <NotionPage {...props} />
</div> </div>
</LayoutBase> </>
} }
// 其他布局暂时留空 // 其他布局暂时留空
const LayoutSearch = (props) => <LayoutBase {...props}><Hero /></LayoutBase> const LayoutSearch = (props) => <><Hero /></>
const LayoutArchive = (props) => <LayoutBase {...props}><Hero /></LayoutBase> const LayoutArchive = (props) => <><Hero /></>
const Layout404 = (props) => <LayoutBase {...props}><Hero /></LayoutBase> const Layout404 = (props) => <><Hero /></>
const LayoutCategoryIndex = (props) => <LayoutBase {...props}><Hero /></LayoutBase> const LayoutCategoryIndex = (props) => <><Hero /></>
const LayoutPostList = (props) => <LayoutBase {...props}><Hero /></LayoutBase> const LayoutPostList = (props) => <><Hero /></>
const LayoutTagIndex = (props) => <LayoutBase {...props}><Hero /></LayoutBase> const LayoutTagIndex = (props) => <><Hero /></>
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutSearch, LayoutSearch,
LayoutArchive, LayoutArchive,

View File

@@ -40,8 +40,14 @@ import { siteConfig } from '@/lib/config'
* @constructor * @constructor
*/ */
const LayoutBase = props => { const LayoutBase = props => {
const { children, headerSlot, meta, siteInfo, containerSlot, post } = props const { children, meta, siteInfo, post } = props
const { onLoading } = useGlobal() const { onLoading, fullWidth } = useGlobal()
const router = useRouter()
const containerSlot= router.route==='/' ? <Announcement {...props} /> : <BlogListBar {...props} />
const headerSlot= siteConfig('MATERY_HOME_BANNER_ENABLE', null, CONFIG) && router.route==='/'
? <Hero {...props} /> : (post && !fullWidth ? <PostHeader {...props} /> : null)
const floatRightBottom = post ? <JumpToCommentButton /> : null
return ( return (
<div id='theme-matery' className="min-h-screen flex flex-col justify-between bg-hexo-background-gray dark:bg-black w-full"> <div id='theme-matery' className="min-h-screen flex flex-col justify-between bg-hexo-background-gray dark:bg-black w-full">
@@ -58,7 +64,7 @@ const LayoutBase = props => {
appear={true} appear={true}
enter="transition ease-in-out duration-700 transform order-first" enter="transition ease-in-out duration-700 transform order-first"
enterFrom="opacity-0 -translate-y-16" enterFrom="opacity-0 -translate-y-16"
enterTo="opacity-100" enterTo="opacity-100 w-full"
leave="transition ease-in-out duration-300 transform" leave="transition ease-in-out duration-300 transform"
leaveFrom="opacity-100 translate-y-0" leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-16" leaveTo="opacity-0 translate-y-16"
@@ -69,17 +75,17 @@ const LayoutBase = props => {
<main id="wrapper" className={`${siteConfig('MATERY_HOME_BANNER_ENABLE', null, CONFIG) ? '' : 'pt-16'} flex-1 w-full py-8 md:px-8 lg:px-24 relative`}> <main id="wrapper" className={`${siteConfig('MATERY_HOME_BANNER_ENABLE', null, CONFIG) ? '' : 'pt-16'} flex-1 w-full py-8 md:px-8 lg:px-24 relative`}>
{/* 嵌入区域 */} {/* 嵌入区域 */}
<div id="container-slot" className={`w-full max-w-6xl ${post && ' lg:max-w-3xl 2xl:max-w-4xl '} mt-6 px-3 mx-auto lg:flex lg:space-x-4 justify-center relative z-10`}> <div id="container-slot" className={`w-full ${fullWidth ? '' : 'max-w-6xl'} ${post && ' lg:max-w-3xl 2xl:max-w-4xl '} mt-6 px-3 mx-auto lg:flex lg:space-x-4 justify-center relative z-10`}>
{containerSlot} {containerSlot}
</div> </div>
<div id="container-inner" className="w-full min-h-fit max-w-6xl mx-auto lg:flex lg:space-x-4 justify-center relative z-10"> <div id="container-inner" className={`w-full min-h-fit ${fullWidth ? '' : 'max-w-6xl'} mx-auto lg:flex lg:space-x-4 justify-center relative z-10`}>
<Transition <Transition
show={!onLoading} show={!onLoading}
appear={true} appear={true}
enter="transition ease-in-out duration-700 transform order-first" enter="transition ease-in-out duration-700 transform order-first"
enterFrom="opacity-0 translate-y-16" enterFrom="opacity-0 translate-y-16"
enterTo="opacity-100" enterTo="opacity-100 w-full"
leave="transition ease-in-out duration-300 transform" leave="transition ease-in-out duration-300 transform"
leaveFrom="opacity-100 translate-y-0" leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 -translate-y-16" leaveTo="opacity-0 -translate-y-16"
@@ -98,7 +104,7 @@ const LayoutBase = props => {
</div> </div>
{/* 右下角悬浮 */} {/* 右下角悬浮 */}
<RightFloatButtons {...props} /> <RightFloatButtons {...props} floatRightBottom={floatRightBottom}/>
{/* 页脚 */} {/* 页脚 */}
<Footer title={siteConfig('TITLE')} /> <Footer title={siteConfig('TITLE')} />
@@ -113,7 +119,7 @@ const LayoutBase = props => {
* @returns * @returns
*/ */
const LayoutIndex = (props) => { const LayoutIndex = (props) => {
return <LayoutPostList {...props} containerSlot={<Announcement {...props} />} headerSlot={siteConfig('MATERY_HOME_BANNER_ENABLE', null, CONFIG) && <Hero {...props} />} /> return <LayoutPostList {...props}/>
} }
/** /**
@@ -123,9 +129,9 @@ const LayoutIndex = (props) => {
*/ */
const LayoutPostList = (props) => { const LayoutPostList = (props) => {
return ( return (
<LayoutBase {...props} containerSlot={<BlogListBar {...props} />}> <>
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase> </>
) )
} }
@@ -152,13 +158,13 @@ const LayoutSearch = props => {
} }
}) })
return ( return (
<LayoutBase {...props} currentSearch={currentSearch}> <>
{!currentSearch {!currentSearch
? <SearchNave {...props} /> ? <SearchNave {...props} />
: <div id="posts-wrapper"> : <div id="posts-wrapper">
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</div>} </div>}
</LayoutBase> </>
) )
} }
@@ -169,7 +175,7 @@ const LayoutSearch = props => {
*/ */
const LayoutArchive = (props) => { const LayoutArchive = (props) => {
const { archivePosts } = props const { archivePosts } = props
return <LayoutBase {...props} headerSlot={<PostHeader {...props} />} > return <>
<Card className='w-full -mt-32'> <Card className='w-full -mt-32'>
<div className="mb-10 pb-20 bg-white md:p-12 p-3 min-h-full dark:bg-hexo-black-gray"> <div className="mb-10 pb-20 bg-white md:p-12 p-3 min-h-full dark:bg-hexo-black-gray">
{Object.keys(archivePosts).map(archiveTitle => ( {Object.keys(archivePosts).map(archiveTitle => (
@@ -181,7 +187,7 @@ const LayoutArchive = (props) => {
))} ))}
</div> </div>
</Card> </Card>
</LayoutBase> </>
} }
/** /**
@@ -191,13 +197,14 @@ const LayoutArchive = (props) => {
*/ */
const LayoutSlug = props => { const LayoutSlug = props => {
const { post, lock, validPassword } = props const { post, lock, validPassword } = props
const { fullWidth } = useGlobal()
return (<LayoutBase {...props} headerSlot={<PostHeader {...props} />} showCategory={false} showTag={false} floatRightBottom={<JumpToCommentButton />}> return (<>
<div id='inner-wrapper' className={'w-full lg:max-w-3xl 2xl:max-w-4xl'} > <div id='inner-wrapper' className={`w-full ${fullWidth ? '' : 'lg:max-w-3xl 2xl:max-w-4xl'}`} >
{/* 文章主体卡片 */} {/* 文章主体卡片 */}
<div className="-mt-32 transition-all duration-300 rounded-md mx-3 lg:border lg:rounded-xl lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black"> <div className={`${fullWidth ? '' : '-mt-32'} transition-all duration-300 rounded-md mx-3 lg:border lg:rounded-xl lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black`}>
{lock && <ArticleLock validPassword={validPassword} />} {lock && <ArticleLock validPassword={validPassword} />}
@@ -221,7 +228,7 @@ const LayoutSlug = props => {
<article itemScope > <article itemScope >
{/* Notion文章主体 */} {/* Notion文章主体 */}
<section className='justify-center mx-auto max-w-2xl lg:max-w-full'> <section className={`justify-center mx-auto ${fullWidth ? '' : 'max-w-2xl lg:max-w-full'}`}>
{post && <NotionPage post={post} />} {post && <NotionPage post={post} />}
</section> </section>
@@ -255,7 +262,7 @@ const LayoutSlug = props => {
</div> </div>
</LayoutBase> </>
) )
} }
@@ -278,7 +285,7 @@ const Layout404 = props => {
}, 3000) }, 3000)
}) })
return ( return (
<LayoutBase {...props}> <>
<div className="text-black w-full h-screen text-center justify-center content-center items-center flex flex-col"> <div className="text-black w-full h-screen text-center justify-center content-center items-center flex flex-col">
<div className="dark:text-gray-200"> <div className="dark:text-gray-200">
<h2 className="inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top"> <h2 className="inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top">
@@ -289,7 +296,7 @@ const Layout404 = props => {
</div> </div>
</div> </div>
</div> </div>
</LayoutBase> </>
) )
} }
@@ -302,7 +309,7 @@ const LayoutCategoryIndex = props => {
const { categoryOptions } = props const { categoryOptions } = props
return ( return (
<LayoutBase {...props} headerSlot={<PostHeader {...props} />} > <>
<div id='inner-wrapper' className='w-full'> <div id='inner-wrapper' className='w-full'>
<div className="drop-shadow-xl -mt-32 rounded-md mx-3 px-5 lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black dark:text-gray-300"> <div className="drop-shadow-xl -mt-32 rounded-md mx-3 px-5 lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black dark:text-gray-300">
@@ -319,7 +326,7 @@ const LayoutCategoryIndex = props => {
</div> </div>
</div> </div>
</div> </div>
</LayoutBase> </>
) )
} }
@@ -332,7 +339,7 @@ const LayoutTagIndex = props => {
const { tagOptions } = props const { tagOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return ( return (
<LayoutBase {...props} headerSlot={<PostHeader {...props} />} > <>
<div id='inner-wrapper' className='w-full drop-shadow-xl'> <div id='inner-wrapper' className='w-full drop-shadow-xl'>
<div className="-mt-32 rounded-md mx-3 px-5 lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black"> <div className="-mt-32 rounded-md mx-3 px-5 lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
@@ -352,12 +359,13 @@ const LayoutTagIndex = props => {
</div> </div>
</div> </div>
</div> </div>
</LayoutBase> </>
) )
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutPostList, LayoutPostList,
LayoutSearch, LayoutSearch,

View File

@@ -47,18 +47,20 @@ export const useMediumGlobal = () => useContext(ThemeGlobalMedium)
* @constructor * @constructor
*/ */
const LayoutBase = props => { const LayoutBase = props => {
const { children, showInfoCard = true, slotRight, slotTop, notice, meta } = props const { children, showInfoCard = true, slotRight, notice, meta } = props
const { locale } = useGlobal() const { locale } = useGlobal()
const router = useRouter() const router = useRouter()
const [tocVisible, changeTocVisible] = useState(false) const [tocVisible, changeTocVisible] = useState(false)
const { onLoading } = useGlobal() const { onLoading, fullWidth } = useGlobal()
const slotTop = <BlogPostBar {...props} />
return ( return (
<ThemeGlobalMedium.Provider value={{ tocVisible, changeTocVisible }}> <ThemeGlobalMedium.Provider value={{ tocVisible, changeTocVisible }}>
{/* SEO相关 */} {/* SEO相关 */}
<CommonHead meta={meta}/> <CommonHead meta={meta} />
{/* CSS样式 */} {/* CSS样式 */}
<Style/> <Style />
<div id='theme-medium' className='bg-white dark:bg-hexo-black-gray w-full h-full min-h-screen justify-center dark:text-gray-300'> <div id='theme-medium' className='bg-white dark:bg-hexo-black-gray w-full h-full min-h-screen justify-center dark:text-gray-300'>
@@ -72,7 +74,7 @@ const LayoutBase = props => {
{/* 顶部导航栏 */} {/* 顶部导航栏 */}
<TopNavBar {...props} /> <TopNavBar {...props} />
<div id='container-inner' className='px-7 max-w-5xl justify-center mx-auto min-h-screen'> <div id='container-inner' className={`px-7 ${fullWidth ? '' : 'max-w-5xl'} justify-center mx-auto min-h-screen`}>
<Transition <Transition
show={!onLoading} show={!onLoading}
appear={true} appear={true}
@@ -96,20 +98,23 @@ const LayoutBase = props => {
</div> </div>
{/* 桌面端右侧 */} {/* 桌面端右侧 */}
<div className={`hidden xl:block border-l dark:border-transparent w-96 relative z-10 ${siteConfig('MEDIUM_RIGHT_PANEL_DARK', null, CONFIG) ? 'bg-hexo-black-gray dark' : ''}`}> {fullWidth
<div className='py-14 px-6 sticky top-0'> ? null
<Tabs> : <div className={`hidden xl:block border-l dark:border-transparent w-96 relative z-10 ${siteConfig('MEDIUM_RIGHT_PANEL_DARK', null, CONFIG) ? 'bg-hexo-black-gray dark' : ''}`}>
{slotRight} <div className='py-14 px-6 sticky top-0'>
<div key={locale.NAV.ABOUT}> <Tabs>
{router.pathname !== '/search' && <SearchInput className='mt-6 mb-12' />} {slotRight}
{showInfoCard && <InfoCard {...props} />} <div key={locale.NAV.ABOUT}>
{siteConfig('MEDIUM_WIDGET_REVOLVER_MAPS', null, CONFIG) === 'true' && <RevolverMaps />} {router.pathname !== '/search' && <SearchInput className='mt-6 mb-12' />}
</div> {showInfoCard && <InfoCard {...props} />}
</Tabs> {siteConfig('MEDIUM_WIDGET_REVOLVER_MAPS', null, CONFIG) === 'true' && <RevolverMaps />}
<Announcement post={notice} /> </div>
<Live2D /> </Tabs>
</div> <Announcement post={notice} />
</div> <Live2D />
</div>
</div>}
</main> </main>
{/* 移动端底部导航栏 */} {/* 移动端底部导航栏 */}
@@ -134,10 +139,9 @@ const LayoutIndex = (props) => {
* @returns * @returns
*/ */
const LayoutPostList = (props) => { const LayoutPostList = (props) => {
const slotTop = <BlogPostBar {...props} /> return <>
return <LayoutBase {...props} slotTop={slotTop}>
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase> </>
} }
/** /**
@@ -155,14 +159,14 @@ const LayoutSlug = props => {
) )
return ( return (
<LayoutBase showInfoCard={true} slotRight={slotRight} {...props} > <div showInfoCard={true} slotRight={slotRight} {...props} >
{/* 文章锁 */} {/* 文章锁 */}
{lock && <ArticleLock validPassword={validPassword} />} {lock && <ArticleLock validPassword={validPassword} />}
{!lock && <div id='article-wrapper'> {!lock && <div id='article-wrapper'>
{/* 文章信息 */} {/* 文章信息 */}
<ArticleInfo {...props}/> <ArticleInfo {...props} />
{/* Notion文章主体 */} {/* Notion文章主体 */}
<section className="px-1 max-w-4xl"> <section className="px-1 max-w-4xl">
@@ -189,7 +193,7 @@ const LayoutSlug = props => {
{/* 移动端目录 */} {/* 移动端目录 */}
<TocDrawer {...props} /> <TocDrawer {...props} />
</div>} </div>}
</LayoutBase> </div>
) )
} }
@@ -217,7 +221,7 @@ const LayoutSearch = (props) => {
} }
}, []) }, [])
return <LayoutBase {...props}> return <>
{/* 搜索导航栏 */} {/* 搜索导航栏 */}
<div className='py-12'> <div className='py-12'>
@@ -233,7 +237,7 @@ const LayoutSearch = (props) => {
{currentSearch && <div> {currentSearch && <div>
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</div>} </div>}
</LayoutBase> </>
} }
/** /**
@@ -244,12 +248,12 @@ const LayoutSearch = (props) => {
const LayoutArchive = props => { const LayoutArchive = props => {
const { archivePosts } = props const { archivePosts } = props
return ( return (
<LayoutBase {...props}> <>
<div className="mb-10 pb-20 md:py-12 py-3 min-h-full"> <div className="mb-10 pb-20 md:py-12 py-3 min-h-full">
{Object.keys(archivePosts)?.map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} /> {Object.keys(archivePosts)?.map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />
)} )}
</div> </div>
</LayoutBase> </>
) )
} }
@@ -259,9 +263,9 @@ const LayoutArchive = props => {
* @returns * @returns
*/ */
const Layout404 = props => { const Layout404 = props => {
return <LayoutBase {...props}> return <>
<div className='w-full h-96 py-80 flex justify-center items-center'>404 Not found.</div> <div className='w-full h-96 py-80 flex justify-center items-center'>404 Not found.</div>
</LayoutBase> </>
} }
/** /**
@@ -273,7 +277,7 @@ const LayoutCategoryIndex = (props) => {
const { categoryOptions } = props const { categoryOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return ( return (
<LayoutBase {...props}> <>
<div className='bg-white dark:bg-gray-700 py-10'> <div className='bg-white dark:bg-gray-700 py-10'>
<div className='dark:text-gray-200 mb-5'> <div className='dark:text-gray-200 mb-5'>
<i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}: <i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}:
@@ -295,7 +299,7 @@ const LayoutCategoryIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
) )
} }
@@ -308,7 +312,7 @@ const LayoutTagIndex = props => {
const { tagOptions } = props const { tagOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return ( return (
<LayoutBase {...props}> <>
<div className="bg-white dark:bg-gray-700 py-10"> <div className="bg-white dark:bg-gray-700 py-10">
<div className="dark:text-gray-200 mb-5"> <div className="dark:text-gray-200 mb-5">
<i className="mr-4 fas fa-tag" /> <i className="mr-4 fas fa-tag" />
@@ -324,12 +328,13 @@ const LayoutTagIndex = props => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
) )
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutPostList, LayoutPostList,
LayoutSearch, LayoutSearch,

View File

@@ -50,9 +50,9 @@ export const MenuItem = ({ link }) => {
{ {
link?.subMenus?.map((sLink, index) => ( link?.subMenus?.map((sLink, index) => (
<div key={index} className='nav-submenu'> <div key={index} className='nav-submenu'>
<a href={sLink?.to}> <Link href={sLink?.to}>
<span className='dark:text-neutral-400 text-gray-500 hover:text-black dark:hover:text-white text-xs font-bold'><i className={`text-xs mr-1 ${sLink?.icon ? sLink?.icon : 'fas fa-hashtag'}`} />{sLink.title}</span> <span className='dark:text-neutral-400 text-gray-500 hover:text-black dark:hover:text-white text-xs font-bold'><i className={`text-xs mr-1 ${sLink?.icon ? sLink?.icon : 'fas fa-hashtag'}`} />{sLink.title}</span>
</a> </Link>
</div> </div>
)) ))
} }

View File

@@ -172,10 +172,10 @@ const LayoutPostListIndex = props => {
// const { customMenu, children, post, allNavPages, categoryOptions, slotLeft, slotRight, slotTop, meta } = props // const { customMenu, children, post, allNavPages, categoryOptions, slotLeft, slotRight, slotTop, meta } = props
// const [filteredNavPages, setFilteredNavPages] = useState(allNavPages) // const [filteredNavPages, setFilteredNavPages] = useState(allNavPages)
return ( return (
<LayoutBase {...props} > <>
<Announcement {...props} /> <Announcement {...props} />
<BlogPostListAll { ...props } /> <BlogPostListAll { ...props } />
</LayoutBase> </>
) )
} }
@@ -189,7 +189,7 @@ const LayoutPostList = props => {
// 顶部如果是按照分类或标签查看文章列表,列表顶部嵌入一个横幅 // 顶部如果是按照分类或标签查看文章列表,列表顶部嵌入一个横幅
// 如果是搜索,则列表顶部嵌入 搜索框 // 如果是搜索,则列表顶部嵌入 搜索框
return ( return (
<LayoutBase {...props} > <>
<div className='w-full max-w-7xl mx-auto justify-center mt-8'> <div className='w-full max-w-7xl mx-auto justify-center mt-8'>
<div id='posts-wrapper' class='card-list grid gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5'> <div id='posts-wrapper' class='card-list grid gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5'>
{posts?.map(post => ( {posts?.map(post => (
@@ -197,7 +197,7 @@ const LayoutPostList = props => {
))} ))}
</div> </div>
</div> </div>
</LayoutBase> </>
) )
} }
@@ -210,7 +210,7 @@ const LayoutSlug = (props) => {
const { post, lock, validPassword } = props const { post, lock, validPassword } = props
return ( return (
<LayoutBase {...props} > <>
{/* 文章锁 */} {/* 文章锁 */}
{lock && <ArticleLock validPassword={validPassword} />} {lock && <ArticleLock validPassword={validPassword} />}
@@ -244,7 +244,7 @@ const LayoutSlug = (props) => {
<TocDrawer {...props} /> <TocDrawer {...props} />
</div>} </div>}
</LayoutBase> </>
) )
} }
@@ -255,7 +255,7 @@ const LayoutSlug = (props) => {
* @returns * @returns
*/ */
const LayoutSearch = (props) => { const LayoutSearch = (props) => {
return <LayoutBase {...props}></LayoutBase> return <></>
} }
/** /**
@@ -267,20 +267,20 @@ const LayoutSearch = (props) => {
const LayoutArchive = (props) => { const LayoutArchive = (props) => {
const { archivePosts } = props const { archivePosts } = props
return <LayoutBase {...props}> return <>
<div className="mb-10 pb-20 md:py-12 py-3 min-h-full"> <div className="mb-10 pb-20 md:py-12 py-3 min-h-full">
{Object.keys(archivePosts)?.map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />)} {Object.keys(archivePosts)?.map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />)}
</div> </div>
</LayoutBase> </>
} }
/** /**
* 404 * 404
*/ */
const Layout404 = props => { const Layout404 = props => {
return <LayoutBase {...props}> return <>
<div className='w-full h-96 py-80 flex justify-center items-center'>404 Not found.</div> <div className='w-full h-96 py-80 flex justify-center items-center'>404 Not found.</div>
</LayoutBase> </>
} }
/** /**
@@ -289,7 +289,7 @@ const Layout404 = props => {
const LayoutCategoryIndex = (props) => { const LayoutCategoryIndex = (props) => {
const { categoryOptions } = props const { categoryOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return <LayoutBase {...props}> return <>
<div className='bg-white dark:bg-gray-700 py-10'> <div className='bg-white dark:bg-gray-700 py-10'>
<div className='dark:text-gray-200 mb-5'> <div className='dark:text-gray-200 mb-5'>
<i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}: <i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}:
@@ -311,7 +311,7 @@ const LayoutCategoryIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
} }
/** /**
@@ -321,7 +321,7 @@ const LayoutTagIndex = (props) => {
const { tagOptions } = props const { tagOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return <LayoutBase {...props}> return <>
<div className="bg-white dark:bg-gray-700 py-10"> <div className="bg-white dark:bg-gray-700 py-10">
<div className="dark:text-gray-200 mb-5"> <div className="dark:text-gray-200 mb-5">
<i className="mr-4 fas fa-tag" /> <i className="mr-4 fas fa-tag" />
@@ -337,11 +337,12 @@ const LayoutTagIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutSearch, LayoutSearch,
LayoutArchive, LayoutArchive,

View File

@@ -14,6 +14,7 @@ import NotionIcon from '@/components/NotionIcon'
import LazyImage from '@/components/LazyImage' import LazyImage from '@/components/LazyImage'
import { formatDateFmt } from '@/lib/formatDate' import { formatDateFmt } from '@/lib/formatDate'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import WWAds from '@/components/WWAds'
/** /**
* *
@@ -79,7 +80,9 @@ export default function ArticleDetail(props) {
{/* Notion内容主体 */} {/* Notion内容主体 */}
<article className='mx-auto'> <article className='mx-auto'>
<WWAds className="w-full" orientation="horizontal" />
{post && (<NotionPage post={post} />)} {post && (<NotionPage post={post} />)}
<WWAds className="w-full" orientation="horizontal" />
</article> </article>
{showArticleInfo && <> {showArticleInfo && <>

View File

@@ -1,10 +1,15 @@
/**
* 卡片组件
* @param {*} param0
* @returns
*/
const Card = (props) => { const Card = (props) => {
const { children, headerSlot } = props const { children, headerSlot, className } = props
return <div {...props}> return <div className={className}>
<>{headerSlot}</> <>{headerSlot}</>
<section className="shadow px-2 py-4 bg-white dark:bg-hexo-black-gray hover:shadow-xl duration-200"> <section className="shadow px-2 py-4 bg-white dark:bg-hexo-black-gray hover:shadow-xl duration-200">
{children} {children}
</section> </section>
</div> </div>
} }
export default Card export default Card

View File

@@ -1,10 +1,13 @@
import { siteConfig } from '@/lib/config'
import Link from 'next/link' import Link from 'next/link'
const CategoryGroup = ({ currentCategory, categories }) => { const CategoryGroup = ({ currentCategory, categories }) => {
if (!categories) return <></> if (!categories || categories.length === 0) return <></>
const categoryCount = siteConfig('PREVIEW_CATEGORY_COUNT')
const categoryOptions = categories.slice(0, categoryCount)
return <> return <>
<div id='category-list' className='dark:border-gray-600 flex flex-wrap'> <div id='category-list' className='dark:border-gray-600 flex flex-wrap'>
{categories.map(category => { {categoryOptions.map(category => {
const selected = currentCategory === category.name const selected = currentCategory === category.name
return ( return (
<Link <Link

View File

@@ -1,3 +1,4 @@
import { siteConfig } from '@/lib/config'
import TagItemMini from './TagItemMini' import TagItemMini from './TagItemMini'
/** /**
@@ -8,11 +9,14 @@ import TagItemMini from './TagItemMini'
* @constructor * @constructor
*/ */
const TagGroups = ({ tags, currentTag }) => { const TagGroups = ({ tags, currentTag }) => {
if (!tags) return <></> if (!tags || tags.length === 0) return <></>
const tagsCount = siteConfig('PREVIEW_TAG_COUNT')
const tagOptions = tags.slice(0, tagsCount)
return ( return (
<div id='tags-group' className='dark:border-gray-600 w-66 space-y-2'> <div id='tags-group' className='dark:border-gray-600 w-66 space-y-2'>
{ {
tags.map(tag => { tagOptions.map(tag => {
const selected = tag.name === currentTag const selected = tag.name === currentTag
return <TagItemMini key={tag.name} tag={tag} selected={selected} /> return <TagItemMini key={tag.name} tag={tag} selected={selected} />
}) })

View File

@@ -33,13 +33,12 @@ import { siteConfig } from '@/lib/config'
* @constructor * @constructor
*/ */
const LayoutBase = (props) => { const LayoutBase = (props) => {
const { children, headerSlot, floatSlot, rightAreaSlot, meta } = props const { children, headerSlot, rightAreaSlot, meta, post } = props
const { onLoading } = useGlobal() const { onLoading } = useGlobal()
const targetRef = useRef(null) const targetRef = useRef(null)
const floatButtonGroup = useRef(null) const floatButtonGroup = useRef(null)
const [showRightFloat, switchShow] = useState(false) const [showRightFloat, switchShow] = useState(false)
const [percent, changePercent] = useState(0) // 页面阅读百分比 const [percent, changePercent] = useState(0) // 页面阅读百分比
const scrollListener = () => { const scrollListener = () => {
const targetRef = document.getElementById('wrapper') const targetRef = document.getElementById('wrapper')
const clientHeight = targetRef?.clientHeight const clientHeight = targetRef?.clientHeight
@@ -68,6 +67,15 @@ const LayoutBase = (props) => {
return () => document.removeEventListener('scroll', scrollListener) return () => document.removeEventListener('scroll', scrollListener)
}, [showRightFloat]) }, [showRightFloat])
// 悬浮抽屉
const drawerRight = useRef(null)
const floatSlot = <div className='block lg:hidden'>
<TocDrawerButton onClick={() => {
drawerRight?.current?.handleSwitchVisible()
}} />
</div>
const tocRef = isBrowser ? document.getElementById('article-wrapper') : null
return ( return (
<div id='theme-next'> <div id='theme-next'>
{/* SEO相关 */} {/* SEO相关 */}
@@ -106,8 +114,14 @@ const LayoutBase = (props) => {
{/* 右侧栏样式 */} {/* 右侧栏样式 */}
{siteConfig('NEXT_RIGHT_BAR', null, CONFIG) && <SideAreaRight targetRef={targetRef} slot={rightAreaSlot} {...props} />} {siteConfig('NEXT_RIGHT_BAR', null, CONFIG) && <SideAreaRight targetRef={targetRef} slot={rightAreaSlot} {...props} />}
</main> </main>
{/* 悬浮目录按钮 */}
{post && <div className='block lg:hidden'>
<TocDrawer post={post} cRef={drawerRight} targetRef={tocRef} />
</div>}
{/* 右下角悬浮 */} {/* 右下角悬浮 */}
<div ref={floatButtonGroup} className='right-8 bottom-12 lg:right-2 fixed justify-end z-20 font-sans'> <div ref={floatButtonGroup} className='right-8 bottom-12 lg:right-2 fixed justify-end z-20 font-sans'>
<div className={(showRightFloat ? 'animate__animated ' : 'hidden') + ' animate__fadeInUp rounded-md glassmorphism justify-center duration-500 animate__faster flex space-x-2 items-center cursor-pointer '}> <div className={(showRightFloat ? 'animate__animated ' : 'hidden') + ' animate__fadeInUp rounded-md glassmorphism justify-center duration-500 animate__faster flex space-x-2 items-center cursor-pointer '}>
@@ -140,7 +154,7 @@ const LayoutIndex = (props) => {
* @returns * @returns
*/ */
const LayoutPostList = (props) => { const LayoutPostList = (props) => {
return <LayoutBase {...props} > return <>
<BlogListBar {...props} /> <BlogListBar {...props} />
@@ -148,7 +162,7 @@ const LayoutPostList = (props) => {
? <BlogPostListScroll {...props} showSummary={true} /> ? <BlogPostListScroll {...props} showSummary={true} />
: <BlogPostListPage {...props} /> : <BlogPostListPage {...props} />
} }
</LayoutBase> </>
} }
/** /**
@@ -174,7 +188,7 @@ const LayoutSearch = (props) => {
}, []) }, [])
return ( return (
<LayoutBase {...props} > <>
<StickyBar> <StickyBar>
<div className="p-4 dark:text-gray-200"> <div className="p-4 dark:text-gray-200">
<i className="mr-1 fas fa-search" />{' '} <i className="mr-1 fas fa-search" />{' '}
@@ -187,7 +201,7 @@ const LayoutSearch = (props) => {
: <BlogPostListPage {...props} /> : <BlogPostListPage {...props} />
} }
</div> </div>
</LayoutBase> </>
) )
} }
@@ -210,7 +224,7 @@ const Layout404 = props => {
}, 3000) }, 3000)
}, []) }, [])
return <LayoutBase {...props}> return <>
<div className='md:-mt-20 text-black w-full h-screen text-center justify-center content-center items-center flex flex-col'> <div className='md:-mt-20 text-black w-full h-screen text-center justify-center content-center items-center flex flex-col'>
<div className='dark:text-gray-200'> <div className='dark:text-gray-200'>
<h2 className='inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top'><i className='mr-2 fas fa-spinner animate-spin' />404</h2> <h2 className='inline-block border-r-2 border-gray-600 mr-2 px-3 py-2 align-top'><i className='mr-2 fas fa-spinner animate-spin' />404</h2>
@@ -219,7 +233,7 @@ const Layout404 = props => {
</div> </div>
</div> </div>
</div> </div>
</LayoutBase> </>
} }
/** /**
@@ -231,7 +245,7 @@ const LayoutArchive = (props) => {
const { archivePosts } = props const { archivePosts } = props
return ( return (
<LayoutBase {...props}> <>
<div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-hexo-black-gray shadow-md min-h-full"> <div className="mb-10 pb-20 bg-white md:p-12 p-3 dark:bg-hexo-black-gray shadow-md min-h-full">
{Object.keys(archivePosts).map(archiveTitle => ( {Object.keys(archivePosts).map(archiveTitle => (
<BlogPostArchive <BlogPostArchive
@@ -241,7 +255,7 @@ const LayoutArchive = (props) => {
/> />
))} ))}
</div> </div>
</LayoutBase> </>
) )
} }
@@ -252,27 +266,14 @@ const LayoutArchive = (props) => {
*/ */
const LayoutSlug = (props) => { const LayoutSlug = (props) => {
const { post, lock, validPassword } = props const { post, lock, validPassword } = props
const drawerRight = useRef(null)
const targetRef = isBrowser ? document.getElementById('article-wrapper') : null
const floatSlot = <div className='block lg:hidden'>
<TocDrawerButton onClick={() => {
drawerRight?.current?.handleSwitchVisible()
}} />
</div>
return ( return (
<LayoutBase {...props} floatSlot={floatSlot}> <>
{post && !lock && <ArticleDetail {...props} />} {post && !lock && <ArticleDetail {...props} />}
{post && lock && <ArticleLock validPassword={validPassword} />} {post && lock && <ArticleLock validPassword={validPassword} />}
{/* 悬浮目录按钮 */} </>
{post && <div className='block lg:hidden'>
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} />
</div>}
</LayoutBase>
) )
} }
@@ -285,7 +286,7 @@ const LayoutCategoryIndex = (props) => {
const { allPosts, categoryOptions } = props const { allPosts, categoryOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return ( return (
<LayoutBase totalPosts={allPosts} {...props}> <div totalPosts={allPosts} {...props}>
<div className='bg-white dark:bg-hexo-black-gray px-10 py-10 shadow h-full'> <div className='bg-white dark:bg-hexo-black-gray px-10 py-10 shadow h-full'>
<div className='dark:text-gray-200 mb-5'> <div className='dark:text-gray-200 mb-5'>
<i className='mr-4 fas faTh' />{locale.COMMON.CATEGORY}: <i className='mr-4 fas faTh' />{locale.COMMON.CATEGORY}:
@@ -307,7 +308,7 @@ const LayoutCategoryIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </div>
) )
} }
@@ -319,7 +320,7 @@ const LayoutCategoryIndex = (props) => {
const LayoutTagIndex = (props) => { const LayoutTagIndex = (props) => {
const { tagOptions } = props const { tagOptions } = props
const { locale } = useGlobal() const { locale } = useGlobal()
return <LayoutBase {...props}> return <>
<div className='bg-white dark:bg-hexo-black-gray px-10 py-10 shadow h-full'> <div className='bg-white dark:bg-hexo-black-gray px-10 py-10 shadow h-full'>
<div className='dark:text-gray-200 mb-5'><i className='fas fa-tags mr-4' />{locale.COMMON.TAGS}:</div> <div className='dark:text-gray-200 mb-5'><i className='fas fa-tags mr-4' />{locale.COMMON.TAGS}:</div>
<div id='tags-list' className='duration-200 flex flex-wrap'> <div id='tags-list' className='duration-200 flex flex-wrap'>
@@ -328,11 +329,12 @@ const LayoutTagIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutSearch, LayoutSearch,
LayoutArchive, LayoutArchive,
@@ -340,5 +342,5 @@ export {
Layout404, Layout404,
LayoutCategoryIndex, LayoutCategoryIndex,
LayoutPostList, LayoutPostList,
LayoutTagIndex LayoutTagIndex,
} }

View File

@@ -1,7 +1,8 @@
import { useNobeliumGlobal } from '..'
import Tags from './Tags' import Tags from './Tags'
export default function BlogListBar(props) { export default function BlogListBar(props) {
const { tag, setFilterKey } = props const { tag, setFilterKey } = useNobeliumGlobal()
const handleSearchChange = (val) => { const handleSearchChange = (val) => {
setFilterKey(val) setFilterKey(val)
} }

View File

@@ -37,14 +37,16 @@ export const useNobeliumGlobal = () => useContext(ThemeGlobalNobelium)
* @constructor * @constructor
*/ */
const LayoutBase = props => { const LayoutBase = props => {
const { children, post, topSlot, meta } = props const { children, post, meta } = props
const fullWidth = post?.fullWidth ?? false const fullWidth = post?.fullWidth ?? false
const { onLoading } = useGlobal() const { onLoading } = useGlobal()
const searchModal = useRef(null) const searchModal = useRef(null)
// 在列表中进行实时过滤
const [filterKey, setFilterKey] = useState('')
const topSlot= <BlogListBar {...props}/>
return ( return (
<ThemeGlobalNobelium.Provider value={{ searchModal }}> <ThemeGlobalNobelium.Provider value={{ searchModal, filterKey, setFilterKey }}>
<div id='theme-nobelium' className='nobelium relative dark:text-gray-300 w-full bg-white dark:bg-black min-h-screen flex flex-col'> <div id='theme-nobelium' className='nobelium relative dark:text-gray-300 w-full bg-white dark:bg-black min-h-screen flex flex-col'>
{/* SEO相关 */} {/* SEO相关 */}
<CommonHead meta={meta} /> <CommonHead meta={meta} />
@@ -114,10 +116,8 @@ const LayoutIndex = props => {
* @returns * @returns
*/ */
const LayoutPostList = props => { const LayoutPostList = props => {
const { posts, topSlot } = props const { posts, topSlot,tag } = props
const { filterKey } = useNobeliumGlobal()
// 在列表中进行实时过滤
const [filterKey, setFilterKey] = useState('')
let filteredBlogPosts = [] let filteredBlogPosts = []
if (filterKey && posts) { if (filterKey && posts) {
filteredBlogPosts = posts.filter(post => { filteredBlogPosts = posts.filter(post => {
@@ -130,10 +130,11 @@ const LayoutPostList = props => {
} }
return ( return (
<LayoutBase {...props} topSlot={<BlogListBar {...props} setFilterKey={setFilterKey} />}> <>
{topSlot} {topSlot}
{tag && <SearchNavBar {...props} />}
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} posts={filteredBlogPosts} /> : <BlogListScroll {...props} posts={filteredBlogPosts} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} posts={filteredBlogPosts} /> : <BlogListScroll {...props} posts={filteredBlogPosts} />}
</LayoutBase> </>
) )
} }
@@ -159,7 +160,7 @@ const LayoutSearch = props => {
}, []) }, [])
// 在列表中进行实时过滤 // 在列表中进行实时过滤
const [filterKey, setFilterKey] = useState('') const {filterKey} = useNobeliumGlobal()
let filteredBlogPosts = [] let filteredBlogPosts = []
if (filterKey && posts) { if (filterKey && posts) {
filteredBlogPosts = posts.filter(post => { filteredBlogPosts = posts.filter(post => {
@@ -171,10 +172,10 @@ const LayoutSearch = props => {
filteredBlogPosts = deepClone(posts) filteredBlogPosts = deepClone(posts)
} }
return <LayoutBase {...props} topSlot={<BlogListBar {...props} setFilterKey={setFilterKey} />}> return <>
<SearchNavBar {...props} /> <SearchNavBar {...props} />
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} posts={filteredBlogPosts} /> : <BlogListScroll {...props} posts={filteredBlogPosts} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} posts={filteredBlogPosts} /> : <BlogListScroll {...props} posts={filteredBlogPosts} />}
</LayoutBase> </>
} }
/** /**
@@ -185,11 +186,11 @@ const LayoutSearch = props => {
const LayoutArchive = props => { const LayoutArchive = props => {
const { archivePosts } = props const { archivePosts } = props
return ( return (
<LayoutBase {...props}> <>
<div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full"> <div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full">
{Object.keys(archivePosts).map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />)} {Object.keys(archivePosts).map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />)}
</div> </div>
</LayoutBase> </>
) )
} }
@@ -202,7 +203,7 @@ const LayoutSlug = props => {
const { post, lock, validPassword } = props const { post, lock, validPassword } = props
return ( return (
<LayoutBase {...props}> <>
{lock && <ArticleLock validPassword={validPassword} />} {lock && <ArticleLock validPassword={validPassword} />}
@@ -216,7 +217,7 @@ const LayoutSlug = props => {
</> </>
</div>} </div>}
</LayoutBase> </>
) )
} }
@@ -226,9 +227,9 @@ const LayoutSlug = props => {
* @returns * @returns
*/ */
const Layout404 = (props) => { const Layout404 = (props) => {
return <LayoutBase {...props}> return <>
404 Not found. 404 Not found.
</LayoutBase> </>
} }
/** /**
@@ -240,7 +241,7 @@ const LayoutCategoryIndex = (props) => {
const { categoryOptions } = props const { categoryOptions } = props
return ( return (
<LayoutBase {...props}> <>
<div id='category-list' className='duration-200 flex flex-wrap'> <div id='category-list' className='duration-200 flex flex-wrap'>
{categoryOptions?.map(category => { {categoryOptions?.map(category => {
return ( return (
@@ -257,7 +258,7 @@ const LayoutCategoryIndex = (props) => {
) )
})} })}
</div> </div>
</LayoutBase> </>
) )
} }
@@ -269,7 +270,7 @@ const LayoutCategoryIndex = (props) => {
const LayoutTagIndex = (props) => { const LayoutTagIndex = (props) => {
const { tagOptions } = props const { tagOptions } = props
return ( return (
<LayoutBase {...props}> <>
<div> <div>
<div id='tags-list' className='duration-200 flex flex-wrap'> <div id='tags-list' className='duration-200 flex flex-wrap'>
{tagOptions.map(tag => { {tagOptions.map(tag => {
@@ -284,12 +285,13 @@ const LayoutTagIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
) )
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutSearch, LayoutSearch,
LayoutArchive, LayoutArchive,

View File

@@ -1,39 +0,0 @@
import Tags from './Tags'
export default function BlogListBar(props) {
const { tag, setFilterKey } = props
const handleSearchChange = (val) => {
setFilterKey(val)
}
if (tag) {
return (<div className="mb-4">
<div className='relative'>
<input
type="text"
placeholder={
tag ? `Search in #${tag}` : 'Search Articles'
}
className="outline-none block w-full border px-4 py-2 border-black bg-white text-black dark:bg-night dark:border-white dark:text-white"
onChange={e => handleSearchChange(e.target.value)}
/>
<svg
className="absolute right-3 top-3 h-5 w-5 text-black dark:text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
></path>
</svg>
</div>
<Tags {...props} />
</div>)
} else {
return <></>
}
}

View File

@@ -5,6 +5,7 @@ import { ArrowPath, ChevronLeft, ChevronRight } from '@/components/HeroIcons'
import Link from 'next/link' import Link from 'next/link'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import LazyImage from '@/components/LazyImage' import LazyImage from '@/components/LazyImage'
import { compressImage } from '@/lib/notion/mapImage'
/** /**
* 弹出框 * 弹出框
@@ -13,7 +14,7 @@ export default function Modal(props) {
const { showModal, setShowModal, modalContent, setModalContent } = usePlogGlobal() const { showModal, setShowModal, modalContent, setModalContent } = usePlogGlobal()
const { siteInfo, posts } = props const { siteInfo, posts } = props
const cancelButtonRef = useRef(null) const cancelButtonRef = useRef(null)
const img = modalContent?.pageCover || siteInfo?.pageCover const img = compressImage(modalContent?.pageCover || siteInfo?.pageCover, 1200, 85, 'webp')
const imgRef = useRef(null) const imgRef = useRef(null)
// 添加loading状态 // 添加loading状态

View File

@@ -98,9 +98,9 @@ const LayoutIndex = props => {
*/ */
const LayoutPostList = props => { const LayoutPostList = props => {
return ( return (
<LayoutBase {...props}> <>
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</LayoutBase> </>
) )
} }
@@ -137,11 +137,11 @@ const LayoutSearch = props => {
const LayoutArchive = props => { const LayoutArchive = props => {
const { archivePosts } = props const { archivePosts } = props
return ( return (
<LayoutBase {...props}> <>
<div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full"> <div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full">
{Object.keys(archivePosts).map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />)} {Object.keys(archivePosts).map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />)}
</div> </div>
</LayoutBase> </>
) )
} }
@@ -154,7 +154,7 @@ const LayoutSlug = props => {
const { post, lock, validPassword } = props const { post, lock, validPassword } = props
return ( return (
<LayoutBase {...props}> <>
{lock && <ArticleLock validPassword={validPassword} />} {lock && <ArticleLock validPassword={validPassword} />}
@@ -168,7 +168,7 @@ const LayoutSlug = props => {
</> </>
</div>} </div>}
</LayoutBase> </>
) )
} }
@@ -178,9 +178,9 @@ const LayoutSlug = props => {
* @returns * @returns
*/ */
const Layout404 = (props) => { const Layout404 = (props) => {
return <LayoutBase {...props}> return <>
404 Not found. 404 Not found.
</LayoutBase> </>
} }
/** /**
@@ -192,7 +192,7 @@ const LayoutCategoryIndex = (props) => {
const { categoryOptions } = props const { categoryOptions } = props
return ( return (
<LayoutBase {...props}> <>
<div id='category-list' className='duration-200 flex flex-wrap'> <div id='category-list' className='duration-200 flex flex-wrap'>
{categoryOptions?.map(category => { {categoryOptions?.map(category => {
return ( return (
@@ -209,7 +209,7 @@ const LayoutCategoryIndex = (props) => {
) )
})} })}
</div> </div>
</LayoutBase> </>
) )
} }
@@ -221,7 +221,7 @@ const LayoutCategoryIndex = (props) => {
const LayoutTagIndex = (props) => { const LayoutTagIndex = (props) => {
const { tagOptions } = props const { tagOptions } = props
return ( return (
<LayoutBase {...props}> <>
<div> <div>
<div id='tags-list' className='duration-200 flex flex-wrap'> <div id='tags-list' className='duration-200 flex flex-wrap'>
{tagOptions.map(tag => { {tagOptions.map(tag => {
@@ -236,12 +236,13 @@ const LayoutTagIndex = (props) => {
})} })}
</div> </div>
</div> </div>
</LayoutBase> </>
) )
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutSearch, LayoutSearch,
LayoutArchive, LayoutArchive,

View File

@@ -1,5 +1,7 @@
import { siteConfig } from '@/lib/config'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useState } from 'react' import { useState } from 'react'
import { useSimpleGlobal } from '..'
import { MenuList } from './MenuList' import { MenuList } from './MenuList'
/** /**
@@ -10,9 +12,15 @@ import { MenuList } from './MenuList'
export default function NavBar (props) { export default function NavBar (props) {
const [showSearchInput, changeShowSearchInput] = useState(false) const [showSearchInput, changeShowSearchInput] = useState(false)
const router = useRouter() const router = useRouter()
const { searchModal } = useSimpleGlobal()
// 展示搜索框
const toggleShowSearchInput = () => { const toggleShowSearchInput = () => {
changeShowSearchInput(!showSearchInput) if (siteConfig('ALGOLIA_APP_ID')) {
searchModal.current.openSearch()
} else {
changeShowSearchInput(!showSearchInput)
}
} }
const onKeyUp = (e) => { const onKeyUp = (e) => {

View File

@@ -8,6 +8,7 @@ import Catalog from './Catalog'
* @param {*} props * @param {*} props
* @returns * @returns
*/ */
let counter = 0;
export default function SideBar (props) { export default function SideBar (props) {
const { notice } = props const { notice } = props
return (<> return (<>

View File

@@ -1,5 +1,5 @@
import CONFIG from './config' import CONFIG from './config'
import { useEffect } from 'react' import { createContext, useContext, useEffect, useRef } from 'react'
import { isBrowser } from '@/lib/utils' import { isBrowser } from '@/lib/utils'
import { useGlobal } from '@/lib/global' import { useGlobal } from '@/lib/global'
import { AdSlot } from '@/components/GoogleAdsense' import { AdSlot } from '@/components/GoogleAdsense'
@@ -10,7 +10,7 @@ import { Style } from './style'
import replaceSearchResult from '@/components/Mark' import replaceSearchResult from '@/components/Mark'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import NotionPage from '@/components/NotionPage' import NotionPage from '@/components/NotionPage'
// const NotionPage = dynamic(() => import('@/components/NotionPage'), { ssr: false }); import AlgoliaSearchModal from '@/components/AlgoliaSearchModal'
// 主题组件 // 主题组件
const BlogListScroll = dynamic(() => import('./components/BlogListScroll'), { ssr: false }); const BlogListScroll = dynamic(() => import('./components/BlogListScroll'), { ssr: false });
@@ -31,6 +31,10 @@ const CommonHead = dynamic(() => import('@/components/CommonHead'), { ssr: false
const WWAds = dynamic(() => import('@/components/WWAds'), { ssr: false }); const WWAds = dynamic(() => import('@/components/WWAds'), { ssr: false });
const BlogListPage = dynamic(() => import('./components/BlogListPage'), { ssr: false }) const BlogListPage = dynamic(() => import('./components/BlogListPage'), { ssr: false })
// 主题全局状态
const ThemeGlobalSimple = createContext()
export const useSimpleGlobal = () => useContext(ThemeGlobalSimple)
/** /**
* 基础布局 * 基础布局
* *
@@ -39,9 +43,11 @@ const BlogListPage = dynamic(() => import('./components/BlogListPage'), { ssr: f
*/ */
const LayoutBase = props => { const LayoutBase = props => {
const { children, slotTop, meta } = props const { children, slotTop, meta } = props
const { onLoading } = useGlobal() const { onLoading, fullWidth } = useGlobal()
const searchModal = useRef(null)
return ( return (
<ThemeGlobalSimple.Provider value={{ searchModal }}>
<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相关 */}
<CommonHead meta={meta}/> <CommonHead meta={meta}/>
@@ -76,9 +82,11 @@ const LayoutBase = props => {
<AdSlot type='native' /> <AdSlot type='native' />
</div> </div>
<div id='right-sidebar' className="hidden xl:block flex-none sticky top-8 w-96 border-l dark:border-gray-800 pl-12 border-gray-100"> {fullWidth
<SideBar {...props} /> ? null
</div> : <div id='right-sidebar' className="hidden xl:block flex-none sticky top-8 w-96 border-l dark:border-gray-800 pl-12 border-gray-100">
<SideBar {...props} />
</div>}
</div> </div>
@@ -86,9 +94,13 @@ const LayoutBase = props => {
<JumpToTopButton /> <JumpToTopButton />
</div> </div>
{/* 搜索框 */}
<AlgoliaSearchModal cRef={searchModal} {...props}/>
<Footer {...props} /> <Footer {...props} />
</div> </div>
</ThemeGlobalSimple.Provider>
) )
} }
@@ -108,9 +120,9 @@ const LayoutIndex = props => {
*/ */
const LayoutPostList = props => { const LayoutPostList = props => {
return ( return (
<LayoutBase {...props}> <>
{siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />} {siteConfig('POST_LIST_STYLE') === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</LayoutBase> </>
) )
} }
@@ -136,7 +148,9 @@ const LayoutSearch = props => {
} }
}, []) }, [])
return <LayoutPostList {...props} slotTop={<SearchInput {...props} />} /> const slotTop = siteConfig('ALGOLIA_APP_ID') ? null : <SearchInput {...props} />
return <LayoutPostList {...props} slotTop={slotTop} />
} }
/** /**
@@ -147,11 +161,11 @@ const LayoutSearch = props => {
const LayoutArchive = props => { const LayoutArchive = props => {
const { archivePosts } = props const { archivePosts } = props
return ( return (
<LayoutBase {...props}> <>
<div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full"> <div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full">
{Object.keys(archivePosts).map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />)} {Object.keys(archivePosts).map(archiveTitle => <BlogArchiveItem key={archiveTitle} archiveTitle={archiveTitle} archivePosts={archivePosts} />)}
</div> </div>
</LayoutBase> </>
) )
} }
@@ -162,13 +176,14 @@ const LayoutArchive = props => {
*/ */
const LayoutSlug = props => { const LayoutSlug = props => {
const { post, lock, validPassword, prev, next } = props const { post, lock, validPassword, prev, next } = props
const { fullWidth } = useGlobal()
return ( return (
<LayoutBase {...props}> <>
{lock && <ArticleLock validPassword={validPassword} />} {lock && <ArticleLock validPassword={validPassword} />}
<div id="article-wrapper" className="px-2 xl:max-w-4xl 2xl:max-w-6xl "> <div id="article-wrapper" className={`px-2 ${fullWidth ? '' : 'xl:max-w-4xl 2xl:max-w-6xl'}`}>
{/* 文章信息 */} {/* 文章信息 */}
<ArticleInfo post={post} /> <ArticleInfo post={post} />
@@ -193,7 +208,7 @@ const LayoutSlug = props => {
</div> </div>
</LayoutBase> </>
) )
} }
@@ -203,9 +218,9 @@ const LayoutSlug = props => {
* @returns * @returns
*/ */
const Layout404 = (props) => { const Layout404 = (props) => {
return <LayoutBase {...props}> return <>
404 Not found. 404 Not found.
</LayoutBase> </>
} }
/** /**
@@ -216,7 +231,7 @@ const Layout404 = (props) => {
const LayoutCategoryIndex = props => { const LayoutCategoryIndex = props => {
const { categoryOptions } = props const { categoryOptions } = props
return ( return (
<LayoutBase {...props}> <>
<div id='category-list' className='duration-200 flex flex-wrap'> <div id='category-list' className='duration-200 flex flex-wrap'>
{categoryOptions?.map(category => { {categoryOptions?.map(category => {
return ( return (
@@ -233,7 +248,7 @@ const LayoutCategoryIndex = props => {
) )
})} })}
</div> </div>
</LayoutBase> </>
) )
} }
@@ -245,7 +260,7 @@ const LayoutCategoryIndex = props => {
const LayoutTagIndex = (props) => { const LayoutTagIndex = (props) => {
const { tagOptions } = props const { tagOptions } = props
return ( return (
<LayoutBase {...props}> <>
<div id='tags-list' className='duration-200 flex flex-wrap'> <div id='tags-list' className='duration-200 flex flex-wrap'>
{tagOptions.map(tag => { {tagOptions.map(tag => {
return ( return (
@@ -261,12 +276,13 @@ const LayoutTagIndex = (props) => {
) )
})} })}
</div> </div>
</LayoutBase> </>
) )
} }
export { export {
CONFIG as THEME_CONFIG, CONFIG as THEME_CONFIG,
LayoutBase,
LayoutIndex, LayoutIndex,
LayoutSearch, LayoutSearch,
LayoutArchive, LayoutArchive,
@@ -274,5 +290,5 @@ export {
Layout404, Layout404,
LayoutCategoryIndex, LayoutCategoryIndex,
LayoutPostList, LayoutPostList,
LayoutTagIndex LayoutTagIndex,
} }

View File

@@ -6,6 +6,22 @@ import getConfig from 'next/config'
import * as ThemeComponents from '@theme-components' import * as ThemeComponents from '@theme-components'
// 所有主题在next.config.js中扫描 // 所有主题在next.config.js中扫描
export const { THEMES = [] } = getConfig().publicRuntimeConfig export const { THEMES = [] } = getConfig().publicRuntimeConfig
/**
* 加载全局布局
* 如果是
* @param {*} themeQuery
* @returns
*/
export const getGlobalLayoutByTheme = (themeQuery) => {
const layout = getLayoutNameByPath(-1)
if (themeQuery !== BLOG.THEME) {
return dynamic(() => import(`@/themes/${themeQuery}`).then(m => m[layout]), { ssr: true })
} else {
return ThemeComponents[layout]
}
}
/** /**
* 加载主题文件 * 加载主题文件
* 如果是 * 如果是
@@ -54,6 +70,8 @@ const checkThemeDOM = () => {
*/ */
export const getLayoutNameByPath = (path) => { export const getLayoutNameByPath = (path) => {
switch (path) { switch (path) {
case -1:
return 'LayoutBase'
case '/': case '/':
return 'LayoutIndex' return 'LayoutIndex'
case '/archive': case '/archive':