Add sitemap and SEO metadata
This commit is contained in:
parent
21551e3a97
commit
b4185eb668
14 changed files with 393 additions and 27 deletions
|
|
@ -3,6 +3,7 @@ const links = [
|
|||
{ href: '/', label: '首页' },
|
||||
{ href: '/archive/', label: '归档' },
|
||||
{ href: '/tags/', label: '标签' },
|
||||
{ href: '/site-map/', label: '站点地图' },
|
||||
{ href: '/rss.xml', label: 'RSS' }
|
||||
];
|
||||
---
|
||||
|
|
|
|||
57
frontend/site/src/components/seo/SeoHead.astro
Normal file
57
frontend/site/src/components/seo/SeoHead.astro
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
import { site } from '../../lib/siteConfig';
|
||||
import { absoluteUrl } from '../../lib/seo';
|
||||
|
||||
type JsonLd = Record<string, unknown> | Record<string, unknown>[];
|
||||
|
||||
type Props = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
path?: string;
|
||||
type?: 'website' | 'article';
|
||||
publishedTime?: string;
|
||||
modifiedTime?: string;
|
||||
tags?: string[];
|
||||
jsonLd?: JsonLd;
|
||||
};
|
||||
|
||||
const {
|
||||
title = site.title,
|
||||
description = site.description,
|
||||
path = '/',
|
||||
type = 'website',
|
||||
publishedTime,
|
||||
modifiedTime,
|
||||
tags = [],
|
||||
jsonLd
|
||||
} = Astro.props;
|
||||
|
||||
const pageTitle = title === site.title ? site.title : `${title} | ${site.title}`;
|
||||
const canonicalUrl = absoluteUrl(path);
|
||||
const rssUrl = absoluteUrl('/rss.xml');
|
||||
---
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{pageTitle}</title>
|
||||
<meta name="description" content={description} />
|
||||
<meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1" />
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
<link rel="alternate" type="application/rss+xml" title={`${site.title} RSS`} href={rssUrl} />
|
||||
<meta property="og:site_name" content={site.title} />
|
||||
<meta property="og:title" content={pageTitle} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:type" content={type} />
|
||||
<meta property="og:url" content={canonicalUrl} />
|
||||
<meta property="og:locale" content={site.language.replace('-', '_')} />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:title" content={pageTitle} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
{publishedTime && <meta property="article:published_time" content={publishedTime} />}
|
||||
{modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}
|
||||
{tags.map((tag) => <meta property="article:tag" content={tag} />)}
|
||||
{
|
||||
jsonLd && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
|
||||
)
|
||||
}
|
||||
21
frontend/site/src/lib/seo.ts
Normal file
21
frontend/site/src/lib/seo.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { site } from './siteConfig';
|
||||
import type { Post } from './posts';
|
||||
|
||||
export function absoluteUrl(path = '/'): string {
|
||||
const base = site.base_url.replace(/\/+$/, '');
|
||||
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
||||
return `${base}${normalizedPath}`;
|
||||
}
|
||||
|
||||
export function postUpdatedAt(post: Pick<Post, 'updated_at' | 'published_at' | 'created_at' | 'date'>): string {
|
||||
return post.updated_at ?? post.published_at ?? post.created_at ?? post.date ?? '';
|
||||
}
|
||||
|
||||
export function xmlEscape(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
|
@ -47,14 +47,26 @@ function loadSiteConfig(): SiteConfig {
|
|||
try {
|
||||
const file = readFileSync(resolve(siteRoot, 'config/site.yaml'), 'utf8');
|
||||
const parsed = YAML.parse(file) ?? {};
|
||||
const siteOverrides = {
|
||||
...parsed.site,
|
||||
...(process.env.SITE_URL ? { base_url: process.env.SITE_URL } : {}),
|
||||
...(process.env.PUBLIC_SITE_URL ? { base_url: process.env.PUBLIC_SITE_URL } : {})
|
||||
};
|
||||
|
||||
return {
|
||||
site: { ...defaults.site, ...parsed.site },
|
||||
site: { ...defaults.site, ...siteOverrides },
|
||||
content: { ...defaults.content, ...parsed.content },
|
||||
build: { ...defaults.build, ...parsed.build }
|
||||
};
|
||||
} catch {
|
||||
return defaults;
|
||||
return {
|
||||
...defaults,
|
||||
site: {
|
||||
...defaults.site,
|
||||
...(process.env.SITE_URL ? { base_url: process.env.SITE_URL } : {}),
|
||||
...(process.env.PUBLIC_SITE_URL ? { base_url: process.env.PUBLIC_SITE_URL } : {})
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,21 +2,20 @@
|
|||
import '../../styles/normalize.css';
|
||||
import DefaultArchive from '../../components/DefaultArchive.astro';
|
||||
import YarArchive from '../../components/themes/yar/YarArchive.astro';
|
||||
import SeoHead from '../../components/seo/SeoHead.astro';
|
||||
import { site } from '../../lib/siteConfig';
|
||||
import { getArchiveYears } from '../../lib/posts';
|
||||
|
||||
const archiveYears = getArchiveYears();
|
||||
const theme = site.theme?.trim().toLowerCase() ?? 'default';
|
||||
const ArchiveView = theme === 'yar' ? YarArchive : DefaultArchive;
|
||||
const description = `${site.title} 的文章归档`;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={site.language}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Archive | {site.title}</title>
|
||||
<meta name="description" content={`Archive of ${site.title}`} />
|
||||
<SeoHead title="归档" description={description} path="/archive/" />
|
||||
</head>
|
||||
<body>
|
||||
<ArchiveView archiveYears={archiveYears} />
|
||||
|
|
|
|||
|
|
@ -2,23 +2,30 @@
|
|||
import '../styles/normalize.css';
|
||||
import DefaultHome from '../components/DefaultHome.astro';
|
||||
import YarHome from '../components/themes/yar/YarHome.astro';
|
||||
import SeoHead from '../components/seo/SeoHead.astro';
|
||||
import { site } from '../lib/siteConfig';
|
||||
import { getPaginatedPosts, getPublishedPosts, getTotalPages } from '../lib/posts';
|
||||
import { absoluteUrl } from '../lib/seo';
|
||||
|
||||
const allPosts = getPublishedPosts();
|
||||
const posts = getPaginatedPosts(allPosts, 1);
|
||||
const totalPages = getTotalPages(allPosts);
|
||||
const theme = site.theme?.trim().toLowerCase() ?? 'default';
|
||||
const Home = theme === 'yar' ? YarHome : DefaultHome;
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Blog',
|
||||
name: site.title,
|
||||
description: site.description,
|
||||
url: absoluteUrl('/'),
|
||||
inLanguage: site.language
|
||||
};
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={site.language}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{site.title}</title>
|
||||
<meta name="description" content={site.description} />
|
||||
<SeoHead title={site.title} description={site.description} path="/" jsonLd={jsonLd} />
|
||||
</head>
|
||||
<body>
|
||||
<Home posts={posts} currentPage={1} totalPages={totalPages} />
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import '../../styles/normalize.css';
|
||||
import DefaultHome from '../../components/DefaultHome.astro';
|
||||
import YarHome from '../../components/themes/yar/YarHome.astro';
|
||||
import SeoHead from '../../components/seo/SeoHead.astro';
|
||||
import { site } from '../../lib/siteConfig';
|
||||
import { getPaginatedPosts, getPublishedPosts, getTotalPages } from '../../lib/posts';
|
||||
|
||||
|
|
@ -24,15 +25,14 @@ const posts = getPaginatedPosts(allPosts, page);
|
|||
const totalPages = getTotalPages(allPosts);
|
||||
const theme = site.theme?.trim().toLowerCase() ?? 'default';
|
||||
const Home = theme === 'yar' ? YarHome : DefaultHome;
|
||||
const title = `第 ${page} 页`;
|
||||
const description = `${site.title} 的第 ${page} 页文章列表`;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={site.language}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>第 {page} 页 | {site.title}</title>
|
||||
<meta name="description" content={site.description} />
|
||||
<SeoHead title={title} description={description} path={`/page/${page}/`} />
|
||||
</head>
|
||||
<body>
|
||||
<Home posts={posts} currentPage={page} totalPages={totalPages} />
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
import '../../styles/normalize.css';
|
||||
import DefaultPost from '../../components/DefaultPost.astro';
|
||||
import YarPost from '../../components/themes/yar/YarPost.astro';
|
||||
import SeoHead from '../../components/seo/SeoHead.astro';
|
||||
import { site } from '../../lib/siteConfig';
|
||||
import { absoluteUrl } from '../../lib/seo';
|
||||
|
||||
export function getStaticPaths() {
|
||||
const modules = import.meta.glob('../../../../../content/posts/*.md', { eager: true });
|
||||
|
|
@ -18,15 +20,46 @@ const { post } = Astro.props;
|
|||
const title = post.frontmatter.title;
|
||||
const theme = site.theme?.trim().toLowerCase() ?? 'default';
|
||||
const PostView = theme === 'yar' ? YarPost : DefaultPost;
|
||||
const description = post.frontmatter.summary ?? site.description;
|
||||
const publishedTime = post.frontmatter.published_at ?? post.frontmatter.created_at;
|
||||
const modifiedTime = post.frontmatter.updated_at ?? publishedTime;
|
||||
const postUrl = `/posts/${post.frontmatter.slug}/`;
|
||||
const tags = post.frontmatter.tags ?? [];
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BlogPosting',
|
||||
headline: title,
|
||||
description,
|
||||
datePublished: publishedTime,
|
||||
dateModified: modifiedTime,
|
||||
url: absoluteUrl(postUrl),
|
||||
mainEntityOfPage: absoluteUrl(postUrl),
|
||||
inLanguage: site.language,
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: site.title
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: site.title
|
||||
},
|
||||
...(tags.length > 0 ? { keywords: tags.join(', ') } : {})
|
||||
};
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={site.language}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{title} | {site.title}</title>
|
||||
{post.frontmatter.summary && <meta name="description" content={post.frontmatter.summary} />}
|
||||
<SeoHead
|
||||
title={title}
|
||||
description={description}
|
||||
path={postUrl}
|
||||
type="article"
|
||||
publishedTime={publishedTime}
|
||||
modifiedTime={modifiedTime}
|
||||
tags={tags}
|
||||
jsonLd={jsonLd}
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<PostView post={post} />
|
||||
|
|
|
|||
14
frontend/site/src/pages/robots.txt.ts
Normal file
14
frontend/site/src/pages/robots.txt.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { absoluteUrl } from '../lib/seo';
|
||||
|
||||
export function GET() {
|
||||
return new Response(`User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: ${absoluteUrl('/sitemap.xml')}
|
||||
`, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'Cache-Control': 'public, max-age=3600'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import rss from '@astrojs/rss';
|
||||
import { getPublishedPosts } from '../lib/posts';
|
||||
import { site } from '../lib/siteConfig';
|
||||
import { absoluteUrl } from '../lib/seo';
|
||||
|
||||
export function GET() {
|
||||
return rss({
|
||||
|
|
@ -10,7 +11,7 @@ export function GET() {
|
|||
items: getPublishedPosts().map((post) => ({
|
||||
title: post.title,
|
||||
description: post.summary ?? '',
|
||||
link: post.url,
|
||||
link: absoluteUrl(post.url),
|
||||
pubDate: post.date ? new Date(post.date) : undefined,
|
||||
categories: post.tags
|
||||
}))
|
||||
|
|
|
|||
150
frontend/site/src/pages/site-map.astro
Normal file
150
frontend/site/src/pages/site-map.astro
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
---
|
||||
import '../styles/normalize.css';
|
||||
import SiteNav from '../components/SiteNav.astro';
|
||||
import SeoHead from '../components/seo/SeoHead.astro';
|
||||
import { site } from '../lib/siteConfig';
|
||||
import { getArchiveYears, getTagSummaries } from '../lib/posts';
|
||||
|
||||
const archiveYears = getArchiveYears();
|
||||
const tags = getTagSummaries();
|
||||
const description = `${site.title} 的文章、归档和标签站点地图`;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={site.language}>
|
||||
<head>
|
||||
<SeoHead title="站点地图" description={description} path="/site-map/" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="site-map">
|
||||
<SiteNav />
|
||||
<header>
|
||||
<h1>站点地图</h1>
|
||||
<p>{description}</p>
|
||||
</header>
|
||||
|
||||
<section aria-labelledby="main-pages">
|
||||
<h2 id="main-pages">主要页面</h2>
|
||||
<ul>
|
||||
<li><a href="/">首页</a></li>
|
||||
<li><a href="/archive/">归档</a></li>
|
||||
<li><a href="/tags/">标签</a></li>
|
||||
<li><a href="/rss.xml">RSS</a></li>
|
||||
<li><a href="/sitemap.xml">XML Sitemap</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{tags.length > 0 && (
|
||||
<section aria-labelledby="tag-pages">
|
||||
<h2 id="tag-pages">标签</h2>
|
||||
<ul class="columns">
|
||||
{tags.map((tag) => (
|
||||
<li><a href={`/tags/${tag.slug}/`}>{tag.name}</a> <span>({tag.count})</span></li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section aria-labelledby="post-pages">
|
||||
<h2 id="post-pages">文章</h2>
|
||||
{archiveYears.map((year) => (
|
||||
<div class="year-group">
|
||||
<h3>{year.year}</h3>
|
||||
<ul>
|
||||
{year.posts.map((post) => (
|
||||
<li>
|
||||
<a href={post.url}>{post.title}</a>
|
||||
{post.date && <time datetime={post.date}>{new Date(post.date).toLocaleDateString(site.language)}</time>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
.site-map {
|
||||
width: min(860px, calc(100% - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 48px 0 80px;
|
||||
color: #20201d;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.site-map :global(.site-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 18px;
|
||||
margin-bottom: 36px;
|
||||
color: #625a50;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
header {
|
||||
border-bottom: 1px solid #d8d1c3;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 2.5rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 32px 0 12px;
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 20px 0 8px;
|
||||
color: #625a50;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #5b554d;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
.columns {
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
}
|
||||
|
||||
li {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
time,
|
||||
span {
|
||||
color: #766e63;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
time {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.year-group + .year-group {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
73
frontend/site/src/pages/sitemap.xml.ts
Normal file
73
frontend/site/src/pages/sitemap.xml.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { getPublishedPosts, getTagSummaries, getTotalPages } from '../lib/posts';
|
||||
import { absoluteUrl, postUpdatedAt, xmlEscape } from '../lib/seo';
|
||||
import { site } from '../lib/siteConfig';
|
||||
|
||||
type SitemapEntry = {
|
||||
path: string;
|
||||
lastmod?: string;
|
||||
changefreq?: 'daily' | 'weekly' | 'monthly' | 'yearly';
|
||||
priority?: string;
|
||||
};
|
||||
|
||||
function formatLastmod(value?: string): string | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
export function GET() {
|
||||
const posts = getPublishedPosts();
|
||||
const latestPostDate = posts.map(postUpdatedAt).sort().at(-1);
|
||||
const entries: SitemapEntry[] = [
|
||||
{ path: '/', lastmod: latestPostDate, changefreq: 'daily', priority: '1.0' },
|
||||
{ path: '/archive/', lastmod: latestPostDate, changefreq: 'weekly', priority: '0.7' },
|
||||
{ path: '/tags/', lastmod: latestPostDate, changefreq: 'weekly', priority: '0.6' },
|
||||
{ path: '/site-map/', lastmod: latestPostDate, changefreq: 'weekly', priority: '0.5' },
|
||||
...Array.from({ length: Math.max(0, getTotalPages(posts) - 1) }, (_, index) => ({
|
||||
path: `/page/${index + 2}/`,
|
||||
lastmod: latestPostDate,
|
||||
changefreq: 'weekly' as const,
|
||||
priority: '0.6'
|
||||
})),
|
||||
...getTagSummaries().map((tag) => ({
|
||||
path: `/tags/${tag.slug}/`,
|
||||
lastmod: latestPostDate,
|
||||
changefreq: 'weekly' as const,
|
||||
priority: '0.5'
|
||||
})),
|
||||
...posts.map((post) => ({
|
||||
path: post.url,
|
||||
lastmod: postUpdatedAt(post),
|
||||
changefreq: 'monthly' as const,
|
||||
priority: '0.8'
|
||||
}))
|
||||
];
|
||||
|
||||
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${entries
|
||||
.map((entry) => {
|
||||
const lastmod = formatLastmod(entry.lastmod);
|
||||
return ` <url>
|
||||
<loc>${xmlEscape(absoluteUrl(entry.path))}</loc>${lastmod ? `\n <lastmod>${lastmod}</lastmod>` : ''}
|
||||
<changefreq>${entry.changefreq ?? 'weekly'}</changefreq>
|
||||
<priority>${entry.priority ?? '0.5'}</priority>
|
||||
</url>`;
|
||||
})
|
||||
.join('\n')}
|
||||
</urlset>`;
|
||||
|
||||
return new Response(body, {
|
||||
headers: {
|
||||
'Content-Type': 'application/xml; charset=utf-8',
|
||||
'Cache-Control': 'public, max-age=3600'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
import '../../styles/normalize.css';
|
||||
import DefaultTagPosts from '../../components/DefaultTagPosts.astro';
|
||||
import YarTagPosts from '../../components/themes/yar/YarTagPosts.astro';
|
||||
import SeoHead from '../../components/seo/SeoHead.astro';
|
||||
import { site } from '../../lib/siteConfig';
|
||||
import { getPostsByTag, getTagSummaries } from '../../lib/posts';
|
||||
|
||||
|
|
@ -16,15 +17,13 @@ const { tag } = Astro.props;
|
|||
const posts = getPostsByTag(tag.slug);
|
||||
const theme = site.theme?.trim().toLowerCase() ?? 'default';
|
||||
const TagPostsView = theme === 'yar' ? YarTagPosts : DefaultTagPosts;
|
||||
const description = `${site.title} 中带有「${tag.name}」标签的文章`;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={site.language}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{tag.name} | {site.title}</title>
|
||||
<meta name="description" content={`Posts tagged ${tag.name}`} />
|
||||
<SeoHead title={tag.name} description={description} path={`/tags/${tag.slug}/`} />
|
||||
</head>
|
||||
<body>
|
||||
<TagPostsView tag={tag} posts={posts} />
|
||||
|
|
|
|||
|
|
@ -2,21 +2,20 @@
|
|||
import '../../styles/normalize.css';
|
||||
import DefaultTags from '../../components/DefaultTags.astro';
|
||||
import YarTags from '../../components/themes/yar/YarTags.astro';
|
||||
import SeoHead from '../../components/seo/SeoHead.astro';
|
||||
import { site } from '../../lib/siteConfig';
|
||||
import { getTagSummaries } from '../../lib/posts';
|
||||
|
||||
const tags = getTagSummaries();
|
||||
const theme = site.theme?.trim().toLowerCase() ?? 'default';
|
||||
const TagsView = theme === 'yar' ? YarTags : DefaultTags;
|
||||
const description = `${site.title} 的全部文章标签`;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={site.language}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Tags | {site.title}</title>
|
||||
<meta name="description" content={`Tags on ${site.title}`} />
|
||||
<SeoHead title="标签" description={description} path="/tags/" />
|
||||
</head>
|
||||
<body>
|
||||
<TagsView tags={tags} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue