Compare commits

..

No commits in common. "4b1726ac0068be418fb6c20a8b03670ec5a413b2" and "d7fa430317f3afa7b62452e04a4f6790cfeb6fdc" have entirely different histories.

23 changed files with 3175 additions and 137 deletions

95
build/fontmin.js Normal file
View 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);
});

3020
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,8 @@
"dev": "astro dev",
"preview": "astro preview",
"astro": "astro",
"build": "astro build"
"subset-font": "node build/fontmin.js",
"build": "npm run subset-font && astro build"
},
"dependencies": {
"@astrojs/rss": "^4.0.17",
@ -17,9 +18,11 @@
"@astrojs/svelte": "^8.0.4",
"astro": "^6.0.8",
"playwright": "^1.59.1",
"rehype-mermaid": "^3.0.0"
"rehype-mermaid": "^3.0.0",
"url": "^0.11.4"
},
"devDependencies": {
"@types/node": "^25.5.0"
"@types/node": "^25.5.0",
"fontmin": "^1.1.1"
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -12,14 +12,14 @@ const tags = data.tags;
<div class="post-link">
<div class="post-text">
<a href={data.url} class="post-title-link">
<h2 class="post-title" transition:name={`post-title-${data.postId}`}>
<h2 class="post-title" transition:name={`post-title-${data.slug}`}>
{data.title}
</h2>
</a>
<a href={data.url} class="post-description">
{data.description}
</a>
<div class="tags" transition:name={`post-tags-${data.postId}`}>
<div class="tags">
{
tags.map((tag: string) => (
<p class="tag">
@ -40,13 +40,7 @@ const tags = data.tags;
</div>
<a href={data.url} class="post-image-link">
<img
src={data.img}
alt={data.title}
class="post-image"
loading="lazy"
transition:name={`post-image-${data.postId}`}
/>
<img src={data.img} alt={data.title} class="post-image" loading="lazy" />
</a>
</div>
</li>
@ -173,7 +167,7 @@ const tags = data.tags;
text-decoration: none;
font-weight: 400;
font-size: 0.92rem;
/* font-style: italic; */
font-style: italic;
}
.post-meta-row {
@ -212,7 +206,7 @@ const tags = data.tags;
aspect-ratio: 16 / 10;
object-fit: cover;
object-position: center;
border: 1.5px #94a0ab dashed;
border: 2px #94a0ab dashed;
flex-shrink: 0;
display: block;
}
@ -262,7 +256,7 @@ const tags = data.tags;
}
.post-image {
/* border: none; */
border: none;
grid-area: image;
width: 100%;
height: calc(145px * 10 / 16);

View file

@ -39,7 +39,6 @@ const latestPosts = filteredPosts.slice(0, 7);
img={post.data.image.url}
tags={post.data.tags}
slug={slug}
postId={post.id}
/>
);
})

View file

