根據 Astro 文檔,我們可以使用 remark 插件在前置資料中添加閱讀時間屬性。然而,由於某些原因,我們無法按照 Astro 文檔中的說明添加此功能。因此,為了實現這一點,我們需要稍微調整一下。本文將演示如何做到這一點。
目錄
在 PostDetails 中添加閱讀時間
步驟 (1) 安裝所需的依賴項。
npm install reading-time mdast-util-to-string
步驟 (2) 在 utils 目錄下創建 remark-reading-time.mjs 文件
import getReadingTime from "reading-time";
import { toString } from "mdast-util-to-string";
export function remarkReadingTime() {
return function (tree, { data }) {
const textOnPage = toString(tree);
const readingTime = getReadingTime(textOnPage);
// readingTime.text 會給我們一個友好的字符串表示的閱讀時間,
// 即 "3 min read"
data.astro.frontmatter.minutesRead = readingTime.text;
};
}
步驟 (3) 將插件添加到 astro.config.ts
import { remarkReadingTime } from "./src/utils/remark-reading-time.mjs"; // 確保你的相對路徑是正確的
// https://astro.build/config
export default defineConfig({
site: SITE.website,
integrations: [
// 其他集成
],
markdown: {
remarkPlugins: [
remarkToc,
remarkReadingTime, // 👈🏻 我們的插件
[
remarkCollapse,
{
test: "目錄",
},
],
],
// 其他配置
},
// 其他配置
});
步驟 (4) 將 readingTime 添加到博客模式 (src/content/config.ts)
import { SITE } from "@config";
import { defineCollection, z } from "astro:content";
const blog = defineCollection({
type: "content_layer",
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: ({ image }) =>
z.object({
// 其他...
canonicalURL: z.string().optional(),
readingTime: z.string().optional(), // 👈🏻 readingTime 前置資料
// 其他...
}),
});
export const collections = { blog };
步驟 (5) 在 src/utils 目錄下創建一個名為 getPostsWithRT.ts 的新文件。
import type { CollectionEntry } from "astro:content";
import { slugifyStr } from "./slugify";
interface Frontmatter {
frontmatter: {
title: string;
minutesRead: string;
};
}
export const getReadingTime = async () => {
// 使用 glob 獲取所有文章。這是為了獲取更新的前置資料
const globPosts = import.meta.glob<Frontmatter>("../content/blog/*.md");
// 然後,將這些前置資料值設置在一個 JS Map 中,使用鍵值對
const mapFrontmatter = new Map();
const globPostsValues = Object.values(globPosts);
await Promise.all(
globPostsValues.map(async globPost => {
const { frontmatter } = await globPost();
mapFrontmatter.set(
slugifyStr(frontmatter.title),
frontmatter.minutesRead
);
})
);
return mapFrontmatter;
};
const getPostsWithRT = async (posts: CollectionEntry<"blog">[]) => {
const mapFrontmatter = await getReadingTime();
return posts.map(post => {
post.data.readingTime = mapFrontmatter.get(slugifyStr(post.data.title));
return post;
});
};
export default getPostsWithRT;
步驟 (6) 將 src/pages/posts/[slug]/index.astro 的 getStaticPaths 重構如下
---
// 其他導入
import getPostsWithRT from "@utils/getPostsWithRT";
export interface Props {
post: CollectionEntry<"blog">;
}
export async function getStaticPaths() {
const posts = await getCollection("blog", ({ data }) => !data.draft);
const postsWithRT = await getPostsWithRT(posts); // 使用此函數替換閱讀時間邏輯
const postResult = postsWithRT.map(post => ({ // 確保用 postsWithRT 替換 posts
params: { slug: post.slug },
props: { post },
}));
// 其他代碼
步驟 (7) 將 PostDetails.astro 重構如下。現在你可以在 PostDetails.astro 中訪問並顯示 readingTime
---
// 導入
export interface Props {
post: CollectionEntry<"blog">;
}
const { post } = Astro.props;
const {
title,
author,
description,
ogImage,
readingTime, // 我們現在可以直接從前置資料中訪問 readingTime
pubDatetime,
modDatetime,
tags } = post.data;
// 其他代碼
---
在 PostDetails 之外訪問閱讀時間(可選)
通過遵循前面的步驟,你現在可以在文章詳情頁中訪問 readingTime 前置資料屬性。有時,這正是你想要的。如果是這樣,你可以跳到下一節。然而,如果你想在索引、文章和技術上任何地方顯示「預估閱讀時間」,你需要執行以下額外步驟。
步驟 (1) 更新 utils/getSortedPosts.ts 如下
import type { CollectionEntry } from "astro:content";
import getPostsWithRT from "./getPostsWithRT";
const getSortedPosts = async (posts: CollectionEntry<"blog">[]) => {
// 確保此函數是異步的
const postsWithRT = await getPostsWithRT(posts); // 添加閱讀時間
return postsWithRT
.filter(({ data }) => !data.draft)
.sort(
(a, b) =>
Math.floor(
new Date(b.data.modDatetime ?? b.data.pubDatetime).getTime() / 1000
) -
Math.floor(
new Date(a.data.modDatetime ?? a.data.pubDatetime).getTime() / 1000
)
);
};
export default getSortedPosts;
步驟 (2) 確保重構每個使用 getSortedPosts 函數的文件。你只需在 getSortedPosts 函數前添加 await 關鍵字。
使用 getSortedPosts 函數的文件如下
- src/pages/index.astro
- src/pages/search.astro
- src/pages/rss.xml.ts
- src/pages/posts/[…page].astro
- src/pages/posts/[slug]/index.astro
- src/utils/getPostsByTag.ts
你所要做的就是這樣
const sortedPosts = getSortedPosts(posts); // 舊代碼 ❌
const sortedPosts = await getSortedPosts(posts); // 新代碼 ✅
現在,getPostsByTag 函數變成了一個異步函數。因此,我們也需要 await getPostsByTag 函數。
- src/pages/tags/[tag]/[page].astro
- src/pages/tags/[tag]/index.astro
const postsByTag = getPostsByTag(posts, tag); // 舊代碼 ❌
const postsByTag = await getPostsByTag(posts, tag); // 新代碼 ✅
此外,更新 src/pages/tags/[tag]/[page].astro 的 getStaticPaths 如下:
export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
const posts = await getCollection("blog");
const tags = getUniqueTags(posts);
// 確保等待承諾
const paths = await Promise.all(
tags.map(async ({ tag, tagName }) => {
const tagPosts = await getPostsByTag(posts, tag);
return paginate(tagPosts, {
params: { tag },
props: { tagName },
pageSize: SITE.postPerPage,
});
})
);
return paths.flat(); // 展平數組
}
現在你可以在 PostDetails 之外的其他地方訪問 readingTime
顯示閱讀時間(可選)
既然你現在可以在文章詳情(或如果你完成了上述部分,則在任何地方)訪問 readingTime,那麼是否顯示 readingTime 完全取決於你。
但在本節中,我將向你展示如何在我的組件中顯示 readingTime。這是可選的。如果你願意,可以忽略本節。
步驟 (1) 更新 Datetime 組件以顯示 readingTime
// 其他代碼
interface Props extends DatetimesProps, EditPostProps {
size?: "sm" | "lg";
className?: string;
readingTime: string | undefined; // 新類型
}
export default function Datetime({
pubDatetime,
modDatetime,
size = "sm",
className = "",
editPost,
postId,
readingTime, // 新屬性
}: Props) {
return (
// 其他代碼
<span className={`italic ${size === "sm" ? "text-sm" : "text-base"}`}>
<FormattedDatetime pubDatetime={pubDatetime} modDatetime={modDatetime} />
<span> ({readingTime})</span> {/* 顯示閱讀時間 */}
{size === "lg" && <EditPost editPost={editPost} postId={postId} />}
</span>
// 其他代碼
);
}
步驟 (2) 然後,從其父組件傳遞 readingTime 屬性。
文件:Card.tsx
export default function Card({ href, frontmatter, secHeading = true }: Props) {
const { title, pubDatetime, modDatetime description, readingTime } = frontmatter; // 不要忘記在這裡添加 readingTime
return (
...
<Datetime
pubDatetime={pubDatetime}
modDatetime={modDatetime}
readingTime={readingTime}
/>
...
);
}
文件:PostDetails.astro
// 其他代碼
<main id="main-content">
<h1 class="post-title">{title}</h1>
<Datetime
pubDatetime={pubDatetime}
modDatetime={modDatetime}
size="lg"
className="my-2"
readingTime={readingTime}
/>
{/* 其他代碼 */}
</main>
// 其他代碼
結論
通過遵循提供的步驟和調整,你現在可以將這個有用的功能集成到你的內容中。我希望這篇文章能幫助你在博客中添加 readingTime。AstroPaper 可能會在未來的版本中默認包含閱讀時間。🤷🏻♂️
感謝閱讀 🙏🏻