mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-13 23:16:47 +00:00
优化控制面板
This commit is contained in:
@@ -5,7 +5,7 @@ import { useEffect } from 'react'
|
||||
import { loadExternalResource } from '@/lib/utils'
|
||||
|
||||
const CusdisComponent = ({ frontMatter }) => {
|
||||
const { locale } = useGlobal()
|
||||
const { lang } = useGlobal()
|
||||
const router = useRouter()
|
||||
const { isDarkMode } = useGlobal()
|
||||
|
||||
@@ -18,7 +18,7 @@ const CusdisComponent = ({ frontMatter }) => {
|
||||
}, [isDarkMode])
|
||||
|
||||
return <div id="cusdis_thread"
|
||||
lang={locale.LOCALE.toLowerCase()}
|
||||
lang={lang.toLowerCase()}
|
||||
data-host={BLOG.COMMENT_CUSDIS_HOST}
|
||||
data-app-id={BLOG.COMMENT_CUSDIS_APP_ID}
|
||||
data-page-id={frontMatter.id}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { saveDarkModeToCookies } from '@/themes/theme'
|
||||
import { Moon, Sun } from './HeroIcons'
|
||||
import { useImperativeHandle } from 'react'
|
||||
|
||||
@@ -8,7 +7,7 @@ import { useImperativeHandle } from 'react'
|
||||
*/
|
||||
const DarkModeButton = (props) => {
|
||||
const { cRef, className } = props
|
||||
const { isDarkMode, updateDarkMode } = useGlobal()
|
||||
const { isDarkMode, toggleDarkMode } = useGlobal()
|
||||
|
||||
/**
|
||||
* 对外暴露方法
|
||||
@@ -16,22 +15,12 @@ const DarkModeButton = (props) => {
|
||||
useImperativeHandle(cRef, () => {
|
||||
return {
|
||||
handleChangeDarkMode: () => {
|
||||
handleChangeDarkMode()
|
||||
toggleDarkMode()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 用户手动设置主题
|
||||
const handleChangeDarkMode = () => {
|
||||
const newStatus = !isDarkMode
|
||||
saveDarkModeToCookies(newStatus)
|
||||
updateDarkMode(newStatus)
|
||||
const htmlElement = document.getElementsByTagName('html')[0]
|
||||
htmlElement.classList?.remove(newStatus ? 'light' : 'dark')
|
||||
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
return <div onClick={handleChangeDarkMode} className={`${className || ''} flex justify-center dark:text-gray-200 text-gray-800`}>
|
||||
return <div onClick={toggleDarkMode} className={`${className || ''} flex justify-center dark:text-gray-200 text-gray-800`}>
|
||||
<div id='darkModeButton' className=' hover:scale-110 cursor-pointer transform duration-200 w-5 h-5'> {isDarkMode ? <Sun /> : <Moon />}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -5,19 +5,21 @@ import { THEMES } from '@/themes/theme'
|
||||
import { useRouter } from 'next/router'
|
||||
import DarkModeButton from './DarkModeButton'
|
||||
import { getQueryParam } from '@/lib/utils'
|
||||
import LANGS from '@/lib/lang'
|
||||
/**
|
||||
*
|
||||
* @returns 主题切换
|
||||
*/
|
||||
const ThemeSwitch = () => {
|
||||
const { theme } = useGlobal()
|
||||
const { theme, lang, changeLang, locale, isDarkMode, toggleDarkMode } = useGlobal()
|
||||
const router = useRouter()
|
||||
const currentTheme = getQueryParam(router.asPath, 'theme') || theme
|
||||
// const currentLang = getQueryParam(router.asPath, 'lang') || lang
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
// 修改当前路径url中的 theme 参数
|
||||
// 例如 http://localhost?theme=hexo 跳转到 http://localhost?theme=newTheme
|
||||
const onSelectChange = (e) => {
|
||||
const onThemeSelectChange = (e) => {
|
||||
setIsLoading(true)
|
||||
const newTheme = e.target.value
|
||||
const query = router.query
|
||||
@@ -27,27 +29,52 @@ const ThemeSwitch = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const onLangSelectChange = (e) => {
|
||||
const newLang = e.target.value
|
||||
changeLang(newLang)
|
||||
}
|
||||
|
||||
return (<>
|
||||
<Draggable>
|
||||
<div id="draggableBox" style={{ left: '10px', top: '80vh' }} className="fixed z-50 dark:text-white bg-gray-50 dark:bg-black rounded-2xl drop-shadow-lg">
|
||||
<div className="p-3 w-full flex items-center text-sm group duration-200 transition-all">
|
||||
<DarkModeButton className='mr-2' />
|
||||
<div className='w-0 group-hover:w-20 transition-all duration-200 overflow-hidden'>
|
||||
<select value={currentTheme} onChange={onSelectChange} name="themes" className='appearance-none outline-none dark:text-white bg-gray-50 dark:bg-black uppercase cursor-pointer'>
|
||||
<div id="draggableBox" style={{ left: '0px', top: '80vh' }} className="fixed group space-y-2 overflow-hidden z-50 p-3 flex flex-col items-start dark:text-white bg-gray-50 dark:bg-black rounded-xl shadow-lg border dark:border-gray-800">
|
||||
{/* 深色按钮 */}
|
||||
<div className="text-sm flex items-center group-hover:w-32 transition-all duration-200">
|
||||
<DarkModeButton />
|
||||
<div onClick={toggleDarkMode} className='cursor-pointer w-0 group-hover:w-24 transition-all duration-200 overflow-hidden whitespace-nowrap pl-1'>{isDarkMode ? locale.MENU.DARK_MODE : locale.MENU.LIGHT_MODE}</div>
|
||||
</div>
|
||||
|
||||
{/* 翻译按钮 */}
|
||||
<div className="text-sm flex items-center group-hover:w-32 transition-all duration-200">
|
||||
<i class="fa-solid fa-language w-5" />
|
||||
<div className='w-0 group-hover:w-24 transition-all duration-200 overflow-hidden'>
|
||||
<select value={lang} onChange={onLangSelectChange} name="themes" className='pl-1 bg-gray-50 dark:bg-black appearance-none outline-none dark:text-white uppercase cursor-pointer'>
|
||||
{Object.keys(LANGS)?.map(t => {
|
||||
return <option key={t} value={t}>{LANGS[t].LOCALE}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 主题切换按钮 */}
|
||||
<div className="text-sm flex items-center group-hover:w-32 transition-all duration-200">
|
||||
<i className="fa-solid fa-palette w-5" />
|
||||
<div className='w-0 group-hover:w-24 transition-all duration-200 overflow-hidden'>
|
||||
<select value={currentTheme} onChange={onThemeSelectChange} name="themes" className='pl-1 bg-gray-50 dark:bg-black appearance-none outline-none dark:text-white uppercase cursor-pointer'>
|
||||
{THEMES?.map(t => {
|
||||
return <option key={t} value={t}>{t}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<i className="fa-solid fa-palette pl-2"/>
|
||||
</div>
|
||||
</div>
|
||||
{/* 切换主题加载时的全屏遮罩 */}
|
||||
<div className={`${isLoading ? 'opacity-50 ' : 'opacity-0'} w-screen h-screen bg-black text-white shadow-text flex justify-center items-center
|
||||
transition-all fixed top-0 left-0 pointer-events-none duration-1000 z-50 shadow-inner`}>
|
||||
<i className='text-3xl mr-5 fas fa-spinner animate-spin' />
|
||||
</div>
|
||||
</Draggable>
|
||||
|
||||
{/* 切换主题加载时的全屏遮罩 */}
|
||||
<div className={`${isLoading ? 'opacity-50 ' : 'opacity-0'}
|
||||
w-screen h-screen bg-black text-white shadow-text flex justify-center items-center
|
||||
transition-all fixed top-0 left-0 pointer-events-none duration-1000 z-50 shadow-inner`}>
|
||||
<i className='text-3xl mr-5 fas fa-spinner animate-spin' />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
31
lib/cache/cache_manager.js
vendored
31
lib/cache/cache_manager.js
vendored
@@ -3,15 +3,6 @@ import FileCache from './local_file_cache'
|
||||
import MongoCache from './mongo_db_cache'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
let api
|
||||
if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) {
|
||||
api = MongoCache
|
||||
} else if (process.env.ENABLE_FILE_CACHE) {
|
||||
api = FileCache
|
||||
} else {
|
||||
api = MemoryCache
|
||||
}
|
||||
|
||||
/**
|
||||
* 为减少频繁接口请求,notion数据将被缓存
|
||||
* @param {*} key
|
||||
@@ -19,11 +10,11 @@ if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) {
|
||||
*/
|
||||
export async function getDataFromCache(key, force) {
|
||||
if (BLOG.ENABLE_CACHE || force) {
|
||||
const dataFromCache = await api.getCache(key)
|
||||
const dataFromCache = await getApi().getCache(key)
|
||||
if (JSON.stringify(dataFromCache) === '[]') {
|
||||
return null
|
||||
}
|
||||
return api.getCache(key)
|
||||
return getApi().getCache(key)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
@@ -33,12 +24,26 @@ export async function setDataToCache(key, data) {
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
await api.setCache(key, data)
|
||||
await getApi().setCache(key, data)
|
||||
}
|
||||
|
||||
export async function delCacheData(key) {
|
||||
if (!BLOG.ENABLE_CACHE) {
|
||||
return
|
||||
}
|
||||
await api.delCache(key)
|
||||
await getApi().delCache(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存实现类
|
||||
* @returns
|
||||
*/
|
||||
function getApi() {
|
||||
if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) {
|
||||
return MongoCache
|
||||
} else if (process.env.ENABLE_FILE_CACHE) {
|
||||
return FileCache
|
||||
} else {
|
||||
return MemoryCache
|
||||
}
|
||||
}
|
||||
|
||||
10
lib/cache/local_file_cache.js
vendored
10
lib/cache/local_file_cache.js
vendored
@@ -41,7 +41,7 @@ export async function setCache (key, data) {
|
||||
fs.writeFileSync(jsonFile, JSON.stringify(json))
|
||||
}
|
||||
|
||||
export async function delCache (key, data) {
|
||||
export async function delCache (key) {
|
||||
const exist = await fs.existsSync(jsonFile)
|
||||
const json = exist ? JSON.parse(await fs.readFileSync(jsonFile)) : {}
|
||||
delete json.key
|
||||
@@ -49,4 +49,12 @@ export async function delCache (key, data) {
|
||||
fs.writeFileSync(jsonFile, JSON.stringify(json))
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存
|
||||
*/
|
||||
export async function cleanCache() {
|
||||
const json = {}
|
||||
fs.writeFileSync(jsonFile, JSON.stringify(json))
|
||||
}
|
||||
|
||||
export default { getCache, setCache, delCache }
|
||||
|
||||
@@ -64,7 +64,7 @@ export const siteConfigMap = () => {
|
||||
const val = deepClone(BLOG)
|
||||
for (const key in val) {
|
||||
val[key] = siteConfig(key)
|
||||
console.log('site', key, val[key], siteConfig(key))
|
||||
// console.log('site', key, val[key], siteConfig(key))
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { generateLocaleDict, initLocale } from './lang'
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { THEMES, initDarkMode } from '@/themes/theme'
|
||||
import { THEMES, initDarkMode, saveDarkModeToCookies } from '@/themes/theme'
|
||||
|
||||
import BLOG from '@/blog.config'
|
||||
import NProgress from 'nprogress'
|
||||
import { isBrowser } from './utils'
|
||||
|
||||
const GlobalContext = createContext()
|
||||
|
||||
@@ -35,17 +34,34 @@ export function GlobalContextProvider(props) {
|
||||
return newTheme
|
||||
}
|
||||
|
||||
// 切换深色模式
|
||||
const toggleDarkMode = () => {
|
||||
const newStatus = !isDarkMode
|
||||
saveDarkModeToCookies(newStatus)
|
||||
updateDarkMode(newStatus)
|
||||
const htmlElement = document.getElementsByTagName('html')[0]
|
||||
htmlElement.classList?.remove(newStatus ? 'light' : 'dark')
|
||||
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新语言
|
||||
*/
|
||||
function changeLang(lang) {
|
||||
if (lang) {
|
||||
updateLang(lang)
|
||||
updateLocale(generateLocaleDict(lang))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
initLocale(lang, locale, updateLang, updateLocale)
|
||||
initDarkMode(updateDarkMode)
|
||||
checkThemeDOM()
|
||||
initLocale(lang, locale, updateLang, updateLocale)
|
||||
}, [])
|
||||
|
||||
// 加载默认主题
|
||||
// useEffect(() => {
|
||||
// const queryTheme = getQueryVariable('theme') || theme
|
||||
// setTheme(queryTheme)
|
||||
// }, [router])
|
||||
useEffect(() => {
|
||||
checkThemeDOM()
|
||||
})
|
||||
|
||||
// 加载进度条
|
||||
useEffect(() => {
|
||||
@@ -62,6 +78,7 @@ export function GlobalContextProvider(props) {
|
||||
NProgress.done()
|
||||
setOnLoading(false)
|
||||
}
|
||||
|
||||
router.events.on('routeChangeStart', handleStart)
|
||||
router.events.on('routeChangeError', handleStop)
|
||||
router.events.on('routeChangeComplete', handleStop)
|
||||
@@ -75,10 +92,11 @@ export function GlobalContextProvider(props) {
|
||||
return (
|
||||
<GlobalContext.Provider value={{
|
||||
NOTION_CONFIG,
|
||||
toggleDarkMode,
|
||||
onLoading,
|
||||
setOnLoading,
|
||||
lang,
|
||||
updateLang,
|
||||
changeLang,
|
||||
locale,
|
||||
updateLocale,
|
||||
isDarkMode,
|
||||
@@ -99,17 +117,13 @@ export function GlobalContextProvider(props) {
|
||||
* 切换主题时的特殊处理
|
||||
*/
|
||||
const checkThemeDOM = () => {
|
||||
if (isBrowser) {
|
||||
setTimeout(() => {
|
||||
const elements = document.querySelectorAll('[id^="theme-"]')
|
||||
if (elements?.length > 1) {
|
||||
elements[elements.length - 1].scrollIntoView()
|
||||
// 删除前面的元素,只保留最后一个元素
|
||||
for (let i = 0; i < elements.length - 1; i++) {
|
||||
elements[i].parentNode.removeChild(elements[i])
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
const elements = document.querySelectorAll('[id^="theme-"]')
|
||||
if (elements?.length > 1) {
|
||||
elements[elements.length - 1].scrollIntoView()
|
||||
// 删除前面的元素,只保留最后一个元素
|
||||
for (let i = 0; i < elements.length - 1; i++) {
|
||||
elements[i].parentNode.removeChild(elements[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
18
lib/lang.js
18
lib/lang.js
@@ -12,7 +12,7 @@ import { getQueryVariable, isBrowser, mergeDeep } from './utils'
|
||||
* 在这里配置所有支持的语言
|
||||
* 国家-地区
|
||||
*/
|
||||
const lang = {
|
||||
const LANGS = {
|
||||
'en-US': enUS,
|
||||
'zh-CN': zhCN,
|
||||
'zh-HK': zhHK,
|
||||
@@ -22,7 +22,7 @@ const lang = {
|
||||
'ja-JP': jaJP
|
||||
}
|
||||
|
||||
export default lang
|
||||
export default LANGS
|
||||
|
||||
/**
|
||||
* 获取当前语言字典
|
||||
@@ -30,7 +30,7 @@ export default lang
|
||||
* @returns 不同语言对应字典
|
||||
*/
|
||||
export function generateLocaleDict(langString) {
|
||||
const supportedLocales = Object.keys(lang)
|
||||
const supportedLocales = Object.keys(LANGS)
|
||||
let userLocale
|
||||
|
||||
// 将语言字符串拆分为语言和地区代码,例如将 "zh-CN" 拆分为 "zh" 和 "CN"
|
||||
@@ -39,24 +39,24 @@ export function generateLocaleDict(langString) {
|
||||
// 优先匹配语言和地区都匹配的情况
|
||||
const specificLocale = `${language}-${region}`
|
||||
if (supportedLocales.includes(specificLocale)) {
|
||||
userLocale = lang[specificLocale]
|
||||
userLocale = LANGS[specificLocale]
|
||||
}
|
||||
|
||||
// 然后尝试匹配只有语言匹配的情况
|
||||
if (!userLocale) {
|
||||
const languageOnlyLocales = supportedLocales.filter(locale => locale.startsWith(language))
|
||||
if (languageOnlyLocales.length > 0) {
|
||||
userLocale = lang[languageOnlyLocales[0]]
|
||||
userLocale = LANGS[languageOnlyLocales[0]]
|
||||
}
|
||||
}
|
||||
|
||||
// 如果还没匹配到,则返回最接近的语言包
|
||||
if (!userLocale) {
|
||||
const fallbackLocale = supportedLocales.find(locale => locale.startsWith('en'))
|
||||
userLocale = lang[fallbackLocale]
|
||||
userLocale = LANGS[fallbackLocale]
|
||||
}
|
||||
|
||||
return mergeDeep({}, lang['en-US'], userLocale)
|
||||
return mergeDeep({}, LANGS['en-US'], userLocale)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,10 +67,10 @@ export function initLocale(lang, locale, changeLang, changeLocale) {
|
||||
if (isBrowser) {
|
||||
const queryLang = getQueryVariable('lang') || loadLangFromCookies()
|
||||
let currentLang = lang
|
||||
if (queryLang !== lang) {
|
||||
if (queryLang && queryLang !== 'undefined' && queryLang !== lang) {
|
||||
currentLang = queryLang
|
||||
}
|
||||
console.log('初始化语言', currentLang)
|
||||
|
||||
changeLang(currentLang)
|
||||
saveLangToCookies(currentLang)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
LOCALE: 'en-US',
|
||||
LOCALE: 'English',
|
||||
MENU: {
|
||||
WALK_AROUND: 'Walk Around',
|
||||
CATEGORY: 'Category',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
LOCALE: 'fr-FR',
|
||||
LOCALE: 'français',
|
||||
NAV: {
|
||||
INDEX: 'Accueil',
|
||||
RSS: 'RSS',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
LOCALE: 'ja-JP',
|
||||
LOCALE: '日本語',
|
||||
NAV: {
|
||||
INDEX: 'ホーム',
|
||||
RSS: '購読',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
LOCALE: 'tr-TR',
|
||||
LOCALE: 'Türkçe',
|
||||
NAV: {
|
||||
INDEX: 'Blog',
|
||||
RSS: 'RSS',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
LOCALE: 'zh-CN',
|
||||
LOCALE: '中文(简体)',
|
||||
MENU: {
|
||||
WALK_AROUND: '随便逛逛',
|
||||
CATEGORY: '博客分类',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
LOCALE: '中文(繁体香港)',
|
||||
NAV: {
|
||||
INDEX: '網誌',
|
||||
RSS: '訂閱',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
LOCALE: 'zh-TW',
|
||||
LOCALE: '中文(繁体台湾)',
|
||||
NAV: {
|
||||
INDEX: '部落格',
|
||||
RSS: '訂閱',
|
||||
|
||||
15
pages/api/cache.js
Normal file
15
pages/api/cache.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { cleanCache } from '@/lib/cache/local_file_cache'
|
||||
|
||||
/**
|
||||
* 清理缓存
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
*/
|
||||
export default async function handler(req, res) {
|
||||
try {
|
||||
await cleanCache()
|
||||
res.status(200).json({ status: 'success', message: 'Clean cache successful!' })
|
||||
} catch (error) {
|
||||
res.status(400).json({ status: 'error', message: 'Clean cache failed!', error })
|
||||
}
|
||||
}
|
||||
@@ -40,12 +40,12 @@ export const MenuList = (props) => {
|
||||
data-aos-once="true"
|
||||
data-aos-anchor-placement="top-bottom"
|
||||
className='hidden md:block leading-8 text-gray-500 dark:text-gray-400 font-sans'>
|
||||
{links.map(link => link && link.show && <MenuItemDrop key={link?.id} link={link} />)}
|
||||
{links.map((link, index) => link && link.show && <MenuItemDrop key={index} link={link} />)}
|
||||
</nav>
|
||||
|
||||
{/* 移动端菜单 */}
|
||||
<div id='nav-menu-mobile' className='block md:hidden my-auto justify-start bg-white'>
|
||||
{links?.map(link => link && link.show && <MenuItemCollapse onHeightChange={props.onHeightChange} key={link?.id} link={link} />)}
|
||||
{links?.map((link, index) => link && link.show && <MenuItemCollapse onHeightChange={props.onHeightChange} key={index} link={link} />)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user