Merge pull request #438 from tangly1024/bugfix/blogs-from-tag

Bugfix/blogs from tag
This commit is contained in:
tangly1024
2022-11-13 11:21:45 +08:00
committed by GitHub
36 changed files with 586 additions and 333 deletions

View File

@@ -0,0 +1,49 @@
import { isIterable } from '../utils'
/**
* 获取所有文章的标签
* @param allPosts
* @param sliceCount 默认截取数量为12若为0则返回全部
* @param tagOptions tags的下拉选项
* @returns {Promise<{}|*[]>}
*/
/**
* 获取所有文章的分类
* @param allPosts
* @returns {Promise<{}|*[]>}
*/
export function getAllCategories({ allPages, categoryOptions, sliceCount = 0 }) {
const allPosts = allPages.filter(page => page.type === 'Post')
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

@@ -3,7 +3,8 @@ 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 { deepClone, isIterable } from '../utils'
import { deepClone } from '../utils'
import { getAllCategories } from './getAllCategories'
import getAllPageIds from './getAllPageIds'
import { getAllTags } from './getAllTags'
import getPageProperties from './getPageProperties'
@@ -31,8 +32,6 @@ export async function getGlobalNotionData({
delete notionPageData.schema
delete notionPageData.rawMetadata
delete notionPageData.pageIds
delete notionPageData.tagOptions
delete notionPageData.categoryOptions
return notionPageData
}
@@ -117,46 +116,6 @@ function getCategoryOptions(schema) {
return categorySchema?.options || []
}
/**
* 获取所有文章的分类
* @param allPosts
* @returns {Promise<{}|*[]>}
*/
function getAllCategories({ allPages, categoryOptions, sliceCount = 0 }) {
const allPosts = allPages.filter(page => page.type === 'Post')
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
@@ -279,7 +238,7 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
const customNav = getCustomNav({ allPages })
const categories = getAllCategories({ allPages, categoryOptions, sliceCount: BLOG.PREVIEW_CATEGORY_COUNT })
const tags = getAllTags({ allPages, tagOptions, sliceCount: BLOG.PREVIEW_TAG_COUNT })
const tags = getAllTags({ allPages, sliceCount: BLOG.PREVIEW_TAG_COUNT, tagOptions })
const latestPosts = getLatestPosts({ allPages, from, latestPostCount: 5 })
return {

View File

@@ -105,3 +105,17 @@ export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
* @returns {boolean}
*/
export const isBrowser = () => typeof window !== 'undefined'
/**
* 获取从第1页到指定页码的文章
* @param pageIndex 第几页
* @param list 所有文章
* @param pageSize 每页文章数量
* @returns {*}
*/
export const getListByPage = function (list, pageIndex, pageSize) {
return list.slice(
0,
pageIndex * pageSize
)
}

View File

@@ -20,10 +20,10 @@ const ArchiveIndex = props => {
export async function getStaticProps() {
const props = await getGlobalNotionData({ from: 'archive-index' })
const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published')
// 处理分页
props.posts = allPosts
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published')
delete props.allPages
return {
props,
revalidate: 1

View File

@@ -2,7 +2,13 @@ import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import React from 'react'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import BLOG from '@/blog.config'
/**
* 分类页
* @param {*} props
* @returns
*/
export default function Category(props) {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
@@ -26,12 +32,23 @@ export default function Category(props) {
export async function getStaticProps({ params: { category } }) {
const from = 'category-props'
let props = await getGlobalNotionData({ from })
const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published')
const posts = allPosts.filter(
post => post && post.category && post.category.includes(category)
)
props = { ...props, posts, category }
// 过滤状态
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published')
// 处理过滤
props.posts = props.posts.filter(post => post && post.category && post.category.includes(category))
// 处理文章页数
props.postCount = props.posts.length
// 处理分页
if (BLOG.POST_LIST_STYLE === 'scroll') {
// 滚动列表 给前端返回所有数据
} else if (BLOG.POST_LIST_STYLE === 'page') {
props.posts = props.posts?.slice(0, BLOG.POSTS_PER_PAGE - 1)
}
delete props.allPages
props = { ...props, category }
return {
props,

View File

@@ -0,0 +1,76 @@
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import React from 'react'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import BLOG from '@/blog.config'
/**
* 分类页
* @param {*} props
* @returns
*/
export default function Category(props) {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { siteInfo, posts } = props
const { locale } = useGlobal()
if (!posts) {
return <ThemeComponents.Layout404 {...props} />
}
const meta = {
title: `${props.category} | ${locale.COMMON.CATEGORY} | ${
siteInfo?.title || ''
}`,
description: siteInfo?.description,
slug: 'category/' + props.category,
image: siteInfo?.pageCover,
type: 'website'
}
return <ThemeComponents.LayoutCategory {...props} meta={meta} />
}
export async function getStaticProps({ params: { category, page } }) {
const from = 'category-page-props'
let props = await getGlobalNotionData({ from })
// 过滤状态类型
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.category && post.category.includes(category))
// 处理文章页数
props.postCount = props.posts.length
// 处理分页
props.posts = props.posts.slice(BLOG.POSTS_PER_PAGE * (page - 1), BLOG.POSTS_PER_PAGE * page - 1)
delete props.allPages
props.page = page
props = { ...props, category, page }
return {
props,
revalidate: 1
}
}
export async function getStaticPaths() {
const from = 'category-paths'
const { categories, allPages } = await getGlobalNotionData({ from })
const paths = []
categories?.forEach(category => {
// 过滤状态类型
const categoryPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.category && post.category.includes(category.name))
// 处理文章页数
const postCount = categoryPosts.length
const totalPages = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
if (totalPages > 1) {
for (let i = 1; i <= totalPages; i++) {
paths.push({ params: { category: category.name, page: '' + i } })
}
}
})
return {
paths,
fallback: true
}
}

View File

@@ -2,7 +2,13 @@ import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import React from 'react'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import { getAllCategories } from '@/lib/notion/getAllCategories'
/**
* 分类首页
* @param {*} props
* @returns
*/
export default function Category(props) {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
@@ -19,10 +25,9 @@ export default function Category(props) {
}
export async function getStaticProps() {
const props = await getGlobalNotionData({
from: 'category-index-props',
categoryCount: 0
})
const props = await getGlobalNotionData({ from: 'category-index-props' })
props.categories = getAllCategories({ allPages: props.allPages, categoryOptions: props.categoryOptions, sliceCount: 0 })
delete props.categoryOptions
return {
props,
revalidate: 1

View File

@@ -12,8 +12,8 @@ const Index = props => {
export async function getStaticProps() {
const from = 'index'
const props = await getGlobalNotionData({ from })
const { allPages, siteInfo } = props
const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published')
const { siteInfo } = props
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published')
const meta = {
title: `${siteInfo?.title} | ${siteInfo?.description}`,
description: siteInfo?.description,
@@ -21,35 +21,23 @@ export async function getStaticProps() {
slug: '',
type: 'website'
}
// 处理分页
const page = 1
let postsToShow
if (BLOG.POST_LIST_STYLE !== 'page') {
postsToShow = Array.from(allPosts)
} else {
postsToShow = allPosts?.slice(
BLOG.POSTS_PER_PAGE * (page - 1),
BLOG.POSTS_PER_PAGE * page
)
if (BLOG.POST_LIST_PREVIEW === 'true') {
for (const i in postsToShow) {
const post = postsToShow[i]
if (post.password && post.password !== '') {
continue
}
const blockMap = await getPostBlocks(
post.id,
'slug',
BLOG.POST_PREVIEW_LINES
)
if (blockMap) {
post.blockMap = blockMap
}
if (BLOG.POST_LIST_STYLE === 'scroll') {
// 滚动列表默认给前端返回所有数据
} else if (BLOG.POST_LIST_STYLE === 'page') {
props.posts = props.posts?.slice(0, BLOG.POSTS_PER_PAGE)
}
// 预览文章内容
if (BLOG.POST_LIST_PREVIEW === 'true') {
for (const i in props.posts) {
const post = props.posts[i]
if (post.password && post.password !== '') {
continue
}
post.blockMap = await getPostBlocks(post.id, 'slug', BLOG.POST_PREVIEW_LINES)
}
}
props.posts = postsToShow
return {
props: {

View File

@@ -37,28 +37,20 @@ export async function getStaticPaths() {
export async function getStaticProps({ params: { page } }) {
const from = `page-${page}`
const props = await getGlobalNotionData({ from })
props.page = page
const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published')
// 处理分页
props.posts = allPosts.slice(
BLOG.POSTS_PER_PAGE * (page - 1),
BLOG.POSTS_PER_PAGE * page
)
props.posts = allPosts.slice(BLOG.POSTS_PER_PAGE * (page - 1), BLOG.POSTS_PER_PAGE * page)
props.page = page
// 处理预览
if (BLOG.POST_LIST_PREVIEW === 'true') {
for (const i in props.posts) {
const post = props.posts[i]
if (post.password && post.password !== '') {
continue
}
const blockMap = await getPostBlocks(
post.id,
'slug',
BLOG.POST_PREVIEW_LINES
)
if (blockMap) {
post.blockMap = blockMap
}
post.blockMap = await getPostBlocks(post.id, 'slug', BLOG.POST_PREVIEW_LINES)
}
}

View File

@@ -1,6 +1,7 @@
import { useGlobal } from '@/lib/global'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import * as ThemeMap from '@/themes'
import BLOG from '@/blog.config'
const Tag = props => {
const { theme } = useGlobal()
@@ -23,16 +24,22 @@ const Tag = props => {
}
export async function getStaticProps({ params: { tag } }) {
const props = await getGlobalNotionData({
from: 'tag-props',
includePage: false,
tagsCount: 0
})
const { allPages } = props
const allPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published')
props.posts = allPosts.filter(
post => post && post.tags && post.tags.includes(tag)
)
const from = 'tag-props'
const props = await getGlobalNotionData({ from })
// 过滤状态
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.tags && post.tags.includes(tag))
// 处理文章页数
props.postCount = props.posts.length
// 处理分页
if (BLOG.POST_LIST_STYLE === 'scroll') {
// 滚动列表 给前端返回所有数据
} else if (BLOG.POST_LIST_STYLE === 'page') {
props.posts = props.posts?.slice(0, BLOG.POSTS_PER_PAGE - 1)
}
props.tag = tag
return {
props,

View File

@@ -0,0 +1,67 @@
import { useGlobal } from '@/lib/global'
import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import * as ThemeMap from '@/themes'
import BLOG from '@/blog.config'
const Tag = props => {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
const { locale } = useGlobal()
const { tag, siteInfo, posts } = props
if (!posts) {
return <ThemeComponents.Layout404 {...props} />
}
const meta = {
title: `${tag} | ${locale.COMMON.TAGS} | ${siteInfo?.title}`,
description: siteInfo?.description,
image: siteInfo?.pageCover,
slug: 'tag/' + tag,
type: 'website'
}
return <ThemeComponents.LayoutTag {...props} meta={meta} />
}
export async function getStaticProps({ params: { tag, page } }) {
const from = 'tag-page-props'
const props = await getGlobalNotionData({ from })
// 过滤状态、标签
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.tags && post.tags.includes(tag))
// 处理文章数
props.postCount = props.posts.length
// 处理分页
props.posts = props.posts.slice(BLOG.POSTS_PER_PAGE * (page - 1), BLOG.POSTS_PER_PAGE * page - 1)
props.tag = tag
props.page = page
delete props.allPages
return {
props,
revalidate: 1
}
}
export async function getStaticPaths() {
const from = 'tag-page-static-path'
const { tags, allPages } = await getGlobalNotionData({ from })
const paths = []
tags?.forEach(tag => {
// 过滤状态类型
const categoryPosts = allPages.filter(page => page.type === 'Post' && page.status === 'Published').filter(post => post && post.category && post.tags.includes(tag.name))
// 处理文章页数
const postCount = categoryPosts.length
const totalPages = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
if (totalPages > 1) {
for (let i = 1; i <= totalPages; i++) {
paths.push({ params: { tag: tag.name, page: '' + i } })
}
}
})
return {
paths: paths,
fallback: true
}
}
export default Tag

View File

@@ -2,7 +2,13 @@ import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import React from 'react'
import { useGlobal } from '@/lib/global'
import * as ThemeMap from '@/themes'
import { getAllTags } from '@/lib/notion'
/**
* 标签首页
* @param {*} props
* @returns
*/
const TagIndex = props => {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
@@ -21,6 +27,8 @@ const TagIndex = props => {
export async function getStaticProps() {
const from = 'tag-index-props'
const props = await getGlobalNotionData({ from })
props.tags = getAllTags({ allPages: props.allPages, sliceCount: 0, tagOptions: props.tagOptions })
delete props.tagOptions
return {
props,
revalidate: 1

View File

@@ -1,61 +1,10 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import { useState } from 'react'
import { BlogListPage } from './components/BlogListPage'
import { BlogListScroll } from './components/BlogListScroll'
import LayoutBase from './LayoutBase'
export const LayoutCategory = props => {
const { category, posts } = props
const { locale } = useGlobal()
const [page, updatePage] = useState(1)
let hasMore = false
const postsToShow = posts
? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page)
: []
if (posts) {
const totalCount = posts.length
hasMore = page * BLOG.POSTS_PER_PAGE < totalCount
}
const handleGetMore = () => {
if (!hasMore) return
updatePage(page + 1)
}
return <LayoutBase {...props}>
<div className='w-full'>
<div className='pb-12'>{category}</div>
{postsToShow.map(p => (
<article key={p.id} className="mb-12" >
<h2 className="mb-4">
<Link href={`/${p.slug}`}>
<a className="text-black text-xl md:text-2xl no-underline hover:underline"> {p.title}</a>
</Link>
</h2>
<div className="mb-4 text-sm text-gray-700">
by <a href="#" className="text-gray-700">{BLOG.AUTHOR}</a> on {p.date?.start_date || p.createdTime}
<span className="font-bold mx-1"> | </span>
<a href="#" className="text-gray-700">{p.category}</a>
<span className="font-bold mx-1"> | </span>
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
</div>
<p className="text-gray-700 leading-normal">
{p.summary}
</p>
</article>
))}
<div
onClick={handleGetMore}
className="w-full my-4 py-4 text-center cursor-pointer "
>
{' '}
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
</div>
</div>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</LayoutBase >
}

View File

@@ -5,7 +5,6 @@ export const LayoutCategoryIndex = (props) => {
const { categories } = props
return <LayoutBase {...props}>
<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>
@@ -16,6 +15,5 @@ export const LayoutCategoryIndex = (props) => {
</Link>
})}
</div>
</div>
</LayoutBase>
}

View File

@@ -1,11 +1,11 @@
import { BlogList } from './components/BlogList'
import { BlogListPage } from './components/BlogListPage'
import LayoutBase from './LayoutBase'
export const LayoutIndex = props => {
return (
<LayoutBase {...props}>
<BlogList {...props} page={1} />
<BlogListPage {...props} page={1} />
</LayoutBase>
)
}

View File

@@ -1,10 +1,10 @@
import { BlogList } from './components/BlogList'
import { BlogListPage } from './components/BlogListPage'
import LayoutBase from './LayoutBase'
export const LayoutPage = props => {
return (
<LayoutBase {...props}>
<BlogList {...props} />
<BlogListPage {...props} />
</LayoutBase>
)
}

View File

@@ -1,59 +1,10 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import { useState } from 'react'
import { BlogListPage } from './components/BlogListPage'
import { BlogListScroll } from './components/BlogListScroll'
import LayoutBase from './LayoutBase'
export const LayoutTag = props => {
const { posts } = props
const { locale } = useGlobal()
const [page, updatePage] = useState(1)
let hasMore = false
const postsToShow = posts
? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page)
: []
if (posts) {
const totalCount = posts.length
hasMore = page * BLOG.POSTS_PER_PAGE < totalCount
}
const handleGetMore = () => {
if (!hasMore) return
updatePage(page + 1)
}
return <LayoutBase>
{postsToShow.map(p => (
<article key={p.id} className="mb-12" >
<h2 className="mb-4">
<Link href={`/${p.slug}`}>
<a className="text-black text-xl md:text-2xl no-underline hover:underline"> {p.title}</a>
</Link>
</h2>
<div className="mb-4 text-sm text-gray-700">
by <a href="#" className="text-gray-700">{BLOG.AUTHOR}</a> on {p.date?.start_date || p.createdTime}
<span className="font-bold mx-1"> | </span>
<a href="#" className="text-gray-700">{p.category}</a>
<span className="font-bold mx-1"> | </span>
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
</div>
<p className="text-gray-700 leading-normal">
{p.summary}
</p>
</article>
))}
<div
onClick={handleGetMore}
className="w-full my-4 py-4 text-center cursor-pointer "
>
{' '}
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
</div>
return <LayoutBase {...props}>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props} />}
</LayoutBase >
}

View File

@@ -4,20 +4,17 @@ import { useGlobal } from '@/lib/global'
import { useRouter } from 'next/router'
import Link from 'next/link'
export const BlogList = (props) => {
const { page, posts, postCount } = props
export const BlogListPage = props => {
const { page = 1, posts, postCount } = props
const { locale } = useGlobal()
const router = useRouter()
const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
const showNext =
page < totalPage &&
posts.length === BLOG.POSTS_PER_PAGE &&
posts.length < postCount
const currentPage = +page
const showPrev = currentPage > 1
const showNext = page < totalPage
const pagePrefix = router.asPath.replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
return <div className="w-full md:pr-12 mb-12">
{posts.map(p => (
@@ -51,10 +48,10 @@ export const BlogList = (props) => {
))}
<div className="flex justify-between text-xs">
<Link href={{ pathname: currentPage - 1 === 1 ? `${BLOG.SUB_PATH || '/'}` : `/page/${currentPage - 1}`, query: router.query.s ? { s: router.query.s } : {} }}>
<a className={`${currentPage > 1 ? 'bg-black ' : 'bg-gray pointer-events-none '} text-white no-underline py-2 px-3 rounded`}>{locale.PAGINATION.PREV}</a>
<Link href={{ pathname: currentPage - 1 === 1 ? `${BLOG.SUB_PATH || pagePrefix}` : `${pagePrefix}/page/${currentPage - 1}`, query: router.query.s ? { s: router.query.s } : {} }}>
<a className={`${showPrev ? 'bg-black ' : 'bg-gray pointer-events-none '} text-white no-underline py-2 px-3 rounded`}>{locale.PAGINATION.PREV}</a>
</Link>
<Link href={{ pathname: `/page/${currentPage + 1}`, query: router.query.s ? { s: router.query.s } : {} }}>
<Link href={{ pathname: `${pagePrefix}/page/${currentPage + 1}`, query: router.query.s ? { s: router.query.s } : {} }}>
<a className={`${showNext ? 'bg-black ' : 'bg-gray pointer-events-none '} text-white no-underline py-2 px-3 rounded`}>{locale.PAGINATION.NEXT}</a>
</Link>
</div>

View File

@@ -0,0 +1,78 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
import Link from 'next/link'
import React from 'react'
import throttle from 'lodash.throttle'
export const BlogListScroll = props => {
const { posts } = props
const { locale } = useGlobal()
const [page, updatePage] = React.useState(1)
let hasMore = false
const postsToShow = posts
? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page)
: []
if (posts) {
const totalCount = posts.length
hasMore = page * BLOG.POSTS_PER_PAGE < totalCount
}
const handleGetMore = () => {
if (!hasMore) return
updatePage(page + 1)
}
const targetRef = React.useRef(null)
// 监听滚动自动分页加载
const scrollTrigger = React.useCallback(throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500))
React.useEffect(() => {
window.addEventListener('scroll', scrollTrigger)
return () => {
window.removeEventListener('scroll', scrollTrigger)
}
})
return <div className="w-full md:pr-12 mb-12" ref={targetRef}>
{postsToShow.map(p => (
<article key={p.id} className="mb-12" >
<h2 className="mb-4">
<Link href={`/${p.slug}`}>
<a className="text-black text-xl md:text-2xl no-underline hover:underline"> {p.title}</a>
</Link>
</h2>
<div className="mb-4 text-sm text-gray-700">
by <a href="#" className="text-gray-700">{BLOG.AUTHOR}</a> on {p.date?.start_date || p.createdTime}
<span className="font-bold mx-1"> | </span>
<a href="#" className="text-gray-700">{p.category}</a>
<span className="font-bold mx-1"> | </span>
{/* <a href="#" className="text-gray-700">2 Comments</a> */}
</div>
<p className="text-gray-700 leading-normal">
{p.summary}
</p>
</article>
))}
<div
onClick={handleGetMore}
className="w-full my-4 py-4 text-center cursor-pointer "
>
{' '}
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
</div>
</div>
}

View File

@@ -1,8 +1,10 @@
import BLOG from '@/blog.config'
import BlogListPage from './components/BlogListPage'
import BlogListScroll from './components/BlogListScroll'
import LayoutBase from './LayoutBase'
export const LayoutCategory = props => {
return <LayoutBase {...props}>
<BlogListPage page={props.page} posts={props.posts} postCount={props.postCount} />
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
</LayoutBase>
}

View File

@@ -1,8 +1,10 @@
import BLOG from '@/blog.config'
import BlogListPage from './components/BlogListPage'
import BlogListScroll from './components/BlogListScroll'
import LayoutBase from './LayoutBase'
export const LayoutIndex = (props) => {
return <LayoutBase {...props}>
<BlogListPage {...props} />
{BLOG.POST_LIST_STYLE === 'page' ? <BlogListPage {...props} /> : <BlogListScroll {...props}/>}
</LayoutBase>
}

View File

@@ -14,7 +14,7 @@ import PaginationSimple from './PaginationSimple'
*/
const BlogListPage = ({ page = 1, posts = [], postCount }) => {
const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
const showNext = page < totalPage && posts.length === BLOG.POSTS_PER_PAGE && posts.length < postCount
const showNext = page < totalPage
const [colCount, changeCol] = useState(1)
function updateCol() {

View File

@@ -0,0 +1,93 @@
import BLOG from '@/blog.config'
import React from 'react'
import BlogCard from './BlogCard'
import BlogPostListEmpty from './BlogListEmpty'
import { useGlobal } from '@/lib/global'
import throttle from 'lodash.throttle'
/**
* 文章列表分页表格
* @param page 当前页
* @param posts 所有文章
* @param tags 所有标签
* @returns {JSX.Element}
* @constructor
*/
const BlogListScroll = props => {
const { posts = [] } = props
const [colCount, changeCol] = React.useState(1)
const { locale } = useGlobal()
function updateCol() {
if (window.outerWidth > 1200) {
changeCol(3)
} else if (window.outerWidth > 900) {
changeCol(2)
} else {
changeCol(1)
}
}
const targetRef = React.useRef(null)
const [page, updatePage] = React.useState(1)
let hasMore = false
const postsToShow = posts
? Object.assign(posts).slice(0, BLOG.POSTS_PER_PAGE * page)
: []
if (posts) {
const totalCount = posts.length
hasMore = page * BLOG.POSTS_PER_PAGE < totalCount
}
const handleGetMore = () => {
if (!hasMore) return
updatePage(page + 1)
}
// 监听滚动自动分页加载
const scrollTrigger = React.useCallback(throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500))
React.useEffect(() => {
updateCol()
window.addEventListener('scroll', scrollTrigger)
window.addEventListener('resize', updateCol)
return () => {
window.removeEventListener('resize', updateCol)
window.removeEventListener('scroll', scrollTrigger)
}
})
if (!posts || posts.length === 0) {
return <BlogPostListEmpty />
} else {
return (
<div id="container" ref={targetRef} >
{/* 文章列表 */}
<div style={{ columnCount: colCount }}>
{postsToShow?.map(post => (
<div key={post.id} className='justify-center flex' style={{ breakInside: 'avoid' }}>
<BlogCard key={post.id} post={post} />
</div>
))}
</div>
<div
onClick={handleGetMore}
className="w-full my-4 py-4 text-center cursor-pointer "
>
{' '}
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
</div>
</div>
)
}
}
export default BlogListScroll

View File

@@ -1,4 +1,3 @@
import BLOG from '@/blog.config'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useGlobal } from '@/lib/global'
@@ -14,14 +13,16 @@ const PaginationSimple = ({ page, showNext }) => {
const { locale } = useGlobal()
const router = useRouter()
const currentPage = +page
const pagePrefix = router.asPath.replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
return (
<div className="my-10 flex justify-between font-medium text-black dark:text-gray-100 space-x-2">
<Link
href={{
pathname:
currentPage === 2
? `${BLOG.SUB_PATH || '/'}`
: `/page/${currentPage - 1}`,
? `${pagePrefix}`
: `${pagePrefix}/page/${currentPage - 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
passHref
@@ -37,7 +38,7 @@ const PaginationSimple = ({ page, showNext }) => {
</Link>
<Link
href={{
pathname: `/page/${currentPage + 1}`,
pathname: `${pagePrefix}/page/${currentPage + 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
passHref

View File

@@ -1,13 +1,15 @@
import BlogPostListScroll from './components/BlogPostListScroll'
import BlogPostListPage from './components/BlogPostListPage'
import LayoutBase from './LayoutBase'
import BLOG from '@/blog.config'
export const LayoutCategory = props => {
const { tags, posts, category } = props
const { category } = props
return <LayoutBase {...props}>
<div className="cursor-pointer px-5 py-1 mb-2 font-light hover:underline hover:text-indigo-700 dark:hover:text-indigo-400 transform text-center dark:text-white">
<i className="mr-1 far fa-folder" />
{category}
</div>
<BlogPostListScroll posts={posts} tags={tags} currentCategory={category} />
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase>
}

View File

@@ -1,10 +1,21 @@
import BLOG from '@/blog.config'
import BlogPostListScroll from './components/BlogPostListScroll'
import BlogPostListPage from './components/BlogPostListPage'
import LayoutBase from './LayoutBase'
import TagItemMini from '../next/components/TagItemMini'
import React from 'react'
export const LayoutTag = (props) => {
const { tags, posts, tag } = props
const currentTag = props.tags.find((t) => {
return t.name === props.tag
})
return <LayoutBase {...props}>
<BlogPostListScroll posts={posts} tags={tags} currentTag={tag}/>
</LayoutBase>
{currentTag && (
<div className="cursor-pointer px-5 py-1 mb-2 font-light hover:underline hover:text-indigo-700 dark:hover:text-indigo-400 transform text-center dark:text-white">
<TagItemMini tag={currentTag} />
</div>
)}
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase>
}

View File

@@ -14,8 +14,7 @@ import BlogPostListEmpty from './BlogPostListEmpty'
const BlogPostListPage = ({ page = 1, posts = [], postCount }) => {
const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
const showPagination = postCount >= BLOG.POSTS_PER_PAGE
if (!posts || posts.length === 0) {
if (!posts || posts.length === 0 || page > totalPage) {
return <BlogPostListEmpty />
} else {
return (

View File

@@ -5,6 +5,7 @@ import { useGlobal } from '@/lib/global'
import throttle from 'lodash.throttle'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import CONFIG_HEXO from '../config_hexo'
import { getListByPage } from '@/lib/utils'
/**
* 博客列表滚动分页
@@ -16,7 +17,7 @@ import CONFIG_HEXO from '../config_hexo'
const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_HEXO.POST_LIST_SUMMARY }) => {
const postsPerPage = BLOG.POSTS_PER_PAGE
const [page, updatePage] = useState(1)
const postsToShow = getPostByPage(page, posts, postsPerPage)
const postsToShow = getListByPage(posts, page, postsPerPage)
let hasMore = false
if (posts) {
@@ -62,9 +63,7 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_HE
</div>
<div>
<div onClick={() => {
handleGetMore()
}}
<div onClick={() => { handleGetMore() }}
className='w-full my-4 py-4 text-center cursor-pointer rounded-xl dark:text-gray-200'
> {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE}`} </div>
</div>
@@ -72,17 +71,4 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_HE
}
}
/**
* 获取从第1页到指定页码的文章
* @param page 第几页
* @param totalPosts 所有文章
* @param postsPerPage 每页文章数量
* @returns {*}
*/
const getPostByPage = function (page, totalPosts, postsPerPage) {
return totalPosts.slice(
0,
postsPerPage * page
)
}
export default BlogPostListScroll

View File

@@ -1,4 +1,3 @@
import BLOG from '@/blog.config'
import Link from 'next/link'
import { useRouter } from 'next/router'
@@ -13,78 +12,66 @@ const PaginationNumber = ({ page, totalPage }) => {
const router = useRouter()
const currentPage = +page
const showNext = page < totalPage
const pages = generatePages(page, currentPage, totalPage)
const pagePrefix = router.asPath.replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
const pages = generatePages(pagePrefix, page, currentPage, totalPage)
return (
<div className="mt-10 mb-5 flex justify-center items-end font-medium text-black duration-500 dark:text-gray-300 py-3 space-x-2">
{/* 上一页 */}
<Link
href={{
pathname:
currentPage - 1 === 1
? `${BLOG.SUB_PATH || '/'}`
: `/page/${currentPage - 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
passHref
>
<div
rel="prev"
className={`${currentPage === 1 ? 'invisible' : 'block'
} pb-0.5 border-white dark:border-indigo-700 hover:border-indigo-400 dark:hover:border-indigo-400 w-6 text-center cursor-pointer duration-200 hover:font-bold`}
>
<i className="fas fa-angle-left" />
</div>
</Link>
<div className="mt-10 mb-5 flex justify-center items-end font-medium text-black duration-500 dark:text-gray-300 py-3 space-x-2">
{/* 上一页 */}
<Link
href={{
pathname: currentPage === 2
? pagePrefix
: `${pagePrefix}/page/${currentPage - 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
>
<a rel="prev" className={`${currentPage === 1 ? 'invisible' : 'block'} pb-0.5 border-white dark:border-indigo-700 hover:border-indigo-400 dark:hover:border-indigo-400 w-6 text-center cursor-pointer duration-200 hover:font-bold`}>
<i className="fas fa-angle-left" />
</a>
</Link>
{pages}
{pages}
{/* 下一页 */}
<Link
href={{
pathname: `/page/${currentPage + 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
passHref
>
<div
rel="next"
className={`${+showNext ? 'block' : 'invisible'
} pb-0.5 border-b border-indigo-300 dark:border-indigo-700 hover:border-indigo-400 dark:hover:border-indigo-400 w-6 text-center cursor-pointer duration-500 hover:font-bold`}
>
<i className="fas fa-angle-right" />
{/* 下一页 */}
<Link
href={{
pathname: `${pagePrefix}/page/${currentPage + 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
>
<a rel="next" className={`${+showNext ? 'block' : 'invisible'} pb-0.5 border-b border-indigo-300 dark:border-indigo-700 hover:border-indigo-400 dark:hover:border-indigo-400 w-6 text-center cursor-pointer duration-500 hover:font-bold`}>
<i className="fas fa-angle-right" />
</a>
</Link>
</div>
</Link>
</div>
)
}
function getPageElement(page, currentPage) {
function getPageElement(page, currentPage, pagePrefix) {
return (
<Link href={page === 1 ? '/' : `/page/${page}`} key={page} passHref>
<a
className={
(page + '' === currentPage + ''
? 'font-bold bg-indigo-400 dark:bg-indigo-500 text-white '
: 'border-b duration-500 border-indigo-300 hover:border-indigo-400 ') +
' border-white dark:border-indigo-700 dark:hover:border-indigo-400 cursor-pointer pb-0.5 w-6 text-center font-light hover:font-bold'
}
>
{page}
</a>
</Link>
<Link href={page === 1 ? `${pagePrefix}` : `${pagePrefix}/page/${page}`} key={page} passHref>
<a className={
(page + '' === currentPage + ''
? 'font-bold bg-indigo-400 dark:bg-indigo-500 text-white '
: 'border-b duration-500 border-indigo-300 hover:border-indigo-400 ') +
' border-white dark:border-indigo-700 dark:hover:border-indigo-400 cursor-pointer pb-0.5 w-6 text-center font-light hover:font-bold'
} >
{page}
</a>
</Link>
)
}
function generatePages(page, currentPage, totalPage) {
function generatePages(pagePrefix, page, currentPage, totalPage) {
const pages = []
const groupCount = 7 // 最多显示页签数
if (totalPage <= groupCount) {
for (let i = 1; i <= totalPage; i++) {
pages.push(getPageElement(i, page))
pages.push(getPageElement(i, page, pagePrefix))
}
} else {
pages.push(getPageElement(1, page))
pages.push(getPageElement(1, page, pagePrefix))
const dynamicGroupCount = groupCount - 2
let startPage = currentPage - 2
if (startPage <= 1) {
@@ -99,7 +86,7 @@ function generatePages(page, currentPage, totalPage) {
for (let i = 0; i < dynamicGroupCount; i++) {
if (startPage + i < totalPage) {
pages.push(getPageElement(startPage + i, page))
pages.push(getPageElement(startPage + i, page, pagePrefix))
}
}
@@ -107,7 +94,7 @@ function generatePages(page, currentPage, totalPage) {
pages.push(<div key={-2}>... </div>)
}
pages.push(getPageElement(totalPage, page))
pages.push(getPageElement(totalPage, page, pagePrefix))
}
return pages
}

View File

@@ -1,11 +1,13 @@
import LayoutBase from './LayoutBase'
import BlogPostListScroll from './components/BlogPostListScroll'
import BlogPostListPage from './components/BlogPostListPage'
import BLOG from '@/blog.config'
export const LayoutCategory = props => {
const { category } = props
const slotTop = <div className='flex items-center font-sans p-8'><div className='text-xl'><i className='mr-2 fas fa-th'/>分类</div>{category}</div>
const slotTop = <div className='flex items-center font-sans p-8'><div className='text-xl'><i className='mr-2 fas fa-th' />分类</div>{category}</div>
return <LayoutBase {...props} slotTop={slotTop}>
<BlogPostListScroll {...props} />
</LayoutBase>
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase>
}

View File

@@ -1,11 +1,13 @@
import LayoutBase from './LayoutBase'
import BlogPostListScroll from './components/BlogPostListScroll'
import BLOG from '@/blog.config'
import BlogPostListPage from './components/BlogPostListPage'
export const LayoutTag = (props) => {
const { tag } = props
const slotTop = <div className='flex items-center font-sans p-8'><div className='text-xl'><i className='mr-2 fas fa-tag'/>标签</div>{tag}</div>
return <LayoutBase {...props} slotTop={slotTop}>
<BlogPostListScroll {...props} />
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
</LayoutBase>
}

View File

@@ -1,4 +1,3 @@
import BLOG from '@/blog.config'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useGlobal } from '@/lib/global'
@@ -15,14 +14,16 @@ const PaginationSimple = ({ page, totalPage }) => {
const router = useRouter()
const currentPage = +page
const showNext = currentPage < totalPage
const pagePrefix = router.asPath.replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '')
return (
<div className="my-10 flex justify-between font-medium text-black dark:text-gray-100 space-x-2">
<Link
href={{
pathname:
currentPage === 2
? `${BLOG.SUB_PATH || '/'}`
: `/page/${currentPage - 1}`,
? `${pagePrefix}`
: `${pagePrefix}/page/${currentPage - 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
passHref
@@ -38,7 +39,7 @@ const PaginationSimple = ({ page, totalPage }) => {
</Link>
<Link
href={{
pathname: `/page/${currentPage + 1}`,
pathname: `${pagePrefix}/page/${currentPage + 1}`,
query: router.query.s ? { s: router.query.s } : {}
}}
passHref

View File

@@ -2,15 +2,20 @@ import LayoutBase from './LayoutBase'
import StickyBar from './components/StickyBar'
import CategoryList from './components/CategoryList'
import BlogPostListScroll from './components/BlogPostListScroll'
import BlogPostListPage from './components/BlogPostListPage'
import BLOG from '@/blog.config'
export const LayoutCategory = (props) => {
const { tags, posts, category, categories } = props
const { category, categories } = props
return <LayoutBase currentCategory={category} {...props}>
<StickyBar>
<CategoryList currentCategory={category} categories={categories} />
</StickyBar>
<div className='md:mt-8'>
<BlogPostListScroll posts={posts} tags={tags} currentCategory={category}/>
{BLOG.POST_LIST_STYLE !== 'page'
? <BlogPostListScroll {...props} showSummary={true} />
: <BlogPostListPage {...props} />
}
</div>
</LayoutBase>
}

View File

@@ -2,16 +2,21 @@ import LayoutBase from './LayoutBase'
import StickyBar from './components/StickyBar'
import TagList from './components/TagList'
import BlogPostListScroll from './components/BlogPostListScroll'
import BlogPostListPage from './components/BlogPostListPage'
import BLOG from '@/blog.config'
export const LayoutTag = (props) => {
const { tags, posts, tag } = props
const { tags, tag } = props
return <LayoutBase currentTag={tag} {...props}>
<StickyBar>
<TagList tags={tags} currentTag={tag}/>
</StickyBar>
<div className='md:mt-8'>
<BlogPostListScroll posts={posts} tags={tags} currentTag={tag}/>
{BLOG.POST_LIST_STYLE !== 'page'
? <BlogPostListScroll {...props} showSummary={true} />
: <BlogPostListPage {...props} />
}
</div>
</LayoutBase>
}

View File

@@ -39,7 +39,7 @@ const StickyBar = ({ children }) => {
return (
<div id='sticky-bar' className='sticky flex-grow justify-center top-0 duration-500 z-10 pb-16'>
<div className='glassmorphism dark:border-gray-600 px-5 absolute shadow-md border w-full scroll-hidden'>
<div className='bg-white dark:bg-black px-5 absolute shadow-md w-full scroll-hidden'>
<div id='tag-container' className="md:pl-3 overflow-x-auto">
{children}
</div>