diff --git a/src/app/[lang]/posts/InfiniteList.tsx b/src/app/[lang]/posts/InfiniteList.tsx
new file mode 100644
index 0000000..745249b
--- /dev/null
+++ b/src/app/[lang]/posts/InfiniteList.tsx
@@ -0,0 +1,34 @@
+'use client';
+
+import { memo } from 'react';
+import { useSearchParams } from 'next/navigation';
+import Link from '@/components/Link';
+import List from '@/components/List';
+import { clamp } from '@/utils/math';
+import Article from '../Article';
+
+type InfiniteListProps = {
+ items: DataFrontmatter[];
+};
+
+const MemoArticle = memo(Article);
+
+function InfiniteList({ items }: InfiniteListProps) {
+ const searchParams = useSearchParams();
+ const total = items.length;
+ const clampLimit = clamp(1, total);
+ const limit = clampLimit(parseInt(searchParams.get('limit') || '10', 10));
+
+ return (
+ <>
+
+ {limit < total && (
+
+ 更多文章
+
+ )}
+ >
+ );
+}
+
+export default InfiniteList;
diff --git a/src/app/[lang]/posts/__tests__/infiniteList.test.tsx b/src/app/[lang]/posts/__tests__/infiniteList.test.tsx
new file mode 100644
index 0000000..d04b35e
--- /dev/null
+++ b/src/app/[lang]/posts/__tests__/infiniteList.test.tsx
@@ -0,0 +1,37 @@
+import { render, screen } from '@testing-library/react';
+import InfiniteList from '../InfiniteList';
+
+jest.mock('next/navigation', () => ({
+ useRouter() {
+ return {
+ prefetch: () => null,
+ };
+ },
+ useSearchParams() {
+ return new URLSearchParams();
+ },
+ usePathname() {
+ return '';
+ },
+}));
+
+describe('InfiniteList component', () => {
+ it('should render correct element', () => {
+ const generateMockPosts = (count: number): DataFrontmatter[] =>
+ new Array(count).fill(null).map((_, i) => ({
+ id: `test-id-${i}`,
+ title: `test-title-${i}`,
+ date: '2024/1/4',
+ description: `test-description-${i}`,
+ categories: [],
+ tags: [],
+ }));
+ render();
+
+ const link = screen.getByRole('link', { name: '更多文章' });
+ const list = screen.getByRole('list');
+
+ expect(list).toBeInTheDocument();
+ expect(link).toBeInTheDocument();
+ });
+});
diff --git a/src/app/[lang]/posts/__tests__/page.test.tsx b/src/app/[lang]/posts/__tests__/page.test.tsx
index 0cb9d11..a1b3e58 100644
--- a/src/app/[lang]/posts/__tests__/page.test.tsx
+++ b/src/app/[lang]/posts/__tests__/page.test.tsx
@@ -6,6 +6,17 @@ jest.mock('@/utils/mdx', () => ({
getAllDataFrontmatter: () => [],
}));
+jest.mock('next/navigation', () => ({
+ useRouter() {
+ return {
+ prefetch: () => null,
+ };
+ },
+ useSearchParams() {
+ return new URLSearchParams();
+ },
+}));
+
describe('Posts page', () => {
it('should render correct element', async () => {
// Arrange
diff --git a/src/app/[lang]/posts/page.tsx b/src/app/[lang]/posts/page.tsx
index 663e7ff..13d1d08 100644
--- a/src/app/[lang]/posts/page.tsx
+++ b/src/app/[lang]/posts/page.tsx
@@ -3,11 +3,10 @@ import type { Metadata } from 'next';
import { getDictionary } from '~/i18n';
import Container from '@/components/Container';
import { H1 } from '@/components/Heading';
-import List from '@/components/List';
import { getAllDataFrontmatter } from '@/utils/mdx';
import type { RootParams } from '../layout';
-import Article from '../Article';
+import InfiniteList from './InfiniteList';
export async function generateMetadata({
params: { lang },
@@ -30,7 +29,7 @@ async function RootPage({ params: { lang } }: RootParams) {
{metadata.description}
-
+
>
);
diff --git a/src/components/Link/Link.tsx b/src/components/Link/Link.tsx
index 3ceeca2..a4ef6f5 100644
--- a/src/components/Link/Link.tsx
+++ b/src/components/Link/Link.tsx
@@ -27,10 +27,11 @@ function Link({
const pathname = usePathname();
const isObjectHref = typeof href === 'object' || href.startsWith('#');
+ const isQueryLink = !isObjectHref && href.startsWith('?');
const isAnchorLink = !isObjectHref && href.startsWith('#');
const isInternalLink = !isObjectHref && href.startsWith('/');
- if (isObjectHref || isAnchorLink) {
+ if (isObjectHref || isAnchorLink || isQueryLink) {
return (
{children}
diff --git a/src/utils/__tests__/math.test.ts b/src/utils/__tests__/math.test.ts
index b4bfe1a..fd292e3 100644
--- a/src/utils/__tests__/math.test.ts
+++ b/src/utils/__tests__/math.test.ts
@@ -1,4 +1,4 @@
-import { toFixedNumber } from '../math';
+import { toFixedNumber, clamp } from '../math';
describe('math function', () => {
it.each([
@@ -18,3 +18,20 @@ describe('math function', () => {
}
);
});
+
+describe('clamp function', () => {
+ it.each([
+ [1, 3, 2, 2],
+ [1, 3, 4, 3],
+ [1, 3, 0, 1],
+ [3, 1, 2, 2],
+ [3, 1, 4, 3],
+ [3, 1, 0, 1],
+ ])(
+ 'should clamp a number between the given range',
+ (number1, number2, number, expected) => {
+ const result = clamp(number1, number2)(number);
+ expect(result).toBe(expected);
+ }
+ );
+});
diff --git a/src/utils/math.ts b/src/utils/math.ts
index 46b6fea..302f090 100644
--- a/src/utils/math.ts
+++ b/src/utils/math.ts
@@ -4,3 +4,16 @@ export const toFixedNumber = (digits: number) => (value: number) => {
return Math.round(value * pow) / pow;
};
+
+export const clamp = (number1: number, number2: number) => (number: number) => {
+ const min = Math.min(number1, number2);
+ const max = Math.max(number1, number2);
+
+ if (Number.isNaN(number) || number < min) {
+ return min;
+ }
+ if (number > max) {
+ return max;
+ }
+ return number;
+};
diff --git a/src/utils/mdx.ts b/src/utils/mdx.ts
index c7ed8ed..554cfc7 100644
--- a/src/utils/mdx.ts
+++ b/src/utils/mdx.ts
@@ -16,7 +16,9 @@ import { compareDates } from './date';
const ROOT_PATH = process.cwd();
/** Retrieve all data front matter sorted by date */
-export async function getAllDataFrontmatter(dirType: DataDirType) {
+export async function getAllDataFrontmatter(
+ dirType: DataDirType
+): Promise {
const dirPath = path.join(ROOT_PATH, 'data', dirType);
const fileNames = fs.readdirSync(dirPath);
const uniqueIdsSet = new Set();
@@ -44,7 +46,10 @@ export async function getAllDataFrontmatter(dirType: DataDirType) {
}
/** Retrieve data content and front matter for a specific data file by its id. */
-export async function getDataById(dirType: DataDirType, id: string) {
+export async function getDataById(
+ dirType: DataDirType,
+ id: string
+): Promise {
try {
const dirPath = path.join(ROOT_PATH, 'data', dirType);
const mdxPath = path.join(dirPath, `${id}.mdx`);
diff --git a/types/data.d.ts b/types/data.d.ts
index 1c901ca..cea429e 100644
--- a/types/data.d.ts
+++ b/types/data.d.ts
@@ -11,3 +11,9 @@ type DataFrontmatter = {
readonly description: string;
readonly image?: string;
};
+
+type Data = {
+ id: string;
+ content: React.ReactElement;
+ frontmatter: DataFrontmatter;
+};