diff --git a/blog.config.js b/blog.config.js index c4b4c886..ba52c635 100644 --- a/blog.config.js +++ b/blog.config.js @@ -7,6 +7,7 @@ const BLOG = { lang: 'zh-CN', // ['zh-CN','en-US'] notionPageId: process.env.NOTION_PAGE_ID || 'bee1fccfa3bd47a1a7be83cc71372d83', // Important page_id!!! notionAccessToken: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public + defaultImgCover: 'https://avatars.githubusercontent.com/u/15920488', // default image cover appearance: 'auto', // ['light', 'dark', 'auto'], font: 'font-serif', // ['font-sans', 'font-serif', 'font-mono'] lightBackground: '#ffffff', // use hex value, don't forget '#' e.g #fffefc @@ -58,7 +59,7 @@ const BLOG = { } }, isProd: process.env.VERCEL_ENV === 'production', // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) - googleAdsenseId: 'ca-pub-2708419466378217', //谷歌广告ID + googleAdsenseId: 'ca-pub-2708419466378217', // 谷歌广告ID DaoVoiceId: '', // 在线聊天 DaoVoice http://dashboard.daovoice.io/get-started TidioId: '8lgekxyr9hr9m7archbs0a4a2gu9ckbg' // 在线聊天 https://www.tidio.com/ } diff --git a/components/BlogPostCard.js b/components/BlogPostCard.js index b30cb5db..ae49f8d0 100644 --- a/components/BlogPostCard.js +++ b/components/BlogPostCard.js @@ -3,7 +3,7 @@ import TagItemMini from '@/components/TagItemMini' import Link from 'next/link' import React from 'react' -const BlogPostCard = ({ post }) => { +const BlogPostCard = ({ post, tags }) => { return (
@@ -25,12 +25,14 @@ const BlogPostCard = ({ post }) => {
-
{post.category}
+
+ {post.category}
{post.date.start_date}
-
{post.tags.map(tag => ())}
+
{post.tagItems.map(tag => ())}
diff --git a/components/BlogPostListScroll.js b/components/BlogPostListScroll.js index 5ff9f721..8d0d76c4 100644 --- a/components/BlogPostListScroll.js +++ b/components/BlogPostListScroll.js @@ -56,7 +56,7 @@ const BlogPostListScroll = ({ posts = [], tags, currentSearch, currentCategory, {/* 文章列表 */}
{postsToShow.map(post => ( - + ))}
diff --git a/components/TagGroups.js b/components/TagGroups.js index ceed3ccb..45ea3c33 100644 --- a/components/TagGroups.js +++ b/components/TagGroups.js @@ -14,7 +14,7 @@ const TagGroups = ({ tags, currentTag }) => { { tags.map(tag => { const selected = tag.name === currentTag - return + return }) } diff --git a/components/TagItemMini.js b/components/TagItemMini.js index 7fdc6abf..eb6e37e4 100644 --- a/components/TagItemMini.js +++ b/components/TagItemMini.js @@ -1,13 +1,13 @@ import Link from 'next/link' -const TagItemMini = ({ tag, selected = false, count }) => { - return +const TagItemMini = ({ tag, selected = false }) => { + return
-
{tag + (count ? `(${count})` : '')}
+ ? 'text-white bg-black dark:bg-black dark:border-gray-600 dark:hover:bg-gray-900 border-gray-800' + : `text-gray-500 hover:shadow-xl hover:text-white border-gray-500 dark:hover:bg-gray-600 dark:border-gray-600 bg-${tag.color}-50 bg-gray-50 dark:bg-${tag.color}-700 dark:bg-gray-600 `}` }> +
{tag.name + (tag.count ? `(${tag.count})` : '')}
} diff --git a/layouts/BaseLayout.js b/layouts/BaseLayout.js index addb3269..814f311b 100644 --- a/layouts/BaseLayout.js +++ b/layouts/BaseLayout.js @@ -44,7 +44,6 @@ const BaseLayout = ({ let windowTop = 0 const scrollTrigger = useCallback(throttle(() => { const scrollS = window.scrollY - console.log(scrollS) if (scrollS >= windowTop && scrollS > 10) { handleScrollDown() windowTop = scrollS diff --git a/lib/cache/cache_manager.js b/lib/cache/cache_manager.js index 34b5fff7..b28c2c0f 100644 --- a/lib/cache/cache_manager.js +++ b/lib/cache/cache_manager.js @@ -1,12 +1,18 @@ import { getCacheFromFile, setCacheToFile } from '@/lib/cache/local_file_cache' import { getCacheFromMemory, setCacheToMemory } from '@/lib/cache/memory_cache' import BLOG from '@/blog.config' +// 关闭本地缓存 +const enableCache = true + /** * 为减少频繁接口请求,notion数据将被缓存 - * @param {*} key - * @returns + * @param {*} key + * @returns */ export async function getDataFromCache (key) { + if (!enableCache) { + return null + } let dataFromCache if (BLOG.isProd) { dataFromCache = await getCacheFromMemory(key) @@ -17,6 +23,9 @@ export async function getDataFromCache (key) { } export async function setDataToCache (key, data) { + if (!enableCache) { + return + } if (BLOG.isProd) { await setCacheToMemory(key, data) } else { diff --git a/lib/notion/getAllPosts.js b/lib/notion/getAllPosts.js index 02aaab2a..becd9ca3 100644 --- a/lib/notion/getAllPosts.js +++ b/lib/notion/getAllPosts.js @@ -1,73 +1,62 @@ import BLOG from '@/blog.config' -import { idToUuid } from 'notion-utils' import getAllPageIds from './getAllPageIds' import getPageProperties from './getPageProperties' import { defaultMapImageUrl } from 'react-notion-x' -import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager' -import { getPostBlocks } from '@/lib/notion/getPostBlocks' - -export async function getAllPosts ({ from }) { - // 尝试从缓存获取 - const data = await getDataFromCache('posts_list') - if (data) return data - const posts = await getPostsFromNotionAPI({ from }) - // 存入缓存 - if (posts) { - await setDataToCache('posts_list', posts) - } - return posts || [] -} +import { getNotionPageData } from '@/lib/notion/getNotionData' +import TagItemMini from '@/components/TagItemMini' +import React from 'react' /** - * 调用NotionAPI获取所有文章列表 - * @returns {Promise} + * 获取所有文章列表 + * @param notionPageData + * @param from + * @param includePage 是否包含Page类型 + * @returns {Promise<*[]>} */ -async function getPostsFromNotionAPI ({ from }) { - let id = BLOG.notionPageId - const pageRecordMap = await getPostBlocks(id, from) - if (!pageRecordMap) { +export async function getAllPosts ({ notionPageData, from, includePage = false }) { + if (!notionPageData) { + notionPageData = await getNotionPageData({ from }) + } + if (!notionPageData) { return [] } - id = idToUuid(id) - const collection = Object.values(pageRecordMap.collection)[0]?.value - const collectionQuery = pageRecordMap.collection_query - const block = pageRecordMap.block - const schema = collection?.schema - const rawMetadata = block[id].value - - // Check Type 兼容Page-Database和Inline-Database - if (rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view') { - console.warn(`pageId "${id}" is not a database`) - return null - } + const pageBlock = notionPageData.block + const schema = notionPageData.schema + const tagOptions = notionPageData.tagOptions + const collectionQuery = notionPageData.collectionQuery // 获取每篇文章信息 const data = [] const pageIds = getAllPageIds(collectionQuery) for (let i = 0; i < pageIds.length; i++) { const id = pageIds[i] - const properties = (await getPageProperties(id, block, schema)) || null - properties.createdTime = new Date( - block[id].value?.created_time - ).toString() - properties.lastEditedTime = new Date( - block[id].value?.last_edited_time - ).toString() - properties.fullWidth = block[id].value?.format?.page_full_width ?? false - properties.page_cover = getPostCover(id, block, pageRecordMap) ?? getContentFirstImage(id, block, pageRecordMap) - properties.content = block[id].value?.content ?? [] + const properties = (await getPageProperties(id, pageBlock, schema)) || null + const tagItems = properties?.tags?.map(tag => { return { name: tag, color: tagOptions.find(t => t.value === tag).color } }) || ['默认'] + properties.createdTime = new Date(pageBlock[id].value?.created_time).toString() + properties.lastEditedTime = new Date(pageBlock[id].value?.last_edited_time).toString() + properties.fullWidth = pageBlock[id].value?.format?.page_full_width ?? false + properties.page_cover = getPostCover(id, pageBlock) ?? BLOG.defaultImgCover + properties.content = pageBlock[id].value?.content ?? [] + properties.tagItems = tagItems data.push(properties) } // remove all the the items doesn't meet requirements const posts = data.filter(post => { - return ( - post.title && - post.slug && - post?.status?.[0] === 'Published' && - (post?.type?.[0] === 'Post' || post?.type?.[0] === 'Page') - ) + if (includePage) { + return ( + post.title && post.slug && + post?.status?.[0] === 'Published' && + (post?.type?.[0] === 'Post' || post?.type?.[0] === 'Page') + ) + } else { + return ( + post.title && post.slug && + post?.status?.[0] === 'Published' && + (post?.type?.[0] === 'Post') + ) + } }) // Sort by date @@ -82,30 +71,10 @@ async function getPostsFromNotionAPI ({ from }) { } // 从Block获取封面图;优先取PageCover,否则取内容图片 -function getPostCover (id, block, pageRecordMap) { +function getPostCover (id, block) { const pageCover = block[id].value?.format?.page_cover if (pageCover) { if (pageCover.startsWith('/')) return 'https://www.notion.so' + pageCover if (pageCover.startsWith('http')) return defaultMapImageUrl(pageCover, block[id].value) } } - -// 取文章的第一个图片内容作为封面 -function getContentFirstImage (id, block, pageRecordMap) { - const pageBlock = block[id]?.value - - const contentBlockId = pageBlock?.content?.find((blockId) => { - const block = pageRecordMap.block[blockId]?.value - if (block?.type === 'image') { - return true - } - }) - - if (contentBlockId) { - const contentBlock = pageRecordMap.block[contentBlockId]?.value - const source = contentBlock.properties?.source?.[0]?.[0] ?? - contentBlock.format?.display_source - return defaultMapImageUrl(source, contentBlock) - } - return '' -} diff --git a/lib/notion/getAllTags.js b/lib/notion/getAllTags.js index a3df72c4..aa0a6d9b 100644 --- a/lib/notion/getAllTags.js +++ b/lib/notion/getAllTags.js @@ -3,9 +3,10 @@ * 获取所有文章的标签 * @param allPosts * @param sliceCount 默认截取数量为12,若为0则返回全部 + * @param tagOptions tags的下拉选项 * @returns {Promise<{}|*[]>} */ -export async function getAllTags (allPosts, sliceCount = 12) { +export async function getAllTags ({ allPosts, sliceCount = 12, tagOptions }) { if (!allPosts) { return [] } @@ -24,8 +25,9 @@ export async function getAllTags (allPosts, sliceCount = 12) { }) // 按照标签数量排序 - const list = Object.keys(tagObj).map((index) => { - return { name: index, count: tagObj[index] } + const list = Object.keys(tagObj).map((tag) => { + const color = tagOptions.find(option => option.value === tag).color + return { name: tag, count: tagObj[tag], color } }) list.sort((a, b) => b.count - a.count) if (sliceCount) { diff --git a/lib/notion/getNotionData.js b/lib/notion/getNotionData.js new file mode 100644 index 00000000..a6cc5b0e --- /dev/null +++ b/lib/notion/getNotionData.js @@ -0,0 +1,66 @@ +import BLOG from '@/blog.config' +import { idToUuid } from 'notion-utils' +import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager' +import { getPostBlocks } from '@/lib/notion/getPostBlocks' + +/** + * 获取指定notion的collection数据 + * @param pageId + * @param from 请求来源 + * @returns {Promise} + */ +export async function getNotionPageData ({ pageId = BLOG.notionPageId, from }) { + // 尝试从缓存获取 + const data = await getDataFromCache('page_record_map_' + pageId) + if (data) return data + const pageRecordMap = await getPageRecordMapByNotionAPI({ pageId, from }) + // 存入缓存 + if (pageRecordMap) { + await setDataToCache('page_record_map', pageRecordMap) + } + return pageRecordMap +} + +/** + * 获取标签选项 + * @param schema + * @returns {undefined} + */ +function getTagOptions (schema) { + const tagSchema = Object.values(schema).find(e => e.name === 'tags') + return tagSchema?.options || {} +} + +/** + * 调用NotionAPI获取Page数据 + * @returns {Promise} + */ +async function getPageRecordMapByNotionAPI ({ pageId, from }) { + const pageRecordMap = await getPostBlocks(pageId, from) + if (!pageRecordMap) { + return [] + } + + pageId = idToUuid(pageId) + const collection = Object.values(pageRecordMap.collection)[0]?.value + const collectionQuery = pageRecordMap.collection_query + const block = pageRecordMap.block + const schema = collection?.schema + const rawMetadata = block[pageId].value + const tagOptions = getTagOptions(schema) + + // Check Type Page-Database和Inline-Database + if (rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view') { + console.warn(`pageId "${pageId}" is not a database`) + return null + } + + return { + collection, + collectionQuery, + block, + schema, + tagOptions, + rawMetadata + } +} diff --git a/lib/notion/getPostBlocks.js b/lib/notion/getPostBlocks.js index c9cbfa50..299d66d5 100644 --- a/lib/notion/getPostBlocks.js +++ b/lib/notion/getPostBlocks.js @@ -10,11 +10,11 @@ export async function getPostBlocks (id, from) { const authToken = BLOG.notionAccessToken || null const api = new NotionAPI({ authToken }) try { - console.log(id, '向Notion请求数据:', from) + console.log('[请求API]:', `from:${from}`, `id:${id}`) pageBlock = await api.getPage(id) - console.log(id, '请求成功:', from) + console.log('[请求成功]', `from:${from}`, `id:${id}`) } catch (error) { - console.error(id, '请求失败:', from, error) + console.error('[请求失败]', `from:${from}`, `id:${id}`, `error:${error}`) return null } diff --git a/package.json b/package.json index b487cab2..98e9ec54 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "scripts": { "dev": "next dev", + "dev-mac": "NODE_OPTIONS='--inspect' next dev", "build": "next build", "start": "next start", "postbuild": "next-sitemap --config next-sitemap.config.js" diff --git a/pages/article/[slug].js b/pages/article/[slug].js index 92881f5e..b0bf47ef 100644 --- a/pages/article/[slug].js +++ b/pages/article/[slug].js @@ -30,7 +30,7 @@ const mapPageUrl = id => { } const ArticleDetail = ({ post, blockMap, tags, prev, next, posts, categories }) => { if (!post) { - return + return } const meta = { title: `${post.title} | ${BLOG.title}`, @@ -42,7 +42,7 @@ const ArticleDetail = ({ post, blockMap, tags, prev, next, posts, categories }) const drawerRight = useRef(null) const url = BLOG.link + useRouter().asPath - return + return
@@ -51,12 +51,13 @@ const ArticleDetail = ({ post, blockMap, tags, prev, next, posts, categories }) className='hover:shadow-2xl hover:scale-105 transform duration-200 mx-auto max-w-5xl mt-16 mb-2 lg:mt-32 md:flex-shrink-0 animate__fadeIn animate__animated'> {/* 封面图 */} {post.page_cover && post.page_cover.length > 1 && ( - {post.title} )} -
+
{/* 文章标题 */}

{post.title} @@ -72,8 +73,9 @@ const ArticleDetail = ({ post, blockMap, tags, prev, next, posts, categories })
分类:
- -
{post.category}
+ +
{post.category}
{post.type[0] !== 'Page' && ( @@ -88,10 +90,9 @@ const ArticleDetail = ({ post, blockMap, tags, prev, next, posts, categories }) {/* 不蒜子 */}

@@ -113,7 +114,7 @@ const ArticleDetail = ({ post, blockMap, tags, prev, next, posts, categories }) - + {/* 版权声明 */}
标签:
- {post.tags.map(tag => ( - + {post.tagItems.map(tag => ( + ))} )} @@ -143,33 +144,45 @@ const ArticleDetail = ({ post, blockMap, tags, prev, next, posts, categories })
-
+
-
{prev.title}
-
{next.title}
+ +
{prev.title}
+ + +
{next.title}
+
{/* 评论互动 */} -
+
- { drawerRight.current.handleSwitchVisible() }} /> + { + drawerRight.current.handleSwitchVisible() + }} /> {/* 目录侧边栏 */} - +
} export async function getStaticPaths () { - let posts = await getAllPosts({ from: 'slug - paths' }) - posts = posts.filter(post => post.status[0] === 'Published') + let posts = [] + if (BLOG.isProd) { + posts = await getAllPosts({ from: 'slug - paths', includePage: true }) + posts = posts.filter(post => post.status[0] === 'Published') + } return { paths: posts.map(row => `${BLOG.path}/article/${row.slug}`), fallback: true diff --git a/pages/category/[category].js b/pages/category/[category].js index 86e7d77d..eec3196a 100644 --- a/pages/category/[category].js +++ b/pages/category/[category].js @@ -5,6 +5,7 @@ import BaseLayout from '@/layouts/BaseLayout' import BlogPostListScroll from '@/components/BlogPostListScroll' import React from 'react' import CategoryList from '@/components/CategoryList' +import { getNotionPageData } from '@/lib/notion/getNotionData' export default function Category ({ tags, posts, category, categories }) { const meta = { @@ -23,14 +24,14 @@ export default function Category ({ tags, posts, category, categories }) { } export async function getStaticProps ({ params }) { + const from = 'category-props' const category = params.category - let posts = await getAllPosts({ from: 'category-props' }) - posts = posts.filter( - post => post.status[0] === 'Published' && post.type[0] === 'Post' - ) - const tags = await getAllTags(posts) - const categories = await getAllCategories(posts) - const filteredPosts = posts.filter( + const notionPageData = await getNotionPageData({ from }) + const allPosts = await getAllPosts({ notionPageData, from }) + const categories = await getAllCategories(allPosts) + const tagOptions = notionPageData.tagOptions + const tags = await getAllTags({ allPosts, tagOptions }) + const filteredPosts = allPosts.filter( post => post && post.category && post.category.includes(category) ) return { @@ -45,16 +46,14 @@ export async function getStaticProps ({ params }) { } export async function getStaticPaths () { + let posts = [] + let categories = [] if (BLOG.isProd) { - const tags = await getAllTags() - return { - paths: Object.keys(tags).map(tag => ({ params: { tag } })), - fallback: true - } - } else { - return { - paths: [], - fallback: true - } + posts = await getAllPosts({ from: 'category-path' }) + categories = await getAllCategories(posts) + } + return { + paths: Object.keys(categories).map(category => ({ params: { category } })), + fallback: true } } diff --git a/pages/index.js b/pages/index.js index b1a7ac78..f889b5fe 100644 --- a/pages/index.js +++ b/pages/index.js @@ -2,14 +2,15 @@ import { getAllCategories, getAllPosts, getAllTags } from '@/lib/notion' import BLOG from '@/blog.config' import BaseLayout from '@/layouts/BaseLayout' import BlogPostListScroll from '@/components/BlogPostListScroll' +import { getNotionPageData } from '@/lib/notion/getNotionData' export async function getStaticProps () { - let posts = await getAllPosts({ from: 'index' }) - posts = posts.filter( - post => post.status[0] === 'Published' && post.type[0] === 'Post' - ) - const tags = await getAllTags(posts) - const categories = await getAllCategories(posts) + const from = 'index' + const notionPageData = await getNotionPageData({ from }) + const allPosts = await getAllPosts({ notionPageData, from }) + const categories = await getAllCategories(allPosts) + const tagOptions = notionPageData.tagOptions + const tags = await getAllTags({ allPosts, tagOptions }) const meta = { title: `${BLOG.title} | ${BLOG.description} `, description: BLOG.description, @@ -17,7 +18,7 @@ export async function getStaticProps () { } return { props: { - posts, + allPosts, tags, categories, meta @@ -26,11 +27,11 @@ export async function getStaticProps () { } } -const Index = ({ posts, tags, meta, categories }) => { +const Index = ({ allPosts, tags, meta, categories }) => { return ( - +
- +
) diff --git a/pages/tag/[tag].js b/pages/tag/[tag].js index 3d493f79..603891a6 100644 --- a/pages/tag/[tag].js +++ b/pages/tag/[tag].js @@ -4,6 +4,7 @@ import StickyBar from '@/components/StickyBar' import BaseLayout from '@/layouts/BaseLayout' import BlogPostListScroll from '@/components/BlogPostListScroll' import TagList from '@/components/TagList' +import { getNotionPageData } from '@/lib/notion/getNotionData' export default function Tag ({ tags, posts, currentTag, categories }) { const meta = { @@ -23,13 +24,13 @@ export default function Tag ({ tags, posts, currentTag, categories }) { export async function getStaticProps ({ params }) { const currentTag = params.tag - let posts = await getAllPosts({ from: 'tag-props' }) - posts = posts.filter( - post => post.status[0] === 'Published' && post.type[0] === 'Post' - ) - const tags = await getAllTags(posts) - const categories = await getAllCategories(posts) - const filteredPosts = posts.filter( + const from = 'tag-props' + const notionPageData = await getNotionPageData({ from }) + const allPosts = await getAllPosts({ notionPageData, from }) + const categories = await getAllCategories(allPosts) + const tagOptions = notionPageData.tagOptions + const tags = await getAllTags({ allPosts, tagOptions }) + const filteredPosts = allPosts.filter( post => post && post.tags && post.tags.includes(currentTag) ) return { @@ -44,16 +45,14 @@ export async function getStaticProps ({ params }) { } export async function getStaticPaths () { + let posts = [] + let tags = [] if (BLOG.isProd) { - const tags = await getAllTags() - return { - paths: Object.keys(tags).map(tag => ({ params: { tag } })), - fallback: true - } - } else { - return { - paths: [], - fallback: true - } + posts = await getAllPosts({ from: 'tag-props' }) + tags = await getAllTags(posts) + } + return { + paths: Object.keys(tags).map(tag => ({ params: { tag } })), + fallback: true } }