Merge pull request #218 from tangly1024/preview

优化notionData、微调example
This commit is contained in:
tangly1024
2022-04-29 15:36:41 +08:00
committed by GitHub
22 changed files with 190 additions and 167 deletions

View File

@@ -1,4 +1,3 @@
export { getAllPosts } from './notion/getAllPosts'
export { getAllTags } from './notion/getAllTags'
export { getPostBlocks } from './notion/getPostBlocks'
export { getAllCategories } from './notion/getAllCategories'

View File

@@ -1,40 +0,0 @@
import { isIterable } from '../utils'
/**
* 获取所有文章的分类
* @param allPosts
* @returns {Promise<{}|*[]>}
*/
export async function getAllCategories({ allPosts, categoryOptions, sliceCount = 0 }) {
if (!allPosts || !categoryOptions) {
return []
}
// 计数
let categories = allPosts.map(p => p.category)
categories = [...categories.flat()]
const categoryObj = {}
categories.forEach(category => {
if (category in categoryObj) {
categoryObj[category]++
} else {
categoryObj[category] = 1
}
})
const list = []
if (isIterable(categoryOptions)) {
for (const c of categoryOptions) {
const count = categoryObj[c.value]
if (count) {
list.push({ id: c.id, name: c.value, color: c.color, count })
}
}
}
// 按照数量排序
// list.sort((a, b) => b.count - a.count)
if (sliceCount && sliceCount > 0) {
return list.slice(0, sliceCount)
} else {
return list
}
}

View File

@@ -23,7 +23,7 @@ export async function getAllPosts({ notionPageData, from, pageType }) {
const pageBlock = notionPageData.block
const schema = notionPageData.schema
const tagOptions = notionPageData.tagOptions
const tagOptions = notionPageData.tags
const collectionQuery = notionPageData.collectionQuery
const data = []
@@ -73,19 +73,3 @@ function getPostCover(id, block) {
if (pageCover.startsWith('http')) return defaultMapImageUrl(pageCover, block[id].value)
}
}
/**
* 获取博文总数
* @param {*} param0
* @returns
*/
export async function getAllPostCount({ notionPageData, from }) {
if (!notionPageData) {
notionPageData = await getNotionPageData({ from })
}
if (!notionPageData) {
return []
}
const allPosts = await getAllPosts({ notionPageData, from, pageType: ['Post'] })
return allPosts?.length || 0
}

View File

