Merge branch 'main' into wind-v4

This commit is contained in:
L4Ph
2025-08-21 17:54:31 +09:00
15 changed files with 519 additions and 493 deletions

4
.gitignore vendored
View File

@@ -25,3 +25,7 @@ pnpm-debug.log*
package-lock.json
bun.lockb
yarn.lock
# ide
.idea
*.iml

View File

@@ -1,63 +1,63 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"includes": [
"**",
"!**/src/**/*.css",
"!**/src/public/**/*",
"!**/dist/**/*",
"!**/node_modules/**/*"
]
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"overrides": [
{
"includes": ["**/*.svelte", "**/*.astro", "**/*.vue"],
"linter": {
"rules": {
"style": {
"useConst": "off",
"useImportType": "off"
},
"correctness": {
"noUnusedVariables": "off",
"noUnusedImports": "off"
}
}
}
}
]
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"includes": [
"**",
"!**/src/**/*.css",
"!**/src/public/**/*",
"!**/dist/**/*",
"!**/node_modules/**/*"
]
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"overrides": [
{
"includes": ["**/*.svelte", "**/*.astro", "**/*.vue"],
"linter": {
"rules": {
"style": {
"useConst": "off",
"useImportType": "off"
},
"correctness": {
"noUnusedVariables": "off",
"noUnusedImports": "off"
}
}
}
}
]
}

View File

