feat: add admin publishing workflow and yar theme
Add Go/Postgres admin APIs, Angular admin UI, manual build flow, asset uploads, markdown import/export, configurable slug generation, and the Yar reading theme. Exclude local docs and generated development artifacts from version control.
This commit is contained in:
parent
b78f4b39c9
commit
f0b50d13ea
121 changed files with 27139 additions and 550 deletions
1
frontend/site/public/assets/.gitkeep
Normal file
1
frontend/site/public/assets/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
BIN
frontend/site/public/assets/P1000638-13ec26665a58.jpg
Normal file
BIN
frontend/site/public/assets/P1000638-13ec26665a58.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
177
frontend/site/src/components/DefaultArchive.astro
Normal file
177
frontend/site/src/components/DefaultArchive.astro
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
---
|
||||
import SiteNav from './SiteNav.astro';
|
||||
import { site } from '../lib/siteConfig';
|
||||
import { tagSlug, type ArchiveYear } from '../lib/posts';
|
||||
|
||||
type Props = {
|
||||
archiveYears: ArchiveYear[];
|
||||
};
|
||||
|
||||
const { archiveYears } = Astro.props;
|
||||
---
|
||||
|
||||
<main class="page">
|
||||
<SiteNav />
|
||||
|
||||
<header class="site-header">
|
||||
<p class="eyebrow">{site.title}</p>
|
||||
<h1>Archive</h1>
|
||||
</header>
|
||||
|
||||
{archiveYears.length === 0 ? (
|
||||
<p class="empty">No published posts yet.</p>
|
||||
) : (
|
||||
<div class="archive-list">
|
||||
{archiveYears.map((group) => (
|
||||
<section class="archive-year" aria-label={group.year}>
|
||||
<h2>{group.year}</h2>
|
||||
<ol>
|
||||
{group.posts.map((post) => (
|
||||
<li>
|
||||
{post.date && (
|
||||
<time datetime={post.date}>
|
||||
{new Date(post.date).toLocaleDateString(site.language, {
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})}
|
||||
</time>
|
||||
)}
|
||||
<a href={post.url}>{post.title}</a>
|
||||
{post.tags.length > 0 && (
|
||||
<ul class="tags compact-tags" aria-label="Tags">
|
||||
{post.tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tagSlug(tag)}/`}>{tag}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.page {
|
||||
width: min(860px, calc(100% - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 56px 0 80px;
|
||||
color: #20201d;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.page :global(.site-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 18px;
|
||||
margin-bottom: 36px;
|
||||
color: #625a50;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.page :global(.site-nav a),
|
||||
.archive-year li > a,
|
||||
.tags a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.page :global(.site-nav a:hover) {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
border-bottom: 1px solid #d8d1c3;
|
||||
padding-bottom: 28px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: #6f675b;
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(2.4rem, 8vw, 5rem);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.archive-list {
|
||||
display: grid;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.archive-year h2 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.archive-year ol {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.archive-year li {
|
||||
display: grid;
|
||||
grid-template-columns: 72px minmax(0, 1fr) auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ded8cd;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
time {
|
||||
color: #7a7268;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
justify-content: flex-end;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tags li {
|
||||
border: 1px solid #d0c8bb;
|
||||
border-radius: 999px;
|
||||
color: #5f584d;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.tags a {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: #5b554d;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.archive-year li {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tags {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
165
frontend/site/src/components/DefaultHome.astro
Normal file
165
frontend/site/src/components/DefaultHome.astro
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
---
|
||||
import SiteNav from './SiteNav.astro';
|
||||
import { site } from '../lib/siteConfig';
|
||||
import { tagSlug, type Post } from '../lib/posts';
|
||||
|
||||
type Props = {
|
||||
posts: Post[];
|
||||
};
|
||||
|
||||
const { posts } = Astro.props;
|
||||
---
|
||||
|
||||
<main class="page">
|
||||
<SiteNav />
|
||||
|
||||
<header class="site-header">
|
||||
<p class="eyebrow">{site.description}</p>
|
||||
<h1>{site.title}</h1>
|
||||
</header>
|
||||
|
||||
<section class="post-list" aria-label="Posts">
|
||||
{posts.length === 0 ? (
|
||||
<p class="empty">No published posts yet.</p>
|
||||
) : (
|
||||
posts.map((post) => (
|
||||
<article class="post-item">
|
||||
<a href={post.url}>
|
||||
<h2>{post.title}</h2>
|
||||
{post.summary && <p>{post.summary}</p>}
|
||||
<div class="post-meta">
|
||||
{post.date && (
|
||||
<time datetime={post.date}>
|
||||
{new Date(post.date).toLocaleDateString(site.language)}
|
||||
</time>
|
||||
)}
|
||||
{post.tags.length > 0 && (
|
||||
<ul class="tags" aria-label="Tags">
|
||||
{post.tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tagSlug(tag)}/`}>{tag}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
))
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.page {
|
||||
width: min(860px, calc(100% - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 56px 0 80px;
|
||||
color: #20201d;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.page :global(.site-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 18px;
|
||||
margin-bottom: 36px;
|
||||
color: #625a50;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.page :global(.site-nav a),
|
||||
.post-item a,
|
||||
.tags a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.page :global(.site-nav a:hover) {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
border-bottom: 1px solid #d8d1c3;
|
||||
padding-bottom: 28px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: #6f675b;
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(2.4rem, 8vw, 5rem);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.post-list {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.post-item {
|
||||
border-bottom: 1px solid #ded8cd;
|
||||
padding: 18px 0 22px;
|
||||
}
|
||||
|
||||
.post-item a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-item h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 1.45rem;
|
||||
}
|
||||
|
||||
.post-item p,
|
||||
.empty {
|
||||
color: #5b554d;
|
||||
}
|
||||
|
||||
.post-item p {
|
||||
margin: 0 0 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
time {
|
||||
color: #7a7268;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 14px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tags li {
|
||||
border: 1px solid #d0c8bb;
|
||||
border-radius: 999px;
|
||||
color: #5f584d;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.tags a {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
</style>
|
||||
130
frontend/site/src/components/DefaultPost.astro
Normal file
130
frontend/site/src/components/DefaultPost.astro
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
---
|
||||
import SiteNav from './SiteNav.astro';
|
||||
import { site } from '../lib/siteConfig';
|
||||
import { tagSlug } from '../lib/posts';
|
||||
|
||||
type Props = {
|
||||
post: any;
|
||||
};
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { Content } = post;
|
||||
const title = post.frontmatter.title;
|
||||
const date = post.frontmatter.created_at ?? post.frontmatter.published_at ?? post.frontmatter.updated_at;
|
||||
---
|
||||
|
||||
<main class="page article-page">
|
||||
<SiteNav />
|
||||
|
||||
<article class="article">
|
||||
<header>
|
||||
<h1>{title}</h1>
|
||||
{date && (
|
||||
<time datetime={date}>
|
||||
{new Date(date).toLocaleDateString(site.language)}
|
||||
</time>
|
||||
)}
|
||||
{post.frontmatter.summary && <p class="summary">{post.frontmatter.summary}</p>}
|
||||
{post.frontmatter.tags?.length > 0 && (
|
||||
<ul class="tags article-tags" aria-label="Tags">
|
||||
{post.frontmatter.tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tagSlug(tag)}/`}>{tag}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<Content />
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.page {
|
||||
width: min(860px, calc(100% - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 56px 0 80px;
|
||||
color: #20201d;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.page :global(.site-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 18px;
|
||||
margin-bottom: 36px;
|
||||
color: #625a50;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.page :global(.site-nav a),
|
||||
.tags a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.page :global(.site-nav a:hover) {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
.article {
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.article header {
|
||||
border-bottom: 1px solid #d8d1c3;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.article h1 {
|
||||
margin: 0 0 16px;
|
||||
font-size: clamp(2.1rem, 7vw, 4rem);
|
||||
}
|
||||
|
||||
.summary {
|
||||
color: #5b554d;
|
||||
}
|
||||
|
||||
time {
|
||||
color: #7a7268;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 0;
|
||||
margin: 16px 0 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tags li {
|
||||
border: 1px solid #d0c8bb;
|
||||
border-radius: 999px;
|
||||
color: #5f584d;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.tags a {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.article :global(pre) {
|
||||
overflow-x: auto;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: #272822;
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.article :global(code) {
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
</style>
|
||||
118
frontend/site/src/components/DefaultTagPosts.astro
Normal file
118
frontend/site/src/components/DefaultTagPosts.astro
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
import SiteNav from './SiteNav.astro';
|
||||
import { site } from '../lib/siteConfig';
|
||||
import type { Post, TagSummary } from '../lib/posts';
|
||||
|
||||
type Props = {
|
||||
tag: TagSummary;
|
||||
posts: Post[];
|
||||
};
|
||||
|
||||
const { tag, posts } = Astro.props;
|
||||
---
|
||||
|
||||
<main class="page">
|
||||
<SiteNav />
|
||||
|
||||
<header class="site-header">
|
||||
<p class="eyebrow">{site.title}</p>
|
||||
<h1>{tag.name}</h1>
|
||||
</header>
|
||||
|
||||
<section class="post-list" aria-label={`Posts tagged ${tag.name}`}>
|
||||
{posts.map((post) => (
|
||||
<article class="post-item">
|
||||
<a href={post.url}>
|
||||
<h2>{post.title}</h2>
|
||||
{post.summary && <p>{post.summary}</p>}
|
||||
{post.date && (
|
||||
<time datetime={post.date}>
|
||||
{new Date(post.date).toLocaleDateString(site.language)}
|
||||
</time>
|
||||
)}
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.page {
|
||||
width: min(860px, calc(100% - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 56px 0 80px;
|
||||
color: #20201d;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.page :global(.site-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 18px;
|
||||
margin-bottom: 36px;
|
||||
color: #625a50;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.page :global(.site-nav a),
|
||||
.post-item a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.page :global(.site-nav a:hover) {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
border-bottom: 1px solid #d8d1c3;
|
||||
padding-bottom: 28px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: #6f675b;
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(2.4rem, 8vw, 5rem);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.post-list {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.post-item {
|
||||
border-bottom: 1px solid #ded8cd;
|
||||
padding: 18px 0 22px;
|
||||
}
|
||||
|
||||
.post-item a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-item h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 1.45rem;
|
||||
}
|
||||
|
||||
.post-item p {
|
||||
margin: 0 0 12px;
|
||||
color: #5b554d;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
time {
|
||||
color: #7a7268;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
111
frontend/site/src/components/DefaultTags.astro
Normal file
111
frontend/site/src/components/DefaultTags.astro
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
---
|
||||
import SiteNav from './SiteNav.astro';
|
||||
import { site } from '../lib/siteConfig';
|
||||
import type { TagSummary } from '../lib/posts';
|
||||
|
||||
type Props = {
|
||||
tags: TagSummary[];
|
||||
};
|
||||
|
||||
const { tags } = Astro.props;
|
||||
---
|
||||
|
||||
<main class="page">
|
||||
<SiteNav />
|
||||
|
||||
<header class="site-header">
|
||||
<p class="eyebrow">{site.title}</p>
|
||||
<h1>Tags</h1>
|
||||
</header>
|
||||
|
||||
{tags.length === 0 ? (
|
||||
<p class="empty">No tags yet.</p>
|
||||
) : (
|
||||
<ul class="tag-index" aria-label="Tags">
|
||||
{tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tag.slug}/`}>
|
||||
<span>{tag.name}</span>
|
||||
<strong>{tag.count}</strong>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.page {
|
||||
width: min(860px, calc(100% - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 56px 0 80px;
|
||||
color: #20201d;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.page :global(.site-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 18px;
|
||||
margin-bottom: 36px;
|
||||
color: #625a50;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.page :global(.site-nav a),
|
||||
.tag-index a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.page :global(.site-nav a:hover) {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
border-bottom: 1px solid #d8d1c3;
|
||||
padding-bottom: 28px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: #6f675b;
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(2.4rem, 8vw, 5rem);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tag-index {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tag-index a {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
border-bottom: 1px solid #ded8cd;
|
||||
padding: 14px 0;
|
||||
}
|
||||
|
||||
.tag-index strong {
|
||||
color: #766f65;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: #5b554d;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
const links = [
|
||||
{ href: '/', label: 'Home' },
|
||||
{ href: '/archive/', label: 'Archive' },
|
||||
{ href: '/tags/', label: 'Tags' },
|
||||
{ href: '/', label: '首页' },
|
||||
{ href: '/archive/', label: '归档' },
|
||||
{ href: '/tags/', label: '标签' },
|
||||
{ href: '/rss.xml', label: 'RSS' }
|
||||
];
|
||||
---
|
||||
|
|
|
|||
346
frontend/site/src/components/themes/yar/YarArchive.astro
Normal file
346
frontend/site/src/components/themes/yar/YarArchive.astro
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
---
|
||||
import SiteNav from '../../SiteNav.astro';
|
||||
import { site } from '../../../lib/siteConfig';
|
||||
import { tagSlug, type ArchiveYear } from '../../../lib/posts';
|
||||
|
||||
type Props = {
|
||||
archiveYears: ArchiveYear[];
|
||||
};
|
||||
|
||||
const { archiveYears } = Astro.props;
|
||||
---
|
||||
|
||||
<main class="yar-archive-page">
|
||||
<div class="yar-shell">
|
||||
<header class="yar-header">
|
||||
<div class="yar-header-inner">
|
||||
<div class="yar-title-box">
|
||||
<h1 class="yar-title">{site.title}</h1>
|
||||
</div>
|
||||
<SiteNav />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="yar-archive" aria-label="Archive">
|
||||
{archiveYears.length === 0 ? (
|
||||
<p class="yar-empty">No published posts yet.</p>
|
||||
) : (
|
||||
<div class="yar-archive-list">
|
||||
{archiveYears.map((group) => (
|
||||
<section class="yar-archive-year" aria-label={group.year}>
|
||||
<div class="yar-archive-year-label">
|
||||
<div class="yar-archive-year-meta">
|
||||
<h3>{group.year}</h3>
|
||||
<p>共 {group.posts.length} 篇</p>
|
||||
</div>
|
||||
</div>
|
||||
<ol>
|
||||
{group.posts.map((post) => (
|
||||
<li>
|
||||
<time datetime={post.date}>
|
||||
{post.date
|
||||
? new Date(post.date).toLocaleDateString(site.language, {
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})
|
||||
: '----'}
|
||||
</time>
|
||||
<a class="yar-archive-post" href={post.url}>{post.title}</a>
|
||||
{post.tags.length > 0 && (
|
||||
<ul class="yar-tags" aria-label="Tags">
|
||||
{post.tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tagSlug(tag)}/`}>{tag}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<footer class="yar-footer">
|
||||
<p>由 osaet 构建</p>
|
||||
<p>© 2026 Osaet. All rights reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.yar-archive-page {
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
color: #1c211b;
|
||||
}
|
||||
|
||||
.yar-shell {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 40px 0 5vh;
|
||||
}
|
||||
|
||||
.yar-header {
|
||||
border-bottom: 1px solid rgba(28, 33, 27, 0.18);
|
||||
}
|
||||
|
||||
.yar-header-inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
width: 50vw;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25em;
|
||||
justify-content: flex-end;
|
||||
margin: 0;
|
||||
border-radius: 0.75em;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0em 0.2em rgb(29 53 87 / 13%);
|
||||
padding: 0.5em 1.4em;
|
||||
color: #53605a;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav a) {
|
||||
border-radius: 0.65em;
|
||||
color: inherit;
|
||||
padding: 0.55em 0.8em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav a:hover) {
|
||||
background: #eef1ed;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-title-box {
|
||||
border-radius: 0.7em;
|
||||
background: #1d3557;
|
||||
padding: 1em 1.4em;
|
||||
}
|
||||
|
||||
.yar-title {
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
sans-serif;
|
||||
font-size: 1em;
|
||||
letter-spacing: 0.16em;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.yar-archive {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 36px 0 0;
|
||||
color: #3d3d3f;
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
"Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN",
|
||||
sans-serif;
|
||||
font-size: 16px;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
|
||||
.yar-archive-list {
|
||||
display: grid;
|
||||
gap: 3.2em 0;
|
||||
}
|
||||
|
||||
.yar-archive-year {
|
||||
display: grid;
|
||||
grid-template-columns: 25% 50% 25%;
|
||||
}
|
||||
|
||||
.yar-archive-year-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.yar-archive-year-meta {
|
||||
display: grid;
|
||||
gap: 0.35em;
|
||||
align-content: start;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.yar-archive-year h3 {
|
||||
margin: 0;
|
||||
color: #8a9089;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
|
||||
.yar-archive-year-label p {
|
||||
margin: 0;
|
||||
color: #b0b5ae;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.yar-archive-year ol {
|
||||
display: grid;
|
||||
gap: 0.8em;
|
||||
grid-column: 2;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.yar-archive-year > ol > li {
|
||||
display: grid;
|
||||
grid-template-columns: 5.5em minmax(0, 1fr) auto;
|
||||
gap: 1em;
|
||||
align-items: flex-start;
|
||||
border-bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.yar-archive-year time {
|
||||
color: #7a8375;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.7em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.yar-archive-post {
|
||||
color: #1c211b;
|
||||
line-height: 1.5;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-archive-post:hover {
|
||||
color: #1d3557;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: rgb(29 53 87 / 32%);
|
||||
text-decoration-thickness: 0.12em;
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
.yar-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45em;
|
||||
justify-content: flex-end;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.yar-tags li {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.yar-tags a {
|
||||
border-radius: 999em;
|
||||
background: #f3f6f4;
|
||||
box-shadow: inset 0 0 0 1px rgb(29 53 87 / 7%);
|
||||
color: #53605a;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1;
|
||||
padding: 0.5em 0.75em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-tags a:hover {
|
||||
background: #e8eee9;
|
||||
}
|
||||
|
||||
.yar-empty {
|
||||
margin: 0;
|
||||
color: #596254;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-footer {
|
||||
width: 50vw;
|
||||
margin: 2em auto 0;
|
||||
color: #8a9089;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.8;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.yar-shell {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.yar-header-inner,
|
||||
.yar-footer {
|
||||
width: calc(100% - 28px);
|
||||
}
|
||||
|
||||
.yar-header-inner {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.yar-archive {
|
||||
width: calc(100% - 28px);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.yar-archive-year {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.8em;
|
||||
}
|
||||
|
||||
.yar-archive-year-label {
|
||||
justify-content: flex-start;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.yar-archive-year ol {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.yar-archive-year > ol > li {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.55em;
|
||||
}
|
||||
|
||||
.yar-tags {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
476
frontend/site/src/components/themes/yar/YarHome.astro
Normal file
476
frontend/site/src/components/themes/yar/YarHome.astro
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
---
|
||||
import SiteNav from '../../SiteNav.astro';
|
||||
import { site } from '../../../lib/siteConfig';
|
||||
import { tagSlug, type Post } from '../../../lib/posts';
|
||||
|
||||
type Props = {
|
||||
posts: Post[];
|
||||
currentPage?: number;
|
||||
totalPages?: number;
|
||||
};
|
||||
|
||||
const { posts, currentPage = 1, totalPages = 1 } = Astro.props;
|
||||
const pages = Array.from({ length: totalPages }, (_, index) => index + 1);
|
||||
|
||||
function pageHref(page: number): string {
|
||||
return page === 1 ? '/' : `/page/${page}/`;
|
||||
}
|
||||
|
||||
function formatPublishedAt(date: string): string {
|
||||
return new Date(date).toLocaleString(site.language, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
}
|
||||
---
|
||||
|
||||
<main class="yar-home">
|
||||
<div class="yar-shell">
|
||||
<header class="yar-header">
|
||||
<div class="yar-header-inner">
|
||||
<div class="yar-title-box">
|
||||
<h1 class="yar-title">{site.title}</h1>
|
||||
</div>
|
||||
<SiteNav />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="yar-section" aria-label="Posts">
|
||||
<div class="yar-posts">
|
||||
{posts.length === 0 ? (
|
||||
<p class="yar-empty">No published posts yet.</p>
|
||||
) : (
|
||||
posts.map((post) => {
|
||||
const Content = post.Content;
|
||||
|
||||
return (
|
||||
<article class="yar-post">
|
||||
<header class="yar-post-header">
|
||||
<a href={post.url}>
|
||||
<div class="heti heti--sans">
|
||||
<h2 class="post-title">{post.title}</h2>
|
||||
</div>
|
||||
</a>
|
||||
{post.date && (
|
||||
<time class="yar-date" datetime={post.date}>
|
||||
发布时间:{formatPublishedAt(post.date)}
|
||||
</time>
|
||||
)}
|
||||
</header>
|
||||
<div class="yar-post-content heti heti--sans">
|
||||
<Content />
|
||||
</div>
|
||||
{post.tags.length > 0 && (
|
||||
<ul class="yar-tags" aria-label="Tags">
|
||||
{post.tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tagSlug(tag)}/`}>{tag}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</article>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<nav class="yar-pagination" aria-label="Pagination">
|
||||
{currentPage > 1 && (
|
||||
<a href={pageHref(currentPage - 1)} aria-label="上一页">上一页</a>
|
||||
)}
|
||||
<ol>
|
||||
{pages.map((page) => (
|
||||
<li>
|
||||
<a
|
||||
href={pageHref(page)}
|
||||
class:list={['yar-page-link', { 'is-current': page === currentPage }]}
|
||||
aria-current={page === currentPage ? 'page' : undefined}
|
||||
>
|
||||
{page}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
{currentPage < totalPages && (
|
||||
<a href={pageHref(currentPage + 1)} aria-label="下一页">下一页</a>
|
||||
)}
|
||||
</nav>
|
||||
)}
|
||||
|
||||
<footer class="yar-footer">
|
||||
<p>由 osaet 构建</p>
|
||||
<p>© 2026 Osaet. All rights reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.yar-home {
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
color: #1c211b;
|
||||
}
|
||||
|
||||
.yar-shell {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 40px 0 5vh;
|
||||
}
|
||||
|
||||
.yar-header {
|
||||
border-bottom: 1px solid rgba(28, 33, 27, 0.18);
|
||||
}
|
||||
|
||||
.yar-header-inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
width: 50vw;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25em;
|
||||
justify-content: flex-end;
|
||||
margin: 0;
|
||||
border-radius: 0.75em;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0em 0.2em rgb(29 53 87 / 13%);
|
||||
padding: 0.5em 1.4em;
|
||||
color: #53605a;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav a) {
|
||||
border-radius: 0.65em;
|
||||
color: inherit;
|
||||
padding: 0.55em 0.8em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav a:hover) {
|
||||
background: #eef1ed;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.heti {
|
||||
max-width: 42em;
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
"Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN",
|
||||
sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.9;
|
||||
letter-spacing: 0;
|
||||
overflow-wrap: break-word;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.heti h1,
|
||||
.heti h2,
|
||||
.heti h3,
|
||||
.heti h4,
|
||||
.heti h5,
|
||||
.heti h6 {
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
sans-serif;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.heti--sans {
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
"Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.heti a {
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 0.18em;
|
||||
}
|
||||
|
||||
.heti img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.yar-title-box {
|
||||
border-radius: 0.7em;
|
||||
background: #1d3557;
|
||||
padding: 1em 1.4em;
|
||||
}
|
||||
|
||||
.yar-title {
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
sans-serif;
|
||||
font-size: 1em;
|
||||
letter-spacing: 0.16em;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.yar-section {
|
||||
width: 50vw;
|
||||
margin: 0 auto;
|
||||
padding: 36px 0 0;
|
||||
}
|
||||
|
||||
.yar-posts {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.yar-post {
|
||||
color: #3d3d3f;
|
||||
font-size: 16px;
|
||||
line-height: 1.7em;
|
||||
border-bottom: 2px dashed #eee;
|
||||
letter-spacing: 1px;
|
||||
padding-bottom: 1.5em;
|
||||
margin-bottom: 3.5em;
|
||||
}
|
||||
|
||||
.yar-post + .yar-post {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.yar-post-header a {
|
||||
display: inline-block;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
margin: 0 0 8px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.yar-post-content {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.yar-post-content :global(p:first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.yar-post-content :global(p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.yar-post-content :global(img) {
|
||||
display: block;
|
||||
border-radius: 0.7em;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin: 1.2em auto;
|
||||
}
|
||||
|
||||
.yar-post-content :global(hr) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
margin: 2em 0;
|
||||
color: #0003;
|
||||
font-size: 1.1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-post-content :global(hr::before) {
|
||||
content: "* * *";
|
||||
}
|
||||
|
||||
.yar-post-content :global(pre) {
|
||||
overflow-x: auto;
|
||||
border-radius: 0.8em;
|
||||
background: #f6f8fa;
|
||||
box-shadow: inset 0 0 0 1px rgb(29 53 87 / 8%);
|
||||
color: #263238;
|
||||
font-size: 0.92em;
|
||||
line-height: 1.7;
|
||||
margin: 1.4em 0;
|
||||
padding: 1em 1.2em;
|
||||
}
|
||||
|
||||
.yar-post-content :global(pre code) {
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-family:
|
||||
"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.yar-post-content :global(:not(pre) > code) {
|
||||
border-radius: 0.35em;
|
||||
background: #f3f6f4;
|
||||
color: #1d3557;
|
||||
font-family:
|
||||
"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-size: 0.92em;
|
||||
padding: 0.15em 0.35em;
|
||||
}
|
||||
|
||||
.yar-post-content :global(a) {
|
||||
color: #1d3557;
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: rgb(29 53 87 / 24%);
|
||||
text-decoration-thickness: 0.12em;
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
.yar-post-content :global(a:hover) {
|
||||
background: #f3f6f4;
|
||||
text-decoration-color: rgb(29 53 87 / 48%);
|
||||
}
|
||||
|
||||
.yar-post p {
|
||||
margin: 0;
|
||||
color: #596254;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.yar-date {
|
||||
display: block;
|
||||
margin-top: 0.45em;
|
||||
color: #7a8375;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.yar-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.55em;
|
||||
padding: 1em 0 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.yar-tags li {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.yar-tags a {
|
||||
border-radius: 999em;
|
||||
background: #f3f6f4;
|
||||
box-shadow: inset 0 0 0 1px rgb(29 53 87 / 7%);
|
||||
color: #53605a;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1;
|
||||
padding: 0.55em 0.8em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-tags a:hover {
|
||||
background: #e8eee9;
|
||||
}
|
||||
|
||||
.yar-empty {
|
||||
margin: 0;
|
||||
color: #596254;
|
||||
}
|
||||
|
||||
.yar-pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.7em;
|
||||
align-items: center;
|
||||
width: 50vw;
|
||||
margin: 0 auto 3.5em;
|
||||
color: #53605a;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.yar-pagination ol {
|
||||
display: flex;
|
||||
gap: 0.45em;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.yar-pagination a {
|
||||
border-radius: 0.7em;
|
||||
color: inherit;
|
||||
padding: 0.55em 0.8em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-pagination a:hover,
|
||||
.yar-pagination .is-current {
|
||||
background: #eef1ed;
|
||||
}
|
||||
|
||||
.yar-footer {
|
||||
width: 50vw;
|
||||
margin: auto auto 0;
|
||||
color: #8a9089;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.8;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.yar-shell {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.yar-header-inner,
|
||||
.yar-section,
|
||||
.yar-pagination,
|
||||
.yar-footer {
|
||||
width: calc(100% - 28px);
|
||||
}
|
||||
|
||||
.yar-header-inner {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.yar-date {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
331
frontend/site/src/components/themes/yar/YarPost.astro
Normal file
331
frontend/site/src/components/themes/yar/YarPost.astro
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
---
|
||||
import { site } from '../../../lib/siteConfig';
|
||||
import { tagSlug } from '../../../lib/posts';
|
||||
|
||||
type Props = {
|
||||
post: any;
|
||||
};
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { Content } = post;
|
||||
const date = post.frontmatter.created_at ?? post.frontmatter.published_at ?? post.frontmatter.updated_at;
|
||||
|
||||
function formatPublishedAt(value: string): string {
|
||||
return new Date(value).toLocaleString(site.language, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
}
|
||||
---
|
||||
|
||||
<main class="yar-page">
|
||||
<div class="yar-shell">
|
||||
<div class="yar-back-row">
|
||||
<a class="yar-back" href="/" data-history-back>← Back</a>
|
||||
</div>
|
||||
|
||||
<article class="yar-article">
|
||||
<header class="yar-article-header">
|
||||
<div class="heti heti--sans">
|
||||
<h1 class="post-title">{post.frontmatter.title}</h1>
|
||||
</div>
|
||||
{date && (
|
||||
<time class="yar-date" datetime={date}>
|
||||
{formatPublishedAt(date)}
|
||||
</time>
|
||||
)}
|
||||
{post.frontmatter.tags?.length > 0 && (
|
||||
<ul class="yar-tags" aria-label="Tags">
|
||||
{post.frontmatter.tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tagSlug(tag)}/`}>{tag}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div class="yar-post-content heti heti--sans">
|
||||
<Content />
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="yar-footer">
|
||||
<p>由 osaet 构建</p>
|
||||
<p>© 2026 Osaet. All rights reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.yar-page {
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
color: #1c211b;
|
||||
}
|
||||
|
||||
.yar-shell {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 40px 0 5vh;
|
||||
}
|
||||
|
||||
.yar-back-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
width: 50vw;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.yar-back {
|
||||
color: #1d3557;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: rgb(29 53 87 / 24%);
|
||||
text-decoration-thickness: 0.12em;
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
.yar-back:hover {
|
||||
background: #f3f6f4;
|
||||
text-decoration-color: rgb(29 53 87 / 48%);
|
||||
}
|
||||
|
||||
.heti,
|
||||
.heti--sans {
|
||||
max-width: 42em;
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
"Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN",
|
||||
sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.9;
|
||||
letter-spacing: 0;
|
||||
overflow-wrap: break-word;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.heti h1,
|
||||
.heti h2,
|
||||
.heti h3,
|
||||
.heti h4,
|
||||
.heti h5,
|
||||
.heti h6 {
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
sans-serif;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.yar-article {
|
||||
width: 50vw;
|
||||
margin: 0 auto;
|
||||
padding: 36px 0 0;
|
||||
color: #3d3d3f;
|
||||
font-size: 16px;
|
||||
line-height: 1.7em;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.yar-article-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-article-header .heti {
|
||||
max-width: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-article-header .post-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 1.75em;
|
||||
letter-spacing: 0.06em;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-date {
|
||||
display: block;
|
||||
margin: 0.45em 0 2.4em;
|
||||
color: #7a8375;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.yar-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.55em;
|
||||
padding: 1em 0 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.yar-tags li {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.yar-tags a {
|
||||
border-radius: 999em;
|
||||
background: #f3f6f4;
|
||||
box-shadow: inset 0 0 0 1px rgb(29 53 87 / 7%);
|
||||
color: #53605a;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1;
|
||||
padding: 0.55em 0.8em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-tags a:hover {
|
||||
background: #e8eee9;
|
||||
}
|
||||
|
||||
.yar-post-content :global(p:first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.yar-post-content :global(p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.yar-post-content :global(img) {
|
||||
display: block;
|
||||
border-radius: 0.7em;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin: 1.2em auto;
|
||||
}
|
||||
|
||||
.yar-post-content :global(hr) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
margin: 2em 0;
|
||||
color: #0003;
|
||||
font-size: 1.1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-post-content :global(hr::before) {
|
||||
content: "* * *";
|
||||
}
|
||||
|
||||
.yar-post-content :global(pre) {
|
||||
overflow-x: auto;
|
||||
border-radius: 0.8em;
|
||||
background: #f6f8fa;
|
||||
box-shadow: inset 0 0 0 1px rgb(29 53 87 / 8%);
|
||||
color: #263238;
|
||||
font-size: 0.92em;
|
||||
line-height: 1.7;
|
||||
margin: 1.4em 0;
|
||||
padding: 1em 1.2em;
|
||||
}
|
||||
|
||||
.yar-post-content :global(pre code) {
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-family:
|
||||
"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.yar-post-content :global(:not(pre) > code) {
|
||||
border-radius: 0.35em;
|
||||
background: #f3f6f4;
|
||||
color: #1d3557;
|
||||
font-family:
|
||||
"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-size: 0.92em;
|
||||
padding: 0.15em 0.35em;
|
||||
}
|
||||
|
||||
.yar-post-content :global(a) {
|
||||
color: #1d3557;
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: rgb(29 53 87 / 24%);
|
||||
text-decoration-thickness: 0.12em;
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
.yar-post-content :global(a:hover) {
|
||||
background: #f3f6f4;
|
||||
text-decoration-color: rgb(29 53 87 / 48%);
|
||||
}
|
||||
|
||||
.yar-post-content :global(p) {
|
||||
color: #596254;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.yar-footer {
|
||||
width: 50vw;
|
||||
margin: auto auto 0;
|
||||
color: #8a9089;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.8;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.yar-shell {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.yar-back-row,
|
||||
.yar-article,
|
||||
.yar-footer {
|
||||
width: calc(100% - 28px);
|
||||
}
|
||||
|
||||
.yar-date {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const backLink = document.querySelector('[data-history-back]');
|
||||
|
||||
backLink?.addEventListener('click', (event) => {
|
||||
if (window.history.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
window.history.back();
|
||||
});
|
||||
</script>
|
||||
237
frontend/site/src/components/themes/yar/YarTagPosts.astro
Normal file
237
frontend/site/src/components/themes/yar/YarTagPosts.astro
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
---
|
||||
import SiteNav from '../../SiteNav.astro';
|
||||
import { site } from '../../../lib/siteConfig';
|
||||
import type { Post, TagSummary } from '../../../lib/posts';
|
||||
|
||||
type Props = {
|
||||
tag: TagSummary;
|
||||
posts: Post[];
|
||||
};
|
||||
|
||||
const { tag, posts } = Astro.props;
|
||||
---
|
||||
|
||||
<main class="yar-tag-page">
|
||||
<div class="yar-shell">
|
||||
<header class="yar-header">
|
||||
<div class="yar-header-inner">
|
||||
<div class="yar-title-box">
|
||||
<h1 class="yar-title">{site.title}</h1>
|
||||
</div>
|
||||
<SiteNav />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="yar-tag-posts" aria-label={`Posts tagged ${tag.name}`}>
|
||||
<div class="yar-current-tag">
|
||||
<span>{tag.name}</span>
|
||||
<strong>{tag.count}</strong>
|
||||
</div>
|
||||
|
||||
<div class="yar-post-list">
|
||||
{posts.map((post) => (
|
||||
<article class="yar-post-item">
|
||||
<a href={post.url}>
|
||||
<h2>{post.title}</h2>
|
||||
{post.date && (
|
||||
<time datetime={post.date}>
|
||||
{new Date(post.date).toLocaleDateString(site.language)}
|
||||
</time>
|
||||
)}
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="yar-footer">
|
||||
<p>由 osaet 构建</p>
|
||||
<p>© 2026 Osaet. All rights reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.yar-tag-page {
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
color: #1c211b;
|
||||
}
|
||||
|
||||
.yar-shell {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 40px 0 5vh;
|
||||
}
|
||||
|
||||
.yar-header {
|
||||
border-bottom: 1px solid rgba(28, 33, 27, 0.18);
|
||||
}
|
||||
|
||||
.yar-header-inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
width: 50vw;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25em;
|
||||
justify-content: flex-end;
|
||||
margin: 0;
|
||||
border-radius: 0.75em;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0em 0.2em rgb(29 53 87 / 13%);
|
||||
padding: 0.5em 1.4em;
|
||||
color: #53605a;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav a) {
|
||||
border-radius: 0.65em;
|
||||
color: inherit;
|
||||
padding: 0.55em 0.8em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav a:hover) {
|
||||
background: #eef1ed;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-title-box {
|
||||
border-radius: 0.7em;
|
||||
background: #1d3557;
|
||||
padding: 1em 1.4em;
|
||||
}
|
||||
|
||||
.yar-title {
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
sans-serif;
|
||||
font-size: 1em;
|
||||
letter-spacing: 0.16em;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.yar-tag-posts {
|
||||
width: 50vw;
|
||||
margin: 0 auto;
|
||||
padding: 36px 0 0;
|
||||
color: #3d3d3f;
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
"Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN",
|
||||
sans-serif;
|
||||
font-size: 16px;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
|
||||
.yar-current-tag {
|
||||
display: inline-flex;
|
||||
gap: 0.7em;
|
||||
align-items: center;
|
||||
margin-bottom: 2em;
|
||||
border-radius: 999em;
|
||||
background: #f3f6f4;
|
||||
box-shadow: inset 0 0 0 1px rgb(29 53 87 / 7%);
|
||||
color: #53605a;
|
||||
padding: 0.7em 1em;
|
||||
}
|
||||
|
||||
.yar-current-tag strong {
|
||||
color: #1d3557;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.yar-post-list {
|
||||
display: grid;
|
||||
gap: 0.9em;
|
||||
}
|
||||
|
||||
.yar-post-item a {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
border-radius: 0.75em;
|
||||
color: inherit;
|
||||
padding: 0.8em 1em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-post-item a:hover {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0em 0.35em rgb(29 53 87 / 13%);
|
||||
}
|
||||
|
||||
.yar-post-item h2 {
|
||||
margin: 0;
|
||||
font-size: 1em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.yar-post-item time {
|
||||
color: #7a8375;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.yar-footer {
|
||||
width: 50vw;
|
||||
margin: auto auto 0;
|
||||
color: #8a9089;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.8;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.yar-shell {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.yar-header-inner,
|
||||
.yar-tag-posts,
|
||||
.yar-footer {
|
||||
width: calc(100% - 28px);
|
||||
}
|
||||
|
||||
.yar-header-inner {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.yar-post-item a {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.45em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
210
frontend/site/src/components/themes/yar/YarTags.astro
Normal file
210
frontend/site/src/components/themes/yar/YarTags.astro
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
---
|
||||
import SiteNav from '../../SiteNav.astro';
|
||||
import { site } from '../../../lib/siteConfig';
|
||||
import type { TagSummary } from '../../../lib/posts';
|
||||
|
||||
type Props = {
|
||||
tags: TagSummary[];
|
||||
};
|
||||
|
||||
const { tags } = Astro.props;
|
||||
---
|
||||
|
||||
<main class="yar-tags-page">
|
||||
<div class="yar-shell">
|
||||
<header class="yar-header">
|
||||
<div class="yar-header-inner">
|
||||
<div class="yar-title-box">
|
||||
<h1 class="yar-title">{site.title}</h1>
|
||||
</div>
|
||||
<SiteNav />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="yar-tags-section" aria-label="Tags">
|
||||
{tags.length === 0 ? (
|
||||
<p class="yar-empty">No tags yet.</p>
|
||||
) : (
|
||||
<ul class="yar-tag-index">
|
||||
{tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tag.slug}/`}>
|
||||
<span>{tag.name}</span>
|
||||
<strong>{tag.count}</strong>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<footer class="yar-footer">
|
||||
<p>由 osaet 构建</p>
|
||||
<p>© 2026 Osaet. All rights reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.yar-tags-page {
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
color: #1c211b;
|
||||
}
|
||||
|
||||
.yar-shell {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 40px 0 5vh;
|
||||
}
|
||||
|
||||
.yar-header {
|
||||
border-bottom: 1px solid rgba(28, 33, 27, 0.18);
|
||||
}
|
||||
|
||||
.yar-header-inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
width: 50vw;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25em;
|
||||
justify-content: flex-end;
|
||||
margin: 0;
|
||||
border-radius: 0.75em;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0em 0.2em rgb(29 53 87 / 13%);
|
||||
padding: 0.5em 1.4em;
|
||||
color: #53605a;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav a) {
|
||||
border-radius: 0.65em;
|
||||
color: inherit;
|
||||
padding: 0.55em 0.8em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-shell :global(.site-nav a:hover) {
|
||||
background: #eef1ed;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-title-box {
|
||||
border-radius: 0.7em;
|
||||
background: #1d3557;
|
||||
padding: 1em 1.4em;
|
||||
}
|
||||
|
||||
.yar-title {
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
sans-serif;
|
||||
font-size: 1em;
|
||||
letter-spacing: 0.16em;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.yar-tags-section {
|
||||
width: 50vw;
|
||||
margin: 0 auto;
|
||||
padding: 36px 0 0;
|
||||
color: #3d3d3f;
|
||||
font-family:
|
||||
"Heti Hei", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
"Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN",
|
||||
sans-serif;
|
||||
font-size: 16px;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
|
||||
.yar-tag-index {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.8em;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.yar-tag-index a {
|
||||
display: inline-flex;
|
||||
gap: 0.7em;
|
||||
align-items: center;
|
||||
border-radius: 999em;
|
||||
background: #f3f6f4;
|
||||
box-shadow: inset 0 0 0 1px rgb(29 53 87 / 7%);
|
||||
color: #53605a;
|
||||
padding: 0.7em 1em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yar-tag-index a:hover {
|
||||
background: #e8eee9;
|
||||
}
|
||||
|
||||
.yar-tag-index strong {
|
||||
color: #1d3557;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.yar-empty {
|
||||
margin: 0;
|
||||
color: #596254;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-footer {
|
||||
width: 50vw;
|
||||
margin: auto auto 0;
|
||||
color: #8a9089;
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.8;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yar-footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.yar-shell {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.yar-header-inner,
|
||||
.yar-tags-section,
|
||||
.yar-footer {
|
||||
width: calc(100% - 28px);
|
||||
}
|
||||
|
||||
.yar-header-inner {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -5,15 +5,18 @@ type MarkdownPost = {
|
|||
summary?: string;
|
||||
status?: string;
|
||||
tags?: string[];
|
||||
created_at?: string;
|
||||
published_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
Content: any;
|
||||
};
|
||||
|
||||
export type Post = MarkdownPost['frontmatter'] & {
|
||||
url: string;
|
||||
date: string;
|
||||
tags: string[];
|
||||
Content: any;
|
||||
};
|
||||
|
||||
export type TagSummary = {
|
||||
|
|
@ -27,23 +30,40 @@ export type ArchiveYear = {
|
|||
posts: Post[];
|
||||
};
|
||||
|
||||
export const POSTS_PER_PAGE = 6;
|
||||
|
||||
function isPublicStatus(status?: string) {
|
||||
return status === 'published' || status === 'archived';
|
||||
}
|
||||
|
||||
export function getPublishedPosts(): Post[] {
|
||||
const modules = import.meta.glob('../../../../content/posts/*.md', { eager: true });
|
||||
|
||||
return Object.values(modules)
|
||||
.map((post) => {
|
||||
const frontmatter = (post as MarkdownPost).frontmatter;
|
||||
const Content = (post as MarkdownPost).Content;
|
||||
return {
|
||||
...frontmatter,
|
||||
Content,
|
||||
url: `/posts/${frontmatter.slug}/`,
|
||||
date: frontmatter.published_at ?? frontmatter.updated_at ?? '',
|
||||
date: frontmatter.created_at ?? frontmatter.published_at ?? frontmatter.updated_at ?? '',
|
||||
tags: frontmatter.tags ?? []
|
||||
};
|
||||
})
|
||||
.filter((post) => post.status === 'published')
|
||||
.filter((post) => isPublicStatus(post.status))
|
||||
.sort((a, b) => String(b.date).localeCompare(String(a.date)));
|
||||
}
|
||||
|
||||
export function getPaginatedPosts(posts: Post[], page: number, perPage = POSTS_PER_PAGE): Post[] {
|
||||
const start = (page - 1) * perPage;
|
||||
return posts.slice(start, start + perPage);
|
||||
}
|
||||
|
||||
export function getTotalPages(posts: Post[], perPage = POSTS_PER_PAGE): number {
|
||||
return Math.max(1, Math.ceil(posts.length / perPage));
|
||||
}
|
||||
|
||||
export function tagSlug(tag: string): string {
|
||||
return encodeURIComponent(tag.trim().toLowerCase().replace(/\s+/g, '-'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ type SiteConfig = {
|
|||
base_url: string;
|
||||
language: string;
|
||||
timezone: string;
|
||||
theme: string;
|
||||
};
|
||||
content: {
|
||||
posts_dir: string;
|
||||
|
|
@ -29,7 +30,8 @@ const defaults: SiteConfig = {
|
|||
description: 'Personal blog',
|
||||
base_url: 'http://localhost:4321',
|
||||
language: 'zh-CN',
|
||||
timezone: 'Asia/Shanghai'
|
||||
timezone: 'Asia/Shanghai',
|
||||
theme: 'default'
|
||||
},
|
||||
content: {
|
||||
posts_dir: 'content/posts',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
---
|
||||
import '../../styles/global.css';
|
||||
import SiteNav from '../../components/SiteNav.astro';
|
||||
import '../../styles/normalize.css';
|
||||
import DefaultArchive from '../../components/DefaultArchive.astro';
|
||||
import YarArchive from '../../components/themes/yar/YarArchive.astro';
|
||||
import { site } from '../../lib/siteConfig';
|
||||
import { getArchiveYears, tagSlug } from '../../lib/posts';
|
||||
import { getArchiveYears } from '../../lib/posts';
|
||||
|
||||
const archiveYears = getArchiveYears();
|
||||
const theme = site.theme?.trim().toLowerCase() ?? 'default';
|
||||
const ArchiveView = theme === 'yar' ? YarArchive : DefaultArchive;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
|
@ -16,49 +19,6 @@ const archiveYears = getArchiveYears();
|
|||
<meta name="description" content={`Archive of ${site.title}`} />
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<SiteNav />
|
||||
|
||||
<header class="site-header">
|
||||
<p class="eyebrow">{site.title}</p>
|
||||
<h1>Archive</h1>
|
||||
</header>
|
||||
|
||||
{archiveYears.length === 0 ? (
|
||||
<p class="empty">No published posts yet.</p>
|
||||
) : (
|
||||
<div class="archive-list">
|
||||
{archiveYears.map((group) => (
|
||||
<section class="archive-year" aria-label={group.year}>
|
||||
<h2>{group.year}</h2>
|
||||
<ol>
|
||||
{group.posts.map((post) => (
|
||||
<li>
|
||||
{post.date && (
|
||||
<time datetime={post.date}>
|
||||
{new Date(post.date).toLocaleDateString(site.language, {
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})}
|
||||
</time>
|
||||
)}
|
||||
<a href={post.url}>{post.title}</a>
|
||||
{post.tags.length > 0 && (
|
||||
<ul class="tags compact-tags" aria-label="Tags">
|
||||
{post.tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tagSlug(tag)}/`}>{tag}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
<ArchiveView archiveYears={archiveYears} />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
---
|
||||
import '../styles/global.css';
|
||||
import SiteNav from '../components/SiteNav.astro';
|
||||
import '../styles/normalize.css';
|
||||
import DefaultHome from '../components/DefaultHome.astro';
|
||||
import YarHome from '../components/themes/yar/YarHome.astro';
|
||||
import { site } from '../lib/siteConfig';
|
||||
import { getPublishedPosts, tagSlug } from '../lib/posts';
|
||||
import { getPaginatedPosts, getPublishedPosts, getTotalPages } from '../lib/posts';
|
||||
|
||||
const posts = getPublishedPosts();
|
||||
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;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
|
@ -16,44 +21,6 @@ const posts = getPublishedPosts();
|
|||
<meta name="description" content={site.description} />
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<SiteNav />
|
||||
|
||||
<header class="site-header">
|
||||
<p class="eyebrow">{site.description}</p>
|
||||
<h1>{site.title}</h1>
|
||||
</header>
|
||||
|
||||
<section class="post-list" aria-label="Posts">
|
||||
{posts.length === 0 ? (
|
||||
<p class="empty">No published posts yet.</p>
|
||||
) : (
|
||||
posts.map((post) => (
|
||||
<article class="post-item">
|
||||
<a href={post.url}>
|
||||
<h2>{post.title}</h2>
|
||||
{post.summary && <p>{post.summary}</p>}
|
||||
<div class="post-meta">
|
||||
{post.date && (
|
||||
<time datetime={post.date}>
|
||||
{new Date(post.date).toLocaleDateString(site.language)}
|
||||
</time>
|
||||
)}
|
||||
{post.tags.length > 0 && (
|
||||
<ul class="tags" aria-label="Tags">
|
||||
{post.tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tagSlug(tag)}/`}>{tag}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
))
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
<Home posts={posts} currentPage={1} totalPages={totalPages} />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
40
frontend/site/src/pages/page/[page].astro
Normal file
40
frontend/site/src/pages/page/[page].astro
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
import '../../styles/normalize.css';
|
||||
import DefaultHome from '../../components/DefaultHome.astro';
|
||||
import YarHome from '../../components/themes/yar/YarHome.astro';
|
||||
import { site } from '../../lib/siteConfig';
|
||||
import { getPaginatedPosts, getPublishedPosts, getTotalPages } from '../../lib/posts';
|
||||
|
||||
export function getStaticPaths() {
|
||||
const allPosts = getPublishedPosts();
|
||||
const totalPages = getTotalPages(allPosts);
|
||||
|
||||
return Array.from({ length: Math.max(0, totalPages - 1) }, (_, index) => {
|
||||
const page = index + 2;
|
||||
return {
|
||||
params: { page: String(page) },
|
||||
props: { page }
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const allPosts = getPublishedPosts();
|
||||
const posts = getPaginatedPosts(allPosts, page);
|
||||
const totalPages = getTotalPages(allPosts);
|
||||
const theme = site.theme?.trim().toLowerCase() ?? 'default';
|
||||
const Home = theme === 'yar' ? YarHome : DefaultHome;
|
||||
---
|
||||
|
||||
<!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} />
|
||||
</head>
|
||||
<body>
|
||||
<Home posts={posts} currentPage={page} totalPages={totalPages} />
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
import '../../styles/global.css';
|
||||
import SiteNav from '../../components/SiteNav.astro';
|
||||
import '../../styles/normalize.css';
|
||||
import DefaultPost from '../../components/DefaultPost.astro';
|
||||
import YarPost from '../../components/themes/yar/YarPost.astro';
|
||||
import { site } from '../../lib/siteConfig';
|
||||
import { tagSlug } from '../../lib/posts';
|
||||
|
||||
export function getStaticPaths() {
|
||||
const modules = import.meta.glob('../../../../../content/posts/*.md', { eager: true });
|
||||
return Object.values(modules)
|
||||
.filter((post) => post.frontmatter.status === 'published')
|
||||
.filter((post) => post.frontmatter.status === 'published' || post.frontmatter.status === 'archived')
|
||||
.map((post) => ({
|
||||
params: { slug: post.frontmatter.slug },
|
||||
props: { post }
|
||||
|
|
@ -15,8 +15,9 @@ export function getStaticPaths() {
|
|||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { Content } = post;
|
||||
const title = post.frontmatter.title;
|
||||
const theme = site.theme?.trim().toLowerCase() ?? 'default';
|
||||
const PostView = theme === 'yar' ? YarPost : DefaultPost;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
|
@ -28,29 +29,6 @@ const title = post.frontmatter.title;
|
|||
{post.frontmatter.summary && <meta name="description" content={post.frontmatter.summary} />}
|
||||
</head>
|
||||
<body>
|
||||
<main class="page article-page">
|
||||
<SiteNav />
|
||||
|
||||
<article class="article">
|
||||
<header>
|
||||
<h1>{title}</h1>
|
||||
<time datetime={post.frontmatter.published_at ?? post.frontmatter.updated_at}>
|
||||
{new Date(post.frontmatter.published_at ?? post.frontmatter.updated_at).toLocaleDateString(site.language)}
|
||||
</time>
|
||||
{post.frontmatter.summary && <p class="summary">{post.frontmatter.summary}</p>}
|
||||
{post.frontmatter.tags?.length > 0 && (
|
||||
<ul class="tags article-tags" aria-label="Tags">
|
||||
{post.frontmatter.tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tagSlug(tag)}/`}>{tag}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<Content />
|
||||
</article>
|
||||
</main>
|
||||
<PostView post={post} />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
import '../../styles/global.css';
|
||||
import SiteNav from '../../components/SiteNav.astro';
|
||||
import '../../styles/normalize.css';
|
||||
import DefaultTagPosts from '../../components/DefaultTagPosts.astro';
|
||||
import YarTagPosts from '../../components/themes/yar/YarTagPosts.astro';
|
||||
import { site } from '../../lib/siteConfig';
|
||||
import { getPostsByTag, getTagSummaries } from '../../lib/posts';
|
||||
|
||||
|
|
@ -13,6 +14,8 @@ export function getStaticPaths() {
|
|||
|
||||
const { tag } = Astro.props;
|
||||
const posts = getPostsByTag(tag.slug);
|
||||
const theme = site.theme?.trim().toLowerCase() ?? 'default';
|
||||
const TagPostsView = theme === 'yar' ? YarTagPosts : DefaultTagPosts;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
|
@ -24,29 +27,6 @@ const posts = getPostsByTag(tag.slug);
|
|||
<meta name="description" content={`Posts tagged ${tag.name}`} />
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<SiteNav />
|
||||
|
||||
<header class="site-header">
|
||||
<p class="eyebrow">{site.title}</p>
|
||||
<h1>{tag.name}</h1>
|
||||
</header>
|
||||
|
||||
<section class="post-list" aria-label={`Posts tagged ${tag.name}`}>
|
||||
{posts.map((post) => (
|
||||
<article class="post-item">
|
||||
<a href={post.url}>
|
||||
<h2>{post.title}</h2>
|
||||
{post.summary && <p>{post.summary}</p>}
|
||||
{post.date && (
|
||||
<time datetime={post.date}>
|
||||
{new Date(post.date).toLocaleDateString(site.language)}
|
||||
</time>
|
||||
)}
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
</main>
|
||||
<TagPostsView tag={tag} posts={posts} />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
---
|
||||
import '../../styles/global.css';
|
||||
import SiteNav from '../../components/SiteNav.astro';
|
||||
import '../../styles/normalize.css';
|
||||
import DefaultTags from '../../components/DefaultTags.astro';
|
||||
import YarTags from '../../components/themes/yar/YarTags.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;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
|
@ -16,28 +19,6 @@ const tags = getTagSummaries();
|
|||
<meta name="description" content={`Tags on ${site.title}`} />
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<SiteNav />
|
||||
|
||||
<header class="site-header">
|
||||
<p class="eyebrow">{site.title}</p>
|
||||
<h1>Tags</h1>
|
||||
</header>
|
||||
|
||||
{tags.length === 0 ? (
|
||||
<p class="empty">No tags yet.</p>
|
||||
) : (
|
||||
<ul class="tag-index" aria-label="Tags">
|
||||
{tags.map((tag) => (
|
||||
<li>
|
||||
<a href={`/tags/${tag.slug}/`}>
|
||||
<span>{tag.name}</span>
|
||||
<strong>{tag.count}</strong>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</main>
|
||||
<TagsView tags={tags} />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
180
frontend/site/src/styles/normalize.css
vendored
Normal file
180
frontend/site/src/styles/normalize.css
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
html {
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none;
|
||||
text-decoration: underline;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
legend {
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
display: table;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue