'use client' import BLOG from '@/blog.config' import { useGlobal } from './global' import { deepClone, isUrl } 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 = {}) => { if (!key) { return null } const getValue = (value, fallback) => (hasVal(value) ? value : fallback) const hasVal = value => value !== undefined && value !== null // 特殊配置处理;以下配置只在服务端生效;而Global的NOTION_CONFIG仅限前端组件使用,因此需要从extendConfig中读取 switch (key) { case 'NEXT_REVALIDATE_SECOND': case 'POST_RECOMMEND_COUNT': case 'IMAGE_COMPRESS_WIDTH': case 'PSEUDO_STATIC': case 'POSTS_SORT_BY': case 'POSTS_PER_PAGE': case 'POST_PREVIEW_LINES': case 'POST_URL_PREFIX': case 'POST_LIST_STYLE': case 'POST_LIST_PREVIEW': case 'POST_URL_PREFIX_MAPPING_CATEGORY': case 'POST_SCHEDULE_PUBLISH': case 'IS_TAG_COLOR_DISTINGUISHED': case 'TAG_SORT_BY_COUNT': case 'THEME': case 'LINK': case 'AI_SUMMARY_API': case 'AI_SUMMARY_KEY': case 'AI_SUMMARY_CACHE_TIME': case 'AI_SUMMARY_WORD_LIMIT': case 'UUID_REDIRECT': // LINK比较特殊, if (key === 'LINK') { if (!extendConfig || Object.keys(extendConfig).length === 0) { break } } return convertVal( getValue(extendConfig[key], getValue(defaultVal, BLOG[key])) ) default: } let global = {} try { // const isClient = typeof window !== 'undefined' // eslint-disable-next-line react-hooks/rules-of-hooks global = useGlobal() // eslint-disable-next-line react-hooks/rules-of-hooks // global = useGlobal() } catch (error) { // 本地调试用 // console.warn('SiteConfig警告', key, error) } // 配置最优先读取NOTION中的表格配置 let val = null let siteInfo = null if (global) { siteInfo = global.siteInfo val = global.NOTION_CONFIG?.[key] || global.THEME_CONFIG?.[key] } 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 } } // 其次 有传入的extendConfig,则尝试读取 if (!hasVal(val) && extendConfig) { val = extendConfig[key] } // 其次 NOTION没有找到配置,则会读取blog.config.js文件 if (!hasVal(val)) { val = BLOG[key] } if (!hasVal(val)) { return defaultVal } return convertVal(val) } export const cleanJsonString = val => { // 使用正则表达式去掉不必要的空格、换行符和制表符 return val.replace(/\s+/g, ' ').trim() } /** * 从环境变量和NotionConfig读取的配置都是string类型; * 这里识别出配置的字符值若为否 数字、布尔、[]数组,{}对象,若是则转成对应类型 * 使用JSON和eval两个函数 * @param {*} val * @returns */ export const convertVal = val => { // 如果传入参数本身就是 obj、数组、boolean,就无需处理 if (typeof val !== 'string' || !val) { return val } // 检测是否数字并避免数值溢出 if (/^\d+$/.test(val)) { const parsedNum = Number(val) // 如果数值大于 JavaScript 最大安全整数,则作为字符串返回 if (parsedNum > Number.MAX_SAFE_INTEGER) { return val + '' } return parsedNum } // 检测是否为布尔值 if (val === 'true' || val === 'false') { return JSON.parse(val) } // 检测是否为 URL if (isUrl(val)) { return val } // 配置值前可能有污染的空格 // 如果字符串中没有 '[' 或 '{',则直接返回 if (!val.trim().startsWith('{') && !val.trim().startsWith('[')) { return val } // 转换 [] , {} 这类字符串为对象 try { val = cleanJsonString(val) const parsedJson = JSON.parse(val) // 检查解析后的结果是否为对象 if (parsedJson !== null) { return parsedJson } } catch (error) { // 解析失败,返回原始字符串 return val } 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 }