mirror of
https://github.com/ClovertaTheTrilobita/SanYeCao-blog.git
synced 2026-04-01 17:50:13 +00:00
added locale switch and adjusted css
This commit is contained in:
parent
4c623030ba
commit
80e2e1b156
32 changed files with 3641 additions and 171 deletions
95
build/fontmin.js
Normal file
95
build/fontmin.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import Fontmin from "fontmin";
|
||||
|
||||
function getFiles(dir) {
|
||||
const results = [];
|
||||
const list = fs.readdirSync(dir);
|
||||
|
||||
for (const file of list) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
results.push(...getFiles(filePath));
|
||||
} else {
|
||||
results.push(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function scanDirectory(dir) {
|
||||
let set = new Set();
|
||||
const files = getFiles(dir);
|
||||
|
||||
for (const file of files) {
|
||||
const ignoredExtensions = [
|
||||
".ttf",
|
||||
".otf",
|
||||
".woff",
|
||||
".woff2",
|
||||
".eot",
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".webp",
|
||||
".gif",
|
||||
".ico",
|
||||
".pdf",
|
||||
];
|
||||
|
||||
if (ignoredExtensions.some((ext) => file.endsWith(ext))) continue;
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(file, "utf8");
|
||||
const currentSet = new Set(content);
|
||||
set = new Set([...set, ...currentSet]);
|
||||
} catch {
|
||||
// 跳过二进制等不可读文件
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
function subsetFont(src, text) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fontmin = new Fontmin()
|
||||
.src(src)
|
||||
.use(
|
||||
Fontmin.glyph({
|
||||
text,
|
||||
hinting: false,
|
||||
})
|
||||
)
|
||||
.use(Fontmin.ttf2woff2())
|
||||
.dest("public/fonts/subset");
|
||||
|
||||
fontmin.run((err) => {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const baseChars =
|
||||
"首页文章标签关于作者评论发布于切换主题,。!?:“”‘’()《》【】、—…·-_/\\'\"()[]{}<>:;.!? ";
|
||||
const scanned = Array.from(scanDirectory("src")).join("");
|
||||
const chars = Array.from(new Set((scanned + baseChars).split(""))).join("");
|
||||
|
||||
await Promise.all([
|
||||
subsetFont("public/fonts/MapleMono-CN-Regular.ttf", chars),
|
||||
subsetFont("public/fonts/MapleMono-CN-Bold.ttf", chars),
|
||||
subsetFont("public/fonts/MapleMono-CN-Italic.ttf", chars),
|
||||
]);
|
||||
|
||||
console.log(`中文子集字体生成完成,共收集 ${chars.length} 个字符`);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
2988
package-lock.json
generated
2988
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -7,9 +7,10 @@
|
|||
},
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
"astro": "astro",
|
||||
"subset-font": "node build/fontmin.js",
|
||||
"build": "npm run subset-font && astro build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/rss": "^4.0.17",
|
||||
|
|
@ -17,6 +18,7 @@
|
|||
"url": "^0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.0"
|
||||
"@types/node": "^25.5.0",
|
||||
"fontmin": "^1.1.1"
|
||||
}
|
||||
}
|
||||
BIN
public/fonts/MapleMono-CN-Bold.ttf
Normal file
BIN
public/fonts/MapleMono-CN-Bold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/MapleMono-CN-Italic.ttf
Normal file
BIN
public/fonts/MapleMono-CN-Italic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/MapleMono-CN-Regular.ttf
Normal file
BIN
public/fonts/MapleMono-CN-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/MapleMono-Italic.ttf.woff2
Normal file
BIN
public/fonts/MapleMono-Italic.ttf.woff2
Normal file
Binary file not shown.
BIN
public/fonts/subset/MapleMono-CN-Bold.ttf
Normal file
BIN
public/fonts/subset/MapleMono-CN-Bold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/subset/MapleMono-CN-Bold.woff2
Normal file
BIN
public/fonts/subset/MapleMono-CN-Bold.woff2
Normal file
Binary file not shown.
BIN
public/fonts/subset/MapleMono-CN-Italic.ttf
Normal file
BIN
public/fonts/subset/MapleMono-CN-Italic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/subset/MapleMono-CN-Italic.woff2
Normal file
BIN
public/fonts/subset/MapleMono-CN-Italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/subset/MapleMono-CN-Regular.ttf
Normal file
BIN
public/fonts/subset/MapleMono-CN-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/subset/MapleMono-CN-Regular.woff2
Normal file
BIN
public/fonts/subset/MapleMono-CN-Regular.woff2
Normal file
Binary file not shown.
|
|
@ -1,8 +1,57 @@
|
|||
---
|
||||
const platform = "github";
|
||||
const username = "ClovertaTheTrilobita";
|
||||
import { getLangFromUrl, getTranslations, type Lang } from "@/i18n";
|
||||
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = getTranslations(lang);
|
||||
---
|
||||
|
||||
<footer>
|
||||
<p>Learn more about my projects on <a href={`https://www.${platform}.com/${username}`}>{platform}</a>!</p>
|
||||
<footer class="footer">
|
||||
<p set:html={t.footer.githubIntro} />
|
||||
<p set:html={t.footer.repoIntro} />
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
.footer {
|
||||
margin-top: 1rem;
|
||||
padding: 1.5rem 0 0;
|
||||
font-size: 0.95rem;
|
||||
opacity: 0.9;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.footer::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
margin-bottom: 1rem;
|
||||
background: repeating-linear-gradient(
|
||||
-45deg,
|
||||
#e96b6b 0 14px,
|
||||
transparent 14px 28px,
|
||||
#7da2ff 28px 42px,
|
||||
transparent 42px 56px
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 0.4rem 0;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #7fb3ff;
|
||||
font-weight: 700;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
:global(.dark) .footer::before {
|
||||
background: repeating-linear-gradient(
|
||||
-45deg,
|
||||
#ff8a8a 0 14px,
|
||||
transparent 14px 28px,
|
||||
#9ecbff 28px 42px,
|
||||
transparent 42px 56px
|
||||
);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,43 @@
|
|||
---
|
||||
import Navigation from "./Navigation.astro";
|
||||
import ThemeIcon from "./ThemeIcon.astro";
|
||||
import { getLangFromUrl, getTranslations } from "@/i18n";
|
||||
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = getTranslations(lang);
|
||||
---
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<h1>SanYeCao Blog</h1>
|
||||
<div>
|
||||
<header class="site-header">
|
||||
<div class="theme-icon-wrap">
|
||||
<ThemeIcon />
|
||||
</div>
|
||||
|
||||
<nav class="header-nav">
|
||||
<h1>{t.banner.title}</h1>
|
||||
<Navigation />
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
div {
|
||||
.site-header {
|
||||
position: relative;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.theme-icon-wrap {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header-nav h1 {
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,69 @@
|
|||
---
|
||||
import { getLangFromUrl, useTranslations } from "@/i18n";
|
||||
import { getLangFromUrl, getTranslations } from "@/i18n";
|
||||
|
||||
const langParam = Astro.url.searchParams.get("lang");
|
||||
const lang = langParam === "en" ? "en" : "zh";
|
||||
const t = useTranslations(lang);
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = getTranslations(lang);
|
||||
---
|
||||
|
||||
<nav>
|
||||
<a href={`/?lang=${lang}`}>{t("nav.home")}</a>
|
||||
<a href={`/about?lang=${lang}`}>{t("nav.about")}</a>
|
||||
<a href={`/tags?lang=${lang}`}>{t("nav.tags")}</a>
|
||||
<nav class="site-nav">
|
||||
<a href={`/${lang}`}>{t.nav.home}</a>
|
||||
<a href={`/${lang}/about`}>{t.nav.about}</a>
|
||||
<a href={`/${lang}/tags`}>{t.nav.tags}</a>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.site-nav {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
margin: 1rem 0;
|
||||
padding-bottom: 3rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.site-nav::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 12px;
|
||||
background: repeating-linear-gradient(
|
||||
-45deg,
|
||||
#e96b6b 0 14px,
|
||||
transparent 14px 28px,
|
||||
#7da2ff 28px 42px,
|
||||
transparent 42px 56px
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:global(.dark) .site-nav::after {
|
||||
background: repeating-linear-gradient(
|
||||
-45deg,
|
||||
#ff8a8a 0 14px,
|
||||
transparent 14px 28px,
|
||||
#9ecbff 28px 42px,
|
||||
transparent 42px 56px
|
||||
);
|
||||
}
|
||||
|
||||
.site-nav a {
|
||||
color: black;
|
||||
text-decoration: underline;
|
||||
font-weight: 700;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.site-nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
:global(.dark) .site-nav a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
:global(.dark) .site-nav a:hover {
|
||||
color: #ddd;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,19 @@
|
|||
---
|
||||
const pathname = Astro.url.pathname;
|
||||
const segments = pathname.split("/").filter(Boolean);
|
||||
|
||||
const currentLang = segments[0] === "en" ? "en" : "zh";
|
||||
const nextLang = currentLang === "zh" ? "en" : "zh";
|
||||
const switchLabel = currentLang === "zh" ? "EN" : "中文";
|
||||
|
||||
if (segments.length > 0) {
|
||||
segments[0] = nextLang;
|
||||
}
|
||||
|
||||
const switchHref = "/" + segments.join("/");
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="top-actions">
|
||||
<button id="themeToggle" aria-label="Toggle theme">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
|
|
@ -22,15 +34,57 @@
|
|||
</svg>
|
||||
</button>
|
||||
|
||||
<a class="lang-switch" href={switchHref}>
|
||||
{switchLabel}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.top-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
#themeToggle {
|
||||
border: 0;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
padding: 0.35rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
opacity 0.2s ease;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
#themeToggle:hover {
|
||||
transform: rotate(12deg);
|
||||
}
|
||||
|
||||
#themeToggle:active {
|
||||
transform: rotate(18deg) scale(0.96);
|
||||
}
|
||||
|
||||
.lang-switch {
|
||||
color: black; !important
|
||||
font-weight: 700; !important
|
||||
font-size: 0.95rem; !important
|
||||
text-decoration: none; !important
|
||||
line-height: 1; !important
|
||||
}
|
||||
|
||||
.lang-switch:hover {
|
||||
text-decoration: underline; !important
|
||||
}
|
||||
|
||||
.sun {
|
||||
fill: black;
|
||||
}
|
||||
|
||||
.moon {
|
||||
fill: transparent;
|
||||
}
|
||||
|
|
@ -38,9 +92,14 @@
|
|||
:global(.dark) .sun {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
:global(.dark) .moon {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
:global(.dark) .lang-switch {
|
||||
color: #eaeaea;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
export default {
|
||||
banner: {
|
||||
title: "Cloverta's Blog",
|
||||
subtitle: ""
|
||||
},
|
||||
nav: {
|
||||
home: "Home",
|
||||
posts: "Posts",
|
||||
|
|
@ -13,4 +17,31 @@ export default {
|
|||
theme: {
|
||||
toggle: "Toggle theme",
|
||||
},
|
||||
langSwitcher: "中文",
|
||||
home: {
|
||||
content: ""
|
||||
},
|
||||
about: {
|
||||
title: "About Me, and This Blog",
|
||||
name: "Cloverta",
|
||||
slogan: "Sata Andagi!!!",
|
||||
profilePicture: "https://files.seeusercontent.com/2026/03/24/Ne8b/009BC44B87E00F74351AA6730F8B7353.jpg",
|
||||
content: [
|
||||
"Who am I? Who are you? What even am I?",
|
||||
"Whatever brought you to this page, I'm glad our paths crossed here on the internet.",
|
||||
'This blog is built with <a href="https://astro.build/">Astro</a> and is a fully static frontend website. Compared with third-party commercial blog platforms or WordPress, it is simpler, more efficient, faster, and, at least to my taste, more beautiful.',
|
||||
"At first, I self-hosted WordPress on my server, but it was just too heavy — loading an article of only a few hundred words could still take several seconds. For any programmer anxiously searching online for a solution, that can be quite frustrating. So for a long time, I had wanted to build a static blog from scratch myself.",
|
||||
"As you can see, the functionality of this website may still be incomplete for now. I will continue improving and maintaining it over time.",
|
||||
"The design of this blog was inspired by:",
|
||||
'· <a href="https://ex-tasty.com/">極限風味</a>',
|
||||
'· <a href="https://blog.cloudti.de/">Parsifal\'s Blog</a>',
|
||||
"· And some other websites whose names I have unfortunately forgotten",
|
||||
"Thank you for your ideas and passion!",
|
||||
"In addition, this blog is fully open source. You can find its source code through the link in the footer.",
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
githubIntro: 'See more on <a href="https://www.github.com/ClovertaTheTrilobita">GitHub</a>!',
|
||||
repoIntro: 'This blog is fully open source at <a href="https://www.github.com/ClovertaTheTrilobita/SanYeCao-blog">ClovertaTheTrilobita/SanYeCao-blog</a>'
|
||||
}
|
||||
};
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import zh from "./zh.ts";
|
||||
import en from "./en.ts";
|
||||
import zh from "./zh";
|
||||
import en from "./en";
|
||||
|
||||
export const languages = {
|
||||
zh,
|
||||
|
|
@ -10,19 +10,9 @@ export type Lang = keyof typeof languages;
|
|||
|
||||
export function getLangFromUrl(url: URL): Lang {
|
||||
const lang = url.pathname.split("/")[1];
|
||||
if (lang === "en") return "en";
|
||||
return "zh";
|
||||
return lang === "en" ? "en" : "zh";
|
||||
}
|
||||
|
||||
export function useTranslations(lang: Lang) {
|
||||
return function t(path: string) {
|
||||
const keys = path.split(".");
|
||||
let current: any = languages[lang];
|
||||
|
||||
for (const key of keys) {
|
||||
current = current?.[key];
|
||||
}
|
||||
|
||||
return current ?? path;
|
||||
};
|
||||
export function getTranslations(lang: Lang) {
|
||||
return languages[lang];
|
||||
}
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
export default {
|
||||
banner: {
|
||||
title: "Cloverta 的博客",
|
||||
subtitle: ""
|
||||
},
|
||||
nav: {
|
||||
home: "首页",
|
||||
posts: "文章",
|
||||
|
|
@ -13,4 +17,31 @@ export default {
|
|||
theme: {
|
||||
toggle: "切换主题",
|
||||
},
|
||||
langSwitcher: "EN",
|
||||
home: {
|
||||
content: ""
|
||||
},
|
||||
about: {
|
||||
title: "关于我,和这个博客",
|
||||
name: "三叶",
|
||||
slogan: "Sata Andagi!!!",
|
||||
profilePicture: "https://files.seeusercontent.com/2026/03/24/Ne8b/009BC44B87E00F74351AA6730F8B7353.jpg",
|
||||
content: [
|
||||
"我是?你是?我是??",
|
||||
"无论你是因为什么点开了这个页面,都很高兴能在互联网中偶遇你。",
|
||||
'这个博客使用 <a href="https://astro.build/">Astro</a> 构建,是一个完全静态的纯前端网页。相比第三方商业博客平台或 WordPress,它更简洁、更高效、更快速,并且(至少在我的审美里)也更美观。',
|
||||
"最初,我曾在服务器上自建过 WordPress,但它实在太重了——加载短短几百字的文章竟然也要花上好几秒。这对于任何一个火急火燎地上网查解决方案的码农来说,都是相当折磨的事情。因此,我从很久以前就想着自己从头写一个静态博客。",
|
||||
"如你所见,这个网站的功能现在可能还不算完善。我会在接下来的时间里慢慢把它维护好。",
|
||||
"这个博客在设计理念上参考了:",
|
||||
'· <a href="https://ex-tasty.com/">極限風味</a>',
|
||||
'· <a href="https://blog.cloudti.de/">Parsifal\'s Blog</a>',
|
||||
"· 还有一些已经忘记名字的网站",
|
||||
"谢谢你们的想法和热情!",
|
||||
"此外,这个博客完全开源,你可以从页脚的链接处获取它的源代码。",
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
githubIntro: '在 <a href="https://www.github.com/ClovertaTheTrilobita">GitHub</a> 查看更多!',
|
||||
repoIntro: '这个博客完全开源于 <a href="https://www.github.com/ClovertaTheTrilobita/SanYeCao-blog">ClovertaTheTrilobita/SanYeCao-blog</a>'
|
||||
}
|
||||
};
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
---
|
||||
import Footer from "../components/Footer.astro";
|
||||
import Footer from "@/components/Footer.astro";
|
||||
import Header from "@/components/Header.astro";
|
||||
import { getLangFromUrl, useTranslations } from "@/i18n";
|
||||
|
||||
const { pageTitle } = Astro.props;
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
|
|
|||
96
src/pages/[lang]/about.astro
Normal file
96
src/pages/[lang]/about.astro
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||
import { getLangFromUrl, getTranslations } from "@/i18n";
|
||||
import "@/styles/global.css";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return [{ params: { lang: "zh" } }, { params: { lang: "en" } }];
|
||||
}
|
||||
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = getTranslations(lang);
|
||||
const pageTitle = t.about.title;
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={pageTitle}>
|
||||
<main class="about">
|
||||
<h1>{t.about.title}</h1>
|
||||
|
||||
<div class="intro">
|
||||
<div class="intro-text">
|
||||
<h2 class="name">{t.about.name}</h2>
|
||||
<p class="slogan">{t.about.slogan}</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src={t.about.profilePicture}
|
||||
alt={t.about.name}
|
||||
width="160"
|
||||
class="avatar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{t.about.content.map((line: string) => <p set:html={line} />)}
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.about {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.about h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.intro {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: 0 0 0.4rem 0;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.slogan {
|
||||
margin: 0;
|
||||
color: gray;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content p {
|
||||
line-height: 1.8;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.content a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.intro {
|
||||
flex-direction: column-reverse;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
17
src/pages/[lang]/index.astro
Normal file
17
src/pages/[lang]/index.astro
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||
import PostList from "@/components/Posts/PostList.astro";
|
||||
import "@/styles/global.css";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return [{ params: { lang: "zh" } }, { params: { lang: "en" } }];
|
||||
}
|
||||
|
||||
const { lang } = Astro.params;
|
||||
const pageTitle = "Homepage";
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={pageTitle}>
|
||||
<h1>My Astro Site</h1>
|
||||
<PostList />
|
||||
</BaseLayout>
|
||||
29
src/pages/[lang]/posts/[...slug].astro
Normal file
29
src/pages/[lang]/posts/[...slug].astro
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
import { getCollection, render } from "astro:content";
|
||||
import MarkdownPostLayout from "@/layouts/MarkdownPostLayout.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection("blog");
|
||||
const langs = ["zh", "en"];
|
||||
|
||||
return langs.flatMap((lang) =>
|
||||
posts.map((post) => ({
|
||||
params: {
|
||||
lang,
|
||||
slug: post.id,
|
||||
},
|
||||
props: {
|
||||
post,
|
||||
lang,
|
||||
},
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
const { post, lang } = Astro.props;
|
||||
const { Content } = await render(post);
|
||||
---
|
||||
|
||||
<MarkdownPostLayout frontmatter={post.data} lang={lang}>
|
||||
<Content />
|
||||
</MarkdownPostLayout>
|
||||
40
src/pages/[lang]/tags/[tag].astro
Normal file
40
src/pages/[lang]/tags/[tag].astro
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||
import PostItem from "@/components/Posts/PostItem.astro";
|
||||
import { getTranslations, type Lang } from "@/i18n";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const allPosts = await getCollection("blog");
|
||||
const uniqueTags = [
|
||||
...new Set(allPosts.map((post) => post.data.tags).flat()),
|
||||
];
|
||||
const langs: Lang[] = ["zh", "en"];
|
||||
|
||||
return langs.flatMap((lang) =>
|
||||
uniqueTags.map((tag) => {
|
||||
const filteredPosts = allPosts.filter((post) =>
|
||||
post.data.tags.includes(tag),
|
||||
);
|
||||
|
||||
return {
|
||||
params: { lang, tag },
|
||||
props: { posts: filteredPosts, lang },
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const { tag } = Astro.params;
|
||||
const { posts, lang } = Astro.props;
|
||||
const t = getTranslations(lang);
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={String(tag)}>
|
||||
<p>
|
||||
{lang === "zh" ? `带有标签 ${tag} 的文章` : `Posts tagged with ${tag}`}
|
||||
</p>
|
||||
<ul>
|
||||
{posts.map((post) => <PostItem post={post} lang={lang} />)}
|
||||
</ul>
|
||||
</BaseLayout>
|
||||
|
|
@ -1,10 +1,18 @@
|
|||
---
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import { getTranslations } from "@/i18n";
|
||||
import "@/styles/global.css";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return [{ params: { lang: "zh" } }, { params: { lang: "en" } }];
|
||||
}
|
||||
|
||||
const { lang } = Astro.params;
|
||||
const t = getTranslations(lang);
|
||||
const allPosts = await getCollection("blog");
|
||||
const tags = [...new Set(allPosts.map((post: any) => post.data.tags).flat())];
|
||||
const pageTitle = "Tag Index";
|
||||
const pageTitle = lang === "zh" ? "标签索引" : "Tag Index";
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={pageTitle}>
|
||||
|
|
@ -12,12 +20,13 @@ const pageTitle = "Tag Index";
|
|||
{
|
||||
tags.map((tag) => (
|
||||
<p class="tag">
|
||||
<a href={`/tags/${tag}`}>{tag}</a>
|
||||
<a href={`/${lang}/tags/${tag}`}>{tag}</a>
|
||||
</p>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
a {
|
||||
color: #00539f;
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
---
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||
import "@/styles/global.css";
|
||||
|
||||
const pageTitle = "About Me";
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={pageTitle}>
|
||||
<h1>{pageTitle}</h1>
|
||||
<h2>... and my new Astro site!</h2>
|
||||
|
||||
<p>
|
||||
I am working through Astro's introductory tutorial. This is the second page
|
||||
on my website, and it's the first one I built myself!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This site will update as I complete more of the tutorial, so keep checking
|
||||
back and see how my journey is going!
|
||||
</p>
|
||||
</BaseLayout>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
---
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import PostList from "@/components/Posts/PostList.astro";
|
||||
import "../styles/global.css";
|
||||
|
||||
const pageTitle = "Homepage";
|
||||
|
||||
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
|
||||
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={pageTitle}>
|
||||
<h1>My Astro Site</h1>
|
||||
<PostList />
|
||||
</BaseLayout>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
---
|
||||
import { getCollection, render } from "astro:content";
|
||||
import MarkdownPostLayout from "@/layouts/MarkdownPostLayout.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection("blog");
|
||||
return posts.map((post) => ({
|
||||
params: { slug: post.id },
|
||||
props: { post },
|
||||
}));
|
||||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { Content } = await render(post);
|
||||
---
|
||||
|
||||
<MarkdownPostLayout frontmatter={post.data}>
|
||||
<Content />
|
||||
</MarkdownPostLayout>
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import BaseLayout from "../../layouts/BaseLayout.astro";
|
||||
import PostItem from "@/components/Posts/PostItem.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const allPosts = await getCollection("blog");
|
||||
const uniqueTags = [
|
||||
...new Set(allPosts.map((post) => post.data.tags).flat()),
|
||||
];
|
||||
|
||||
return uniqueTags.map((tag) => {
|
||||
const filteredPosts = allPosts.filter((post) =>
|
||||
post.data.tags.includes(tag),
|
||||
);
|
||||
return {
|
||||
params: { tag },
|
||||
props: { posts: filteredPosts },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { tag } = Astro.params;
|
||||
const { posts } = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={tag}>
|
||||
<p>Posts tagged with {tag}</p>
|
||||
<ul>
|
||||
{
|
||||
posts.map((post) => (
|
||||
<PostItem url={`/posts/${post.id}/`} title={post.data.title} />
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</BaseLayout>
|
||||
0
src/scripts/main.ts
Normal file
0
src/scripts/main.ts
Normal file
|
|
@ -6,6 +6,14 @@
|
|||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Maple Mono";
|
||||
src: url("/fonts/MapleMono-Italic.ttf.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Maple Mono";
|
||||
src: url("/fonts/MapleMono-Bold.ttf.woff2") format("woff2");
|
||||
|
|
@ -14,9 +22,34 @@
|
|||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Maple Mono CN";
|
||||
src: url("/fonts/subset/MapleMono-CN-Regular.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Maple Mono CN";
|
||||
src: url("/fonts/subset/MapleMono-CN-Italic.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Maple Mono CN";
|
||||
src: url("/fonts/subset/MapleMono-CN-Bold.woff2") format("woff2");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
html {
|
||||
/* background-color: #f1f5f9; */
|
||||
font-family: "Maple Mono", monospace;
|
||||
font-family: "Maple Mono", "Maple Mono CN", monospace;
|
||||
background-color: #ffffff;
|
||||
color: #1f2328;
|
||||
}
|
||||
|
||||
body {
|
||||
|
|
@ -24,7 +57,7 @@ body {
|
|||
width: 100%;
|
||||
max-width: 80ch;
|
||||
padding: 1rem;
|
||||
line-height: 1.5;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
@ -34,27 +67,46 @@ body {
|
|||
h1 {
|
||||
margin: 1rem 0;
|
||||
font-size: 2.5rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #7fb3ff;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
background-color: #0d0950;
|
||||
color: #fff;
|
||||
background-color: #1e1e1e;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.dark .menu {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
background-color: #2a2a2a;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.dark .nav-links a {
|
||||
color: #9ecbff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.dark .nav-links a:hover,
|
||||
.dark .nav-links a:focus {
|
||||
color: #0d0950;
|
||||
}
|
||||
|
||||
.dark .nav-links a {
|
||||
color: #fff;
|
||||
color: #c2deff;
|
||||
}
|
||||
|
||||
.dark a {
|
||||
color: #ff9776;
|
||||
color: #9ecbff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.dark a:hover,
|
||||
.dark a:focus {
|
||||
color: #c2deff;
|
||||
}
|
||||
Loading…
Reference in a new issue