@ -54,7 +54,7 @@ const groupedPosts = filteredPosts.reduce((acc: any[], post: any) => {
>
<h2
class="post-title"
transition:name={`post-title-${post.id}`}
transition:name={`post-title-${slug}`}
>
{post.data.title}
</h2>
@ -147,8 +147,7 @@ const groupedPosts = filteredPosts.reduce((acc: any[], post: any) => {
margin: 0;
font-size: 0.88rem;
color: #555;
font-weight: 400;
/* font-style: italic; */
font-style: italic;
line-height: 1.5;
overflow-wrap: anywhere;
word-break: break-word;

View file

@ -70,15 +70,15 @@ const switchHref = "/" + segments.join("/");
}
.lang-switch {
color: black;
font-weight: 700;
font-size: 0.95rem;
text-decoration: none;
line-height: 1;
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;
text-decoration: underline; !important
}
.sun {
@ -104,37 +104,37 @@ const switchHref = "/" + segments.join("/");
<script is:inline>
function applyTheme() {
const localStorageTheme = localStorage?.getItem("theme") ?? "";
let theme = "light";
const localStorageTheme = localStorage?.getItem("theme") ?? "";
let theme = "light";
if (["dark", "light"].includes(localStorageTheme)) {
theme = localStorageTheme;
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
theme = "dark";
}
document.documentElement.classList.toggle("dark", theme === "dark");
window.localStorage.setItem("theme", theme);
if (["dark", "light"].includes(localStorageTheme)) {
theme = localStorageTheme;
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
theme = "dark";
}
function bindThemeToggle() {
const button = document.getElementById("themeToggle");
if (!button) return;
document.documentElement.classList.toggle("dark", theme === "dark");
window.localStorage.setItem("theme", theme);
}
button.onclick = () => {
const element = document.documentElement;
element.classList.toggle("dark");
function bindThemeToggle() {
const button = document.getElementById("themeToggle");
if (!button) return;
const isDark = element.classList.contains("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
};
}
button.onclick = () => {
const element = document.documentElement;
element.classList.toggle("dark");
const isDark = element.classList.contains("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
};
}
applyTheme();
bindThemeToggle();
document.addEventListener("astro:after-swap", () => {
applyTheme();
bindThemeToggle();
document.addEventListener("astro:after-swap", () => {
applyTheme();
bindThemeToggle();
});
});
</script>

View file

@ -1,11 +1,10 @@
---
import BaseLayout from "./BaseLayout.astro";
import Remark42Embed from "@/components/Remark42Embed.astro";
import { fade } from "astro:transitions";
import { getLangFromUrl, getTranslations } from "@/i18n";
import "@/styles/global.css";
const { frontmatter, lang, slug, postId } = Astro.props;
const { frontmatter, lang, postId } = Astro.props;
const comments = lang === "zh" ? "评论区" : "comments";
const t = getTranslations(lang);
---
@ -15,7 +14,7 @@ const t = getTranslations(lang);
description={frontmatter.description}
image={frontmatter.image?.url}
>
<div id="reading-progress" aria-hidden="true"></div>
<div id="reading-progress" aria-hidden="true"></div>
<a
href="/"
class="back-button"
@ -40,18 +39,14 @@ const t = getTranslations(lang);
<article class="post-article">
<div class="post-header">
<div class="post-meta">
<h1 class="post-title" transition:name={`post-title-${postId}`}>
{frontmatter.title}
</h1>
<p class="description">
<em>{frontmatter.description}</em>
</p>
<h1 class="post-title" transition:name={`post-title-${postId}`} >{frontmatter.title}</h1>
<p class="description"><em>{frontmatter.description}</em></p>
<p class="meta-line">
{t.post.publishedOn}: {frontmatter.pubDate.toLocaleDateString()}
</p>
<p class="meta-line">{t.post.writtenBy}: {frontmatter.author}</p>
<div class="tags" transition:name={`post-tags-${postId}`}>
<div class="tags">
{
frontmatter.tags.map((tag: string) => (
<p class="tag">
@ -66,18 +61,17 @@ const t = getTranslations(lang);
src={frontmatter.image.url}
alt={frontmatter.image.alt}
class="post-cover"
transition:name={`post-image-${postId}`}
/>
</div>
<div class="post-divider"></div>
<div class="post-content" transition:animate={fade({ duration: "0.2s" })}>
<div class="post-content">
<slot />
</div>
<h2>{comments}</h2>
<Remark42Embed slug={slug} />
<Remark42Embed slug={postId} />
</article>
</BaseLayout>
@ -137,7 +131,7 @@ const t = getTranslations(lang);
backButton.dataset.bound = "true";
}
};
const updateProgress = () => {
const updateProgress = () => {
const doc = document.documentElement;
const scrollTop = window.scrollY || doc.scrollTop;
const scrollHeight = doc.scrollHeight - window.innerHeight;
@ -158,7 +152,7 @@ const t = getTranslations(lang);
</script>
<style>
#reading-progress {
#reading-progress {
position: fixed;
top: 0;
left: 0;
@ -192,8 +186,8 @@ const t = getTranslations(lang);
/* opacity: 0; */
transition:
opacity 0.2s ease,
transform 0.27s ease,
visibility 0.2s ease,
transform 0.27s ease,
visibility 0.2s ease;
box-shadow 0.2s ease,
background 0.2s ease,
color 0.2s ease;
@ -290,11 +284,10 @@ const t = getTranslations(lang);
.post-cover {
width: 300px;
height: auto;
aspect-ratio: 16 / 10;
aspect-ratio: 16 / 9;
object-fit: cover;
object-position: center;
/* border-radius: 0.4rem; */
border: 1.5px #94a0ab dashed;
border-radius: 0.4rem;
display: block;
}
@ -353,7 +346,6 @@ const t = getTranslations(lang);
.post-cover {
width: 100%;
max-width: 100%;
aspect-ratio: 16 / 9;
}
}
</style>

View file

@ -33,7 +33,7 @@ const pageTitle = t.home.title;
width="33"
height="33"
aria-hidden="true"
style="margin-left: 0px; margin-bottom: -5px; color:#3f50e5"
style="margin-left: -15px; margin-bottom: -5px; color:#3f50e5"
>
<path
d="M6.18 17.82a1.64 1.64 0 1 1 0 3.28 1.64 1.64 0 0 1 0-3.28ZM3 10.44v2.25c4.56 0 8.27 3.71 8.27 8.27h2.25C13.52 15.16 8.84 10.44 3 10.44Zm0-4.54v2.25c7.06 0 12.81 5.75 12.81 12.81h2.25C18.06 12.66 11.3 5.9 3 5.9Z"
@ -66,8 +66,4 @@ const pageTitle = t.home.title;
:global(.dark) .section-divider {
background: #7f8b97;
}
.page-title {
font-weight: 700;
}
</style>

View file

@ -28,11 +28,6 @@ const [postLang, ...slugParts] = post.id.split("/");
const slug = slugParts.join("/");
---
<MarkdownPostLayout
frontmatter={post.data}
lang={lang}
slug={slug}
postId={post.id}
>
<MarkdownPostLayout frontmatter={post.data} lang={lang} postId={slug}>
<Content />
</MarkdownPostLayout>

View file

@ -34,7 +34,7 @@ const t = getTranslations(lang);
<BaseLayout pageTitle={String(tag)}>
<p class="tag-heading">
{lang === "zh" ? "带有标签" : "Posts tagged with"}{" "}
<span class="tag-chip" transition:name={`post-tags-${tag}`}>{tag}</span>
<span class="tag-chip">{tag}</span>
{lang === "zh" ? "的文章" : ""}
</p>
<ul>
@ -54,7 +54,6 @@ const t = getTranslations(lang);
date={formattedDate}
img={post.data.image.url}
tags={post.data.tags}
postId={post.id}
/>
);
})

View file

@ -15,14 +15,14 @@ const tags = [...new Set(allPosts.map((post: any) => post.data.tags).flat())];
const pageTitle = lang === "zh" ? "标签索引" : "Tag Index";
---
<BaseLayout pageTitle=`${pageTitle} - ${t.banner.title}`>
<BaseLayout pageTitle=`${pageTitle} - ${t.banner.title}` `>
<h1>{t.tags.title}</h1>
<p>{t.tags.description}</p>
<div class="tags">
{
tags.map((tag) => (
<p class="tag" transition:name={`post-tags-${tag}`}>
<p class="tag">
<a href={`/${lang}/tags/${tag}`}>{tag}</a>
</p>
))

View file

@ -2,6 +2,30 @@
@import url("https://unpkg.com/@fontsource/maple-mono@5.2.6/400-italic.css");
@import url("https://unpkg.com/@fontsource/maple-mono@5.2.6/700.css");
@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;
}
pre {
padding: 1rem;
border-radius: 0.5rem;
@ -33,19 +57,7 @@ article svg[id^="mermaid-"] {
}
html {
/* font-family: "Maple Mono", "Maple Mono CN", monospace; */
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Oxygen,
Ubuntu,
Cantarell,
"Open Sans",
"Helvetica Neue",
sans-serif;
font-family: "Maple Mono", "Maple Mono CN", monospace;
background-color: #ffffff;
color: #1f2328;
}
@ -89,7 +101,7 @@ html.dark body::after {
@media (max-width: 900px) {
body::after {
opacity: 0;
opacity: 0.1;
}
}
@ -101,6 +113,10 @@ html.dark body::after {
body {
font-size: 1rem;
}
body::after {
opacity: 0;
}
}