diff --git a/.env.local b/.env.example similarity index 99% rename from .env.local rename to .env.example index 6952a6160be..eb29e18cf32 100644 --- a/.env.local +++ b/.env.example @@ -1,12 +1,11 @@ # 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables -NEXT_PUBLIC_VERSION=4.7.7 - # 可在此添加环境变量,去掉最左边的(# )注释即可 # Notion页面ID,必须 # NOTION_PAGE_ID=097e5f674880459d8e1b4407758dc4fb # 非必须 +# NEXT_PUBLIC_VERSION= # NEXT_PUBLIC_PSEUDO_STATIC= # NEXT_PUBLIC_REVALIDATE_SECOND= # NEXT_PUBLIC_THEME=matery diff --git a/.eslintrc.js b/.eslintrc.js index e2a5efe4006..f27440217f7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,7 +8,7 @@ module.exports = { 'plugin:react/jsx-runtime', 'plugin:react/recommended', 'plugin:@next/next/recommended', - 'standard', + 'next', 'prettier', 'plugin:@typescript-eslint/recommended', // 添加 TypeScript 推荐规则 'plugin:@typescript-eslint/recommended-requiring-type-checking' // 添加需要类型检查的规则 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000000..71291e2eccf --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,36 @@ +> 尽量按此模板PR内容,或粘贴相关的ISSUE链接。 + +## 已知问题 + +1. (示例)版本号管理不规范 + - 版本号直接写在环境变量中,容易出错 + - 多处维护版本号,可能不一致 + +## 解决方案 + +1. (示例)将版本号管理从 `.env.local` 迁移到 `package.json` + - 统一从 `package.json` 读取版本号 + - 使用 IIFE 优雅处理版本号获取逻辑 + - 保持向后兼容,支持环境变量覆盖 + +## 改动收益 + +1. (示例)更规范的版本管理 + - 统一从 `package.json` 读取 + - 保持与 npm 生态一致 + - 减少人为错误 + +## 具体改动 + +1. (示例)`blog.config.js` + - 移除原有的静态版本号配置 + - 在文件末尾添加动态版本号获取逻辑 + - 保持向后兼容,优先使用环境变量 + - 添加错误处理和默认值 + +## 测试确认 + +- [x] 本地开发环境测试通过 +- [x] 生产环境构建测试通过 +- [x] 版本号正确显示 +- [x] 环境变量配置正常工作 diff --git a/.gitignore b/.gitignore index b400f485732..a0811bd90d7 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ yarn-debug.log* yarn-error.log* # local env files -# .env.local # 版本号放在此环境变量中 +.env.local .env.development.local .env.test.local .env.production.local diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5fffb37aca2..9fde0549768 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,7 @@ - [Setup](#setup) - [Creating new themes](#creating-new-themes) - [Adding localizations](#adding-localizations) +- [Environment Variables](#environment-variables) Thanks for considering to contribute! @@ -42,6 +43,19 @@ localization! Follow these steps to add a new localization: 3. Add your language config to [lang.js][lang.js]. 4. [Create a PR][pr] with your localization updates. +## Environment Variables + +NotionNext uses environment variables for configuration. To set up your development environment: + +1. Copy `.env.example` to `.env.local` +2. Fill in the required values in `.env.local` +3. Never commit `.env.local` to version control + +The configuration priority is: +1. Notion Config Table (highest) +2. Environment Variables +3. blog.config.js (lowest) + [fork]: https://github.com/tangly1024/NotionNext/fork [pr]: https://github.com/tangly1024/NotionNext/compare [next.js]: https://github.com/vercel/next.js diff --git a/README.md b/README.md index 7002bf90544..f7ce7138faa 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # 帮助教程 -访问帮助:[NotionNext帮助手册](https://docs.tangly1024.com/) +访问帮助:[NotionNext帮助手册](https://docs.tangly1024.com/) > 本项目教程为免费、公开资源,仅限个人学习使用。严禁任何个人或组织将本教程用于商业用途,包括但不限于直接售卖、间接收费、或其他变相盈利行为。转载、复制或介绍本教程内容时,须保留作者信息并明确注明来源。 > 本项目仅提供由作者团队授权的付费咨询服务,请注意辨别,谨防诈骗行为。任何未经授权的收费服务均可能存在法律风险。 Notion是一个能让效率暴涨的生产力引擎,可以帮你书写文档、管理笔记,搭建知识库,甚至可以为你规划项目、时间管理、组织团队、提高生产力、还有当前最强大的AI技术加持。 + > 如果希望进一步探索Notion的功能,欢迎购买《[Notion笔记从入门到精通进阶课程](https://docs.tangly1024.com/article/notion-tutorial)》 # NotionNext @@ -27,23 +28,24 @@ Notion是一个能让效率暴涨的生产力引擎,可以帮你书写文档

- 中文文档 | [README in English](./README_EN.md)
一个使用 NextJS + Notion API 实现的,部署在 Vercel 上的静态博客系统。为Notion和所有创作者设计。 +支持多种部署方案 ## 预览效果 在线演示:[https://preview.tangly1024.com/](https://preview.tangly1024.com/) ,点击左下角挂件可以切换主题,没找到喜欢的主题?[贡献](/CONTRIBUTING.md)一个吧~ -| Next | Medium | Hexo | Fukasawa | -|--|--|--|--| -| [预览NEXT](https://preview.tangly1024.com/?theme=next) | [预览MEDIUM](https://preview.tangly1024.com/?theme=medium) | [预览HEXO](https://preview.tangly1024.com/?theme=hexo) | [预览FUKASAWA](https://preview.tangly1024.com/?theme=fukasawa) | +| Next | Medium | Hexo | Fukasawa | +| ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| [预览NEXT](https://preview.tangly1024.com/?theme=next) | [预览MEDIUM](https://preview.tangly1024.com/?theme=medium) | [预览HEXO](https://preview.tangly1024.com/?theme=hexo) | [预览FUKASAWA](https://preview.tangly1024.com/?theme=fukasawa) | ## 致谢 + 感谢Craig Hart发起的Nobelium项目 @@ -189,7 +191,6 @@ Notion是一个能让效率暴涨的生产力引擎,可以帮你书写文档
- ## 引用技术 - **框架**: [Next.js](https://nextjs.org) @@ -198,15 +199,14 @@ Notion是一个能让效率暴涨的生产力引擎,可以帮你书写文档 - **评论**: [Twikoo](https://github.com/imaegoo/twikoo), [Giscus](https://giscus.app/zh-CN), [Gitalk](https://gitalk.github.io), [Cusdis](https://cusdis.com), [Utterances](https://utteranc.es) - **图标**: [Fontawesome](https://fontawesome.com/v6/icons/) - ## 🔗 友情链接 + - [Elog](https://github.com/LetTTGACO/elog) Markdown 批量导出工具、开放式跨平台博客解决方案,随意组合写作平台(语雀/Notion/FlowUs/飞书)和博客平台(Hexo/Vitepress/Halo/Confluence/WordPress等) ## License The MIT License. - ## Star History [![Star History Chart](https://api.star-history.com/svg?repos=tangly1024/NotionNext&type=Date)](https://star-history.com/#tangly1024/NotionNext&Date) diff --git a/blog.config.js b/blog.config.js index db28a46a633..e919ebbbe37 100644 --- a/blog.config.js +++ b/blog.config.js @@ -548,7 +548,15 @@ const BLOG = { process.env.npm_lifecycle_event === 'export', // 在打包过程中默认开启缓存,开发或运行时开启此功能意义不大。 isProd: process.env.VERCEL_ENV === 'production' || process.env.EXPORT, // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) BUNDLE_ANALYZER: process.env.ANALYZE === 'true' || false, // 是否展示编译依赖内容与大小 - VERSION: process.env.NEXT_PUBLIC_VERSION // 版本号 + VERSION: (() => { + try { + // 优先使用环境变量,否则从package.json中获取版本号 + return process.env.NEXT_PUBLIC_VERSION || require('./package.json').version + } catch (error) { + console.warn('Failed to load package.json version:', error) + return '1.0.0' // 缺省版本号 + } + })() } module.exports = BLOG diff --git a/components/WordCount.js b/components/WordCount.js index 315c58d39a7..7f7066cf1d6 100644 --- a/components/WordCount.js +++ b/components/WordCount.js @@ -28,7 +28,7 @@ export default function WordCount() { * 更新字数统计和阅读时间 */ function countWords() { - const articleText = deleteHtmlTag(document.getElementById('notion-article')?.innerHTML) + const articleText = deleteHtmlTag(document.querySelector('#article-wrapper #notion-article')?.innerHTML) const wordCount = fnGetCpmisWords(articleText) // 阅读速度 300-500每分钟 document.getElementById('wordCount').innerHTML = wordCount diff --git a/lib/db/getSiteData.js b/lib/db/getSiteData.js index 4d615824af2..302efccf362 100755 --- a/lib/db/getSiteData.js +++ b/lib/db/getSiteData.js @@ -1,5 +1,4 @@ import BLOG from '@/blog.config' -import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager' import { getAllCategories } from '@/lib/notion/getAllCategories' import getAllPageIds from '@/lib/notion/getAllPageIds' import { getAllTags } from '@/lib/notion/getAllTags' @@ -42,7 +41,7 @@ export async function getGlobalData({ const prefix = extractLangPrefix(siteId) // 第一个id站点默认语言 if (index === 0 || locale === prefix) { - data = await getNotionPageData({ + data = await getSiteDataByPageId({ pageId: id, from }) @@ -60,24 +59,241 @@ export async function getGlobalData({ * @param from 请求来源 * @returns {Promise} */ -export async function getNotionPageData({ pageId, from }) { - // 尝试从缓存获取 - const cacheKey = 'page_block_' + pageId - let data = await getDataFromCache(cacheKey) - if (data && data.pageIds?.length > 0) { - console.debug('[API<<--缓存]', `from:${from}`, `root-page-id:${pageId}`) - return data +export async function getSiteDataByPageId({ pageId, from }) { + // 获取NOTION原始数据,此接支持mem缓存。 + const pageRecordMap = await getPage(pageId, from) + // 将Notion数据按规则转成站点数据 + const data = await converNotionToSiteDate(pageId, from, deepClone(pageRecordMap)) + return data +} + +/** + * 获取公告 + */ +async function getNotice(post) { + if (!post) { + return null + } + + post.blockMap = await getPage(post.id, 'data-notice') + return post +} + +/** + * 空的默认数据 + * @param {*} pageId + * @returns + */ +const EmptyData = pageId => { + const empty = { + notice: null, + siteInfo: getSiteInfo({}), + allPages: [ + { + id: 1, + title: `无法获取Notion数据,请检查Notion_ID: \n 当前 ${pageId}`, + summary: + '访问文档获取帮助→ https://tangly1024.com/article/vercel-deploy-notion-next', + status: 'Published', + type: 'Post', + slug: '13a171332816461db29d50e9f575b00d', + publishDay: '2024-11-13', + pageCoverThumbnail: BLOG.HOME_BANNER_IMAGE, + date: { + start_date: '2023-04-24', + lastEditedDay: '2023-04-24', + tagItems: [] + } + } + ], + allNavPages: [], + collection: [], + collectionQuery: {}, + collectionId: null, + collectionView: {}, + viewIds: [], + block: {}, + schema: {}, + tagOptions: [], + categoryOptions: [], + rawMetadata: {}, + customNav: [], + customMenu: [], + postCount: 1, + pageIds: [], + latestPosts: [] + } + return empty +} + +/** + * 将Notion数据转站点数据 + * 这里统一对数据格式化 + * @returns {Promise} + */ +async function converNotionToSiteDate(pageId, from, pageRecordMap) { + if (!pageRecordMap) { + console.error('can`t get Notion Data ; Which id is: ', pageId) + return {} + } + pageId = idToUuid(pageId) + let block = pageRecordMap.block || {} + const rawMetadata = block[pageId]?.value + // 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) + } + const collection = Object.values(pageRecordMap.collection)[0]?.value || {} + const collectionId = rawMetadata?.collection_id + const collectionQuery = pageRecordMap.collection_query + const collectionView = pageRecordMap.collection_view + const schema = collection?.schema + + const viewIds = rawMetadata?.view_ids + const collectionData = [] + + const pageIds = getAllPageIds( + collectionQuery, + collectionId, + collectionView, + viewIds + ) + + if (pageIds?.length === 0) { + console.error( + '获取到的文章列表为空,请检查notion模板', + collectionQuery, + collection, + collectionView, + viewIds, + pageRecordMap + ) } else { - // 从接口读取 - data = await getDataBaseInfoByNotionAPI({ pageId, from }) - // 存入缓存 - if (data) { - await setDataToCache(cacheKey, data) + // console.log('有效Page数量', pageIds?.length) + } + + // 抓取主数据库最多抓取1000个blocks,溢出的数block这里统一抓取一遍 + const blockIdsNeedFetch = [] + for (let i = 0; i < pageIds.length; i++) { + const id = pageIds[i] + const value = block[id]?.value + if (!value) { + blockIdsNeedFetch.push(id) } } + const fetchedBlocks = await fetchInBatches(blockIdsNeedFetch) + block = Object.assign({}, block, fetchedBlocks) - // 返回给前端的数据做处理 - return data + // 获取每篇文章基础数据 + for (let i = 0; i < pageIds.length; i++) { + const id = pageIds[i] + const value = block[id]?.value || fetchedBlocks[id]?.value + const properties = + (await getPageProperties( + id, + value, + schema, + null, + getTagOptions(schema) + )) || null + + if (properties) { + collectionData.push(properties) + } + } + + // 站点配置优先读取配置表格,否则读取blog.config.js 文件 + const NOTION_CONFIG = (await getConfigMapFromConfigPage(collectionData)) || {} + + // 处理每一条数据的字段 + collectionData.forEach(function (element) { + adjustPageProperties(element, NOTION_CONFIG) + }) + + // 站点基础信息 + const siteInfo = getSiteInfo({ collection, block, pageId }) + + // 文章计数 + let postCount = 0 + + // 查找所有的Post和Page + const allPages = collectionData.filter(post => { + if (post?.type === 'Post' && post.status === 'Published') { + postCount++ + } + return ( + post && + post?.slug && + // !post?.slug?.startsWith('http') && + (post?.status === 'Invisible' || post?.status === 'Published') + ) + }) + + // Sort by date + if (siteConfig('POSTS_SORT_BY', '', NOTION_CONFIG) === 'date') { + allPages.sort((a, b) => { + return b?.publishDate - a?.publishDate + }) + } + + const notice = await getNotice( + collectionData.filter(post => { + return ( + post && + post?.type && + post?.type === 'Notice' && + post.status === 'Published' + ) + })?.[0] + ) + // 所有分类 + const categoryOptions = getAllCategories({ + allPages, + categoryOptions: getCategoryOptions(schema) + }) + // 所有标签 + const tagOptions = getAllTags({ + allPages, + tagOptions: getTagOptions(schema), + NOTION_CONFIG + }) + // 旧的菜单 + const customNav = getCustomNav({ + allPages: collectionData.filter( + post => post?.type === 'Page' && post.status === 'Published' + ) + }) + // 新的菜单 + const customMenu = await getCustomMenu({ collectionData, NOTION_CONFIG }) + const latestPosts = getLatestPosts({ allPages, from, latestPostCount: 6 }) + const allNavPages = getNavPages({ allPages }) + + return { + NOTION_CONFIG, + notice, + siteInfo, + allPages, + allNavPages, + collection, + collectionQuery, + collectionId, + collectionView, + viewIds, + block, + schema, + tagOptions, + categoryOptions, + rawMetadata, + customNav, + customMenu, + postCount, + pageIds, + latestPosts + } } /** @@ -404,228 +620,3 @@ export function getNavPages({ allPages }) { ext: item.ext || {} })) } - -/** - * 获取公告 - */ -async function getNotice(post) { - if (!post) { - return null - } - - post.blockMap = await getPage(post.id, 'data-notice') - return post -} - -// 没有数据时返回 -const EmptyData = pageId => { - const empty = { - notice: null, - siteInfo: getSiteInfo({}), - allPages: [ - { - id: 1, - title: `无法获取Notion数据,请检查Notion_ID: \n 当前 ${pageId}`, - summary: - '访问文档获取帮助→ https://tangly1024.com/article/vercel-deploy-notion-next', - status: 'Published', - type: 'Post', - slug: '13a171332816461db29d50e9f575b00d', - pageCoverThumbnail: BLOG.HOME_BANNER_IMAGE, - date: { - start_date: '2023-04-24', - lastEditedDay: '2023-04-24', - tagItems: [] - } - } - ], - allNavPages: [], - collection: [], - collectionQuery: {}, - collectionId: null, - collectionView: {}, - viewIds: [], - block: {}, - schema: {}, - tagOptions: [], - categoryOptions: [], - rawMetadata: {}, - customNav: [], - customMenu: [], - postCount: 1, - pageIds: [], - latestPosts: [] - } - return empty -} - -/** - * 调用NotionAPI获取Page数据 - * @returns {Promise} - */ -async function getDataBaseInfoByNotionAPI({ pageId, from }) { - console.log('[Fetching Data]', pageId, from) - const pageRecordMap = await getPage(pageId, from) - if (!pageRecordMap) { - console.error('can`t get Notion Data ; Which id is: ', pageId) - return {} - } - pageId = idToUuid(pageId) - let block = pageRecordMap.block || {} - const rawMetadata = block[pageId]?.value - // 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) - } - const collection = Object.values(pageRecordMap.collection)[0]?.value || {} - const collectionId = rawMetadata?.collection_id - const collectionQuery = pageRecordMap.collection_query - const collectionView = pageRecordMap.collection_view - const schema = collection?.schema - - const viewIds = rawMetadata?.view_ids - const collectionData = [] - - const pageIds = getAllPageIds( - collectionQuery, - collectionId, - collectionView, - viewIds - ) - - if (pageIds?.length === 0) { - console.error( - '获取到的文章列表为空,请检查notion模板', - collectionQuery, - collection, - collectionView, - viewIds, - pageRecordMap - ) - } else { - // console.log('有效Page数量', pageIds?.length) - } - - // 抓取主数据库最多抓取1000个blocks,溢出的数block这里统一抓取一遍 - const blockIdsNeedFetch = [] - for (let i = 0; i < pageIds.length; i++) { - const id = pageIds[i] - const value = block[id]?.value - if (!value) { - blockIdsNeedFetch.push(id) - } - } - const fetchedBlocks = await fetchInBatches(blockIdsNeedFetch) - block = Object.assign({}, block, fetchedBlocks) - - // 获取每篇文章基础数据 - for (let i = 0; i < pageIds.length; i++) { - const id = pageIds[i] - const value = block[id]?.value || fetchedBlocks[id]?.value - const properties = - (await getPageProperties( - id, - value, - schema, - null, - getTagOptions(schema) - )) || null - - if (properties) { - collectionData.push(properties) - } - } - - // 站点配置优先读取配置表格,否则读取blog.config.js 文件 - const NOTION_CONFIG = (await getConfigMapFromConfigPage(collectionData)) || {} - - // 处理每一条数据的字段 - collectionData.forEach(function (element) { - adjustPageProperties(element, NOTION_CONFIG) - }) - - // 站点基础信息 - const siteInfo = getSiteInfo({ collection, block, pageId }) - - // 文章计数 - let postCount = 0 - - // 查找所有的Post和Page - const allPages = collectionData.filter(post => { - if (post?.type === 'Post' && post.status === 'Published') { - postCount++ - } - return ( - post && - post?.slug && - // !post?.slug?.startsWith('http') && - (post?.status === 'Invisible' || post?.status === 'Published') - ) - }) - - // Sort by date - if (siteConfig('POSTS_SORT_BY', '', NOTION_CONFIG) === 'date') { - allPages.sort((a, b) => { - return b?.publishDate - a?.publishDate - }) - } - - const notice = await getNotice( - collectionData.filter(post => { - return ( - post && - post?.type && - post?.type === 'Notice' && - post.status === 'Published' - ) - })?.[0] - ) - // 所有分类 - const categoryOptions = getAllCategories({ - allPages, - categoryOptions: getCategoryOptions(schema) - }) - // 所有标签 - const tagOptions = getAllTags({ - allPages, - tagOptions: getTagOptions(schema), - NOTION_CONFIG - }) - // 旧的菜单 - const customNav = getCustomNav({ - allPages: collectionData.filter( - post => post?.type === 'Page' && post.status === 'Published' - ) - }) - // 新的菜单 - const customMenu = await getCustomMenu({ collectionData, NOTION_CONFIG }) - const latestPosts = getLatestPosts({ allPages, from, latestPostCount: 6 }) - const allNavPages = getNavPages({ allPages }) - - return { - NOTION_CONFIG, - notice, - siteInfo, - allPages, - allNavPages, - collection, - collectionQuery, - collectionId, - collectionView, - viewIds, - block, - schema, - tagOptions, - categoryOptions, - rawMetadata, - customNav, - customMenu, - postCount, - pageIds, - latestPosts - } -} diff --git a/lib/notion/getNotionConfig.js b/lib/notion/getNotionConfig.js index c0ef7c29a5f..ae98736975c 100644 --- a/lib/notion/getNotionConfig.js +++ b/lib/notion/getNotionConfig.js @@ -157,7 +157,8 @@ export async function getConfigMapFromConfigPage(allPages) { // 只导入生效的配置 if (config.enable) { // console.log('[Notion配置]', config.key, config.value) - notionConfig[config.key] = config.value + notionConfig[config.key] = config.value || '' + // 配置不能是undefined,至少是空字符串 } } } diff --git a/lib/notion/getPageProperties.js b/lib/notion/getPageProperties.js index d62568cd2c2..44f050c54b4 100644 --- a/lib/notion/getPageProperties.js +++ b/lib/notion/getPageProperties.js @@ -180,9 +180,7 @@ export function adjustPageProperties(properties, NOTION_CONFIG) { // 1.按照用户配置的URL_PREFIX 转换一下slug // 2.为文章添加一个href字段,存储最终调整的路径 if (properties.type === 'Post') { - if (siteConfig('POST_URL_PREFIX', '', NOTION_CONFIG)) { - properties.slug = generateCustomizeSlug(properties, NOTION_CONFIG) - } + properties.slug = generateCustomizeSlug(properties, NOTION_CONFIG) properties.href = properties.slug ?? properties.id } else if (properties.type === 'Page') { properties.href = properties.slug ?? properties.id @@ -245,12 +243,14 @@ function generateCustomizeSlug(postProperties, NOTION_CONFIG) { return postProperties.slug } let fullPrefix = '' - const allSlugPatterns = siteConfig( - 'POST_URL_PREFIX', - '', - NOTION_CONFIG - ).split('/') - + let allSlugPatterns = NOTION_CONFIG?.POST_URL_PREFIX + if (allSlugPatterns === undefined) { + allSlugPatterns = siteConfig('POST_URL_PREFIX', '', NOTION_CONFIG).split( + '/' + ) + } else { + allSlugPatterns = allSlugPatterns.split('/') + } const POST_URL_PREFIX_MAPPING_CATEGORY = siteConfig( 'POST_URL_PREFIX_MAPPING_CATEGORY', {}, diff --git a/lib/notion/getPostBlocks.js b/lib/notion/getPostBlocks.js index 2859345f72c..1f8733f56d3 100644 --- a/lib/notion/getPostBlocks.js +++ b/lib/notion/getPostBlocks.js @@ -10,19 +10,20 @@ import { deepClone, delay } from '../utils' * @param {*} slice * @returns */ -export async function getPage(id, from, slice) { - const cacheKey = 'page_block_' + id +export async function getPage(id, from = null, slice) { + const cacheKey = `page_block_${id}` let pageBlock = await getDataFromCache(cacheKey) if (pageBlock) { - // console.log('[API<<--缓存]', `from:${from}`, cacheKey) - return filterPostBlocks(id, pageBlock, slice) + // console.debug('[API<<--缓存]', `from:${from}`, cacheKey) + return convertNotionBlocksToPost(id, pageBlock, slice) } + // 抓取最新数据 pageBlock = await getPageWithRetry(id, from) if (pageBlock) { await setDataToCache(cacheKey, pageBlock) - return filterPostBlocks(id, pageBlock, slice) + return convertNotionBlocksToPost(id, pageBlock, slice) } return pageBlock } @@ -69,7 +70,7 @@ export async function getPageWithRetry(id, from, retryAttempts = 3) { } /** - * 获取到的页面BLOCK特殊处理 + * Notion页面BLOCK格式化处理 * 1.删除冗余字段 * 2.比如文件、视频、音频、url格式化 * 3.代码块等元素兼容 @@ -78,72 +79,72 @@ export async function getPageWithRetry(id, from, retryAttempts = 3) { * @param {*} slice 截取数量 * @returns */ -function filterPostBlocks(id, blockMap, slice) { +function convertNotionBlocksToPost(id, blockMap, slice) { const clonePageBlock = deepClone(blockMap) let count = 0 const blocksToProcess = Object.keys(clonePageBlock?.block || {}) // 循环遍历文档的每个block for (let i = 0; i < blocksToProcess.length; i++) { - const blockId = blocksToProcess[i] - const b = clonePageBlock?.block[blockId] - - if (slice && slice > 0 && count > slice) { - delete clonePageBlock?.block[blockId] - continue - } - - // 当BlockId等于PageId时移除 - if (b?.value?.id === id) { - // 此block含有敏感信息 - delete b?.value?.properties - continue - } - - count++ - - if (b?.value?.type === 'sync_block' && b?.value?.children) { - const childBlocks = b.value.children - // 移除同步块 - delete clonePageBlock.block[blockId] - // 用子块替代同步块 - childBlocks.forEach((childBlock, index) => { - const newBlockId = `${blockId}_child_${index}` - clonePageBlock.block[newBlockId] = childBlock - blocksToProcess.splice(i + index + 1, 0, newBlockId) - }) - // 重新处理新加入的子块 - i-- - continue - } - - // 处理 c++、c#、汇编等语言名字映射 - if (b?.value?.type === 'code') { - if (b?.value?.properties?.language?.[0][0] === 'C++') { - b.value.properties.language[0][0] = 'cpp' + const blockId = blocksToProcess[i] + const b = clonePageBlock?.block[blockId] + + if (slice && slice > 0 && count > slice) { + delete clonePageBlock?.block[blockId] + continue } - if (b?.value?.properties?.language?.[0][0] === 'C#') { - b.value.properties.language[0][0] = 'csharp' + + // 当BlockId等于PageId时移除 + if (b?.value?.id === id) { + // 此block含有敏感信息 + delete b?.value?.properties + continue } - if (b?.value?.properties?.language?.[0][0] === 'Assembly') { - b.value.properties.language[0][0] = 'asm6502' + + count++ + + if (b?.value?.type === 'sync_block' && b?.value?.children) { + const childBlocks = b.value.children + // 移除同步块 + delete clonePageBlock.block[blockId] + // 用子块替代同步块 + childBlocks.forEach((childBlock, index) => { + const newBlockId = `${blockId}_child_${index}` + clonePageBlock.block[newBlockId] = childBlock + blocksToProcess.splice(i + index + 1, 0, newBlockId) + }) + // 重新处理新加入的子块 + i-- + continue + } + + // 处理 c++、c#、汇编等语言名字映射 + if (b?.value?.type === 'code') { + if (b?.value?.properties?.language?.[0][0] === 'C++') { + b.value.properties.language[0][0] = 'cpp' + } + if (b?.value?.properties?.language?.[0][0] === 'C#') { + b.value.properties.language[0][0] = 'csharp' + } + if (b?.value?.properties?.language?.[0][0] === 'Assembly') { + b.value.properties.language[0][0] = 'asm6502' + } + } + + // 如果是文件,或嵌入式PDF,需要重新加密签名 + 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 } } - - // 如果是文件,或嵌入式PDF,需要重新加密签名 - 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 - } -} // 去掉不用的字段 if (id === BLOG.NOTION_PAGE_ID) { diff --git a/lib/sitemap.xml.js b/lib/sitemap.xml.js index c520c0ff094..55b85b9baef 100644 --- a/lib/sitemap.xml.js +++ b/lib/sitemap.xml.js @@ -1,29 +1,34 @@ - -import fs from 'fs' import BLOG from '@/blog.config' +import fs from 'fs' export async function generateSitemapXml({ allPages }) { - const urls = [{ - loc: `${BLOG.LINK}`, - lastmod: new Date().toISOString().split('T')[0], - changefreq: 'daily' - }, { - loc: `${BLOG.LINK}/archive`, - lastmod: new Date().toISOString().split('T')[0], - changefreq: 'daily' - }, { - loc: `${BLOG.LINK}/category`, - lastmod: new Date().toISOString().split('T')[0], - changefreq: 'daily' - }, { - loc: `${BLOG.LINK}/tag`, - lastmod: new Date().toISOString().split('T')[0], - changefreq: 'daily' - }] - + const urls = [ + { + loc: `${BLOG.LINK}`, + lastmod: new Date().toISOString().split('T')[0], + changefreq: 'daily' + }, + { + loc: `${BLOG.LINK}/archive`, + lastmod: new Date().toISOString().split('T')[0], + changefreq: 'daily' + }, + { + loc: `${BLOG.LINK}/category`, + lastmod: new Date().toISOString().split('T')[0], + changefreq: 'daily' + }, + { + loc: `${BLOG.LINK}/tag`, + lastmod: new Date().toISOString().split('T')[0], + changefreq: 'daily' + } + ] // 循环页面生成 allPages?.forEach(post => { - const slugWithoutLeadingSlash = post?.slug?.startsWith('/') ? post?.slug?.slice(1) : post.slug + const slugWithoutLeadingSlash = post?.slug?.startsWith('/') + ? post?.slug?.slice(1) + : post.slug urls.push({ loc: `${BLOG.LINK}/${slugWithoutLeadingSlash}`, lastmod: new Date(post?.publishDay).toISOString().split('T')[0], diff --git a/next.config.js b/next.config.js index 5637feb5b4e..00d086651aa 100644 --- a/next.config.js +++ b/next.config.js @@ -228,4 +228,6 @@ const nextConfig = { } } -module.exports = withBundleAnalyzer(nextConfig) +module.exports = process.env.ANALYZE + ? withBundleAnalyzer(nextConfig) + : nextConfig diff --git a/package.json b/package.json index dff2f46a9f5..35d90ad9bfd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "notion-next", - "version": "4.7.7", + "version": "4.7.8", "homepage": "https://github.com/tangly1024/NotionNext.git", "license": "MIT", "repository": { @@ -19,7 +19,8 @@ "post-build": "next-sitemap --config next-sitemap.config.js", "export": "cross-env EXPORT=true next build && next-sitemap --config next-sitemap.config.js", "bundle-report": "cross-env ANALYZE=true next build", - "build-all-in-dev": "cross-env VERCEL_ENV=production next build" + "build-all-in-dev": "cross-env VERCEL_ENV=production next build", + "version": "echo $npm_package_version" }, "dependencies": { "@clerk/localizations": "^3.0.4", @@ -51,14 +52,12 @@ "@waline/client": "^2.5.1", "autoprefixer": "^10.4.13", "cross-env": "^7.0.3", - "eslint": "^9.6.0", + "eslint": "^8.57.1", "eslint-config-next": "^13.1.1", "eslint-config-prettier": "^9.1.0", - "eslint-config-standard": "^16.0.2", "eslint-plugin-import": "^2.23.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-promise": "^5.1.0", "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.2", "next-sitemap": "^1.6.203", diff --git a/pages/[prefix]/index.js b/pages/[prefix]/index.js index 3e0b22fe9d7..6b05f7f1655 100644 --- a/pages/[prefix]/index.js +++ b/pages/[prefix]/index.js @@ -82,7 +82,7 @@ const Slug = props => { } }, [router, lock]) - props = { ...props, lock, setLock, validPassword } + props = { ...props, lock, validPassword } // 根据页面路径加载不同Layout文件 const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), diff --git a/themes/commerce/index.js b/themes/commerce/index.js index 7ced2e85726..9efbd684c3e 100644 --- a/themes/commerce/index.js +++ b/themes/commerce/index.js @@ -246,7 +246,7 @@ const LayoutSlug = props => {
{lock && } - {!lock && ( + {!lock && post && (
@@ -304,7 +304,7 @@ const Layout404 = props => { // 延时3秒如果加载失败就返回首页 setTimeout(() => { if (isBrowser) { - const article = document.getElementById('notion-article') + const article = document.querySelector('#article-wrapper #notion-article') if (!article) { router.push('/').then(() => { // console.log('找不到页面', router.asPath) diff --git a/themes/example/index.js b/themes/example/index.js index 60b896c1f4f..d78e20a4561 100644 --- a/themes/example/index.js +++ b/themes/example/index.js @@ -162,7 +162,7 @@ const LayoutSlug = props => { setTimeout( () => { if (isBrowser) { - const article = document.getElementById('notion-article') + const article = document.querySelector('#article-wrapper #notion-article') if (!article) { router.push('/404').then(() => { console.warn('找不到页面', router.asPath) @@ -178,7 +178,7 @@ const LayoutSlug = props => { <> {lock ? ( - ) : ( + ) : post && (
diff --git a/themes/fukasawa/index.js b/themes/fukasawa/index.js index 96936df8229..0e2cf9d3623 100644 --- a/themes/fukasawa/index.js +++ b/themes/fukasawa/index.js @@ -143,7 +143,7 @@ const LayoutSlug = props => { setTimeout( () => { if (isBrowser) { - const article = document.getElementById('notion-article') + const article = document.querySelector('#article-wrapper #notion-article') if (!article) { router.push('/404').then(() => { console.warn('找不到页面', router.asPath) @@ -159,7 +159,7 @@ const LayoutSlug = props => { <> {lock ? ( - ) : ( + ) : post && ( )} diff --git a/themes/game/index.js b/themes/game/index.js index 718f6a92a8c..04bc192ccf5 100644 --- a/themes/game/index.js +++ b/themes/game/index.js @@ -307,7 +307,7 @@ const LayoutSlug = props => { <> {lock && } - {!lock && ( + {!lock && post && (
diff --git a/themes/gitbook/index.js b/themes/gitbook/index.js index 9898f0aeeb9..0fac89b5f9b 100644 --- a/themes/gitbook/index.js +++ b/themes/gitbook/index.js @@ -255,7 +255,7 @@ const LayoutIndex = props => { // 重定向到指定文章 router.push(index).then(() => { setTimeout(() => { - const article = document.getElementById('notion-article') + const article = document.querySelector('#article-wrapper #notion-article') if (!article) { console.log( '请检查您的Notion数据库中是否包含此slug页面: ', @@ -309,7 +309,7 @@ const LayoutSlug = props => { setTimeout( () => { if (isBrowser) { - const article = document.getElementById('notion-article') + const article = document.querySelector('#article-wrapper #notion-article') if (!article) { router.push('/404').then(() => { console.warn('找不到页面', router.asPath) diff --git a/themes/heo/index.js b/themes/heo/index.js index 9bab5daffc8..aafe116e32b 100644 --- a/themes/heo/index.js +++ b/themes/heo/index.js @@ -273,7 +273,7 @@ const LayoutSlug = props => { setTimeout( () => { if (isBrowser) { - const article = document.getElementById('notion-article') + const article = document.querySelector('#article-wrapper #notion-article') if (!article) { router.push('/404').then(() => { console.warn('找不到页面', router.asPath) @@ -292,7 +292,7 @@ const LayoutSlug = props => { {/* 文章锁 */} {lock && } - {!lock && ( + {!lock && post && (
{/* 文章主体 */}
{