@@ -33,8 +33,8 @@
2. เริ่มแก้ไขบล็อกของคุณแบบ local โดยการ clone repository ของคุณ (จากข้อ 1) ไว้ในเครื่องของคุณ แล้วรันคำสั่ง `pnpm install` เพื่อติดตั้ง dependencies ที่จำเป็น
- ติดตั้ง [pnpm](https://pnpm.io) ด้วยคำสั่ง `npm install -g pnpm` ก่อน ถ้ายังไม่เคยติดตั้ง
3. แก้ไขไฟล์การตั้งค่า `src/config.ts` เพื่อปรับแต่งบล็อกของคุณ
4. รันคำสั่ง `pnpm new-post <filename>` เพื่อสร้างโพสต์ใหม่ใน `src/content/posts/` และแก้ไขไฟล์โพสต์นั้นๆ ให้สมบูรณ์
5. Deploy เว็บบล็อกของคุณไปยัง Vercel, Netlify, GitHub Pages หรือบริการอื่นๆ โดยอ้างอิงวิธีการจาก[คู่มือนี้](https://docs.astro.build/en/guides/deploy/) อย่าลืมแก้ไขการตั้งค่าเว็บไซต์ในไฟล์ `astro.config.mjs` ก่อนที่คุณจะ deploy เว็บ
4. รันคำสั่ง `pnpm new-post <filename>` เพื่อสร้างโพสต์ใหม่ใน `src/content/posts/` และแก้ไขไฟล์โพสต์นั้น ๆ ให้สมบูรณ์
5. Deploy เว็บบล็อกของคุณไปยัง Vercel, Netlify, GitHub Pages หรือบริการอื่น ๆ โดยอ้างอิงวิธีการจาก[คู่มือนี้](https://docs.astro.build/en/guides/deploy/) อย่าลืมแก้ไขการตั้งค่าเว็บไซต์ในไฟล์ `astro.config.mjs` ก่อนที่คุณจะ deploy เว็บ
## 📝 Frontmatter (ส่วนหัวไฟล์) ของโพสต์
@@ -47,13 +47,13 @@ image: ./cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
lang: jp # เขียนค่านี้เมื่อภาษาของโพสต์นั้นๆ แตกต่างจากภาษาของเว็บไซต์ที่ตั้งค่าไว้ใน `config.ts` เท่านั้น
lang: jp # เขียนค่านี้เมื่อภาษาของโพสต์นั้น ๆ แตกต่างจากภาษาของเว็บไซต์ที่ตั้งค่าไว้ใน `config.ts` เท่านั้น
---
```
## 🧩 Markdown Extended Syntax
เดิมที Astro มีการสนับสนุน[ภาษามาร์กดาวน์แบบของ GitHub](https://github.github.com/gfm/) ไว้อยู่แล้ว แต่ Fuwari ได้เพิ่มเติมคุณสมบัติพิเศษอื่นๆ เข้าไปอีก:
เดิมที Astro มีการสนับสนุน[ภาษามาร์กดาวน์แบบของ GitHub](https://github.github.com/gfm/) ไว้อยู่แล้ว แต่ Fuwari ได้เพิ่มเติมคุณสมบัติพิเศษอื่น ๆ เข้าไปอีก:
- Admonitions หรือ กล่องข้อมูลพิเศษ ([ดูตัวอย่างและการใช้งาน](https://fuwari.vercel.app/posts/markdown-extended/#admonitions))
- การ์ด GitHub Repository ([ดูตัวอย่างและการใช้งาน](https://fuwari.vercel.app/posts/markdown-extended/#github-repository-cards))

View File

@@ -18,7 +18,7 @@
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/rss": "^4.0.12",
"@astrojs/sitemap": "^3.4.2",
"@astrojs/sitemap": "^3.5.0",
"@astrojs/svelte": "7.1.0",
"@expressive-code/core": "^0.41.3",
"@expressive-code/plugin-collapsible-sections": "^0.41.3",
@@ -33,7 +33,7 @@
"@swup/astro": "^1.7.0",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.1.11",
"astro": "5.12.5",
"astro": "5.13.2",
"astro-expressive-code": "^0.41.3",
"astro-icon": "^1.1.5",
"hastscript": "^9.0.1",
@@ -56,14 +56,14 @@
"sanitize-html": "^2.17.0",
"sharp": "^0.34.3",
"stylus": "^0.64.0",
"svelte": "^5.37.2",
"svelte": "^5.38.2",
"tailwindcss": "^4.1.11",
"typescript": "^5.9.2",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@astrojs/ts-plugin": "^1.10.4",
"@biomejs/biome": "2.1.3",
"@biomejs/biome": "2.2.0",
"@rollup/plugin-yaml": "^4.1.2",
"@types/hast": "^3.0.4",
"@types/markdown-it": "^14.1.2",

817
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -75,8 +75,8 @@ onMount(async () => {
);
const groupedPostsArray = Object.keys(grouped).map((yearStr) => ({
year: Number.parseInt(yearStr),
posts: grouped[Number.parseInt(yearStr)],
year: Number.parseInt(yearStr, 10),
posts: grouped[Number.parseInt(yearStr, 10)],
}));
groupedPostsArray.sort((a, b) => b.year - a.year);

View File

@@ -23,7 +23,7 @@ $: if (hue || hue === 0) {
before:absolute before:-left-3 before:top-[0.33rem]"
>
{i18n(I18nKey.themeColor)}
<button aria-label="Reset to Default" class="btn-regular w-7 h-7 rounded-md active:scale-90"
<button aria-label="Reset to Default" class="btn-regular w-7 h-7 rounded-md active:scale-90 will-change-transform"
class:opacity-0={hue === defaultHue} class:pointer-events-none={hue === defaultHue} on:click={resetHue}>
<div class="text-[var(--btn-content)]">
<Icon icon="fa6-solid:arrow-rotate-left" class="text-[0.875rem]"></Icon>

View File

@@ -55,7 +55,7 @@ const maxLevel = siteConfig.toc.depth;
}]}>{removeTailingHash(heading.text)}</div>
</a>
)}
<div id="active-indicator" class:list={[{'hidden': headings.length == 0}, "-z-10 absolute bg-[var(--toc-btn-hover)] left-0 right-0 rounded-xl transition-all " +
<div id="active-indicator" style="opacity: 0" class:list={[{'hidden': headings.length == 0}, "-z-10 absolute bg-[var(--toc-btn-hover)] left-0 right-0 rounded-xl transition-all " +
"group-hover:bg-transparent border-2 border-[var(--toc-btn-hover)] group-hover:border-[var(--toc-btn-active)] border-dashed"]}></div>
</table-of-contents>}
@@ -97,7 +97,7 @@ class TableOfContents extends HTMLElement {
toggleActiveHeading = () => {
let i = this.active.length - 1;
let min = this.active.length - 1, max = 0;
let min = this.active.length - 1, max = -1;
while (i >= 0 && !this.active[i]) {
this.tocEntries[i].classList.remove(this.visibleClass);
i--;
@@ -112,11 +112,15 @@ class TableOfContents extends HTMLElement {
this.tocEntries[i].classList.remove(this.visibleClass);
i--;
}
let parentOffset = this.tocEl?.getBoundingClientRect().top || 0;
let scrollOffset = this.tocEl?.scrollTop || 0;
let top = this.tocEntries[min].getBoundingClientRect().top - parentOffset + scrollOffset;
let bottom = this.tocEntries[max].getBoundingClientRect().bottom - parentOffset + scrollOffset;
this.activeIndicator?.setAttribute("style", `top: ${top}px; height: ${bottom - top}px`);
if (min > max) {
this.activeIndicator?.setAttribute("style", `opacity: 0`);
} else {
let parentOffset = this.tocEl?.getBoundingClientRect().top || 0;
let scrollOffset = this.tocEl?.scrollTop || 0;
let top = this.tocEntries[min].getBoundingClientRect().top - parentOffset + scrollOffset;
let bottom = this.tocEntries[max].getBoundingClientRect().bottom - parentOffset + scrollOffset;
this.activeIndicator?.setAttribute("style", `top: ${top}px; height: ${bottom - top}px`);
}
};
scrollToActiveHeading = () => {

View File

@@ -10,7 +10,7 @@ import { LinkPreset } from "./types/config";
export const siteConfig: SiteConfig = {
title: "Fuwari",
subtitle: "Demo Site",
lang: "en", // Language code, e.g. 'en', 'zh-CN', 'ja', etc.
lang: "en", // Language code, e.g. 'en', 'zh_CN', 'ja', etc.
themeColor: {
hue: 250, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
fixed: false, // Hide the theme color picker for visitors

View File

@@ -1,5 +1,6 @@
import rss from "@astrojs/rss";
import { getSortedPosts } from "@utils/content-utils";
import { url } from "@utils/url-utils";
import type { APIContext } from "astro";
import MarkdownIt from "markdown-it";
import sanitizeHtml from "sanitize-html";
@@ -30,7 +31,7 @@ export async function GET(context: APIContext) {
title: post.data.title,
pubDate: post.data.published,
description: post.data.description || "",
link: `/posts/${post.slug}/`,
link: url(`/posts/${post.slug}/`),
content: sanitizeHtml(parser.render(cleanedContent), {
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
}),

View File

@@ -6,7 +6,7 @@ import { definePlugin } from "@expressive-code/core";
export function pluginLanguageBadge() {
return definePlugin({
name: "Language Badge",
// @ts-ignore
// @ts-expect-error
baseStyles: ({ _cssVar }) => `
[data-language]::before {
position: absolute;
@@ -15,6 +15,7 @@ export function pluginLanguageBadge() {
top: 0.5rem;
padding: 0.1rem 0.5rem;
content: attr(data-language);
font-family: "JetBrains Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.75rem;
font-weight: bold;
text-transform: uppercase;

View File

@@ -1,5 +1,11 @@
@reference "tailwindcss";
.expressive-code .frame {
@apply !shadow-none;
.expressive-code {
.frame {
@apply !shadow-none;
}
.title {
font-family: "JetBrains Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
}

View File

@@ -26,6 +26,7 @@
underline decoration-[var(--link-underline)] decoration-1 decoration-dashed underline-offset-4;
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
display: inline-block;
&:hover, &:active {
@apply decoration-transparent;

View File

@@ -57,8 +57,8 @@ export async function getTagList(): Promise<Tag[]> {
});
const countMap: { [key: string]: number } = {};
allBlogPosts.map((post: { data: { tags: string[] } }) => {
post.data.tags.map((tag: string) => {
allBlogPosts.forEach((post: { data: { tags: string[] } }) => {
post.data.tags.forEach((tag: string) => {
if (!countMap[tag]) countMap[tag] = 0;
countMap[tag]++;
});
@@ -83,7 +83,7 @@ export async function getCategoryList(): Promise<Category[]> {
return import.meta.env.PROD ? data.draft !== true : true;
});
const count: { [key: string]: number } = {};
allBlogPosts.map((post: { data: { category: string | null } }) => {
allBlogPosts.forEach((post: { data: { category: string | null } }) => {
if (!post.data.category) {
const ucKey = i18n(I18nKey.uncategorized);
count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1;

View File

@@ -10,12 +10,12 @@ import type { LIGHT_DARK_MODE } from "@/types/config";
export function getDefaultHue(): number {
const fallback = "250";
const configCarrier = document.getElementById("config-carrier");
return Number.parseInt(configCarrier?.dataset.hue || fallback);
return Number.parseInt(configCarrier?.dataset.hue || fallback, 10);
}
export function getHue(): number {
const stored = localStorage.getItem("hue");
return stored ? Number.parseInt(stored) : getDefaultHue();
return stored ? Number.parseInt(stored, 10) : getDefaultHue();
}
export function setHue(hue: number): void {