master
1import { t } from './i18n';
2import { buildConfig } from '@/config';
3import type { BlogPostData } from '@/types/data';
4import type { BlogPost } from '@/types/data';
5import { type CollectionEntry, getCollection, render } from 'astro:content';
6import dayjs from 'dayjs';
7
8export async function getPosts() {
9 const posts = await getCollection('posts');
10 const onDev = import.meta.env.DEV;
11 if (onDev && buildConfig.showDraftsOnDev) {
12 const drafts = (await getCollection('drafts')) as unknown as CollectionEntry<'posts'>[];
13 drafts.forEach(async (draft) => {
14 if (draft.data.published !== undefined) return;
15 const { remarkPluginFrontmatter } = await render(draft);
16 const published = dayjs(remarkPluginFrontmatter.createAt as string).toDate();
17 draft.data.published = published;
18 draft.data.draft = true;
19 });
20 return [...posts, ...drafts];
21 }
22 return posts;
23}
24
25export async function getSortedPosts(): Promise<BlogPost[]> {
26 const allBlogPosts = (await getPosts()) as unknown as BlogPost[];
27 const sortedBlogPosts = allBlogPosts.sort(
28 (a: { data: BlogPostData }, b: { data: BlogPostData }) => {
29 const dateA = new Date(a.data.published);
30 const dateB = new Date(b.data.published);
31 return dateA > dateB ? -1 : 1;
32 }
33 );
34 return sortedBlogPosts;
35}
36
37export async function getPostsCount(): Promise<number> {
38 const allBlogPosts = (await getCollection('posts')) as unknown as {
39 body: string;
40 data: BlogPostData;
41 }[];
42 return allBlogPosts.length;
43}
44
45export async function getCategories(): Promise<Map<string, number>> {
46 const allBlogPosts = await getSortedPosts();
47 const categoryMap = new Map<string, number>();
48
49 allBlogPosts.forEach((post) => {
50 const category = post.data.category || t.meta.unCategorized();
51 categoryMap.set(category, (categoryMap.get(category) || 0) + 1);
52 });
53
54 return categoryMap;
55}
56
57export async function getTags(): Promise<Map<string, number>> {
58 const allBlogPosts = await getSortedPosts();
59 const tagMap = new Map<string, number>();
60
61 allBlogPosts.forEach((post) => {
62 const tags = post.data.tags || [];
63 tags.forEach((tag) => {
64 tagMap.set(tag, (tagMap.get(tag) || 0) + 1);
65 });
66 });
67
68 return tagMap;
69}
70
71export function getCategoryUrl(category: string | undefined) {
72 return category
73 ? `/archives/categories/${category.replaceAll(/[\\/]/g, '-')}/1/`
74 : `/archives/categories/uncategorized/1/`;
75}
76
77export function getTagUrl(tag: string) {
78 return tag === t.meta.unTagged()
79 ? `/archives/tags/untagged/1`
80 : `/archives/tags/${tag.replaceAll(/[\\/]/g, '-')}/1`;
81}
82
83/**
84 * 获取所有的引用,返回一个数组
85 */
86export async function getAllReferences() {
87 type frontmatterRef = {
88 /** 引用的文章,即 [[ref|alias]] 中的 ref 部分 */
89 reference: string;
90 /** 引用的上下文,即整个 xxxx[[ref|alias]]xxxx 的内容,截取前后 20 个字符 */
91 context: string;
92 /** 引用的偏移量,即 [[ref|alias]] 在上下文字符串中的起始和结束位置 */
93 offset: [number, number];
94 /** 引用应该被分配的 id,用于通过 #id 跳转 */
95 id: string;
96 };
97 type Article = {
98 /** 文章的标题 */
99 title: string;
100 /** 文章的集合,posts 或 spec,drafts 会被处理为 posts */
101 collection: 'posts' | 'spec';
102 /** 文章的 id,对 posts 来说是 slug 参数,对 spec 来说是文件名 */
103 id: string;
104 };
105
106 const posts = await getPosts();
107 const specs = await getCollection('spec');
108
109 const pathMap = [
110 ...posts.map((it) => ({
111 id: it.id,
112 title: it.data.title,
113 collection: it.collection,
114 filePath: it.filePath,
115 })),
116 ...specs.map((it) => ({
117 id: it.id,
118 title: it.data.title,
119 collection: it.collection,
120 filePath: it.filePath,
121 })),
122 ].reduce(
123 (acc, it) => {
124 const path = it.filePath!.replace('src/content/', '').split('.').slice(0, -1).join('.');
125 acc[path] = {
126 id: it.id,
127 title: it.title || path.split('/').slice(-1)[-1],
128 collection: it.collection,
129 };
130 return acc;
131 },
132 {} as Record<
133 string,
134 {
135 id: string;
136 title: string;
137 collection: 'posts' | 'spec';
138 }
139 >
140 );
141
142 const postsRefData = posts.map(async (post) => {
143 const { remarkPluginFrontmatter } = await render(post);
144 const references: frontmatterRef[] = remarkPluginFrontmatter.references || [];
145 return {
146 title: post.data.title,
147 colletion: 'posts',
148 id: post.id,
149 references: references.map((ref) => ({
150 reference: ref.reference.split('#')[0],
151 context: ref.context,
152 offset: ref.offset,
153 id: ref.id,
154 })),
155 };
156 });
157 const specRefData = specs.map(async (spec) => {
158 const { remarkPluginFrontmatter } = await render(spec);
159 const references: frontmatterRef[] = remarkPluginFrontmatter.references || [];
160 return {
161 title: spec.data.title || spec.filePath?.split('/').slice(-1)[0],
162 colletion: 'spec',
163 id: spec.id,
164 references,
165 };
166 });
167
168 const getArticle = (refPath: string): Article | null => {
169 let collection = refPath.split('/')[0];
170 if (!['posts', 'drafts', 'spec'].includes(collection)) {
171 collection = 'posts';
172 refPath = `posts/${refPath}`;
173 }
174 const data = pathMap[refPath];
175 if (!data) return null;
176 const { id, title } = data;
177 if (collection === 'spec') {
178 const article = specs.find((it) => it.id === id);
179 if (article) return { title, collection, id };
180 } else {
181 const article = posts.find((it) => it.id === id);
182 if (article) return { title, collection: 'posts', id };
183 }
184 return null;
185 };
186
187 const references: {
188 refBy: Article;
189 refTo: Article;
190 /** 引用的上下文,即整个 xxxx[[ref|alias]]xxxx 的内容,截取前后 20 个字符 */
191 context: string;
192 /** 引用的偏移量,即 [[ref|alias]] 在上下文字符串中的起始和结束位置 */
193 offset: [number, number];
194 /** 引用应该被分配的 id,用于通过 #id 跳转 */
195 id: string;
196 }[] = [
197 ...(await Promise.all(postsRefData)).flatMap((data) => {
198 const article: Article = {
199 title: data.title,
200 collection: data.colletion as 'posts' | 'spec',
201 id: data.id,
202 };
203 return data.references
204 .map((ref) => {
205 const { reference, context, offset, id } = ref;
206 const refTo = getArticle(reference);
207 if (refTo) return { refBy: article, refTo, context, offset, id };
208 return null;
209 })
210 .filter((it) => it !== null);
211 }),
212 ...(await Promise.all(specRefData)).flatMap((data) => {
213 const article: Article = {
214 title: data.title || data.id,
215 collection: data.colletion as 'posts' | 'spec',
216 id: data.id,
217 };
218 return data.references
219 .map((ref) => {
220 const { reference, context, offset, id } = ref;
221 const refTo = getArticle(reference);
222 if (refTo) return { refBy: article, refTo, context, offset, id };
223 return null;
224 })
225 .filter((it) => it !== null);
226 }),
227 ];
228 return references;
229}