@@ -7,7 +7,7 @@ import { isIterable } from '../utils'
* @param tagOptions tags的下拉选项
* @returns {Promise<{}|*[]>}
*/
export async function getAllTags({ allPosts, sliceCount = 0, tagOptions }) {
export function getAllTags({ allPosts, sliceCount = 0, tagOptions }) {
if (!allPosts || !tagOptions) {
return []
}

View File

@@ -3,9 +3,11 @@ import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
import { getPostBlocks } from '@/lib/notion/getPostBlocks'
import { idToUuid } from 'notion-utils'
import { defaultMapImageUrl } from 'react-notion-x'
import { getAllCategories } from './getAllCategories'
import { getAllPosts, getAllPostCount } from './getAllPosts'
import { deepClone, isIterable } from '../utils'
import getAllPageIds from './getAllPageIds'
import { getAllPosts } from './getAllPosts'
import { getAllTags } from './getAllTags'
import getPageProperties from './getPageProperties'
/**
* 获取博客数据
@@ -15,40 +17,29 @@ import { getAllTags } from './getAllTags'
* @param categoryCount
* @param tagsCount 截取标签数量
* @param pageType 过滤的文章类型,数组格式 ['Page','Post']
* @returns {
allPosts, 所有博客
latestPosts,
categories, 所有分类
postCount,
customNav, 自定义导航菜单
tags, 所有标签
siteInfo:{
title,
description,
pageCover
}
}
* @returns
*
*/
export async function getGlobalNotionData({
pageId = BLOG.NOTION_PAGE_ID,
from,
latestPostCount = 5,
categoryCount = BLOG.PREVIEW_CATEGORY_COUNT,
tagsCount = BLOG.PREVIEW_TAG_COUNT,
pageType = ['Post']
}) {
const notionPageData = await getNotionPageData({ pageId, from })
const siteInfo = await getBlogInfo({ notionPageData, from })
const tagOptions = notionPageData.tagOptions
const categoryOptions = notionPageData.categoryOptions
const customNav = await getCustomNav({ notionPageData })
// 深拷贝数据
const notionPageData = deepClone(await getNotionPageData({ pageId, from }))
const allPosts = await getAllPosts({ notionPageData, from, pageType })
const postCount = await getAllPostCount({ notionPageData, from })
const categories = await getAllCategories({ allPosts, categoryOptions, sliceCount: categoryCount })
const tags = await getAllTags({ allPosts, tagOptions, sliceCount: tagsCount })
const latestPosts = await getLatestPosts({ notionPageData, from, latestPostCount })
return { allPosts, latestPosts, categories, postCount, customNav, tags, siteInfo }
notionPageData.allPosts = allPosts
// 删除前端不需要的数据
delete notionPageData.block
delete notionPageData.collection
delete notionPageData.collectionQuery
delete notionPageData.schema
delete notionPageData.rawMetadata
delete notionPageData.pageIds
delete notionPageData.tagOptions
delete notionPageData.categoryOptions
return notionPageData
}
/**
@@ -56,8 +47,7 @@ export async function getGlobalNotionData({
* @param {*}} param0
* @returns
*/
async function getLatestPosts({ notionPageData, from, latestPostCount }) {
const allPosts = await getAllPosts({ notionPageData, from, pageType: ['Post'] })
function getLatestPosts({ allPosts, from, latestPostCount }) {
const latestPosts = Object.create(allPosts).sort((a, b) => {
const dateA = new Date(a?.lastEditedTime || a?.createdTime || a?.date?.start_date)
const dateB = new Date(b?.lastEditedTime || b?.createdTime || b?.date?.start_date)
@@ -79,7 +69,7 @@ export async function getNotionPageData({ pageId, from }) {
// 尝试从缓存获取
const cacheKey = 'page_block_' + pageId
const data = await getDataFromCache(cacheKey)
if (data) {
if (data && data.pageIds?.length > 0) {
console.log('[请求缓存]:', `from:${from}`, `root-page-id:${pageId}`)
return data
}
@@ -96,14 +86,7 @@ export async function getNotionPageData({ pageId, from }) {
* @param notionPageData
* @returns {Promise<[]|*[]>}
*/
async function getCustomNav({ notionPageData }) {
if (!notionPageData) {
notionPageData = await getNotionPageData({ from: 'custom-nav' })
}
if (!notionPageData) {
return []
}
const allPage = await getAllPosts({ notionPageData, from: 'custom-nav', pageType: ['Page'] })
function getCustomNav({ allPage }) {
const customNav = []
if (allPage && allPage.length > 0) {
allPage.forEach(p => {
@@ -139,23 +122,55 @@ function getCategoryOptions(schema) {
return categorySchema?.options || []
}
/**
* 获取所有文章的分类
* @param allPosts
* @returns {Promise<{}|*[]>}
*/
function getAllCategories({ allPosts, categoryOptions, sliceCount = 0 }) {
if (!allPosts || !categoryOptions) {
return []
}
// 计数
let categories = allPosts.map(p => p.category)
categories = [...categories.flat()]
const categoryObj = {}
categories.forEach(category => {
if (category in categoryObj) {
categoryObj[category]++
} else {
categoryObj[category] = 1
}
})
const list = []
if (isIterable(categoryOptions)) {
for (const c of categoryOptions) {
const count = categoryObj[c.value]
if (count) {
list.push({ id: c.id, name: c.value, color: c.color, count })
}
}
}
// 按照数量排序
// list.sort((a, b) => b.count - a.count)
if (sliceCount && sliceCount > 0) {
return list.slice(0, sliceCount)
} else {
return list
}
}
/**
* 站点信息
* @param notionPageData
* @param from
* @returns {Promise<{title,description,pageCover}>}
*/
async function getBlogInfo({ notionPageData, from }) {
if (!notionPageData) {
notionPageData = await getNotionPageData({ from })
}
if (!notionPageData) {
return null
}
const collection = notionPageData?.collection
function getBlogInfo(collection, block) {
const title = collection?.name?.[0][0] || BLOG.TITLE
const description = collection?.description?.[0][0] || BLOG.DESCRIPTION
const pageCover = mapCoverUrl(collection?.cover, notionPageData.block)
const pageCover = mapCoverUrl(collection?.cover, block)
return { title, description, pageCover }
}
@@ -184,23 +199,47 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
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)
const categoryOptions = getCategoryOptions(schema)
const rawMetadata = block[pageId]?.value
// Check Type Page-Database和Inline-Database
if (
rawMetadata?.type !== 'collection_view_page' &&
rawMetadata?.type !== 'collection_view'
rawMetadata?.type !== 'collection_view'
) {
console.warn(`pageId "${pageId}" is not a database`)
return null
}
const collection = Object.values(pageRecordMap.collection)[0]?.value
const collectionQuery = pageRecordMap.collection_query
const schema = collection?.schema
const tagOptions = getTagOptions(schema)
const categoryOptions = getCategoryOptions(schema)
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.slug = properties.slug ?? properties.id
delete properties.content
data.push(properties)
}
const allPage = data.filter(post => {
return post.title && post?.status?.[0] === 'Published' && ['Page'].indexOf(post?.type?.[0]) > -1
})
const allPosts = data.filter(post => {
return post.title && post?.status?.[0] === 'Published' && ['Post'].indexOf(post?.type?.[0]) > -1
})
const customNav = getCustomNav({ allPage })
const postCount = allPosts?.length || 0
const categories = getAllCategories({ allPosts, categoryOptions, sliceCount: BLOG.PREVIEW_CATEGORY_COUNT })
const tags = getAllTags({ allPosts, tagOptions, sliceCount: BLOG.PREVIEW_TAG_COUNT })
const latestPosts = getLatestPosts({ allPosts, from, latestPostCount: 5 })
const siteInfo = getBlogInfo({ collection, block })
return {
collection,
collectionQuery,
@@ -208,6 +247,13 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
schema,
tagOptions,
categoryOptions,
rawMetadata
rawMetadata,
siteInfo,
customNav,
postCount,
pageIds,
categories,
tags,
latestPosts
}
}

View File

@@ -1,28 +1,19 @@
import BLOG from '@/blog.config'
import { NotionAPI } from 'notion-client'
import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
import { deepClone, delay } from '../utils'
export async function getPostBlocks(id, from, slice, retryCount = 3) {
export async function getPostBlocks(id, from, slice) {
const cacheKey = 'page_block_' + id
let pageBlock = await getDataFromCache(cacheKey)
if (pageBlock) {
console.log('[请求缓存]:', `from:${from}`, `id:${id}`, cacheKey)
console.log('[请求缓存]:', `from:${from}`, cacheKey)
return filterPostBlocks(id, pageBlock, slice)
}
const authToken = BLOG.NOTION_ACCESS_TOKEN || null
const api = new NotionAPI({ authToken })
try {
console.warn('[请求API]:', `from:${from}`, `id:${id}`)
pageBlock = await api.getPage(id)
console.warn('[请求成功]', `from:${from}`, `id:${id}`)
} catch (error) {
console.error('[请求失败]', `from:${from}`, `id:${id}`, `error:${error}`)
if (retryCount && retryCount > 0) { // 重试
console.error('[重试请求]', `from:${from}`, `id:${id}`, `剩余次数:${retryCount}`)
return getPostBlocks(id, from, slice, retryCount - 1)
}
return null
}
console.warn('[请求API]:', `from:${from}`, `id:${id}`)
pageBlock = await getPageWithRetry(id, from)
console.warn('[请求完成]: 结果', `${pageBlock ? '成功' : '失败'}`, `from:${from}`, `id:${id}`)
if (pageBlock) {
await setDataToCache(cacheKey, pageBlock)
@@ -31,6 +22,33 @@ export async function getPostBlocks(id, from, slice, retryCount = 3) {
return pageBlock
}
/**
* 调用接口,失败会重试
* @param {*} id
* @param {*} retryAttempts
*/
async function getPageWithRetry(id, from, retryAttempts = 3) {
if (retryAttempts && retryAttempts > 0) {
console.error('[发起请求]', `from:${from}`, `id:${id}`, `剩余重试次数:${retryAttempts}`)
try {
const authToken = BLOG.NOTION_ACCESS_TOKEN || null
const api = new NotionAPI({ authToken })
return await api.getPage(id)
} catch (e) {
await delay(1000)
const cacheKey = 'page_block_' + id
const pageBlock = await getDataFromCache(cacheKey)
if (pageBlock) {
console.error('[重试获取缓存]', `from:${from}`, `id:${id}`)
return pageBlock
}
return await getPageWithRetry(id, from, retryAttempts - 1)
}
} else {
return null
}
}
/**
* 获取到的blockMap删除不需要的字段
* @param {*} id 页面ID
@@ -74,15 +92,3 @@ function filterPostBlocks(id, pageBlock, slice) {
}
return clonePageBlock
}
function deepClone(obj) {
const newObj = Array.isArray(obj) ? [] : {}
if (obj && typeof obj === 'object') {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
newObj[key] = (obj && typeof obj[key] === 'object') ? deepClone(obj[key]) : obj[key]
}
}
}
return newObj
}

View File

@@ -80,3 +80,22 @@ export function isObject(item) {
export function isIterable(obj) {
return obj != null && typeof obj[Symbol.iterator] === 'function'
}
export function deepClone(obj) {
const newObj = Array.isArray(obj) ? [] : {}
if (obj && typeof obj === 'object') {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
newObj[key] = (obj && typeof obj[key] === 'object') ? deepClone(obj[key]) : obj[key]
}
}
}
return newObj
}
/**
* 延时
* @param {*} ms
* @returns
*/
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

View File

@@ -29,7 +29,7 @@ const Slug = props => {
})
}
}
}, 5000)
}, 10000)
const meta = { title: `${props?.siteInfo?.title || BLOG.TITLE} | loading` }
return <ThemeComponents.LayoutSlug {...props} showArticleInfo={true} meta={meta} />
}

View File

@@ -4,7 +4,7 @@ import { getGlobalNotionData } from '@/lib/notion/getNotionData'
export async function getServerSideProps ({ res }) {
res.setHeader('Content-Type', 'text/xml')
// 获取最新文章
const globalNotionData = await getGlobalNotionData({ from: 'rss', latestPostCount: 5 })
const globalNotionData = await getGlobalNotionData({ from: 'rss' })
const xmlFeed = await generateRss(globalNotionData?.latestPosts || [])
res.write(xmlFeed)
res.end()

View File

@@ -16,7 +16,7 @@ export async function getStaticProps() {
const meta = {
title: `${siteInfo?.title} | ${siteInfo?.description}`,
description: siteInfo?.description,
image: siteInfo.pageCover,
image: siteInfo?.pageCover,
slug: '',
type: 'website'
}

View File

@@ -53,7 +53,7 @@ function getTagNames(tags) {
export async function getStaticPaths() {
const from = 'tag-static-path'
const { tags } = await getGlobalNotionData({ from, tagsCount: 0 })
const { tags } = await getGlobalNotionData({ from })
const tagNames = getTagNames(tags)
return {

View File

@@ -19,7 +19,7 @@ const TagIndex = props => {
export async function getStaticProps() {
const from = 'tag-index-props'
const props = await getGlobalNotionData({ from, tagsCount: 0 })
const props = await getGlobalNotionData({ from })
return {
props,
revalidate: 1

View File

@@ -24,7 +24,7 @@ export const LayoutArchive = props => {
})
return (
<LayoutBase {...props}>
<div className="mb-10 pb-20 md:p-12 p-3 min-h-screen w-full">
<div className="mb-10 pb-20 md:py-12 p-3 min-h-screen w-full">
{Object.keys(archivePosts).map(archiveTitle => (
<div key={archiveTitle}>
<div id={archiveTitle} className="pt-16 pb-4 text-3xl dark:text-gray-300" >

View File

@@ -28,9 +28,9 @@ const LayoutBase = props => {
<Title {...props} />
<div className="container max-w-4xl mx-auto md:flex items-start py-8 px-2">
<div className="container mx-auto justify-center md:flex items-start py-8 px-2">
<div className='w-full flex-grow'>{children}</div>
<div className='w-full max-w-3xl xl:px-14 lg:px-4 '>{children}</div>
<SideBar {...props} />

View File

@@ -1,15 +1,11 @@
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import LayoutBase from './LayoutBase'
export const LayoutCategoryIndex = (props) => {
const { categories } = props
const { locale } = useGlobal()
return <LayoutBase {...props}>
<div className=' p-10 w-full'>
<div className='dark:text-gray-200 mb-5'>
<i className='mr-4 fas fa-th' />{locale.COMMON.CATEGORY}:
</div>
<div >
<div id='category-list' className='duration-200 flex flex-wrap'>
{categories && categories.map(category => {
return <Link key={category.name} href={`/category/${category.name}`} passHref>

View File

@@ -1,12 +1,15 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import SearchInput from './components/SearchInput'
import LayoutBase from './LayoutBase'
export const LayoutSearch = props => {
const { keyword, posts } = props
const router = useRouter()
useEffect(() => {
setTimeout(() => {
const container = typeof document !== 'undefined' && document.getElementById('container')
@@ -15,7 +18,7 @@ export const LayoutSearch = props => {
container.innerHTML = container.innerHTML.replace(re, `<span class='text-red-500 border-b border-dashed'>${keyword}</span>`)
}
}, 100)
})
}, [router.events])
const { locale } = useGlobal()
@@ -51,7 +54,7 @@ export const LayoutSearch = props => {
}, [])
return <LayoutBase {...props}>
<div className='py-2'>
<div className='pb-12'>
<SearchInput {...props} />
</div>

View File

@@ -4,7 +4,7 @@ import LayoutBase from './LayoutBase'
export const LayoutTagIndex = (props) => {
const { tags } = props
return <LayoutBase {...props}>
<div className='p-10'>
<div>
<div id='tags-list' className='duration-200 flex flex-wrap'>
{tags.map(tag => {
return <div key={tag.name} className='p-2'>

View File

@@ -34,14 +34,13 @@ export const ArticleInfo = (props) => {
{locale.COMMON.LAST_EDITED_TIME}: {post.lastEditedTime}
</span>
<span className='mr-2'>|</span>
</>)}
<span className="hidden busuanzi_container_page_pv font-light mr-2">
<span className="hidden busuanzi_container_page_pv font-light mr-2">
<i className='mr-1 fas fa-eye' />
&nbsp;
<span className="mr-2 busuanzi_value_page_pv" />
</span>
</>)}
</div>
</section>

View File

@@ -39,6 +39,14 @@ export const BlogList = (props) => {
<p className="text-gray-700 leading-normal">
{p.summary}
</p>
{/* 搜索结果 */}
{p.results && (
<p className="mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
{p.results.map(r => (
<span key={r}>{r}</span>
))}
</p>
)}
</article>
))}

View File

@@ -103,7 +103,7 @@ const Header = props => {
className="duration-500 md:bg-fixed w-full bg-cover bg-center h-screen bg-black text-white"
style={{
backgroundImage:
`linear-gradient(rgba(0, 0, 0, 0.8), rgba(0,0,0,0.2), rgba(0, 0, 0, 0.8) ),url("${siteInfo.pageCover}")`
`linear-gradient(rgba(0, 0, 0, 0.8), rgba(0,0,0,0.2), rgba(0, 0, 0, 0.8) ),url("${siteInfo?.pageCover}")`
}}
>
<div className="absolute flex flex-col h-full items-center justify-center w-full font-sans">

View File

@@ -41,6 +41,9 @@ export default function HeaderArticle({ post, siteInfo }) {
if (!isDarkMode) {
const stickyNavElement = document.getElementById('sticky-nav')
const header = document.querySelector('#header')
if (!header || !stickyNavElement) {
return
}
if (window.scrollY < header.clientHeight) {
stickyNavElement?.classList?.add('dark')
} else {

View File

@@ -98,7 +98,7 @@ export default function Header(props) {
className="duration-500 md:bg-fixed w-full bg-cover bg-center h-screen bg-black"
style={{
backgroundImage:
`linear-gradient(rgba(0, 0, 0, 0.8), rgba(0,0,0,0.2), rgba(0, 0, 0, 0.8) ),url("${siteInfo.pageCover}")`
`linear-gradient(rgba(0, 0, 0, 0.8), rgba(0,0,0,0.2), rgba(0, 0, 0, 0.8) ),url("${siteInfo?.pageCover}")`
}}
>
<div className="absolute flex h-full items-center lg:-mt-14 justify-center w-full text-4xl md:text-7xl text-white">