From ef832f72c73fb1eac67580528824be048dd53236 Mon Sep 17 00:00:00 2001 From: tangly1024 Date: Sun, 8 Feb 2026 11:51:37 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E4=BF=AE=E5=A4=8DNotion=5FConfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ExternalPlugins.js | 2 +- components/NotionPage.js | 4 +- components/PWA.js | 2 +- lib/db/{getSiteData.js => SiteDataApi.js} | 256 +++++++++++------- lib/{ => db}/notion/CustomNotionApi.ts | 0 lib/{ => db}/notion/RateLimiter.ts | 0 lib/{ => db}/notion/convertInnerUrl.js | 2 +- lib/{ => db}/notion/getAllCategories.js | 2 +- lib/{ => db}/notion/getAllPageIds.js | 0 lib/{ => db}/notion/getAllTags.js | 4 +- lib/{ => db}/notion/getMetadata.js | 0 lib/{ => db}/notion/getNotionAPI.js | 0 lib/{ => db}/notion/getNotionConfig.js | 31 ++- lib/{ => db}/notion/getNotionPost.js | 20 +- lib/{ => db}/notion/getPageContentText.js | 0 lib/{ => db}/notion/getPageProperties.js | 10 +- lib/{ => db}/notion/getPageTableOfContents.js | 0 lib/{ => db}/notion/getPostBlocks.js | 188 +++++++++++-- lib/{ => db}/notion/mapImage.js | 2 +- lib/db/notion/normalizeUtil.js | 105 +++++++ lib/global.js | 2 +- lib/plugins/aiSummary.js | 32 +++ lib/plugins/algolia.js | 2 +- lib/site/adapters/notion/notion.adapter.ts | 10 + lib/site/adapters/notion/notion.fetcher.ts | 11 + lib/site/adapters/notion/notion.normalizer.ts | 31 +++ lib/site/processors/empty.processor.ts | 24 ++ lib/site/processors/page.processor.ts | 21 ++ lib/site/processors/schedule.processor.ts | 10 + lib/site/site.api.ts | 19 ++ lib/site/site.service.ts | 19 ++ lib/site/site.types.ts | 97 +++++++ lib/utils/clean.util.ts | 19 ++ lib/{ => utils}/font.js | 2 +- lib/{ => utils}/lang.js | 16 +- lib/{ => utils}/password.js | 2 +- lib/utils/post.js | 19 +- lib/{ => utils}/redirect.js | 0 lib/{ => utils}/robots.txt.js | 0 lib/{ => utils}/rss.js | 2 +- lib/{ => utils}/sitemap.js | 2 +- lib/{ => utils}/sitemap.xml.js | 2 +- lib/utils/time.util.ts | 4 + pages/404.js | 4 +- pages/[prefix]/[slug]/[...suffix].js | 45 +-- pages/[prefix]/[slug]/index.js | 43 +-- pages/[prefix]/index.js | 50 +--- pages/archive/index.js | 4 +- pages/auth/index.js | 4 +- pages/auth/result.js | 4 +- pages/category/[category]/index.js | 6 +- pages/category/[category]/page/[page].js | 6 +- pages/category/index.js | 4 +- pages/dashboard/[[...index]].js | 27 +- pages/index.js | 12 +- pages/page/[page].js | 6 +- pages/search/[keyword]/index.js | 6 +- pages/search/[keyword]/page/[page].js | 4 +- pages/search/index.js | 4 +- pages/sign-in/[[...index]].js | 4 +- pages/sign-up/[[...index]].js | 4 +- pages/sitemap.xml.js | 4 +- pages/tag/[tag]/index.js | 6 +- pages/tag/[tag]/page/[page].js | 6 +- pages/tag/index.js | 4 +- tailwind.config.js | 2 +- themes/plog/components/BlogPost.js | 2 +- themes/plog/components/Modal.js | 2 +- 68 files changed, 880 insertions(+), 357 deletions(-) rename lib/db/{getSiteData.js => SiteDataApi.js} (81%) mode change 100755 => 100644 rename lib/{ => db}/notion/CustomNotionApi.ts (100%) rename lib/{ => db}/notion/RateLimiter.ts (100%) rename lib/{ => db}/notion/convertInnerUrl.js (99%) rename lib/{ => db}/notion/getAllCategories.js (96%) rename lib/{ => db}/notion/getAllPageIds.js (100%) rename lib/{ => db}/notion/getAllTags.js (96%) rename lib/{ => db}/notion/getMetadata.js (100%) rename lib/{ => db}/notion/getNotionAPI.js (100%) rename lib/{ => db}/notion/getNotionConfig.js (84%) rename lib/{ => db}/notion/getNotionPost.js (72%) rename lib/{ => db}/notion/getPageContentText.js (100%) rename lib/{ => db}/notion/getPageProperties.js (97%) rename lib/{ => db}/notion/getPageTableOfContents.js (100%) rename lib/{ => db}/notion/getPostBlocks.js (64%) rename lib/{ => db}/notion/mapImage.js (99%) create mode 100644 lib/db/notion/normalizeUtil.js create mode 100644 lib/site/adapters/notion/notion.adapter.ts create mode 100644 lib/site/adapters/notion/notion.fetcher.ts create mode 100644 lib/site/adapters/notion/notion.normalizer.ts create mode 100644 lib/site/processors/empty.processor.ts create mode 100644 lib/site/processors/page.processor.ts create mode 100644 lib/site/processors/schedule.processor.ts create mode 100644 lib/site/site.api.ts create mode 100644 lib/site/site.service.ts create mode 100644 lib/site/site.types.ts create mode 100644 lib/utils/clean.util.ts rename lib/{ => utils}/font.js (95%) rename lib/{ => utils}/lang.js (91%) rename lib/{ => utils}/password.js (96%) rename lib/{ => utils}/redirect.js (100%) rename lib/{ => utils}/robots.txt.js (100%) rename lib/{ => utils}/rss.js (98%) rename lib/{ => utils}/sitemap.js (99%) rename lib/{ => utils}/sitemap.xml.js (98%) create mode 100644 lib/utils/time.util.ts diff --git a/components/ExternalPlugins.js b/components/ExternalPlugins.js index 59ef9f40e75..0e0ef307ba6 100644 --- a/components/ExternalPlugins.js +++ b/components/ExternalPlugins.js @@ -1,5 +1,5 @@ import { siteConfig } from '@/lib/config' -import { convertInnerUrl } from '@/lib/notion/convertInnerUrl' +import { convertInnerUrl } from '@/lib/db/notion/convertInnerUrl' import { isBrowser, loadExternalResource } from '@/lib/utils' import dynamic from 'next/dynamic' import { useRouter } from 'next/router' diff --git a/components/NotionPage.js b/components/NotionPage.js index 0a011968ac4..e0375465e8d 100644 --- a/components/NotionPage.js +++ b/components/NotionPage.js @@ -1,5 +1,5 @@ import { siteConfig } from '@/lib/config' -import { compressImage, mapImgUrl } from '@/lib/notion/mapImage' +import { compressImage, mapImgUrl } from '@/lib/db/notion/mapImage' import { isBrowser, loadExternalResource } from '@/lib/utils' import mediumZoom from '@fisch0920/medium-zoom' import 'katex/dist/katex.min.css' @@ -147,7 +147,7 @@ function cleanBlocksWithWarn(blockMap) { const cleanedBlocks = {}; const removedBlockIds = []; - for (const [id, block] of Object.entries(blockMap.block || {})) { + for (const [id, block] of Object.entries(blockMap?.block || {})) { if (!block?.value?.id) { removedBlockIds.push(id); continue; diff --git a/components/PWA.js b/components/PWA.js index 2a2921002b1..11492179fe5 100644 --- a/components/PWA.js +++ b/components/PWA.js @@ -1,4 +1,4 @@ -import { compressImage } from '@/lib/notion/mapImage' +import { compressImage } from '@/lib/db/notion/mapImage' import { isBrowser } from '../lib/utils' /** diff --git a/lib/db/getSiteData.js b/lib/db/SiteDataApi.js old mode 100755 new mode 100644 similarity index 81% rename from lib/db/getSiteData.js rename to lib/db/SiteDataApi.js index 9cb68813ddd..1c7507ae4ce --- a/lib/db/getSiteData.js +++ b/lib/db/SiteDataApi.js @@ -1,32 +1,44 @@ import BLOG from '@/blog.config' -import { getOrSetDataWithCache } from '@/lib/cache/cache_manager' -import { getAllCategories } from '@/lib/notion/getAllCategories' -import getAllPageIds from '@/lib/notion/getAllPageIds' -import { getAllTags } from '@/lib/notion/getAllTags' -import { getConfigMapFromConfigPage } from '@/lib/notion/getNotionConfig' +import { getOrSetDataWithCache } from '../cache/cache_manager' +import { getAllCategories } from '@/lib/db/notion/getAllCategories' +import getAllPageIds from '@/lib/db/notion/getAllPageIds' +import { getAllTags } from '@/lib/db/notion/getAllTags' +import { getConfigMapFromConfigPage } from '@/lib/db/notion/getNotionConfig' import getPageProperties, { adjustPageProperties -} from '@/lib/notion/getPageProperties' -import { fetchInBatches, getPage } from '@/lib/notion/getPostBlocks' -import { compressImage, mapImgUrl } from '@/lib/notion/mapImage' +} from '@/lib/db/notion/getPageProperties' +import { fetchInBatches, fetchNotionPageBlocks } from '@/lib/db/notion/getPostBlocks' +import { compressImage, mapImgUrl } from '@/lib/db/notion/mapImage' import { deepClone } from '@/lib/utils' import { idToUuid } from 'notion-utils' import { siteConfig } from '../config' import { extractLangId, extractLangPrefix, getShortId } from '../utils/pageId' +import { normalizeNotionMetadata, normalizeCollection, normalizeSchema, normalizePageBlock } from './notion/normalizeUtil' -export { getAllTags } from '../notion/getAllTags' -export { getPost } from '../notion/getNotionPost' -export { getPage as getPostBlocks } from '../notion/getPostBlocks' +import { fetchPageFromNotion } from './notion/getNotionPost' +import { processPostData } from '../utils/post' + +export { getAllTags } from './notion/getAllTags' +export { fetchPageFromNotion as getPost } from './notion/getNotionPost' +export { fetchNotionPageBlocks as getPostBlocks } from './notion/getPostBlocks' /** - * 获取博客数据; 基于Notion实现 + * 获取全站数据; 基于Notion实现 + * TODO 计划这个文件改成类似Restful的接口形式; + * 按照站点数据封装,从而进一步提升兼容性和可维护性 + * @see /lib/site/site.api.ts + * /site-info + * /posts?tag=xxx&category=yyy&page=1&limit=10 + * /posts/:id + * /categories + * /tags * @param {*} pageId * @param {*} from * @param {*} locale 语言 zh|en|jp 等等 * @returns * */ -export async function getGlobalData({ +export async function fetchGlobalAllData({ pageId = BLOG.NOTION_PAGE_ID, from, locale @@ -55,6 +67,8 @@ export async function getGlobalData({ } catch (error) { console.error('异常', error) } + + // 返回给客户端前的清理操作 return handleDataBeforeReturn(deepClone(data)) } @@ -66,15 +80,18 @@ export async function getGlobalData({ */ export async function getSiteDataByPageId({ pageId, from }) { // 获取NOTION原始数据,此接支持mem缓存。 - return await getOrSetDataWithCache( + const originalPageRecordMap = await getOrSetDataWithCache( `site_data_${pageId}`, async (pageId, from) => { - const pageRecordMap = await getPage(pageId, from) - return convertNotionToSiteData(pageId, from, deepClone(pageRecordMap)) + const pageRecordMap = await fetchNotionPageBlocks(pageId, from) + return pageRecordMap }, pageId, from ) + + // 获取的数据格式与站点不同 + return convertNotionToSiteData(pageId, from, deepClone(originalPageRecordMap)) } /** @@ -85,7 +102,7 @@ async function getNotice(post) { return null } - post.blockMap = await getPage(post.id, 'data-notice') + post.blockMap = await fetchNotionPageBlocks(post.id, 'data-notice') return post } @@ -137,128 +154,177 @@ const EmptyData = pageId => { } /** - * 可能由于Notion接口升级导致数据格式变化,这里进行统一处理 - * @param {*} block - * @param {*} pageId - * @returns + * 在服务端解析 post 相关 props + * ✅ 兼容 prefix / slug / suffix 任意组合 + * ⚠️ 只能在 getStaticProps / getServerSideProps 使用 */ -function normalizeNotionMetadata(block, pageId) { - const rawValue = block?.[pageId]?.value - if (!rawValue) return null - return rawValue.type ? rawValue : rawValue.value ?? null -} +export async function resolvePostProps({ + prefix, + slug, + suffix, + locale, + from, +}) { + /** + * 1️⃣ 统一路径片段 + */ + const segments = [] + if (prefix) segments.push(prefix) + if (slug) segments.push(slug) + if (Array.isArray(suffix)) segments.push(...suffix) + + const fullSlug = segments.join('/') + const source = from || `slug-props-${fullSlug}` + + /** + * 2️⃣ 构造所有可能命中的 slug + */ + const lastSegment = segments[segments.length - 1] + + const slugCandidates = new Set([ + fullSlug, + lastSegment, + slug, + prefix, + ]) + + // 去掉 falsy + for (const s of Array.from(slugCandidates)) { + if (!s) slugCandidates.delete(s) + } -/** - * 兼容新老 Notion collection 结构 , 新版会用space_id 包裹一层 - * 统一返回真正的 collection.value(包含 schema 的那一层) - */ -function normalizeCollection(collection) { - let current = collection + /** + * 3️⃣ 拉取全局数据 + */ + const props = await fetchGlobalAllData({ from: source, locale }) - // 最多剥 3 层,防止死循环 - for (let i = 0; i < 3; i++) { - if (!current) break + let post = null - // 已经是最终形态:有 schema - if (current.schema) { - return current - } + /** + * 4️⃣ 列表内匹配 + */ + post = props?.allPages?.find(p => { + if (!p || p?.type?.includes('Menu')) return false - // 常见包装:{ value: {...}, role } - if (current.value) { - current = current.value - continue - } + return ( + slugCandidates.has(p.slug) || + slugCandidates.has(p.slug?.split('/').pop()) || + slugCandidates.has(p.id) || + slugCandidates.has(idToUuid(p.id)) || + slugCandidates.has(idToUuid(fullSlug)) + ) + }) - break + /** + * 5️⃣ 非列表文章兜底(pageId) + */ + if (!post && typeof lastSegment === 'string' && lastSegment.length >= 32) { + try { + post = await fetchPageFromNotion(lastSegment) + } catch (e) { + console.warn('[resolvePostProps] fetchPageFromNotion failed:', lastSegment, e) + } } - return current ?? {} -} + /** + * 6️⃣ blockMap 兜底 + */ + if (post && post.id && !post.blockMap) { + try { -/** - * 兼容 Notion schema - * 保留原始字段 id 作为 key - */ -/** - * 兼容 Notion schema - * 保留原始字段 id 作为 key - */ -function normalizeSchema(schema = {}) { - const result = {} - - Object.entries(schema).forEach(([key, value]) => { - result[key] = { - ...value, - name: value?.name || '', - type: value?.type || '' + const rawBlockMap = await fetchNotionPageBlocks(post.id, source) + + post.blockMap = unwrapNotionBlockMap(rawBlockMap) + + } catch (e) { + console.warn('[resolvePostProps] fetchNotionPageBlocks failed:', post.id, e) } - }) + } + + /** + * 7️⃣ 后处理 + */ + if (post) { + props.post = post + try { + await processPostData(props, source) + } catch (e) { + console.warn('[resolvePostProps] processPostData failed', e) + } + } else { + props.post = null + } - return result + return props } /** - * ✅ 终极版:兼容 Notion 新老 Page Block 结构 - * 最终一定返回:{ id, type, properties } + * 修复新版 Notion 返回的包裹结构 + * 只解 spaceId 包裹,不拍平 value + * 兼容 block / collection */ -function normalizePageBlock(blockItem) { - if (!blockItem) return null +export function unwrapNotionBlockMap(blockMap) { + if (!blockMap) return blockMap - let current = blockItem + const unwrap = (source) => { + if (!source) return source - for (let i = 0; i < 5; i++) { - if (!current) return null - - // 针对 collection 兼容 - if ( - (current.type === 'collection_view_page' || current.type === 'collection_view') && - current.collection_id - ) { - return current - } + const fixed = {} - if (current.type && current.properties) { - return current - } + for (const [id, item] of Object.entries(source)) { + if (!item) continue - if (current.value) { - current = current.value - continue + // 新结构:{ spaceId, value } + if (item.spaceId && item.value) { + fixed[id] = { + value: item.value + } + } else { + fixed[id] = item + } } - break + return fixed } - return null + return { + ...blockMap, + block: unwrap(blockMap.block), + collection: unwrap(blockMap.collection), + } } + + /** * 将Notion数据转站点数据 * 这里统一对数据格式化 * @returns {Promise} */ -async function convertNotionToSiteData(pageId, from, pageRecordMap) { +async function convertNotionToSiteData(SITE_DATABASE_PAGE_ID, from, pageRecordMap) { if (!pageRecordMap) { - console.error('can`t get Notion Data ; Which id is: ', pageId) + console.error('can`t get Notion Data ; Which id is: ', SITE_DATABASE_PAGE_ID) return {} } - pageId = idToUuid(pageId) + SITE_DATABASE_PAGE_ID = idToUuid(SITE_DATABASE_PAGE_ID) let block = pageRecordMap.block || {} - const rawMetadata = normalizeNotionMetadata(block, pageId) + const rawMetadata = normalizeNotionMetadata(block, SITE_DATABASE_PAGE_ID) + // spaceId 提取备用 + const spaceId = rawMetadata?.space_id || null // Check Type Page-Database和Inline-Database if ( rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view' ) { - console.error(`pageId "${pageId}" is not a database`) - return EmptyData(pageId) + console.error(`pageId "${SITE_DATABASE_PAGE_ID}" is not a database`) + return EmptyData(SITE_DATABASE_PAGE_ID) } - const collectionId = rawMetadata?.collection_id + // 解析读取根数据库信息 + const collectionId = rawMetadata?.collection_id const rawCollection = pageRecordMap.collection?.[collectionId] || pageRecordMap.collection?.[idToUuid(collectionId)] || diff --git a/lib/notion/CustomNotionApi.ts b/lib/db/notion/CustomNotionApi.ts similarity index 100% rename from lib/notion/CustomNotionApi.ts rename to lib/db/notion/CustomNotionApi.ts diff --git a/lib/notion/RateLimiter.ts b/lib/db/notion/RateLimiter.ts similarity index 100% rename from lib/notion/RateLimiter.ts rename to lib/db/notion/RateLimiter.ts diff --git a/lib/notion/convertInnerUrl.js b/lib/db/notion/convertInnerUrl.js similarity index 99% rename from lib/notion/convertInnerUrl.js rename to lib/db/notion/convertInnerUrl.js index 213443cda30..ab6baf353c6 100644 --- a/lib/notion/convertInnerUrl.js +++ b/lib/db/notion/convertInnerUrl.js @@ -1,5 +1,5 @@ import { idToUuid } from 'notion-utils' -import { checkStrIsNotionId, getLastPartOfUrl, isBrowser } from '../utils' +import { checkStrIsNotionId, getLastPartOfUrl, isBrowser } from '../../utils' /** * 处理页面内连接跳转: diff --git a/lib/notion/getAllCategories.js b/lib/db/notion/getAllCategories.js similarity index 96% rename from lib/notion/getAllCategories.js rename to lib/db/notion/getAllCategories.js index c7e66ba2252..cce73d86ff0 100644 --- a/lib/notion/getAllCategories.js +++ b/lib/db/notion/getAllCategories.js @@ -1,4 +1,4 @@ -import { isIterable } from '../utils' +import { isIterable } from '../../utils' /** * 获取所有文章的标签 diff --git a/lib/notion/getAllPageIds.js b/lib/db/notion/getAllPageIds.js similarity index 100% rename from lib/notion/getAllPageIds.js rename to lib/db/notion/getAllPageIds.js diff --git a/lib/notion/getAllTags.js b/lib/db/notion/getAllTags.js similarity index 96% rename from lib/notion/getAllTags.js rename to lib/db/notion/getAllTags.js index db5ea55b757..f2625c066f4 100644 --- a/lib/notion/getAllTags.js +++ b/lib/db/notion/getAllTags.js @@ -1,5 +1,5 @@ -import { siteConfig } from '../config' -import { isIterable } from '../utils' +import { siteConfig } from '../../config' +import { isIterable } from '../../utils' /** * 获取所有文章的标签 diff --git a/lib/notion/getMetadata.js b/lib/db/notion/getMetadata.js similarity index 100% rename from lib/notion/getMetadata.js rename to lib/db/notion/getMetadata.js diff --git a/lib/notion/getNotionAPI.js b/lib/db/notion/getNotionAPI.js similarity index 100% rename from lib/notion/getNotionAPI.js rename to lib/db/notion/getNotionAPI.js diff --git a/lib/notion/getNotionConfig.js b/lib/db/notion/getNotionConfig.js similarity index 84% rename from lib/notion/getNotionConfig.js rename to lib/db/notion/getNotionConfig.js index 989950da718..3eea024dd06 100644 --- a/lib/notion/getNotionConfig.js +++ b/lib/db/notion/getNotionConfig.js @@ -7,10 +7,11 @@ * */ import { getDateValue, getTextContent } from 'notion-utils' -import { deepClone } from '../utils' +import { deepClone } from '../../utils' import getAllPageIds from './getAllPageIds' -import { getPage } from './getPostBlocks' +import { fetchNotionPageBlocks } from './getPostBlocks' import { encryptEmail } from '@/lib/plugins/mailEncrypt' +import { normalizeNotionMetadata, normalizeCollection, normalizeSchema, normalizePageBlock } from './normalizeUtil' /** * 从Notion中读取Config配置表 @@ -42,12 +43,12 @@ export async function getConfigMapFromConfigPage(allPages) { } const configPageId = configPage.id // console.log('[Notion配置]请求配置数据 ', configPage.id) - let pageRecordMap = await getPage(configPageId, 'config-table') + let pageRecordMap = await fetchNotionPageBlocks(configPageId, 'config-table') // console.log('配置中心Page', configPageId, pageRecordMap) - let content = pageRecordMap.block[configPageId]?.value?.content + let content = normalizePageBlock(pageRecordMap.block[configPageId].value)?.content for (const table of ['Config-Table', 'CONFIG-TABLE']) { if (content) break - pageRecordMap = await getPage(configPageId, table) + pageRecordMap = await fetchNotionPageBlocks(configPageId, table) content = pageRecordMap.block[configPageId]?.value?.content } @@ -62,7 +63,7 @@ export async function getConfigMapFromConfigPage(allPages) { // 找到PAGE文件中的database const configTableId = content?.find(contentId => { - return pageRecordMap.block[contentId].value.type === 'collection_view' + return normalizePageBlock(pageRecordMap.block[contentId].value)?.type === 'collection_view' }) // eslint-disable-next-line no-constant-condition, no-self-compare @@ -75,10 +76,9 @@ export async function getConfigMapFromConfigPage(allPages) { return null } - // 页面查找 - const databaseRecordMap = pageRecordMap.block[configTableId] + // 页内查找数据表格 const block = pageRecordMap.block || {} - const rawMetadata = databaseRecordMap.value + const rawMetadata = normalizePageBlock(pageRecordMap.block[configTableId]) // Check Type Page-Database和Inline-Database if ( rawMetadata?.type !== 'collection_view_page' && @@ -87,12 +87,11 @@ export async function getConfigMapFromConfigPage(allPages) { 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 collection = normalizeCollection(pageRecordMap.collection[collectionId].value) const collectionQuery = pageRecordMap.collection_query const collectionView = pageRecordMap.collection_view - const schema = collection?.schema + const schema = normalizeSchema(collection?.schema || {}) const viewIds = rawMetadata?.view_ids const pageIds = getAllPageIds( collectionQuery, @@ -117,7 +116,11 @@ export async function getConfigMapFromConfigPage(allPages) { if (!value) { continue } - const rawProperties = Object.entries(block?.[id]?.value?.properties || []) + const temp = normalizePageBlock(block?.[id]?.value) + if(!temp?.properties){ + continue + } + const rawProperties = Object.entries(temp?.properties || []) const excludeProperties = ['date', 'select', 'multi_select', 'person'] const properties = {} for (let i = 0; i < rawProperties.length; i++) { @@ -147,7 +150,7 @@ export async function getConfigMapFromConfigPage(allPages) { } } - if (properties) { + if (properties && typeof properties === 'object' && !Array.isArray(properties) && Object.keys(properties).length > 0) { // 将表格中的字段映射成 英文 const config = { enable: (properties['启用'] || properties.Enable) === 'Yes', diff --git a/lib/notion/getNotionPost.js b/lib/db/notion/getNotionPost.js similarity index 72% rename from lib/notion/getNotionPost.js rename to lib/db/notion/getNotionPost.js index 97ceea9a48d..f99b39ae6ff 100644 --- a/lib/notion/getNotionPost.js +++ b/lib/db/notion/getNotionPost.js @@ -1,17 +1,17 @@ import BLOG from '@/blog.config' import { idToUuid } from 'notion-utils' -import { defaultMapImageUrl } from 'react-notion-x' -import formatDate from '../utils/formatDate' -import { getPage } from './getPostBlocks' +import ReactNotionX from 'react-notion-x' +import formatDate from '../../utils/formatDate' +import { fetchNotionPageBlocks } from './getPostBlocks' import { checkStrIsNotionId, checkStrIsUuid } from '@/lib/utils' /** - * 根据页面ID获取内容 + * 根据页面ID获取文章 * @param {*} pageId * @returns */ -export async function getPost(pageId) { - const blockMap = await getPage(pageId, 'slug') +export async function fetchPageFromNotion(pageId) { + const blockMap = await fetchNotionPageBlocks(pageId, 'slug') if (!blockMap) { return null } @@ -56,7 +56,11 @@ function getPageCover(postInfo) { const pageCover = postInfo.format?.page_cover if (pageCover) { if (pageCover.startsWith('/')) return BLOG.NOTION_HOST + pageCover - if (pageCover.startsWith('http')) - return defaultMapImageUrl(pageCover, postInfo) + if (pageCover.startsWith('http')) { + console.log('ReactNotionX', ReactNotionX) + return pageCover + } + // return defaultMapImageUrl(pageCover, postInfo) + return null } } diff --git a/lib/notion/getPageContentText.js b/lib/db/notion/getPageContentText.js similarity index 100% rename from lib/notion/getPageContentText.js rename to lib/db/notion/getPageContentText.js diff --git a/lib/notion/getPageProperties.js b/lib/db/notion/getPageProperties.js similarity index 97% rename from lib/notion/getPageProperties.js rename to lib/db/notion/getPageProperties.js index 29bb547246d..e2876e3ed66 100644 --- a/lib/notion/getPageProperties.js +++ b/lib/db/notion/getPageProperties.js @@ -1,13 +1,13 @@ import BLOG from '@/blog.config' import { getDateValue, getTextContent } from 'notion-utils' -import formatDate from '../utils/formatDate' +import formatDate from '../../utils/formatDate' // import { createHash } from 'crypto' import md5 from 'js-md5' -import { siteConfig } from '../config' -import { convertUrlStartWithOneSlash, getLastSegmentFromUrl, isHttpLink, isMailOrTelLink } from '../utils' -import { extractLangPrefix } from '../utils/pageId' +import { siteConfig } from '../../config' +import { convertUrlStartWithOneSlash, getLastSegmentFromUrl, isHttpLink, isMailOrTelLink } from '../../utils' +import { extractLangPrefix } from '../../utils/pageId' import { mapImgUrl } from './mapImage' -import notionAPI from '@/lib/notion/getNotionAPI' +import notionAPI from '@/lib/db/notion/getNotionAPI' /** * 获取页面元素成员属性 diff --git a/lib/notion/getPageTableOfContents.js b/lib/db/notion/getPageTableOfContents.js similarity index 100% rename from lib/notion/getPageTableOfContents.js rename to lib/db/notion/getPageTableOfContents.js diff --git a/lib/notion/getPostBlocks.js b/lib/db/notion/getPostBlocks.js similarity index 64% rename from lib/notion/getPostBlocks.js rename to lib/db/notion/getPostBlocks.js index 1d920e92f0f..7615e7d7617 100644 --- a/lib/notion/getPostBlocks.js +++ b/lib/db/notion/getPostBlocks.js @@ -4,41 +4,38 @@ import { getOrSetDataWithCache, setDataToCache } from '@/lib/cache/cache_manager' -import { deepClone, delay } from '../utils' -import notionAPI from '@/lib/notion/getNotionAPI' +import { deepClone, delay } from '../../utils' +import notionAPI from '@/lib/db/notion/getNotionAPI' /** * 获取文章内容块 - * @param {*} id + * @param {string} id * @param {*} from * @param {*} slice - * @returns */ -export async function getPage(id, from = null, slice) { +export async function fetchNotionPageBlocks(id, from = null, slice = 0) { const cacheKey = `page_content_${id}` - return await getOrSetDataWithCache( - cacheKey, - async (id, slice) => { - let pageBlock = await getDataFromCache(cacheKey) - if (pageBlock) { - // console.debug('[API<<--缓存]', `from:${from}`, cacheKey) - return convertNotionBlocksToPost(id, pageBlock, slice) - } + // 1️⃣ 统一由缓存工具负责「读 / 写 / 兜底获取」 + const pageBlock = await getOrSetDataWithCache( + cacheKey, + async () => { // 抓取最新数据 - pageBlock = await getPageWithRetry(id, from) - - if (pageBlock) { - await setDataToCache(cacheKey, pageBlock) - return convertNotionBlocksToPost(id, pageBlock, slice) - } - return pageBlock - }, - id, - slice + return await getPageWithRetry(id, from) + } ) + + // 2️⃣ 防御式返回 + if (!pageBlock) { + console.warn('[getPage] empty pageBlock:', id) + return null + } + + // 3️⃣ 转换为 post + return pageBlock } + /** * 调用接口,失败会重试 * @param {*} id @@ -86,7 +83,7 @@ export async function getPageWithRetry(id, from, retryAttempts = 3) { * @returns */ function convertNotionBlocksToPost(id, blockMap, slice) { - const clonePageBlock = deepClone(blockMap) + const clonePageBlock = normalizeNotionBlockMap(deepClone(blockMap)) let count = 0 const blocksToProcess = Object.keys(clonePageBlock?.block || {}) @@ -109,7 +106,7 @@ function convertNotionBlocksToPost(id, blockMap, slice) { count++ - // === 【新增】强制修复非法 URL === + // === 【新增】强制修复非法 URL === sanitizeBlockUrls(b?.value) if (b?.value?.type === 'sync_block' && b?.value?.children) { @@ -154,12 +151,145 @@ function convertNotionBlocksToPost(id, blockMap, slice) { } // 去掉不用的字段 - if (id === BLOG.NOTION_PAGE_ID) { - return clonePageBlock - } + // if (id === BLOG.NOTION_PAGE_ID) { + // return clonePageBlock + // } return clonePageBlock } +/** + * 统一 Notion block / collection / collection_view 结构 + * - 兼容新老 Notion 数据 + * - 彻底展开 spaceId + * - 彻底解包 value.value.value... + * - 输出 react-notion-x 可直接消费的格式 + */ + +/* =========================== + * 主入口 + * =========================== */ + +export function normalizeNotionBlockMap(blockMap) { + if (!blockMap || typeof blockMap !== 'object') return blockMap + + const res = { + ...blockMap, + // block: blockMap.block, + collection: normalizeCollectionMap(blockMap.collection), + collection_view: normalizeCollectionMap(blockMap.collection_view) + } + + console.log('[Notion BlockMap 规范化]', res) + return res +} + +function normalizeBlockMap(blockMap) { + if (!blockMap || typeof blockMap !== 'object') return {} + + const result = {} + + for (const [key, raw] of Object.entries(blockMap)) { + // 新版 Notion:spaceId 层 + if (isSpaceLayer(raw)) { + for (const [blockId, blockRaw] of Object.entries(raw)) { + const normalized = normalizeSingleBlock(blockId, blockRaw) + if (normalized) { + result[blockId] = normalized + } + } + continue + } + + // 老版 Notion:直接是 block + const normalized = normalizeSingleBlock(key, raw) + if (normalized) { + result[key] = normalized + } + } + + return result +} + +function normalizeSingleBlock(blockId, raw) { + if (!raw || typeof raw !== 'object') { + console.warn('[normalizeBlockMap] invalid block', blockId, raw) + return null + } + + const value = unwrapValueDeep(raw) + + if (!value || !value.id) { + console.warn('[normalizeBlockMap] missing value.id', blockId, raw) + return null + } + + return { value } +} + +function unwrapValueDeep(raw) { + let cur = raw + + // 只要还有 value.value,就继续拆 + while ( + cur && + typeof cur === 'object' && + cur.value && + typeof cur.value === 'object' && + cur.value.value + ) { + cur = cur.value + } + + return cur.value ?? cur +} + +function isSpaceLayer(obj) { + if (!obj || typeof obj !== 'object') return false + + // spaceId 层的特征: + // value 不在第一层,而在第二层 + return Object.values(obj).some( + v => v && typeof v === 'object' && v.value + ) +} + +function normalizeCollectionMap(map) { + if (!map || typeof map !== 'object') return {} + + const flattened = flattenSpaceMap(map) + const result = {} + + for (const [id, raw] of Object.entries(flattened)) { + const value = unwrapValueDeep(raw) + if (!value) continue + + result[id] = { value } + } + + return result +} + +function flattenSpaceMap(map) { + const result = {} + + for (const value of Object.values(map)) { + // 老版:已经是 { id: { value } } + if (value?.value) { + return map + } + + // 新版:{ spaceId: { id: { value } } } + if (value && typeof value === 'object') { + for (const [id, raw] of Object.entries(value)) { + result[id] = raw + } + } + } + + return result +} + + /** * 根据[]ids,批量抓取blocks * 在获取数据库文章列表时,超过一定数量的block会被丢弃,因此根据pageId批量抓取block @@ -208,7 +338,7 @@ function sanitizeBlockUrls(blockValue) { if (url.startsWith('/')) { return url } - + // 修复 http:xxx → http://xxx if (url.startsWith('http:') && !url.startsWith('http://')) { url = 'http://' + url.slice(5) diff --git a/lib/notion/mapImage.js b/lib/db/notion/mapImage.js similarity index 99% rename from lib/notion/mapImage.js rename to lib/db/notion/mapImage.js index 72ab0d001c0..740d65a43cb 100644 --- a/lib/notion/mapImage.js +++ b/lib/db/notion/mapImage.js @@ -1,5 +1,5 @@ import BLOG from '@/blog.config' -import { siteConfig } from '../config' +import { siteConfig } from '../../config' /** * 图片映射 diff --git a/lib/db/notion/normalizeUtil.js b/lib/db/notion/normalizeUtil.js new file mode 100644 index 00000000000..d68f1e2c439 --- /dev/null +++ b/lib/db/notion/normalizeUtil.js @@ -0,0 +1,105 @@ + +/** + * 可能由于Notion接口升级导致数据格式变化,这里进行统一处理 + * @param {*} block + * @param {*} pageId + * @returns + */ +function normalizeNotionMetadata(block, pageId) { + const rawValue = block?.[pageId]?.value + if (!rawValue) return null + return rawValue.type ? rawValue : rawValue.value ?? null +} + +/** + * 兼容新老 Notion collection 结构 , 新版会用space_id 包裹一层 + * 统一返回真正的 collection.value(包含 schema 的那一层) + */ +function normalizeCollection(collection) { + let current = collection + + // 最多剥 3 层,防止死循环 + for (let i = 0; i < 3; i++) { + if (!current) break + + // 已经是最终形态:有 schema + if (current.schema) { + return current + } + + // 常见包装:{ value: {...}, role } + if (current.value) { + current = current.value + continue + } + + break + } + + return current ?? {} +} + +/** + * 兼容 Notion schema + * 保留原始字段 id 作为 key + */ +/** + * 兼容 Notion schema + * 保留原始字段 id 作为 key + */ +function normalizeSchema(schema = {}) { + const result = {} + + Object.entries(schema).forEach(([key, value]) => { + result[key] = { + ...value, + name: value?.name || '', + type: value?.type || '' + } + }) + + return result +} + + +/** + * ✅ 终极版:兼容 Notion 新老 Page Block 结构 + * 最终一定返回:{ id, type, properties } + */ +function normalizePageBlock(blockItem) { + if (!blockItem) return null + + let current = blockItem + + for (let i = 0; i < 5; i++) { + if (!current) return null + + // 针对 collection 兼容 + if ( + (current.type === 'collection_view_page' || current.type === 'collection_view') && + current.collection_id + ) { + return current + } + + if (current.type || current.properties) { + return current + } + + if (current.value) { + current = current.value + continue + } + + break + } + + return null +} + +module.exports = { + normalizeNotionMetadata, + normalizeCollection, + normalizeSchema, + normalizePageBlock +} \ No newline at end of file diff --git a/lib/global.js b/lib/global.js index 3298fa4dcba..a6c38f970a0 100644 --- a/lib/global.js +++ b/lib/global.js @@ -8,7 +8,7 @@ import { import { useUser } from '@clerk/nextjs' import { useRouter } from 'next/router' import { createContext, useContext, useEffect, useState } from 'react' -import { generateLocaleDict, initLocale, redirectUserLang } from './lang' +import { generateLocaleDict, initLocale, redirectUserLang } from './utils/lang' /** * 全局上下文 diff --git a/lib/plugins/aiSummary.js b/lib/plugins/aiSummary.js index 67021c644a5..e49a08d2944 100644 --- a/lib/plugins/aiSummary.js +++ b/lib/plugins/aiSummary.js @@ -30,3 +30,35 @@ export async function getAiSummary(aiSummaryAPI, aiSummaryKey, truncatedText) { return null } } + + +/** + * 获取文章摘要 + * @param props + * @param pageContentText + * @returns {Promise} + */ +export async function getPageAISummary(post, pageContentText) { + const aiSummaryAPI = siteConfig('AI_SUMMARY_API') + if (aiSummaryAPI) { + const cacheKey = `ai_summary_${post.id}` + let aiSummary = await getDataFromCache(cacheKey) + if (aiSummary) { + post.aiSummary = aiSummary + } else { + const aiSummaryKey = siteConfig('AI_SUMMARY_KEY') + const aiSummaryCacheTime = siteConfig('AI_SUMMARY_CACHE_TIME') + const wordLimit = siteConfig('AI_SUMMARY_WORD_LIMIT', '1000') + let content = '' + for (let heading of post.toc) { + content += heading.text + ' ' + } + content += pageContentText + const combinedText = post.title + ' ' + content + const truncatedText = combinedText.slice(0, wordLimit) + aiSummary = await getAiSummary(aiSummaryAPI, aiSummaryKey, truncatedText) + await setDataToCache(cacheKey, aiSummary, aiSummaryCacheTime) + post.aiSummary = aiSummary + } + } +} diff --git a/lib/plugins/algolia.js b/lib/plugins/algolia.js index 53df833365c..ebfe1b1899a 100644 --- a/lib/plugins/algolia.js +++ b/lib/plugins/algolia.js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import algoliasearch from 'algoliasearch' -import { getPageContentText } from '@/lib/notion/getPageContentText' +import { getPageContentText } from '@/lib/db/notion/getPageContentText' // 全局初始化 Algolia 客户端和索引 let algoliaClient diff --git a/lib/site/adapters/notion/notion.adapter.ts b/lib/site/adapters/notion/notion.adapter.ts new file mode 100644 index 00000000000..2d09b88cc1b --- /dev/null +++ b/lib/site/adapters/notion/notion.adapter.ts @@ -0,0 +1,10 @@ +import { fetchNotionRecordMap } from './notion.fetcher' +import { normalizeNotionSite } from './notion.normalizer' +import type { FetchSiteParams, SiteData } from '../../site.types' + +export async function fetchSiteFromNotion( + params: FetchSiteParams +): Promise { + const recordMap = await fetchNotionRecordMap(params.pageId, params.from) + return normalizeNotionSite(recordMap, params.pageId, params.from) +} diff --git a/lib/site/adapters/notion/notion.fetcher.ts b/lib/site/adapters/notion/notion.fetcher.ts new file mode 100644 index 00000000000..b03a996bf75 --- /dev/null +++ b/lib/site/adapters/notion/notion.fetcher.ts @@ -0,0 +1,11 @@ +import { getOrSetDataWithCache } from '@/lib/cache/cache_manager' +import { fetchNotionPageBlocks } from '@/lib/db/notion/getPostBlocks' + +export async function fetchNotionRecordMap(pageId: string, from?: string) { + return getOrSetDataWithCache( + `site_data_${pageId}`, + async () => fetchNotionPageBlocks(pageId, from, 0), + pageId, + from + ) +} diff --git a/lib/site/adapters/notion/notion.normalizer.ts b/lib/site/adapters/notion/notion.normalizer.ts new file mode 100644 index 00000000000..d3f9dc6fb37 --- /dev/null +++ b/lib/site/adapters/notion/notion.normalizer.ts @@ -0,0 +1,31 @@ +import { idToUuid } from 'notion-utils' +import type { SiteData } from '../../site.types' + +export function normalizeNotionSite( + recordMap: any, + sitePageId: string, + from?: string +): SiteData { + sitePageId = idToUuid(sitePageId) + + // ⬇️ 原 convertNotionToSiteData 内容迁到这里 + // normalize metadata / collection / schema / pages + // return SiteData(未清洗版) + + return { + NOTION_CONFIG: {}, + siteInfo: {} as any, + notice: null, + allPages: [], + allNavPages: [], + latestPosts: [], + categoryOptions: [], + tagOptions: [], + customNav: [], + customMenu: [], + postCount: 0, + block: recordMap?.block, + schema: {}, + rawMetadata: {} + } +} diff --git a/lib/site/processors/empty.processor.ts b/lib/site/processors/empty.processor.ts new file mode 100644 index 00000000000..8ed4ed315f2 --- /dev/null +++ b/lib/site/processors/empty.processor.ts @@ -0,0 +1,24 @@ +import BLOG from '@/blog.config' +import type { SiteData } from '../site.types' + +export function EmptyData(pageId?: string): SiteData { + return { + NOTION_CONFIG: {}, + siteInfo: { + title: 'NotionNext BLOG', + description: '无法获取 Notion 数据', + pageCover: '/bg_image.jpg', + icon: '/avatar.svg', + link: BLOG.LINK + }, + notice: null, + allPages: [], + allNavPages: [], + latestPosts: [], + categoryOptions: [], + tagOptions: [], + customNav: [], + customMenu: [], + postCount: 0 + } +} diff --git a/lib/site/processors/page.processor.ts b/lib/site/processors/page.processor.ts new file mode 100644 index 00000000000..c741cf5bfd5 --- /dev/null +++ b/lib/site/processors/page.processor.ts @@ -0,0 +1,21 @@ +import { cleanPages, cleanIds, shortenIds } from '@/lib/utils/clean.util' +import { applySchedulePublish } from '@/lib/site/processors/schedule.processor' +import type { SiteData } from '@/lib/site/site.types' + +export function handleDataBeforeReturn(db: SiteData): SiteData { + applySchedulePublish(db) + + db.categoryOptions = cleanIds(db.categoryOptions) + db.customMenu = cleanIds(db.customMenu) + + db.allNavPages = cleanPages(db.allNavPages, db.tagOptions) + db.allPages = cleanPages(db.allPages, db.tagOptions) + db.latestPosts = cleanPages(db.latestPosts, db.tagOptions) + + delete db.block + delete db.schema + delete db.rawMetadata + delete db.pageIds + + return db +} diff --git a/lib/site/processors/schedule.processor.ts b/lib/site/processors/schedule.processor.ts new file mode 100644 index 00000000000..8cf2e585385 --- /dev/null +++ b/lib/site/processors/schedule.processor.ts @@ -0,0 +1,10 @@ +import { isInRange } from '@/lib/utils/time.util' +import type { SiteData } from '../site.types' + +export function applySchedulePublish(db: SiteData) { + db.allPages?.forEach(p => { + if (!isInRange(p.title, p.date)) { + p.status = 'Invisible' + } + }) +} diff --git a/lib/site/site.api.ts b/lib/site/site.api.ts new file mode 100644 index 00000000000..7d88500548c --- /dev/null +++ b/lib/site/site.api.ts @@ -0,0 +1,19 @@ +import type { + SiteData, + FetchSiteParams, + NavPage +} from './site.types.js' + +/** + * 获取整个站点数据 + * 等价于:GET /site + */ +export interface SiteAPI { + fetchSite(params: FetchSiteParams): Promise + + /** + * 获取导航用文章列表 + * 等价于:GET /nav-pages + */ + getNavPages(allPages: SiteData['allPages']): NavPage[] +} diff --git a/lib/site/site.service.ts b/lib/site/site.service.ts new file mode 100644 index 00000000000..d90489dba4f --- /dev/null +++ b/lib/site/site.service.ts @@ -0,0 +1,19 @@ +import { fetchSiteFromNotion } from '@/lib/site/adapters/notion/notion.adapter' +import { handleDataBeforeReturn } from '@/lib/site/processors/page.processor' +import { EmptyData } from '@/lib/site/processors/empty.processor' +import type { FetchSiteParams, SiteData } from './site.types' + +export async function fetchSite( + params: FetchSiteParams +): Promise { + const { pageId } = params + if (!pageId) return EmptyData(pageId) + + try { + const siteData = await fetchSiteFromNotion(params) + return handleDataBeforeReturn(siteData) + } catch (e) { + console.error('[site] fetch failed', e) + return EmptyData(pageId) + } +} diff --git a/lib/site/site.types.ts b/lib/site/site.types.ts new file mode 100644 index 00000000000..35f5486e664 --- /dev/null +++ b/lib/site/site.types.ts @@ -0,0 +1,97 @@ +export interface FetchSiteParams { + pageId: string + from?: string + locale?: string +} + +export interface SiteInfo { + title: string + description: string + pageCover: string + icon: string + link: string +} + +export type PageStatus = 'Published' | 'Invisible' +export type PageType = 'Post' | 'Page' | 'Notice' | 'Menu' | 'SubMenu' + +export interface PageDate { + start_date?: string + start_time?: string + end_date?: string + end_time?: string + time_zone?: string + lastEditedDay?: string +} + +export interface TagItem { + name: string +} + +export interface BasePage { + id?: string + title: string + slug: string + type: PageType + status: PageStatus + summary?: string + category?: string + tags?: string[] + tagItems?: TagItem[] + date?: PageDate + publishDate?: number + lastEditedDate?: number + pageCoverThumbnail?: string + pageIcon?: string + href?: string + ext?: Record +} + +export interface NavPage { + id?: string + title: string + slug: string + summary?: string + category?: string + tags?: string[] + pageCoverThumbnail?: string + pageIcon?: string + href?: string + publishDate?: number + lastEditedDate?: number + ext?: Record +} + +export interface MenuItem { + name: string + icon?: string | null + href?: string + target?: string + show: boolean + subMenus?: MenuItem[] +} + +export interface SiteData { + NOTION_CONFIG: Record + + siteInfo: SiteInfo + notice: BasePage | null + + allPages: BasePage[] + allNavPages: NavPage[] + latestPosts: BasePage[] + + categoryOptions: any[] + tagOptions: any[] + + customNav: MenuItem[] + customMenu: MenuItem[] + + postCount: number + + // 以下字段仅服务端使用 + block?: any + schema?: any + rawMetadata?: any + pageIds?: string[] +} diff --git a/lib/utils/clean.util.ts b/lib/utils/clean.util.ts new file mode 100644 index 00000000000..5c9576d9989 --- /dev/null +++ b/lib/utils/clean.util.ts @@ -0,0 +1,19 @@ +import { deepClone } from '@/lib/utils' + +export function cleanIds(items?: any[]) { + if (!Array.isArray(items)) return items + return deepClone(items.map(i => { + delete i.id + return i + })) +} + +export function cleanPages(pages?: any[], tagOptions?: any[]) { + if (!Array.isArray(pages)) return pages || [] + return pages +} + +export function shortenIds(items?: any[]) { + if (!Array.isArray(items)) return items + return items +} diff --git a/lib/font.js b/lib/utils/font.js similarity index 95% rename from lib/font.js rename to lib/utils/font.js index 797c5dc4d85..1884bec3dfa 100644 --- a/lib/font.js +++ b/lib/utils/font.js @@ -1,7 +1,7 @@ /** * 在此处配置字体 */ -const BLOG = require('../blog.config') +const BLOG = require('../../blog.config') // const { fontFamily } = require('tailwindcss/defaultTheme') diff --git a/lib/lang.js b/lib/utils/lang.js similarity index 91% rename from lib/lang.js rename to lib/utils/lang.js index 47bb7093f47..bf2e3b0d1d0 100644 --- a/lib/lang.js +++ b/lib/utils/lang.js @@ -1,13 +1,13 @@ import BLOG from '@/blog.config' import { getQueryVariable, isBrowser, mergeDeep } from '@/lib/utils' -import enUS from './lang/en-US' -import frFR from './lang/fr-FR' -import jaJP from './lang/ja-JP' -import trTR from './lang/tr-TR' -import zhCN from './lang/zh-CN' -import zhHK from './lang/zh-HK' -import zhTW from './lang/zh-TW' -import { extractLangPrefix } from './utils/pageId' +import enUS from '../lang/en-US' +import frFR from '../lang/fr-FR' +import jaJP from '../lang/ja-JP' +import trTR from '../lang/tr-TR' +import zhCN from '../lang/zh-CN' +import zhHK from '../lang/zh-HK' +import zhTW from '../lang/zh-TW' +import { extractLangPrefix } from './pageId' import { useRouter } from 'next/router' /** diff --git a/lib/password.js b/lib/utils/password.js similarity index 96% rename from lib/password.js rename to lib/utils/password.js index 67b341a6ad6..2342be10784 100644 --- a/lib/password.js +++ b/lib/utils/password.js @@ -1,4 +1,4 @@ -import { isBrowser } from './utils' +import { isBrowser } from '.' /** * 获取默认密码 diff --git a/lib/utils/post.js b/lib/utils/post.js index fb1ef075d07..fddedcc4769 100644 --- a/lib/utils/post.js +++ b/lib/utils/post.js @@ -1,16 +1,14 @@ /** * 文章相关工具 + * 此处只能放客户端支持的代码 */ +import BLOG from '@/blog.config' import { isHttpLink } from '.' -import { getPostBlocks } from '@/lib/db/getSiteData' -import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents' import { siteConfig } from '@/lib/config' -import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager' -import { getAiSummary } from '@/lib/plugins/aiSummary' -import BLOG from '@/blog.config' -import { uploadDataToAlgolia } from '@/lib/plugins/algolia' -import { countWords } from '@/lib/plugins/wordCount' -import { getPageContentText } from '@/lib/notion/getPageContentText' +import { uploadDataToAlgolia } from '../plugins/algolia' +import { getPageContentText } from '@/lib/db/notion/getPageContentText' +import { getPageTableOfContents } from '../db/notion/getPageTableOfContents' +import { countWords } from '../plugins/wordCount' /** * 获取文章的关联推荐文章列表,目前根据标签关联性筛选 @@ -98,6 +96,7 @@ export function checkSlugHasMorThanTwoSlash(row) { ) } + /** * 获取文章摘要 * @param props @@ -137,10 +136,6 @@ async function getPageAISummary(props, pageContentText) { * @returns {Promise} */ export async function processPostData(props, from) { - // 文章内容加载 - if (!props?.post?.blockMap) { - props.post.blockMap = await getPostBlocks(props.post.id, from) - } if (props.post?.blockMap?.block) { // 目录默认加载 diff --git a/lib/redirect.js b/lib/utils/redirect.js similarity index 100% rename from lib/redirect.js rename to lib/utils/redirect.js diff --git a/lib/robots.txt.js b/lib/utils/robots.txt.js similarity index 100% rename from lib/robots.txt.js rename to lib/utils/robots.txt.js diff --git a/lib/rss.js b/lib/utils/rss.js similarity index 98% rename from lib/rss.js rename to lib/utils/rss.js index 04c667ce58f..593e710b6c9 100644 --- a/lib/rss.js +++ b/lib/utils/rss.js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import NotionPage from '@/components/NotionPage' -import { getPostBlocks } from '@/lib/db/getSiteData' +import { getPostBlocks } from '@/lib/db/SiteDataApi' import { Feed } from 'feed' import fs from 'fs' import ReactDOMServer from 'react-dom/server' diff --git a/lib/sitemap.js b/lib/utils/sitemap.js similarity index 99% rename from lib/sitemap.js rename to lib/utils/sitemap.js index 3b34b217c22..50083158ed2 100644 --- a/lib/sitemap.js +++ b/lib/utils/sitemap.js @@ -1,5 +1,5 @@ import { siteConfig } from '@/lib/config' -import { getAllPosts } from '@/lib/db/getSiteData' +import { getAllPosts } from '@/lib/db/SiteDataApi' /** * 生成站点地图 diff --git a/lib/sitemap.xml.js b/lib/utils/sitemap.xml.js similarity index 98% rename from lib/sitemap.xml.js rename to lib/utils/sitemap.xml.js index 1c931a5051b..13be702bdd8 100644 --- a/lib/sitemap.xml.js +++ b/lib/utils/sitemap.xml.js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import fs from 'fs' -import { siteConfig } from './config' +import { siteConfig } from '../config' /** * 生成站点地图 * @param {*} param0 diff --git a/lib/utils/time.util.ts b/lib/utils/time.util.ts new file mode 100644 index 00000000000..8b148f5ea20 --- /dev/null +++ b/lib/utils/time.util.ts @@ -0,0 +1,4 @@ +export function isInRange(title?: string, date: any = {}) { + // 直接迁你原来的逻辑 + return true +} diff --git a/pages/404.js b/pages/404.js index ca421b9ff3a..81125b7f422 100644 --- a/pages/404.js +++ b/pages/404.js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' /** @@ -16,7 +16,7 @@ const NoFound = props => { export async function getStaticProps(req) { const { locale } = req - const props = (await getGlobalData({ from: '404', locale })) || {} + const props = (await fetchGlobalAllData({ from: '404', locale })) || {} return { props } } diff --git a/pages/[prefix]/[slug]/[...suffix].js b/pages/[prefix]/[slug]/[...suffix].js index 5e55ef856ad..f8a9615b25d 100644 --- a/pages/[prefix]/[slug]/[...suffix].js +++ b/pages/[prefix]/[slug]/[...suffix].js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData, getPost } from '@/lib/db/getSiteData' +import { fetchGlobalAllData, resolvePostProps } from '@/lib/db/SiteDataApi' import { checkSlugHasMorThanTwoSlash, processPostData } from '@/lib/utils/post' import { idToUuid } from 'notion-utils' import Slug from '..' @@ -28,7 +28,7 @@ export async function getStaticPaths() { } const from = 'slug-paths' - const { allPages } = await getGlobalData({ from }) + const { allPages } = await fetchGlobalAllData({ from }) const paths = allPages ?.filter(row => checkSlugHasMorThanTwoSlash(row)) .map(row => ({ @@ -53,45 +53,22 @@ export async function getStaticProps({ params: { prefix, slug, suffix }, locale }) { - const fullSlug = prefix + '/' + slug + '/' + suffix.join('/') - const from = `slug-props-${fullSlug}` - const props = await getGlobalData({ from, locale }) - - // 在列表内查找文章 - props.post = props?.allPages?.find(p => { - return ( - p.type.indexOf('Menu') < 0 && - (p.slug === suffix || - p.slug === fullSlug.substring(fullSlug.lastIndexOf('/') + 1) || - p.slug === fullSlug || - p.id === idToUuid(fullSlug)) - ) + const props = await resolvePostProps({ + prefix, + slug, + suffix, + locale, }) - // 处理非列表内文章的内信息 - if (!props?.post) { - const pageId = fullSlug.slice(-1)[0] - if (pageId.length >= 32) { - const post = await getPost(pageId) - props.post = post - } - } - - if (!props?.post) { - // 无法获取文章 - props.post = null - } else { - await processPostData(props, from) - } return { props, revalidate: process.env.EXPORT ? undefined : siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/[prefix]/[slug]/index.js b/pages/[prefix]/[slug]/index.js index cb81cfd6e9e..42c550e9777 100644 --- a/pages/[prefix]/[slug]/index.js +++ b/pages/[prefix]/[slug]/index.js @@ -1,8 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData, getPost } from '@/lib/db/getSiteData' -import { checkSlugHasOneSlash, processPostData } from '@/lib/utils/post' -import { idToUuid } from 'notion-utils' +import { fetchGlobalAllData, resolvePostProps } from '@/lib/db/SiteDataApi' import Slug from '..' /** @@ -24,7 +22,7 @@ export async function getStaticPaths() { } const from = 'slug-paths' - const { allPages } = await getGlobalData({ from }) + const { allPages } = await fetchGlobalAllData({ from }) // 根据slug中的 / 分割成prefix和slug两个字段 ; 例如 article/test // 最终用户可以通过 [domain]/[prefix]/[slug] 路径访问,即这里的 [domain]/article/test @@ -45,42 +43,21 @@ export async function getStaticPaths() { } export async function getStaticProps({ params: { prefix, slug }, locale }) { - const fullSlug = prefix + '/' + slug - const from = `slug-props-${fullSlug}` - const props = await getGlobalData({ from, locale }) - - // 在列表内查找文章 - props.post = props?.allPages?.find(p => { - return ( - p.type.indexOf('Menu') < 0 && - (p.slug === slug || p.slug === fullSlug || p.id === idToUuid(fullSlug)) - ) + const props = await resolvePostProps({ + prefix, + slug, + locale, }) - // 处理非列表内文章的内信息 - if (!props?.post) { - const pageId = slug.slice(-1)[0] - if (pageId.length >= 32) { - const post = await getPost(pageId) - props.post = post - } - } - - if (!props?.post) { - // 无法获取文章 - props.post = null - } else { - await processPostData(props, from) - } return { props, revalidate: process.env.EXPORT ? undefined : siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ), } } diff --git a/pages/[prefix]/index.js b/pages/[prefix]/index.js index 672e6a0dd01..cbe20862d6d 100644 --- a/pages/[prefix]/index.js +++ b/pages/[prefix]/index.js @@ -2,11 +2,11 @@ import BLOG from '@/blog.config' import useNotification from '@/components/Notification' import OpenWrite from '@/components/OpenWrite' import { siteConfig } from '@/lib/config' -import { getGlobalData, getPost } from '@/lib/db/getSiteData' +import { fetchGlobalAllData, resolvePostProps } from '@/lib/db/SiteDataApi' import { useGlobal } from '@/lib/global' -import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents' -import { getPasswordQuery } from '@/lib/password' -import { checkSlugHasNoSlash, processPostData } from '@/lib/utils/post' +import { getPageTableOfContents } from '@/lib/db/notion/getPageTableOfContents' +import { getPasswordQuery } from '@/lib/utils/password' +import { checkSlugHasMorThanTwoSlash, processPostData } from '@/lib/utils/post' import { DynamicLayout } from '@/themes/theme' import md5 from 'js-md5' import { useRouter } from 'next/router' @@ -104,7 +104,7 @@ export async function getStaticPaths() { } const from = 'slug-paths' - const { allPages } = await getGlobalData({ from }) + const { allPages } = await fetchGlobalAllData({ from }) const paths = allPages ?.filter(row => checkSlugHasNoSlash(row)) .map(row => ({ params: { prefix: row.slug } })) @@ -115,46 +115,20 @@ export async function getStaticPaths() { } export async function getStaticProps({ params: { prefix }, locale }) { - let fullSlug = prefix - const from = `slug-props-${fullSlug}` - const props = await getGlobalData({ from, locale }) - if (siteConfig('PSEUDO_STATIC', false, props.NOTION_CONFIG)) { - if (!fullSlug.endsWith('.html')) { - fullSlug += '.html' - } - } - - // 在列表内查找文章 - props.post = props?.allPages?.find(p => { - return ( - p.type.indexOf('Menu') < 0 && - (p.slug === prefix || p.id === idToUuid(prefix)) - ) + const props = await resolvePostProps({ + prefix, + locale, }) - // 处理非列表内文章的内信息 - if (!props?.post) { - const pageId = prefix - if (pageId.length >= 32) { - const post = await getPost(pageId) - props.post = post - } - } - if (!props?.post) { - // 无法获取文章 - props.post = null - } else { - await processPostData(props, from) - } return { props, revalidate: process.env.EXPORT ? undefined : siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/archive/index.js b/pages/archive/index.js index e7aade1e5ad..164a74b4426 100644 --- a/pages/archive/index.js +++ b/pages/archive/index.js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { isBrowser } from '@/lib/utils' import { formatDateFmt } from '@/lib/utils/formatDate' import { DynamicLayout } from '@/themes/theme' @@ -31,7 +31,7 @@ const ArchiveIndex = props => { } export async function getStaticProps({ locale }) { - const props = await getGlobalData({ from: 'archive-index', locale }) + const props = await fetchGlobalAllData({ from: 'archive-index', locale }) // 处理分页 props.posts = props.allPages?.filter( page => page.type === 'Post' && page.status === 'Published' diff --git a/pages/auth/index.js b/pages/auth/index.js index cce744d9233..9a64012a46b 100644 --- a/pages/auth/index.js +++ b/pages/auth/index.js @@ -1,5 +1,5 @@ // pages/sitemap.xml.js -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import axios from 'axios' import { useRouter } from 'next/router' import { useEffect } from 'react' @@ -27,7 +27,7 @@ const UI = props => { */ export const getServerSideProps = async ctx => { const from = `auth` - const props = await getGlobalData({ from }) + const props = await fetchGlobalAllData({ from }) delete props.allPages const code = ctx.query.code diff --git a/pages/auth/result.js b/pages/auth/result.js index c4553153edb..03ae5d0ce67 100644 --- a/pages/auth/result.js +++ b/pages/auth/result.js @@ -1,5 +1,5 @@ // pages/sitemap.xml.js -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { useRouter } from 'next/router' import Slug from '../[prefix]' @@ -9,7 +9,7 @@ import Slug from '../[prefix]' */ export const getStaticProps = async () => { const from = `auth` - const props = await getGlobalData({ from }) + const props = await fetchGlobalAllData({ from }) delete props.allPages return { diff --git a/pages/category/[category]/index.js b/pages/category/[category]/index.js index f019dd327c8..1b160473383 100644 --- a/pages/category/[category]/index.js +++ b/pages/category/[category]/index.js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' /** @@ -15,7 +15,7 @@ export default function Category(props) { export async function getStaticProps({ params: { category }, locale }) { const from = 'category-props' - let props = await getGlobalData({ from, locale }) + let props = await fetchGlobalAllData({ from, locale }) // 过滤状态 props.posts = props.allPages?.filter( @@ -55,7 +55,7 @@ export async function getStaticProps({ params: { category }, locale }) { export async function getStaticPaths() { const from = 'category-paths' - const { categoryOptions } = await getGlobalData({ from }) + const { categoryOptions } = await fetchGlobalAllData({ from }) return { paths: Object.keys(categoryOptions).map(category => ({ params: { category: categoryOptions[category]?.name } diff --git a/pages/category/[category]/page/[page].js b/pages/category/[category]/page/[page].js index 6863ee43e3f..8ab88550763 100644 --- a/pages/category/[category]/page/[page].js +++ b/pages/category/[category]/page/[page].js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' /** @@ -16,7 +16,7 @@ export default function Category(props) { export async function getStaticProps({ params: { category, page } }) { const from = 'category-page-props' - let props = await getGlobalData({ from }) + let props = await fetchGlobalAllData({ from }) // 过滤状态类型 props.posts = props.allPages @@ -50,7 +50,7 @@ export async function getStaticProps({ params: { category, page } }) { export async function getStaticPaths() { const from = 'category-paths' - const { categoryOptions, allPages, NOTION_CONFIG } = await getGlobalData({ + const { categoryOptions, allPages, NOTION_CONFIG } = await fetchGlobalAllData({ from }) const paths = [] diff --git a/pages/category/index.js b/pages/category/index.js index 1fcf752dec4..e107464ecb3 100644 --- a/pages/category/index.js +++ b/pages/category/index.js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' /** @@ -16,7 +16,7 @@ export default function Category(props) { } export async function getStaticProps({ locale }) { - const props = await getGlobalData({ from: 'category-index-props', locale }) + const props = await fetchGlobalAllData({ from: 'category-index-props', locale }) delete props.allPages return { props, diff --git a/pages/dashboard/[[...index]].js b/pages/dashboard/[[...index]].js index 330625e315a..ed6e257a1be 100644 --- a/pages/dashboard/[[...index]].js +++ b/pages/dashboard/[[...index]].js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData' +import { fetchGlobalAllData, fetchPostDetail, getPostBlocks } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' /** @@ -18,7 +18,7 @@ export async function getStaticProps({ locale }) { const prefix = 'dashboard' let fullSlug = 'dashboard' const from = `slug-props-${fullSlug}` - const props = await getGlobalData({ from, locale }) + const props = await fetchGlobalAllData({ from, locale }) if (siteConfig('PSEUDO_STATIC', false, props.NOTION_CONFIG)) { if (!fullSlug.endsWith('.html')) { fullSlug += '.html' @@ -34,7 +34,7 @@ export async function getStaticProps({ locale }) { if (!props?.post) { const pageId = prefix if (pageId.length >= 32) { - const post = await getPost(pageId) + const post = await fetchPostDetail(pageId) props.post = post } } @@ -46,28 +46,23 @@ export async function getStaticProps({ locale }) { revalidate: process.env.EXPORT ? undefined : siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } - // 文章内容加载 - if (!props?.post?.blockMap) { - props.post.blockMap = await getPostBlocks(props.post.id, from) - } - delete props.allPages return { props, revalidate: process.env.EXPORT ? undefined : siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) + 'NEXT_REVALIDATE_SECOND', + BLOG.NEXT_REVALIDATE_SECOND, + props.NOTION_CONFIG + ) } } diff --git a/pages/index.js b/pages/index.js index b4b9937a791..d9502f71818 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,11 +1,11 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData, getPostBlocks } from '@/lib/db/getSiteData' -import { generateRobotsTxt } from '@/lib/robots.txt' -import { generateRss } from '@/lib/rss' -import { generateSitemapXml } from '@/lib/sitemap.xml' +import { fetchGlobalAllData, getPostBlocks } from '@/lib/db/SiteDataApi' +import { generateRobotsTxt } from '@/lib/utils/robots.txt' +import { generateRss } from '@/lib/utils/rss' +import { generateSitemapXml } from '@/lib/utils/sitemap.xml' import { DynamicLayout } from '@/themes/theme' -import { generateRedirectJson } from '@/lib/redirect' +import { generateRedirectJson } from '@/lib/utils/redirect' import { checkDataFromAlgolia } from '@/lib/plugins/algolia' /** @@ -25,7 +25,7 @@ const Index = props => { export async function getStaticProps(req) { const { locale } = req const from = 'index' - const props = await getGlobalData({ from, locale }) + const props = await fetchGlobalAllData({ from, locale }) const POST_PREVIEW_LINES = siteConfig( 'POST_PREVIEW_LINES', 12, diff --git a/pages/page/[page].js b/pages/page/[page].js index 1ee22afb574..d718b7de3a6 100644 --- a/pages/page/[page].js +++ b/pages/page/[page].js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData, getPostBlocks } from '@/lib/db/getSiteData' +import { fetchGlobalAllData, getPostBlocks } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' /** @@ -15,7 +15,7 @@ const Page = props => { export async function getStaticPaths({ locale }) { const from = 'page-paths' - const { postCount, NOTION_CONFIG } = await getGlobalData({ from, locale }) + const { postCount, NOTION_CONFIG } = await fetchGlobalAllData({ from, locale }) const totalPages = Math.ceil( postCount / siteConfig('POSTS_PER_PAGE', null, NOTION_CONFIG) ) @@ -30,7 +30,7 @@ export async function getStaticPaths({ locale }) { export async function getStaticProps({ params: { page }, locale }) { const from = `page-${page}` - const props = await getGlobalData({ from, locale }) + const props = await fetchGlobalAllData({ from, locale }) const { allPages } = props const POST_PREVIEW_LINES = siteConfig( 'POST_PREVIEW_LINES', diff --git a/pages/search/[keyword]/index.js b/pages/search/[keyword]/index.js index cb486895460..721721f2582 100644 --- a/pages/search/[keyword]/index.js +++ b/pages/search/[keyword]/index.js @@ -1,9 +1,9 @@ import BLOG from '@/blog.config' import { getDataFromCache } from '@/lib/cache/cache_manager' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' -import { getPageContentText } from '@/lib/notion/getPageContentText' +import { getPageContentText } from '@/lib/db/notion/getPageContentText' const Index = props => { const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) @@ -16,7 +16,7 @@ const Index = props => { * @returns */ export async function getStaticProps({ params: { keyword }, locale }) { - const props = await getGlobalData({ + const props = await fetchGlobalAllData({ from: 'search-props', locale }) diff --git a/pages/search/[keyword]/page/[page].js b/pages/search/[keyword]/page/[page].js index 371d23e750e..2ff4fc70c46 100644 --- a/pages/search/[keyword]/page/[page].js +++ b/pages/search/[keyword]/page/[page].js @@ -1,7 +1,7 @@ import BLOG from '@/blog.config' import { getDataFromCache } from '@/lib/cache/cache_manager' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' const Index = props => { @@ -18,7 +18,7 @@ const Index = props => { * @returns */ export async function getStaticProps({ params: { keyword, page }, locale }) { - const props = await getGlobalData({ + const props = await fetchGlobalAllData({ from: 'search-props', pageType: ['Post'], locale diff --git a/pages/search/index.js b/pages/search/index.js index 3eb642b1bfa..1da2574739e 100644 --- a/pages/search/index.js +++ b/pages/search/index.js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' @@ -39,7 +39,7 @@ const Search = props => { * 浏览器前端搜索 */ export async function getStaticProps({ locale }) { - const props = await getGlobalData({ + const props = await fetchGlobalAllData({ from: 'search-props', locale }) diff --git a/pages/sign-in/[[...index]].js b/pages/sign-in/[[...index]].js index f11964f1b71..b8cdd9b745a 100644 --- a/pages/sign-in/[[...index]].js +++ b/pages/sign-in/[[...index]].js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' // import { getGlobalData } from '@/lib/db/getSiteData' import { DynamicLayout } from '@/themes/theme' @@ -18,7 +18,7 @@ export async function getStaticProps(req) { const { locale } = req const from = 'SignIn' - const props = await getGlobalData({ from, locale }) + const props = await fetchGlobalAllData({ from, locale }) delete props.allPages return { diff --git a/pages/sign-up/[[...index]].js b/pages/sign-up/[[...index]].js index d51206009e5..53601f113b3 100644 --- a/pages/sign-up/[[...index]].js +++ b/pages/sign-up/[[...index]].js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' /** @@ -17,7 +17,7 @@ export async function getStaticProps(req) { const { locale } = req const from = 'SignIn' - const props = await getGlobalData({ from, locale }) + const props = await fetchGlobalAllData({ from, locale }) delete props.allPages return { diff --git a/pages/sitemap.xml.js b/pages/sitemap.xml.js index 06c21f4297a..0da91764fab 100644 --- a/pages/sitemap.xml.js +++ b/pages/sitemap.xml.js @@ -1,7 +1,7 @@ // pages/sitemap.xml.js import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { extractLangId, extractLangPrefix } from '@/lib/utils/pageId' import { getServerSideSitemap } from 'next-sitemap' @@ -14,7 +14,7 @@ export const getServerSideProps = async ctx => { const id = extractLangId(siteId) const locale = extractLangPrefix(siteId) // 第一个id站点默认语言 - const siteData = await getGlobalData({ + const siteData = await fetchGlobalAllData({ pageId: id, from: 'sitemap.xml' }) diff --git a/pages/tag/[tag]/index.js b/pages/tag/[tag]/index.js index 978e8545713..9f874a64845 100644 --- a/pages/tag/[tag]/index.js +++ b/pages/tag/[tag]/index.js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' /** @@ -15,7 +15,7 @@ const Tag = props => { export async function getStaticProps({ params: { tag }, locale }) { const from = 'tag-props' - const props = await getGlobalData({ from, locale }) + const props = await fetchGlobalAllData({ from, locale }) // 过滤状态 props.posts = props.allPages @@ -64,7 +64,7 @@ function getTagNames(tags) { export async function getStaticPaths() { const from = 'tag-static-path' - const { tagOptions } = await getGlobalData({ from }) + const { tagOptions } = await fetchGlobalAllData({ from }) const tagNames = getTagNames(tagOptions) return { diff --git a/pages/tag/[tag]/page/[page].js b/pages/tag/[tag]/page/[page].js index 414eaf446d8..c13b9661a37 100644 --- a/pages/tag/[tag]/page/[page].js +++ b/pages/tag/[tag]/page/[page].js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' const Tag = props => { @@ -10,7 +10,7 @@ const Tag = props => { export async function getStaticProps({ params: { tag, page }, locale }) { const from = 'tag-page-props' - const props = await getGlobalData({ from, locale }) + const props = await fetchGlobalAllData({ from, locale }) // 过滤状态、标签 props.posts = props.allPages ?.filter(page => page.type === 'Post' && page.status === 'Published') @@ -41,7 +41,7 @@ export async function getStaticProps({ params: { tag, page }, locale }) { export async function getStaticPaths() { const from = 'tag-page-static-path' - const { tagOptions, allPages, NOTION_CONFIG } = await getGlobalData({ from }) + const { tagOptions, allPages, NOTION_CONFIG } = await fetchGlobalAllData({ from }) const paths = [] tagOptions?.forEach(tag => { // 过滤状态类型 diff --git a/pages/tag/index.js b/pages/tag/index.js index 0f6137db277..6b41ed925c3 100644 --- a/pages/tag/index.js +++ b/pages/tag/index.js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { getGlobalData } from '@/lib/db/getSiteData' +import { fetchGlobalAllData } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' import { useRouter } from 'next/router' @@ -19,7 +19,7 @@ export async function getStaticProps(req) { const { locale } = req const from = 'tag-index-props' - const props = await getGlobalData({ from, locale }) + const props = await fetchGlobalAllData({ from, locale }) delete props.allPages return { props, diff --git a/tailwind.config.js b/tailwind.config.js index 60b84aa197d..aa0522408e1 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,5 +1,5 @@ const BLOG = require('./blog.config') -const { fontFamilies } = require('./lib/font') +const { fontFamilies } = require('./lib/utils/font') module.exports = { content: [ diff --git a/themes/plog/components/BlogPost.js b/themes/plog/components/BlogPost.js index ce8dd56bc07..4cd93c640a2 100644 --- a/themes/plog/components/BlogPost.js +++ b/themes/plog/components/BlogPost.js @@ -1,4 +1,4 @@ -import { compressImage } from '@/lib/notion/mapImage' +import { compressImage } from '@/lib/db/notion/mapImage' import SmartLink from '@/components/SmartLink' import { usePlogGlobal } from '..' import { isMobile } from '@/lib/utils' diff --git a/themes/plog/components/Modal.js b/themes/plog/components/Modal.js index 6b492df1b0d..b18fb1ecfa7 100644 --- a/themes/plog/components/Modal.js +++ b/themes/plog/components/Modal.js @@ -1,6 +1,6 @@ import { ArrowPath, ChevronLeft, ChevronRight } from '@/components/HeroIcons' import LazyImage from '@/components/LazyImage' -import { compressImage } from '@/lib/notion/mapImage' +import { compressImage } from '@/lib/db/notion/mapImage' import { Dialog, Transition } from '@headlessui/react' import SmartLink from '@/components/SmartLink' import { Fragment, useRef, useState } from 'react' From 665e1e5eb02f828fd0f56c09bd22db0bba7a9754 Mon Sep 17 00:00:00 2001 From: tangly1024 Date: Sun, 8 Feb 2026 11:52:54 +0800 Subject: [PATCH 2/5] fix build --- lib/db/SiteDataApi.js | 47 ++++++++++++++++++++------------- pages/[prefix]/[slug]/index.js | 1 + pages/[prefix]/index.js | 3 +-- pages/dashboard/[[...index]].js | 41 +++------------------------- 4 files changed, 34 insertions(+), 58 deletions(-) diff --git a/lib/db/SiteDataApi.js b/lib/db/SiteDataApi.js index 1c7507ae4ce..e8f1166287c 100644 --- a/lib/db/SiteDataApi.js +++ b/lib/db/SiteDataApi.js @@ -255,31 +255,40 @@ export async function resolvePostProps({ props.post = null } + delete props.allPages + return props } /** * 修复新版 Notion 返回的包裹结构 - * 只解 spaceId 包裹,不拍平 value - * 兼容 block / collection + * 规则: + * - 只要对象形如 { spaceId | space_id, value } + * - 就解包为 { value } + * - 不拍平 value,不修改内部结构 */ export function unwrapNotionBlockMap(blockMap) { - if (!blockMap) return blockMap + if (!blockMap || typeof blockMap !== 'object') return blockMap - const unwrap = (source) => { - if (!source) return source + const unwrapMap = (map) => { + if (!map || typeof map !== 'object') return map const fixed = {} - for (const [id, item] of Object.entries(source)) { - if (!item) continue + for (const [id, item] of Object.entries(map)) { + if (!item || typeof item !== 'object') { + fixed[id] = item + continue + } + + // ✅ 统一识别新版 Notion 包裹结构 + const hasSpaceId = + Object.prototype.hasOwnProperty.call(item, 'spaceId') || + Object.prototype.hasOwnProperty.call(item, 'space_id') - // 新结构:{ spaceId, value } - if (item.spaceId && item.value) { - fixed[id] = { - value: item.value - } + if (hasSpaceId && item.value) { + fixed[id] = { value: item.value } } else { fixed[id] = item } @@ -288,15 +297,15 @@ export function unwrapNotionBlockMap(blockMap) { return fixed } - return { - ...blockMap, - block: unwrap(blockMap.block), - collection: unwrap(blockMap.collection), - } -} - + const result = { ...blockMap } + // 🔑 对 blockMap 下的所有一级对象统一处理 + for (const key of Object.keys(blockMap)) { + result[key] = unwrapMap(blockMap[key]) + } + return result +} /** diff --git a/pages/[prefix]/[slug]/index.js b/pages/[prefix]/[slug]/index.js index 42c550e9777..07a4707fe74 100644 --- a/pages/[prefix]/[slug]/index.js +++ b/pages/[prefix]/[slug]/index.js @@ -2,6 +2,7 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' import { fetchGlobalAllData, resolvePostProps } from '@/lib/db/SiteDataApi' import Slug from '..' +import { checkSlugHasOneSlash } from '@/lib/utils/post' /** * 根据notion的slug访问页面 diff --git a/pages/[prefix]/index.js b/pages/[prefix]/index.js index cbe20862d6d..7de9070793d 100644 --- a/pages/[prefix]/index.js +++ b/pages/[prefix]/index.js @@ -6,7 +6,7 @@ import { fetchGlobalAllData, resolvePostProps } from '@/lib/db/SiteDataApi' import { useGlobal } from '@/lib/global' import { getPageTableOfContents } from '@/lib/db/notion/getPageTableOfContents' import { getPasswordQuery } from '@/lib/utils/password' -import { checkSlugHasMorThanTwoSlash, processPostData } from '@/lib/utils/post' +import { checkSlugHasMorThanTwoSlash, checkSlugHasNoSlash, processPostData } from '@/lib/utils/post' import { DynamicLayout } from '@/themes/theme' import md5 from 'js-md5' import { useRouter } from 'next/router' @@ -119,7 +119,6 @@ export async function getStaticProps({ params: { prefix }, locale }) { prefix, locale, }) - return { props, revalidate: process.env.EXPORT diff --git a/pages/dashboard/[[...index]].js b/pages/dashboard/[[...index]].js index ed6e257a1be..05f79a066c9 100644 --- a/pages/dashboard/[[...index]].js +++ b/pages/dashboard/[[...index]].js @@ -1,6 +1,6 @@ import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' -import { fetchGlobalAllData, fetchPostDetail, getPostBlocks } from '@/lib/db/SiteDataApi' +import { resolvePostProps } from '@/lib/db/SiteDataApi' import { DynamicLayout } from '@/themes/theme' /** @@ -16,44 +16,11 @@ const Dashboard = props => { export async function getStaticProps({ locale }) { const prefix = 'dashboard' - let fullSlug = 'dashboard' - const from = `slug-props-${fullSlug}` - const props = await fetchGlobalAllData({ from, locale }) - if (siteConfig('PSEUDO_STATIC', false, props.NOTION_CONFIG)) { - if (!fullSlug.endsWith('.html')) { - fullSlug += '.html' - } - } - - // 在列表内查找文章 - props.post = props?.allPages?.find(p => { - return p.type.indexOf('Menu') < 0 && p.slug === fullSlug + const props = await resolvePostProps({ + prefix, + locale, }) - // 处理非列表内文章的内信息 - if (!props?.post) { - const pageId = prefix - if (pageId.length >= 32) { - const post = await fetchPostDetail(pageId) - props.post = post - } - } - // 无法获取文章 - if (!props?.post) { - props.post = null - return { - props, - revalidate: process.env.EXPORT - ? undefined - : siteConfig( - 'NEXT_REVALIDATE_SECOND', - BLOG.NEXT_REVALIDATE_SECOND, - props.NOTION_CONFIG - ) - } - } - - delete props.allPages return { props, revalidate: process.env.EXPORT From e584ad6d8137e4d588931b751c78d8b021686f61 Mon Sep 17 00:00:00 2001 From: tangly1024 Date: Sun, 8 Feb 2026 12:22:41 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E4=BF=AE=E5=BE=A9clerk=20=E7=84=A1?= =?UTF-8?q?=E6=B3=95=E7=B7=A8=E8=AD=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ExternalPlugins.js | 2 +- components/GlobalStyle.js | 2 +- components/NotionPage.js | 4 ++-- lib/db/SiteDataApi.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/ExternalPlugins.js b/components/ExternalPlugins.js index 0e0ef307ba6..fd062566522 100644 --- a/components/ExternalPlugins.js +++ b/components/ExternalPlugins.js @@ -182,7 +182,7 @@ const ExternalPlugin = props => { // 执行注入脚本 // eslint-disable-next-line no-eval if (GLOBAL_JS && GLOBAL_JS.trim() !== '') { - console.log('Inject JS:', GLOBAL_JS); + // console.log('Inject JS:', GLOBAL_JS); } eval(GLOBAL_JS) }) diff --git a/components/GlobalStyle.js b/components/GlobalStyle.js index e04201c33d8..4cc90510806 100644 --- a/components/GlobalStyle.js +++ b/components/GlobalStyle.js @@ -12,7 +12,7 @@ const GlobalStyle = () => { const GLOBAL_CSS = siteConfig('GLOBAL_CSS') // 如果这个字符串不为空,则打印显示 if (GLOBAL_CSS && GLOBAL_CSS.trim() !== '') { - console.log('Inject CSS:', GLOBAL_CSS); + // console.log('Inject CSS:', GLOBAL_CSS); } return (