diff --git a/components/TwikooCommentCounter.js b/components/TwikooCommentCounter.js
index 87c16aaf..7e2a15df 100644
--- a/components/TwikooCommentCounter.js
+++ b/components/TwikooCommentCounter.js
@@ -1,4 +1,4 @@
-import BLOG from '@/blog.config'
+import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { loadExternalResource } from '@/lib/utils'
import { useRouter } from 'next/router'
@@ -13,16 +13,37 @@ import { useEffect } from 'react'
const TwikooCommentCounter = (props) => {
let commentsData = []
const { theme } = useGlobal()
+ const router = useRouter()
+ useEffect(() => {
+ // console.log('路由触发评论计数')
+ if (props?.posts && props?.posts?.length > 0) {
+ fetchTwikooData(props.posts)
+ }
+ }, [router.events])
+
+ // 监控主题变化时的的评论数
+ useEffect(() => {
+ // console.log('主题触发评论计数', commentsData)
+ updateCommentCount()
+ }, [theme])
+
+ const twikooCDNURL = siteConfig('COMMENT_TWIKOO_CDN_URL')
+ const twikooENVID = siteConfig('COMMENT_TWIKOO_ENV_ID')
+
+ /**
+ * 加载外部twikoojs
+ * @param {*} posts
+ */
const fetchTwikooData = async (posts) => {
posts.forEach(post => {
post.slug = post.slug.startsWith('/') ? post.slug : `/${post.slug}`
})
try {
- await loadExternalResource(BLOG.COMMENT_TWIKOO_CDN_URL, 'js')
+ await loadExternalResource(twikooCDNURL, 'js')
const twikoo = window.twikoo
twikoo.getCommentsCount({
- envId: BLOG.COMMENT_TWIKOO_ENV_ID, // 环境 ID
+ envId: twikooENVID, // 环境 ID
// region: 'ap-guangzhou', // 环境地域,默认为 ap-shanghai,如果您的环境地域不是上海,需传此参数
urls: posts?.map(post => post.slug), // 不包含协议、域名、参数的文章路径列表,必传参数
includeReply: true // 评论数是否包括回复,默认:false
@@ -58,20 +79,7 @@ const TwikooCommentCounter = (props) => {
}
})
}
- const router = useRouter()
- useEffect(() => {
- // console.log('路由触发评论计数')
- if (props?.posts && props?.posts?.length > 0) {
- fetchTwikooData(props.posts)
- }
- }, [router.events])
-
- // 监控主题变化时的的评论数
- useEffect(() => {
- // console.log('主题触发评论计数', commentsData)
- updateCommentCount()
- }, [theme])
return null
}
diff --git a/components/Utterances.js b/components/Utterances.js
index e0298daf..34c4f09c 100644
--- a/components/Utterances.js
+++ b/components/Utterances.js
@@ -1,5 +1,6 @@
-import BLOG from '@/blog.config'
-import { useEffect } from 'react'
+import { useEffect, useState } from 'react'
+import { siteConfig } from '@/lib/config'
+import { useGlobal } from '@/lib/global'
/**
* 评论插件
@@ -9,28 +10,48 @@ import { useEffect } from 'react'
* @constructor
*/
const Utterances = ({ issueTerm, layout }) => {
+ const { isDarkMode } = useGlobal()
+
+ const [isLoading, setLoading] = useState(true);
+
useEffect(() => {
- const theme =
- BLOG.APPEARANCE === 'auto'
- ? 'preferred-color-scheme'
- : BLOG.APPEARANCE === 'light'
- ? 'github-light'
- : 'github-dark'
- const script = document.createElement('script')
- const anchor = document.getElementById('comments')
- script.setAttribute('src', 'https://utteranc.es/client.js')
- script.setAttribute('crossorigin', 'anonymous')
- script.setAttribute('async', true)
- script.setAttribute('repo', BLOG.COMMENT_UTTERRANCES_REPO)
- script.setAttribute('issue-term', 'title')
- script.setAttribute('theme', theme)
- anchor.appendChild(script)
+ const script = document.createElement('script');
+ const anchor = document.getElementById('comments');
+ script.onload = () => setLoading(false);
+ script.setAttribute('src', 'https://utteranc.es/client.js');
+ script.setAttribute('crossorigin', 'anonymous');
+ script.setAttribute('async', true);
+ script.setAttribute('repo', siteConfig('COMMENT_UTTERRANCES_REPO'));
+ script.setAttribute('issue-term', 'title');
+ // 初始主题
+ script.setAttribute('theme', isDarkMode ? 'github-dark' : 'github-light');
+ anchor.appendChild(script);
+
return () => {
- anchor.innerHTML = ''
+ // anchor.innerHTML = ''
+ };
+ }, []);
+
+ useEffect(() => {
+ // 直接设置 iframe 的类来改变主题,不重新加载脚本
+ const iframe = document.querySelector('iframe.utterances-frame');
+ if (iframe) {
+ iframe.contentWindow.postMessage({
+ type: 'set-theme',
+ theme: isDarkMode ? 'github-dark' : 'github-light'
+ }, 'https://utteranc.es');
}
- })
- return
+ }, [isDarkMode]);
+
+ return (
+
+ );
}
export default Utterances
diff --git a/components/ValineComponent.js b/components/ValineComponent.js
index 0a7ab6cb..b26fb34d 100644
--- a/components/ValineComponent.js
+++ b/components/ValineComponent.js
@@ -1,23 +1,23 @@
-import BLOG from '@/blog.config'
+import { siteConfig } from '@/lib/config'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
const ValineComponent = ({ path }) => {
const loadValine = async () => {
try {
- await loadExternalResource(BLOG.COMMENT_VALINE_CDN, 'js')
+ await loadExternalResource(siteConfig('COMMENT_VALINE_CDN'), 'js')
const Valine = window.Valine
// eslint-disable-next-line no-unused-vars
const valine = new Valine({
el: '#valine', // 容器元素
- lang: BLOG.LANG, // 用于手动设定评论区语言,支持的语言列表 https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js
- appId: BLOG.COMMENT_VALINE_APP_ID,
- appKey: BLOG.COMMENT_VALINE_APP_KEY,
+ lang: siteConfig('LANG'), // 用于手动设定评论区语言,支持的语言列表 https://github.com/imaegoo/twikoo/blob/main/src/client/utils/i18n/index.js
+ appId: siteConfig('COMMENT_VALINE_APP_ID'),
+ appKey: siteConfig('COMMENT_VALINE_APP_KEY'),
avatar: '',
path,
recordIP: true,
- placeholder: BLOG.COMMENT_VALINE_PLACEHOLDER,
- serverURLs: BLOG.COMMENT_VALINE_SERVER_URLS,
+ placeholder: siteConfig('COMMENT_VALINE_PLACEHOLDER'),
+ serverURLs: siteConfig('COMMENT_VALINE_SERVER_URLS'),
visitor: true
})
} catch (error) {
diff --git a/components/WWAds.js b/components/WWAds.js
index 87901f39..d1f786a4 100644
--- a/components/WWAds.js
+++ b/components/WWAds.js
@@ -1,5 +1,4 @@
-import React from 'react'
-import BLOG from '@/blog.config'
+import { siteConfig } from '@/lib/config'
/**
* 万维广告插件
@@ -8,11 +7,13 @@ import BLOG from '@/blog.config'
* @returns {JSX.Element | null} - 返回渲染的 JSX 元素或 null
*/
export default function WWAds({ orientation = 'vertical', sticky = false, className }) {
- if (!JSON.parse(BLOG.AD_WWADS_ID)) {
+ const adWWADSId = siteConfig('AD_WWADS_ID')
+
+ if (!adWWADSId) {
return null
}
- return (
-
- )
+ return
}
diff --git a/components/WalineComponent.js b/components/WalineComponent.js
index 0b033922..37247d48 100644
--- a/components/WalineComponent.js
+++ b/components/WalineComponent.js
@@ -1,8 +1,8 @@
-import React from 'react'
+import React, { createRef } from 'react'
import { init } from '@waline/client'
-import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import '@waline/client/dist/waline.css'
+import { siteConfig } from '@/lib/config'
const path = ''
let waline = null
@@ -12,7 +12,7 @@ let waline = null
* @returns
*/
const WalineComponent = (props) => {
- const containerRef = React.createRef()
+ const containerRef = createRef()
const router = useRouter()
const updateWaline = url => {
@@ -26,8 +26,8 @@ const WalineComponent = (props) => {
waline = init({
...props,
el: containerRef.current,
- serverURL: BLOG.COMMENT_WALINE_SERVER_URL,
- lang: BLOG.lang,
+ serverURL: siteConfig('COMMENT_WALINE_SERVER_URL'),
+ lang: siteConfig('LANG'),
reaction: true,
dark: 'html.dark',
emoji: [
diff --git a/components/WebMention.js b/components/WebMention.js
index 375a0141..61e11c69 100644
--- a/components/WebMention.js
+++ b/components/WebMention.js
@@ -1,7 +1,7 @@
-import BLOG from '@/blog.config'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import Image from 'next/image'
+import { siteConfig } from '@/lib/config'
/**
* 评论插件
@@ -78,7 +78,7 @@ const WebmentionReplies = ({ target }) => {
const [mentions, setMentions] = useState([])
const fetchMentions = async (target) =>
fetch(
- `https://webmention.io/api/mentions.jf2?per-page=500&target=${encodeURIComponent(target)}&token=${BLOG.COMMENT_WEBMENTION.TOKEN}`
+ `https://webmention.io/api/mentions.jf2?per-page=500&target=${encodeURIComponent(target)}&token=${siteConfig('COMMENT_WEBMENTION_TOKEN')}`
).then((response) => (response.json ? response.json() : response))
useEffect(() => {
async function getMentions() {
@@ -137,8 +137,8 @@ const WebmentionReplies = ({ target }) => {
const WebMentionBlock = ({ frontMatter }) => {
const router = useRouter()
- const url = `https://${BLOG.COMMENT_WEBMENTION.HOSTNAME}${router.asPath}`
- const tweet = `${frontMatter.title} by @${BLOG.COMMENT_WEBMENTION.TWITTER_USERNAME} ${url}`
+ const url = `https://${siteConfig('COMMENT_WEBMENTION_HOSTNAME')}${router.asPath}`
+ const tweet = `${frontMatter.title} by @${siteConfig('COMMENT_WEBMENTION_TWITTER_USERNAME')} ${url}`
return (
diff --git a/components/Webwhiz.js b/components/Webwhiz.js
index 690333cb..4a736647 100644
--- a/components/Webwhiz.js
+++ b/components/Webwhiz.js
@@ -1,4 +1,4 @@
-import BLOG from '@/blog.config'
+import { siteConfig } from '@/lib/config'
import ExternalScript from './ExternalScript'
/**
@@ -10,8 +10,8 @@ export default function WebWhiz() {
const props = {
id: '__webwhizSdk__',
src: 'https://www.unpkg.com/webwhiz@1.0.0/dist/sdk.js',
- baseUrl: BLOG.WEB_WHIZ_BASE_URL,
- chatbotId: BLOG.WEB_WHIZ_CHAT_BOT_ID
+ baseUrl: siteConfig('WEB_WHIZ_BASE_URL'),
+ chatbotId: siteConfig('WEB_WHIZ_CHAT_BOT_ID')
}
return
}
diff --git a/hooks/useAdjustStyle.js b/hooks/useAdjustStyle.js
new file mode 100644
index 00000000..4cd6f880
--- /dev/null
+++ b/hooks/useAdjustStyle.js
@@ -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;
diff --git a/lib/algolia.js b/lib/algolia.js
index d2d9cb37..fe2f9a83 100644
--- a/lib/algolia.js
+++ b/lib/algolia.js
@@ -42,7 +42,7 @@ const uploadDataToAlgolia = async(post) => {
if (!existed || !existed?.lastEditedDate || !existed?.lastIndexDate) {
needUpdateIndex = true
} else {
- const lastEditedDate = new Date(existed.lastEditedDate)
+ const lastEditedDate = new Date(post.lastEditedDate)
const lastIndexDate = new Date(existed.lastIndexDate)
if (lastEditedDate.getTime() > lastIndexDate.getTime()) {
needUpdateIndex = true
diff --git a/lib/cache/cache_manager.js b/lib/cache/cache_manager.js
index 8796d3f4..9103b4b1 100644
--- a/lib/cache/cache_manager.js
+++ b/lib/cache/cache_manager.js
@@ -3,27 +3,18 @@ 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
* @returns
*/
export async function getDataFromCache(key, force) {
- if (BLOG.ENABLE_CACHE || force) {
- const dataFromCache = await api.getCache(key)
+ if (JSON.parse(BLOG.ENABLE_CACHE) || force) {
+ 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) {
+ if (!JSON.parse(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
+ }
}
diff --git a/lib/cache/local_file_cache.js b/lib/cache/local_file_cache.js
index cda7f35a..c411d6e5 100644
--- a/lib/cache/local_file_cache.js
+++ b/lib/cache/local_file_cache.js
@@ -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 }
diff --git a/lib/config.js b/lib/config.js
new file mode 100644
index 00000000..46575239
--- /dev/null
+++ b/lib/config.js
@@ -0,0 +1,96 @@
+'use client'
+
+import BLOG from '@/blog.config'
+import { useGlobal } from './global'
+import { deepClone } from './utils'
+
+/**
+ * 读取配置顺序
+ * 1. 优先读取NotionConfig表
+ * 2. 其次读取环境变量
+ * 3. 再读取blog.config.js / 或各个主题的CONFIG文件
+ * @param {*} key ; 参数名
+ * @param {*} defaultVal ; 参数不存在默认返回值
+ * @param {*} extendConfig ; 参考配置对象{key:val},如果notion中找不到优先尝试在这里面查找
+ * @returns
+ */
+export const siteConfig = (key, defaultVal = null, extendConfig = null) => {
+ let global = null
+ try {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ global = useGlobal()
+ } catch (error) {}
+
+ // 首先 配置最优先读取NOTION中的表格配置
+ let val = null
+ let siteInfo = null
+
+ if (global) {
+ val = global.NOTION_CONFIG?.[key]
+ siteInfo = global.siteInfo
+ // console.log('当前变量', key, val)
+ }
+
+ if (!val) {
+ // 这里针对部分key做一些兼容处理
+ switch (key) {
+ case 'HOME_BANNER_IMAGE':
+ val = siteInfo?.pageCover // 封面图取Notion的封面
+ break
+ case 'AVATAR':
+ val = siteInfo?.icon // 封面图取Notion的头像
+ break
+ case 'TITLE':
+ val = siteInfo?.title // 标题取Notion中的标题
+ break
+ case 'DESCRIPTION':
+ val = siteInfo?.description // 标题取Notion中的标题
+ break
+ }
+ }
+
+ // 其次 有传入的配置参考,则尝试读取
+ if (!val && extendConfig) {
+ val = extendConfig[key]
+ }
+
+ // 其次 NOTION没有找到配置,则会读取blog.config.js文件
+ if (!val) {
+ val = BLOG[key]
+ }
+
+ if (!val) {
+ return defaultVal
+ } else {
+ if (typeof val === 'string') {
+ if (val === 'true' || val === 'false') {
+ return JSON.parse(val);
+ }
+ return val;
+ } else {
+ try {
+ return JSON.parse(val);
+ } catch (error) {
+ // 如果值是一个字符串但不是有效的 JSON 格式,直接返回字符串
+ return val;
+ }
+ }
+ }
+}
+
+/**
+ * 读取所有配置
+ * 1. 优先读取NotionConfig表
+ * 2. 其次读取环境变量
+ * 3. 再读取blog.config.js文件
+ * @param {*} key
+ * @returns
+ */
+export const siteConfigMap = () => {
+ const val = deepClone(BLOG)
+ for (const key in val) {
+ val[key] = siteConfig(key)
+ // console.log('site', key, val[key], siteConfig(key))
+ }
+ return val
+}
diff --git a/lib/global.js b/lib/global.js
index c290a9e0..f6ffc1e8 100644
--- a/lib/global.js
+++ b/lib/global.js
@@ -1,59 +1,27 @@
-import { generateLocaleDict, initLocale } from './lang'
+import { generateLocaleDict, initLocale, saveLangToCookies } from './lang'
import { createContext, useContext, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
-import BLOG from '@/blog.config'
-import { THEMES, initDarkMode } from '@/themes/theme'
-import NProgress from 'nprogress'
-import { getQueryVariable, isBrowser } from './utils'
-
+import { THEMES, initDarkMode, saveDarkModeToCookies } from '@/themes/theme'
+import { APPEARANCE, LANG, THEME } from 'blog.config'
const GlobalContext = createContext()
/**
- * 全局变量Provider,包括语言本地化、样式主题、搜索词
+ * 定义全局变量,包括语言、主题、深色模式、加载状态
* @param children
* @returns {JSX.Element}
* @constructor
*/
export function GlobalContextProvider(props) {
- const { children, siteInfo, categoryOptions, tagOptions } = props
+ const { post, children, siteInfo, categoryOptions, tagOptions, NOTION_CONFIG } = props
const router = useRouter()
- const [lang, updateLang] = useState(BLOG.LANG) // 默认语言
- const [locale, updateLocale] = useState(generateLocaleDict(BLOG.LANG)) // 默认语言
- const [theme, setTheme] = useState(BLOG.THEME) // 默认博客主题
- const [isDarkMode, updateDarkMode] = useState(BLOG.APPEARANCE === 'dark') // 默认深色模式
+ const [lang, updateLang] = useState(NOTION_CONFIG?.LANG || LANG) // 默认语言
+ const [locale, updateLocale] = useState(generateLocaleDict(NOTION_CONFIG?.LANG || LANG)) // 默认语言
+ const [theme, setTheme] = useState(NOTION_CONFIG?.THEME || THEME) // 默认博客主题
+ const [isDarkMode, updateDarkMode] = useState(NOTION_CONFIG?.APPEARANCE || APPEARANCE === 'dark') // 默认深色模式
const [onLoading, setOnLoading] = useState(false) // 抓取文章数据
- useEffect(() => {
- initLocale(lang, locale, updateLang, updateLocale)
- initDarkMode(updateDarkMode)
- initTheme()
- }, [])
-
- useEffect(() => {
- const handleStart = (url) => {
- NProgress.start()
- const { theme } = router.query
- if (theme && !url.includes(`theme=${theme}`)) {
- const newUrl = `${url}${url.includes('?') ? '&' : '?'}theme=${theme}`
- router.push(newUrl)
- }
- setOnLoading(true)
- }
- const handleStop = () => {
- NProgress.done()
- setOnLoading(false)
- }
- const queryTheme = getQueryVariable('theme') || BLOG.THEME
- setTheme(queryTheme)
- router.events.on('routeChangeStart', handleStart)
- router.events.on('routeChangeError', handleStop)
- router.events.on('routeChangeComplete', handleStop)
- return () => {
- router.events.off('routeChangeStart', handleStart)
- router.events.off('routeChangeComplete', handleStop)
- router.events.off('routeChangeError', handleStop)
- }
- }, [router])
+ // 是否全屏
+ const fullWidth = post?.fullWidth ?? false
// 切换主题
function switchTheme() {
@@ -66,10 +34,65 @@ 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) {
+ saveLangToCookies(lang)
+ updateLang(lang)
+ updateLocale(generateLocaleDict(lang))
+ }
+ }
+
+ useEffect(() => {
+ initDarkMode(updateDarkMode)
+ initLocale(lang, locale, updateLang, updateLocale)
+ }, [])
+
+ // 加载进度条
+ useEffect(() => {
+ const handleStart = (url) => {
+ const { theme } = router.query
+ if (theme && !url.includes(`theme=${theme}`)) {
+ const newUrl = `${url}${url.includes('?') ? '&' : '?'}theme=${theme}`
+ router.push(newUrl)
+ }
+ setOnLoading(true)
+ }
+ const handleStop = () => {
+ setOnLoading(false)
+ }
+
+ router.events.on('routeChangeStart', handleStart)
+ router.events.on('routeChangeError', handleStop)
+ router.events.on('routeChangeComplete', handleStop)
+ return () => {
+ router.events.off('routeChangeStart', handleStart)
+ router.events.off('routeChangeComplete', handleStop)
+ router.events.off('routeChangeError', handleStop)
+ }
+ }, [router])
+
return (
{
- if (isBrowser) {
- setTimeout(() => {
- const elements = document.querySelectorAll('[id^="theme-"]')
- if (elements?.length > 1) {
- elements[elements.length - 1].scrollIntoView()
- // 删除前面的元素,只保留最后一个元素
- for (let i = 0; i < elements.length - 1; i++) {
- elements[i].parentNode.removeChild(elements[i])
- }
- }
- }, 500)
- }
-}
-
export const useGlobal = () => useContext(GlobalContext)
diff --git a/lib/lang.js b/lib/lang.js
index abc51354..5f81af18 100644
--- a/lib/lang.js
+++ b/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,33 +30,33 @@ export default lang
* @returns 不同语言对应字典
*/
export function generateLocaleDict(langString) {
- const supportedLocales = Object.keys(lang)
+ const supportedLocales = Object.keys(LANGS)
let userLocale
// 将语言字符串拆分为语言和地区代码,例如将 "zh-CN" 拆分为 "zh" 和 "CN"
- const [language, region] = langString.split(/[-_]/)
+ const [language, region] = langString?.split(/[-_]/)
// 优先匹配语言和地区都匹配的情况
const specificLocale = `${language}-${region}`
if (supportedLocales.includes(specificLocale)) {
- userLocale = lang[specificLocale]
+ userLocale = LANGS[specificLocale]
}
// 然后尝试匹配只有语言匹配的情况
if (!userLocale) {
const languageOnlyLocales = supportedLocales.filter(locale => locale.startsWith(language))
if (languageOnlyLocales.length > 0) {
- userLocale = lang[languageOnlyLocales[0]]
+ userLocale = LANGS[languageOnlyLocales[0]]
}
}
// 如果还没匹配到,则返回最接近的语言包
if (!userLocale) {
const fallbackLocale = supportedLocales.find(locale => locale.startsWith('en'))
- userLocale = lang[fallbackLocale]
+ userLocale = LANGS[fallbackLocale]
}
- return mergeDeep({}, lang['en-US'], userLocale)
+ return mergeDeep({}, LANGS['en-US'], userLocale)
}
/**
@@ -65,11 +65,12 @@ export function generateLocaleDict(langString) {
*/
export function initLocale(lang, locale, changeLang, changeLocale) {
if (isBrowser) {
- const queryLang = getQueryVariable('lang') || loadLangFromCookies() || window.navigator.language
+ const queryLang = getQueryVariable('lang') || loadLangFromCookies()
let currentLang = lang
- if (queryLang !== lang) {
+ if (queryLang && queryLang !== 'undefined' && queryLang !== lang) {
currentLang = queryLang
}
+
changeLang(currentLang)
saveLangToCookies(currentLang)
@@ -88,9 +89,9 @@ export const loadLangFromCookies = () => {
}
/**
- * 保存语言
- * @param newTheme
- */
+ * 保存语言
+ * @param newTheme
+ */
export const saveLangToCookies = (lang) => {
cookie.save('lang', lang, { path: '/' })
}
diff --git a/lib/lang/en-US.js b/lib/lang/en-US.js
index 51e38115..063da7f8 100644
--- a/lib/lang/en-US.js
+++ b/lib/lang/en-US.js
@@ -1,5 +1,5 @@
export default {
- LOCALE: 'en-US',
+ LOCALE: 'English',
MENU: {
WALK_AROUND: 'Walk Around',
CATEGORY: 'Category',
@@ -41,6 +41,7 @@ export default {
ARTICLE_DETAIL: 'Article Details',
PASSWORD_ERROR: 'Password Error!',
ARTICLE_LOCK_TIPS: 'Please Enter the password:',
+ NO_RESULTS_FOUND: 'No results found.',
SUBMIT: 'Submit',
POST_TIME: 'Post on',
LAST_EDITED_TIME: 'Last edited',
@@ -52,8 +53,8 @@ export default {
ANNOUNCEMENT: 'Announcement',
START_READING: 'Start Reading',
MINUTE: 'min',
- WORD_COUNT: 'W.C.'
-
+ WORD_COUNT: 'Words',
+ READ_TIME: 'Read Time'
},
PAGINATION: {
PREV: 'Prev',
@@ -66,5 +67,10 @@ export default {
POST: {
BACK: 'Back',
TOP: 'Top'
+ },
+ MAILCHIMP: {
+ SUBSCRIBE: 'Subscribe',
+ MSG: 'Get the latest news and articles to your inbox every month.',
+ EMAIL: 'Email'
}
}
diff --git a/lib/lang/fr-FR.js b/lib/lang/fr-FR.js
index 30089a70..2bc40057 100644
--- a/lib/lang/fr-FR.js
+++ b/lib/lang/fr-FR.js
@@ -1,5 +1,5 @@
export default {
- LOCALE: 'fr-FR',
+ LOCALE: 'français',
NAV: {
INDEX: 'Accueil',
RSS: 'RSS',
diff --git a/lib/lang/ja-JP.js b/lib/lang/ja-JP.js
index ab0e81f8..c27eef0b 100644
--- a/lib/lang/ja-JP.js
+++ b/lib/lang/ja-JP.js
@@ -1,5 +1,5 @@
export default {
- LOCALE: 'ja-JP',
+ LOCALE: '日本語',
NAV: {
INDEX: 'ホーム',
RSS: '購読',
diff --git a/lib/lang/tr-TR.js b/lib/lang/tr-TR.js
index 227b3fba..5be7a520 100644
--- a/lib/lang/tr-TR.js
+++ b/lib/lang/tr-TR.js
@@ -1,5 +1,5 @@
export default {
- LOCALE: 'tr-TR',
+ LOCALE: 'Türkçe',
NAV: {
INDEX: 'Blog',
RSS: 'RSS',
diff --git a/lib/lang/zh-CN.js b/lib/lang/zh-CN.js
index 91a4600a..2f4af10f 100644
--- a/lib/lang/zh-CN.js
+++ b/lib/lang/zh-CN.js
@@ -1,5 +1,5 @@
export default {
- LOCALE: 'zh-CN',
+ LOCALE: '中文(简体)',
MENU: {
WALK_AROUND: '随便逛逛',
CATEGORY: '博客分类',
@@ -40,6 +40,7 @@ export default {
VIEWS: '次查看',
COPYRIGHT_NOTICE: '本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。',
RESULT_OF_SEARCH: '篇搜索到的结果',
+ NO_RESULTS_FOUND: '没有找到文章',
ARTICLE_DETAIL: '文章详情',
PASSWORD_ERROR: '密码错误!',
ARTICLE_LOCK_TIPS: '文章已上锁,请输入访问密码',
@@ -54,7 +55,8 @@ export default {
ANNOUNCEMENT: '公告',
START_READING: '开始阅读',
MINUTE: '分钟',
- WORD_COUNT: '字数'
+ WORD_COUNT: '字数',
+ READ_TIME: '阅读时长'
},
PAGINATION: {
PREV: '上页',
@@ -67,5 +69,10 @@ export default {
POST: {
BACK: '返回上页',
TOP: '回到顶部'
+ },
+ MAILCHIMP: {
+ SUBSCRIBE: '邮件订阅',
+ MSG: '订阅以获取每月更新的新闻和文章,直接发送至您的邮箱。',
+ EMAIL: '邮箱'
}
}
diff --git a/lib/lang/zh-HK.js b/lib/lang/zh-HK.js
index 6a05df55..f434fe6e 100644
--- a/lib/lang/zh-HK.js
+++ b/lib/lang/zh-HK.js
@@ -1,4 +1,5 @@
export default {
+ LOCALE: '中文(繁体香港)',
NAV: {
INDEX: '網誌',
RSS: '訂閱',
diff --git a/lib/lang/zh-TW.js b/lib/lang/zh-TW.js
index 673fe109..de746c08 100644
--- a/lib/lang/zh-TW.js
+++ b/lib/lang/zh-TW.js
@@ -1,5 +1,5 @@
export default {
- LOCALE: 'zh-TW',
+ LOCALE: '中文(繁体台湾)',
NAV: {
INDEX: '部落格',
RSS: '訂閱',
diff --git a/lib/notion/getNotion.js b/lib/notion/getNotion.js
index 3da84ee8..aad65379 100644
--- a/lib/notion/getNotion.js
+++ b/lib/notion/getNotion.js
@@ -16,7 +16,6 @@ export async function getNotion(pageId) {
}
const postInfo = blockMap?.block?.[idToUuid(pageId)].value
-
return {
id: pageId,
type: postInfo,
@@ -26,7 +25,7 @@ export async function getNotion(pageId) {
status: 'Published',
createdTime: formatDate(new Date(postInfo.created_time).toString(), BLOG.LANG),
lastEditedDay: formatDate(new Date(postInfo?.last_edited_time).toString(), BLOG.LANG),
- fullWidth: false,
+ fullWidth: postInfo?.fullWidth,
page_cover: getPageCover(postInfo),
date: { start_date: formatDate(new Date(postInfo?.last_edited_time).toString(), BLOG.LANG) },
blockMap
diff --git a/lib/notion/getNotionConfig.js b/lib/notion/getNotionConfig.js
new file mode 100644
index 00000000..984b6eb7
--- /dev/null
+++ b/lib/notion/getNotionConfig.js
@@ -0,0 +1,140 @@
+/**
+ * 从Notion中读取站点配置;
+ * 在Notion模板中创建一个类型为CONFIG的页面,再添加一个数据库表格,即可用于填写配置
+ * Notion数据库配置优先级最高,将覆盖vercel环境变量以及blog.config.js中的配置
+ * --注意--
+ * 数据库请从模板复制 https://www.notion.so/tanghh/287869a92e3d4d598cf366bd6994755e
+ *
+ */
+import { getDateValue, getTextContent } from 'notion-utils'
+import { getPostBlocks } from './getPostBlocks'
+import getAllPageIds from './getAllPageIds'
+
+/**
+ * 从Notion中读取Config配置表
+ * @param {*} allPages
+ * @returns
+ */
+export async function getConfigMapFromConfigPage(allPages) {
+ // 默认返回配置文件
+ const notionConfig = {}
+
+ if (!allPages || !Array.isArray(allPages) || allPages.length === 0) {
+ console.warn('[Notion配置] 忽略的配置')
+ return null
+ }
+ const configPage = allPages?.find(post => {
+ return post && post?.type && (post?.type === 'CONFIG' || post?.type === 'config' || post?.type === 'Config')
+ })
+
+ if (!configPage) {
+ console.warn('[Notion配置] 未找到配置页面')
+ return null
+ }
+ const configPageId = configPage.id
+ // console.log('[Notion配置]请求配置数据 ', configPage.id)
+ let pageRecordMap = await getPostBlocks(configPageId, 'config-table')
+ // console.log('配置中心Page', configPageId, pageRecordMap)
+ let content = pageRecordMap.block[configPageId].value.content
+ for (const table of ['Config-Table', 'CONFIG-TABLE']) {
+ if (content) break
+ pageRecordMap = await getPostBlocks(configPageId, table)
+ content = pageRecordMap.block[configPageId].value.content
+ }
+
+ if (!content) {
+ console.warn('[Notion配置] 未找到配置表格', pageRecordMap.block[configPageId], pageRecordMap.block[configPageId].value)
+ return null
+ }
+
+ // 找到配置文件中的database
+ // for (const contentId of content) {
+ // console.log('内容', contentId, configPageRecordMap.block[contentId].value.type === 'collection_view')
+ // }
+ const configTableId = content?.find(contentId => {
+ return pageRecordMap.block[contentId].value.type === 'collection_view'
+ })
+
+ // eslint-disable-next-line no-constant-condition, no-self-compare
+ if (!configTableId) {
+ console.warn('[Notion配置]未找到配置表格数据', pageRecordMap.block[configPageId], pageRecordMap.block[configPageId].value)
+ return null
+ }
+
+ // 页面查找
+ const databaseRecordMap = pageRecordMap.block[configTableId]
+ const block = pageRecordMap.block || {}
+ const rawMetadata = databaseRecordMap.value
+ // Check Type Page-Database和Inline-Database
+ if (
+ rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view'
+ ) {
+ console.error(`pageId "${configTableId}" is not a database`)
+ return null
+ }
+ // console.log('表格', databaseRecordMap, block, rawMetadata)
+ const collectionId = rawMetadata?.collection_id
+ const collection = pageRecordMap.collection[collectionId].value
+ const collectionQuery = pageRecordMap.collection_query
+ const collectionView = pageRecordMap.collection_view
+ const schema = collection?.schema
+ const viewIds = rawMetadata?.view_ids
+ const pageIds = getAllPageIds(collectionQuery, collectionId, collectionView, viewIds)
+ if (pageIds?.length === 0) {
+ console.error('[Notion配置]获取到的文章列表为空,请检查notion模板', collectionQuery, collection, collectionView, viewIds, databaseRecordMap)
+ }
+ // 遍历用户的表格
+ for (let i = 0; i < pageIds.length; i++) {
+ const id = pageIds[i]
+ const value = block[id]?.value
+ if (!value) {
+ continue
+ }
+ const rawProperties = Object.entries(block?.[id]?.value?.properties || [])
+ const excludeProperties = ['date', 'select', 'multi_select', 'person']
+ const properties = {}
+ for (let i = 0; i < rawProperties.length; i++) {
+ const [key, val] = rawProperties[i]
+ properties.id = id
+ if (schema[key]?.type && !excludeProperties.includes(schema[key].type)) {
+ properties[schema[key].name] = getTextContent(val)
+ } else {
+ switch (schema[key]?.type) {
+ case 'date': {
+ const dateProperty = getDateValue(val)
+ delete dateProperty.type
+ properties[schema[key].name] = dateProperty
+ break
+ }
+ case 'select':
+ case 'multi_select': {
+ const selects = getTextContent(val)
+ if (selects[0]?.length) {
+ properties[schema[key].name] = selects.split(',')
+ }
+ break
+ }
+ default:
+ break
+ }
+ }
+ }
+
+ if (properties) {
+ // 将表格中的字段映射成 英文
+ const config = {
+ enable: (properties['启用'] || properties.Enable) === 'Yes',
+ key: properties['配置名'] || properties.Name,
+ value: properties['配置值'] || properties.Value
+ }
+
+ // 只导入生效的配置
+ if (config.enable) {
+ // console.log('[Notion配置]', config.key, config.value)
+ notionConfig[config.key] = config.value
+ }
+ }
+ }
+
+ return notionConfig
+}
diff --git a/lib/notion/getNotionData.js b/lib/notion/getNotionData.js
index f885f22b..5b77e716 100755
--- a/lib/notion/getNotionData.js
+++ b/lib/notion/getNotionData.js
@@ -8,6 +8,7 @@ import getAllPageIds from './getAllPageIds'
import { getAllTags } from './getAllTags'
import getPageProperties from './getPageProperties'
import { mapImgUrl, compressImage } from './mapImage'
+import { getConfigMapFromConfigPage } from './getNotionConfig'
/**
* 获取博客数据
@@ -272,6 +273,7 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) {
// 文章计数
let postCount = 0
+
// 查找所有的Post和Page
const allPages = collectionData.filter(post => {
if (post?.type === 'Post' && post.status === 'Published') {
@@ -282,6 +284,9 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) {
(post?.status === 'Invisible' || post?.status === 'Published')
})
+ // 站点配置优先读取配置表格,否则读取blog.config.js 文件
+ const NOTION_CONFIG = await getConfigMapFromConfigPage(collectionData) || {}
+
// Sort by date
if (BLOG.POSTS_SORT_BY === 'date') {
allPages.sort((a, b) => {
@@ -300,6 +305,7 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) {
const allNavPages = getNavPages({ allPages })
return {
+ NOTION_CONFIG,
notice,
siteInfo,
allPages,
diff --git a/lib/notion/getPageProperties.js b/lib/notion/getPageProperties.js
index ddf47f3e..aca92b25 100644
--- a/lib/notion/getPageProperties.js
+++ b/lib/notion/getPageProperties.js
@@ -6,6 +6,15 @@ import formatDate from '../formatDate'
import md5 from 'js-md5'
import { mapImgUrl } from './mapImage'
+/**
+ * 获取页面元素成员属性
+ * @param {*} id
+ * @param {*} block
+ * @param {*} schema
+ * @param {*} authToken
+ * @param {*} tagOptions
+ * @returns
+ */
export default async function getPageProperties(id, block, schema, authToken, tagOptions) {
const rawProperties = Object.entries(block?.[id]?.value?.properties || [])
const excludeProperties = ['date', 'select', 'multi_select', 'person']
@@ -92,11 +101,11 @@ export default async function getPageProperties(id, block, schema, authToken, ta
delete properties.content
// 处理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)
- } else if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_page) {
+ } else if (properties.type === 'Page') {
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.name = properties.title ?? ''
@@ -108,6 +117,7 @@ export default async function getPageProperties(id, block, schema, authToken, ta
properties.slug += '.html'
}
}
+ // 密码字段md5
properties.password = properties.password ? md5(properties.slug + properties.password) : ''
return properties
}
diff --git a/lib/notion/getPostBlocks.js b/lib/notion/getPostBlocks.js
index 809dfe7a..167f0b0e 100644
--- a/lib/notion/getPostBlocks.js
+++ b/lib/notion/getPostBlocks.js
@@ -62,8 +62,10 @@ export async function getPageWithRetry(id, from, retryAttempts = 3) {
}
/**
- * 获取到的blockMap删除不需要的字段
- * 并且对于页面内容进行特殊处理,比如文件url格式化
+ * 获取到的页面BLOCK特殊处理
+ * 1.删除冗余字段
+ * 2.比如文件、视频、音频、url格式化
+ * 3.代码块等元素兼容
* @param {*} id 页面ID
* @param {*} pageBlock 页面元素
* @param {*} slice 截取数量
@@ -102,7 +104,10 @@ function filterPostBlocks(id, pageBlock, slice) {
}
// 如果是文件,或嵌入式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 newUrl = `https://notion.so/signed/${encodeURIComponent(oldUrl)}?table=block&id=${b?.value?.id}`
b.value.properties.source[0][0] = newUrl
diff --git a/lib/notion/mapImage.js b/lib/notion/mapImage.js
index b6da8d87..dbb785e1 100644
--- a/lib/notion/mapImage.js
+++ b/lib/notion/mapImage.js
@@ -1,5 +1,81 @@
import BLOG from '@/blog.config'
+/**
+ * 图片映射
+ * 1. 如果是 /xx.xx 相对路径格式,则转化为 完整notion域名图片
+ * 2. 如果是 bookmark类型的block 图片封面无需处理
+ * @param {*} img
+ * @param {*} value
+ * @returns
+ */
+const mapImgUrl = (img, block, type = 'block', from) => {
+ if (!img) {
+ return null
+ }
+ let ret = null
+ // 相对目录,则视为notion的自带图片
+ if (img.startsWith('/')) {
+ ret = BLOG.NOTION_HOST + img
+ } else {
+ ret = img
+ }
+
+ // Notion 图床转换为永久地址
+ 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'
+ if (isNotionSignImg && isImgBlock) {
+ ret = BLOG.NOTION_HOST + '/image/' + encodeURIComponent(ret) + '?table=' + type + '&id=' + block.id
+ }
+
+ if (!isEmoji(ret) && ret.indexOf('notion.so/images/page-cover') < 0) {
+ if (BLOG.RANDOM_IMAGE_URL) {
+ // 只有配置了随机图片接口,才会替换图片
+ const texts = BLOG.RANDOM_IMAGE_REPLACE_TEXT
+ let isReplace = false;
+ if (texts) {
+ const textArr = texts.split(',')
+ // 判断是否包含替换的文本
+ textArr.forEach(text => {
+ if (ret.indexOf(text) > -1) {
+ isReplace = true
+ }
+ })
+ } else {
+ isReplace = true
+ }
+ if (isReplace) {
+ ret = BLOG.RANDOM_IMAGE_URL
+ }
+ }
+
+ // 图片url优化,确保每一篇文章的图片url唯一
+ if (ret && ret.length > 4) {
+ // 图片接口拼接唯一识别参数,防止请求的图片被缓,而导致随机结果相同
+ const separator = ret.includes('?') ? '&' : '?'
+ ret = `${ret.trim()}${separator}t=${block.id}`
+ }
+ }
+
+ // 文章封面压缩
+ if (from === 'pageCoverThumbnail' || block.type === 'image') {
+ // 统一压缩图片
+ const width = block?.format?.block_width
+ ret = compressImage(ret, width)
+ }
+
+ return ret
+}
+
+/**
+ * 是否是emoji图标
+ * @param {*} str
+ * @returns
+ */
+function isEmoji(str) {
+ const emojiRegex = /[\u{1F300}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1F018}-\u{1F270}\u{238C}\u{2B06}\u{2B07}\u{2B05}\u{27A1}\u{2194}-\u{2199}\u{2194}\u{21A9}\u{21AA}\u{2934}\u{2935}\u{25AA}\u{25AB}\u{25FE}\u{25FD}\u{25FB}\u{25FC}\u{25B6}\u{25C0}\u{1F200}-\u{1F251}]/u;
+ return emojiRegex.test(str);
+}
+
/**
* 压缩图片
* 1. Notion图床可以通过指定url-query参数来压缩裁剪图片 例如 ?xx=xx&width=400
@@ -11,7 +87,7 @@ const compressImage = (image, width = 800, quality = 50, fmt = 'webp') => {
return null
}
if (image.indexOf(BLOG.NOTION_HOST) === 0 && image.indexOf('amazonaws.com') > 0) {
- return `${image}&width=${width}`
+ return `${image}&width=${width}&cache=v2`
}
// 压缩unsplash图片
if (image.indexOf('https://images.unsplash.com/') === 0) {
@@ -40,51 +116,4 @@ const compressImage = (image, width = 800, quality = 50, fmt = 'webp') => {
return image
}
-/**
- * 图片映射
- * 1. 如果是 /xx.xx 相对路径格式,则转化为 完整notion域名图片
- * 2. 如果是 bookmark类型的block 图片封面无需处理
- * @param {*} img
- * @param {*} value
- * @returns
- */
-const mapImgUrl = (img, block, type = 'block', from) => {
- if (!img) {
- return null
- }
- let ret = null
- // 相对目录,则视为notion的自带图片
- if (img.startsWith('/')) {
- ret = BLOG.NOTION_HOST + img
- } else {
- ret = img
- }
-
- // Notion 图床转换为永久地址
- const isNotionImg = ret.indexOf('secure.notion-static.com') > 0 || ret.indexOf('prod-files-secure') > 0
- const isImgBlock = BLOG.IMG_URL_TYPE === 'Notion' || type !== 'block'
- if (isNotionImg && isImgBlock) {
- ret = BLOG.NOTION_HOST + '/image/' + encodeURIComponent(ret) + '?table=' + type + '&id=' + block.id
- }
-
- if (!isEmoji(ret) && ret.indexOf('notion.so/images/page-cover') < 0) {
- // 随机图片接口优化 防止因url一致而随机结果相同
- const separator = ret.includes('?') ? '&' : '?'
- // 拼接唯一识别参数,防止请求的图片被缓存
- ret = `${ret.trim()}${separator}t=${block.id}`
- }
-
- // 文章封面
- if (from === 'pageCoverThumbnail') {
- ret = compressImage(ret)
- }
-
- return ret
-}
-
-function isEmoji(str) {
- const emojiRegex = /[\u{1F300}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1F018}-\u{1F270}]/u;
- return emojiRegex.test(str);
-}
-
export { mapImgUrl, compressImage }
diff --git a/lib/utils.js b/lib/utils.js
index 75278521..3f0d858e 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -7,6 +7,20 @@ import { memo } from 'react'
*/
export const isBrowser = typeof window !== 'undefined'
+/**
+ * google机器人
+ * @returns
+ */
+export const isSearchEngineBot = () => {
+ if (typeof navigator === 'undefined') {
+ return false
+ }
+ // 获取用户代理字符串
+ const userAgent = navigator.userAgent;
+ // 使用正则表达式检测是否包含搜索引擎爬虫关键字
+ return /Googlebot|bingbot|Baidu/.test(userAgent)
+}
+
/**
* 组件持久化
*/
@@ -75,7 +89,6 @@ export function getQueryVariable(key) {
}
return (false)
}
-
/**
* 获取 URL 中指定参数的值
* @param {string} url
@@ -83,8 +96,10 @@ export function getQueryVariable(key) {
* @returns {string|null}
*/
export function getQueryParam(url, param) {
- const searchParams = new URLSearchParams(url.split('?')[1])
- return searchParams.get(param)
+ // 移除哈希部分
+ const urlWithoutHash = url.split('#')[0];
+ const searchParams = new URLSearchParams(urlWithoutHash.split('?')[1]);
+ return searchParams.get(param);
}
/**
@@ -202,3 +217,46 @@ export const isMobile = () => {
return isMobile
}
+
+/**
+ * 扫描页面上的所有文本节点,将url格式的文本转为可点击链接
+ * @param {*} node
+ */
+export const scanAndConvertToLinks = (node) => {
+ if (node.nodeType === Node.TEXT_NODE) {
+ const text = node.textContent;
+ const urlRegex = /https?:\/\/[^\s]+/g;
+ let lastIndex = 0;
+ let match;
+
+ const newNode = document.createElement('span');
+
+ while ((match = urlRegex.exec(text)) !== null) {
+ const beforeText = text.substring(lastIndex, match.index);
+ const url = match[0];
+
+ if (beforeText) {
+ newNode.appendChild(document.createTextNode(beforeText));
+ }
+
+ const link = document.createElement('a');
+ link.href = url;
+ link.target = '_blank'
+ link.textContent = url;
+
+ newNode.appendChild(link);
+
+ lastIndex = urlRegex.lastIndex;
+ }
+
+ if (lastIndex < text.length) {
+ newNode.appendChild(document.createTextNode(text.substring(lastIndex)));
+ }
+
+ node.parentNode.replaceChild(newNode, node);
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
+ for (const childNode of node.childNodes) {
+ scanAndConvertToLinks(childNode);
+ }
+ }
+}
diff --git a/next-sitemap.config.js b/next-sitemap.config.js
index a094e809..29e1ca67 100644
--- a/next-sitemap.config.js
+++ b/next-sitemap.config.js
@@ -1,5 +1,8 @@
const BLOG = require('./blog.config')
+/**
+ * 通常没啥用,sitemap交给 /pages/sitemap.xml.js 动态生成
+ */
module.exports = {
siteUrl: BLOG.LINK,
changefreq: 'daily',
diff --git a/next.config.js b/next.config.js
index 0a8bd36d..6266fb48 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,10 +1,11 @@
-const withBundleAnalyzer = require('@next/bundle-analyzer')({
- enabled: process.env.ANALYZE === 'true'
-})
-
const { THEME } = require('./blog.config')
const fs = require('fs')
const path = require('path')
+const BLOG = require('./blog.config')
+
+const withBundleAnalyzer = require('@next/bundle-analyzer')({
+ enabled: BLOG.BUNDLE_ANALYZER
+})
/**
* 扫描指定目录下的文件夹名,用于获取当前有几个主题
@@ -91,6 +92,7 @@ module.exports = withBundleAnalyzer({
// })
// }
// 动态主题:添加 resolve.alias 配置,将动态路径映射到实际路径
+ console.log('加载默认主题', path.resolve(__dirname, 'themes', THEME))
config.resolve.alias['@theme-components'] = path.resolve(__dirname, 'themes', THEME)
return config
},
diff --git a/package.json b/package.json
index 6164cbb4..776d6932 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "notion-next",
- "version": "4.0.18",
+ "version": "4.2.0",
"homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT",
"repository": {
diff --git a/pages/404.js b/pages/404.js
index c770385f..ae8eea37 100644
--- a/pages/404.js
+++ b/pages/404.js
@@ -1,7 +1,7 @@
import { getGlobalData } from '@/lib/notion/getNotionData'
-import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
/**
* 404
@@ -9,13 +9,12 @@ import { getLayoutByTheme } from '@/themes/theme'
* @returns
*/
const NoFound = props => {
- const { siteInfo } = useGlobal()
- const meta = { title: `${props?.siteInfo?.title} | 页面找不到啦`, image: siteInfo?.pageCover }
+ const meta = { title: `${siteConfig('TITLE')} | 页面找不到啦`, image: siteConfig('HOME_BANNER_IMAGE') }
props = { ...props, meta }
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
return
}
diff --git a/pages/[prefix]/index.js b/pages/[prefix]/index.js
index 6fa5d47b..dfc90fcc 100644
--- a/pages/[prefix]/index.js
+++ b/pages/[prefix]/index.js
@@ -10,6 +10,7 @@ import { getLayoutByTheme } from '@/themes/theme'
import md5 from 'js-md5'
import { isBrowser } from '@/lib/utils'
import { uploadDataToAlgolia } from '@/lib/algolia'
+import { siteConfig } from '@/lib/config'
/**
* 根据notion的slug访问页面
@@ -66,7 +67,7 @@ const Slug = props => {
}, [post])
const meta = {
- title: post ? `${post?.title} | ${siteInfo?.title}` : `${props?.siteInfo?.title || BLOG.TITLE} | loading`,
+ title: post ? `${post?.title} | ${siteConfig('TITLE')}` : `${siteConfig('TITLE')} | loading`,
description: post?.summary,
type: post?.type,
slug: post?.slug,
@@ -76,7 +77,7 @@ const Slug = props => {
}
props = { ...props, lock, meta, setLock, validPassword }
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
return
}
diff --git a/pages/_app.js b/pages/_app.js
index a16513b9..82b4ca72 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -6,52 +6,44 @@ import '@/styles/utility-patterns.css'
// core styles shared by all of react-notion-x (required)
import 'react-notion-x/src/styles.css'
import '@/styles/notion.css' // 重写部分样式
+import 'aos/dist/aos.css' // You can also use for styles
import { GlobalContextProvider } from '@/lib/global'
+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 AOS from 'aos'
-import 'aos/dist/aos.css' // You can also use for styles
-import dynamic from 'next/dynamic'
-import { isBrowser, loadExternalResource } from '@/lib/utils'
-import BLOG from '@/blog.config'
-
-// 各种扩展插件 动画等
-const ExternalPlugins = dynamic(() => import('@/components/ExternalPlugins'))
+// 各种扩展插件 这个要阻塞引入
+import ExternalPlugins from '@/components/ExternalPlugins'
+import { THEME } from '@/blog.config'
const MyApp = ({ Component, pageProps }) => {
- // 自定义样式css和js引入
- if (isBrowser) {
- // 初始化AOS动画
- AOS.init()
- // 静态导入本地自定义样式
- loadExternalResource('/css/custom.css', 'css')
- loadExternalResource('/js/custom.js', 'js')
+ // 一些可能出现 bug 的样式,可以统一放入该钩子进行调整
+ useAdjustStyle();
- // 自动添加图片阴影
- if (BLOG.IMG_SHADOW) {
- loadExternalResource('/css/img-shadow.css', 'css')
- }
+ const route = useRouter()
+ const queryParam = useMemo(() => {
+ return getQueryParam(route.asPath, 'theme') || THEME
+ }, [route])
- // 导入外部自定义脚本
- if (BLOG.CUSTOM_EXTERNAL_JS && BLOG.CUSTOM_EXTERNAL_JS.length > 0) {
- for (const url of BLOG.CUSTOM_EXTERNAL_JS) {
- loadExternalResource(url, 'js')
- }
- }
-
- // 导入外部自定义样式
- if (BLOG.CUSTOM_EXTERNAL_CSS && BLOG.CUSTOM_EXTERNAL_CSS.length > 0) {
- for (const url of BLOG.CUSTOM_EXTERNAL_CSS) {
- loadExternalResource(url, 'css')
- }
- }
- }
+ const GLayout = useCallback(
+ props => {
+ // 根据页面路径加载不同Layout文件
+ const Layout = getGlobalLayoutByTheme(queryParam)
+ return
+ },
+ [queryParam]
+ )
return (
-
-
-
-
+
+
+
+
+
+
)
}
diff --git a/pages/_document.js b/pages/_document.js
index 9c7bd4c8..96c999c3 100644
--- a/pages/_document.js
+++ b/pages/_document.js
@@ -1,7 +1,6 @@
// eslint-disable-next-line @next/next/no-document-import-in-page
import Document, { Html, Head, Main, NextScript } from 'next/document'
import BLOG from '@/blog.config'
-import CommonScript from '@/components/CommonScript'
class MyDocument extends Document {
static async getInitialProps(ctx) {
@@ -14,7 +13,6 @@ class MyDocument extends Document {
-
{/* 预加载字体 */}
{BLOG.FONT_AWESOME && <>
@@ -22,6 +20,7 @@ class MyDocument extends Document {
>}
{BLOG.FONT_URL?.map((fontUrl, index) => {
+ console.log(fontUrl)
if (fontUrl.endsWith('.css')) {
return
} else {
diff --git a/pages/api/cache.js b/pages/api/cache.js
new file mode 100644
index 00000000..97738651
--- /dev/null
+++ b/pages/api/cache.js
@@ -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 })
+ }
+}
diff --git a/pages/archive/index.js b/pages/archive/index.js
index 106ad7d2..a0a57f28 100644
--- a/pages/archive/index.js
+++ b/pages/archive/index.js
@@ -6,13 +6,14 @@ import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
import { isBrowser } from '@/lib/utils'
import { formatDateFmt } from '@/lib/formatDate'
+import { siteConfig } from '@/lib/config'
const ArchiveIndex = props => {
const { siteInfo } = props
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
useEffect(() => {
if (isBrowser) {
@@ -29,8 +30,8 @@ const ArchiveIndex = props => {
}, [])
const meta = {
- title: `${locale.NAV.ARCHIVE} | ${siteInfo?.title}`,
- description: siteInfo?.description,
+ title: `${locale.NAV.ARCHIVE} | ${siteConfig('TITLE')}`,
+ description: siteConfig('DESCRIPTION'),
image: siteInfo?.pageCover,
slug: 'archive',
type: 'website'
diff --git a/pages/category/[category]/index.js b/pages/category/[category]/index.js
index 03cd4fbd..a623cc6b 100644
--- a/pages/category/[category]/index.js
+++ b/pages/category/[category]/index.js
@@ -4,6 +4,7 @@ import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
/**
* 分类页
@@ -15,13 +16,13 @@ export default function Category(props) {
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${props.category} | ${locale.COMMON.CATEGORY} | ${
- siteInfo?.title || ''
+ siteConfig('TITLE') || ''
}`,
- description: siteInfo?.description,
+ description: siteConfig('DESCRIPTION'),
slug: 'category/' + props.category,
image: siteInfo?.pageCover,
type: 'website'
diff --git a/pages/category/[category]/page/[page].js b/pages/category/[category]/page/[page].js
index 9174e117..ae5124b1 100644
--- a/pages/category/[category]/page/[page].js
+++ b/pages/category/[category]/page/[page].js
@@ -4,6 +4,7 @@ import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
/**
* 分类页
@@ -15,13 +16,13 @@ export default function Category(props) {
const { siteInfo } = props
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
title: `${props.category} | ${locale.COMMON.CATEGORY} | ${
- siteInfo?.title || ''
+ siteConfig('TITLE') || ''
}`,
- description: siteInfo?.description,
+ description: siteConfig('DESCRIPTION'),
slug: 'category/' + props.category,
image: siteInfo?.pageCover,
type: 'website'
diff --git a/pages/category/index.js b/pages/category/index.js
index b4872000..85121910 100644
--- a/pages/category/index.js
+++ b/pages/category/index.js
@@ -4,6 +4,7 @@ import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
/**
* 分类首页
@@ -15,11 +16,11 @@ export default function Category(props) {
const { siteInfo } = props
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
- title: `${locale.COMMON.CATEGORY} | ${siteInfo?.title}`,
- description: siteInfo?.description,
+ title: `${locale.COMMON.CATEGORY} | ${siteConfig('TITLE')}`,
+ description: siteConfig('DESCRIPTION'),
image: siteInfo?.pageCover,
slug: 'category',
type: 'website'
diff --git a/pages/index.js b/pages/index.js
index 328ece1e..18f7ec47 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -3,8 +3,9 @@ import { getPostBlocks } from '@/lib/notion'
import { getGlobalData } from '@/lib/notion/getNotionData'
import { generateRss } from '@/lib/rss'
import { generateRobotsTxt } from '@/lib/robots.txt'
-import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
+import { useRouter } from 'next/router'
/**
* 首页布局
@@ -13,8 +14,16 @@ import { getLayoutByTheme } from '@/themes/theme'
*/
const Index = props => {
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
- return
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
+
+ const meta = {
+ title: `${siteConfig('TITLE')} | ${siteConfig('DESCRIPTION')}`,
+ description: siteConfig('DESCRIPTION'),
+ image: siteConfig('HOME_BANNER_IMAGE'),
+ slug: '',
+ type: 'website'
+ }
+ return
}
/**
@@ -25,16 +34,8 @@ export async function getStaticProps() {
const from = 'index'
const props = await getGlobalData({ from })
- const { siteInfo } = props
props.posts = props.allPages?.filter(page => page.type === 'Post' && page.status === 'Published')
- const meta = {
- title: `${siteInfo?.title} | ${siteInfo?.description}`,
- description: siteInfo?.description,
- image: siteInfo?.pageCover,
- slug: '',
- type: 'website'
- }
// 处理分页
if (BLOG.POST_LIST_STYLE === 'scroll') {
// 滚动列表默认给前端返回所有数据
@@ -65,10 +66,7 @@ export async function getStaticProps() {
delete props.allPages
return {
- props: {
- meta,
- ...props
- },
+ props,
revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND)
}
}
diff --git a/pages/page/[page].js b/pages/page/[page].js
index 02b847a8..c34aef1d 100644
--- a/pages/page/[page].js
+++ b/pages/page/[page].js
@@ -3,6 +3,7 @@ import { getPostBlocks } from '@/lib/notion'
import { getGlobalData } from '@/lib/notion/getNotionData'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
/**
* 文章列表分页
@@ -13,11 +14,11 @@ const Page = props => {
const { siteInfo } = props
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
- title: `${props?.page} | Page | ${siteInfo?.title}`,
- description: siteInfo?.description,
+ title: `${props?.page} | Page | ${siteConfig('TITLE')}`,
+ description: siteConfig('DESCRIPTION'),
image: siteInfo?.pageCover,
slug: 'page/' + props.page,
type: 'website'
diff --git a/pages/search/[keyword]/index.js b/pages/search/[keyword]/index.js
index 042bbf85..89bec089 100644
--- a/pages/search/[keyword]/index.js
+++ b/pages/search/[keyword]/index.js
@@ -4,17 +4,18 @@ import { getDataFromCache } from '@/lib/cache/cache_manager'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
const Index = props => {
const { keyword, siteInfo } = props
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
- title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`,
- description: siteInfo?.title,
+ title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`,
+ description: siteConfig('TITLE'),
image: siteInfo?.pageCover,
slug: 'search/' + (keyword || ''),
type: 'website'
@@ -112,7 +113,7 @@ const isIterable = obj =>
async function filterByMemCache(allPosts, keyword) {
const filterPosts = []
if (keyword) {
- keyword = keyword.trim()
+ keyword = keyword.trim().toLowerCase()
}
for (const post of allPosts) {
const cacheKey = 'page_block_' + post.id
@@ -130,7 +131,7 @@ async function filterByMemCache(allPosts, keyword) {
if (!c) {
continue
}
- const index = c.toLowerCase().indexOf(keyword.toLowerCase())
+ const index = c.toLowerCase().indexOf(keyword)
if (index > -1) {
hit = true
hitCount += 1
diff --git a/pages/search/[keyword]/page/[page].js b/pages/search/[keyword]/page/[page].js
index 20240f19..9de43cc1 100644
--- a/pages/search/[keyword]/page/[page].js
+++ b/pages/search/[keyword]/page/[page].js
@@ -4,17 +4,18 @@ import { getDataFromCache } from '@/lib/cache/cache_manager'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
const Index = props => {
const { keyword, siteInfo } = props
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
- title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`,
- description: siteInfo?.title,
+ title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`,
+ description: siteConfig('TITLE'),
image: siteInfo?.pageCover,
slug: 'search/' + (keyword || ''),
type: 'website'
diff --git a/pages/search/index.js b/pages/search/index.js
index 22061cbd..fa45d2d5 100644
--- a/pages/search/index.js
+++ b/pages/search/index.js
@@ -3,6 +3,7 @@ import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import BLOG from '@/blog.config'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
/**
* 搜索路由
@@ -14,7 +15,7 @@ const Search = props => {
const { locale } = useGlobal()
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const router = useRouter()
const keyword = getSearchKey(router)
@@ -34,8 +35,8 @@ const Search = props => {
}
const meta = {
- title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`,
- description: siteInfo?.description,
+ title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`,
+ description: siteConfig('DESCRIPTION'),
image: siteInfo?.pageCover,
slug: 'search',
type: 'website'
diff --git a/pages/tag/[tag]/index.js b/pages/tag/[tag]/index.js
index bd9f7529..1d6c94d2 100644
--- a/pages/tag/[tag]/index.js
+++ b/pages/tag/[tag]/index.js
@@ -3,6 +3,7 @@ import { getGlobalData } from '@/lib/notion/getNotionData'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
/**
* 标签下的文章列表
@@ -14,11 +15,11 @@ const Tag = props => {
const { tag, siteInfo } = props
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
- title: `${tag} | ${locale.COMMON.TAGS} | ${siteInfo?.title}`,
- description: siteInfo?.description,
+ title: `${tag} | ${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`,
+ description: siteConfig('DESCRIPTION'),
image: siteInfo?.pageCover,
slug: 'tag/' + tag,
type: 'website'
diff --git a/pages/tag/[tag]/page/[page].js b/pages/tag/[tag]/page/[page].js
index 2eaef518..ab28fdbc 100644
--- a/pages/tag/[tag]/page/[page].js
+++ b/pages/tag/[tag]/page/[page].js
@@ -3,17 +3,18 @@ import { getGlobalData } from '@/lib/notion/getNotionData'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
const Tag = props => {
const { locale } = useGlobal()
const { tag, siteInfo } = props
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
- title: `${tag} | ${locale.COMMON.TAGS} | ${siteInfo?.title}`,
- description: siteInfo?.description,
+ title: `${tag} | ${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`,
+ description: siteConfig('DESCRIPTION'),
image: siteInfo?.pageCover,
slug: 'tag/' + tag,
type: 'website'
diff --git a/pages/tag/index.js b/pages/tag/index.js
index 54dab9a4..bb618414 100644
--- a/pages/tag/index.js
+++ b/pages/tag/index.js
@@ -3,6 +3,7 @@ import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { getLayoutByTheme } from '@/themes/theme'
+import { siteConfig } from '@/lib/config'
/**
* 标签首页
@@ -14,11 +15,11 @@ const TagIndex = props => {
const { siteInfo } = props
// 根据页面路径加载不同Layout文件
- const Layout = getLayoutByTheme(useRouter())
+ const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() })
const meta = {
- title: `${locale.COMMON.TAGS} | ${siteInfo?.title}`,
- description: siteInfo?.description,
+ title: `${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`,
+ description: siteConfig('DESCRIPTION'),
image: siteInfo?.pageCover,
slug: 'tag',
type: 'website'
diff --git a/styles/globals.css b/styles/globals.css
index 67644891..65057e3e 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -160,6 +160,11 @@ nav {
@apply text-blue-700
}
+/* twikoo 内置的 element-ui 加载样式 */
+.el-loading-spinner {
+ @apply flex justify-center items-center;
+}
+
/* Webmention style */
.webmention-block {
background: rgba(0, 116, 222, .2);
diff --git a/styles/notion.css b/styles/notion.css
index c7d939be..cd461c33 100644
--- a/styles/notion.css
+++ b/styles/notion.css
@@ -724,7 +724,6 @@ svg.notion-page-icon {
.notion-text {
width: 100%;
white-space: pre-wrap;
- line-height: 1.5rem !important;
word-break: break-word;
padding: 3px 2px !important;
margin: 1px 0 !important;
@@ -1131,9 +1130,10 @@ code[class*='language-'] {
transition: background 20ms ease-in 0s;
cursor: pointer;
width: 100%;
+ opacity: 0.9;
padding: 6px 2px;
- font-size: 14px;
+ font-size: 15px;
line-height: 1.2;
display: flex;
align-items: center;
diff --git a/themes/example/components/BlogListGroupByDate.js b/themes/example/components/BlogListGroupByDate.js
index ac5f9cbd..29598f0b 100644
--- a/themes/example/components/BlogListGroupByDate.js
+++ b/themes/example/components/BlogListGroupByDate.js
@@ -1,4 +1,4 @@
-import BLOG from '@/blog.config'
+import { siteConfig } from '@/lib/config'
import Link from 'next/link'
/**
@@ -24,7 +24,7 @@ export default function BlogListGroupByDate({ archiveTitle, archivePosts }) {
{post?.publishDay}
{' '}
-
+
{post.title}
diff --git a/themes/example/components/BlogListPage.js b/themes/example/components/BlogListPage.js
index 429ee30e..5ae2cf5c 100644
--- a/themes/example/components/BlogListPage.js
+++ b/themes/example/components/BlogListPage.js
@@ -1,5 +1,5 @@
-import BLOG from '@/blog.config'
+import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import Link from 'next/link'
@@ -10,14 +10,14 @@ export const BlogListPage = props => {
const { page = 1, posts, postCount } = props
const { locale } = useGlobal()
const router = useRouter()
- const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
+ const totalPage = Math.ceil(postCount / parseInt(siteConfig('POSTS_PER_PAGE')))
const currentPage = +page
const showPrev = currentPage > 1
const showNext = page < totalPage
const pagePrefix = router.asPath.split('?')[0].replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
- const showPageCover = CONFIG.POST_LIST_COVER
+ const showPageCover = siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG)
return (
diff --git a/themes/example/components/BlogListScroll.js b/themes/example/components/BlogListScroll.js
index ad6df10e..e876f312 100644
--- a/themes/example/components/BlogListScroll.js
+++ b/themes/example/components/BlogListScroll.js
@@ -1,6 +1,6 @@
-import BLOG from '@/blog.config'
+import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
-import React, { useEffect } from 'react'
+import { useCallback, useEffect, useRef, useState } from 'react'
import throttle from 'lodash.throttle'
import BlogPostCard from './BlogPostCard'
import CONFIG from '../config'
@@ -9,33 +9,33 @@ export const BlogListScroll = props => {
const { posts } = props
const { locale } = useGlobal()
- const [page, updatePage] = React.useState(1)
+ const [page, updatePage] = useState(1)
let hasMore = false
const postsToShow = posts
- ? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page)
+ ? Object.assign(posts).slice(0, parseInt(siteConfig('POSTS_PER_PAGE')) * page)
: []
if (posts) {
const totalCount = posts.length
- hasMore = page * BLOG.POSTS_PER_PAGE < totalCount
+ hasMore = page * parseInt(siteConfig('POSTS_PER_PAGE')) < totalCount
}
const handleGetMore = () => {
if (!hasMore) return
updatePage(page + 1)
}
- const targetRef = React.useRef(null)
+ const targetRef = useRef(null)
// 监听滚动自动分页加载
- const scrollTrigger = React.useCallback(throttle(() => {
+ const scrollTrigger = useCallback(throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500))
- const showPageCover = CONFIG.POST_LIST_COVER
+ const showPageCover = siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG)
useEffect(() => {
window.addEventListener('scroll', scrollTrigger)
diff --git a/themes/example/components/BlogPostCard.js b/themes/example/components/BlogPostCard.js
index faf1753d..cd330a83 100644
--- a/themes/example/components/BlogPostCard.js
+++ b/themes/example/components/BlogPostCard.js
@@ -1,11 +1,11 @@
-import BLOG from '@/blog.config'
+import { siteConfig } from '@/lib/config'
import CONFIG from '../config'
import Link from 'next/link'
import TwikooCommentCount from '@/components/TwikooCommentCount'
import LazyImage from '@/components/LazyImage'
const BlogPostCard = ({ post }) => {
- const showPageCover = CONFIG.POST_LIST_COVER && post?.pageCoverThumbnail
+ const showPageCover = siteConfig('EXAMPLE_POST_LIST_COVER', null, CONFIG) && post?.pageCoverThumbnail
return
@@ -18,10 +18,10 @@ const BlogPostCard = ({ post }) => {
@@ -40,9 +40,9 @@ const BlogPostCard = ({ post }) => {
{/* 图片封面 */}
{showPageCover && (
-
-
-
+
+
+
)}
diff --git a/themes/example/components/ExampleRecentComments.js b/themes/example/components/ExampleRecentComments.js
index b1555c1d..e5097740 100644
--- a/themes/example/components/ExampleRecentComments.js
+++ b/themes/example/components/ExampleRecentComments.js
@@ -1,5 +1,5 @@
-import React from 'react'
-import BLOG from '@/blog.config'
+import { useEffect, useState } from 'react'
+import { siteConfig } from '@/lib/config'
import Link from 'next/link'
import { RecentComments } from '@waline/client'
@@ -9,11 +9,11 @@ import { RecentComments } from '@waline/client'
* @returns
*/
const ExampleRecentComments = (props) => {
- const [comments, updateComments] = React.useState([])
- const [onLoading, changeLoading] = React.useState(true)
- React.useEffect(() => {
+ const [comments, updateComments] = useState([])
+ const [onLoading, changeLoading] = useState(true)
+ useEffect(() => {
RecentComments({
- serverURL: BLOG.COMMENT_WALINE_SERVER_URL,
+ serverURL: siteConfig('COMMENT_WALINE_SERVER_URL'),
count: 5
}).then(({ comments }) => {
changeLoading(false)
diff --git a/themes/example/components/Footer.js b/themes/example/components/Footer.js
index 09af18fe..77c8bad6 100644
--- a/themes/example/components/Footer.js
+++ b/themes/example/components/Footer.js
@@ -1,28 +1,24 @@
-import BLOG from '@/blog.config'
import DarkModeButton from '@/components/DarkModeButton'
+import { siteConfig } from '@/lib/config'
export const Footer = (props) => {
const d = new Date()
const currentYear = d.getFullYear()
- const copyrightDate = (function() {
- if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
- return BLOG.SINCE + '-' + currentYear
- }
- return currentYear
- })()
+ const since = siteConfig('SINCE')
+ const copyrightDate = parseInt(since) < currentYear ? since + '-' + currentYear : currentYear
return