--- import { type CollectionEntry, render } from "astro:content"; import path from "node:path"; import License from "@components/misc/License.astro"; import Markdown from "@components/misc/Markdown.astro"; import I18nKey from "@i18n/i18nKey"; import { i18n } from "@i18n/translation"; import MainGridLayout from "@layouts/MainGridLayout.astro"; import { getSortedPosts } from "@utils/content-utils"; import { getDir, getPostUrlBySlug } from "@utils/url-utils"; import { Icon } from "astro-icon/components"; import { licenseConfig } from "src/config"; import ImageWrapper from "../../components/misc/ImageWrapper.astro"; import PostMetadata from "../../components/PostMeta.astro"; import { profileConfig, siteConfig } from "../../config"; import { formatDateToYYYYMMDD } from "../../utils/date-utils"; export async function getStaticPaths() { const blogEntries = await getSortedPosts(); return blogEntries.map((entry) => ({ params: { slug: entry.id }, props: { entry }, })); } interface Props { entry: CollectionEntry<"posts">; } const { entry }: Props = Astro.props; const { Content, headings, remarkPluginFrontmatter } = await render(entry); const postSlug = entry.id; const lastUpdatedAt = entry.data.updated ?? entry.data.published; const lastUpdatedLabel = formatDateToYYYYMMDD(lastUpdatedAt); const lastUpdatedTimestamp = lastUpdatedAt.getTime(); const postSources = import.meta.glob("../../content/posts/**/*.{md,mdx}", { query: "?raw", import: "default", eager: true, }) as Record; const normalizedPostSources = Object.fromEntries( Object.entries(postSources).map(([key, source]) => [ key.toLowerCase(), source, ]), ); function getPostSourceKey(entryId: string) { const normalizedEntryPath = `../../content/posts/${entryId}`.toLowerCase(); return Object.keys(postSources).find((key) => { const normalizedKey = key.toLowerCase(); return ( normalizedKey === normalizedEntryPath || normalizedKey === `${normalizedEntryPath}.md` || normalizedKey === `${normalizedEntryPath}.mdx` ); }); } function getPostSourcePath(sourceKey: string | undefined) { return sourceKey?.replace("../../content/posts/", "src/content/posts/"); } function getGitHubPostSourceUrl(repo: string | undefined, sourcePath: string | undefined) { if (!repo || !sourcePath) return ""; const normalizedRepo = repo.replace(/\.git$/, "").replace(/\/+$/, ""); const repoPath = normalizedRepo.includes("://") ? new URL(normalizedRepo).pathname.replace(/^\/+|\/+$/g, "") : normalizedRepo.replace(/^\/+|\/+$/g, ""); const [owner, repository] = repoPath.split("/"); if (!owner || !repository) return ""; const encodedSourcePath = sourcePath.split("/").map(encodeURIComponent).join("/"); return `https://raw.githubusercontent.com/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/refs/heads/main/${encodedSourcePath}`; } function parseAiSummaryValue(value: string) { const trimmed = value.trim(); if (!trimmed) return ""; if (trimmed.startsWith('"')) { try { return JSON.parse(trimmed); } catch { return trimmed; } } return trimmed; } function extractFrontmatterValue(frontmatter: string, key: string) { const valueMatch = frontmatter.match(new RegExp(`^${key}:[ \\t]*(.*)$`, "m")); if (!valueMatch) return ""; const inlineValue = valueMatch[1].trim(); if (inlineValue !== ">" && inlineValue !== "|") { return parseAiSummaryValue(inlineValue); } const afterValue = frontmatter.slice( valueMatch.index! + valueMatch[0].length, ); const lines = afterValue.split(/\r?\n/); const blockLines = []; for (const line of lines) { if (!line.startsWith(" ") && line.trim()) break; blockLines.push(line.replace(/^ {1,2}/, "")); } return blockLines.join("\n").trim(); } function getAiSummaryMeta(entryId: string) { const source = normalizedPostSources[`../../content/posts/${entryId}`.toLowerCase()] ?? normalizedPostSources[`../../content/posts/${entryId}.md`.toLowerCase()] ?? normalizedPostSources[`../../content/posts/${entryId}.mdx`.toLowerCase()]; const frontmatter = source?.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1]; return { summary: frontmatter ? extractFrontmatterValue(frontmatter, "aiSummary") : "", model: frontmatter ? extractFrontmatterValue(frontmatter, "aiSummaryModel") : "", }; } const aiSummaryMeta = getAiSummaryMeta(entry.id); const postSourceKey = getPostSourceKey(entry.id); const postSourcePath = getPostSourcePath(postSourceKey); const postSourceUrl = getGitHubPostSourceUrl(siteConfig.githubRepo, postSourcePath); const copyPageText = postSourceKey ? postSources[postSourceKey] : ""; const aiPrompt = `Read from ${postSourceUrl} so I can ask questions about it.`; const encodedAiPrompt = encodeURIComponent(aiPrompt); const copyPageAiLinks = [ { label: "ChatGPT", icon: "gpt", href: `https://chatgpt.com/?q=${encodedAiPrompt}`, }, { label: "Gemini", icon: "gemini", href: `https://gemini.google.com/app?q=${encodedAiPrompt}`, }, { label: "Claude", icon: "claude", href: `https://claude.ai/new?q=${encodedAiPrompt}`, }, { label: "Grok", icon: "grok", href: `https://grok.com/?q=${encodedAiPrompt}`, }, ]; const jsonLd = { "@context": "https://schema.org", "@type": "BlogPosting", headline: entry.data.title, description: entry.data.description || entry.data.title, keywords: entry.data.tags, author: { "@type": "Person", name: profileConfig.name, url: Astro.site, }, datePublished: formatDateToYYYYMMDD(entry.data.published), inLanguage: entry.data.lang ? entry.data.lang.replace("_", "-") : siteConfig.lang.replace("_", "-"), // TODO include cover image here }; // 获取头图 boolean,无需设置 true const showcover = entry.data.showcover; // 获取自定义的头图(Markdown 内部) const customcover = entry.data.customcover; const isOutdated = entry.data.outdated; ---
{remarkPluginFrontmatter.words} {" " + i18n(I18nKey.wordsCount)}
大约 {remarkPluginFrontmatter.minutes} {" " + i18n(remarkPluginFrontmatter.minutes === 1 ? I18nKey.minuteCount : I18nKey.minutesCount)}
加载中...
{entry.data.title}
{(entry.data.description || remarkPluginFrontmatter.excerpt) && (
{entry.data.description || remarkPluginFrontmatter.excerpt}
)}
{isOutdated ? ( <>本文部分内容可能已失效,请谨慎参考。 ) : ( <> 本文最后更新于 {lastUpdatedLabel},距今 计算中...。 随着时间推移,文中描述可能与当前实际情况有出入,请注意甄别。 )}
{!entry.data.image &&
}
{aiSummaryMeta.summary && (
AI 摘要{aiSummaryMeta.model && · {aiSummaryMeta.model}}
{aiSummaryMeta.summary}
)} {showcover && entry.data.image &&