summaryrefslogtreecommitdiff
path: root/src/app/(main)/(home)/posts/page.tsx
blob: 00e51c45235def891cbdcb36ea461d9557629d7b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import { postsPerPage } from '@/app/(main)/layout.config';
import { NumberedPagination } from '@/components/numbered-pagination';
import { PostCard } from '@/components/posts/post-card';
import { Section } from '@/components/section';
import { createMetadata } from '@/lib/metadata';
import { getPublishedPosts, type BlogPost } from '@/lib/payload-posts';
import { getSortedByDatePosts, type Page } from '@/lib/source';
import type { Metadata, ResolvingMetadata } from 'next';
import { notFound, redirect } from 'next/navigation';

// 统一的文章类型
interface UnifiedPost {
  id: string;
  title: string;
  description: string;
  image?: string;
  url: string;
  date: Date;
  author: string;
  tags?: string[];
  source: 'mdx' | 'payload';
}

// 将 MDX 文章转换为统一格式
function transformMdxPost(post: Page): UnifiedPost {
  return {
    id: `mdx-${post.url}`,
    title: post.data.title,
    description: post.data.description ?? '',
    image: post.data.image,
    url: post.url,
    date: new Date(post.data.date),
    author: post.data.author,
    tags: post.data.tags,
    source: 'mdx',
  };
}

// 将 Payload 文章转换为统一格式
function transformPayloadPost(post: BlogPost): UnifiedPost {
  return {
    id: `payload-${post.id}`,
    title: post.title,
    description: post.description,
    image: post.image,
    url: post.url,
    date: post.date,
    author: post.author,
    tags: post.tags,
    source: 'payload',
  };
}

// 获取所有文章(合并 MDX 和 Payload)
async function getAllPosts(): Promise<UnifiedPost[]> {
  // 获取 MDX 文章
  const mdxPosts = getSortedByDatePosts().map(transformMdxPost);

  // 获取 Payload 文章(获取全部,用于分页计算)
  const { posts: payloadPosts } = await getPublishedPosts({ limit: 1000 });
  const transformedPayloadPosts = payloadPosts.map(transformPayloadPost);

  // 合并并按日期排序
  const allPosts = [...mdxPosts, ...transformedPayloadPosts];
  allPosts.sort((a, b) => b.date.getTime() - a.date.getTime());

  return allPosts;
}

const CurrentPostsCount = ({
  startIndex,
  endIndex,
  totalPosts,
}: {
  startIndex: number;
  endIndex: number;
  totalPosts: number;
}) => {
  const start = startIndex + 1;
  const end = endIndex < totalPosts ? endIndex : totalPosts;
  if (start === end) return <span>({start})</span>;
  return (
    <span>
      ({start}-{end})
    </span>
  );
};

const Pagination = ({
  pageIndex,
  pageCount,
}: {
  pageIndex: number;
  pageCount: number;
}) => {
  const handlePageChange = async (page: number) => {
    'use server';
    redirect(`/posts?page=${page}`);
  };

  return (
    <Section className='bg-dashed'>
      <NumberedPagination
        currentPage={pageIndex + 1}
        totalPages={pageCount}
        paginationItemsToDisplay={5}
        onPageChange={handlePageChange}
      />
    </Section>
  );
};

export default async function Page(props: {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
  const searchParams = await props.searchParams;

  // 获取所有文章
  const allPosts = await getAllPosts();
  const totalPosts = allPosts.length;
  const pageCount = Math.ceil(totalPosts / postsPerPage);

  const pageIndex = searchParams.page
    ? Number.parseInt(
        Array.isArray(searchParams.page)
          ? searchParams.page[0] ?? ''
          : searchParams.page,
        10
      ) - 1
    : 0;

  if (pageIndex < 0 || (pageCount > 0 && pageIndex >= pageCount)) notFound();

  const startIndex = pageIndex * postsPerPage;
  const endIndex = startIndex + postsPerPage;
  const posts = allPosts.slice(startIndex, endIndex);

  return (
    <>
      <Section className='p-4 lg:p-6'>
        <h1 className='font-bold text-3xl leading-tight tracking-tighter md:text-4xl'>
          All {totalPosts} Posts{' '}
          <CurrentPostsCount
            startIndex={startIndex}
            endIndex={endIndex}
            totalPosts={totalPosts}
          />
        </h1>
      </Section>
      <Section className='h-full' sectionClassName='flex flex-1'>
        <div className='grid divide-y divide-dashed divide-border/70 text-left dark:divide-border'>
          {posts.map((post) => {
            const date = post.date.toDateString();
            return (
              <PostCard
                title={post.title}
                description={post.description}
                image={post.image}
                url={post.url}
                date={date}
                key={post.id}
                author={post.author}
                tags={post.tags}
              />
            );
          })}
        </div>
      </Section>
      {pageCount > 1 && <Pagination pageIndex={pageIndex} pageCount={pageCount} />}
    </>
  );
}

type Props = {
  params: Promise<{ slug: string[] }>;
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};

export async function generateMetadata(
  props: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const searchParams = await props.searchParams;

  const pageIndex = searchParams.page
    ? Number.parseInt(searchParams.page as string, 10)
    : 1;

  const isFirstPage = pageIndex === 1 || !searchParams.page;
  const pageTitle = isFirstPage ? 'Posts' : `Posts - Page ${pageIndex}`;
  const canonicalUrl = isFirstPage ? '/posts' : `/posts?page=${pageIndex}`;

  return createMetadata({
    title: pageTitle,
    description: `Posts${!isFirstPage ? ` - Page ${pageIndex}` : ''}`,
    openGraph: {
      url: canonicalUrl,
    },
    alternates: {
      canonical: canonicalUrl,
    },
  });
}