Doucsaurus自定义blog列表的分页
· 阅读需 6 分钟
默认情况下,Docusaurus 的 blog 列表分页是「较新的博文/较旧的博文」的形式。如果想改成数字按钮形式,需要自定义分页组件。
我的需求如下:
- 需要一个除了
/blog
以外的路径,例如:/code
- code 列表页可以使用自定义的分页组件,blog 列表页使用默认的分页组件
定义独立的 blog 类型
Docusaurus 允许通过 @docusaurus/plugin-content-blog
为不同路径定义多个独立的博客类型。
修改默认的分页组件
pnpm docusaurus swizzle @docusaurus/theme-classic BlogListPaginator --eject --typescript
修改 src/theme/BlogListPaginator/index.tsx
文件:
import React from 'react';
import type { Props } from '@theme/BlogListPaginator';
import CustomPaginator from '../../components/CustomBlogPaginator';
export default function BlogListPaginator(props: Props): JSX.Element {
return <CustomPaginator {...props} />;
}
创建自定义分页组件
在 src/components
中创建一个 CustomBlogPaginator 组件,目录结构如下:
- src
- components
- CustomBlogPaginator
- CustomPaginator.tsx
- index.module.css
- index.tsx
判断路径中是否包含 /code
,如果是,则使用自定义分页组件,否则使用默认的分页组件。
index.tsx
import React from 'react';
import BlogListPaginator from '@theme-original/BlogListPaginator';
import CustomPaginator from './CustomPaginator';
export default function CodePaginator({ metadata }) {
if (metadata.permalink.includes('/code')) {
return <CustomPaginator metadata={metadata} />;
}
return <BlogListPaginator metadata={metadata} />;
}
定义分页组件
CustomPaginator.tsx
import React, { useState } from 'react';
import type { Props } from '@theme/BlogListPaginator';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import styles from './index.module.css';
interface ChevronIconProps {
left: string;
right: string;
doubleLeft: string;
doubleRight: string;
ellipsis: string;
}
type ChevronIconType = keyof ChevronIconProps;
const dataMap: ChevronIconProps = {
left: 'M15.75 19.5 8.25 12l7.5-7.5',
right: 'm8.25 4.5 7.5 7.5-7.5 7.5',
doubleLeft: 'm18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5',
doubleRight: 'm5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5',
ellipsis:
'M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z'
};
const chevronIcon = (type: ChevronIconType) => {
let data = dataMap[type];
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
style={{ width: '20px', height: '20px', verticalAlign: 'middle' }}
>
<path strokeLinecap="round" strokeLinejoin="round" d={data} />;
</svg>
);
};
export default function BlogListPaginator(props: Props): JSX.Element {
const { totalPages, page, permalink } = props.metadata;
const [hoveredEllipsis, setHoveredEllipsis] = useState(null);
// 生成分页按钮的路径
const getPageLink = pageNumber => {
if (pageNumber === 1) {
return permalink.replace(/\/page\/\d+$/, ''); // 第一页为根路径
}
return `${permalink.replace(/\/page\/\d+$/, '')}/page/${pageNumber}`;
};
// 动态生成分页显示范围
const handlePageRange = () => {
const maxButtons = 5; // 默认显示的按钮数量
const range = [];
// 始终显示第一页
range.push(1);
if (totalPages <= maxButtons) {
// 如果总页数少于等于 5,则直接显示所有页码
for (let i = 2; i <= totalPages; i++) {
range.push(i);
}
} else if (page <= 3) {
// 当前页在 1-3 时,显示前 5 页和省略号
for (let i = 2; i <= Math.min(5, totalPages - 1); i++) {
range.push(i);
}
range.push('next-ellipsis');
range.push(totalPages);
} else if (page === 4) {
// 当前页为第 4 页时,显示前 6 页和省略号
for (let i = 2; i <= 6; i++) {
range.push(i);
}
range.push('next-ellipsis');
range.push(totalPages);
} else if (page >= 5 && page <= totalPages - 4) {
// 当前页在中间时,显示左右省略号和前后 2 页
range.push('prev-ellipsis');
for (let i = page - 2; i <= page + 2; i++) {
range.push(i);
}
range.push('next-ellipsis');
range.push(totalPages);
} else {
// 当前页接近末尾时,显示最后 5 页和省略号
range.push('prev-ellipsis');
for (let i = totalPages - 4; i < totalPages; i++) {
range.push(i);
}
range.push(totalPages);
}
return range;
};
const pageNumbers = handlePageRange();
return (
<nav className="pagination-nav">
<ul className={styles.pagination}>
{/* 左箭头 */}
<li
className={clsx(styles.pageItem, page === 1 && styles.disabled)}
title={page === 1 ? '没有上一页' : '上一页'}
>
<Link to={page > 1 ? getPageLink(page - 1) : '#'} className={styles.pageLink} aria-disabled={page === 1}>
{chevronIcon('left')}
</Link>
</li>
{/* 数字按钮和省略号 */}
{pageNumbers.map((pageNumber, index) => {
if (pageNumber === 'prev-ellipsis' || pageNumber === 'next-ellipsis') {
const isPrev = pageNumber === 'prev-ellipsis';
return (
<li
key={`${pageNumber}-${index}`}
className={styles.pageItem}
onMouseEnter={() => setHoveredEllipsis(pageNumber)}
onMouseLeave={() => setHoveredEllipsis(null)}
>
<Link
to={getPageLink(isPrev ? Math.max(1, page - 5) : Math.min(totalPages, page + 5))}
title={isPrev ? '向前 5 页' : '向后 5 页'}
className={styles.pageLink}
>
{hoveredEllipsis === pageNumber
? isPrev
? chevronIcon('doubleLeft')
: chevronIcon('doubleRight')
: chevronIcon('ellipsis')}
</Link>
</li>
);
}
return (
<li key={pageNumber} className={clsx(styles.pageItem, pageNumber === page && styles.active)}>
<Link to={getPageLink(pageNumber)} className={styles.pageLink}>
{pageNumber}
</Link>
</li>
);
})}
{/* 右箭头 */}
<li
className={clsx(styles.pageItem, page === totalPages && styles.disabled)}
title={page === totalPages ? '没有下一页' : '下一页'}
>
<Link
to={page < totalPages ? getPageLink(page + 1) : '#'}
className={styles.pageLink}
aria-disabled={page === totalPages}
>
{chevronIcon('right')}
</Link>
</li>
</ul>
</nav>
);
}