mirror of
https://github.com/saicaca/fuwari.git
synced 2026-01-11 06:42:53 +01:00
Merge branch 'main' into tailwind-v4-poc-1
This commit is contained in:
36
README.es.md
36
README.es.md
@@ -5,7 +5,7 @@ Un tema estático para blogs construido con [Astro](https://astro.build).
|
|||||||
[**🖥️ Demostración en Vivo (Vercel)**](https://fuwari.vercel.app) /
|
[**🖥️ Demostración en Vivo (Vercel)**](https://fuwari.vercel.app) /
|
||||||
[**📦 Versión Antigua de Hexo**](https://github.com/saicaca/hexo-theme-vivia) /
|
[**📦 Versión Antigua de Hexo**](https://github.com/saicaca/hexo-theme-vivia) /
|
||||||
|
|
||||||
> Versión del README: `2024-04-07`
|
> Versión del README: `2025-04-24`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -18,9 +18,39 @@ Un tema estático para blogs construido con [Astro](https://astro.build).
|
|||||||
- [x] Diseño responsivo
|
- [x] Diseño responsivo
|
||||||
- [ ] Comentarios
|
- [ ] Comentarios
|
||||||
- [x] Buscador
|
- [x] Buscador
|
||||||
- [ ] TOC (Tabla de Contenidos)
|
- [x] TOC (Tabla de Contenidos)
|
||||||
|
|
||||||
## 🚀 Cómo Usar
|
## 👀 requiere
|
||||||
|
|
||||||
|
- Node.js <= 22
|
||||||
|
- pnpm <= 9
|
||||||
|
|
||||||
|
## 🚀 Cómo Usar 1
|
||||||
|
|
||||||
|
Inicializa el proyecto localmente usando [create-fuwari](https://github.com/L4Ph/create-fuwari).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# npm
|
||||||
|
npm create fuwari@latest.
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn create fuwari.
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm create fuwari@latest
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun create fuwari@latest
|
||||||
|
|
||||||
|
# deno
|
||||||
|
deno run -A npm:create-fuwari@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Edita el archivo de configuración `src/config.ts` para personalizar tu blog.
|
||||||
|
2. Ejecuta `pnpm new-post <nombre-de-archivo>` para crear una nueva entrada y edítala en `src/content/posts/`.
|
||||||
|
3. Despliega tu blog en Vercel, Netlify, GitHub Pages, etc., siguiendo [las guías](https://docs.astro.build/en/guides/deploy/). Necesitas editar la configuración del sitio en `astro.config.mjs` antes del despliegue.
|
||||||
|
|
||||||
|
## 🚀 Cómo Usar 2
|
||||||
|
|
||||||
1. [Genera un nuevo repositorio](https://github.com/saicaca/fuwari/generate) desde esta plantilla o haz un fork de este repositorio.
|
1. [Genera un nuevo repositorio](https://github.com/saicaca/fuwari/generate) desde esta plantilla o haz un fork de este repositorio.
|
||||||
2. Para editar tu blog localmente, clona tu repositorio, ejecuta `pnpm install` y `pnpm add sharp` para instalar las dependencias.
|
2. Para editar tu blog localmente, clona tu repositorio, ejecuta `pnpm install` y `pnpm add sharp` para instalar las dependencias.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
[**🖥️ライブデモ (Vercel)**](https://fuwari.vercel.app) /
|
[**🖥️ライブデモ (Vercel)**](https://fuwari.vercel.app) /
|
||||||
[**📦旧 Hexo バージョン**](https://github.com/saicaca/hexo-theme-vivia)
|
[**📦旧 Hexo バージョン**](https://github.com/saicaca/hexo-theme-vivia)
|
||||||
|
|
||||||
> README バージョン:`2024-04-07`
|
> README バージョン:`2025-04-24`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -18,9 +18,39 @@
|
|||||||
- [x] レスポンシブデザイン
|
- [x] レスポンシブデザイン
|
||||||
- [ ] コメント機能
|
- [ ] コメント機能
|
||||||
- [x] 検索機能
|
- [x] 検索機能
|
||||||
- [ ] 目次
|
- [x] 目次
|
||||||
|
|
||||||
## 🚀 使用方法
|
## 👀 以下が必要
|
||||||
|
|
||||||
|
- Node.js <= 22
|
||||||
|
- pnpm <= 9
|
||||||
|
|
||||||
|
## 🚀 使用方法 1
|
||||||
|
|
||||||
|
[create-fuwari](https://github.com/L4Ph/create-fuwari)を使用して、ローカルにプロジェクトを初期化します。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# npm
|
||||||
|
npm create fuwari@latest
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn create fuwari
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm create fuwari@latest
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun create fuwari@latest
|
||||||
|
|
||||||
|
# deno
|
||||||
|
deno run -A npm:create-fuwari@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
1. `src/config.ts` ファイルを編集する事でブログを自分好みにカスタマイズ出来ます。
|
||||||
|
2. `pnpm new-post <filename>` で新しい記事を作成し、`src/content/posts/`.フォルダ内で編集します。
|
||||||
|
3. 作成したブログをVercel、Netlify、GitHub Pagesなどにデプロイするには[ガイド](https://docs.astro.build/ja/guides/deploy/)に従って下さい。加えて、別途デプロイを行う前に `astro.config.mjs` を編集してサイト構成を変更する必要があります。
|
||||||
|
|
||||||
|
## 🚀 使用方法 2
|
||||||
|
|
||||||
1. [テンプレート](https://github.com/saicaca/fuwari/generate)から新しいリポジトリを作成するかCloneをします。
|
1. [テンプレート](https://github.com/saicaca/fuwari/generate)から新しいリポジトリを作成するかCloneをします。
|
||||||
2. ブログをローカルで編集するには、リポジトリをクローンした後、`pnpm install` と `pnpm add sharp` を実行して依存関係をインストールします。
|
2. ブログをローカルで編集するには、リポジトリをクローンした後、`pnpm install` と `pnpm add sharp` を実行して依存関係をインストールします。
|
||||||
|
|||||||
43
README.ko.md
43
README.ko.md
@@ -3,9 +3,14 @@
|
|||||||
[Astro](https://astro.build)로 구축된 정적 블로그 템플릿입니다.
|
[Astro](https://astro.build)로 구축된 정적 블로그 템플릿입니다.
|
||||||
|
|
||||||
[**🖥️미리보기 (Vercel)**](https://fuwari.vercel.app) /
|
[**🖥️미리보기 (Vercel)**](https://fuwari.vercel.app) /
|
||||||
[**📦Old Hexo Version**](https://github.com/saicaca/hexo-theme-vivia)
|
[**📦Old Hexo Version**](https://github.com/saicaca/hexo-theme-vivia) /
|
||||||
|
[**🌏 English**](https://github.com/saicaca/fuwari/blob/main/README.md) /
|
||||||
|
[**🌏 中文**](https://github.com/saicaca/fuwari/blob/main/README.zh-CN.md) /
|
||||||
|
[**🌏 日本語**](https://github.com/saicaca/fuwari/blob/main/README.ja-JP.md) /
|
||||||
|
[**🌏 Español**](https://github.com/saicaca/fuwari/blob/main/README.es.md) /
|
||||||
|
[**🌏 ไทย**](https://github.com/saicaca/fuwari/blob/main/README.th.md)
|
||||||
|
|
||||||
> README 버전: `2024-04-07`
|
> README 버전: `2025-04-24`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -18,7 +23,38 @@
|
|||||||
- [x] 반응형 디자인
|
- [x] 반응형 디자인
|
||||||
- [ ] 댓글
|
- [ ] 댓글
|
||||||
- [x] 검색
|
- [x] 검색
|
||||||
- [ ] 목차
|
- [x] 목차
|
||||||
|
|
||||||
|
## 요구 사항
|
||||||
|
|
||||||
|
- Node.js <= 22
|
||||||
|
- pnpm <= 9
|
||||||
|
|
||||||
|
## 🚀 사용하는 방법 1
|
||||||
|
|
||||||
|
[create-fuwari](https://github.com/L4Ph/create-fuwari)를 사용하여 로컬에서 프로젝트를 초기화합니다.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# npm
|
||||||
|
npm create fuwari@latest
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn create fuwari
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm create fuwari@latest
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun create fuwari@latest
|
||||||
|
|
||||||
|
# deno
|
||||||
|
deno run -A npm:create-fuwari@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
1. 블로그를 사용자 정의하려면 `src/config.ts` 구성 파일을 편집하세요.
|
||||||
|
2. `pnpm new-post <filename>`을 실행하여 새 게시물을 만들고 `src/content/posts/`에서 편집하세요.
|
||||||
|
3. [가이드](https://docs.astro.build/en/guides/deploy/)에 따라 블로그를 Vercel, Netlify, GitHub 페이지 등에 배포하세요. 배포하기 전에 `astro.config.mjs`에서 사이트 구성을 편집해야 합니다.
|
||||||
|
|
||||||
## 🚀 사용하는 방법
|
## 🚀 사용하는 방법
|
||||||
|
|
||||||
1. 이 템플릿에서 [새 저장소를 생성](https://github.com/saicaca/fuwari/generate)하거나 이 저장소를 포크하세요.
|
1. 이 템플릿에서 [새 저장소를 생성](https://github.com/saicaca/fuwari/generate)하거나 이 저장소를 포크하세요.
|
||||||
@@ -39,6 +75,7 @@ image: /images/cover.jpg
|
|||||||
tags: [푸, 바, 오]
|
tags: [푸, 바, 오]
|
||||||
category: 앞-끝
|
category: 앞-끝
|
||||||
draft: false
|
draft: false
|
||||||
|
lang: jp # 게시물의 언어가 `config.ts`의 사이트 언어와 다른 경우에만 설정합니다.
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
34
README.md
34
README.md
@@ -10,7 +10,7 @@ A static blog template built with [Astro](https://astro.build).
|
|||||||
[**🌏 Español**](https://github.com/saicaca/fuwari/blob/main/README.es.md) /
|
[**🌏 Español**](https://github.com/saicaca/fuwari/blob/main/README.es.md) /
|
||||||
[**🌏 ไทย**](https://github.com/saicaca/fuwari/blob/main/README.th.md)
|
[**🌏 ไทย**](https://github.com/saicaca/fuwari/blob/main/README.th.md)
|
||||||
|
|
||||||
> README version: `2024-09-10`
|
> README version: `2025-04-24`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -23,7 +23,37 @@ A static blog template built with [Astro](https://astro.build).
|
|||||||
- [x] Responsive design
|
- [x] Responsive design
|
||||||
- [ ] Comments
|
- [ ] Comments
|
||||||
- [x] Search
|
- [x] Search
|
||||||
- [ ] TOC
|
- [x] TOC
|
||||||
|
|
||||||
|
## require
|
||||||
|
|
||||||
|
- Node.js <= 22
|
||||||
|
- pnpm <= 9
|
||||||
|
|
||||||
|
## 🚀 How to Use 1
|
||||||
|
|
||||||
|
Initialize the project locally using [create-fuwari](https://github.com/L4Ph/create-fuwari).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# npm
|
||||||
|
npm create fuwari@latest
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn create fuwari
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm create fuwari@latest
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun create fuwari@latest
|
||||||
|
|
||||||
|
# deno
|
||||||
|
deno run -A npm:create-fuwari@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Edit the config file `src/config.ts` to customize your blog.
|
||||||
|
2. Run `pnpm new-post <filename>` to create a new post and edit it in `src/content/posts/`.
|
||||||
|
3. Deploy your blog to Vercel, Netlify, GitHub Pages, etc. following [the guides](https://docs.astro.build/en/guides/deploy/). You need to edit the site configuration in `astro.config.mjs` before deployment.
|
||||||
|
|
||||||
## 🚀 How to Use
|
## 🚀 How to Use
|
||||||
|
|
||||||
|
|||||||
37
README.th.md
37
README.th.md
@@ -5,7 +5,7 @@
|
|||||||
[**🖥️ ตัวอย่างการใช้งานจริง (Vercel)**](https://fuwari.vercel.app) /
|
[**🖥️ ตัวอย่างการใช้งานจริง (Vercel)**](https://fuwari.vercel.app) /
|
||||||
[**📦 เวอร์ชั่นเก่าสำหรับ Hexo**](https://github.com/saicaca/hexo-theme-vivia)
|
[**📦 เวอร์ชั่นเก่าสำหรับ Hexo**](https://github.com/saicaca/hexo-theme-vivia)
|
||||||
|
|
||||||
> เวอร์ชั่นของ README: `2024-09-10`
|
> เวอร์ชั่นของ README: `2025-04-24`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -18,9 +18,40 @@
|
|||||||
- [x] Responsive design (หน้าตาเว็บปรับเปลี่ยนตามขนาดจอ)
|
- [x] Responsive design (หน้าตาเว็บปรับเปลี่ยนตามขนาดจอ)
|
||||||
- [ ] การแสดงความคิดเห็น
|
- [ ] การแสดงความคิดเห็น
|
||||||
- [x] การค้นหา
|
- [x] การค้นหา
|
||||||
- [ ] TOC (สารบัญ)
|
- [x] TOC (สารบัญ)
|
||||||
|
|
||||||
## 🚀 วิธีใช้งาน
|
|
||||||
|
## จำเป็นต้อง
|
||||||
|
|
||||||
|
- Node.js <= 22
|
||||||
|
- pnpm <= 9
|
||||||
|
|
||||||
|
## 🚀 วิธีใช้งาน 1
|
||||||
|
|
||||||
|
เริ่มต้นโปรเจ็กต์ในเครื่องโดยใช้ [create-fuwari](https://github.com/L4Ph/create-fuwari)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# npm
|
||||||
|
npm create fuwari@latest
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn create fuwari
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm create fuwari@latest
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun create fuwari@latest
|
||||||
|
|
||||||
|
# deno
|
||||||
|
deno run -A npm:create-fuwari@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
1. แก้ไขไฟล์การตั้งค่า `src/config.ts` เพื่อปรับแต่งบล็อกของคุณ
|
||||||
|
2. รันคำสั่ง `pnpm new-post <filename>` เพื่อสร้างโพสต์ใหม่ใน `src/content/posts/` และแก้ไขไฟล์โพสต์นั้นๆ ให้สมบูรณ์
|
||||||
|
3. Deploy เว็บบล็อกของคุณไปยัง Vercel, Netlify, GitHub Pages หรือบริการอื่นๆ โดยอ้างอิงวิธีการจาก[คู่มือนี้](https://docs.astro.build/en/guides/deploy/) อย่าลืมแก้ไขการตั้งค่าเว็บไซต์ในไฟล์ `astro.config.mjs` ก่อนที่คุณจะ deploy เว็บ
|
||||||
|
|
||||||
|
## 🚀 วิธีใช้งาน 2
|
||||||
|
|
||||||
1. [Generate repository ใหม่](https://github.com/saicaca/fuwari/generate)ขึ้นมาจากแม่แบบนี้ หรือจะ fork repository นี้ก็ได้
|
1. [Generate repository ใหม่](https://github.com/saicaca/fuwari/generate)ขึ้นมาจากแม่แบบนี้ หรือจะ fork repository นี้ก็ได้
|
||||||
2. เริ่มแก้ไขบล็อกของคุณแบบ local โดยการ clone repository ของคุณ (จากข้อ 1) ไว้ในเครื่องของคุณ แล้วรันคำสั่ง `pnpm install` และ `pnpm add sharp` เพื่อติดตั้ง dependencies ที่จำเป็น
|
2. เริ่มแก้ไขบล็อกของคุณแบบ local โดยการ clone repository ของคุณ (จากข้อ 1) ไว้ในเครื่องของคุณ แล้วรันคำสั่ง `pnpm install` และ `pnpm add sharp` เพื่อติดตั้ง dependencies ที่จำเป็น
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
[**🖥️在线预览(Vercel)**](https://fuwari.vercel.app) /
|
[**🖥️在线预览(Vercel)**](https://fuwari.vercel.app) /
|
||||||
[**📦旧 Hexo 版本**](https://github.com/saicaca/hexo-theme-vivia)
|
[**📦旧 Hexo 版本**](https://github.com/saicaca/hexo-theme-vivia)
|
||||||
|
|
||||||
> README 版本:`2024-09-10`
|
> README 版本:`2025-04-24`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -18,9 +18,39 @@
|
|||||||
- [x] 响应式设计
|
- [x] 响应式设计
|
||||||
- [ ] 评论
|
- [ ] 评论
|
||||||
- [x] 搜索
|
- [x] 搜索
|
||||||
- [ ] 文内目录
|
- [x] 文内目录
|
||||||
|
|
||||||
## 🚀 使用方法
|
## 👀 要求
|
||||||
|
|
||||||
|
- Node.js <= 22
|
||||||
|
- pnpm <= 9
|
||||||
|
|
||||||
|
## 🚀 使用方法 1
|
||||||
|
|
||||||
|
使用 [create-fuwari](https://github.com/L4Ph/create-fuwari) 在本地初始化项目。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# npm
|
||||||
|
npm create fuwari@latest
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn create fuwari
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm create fuwari@latest
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun create fuwari@latest
|
||||||
|
|
||||||
|
# deno
|
||||||
|
deno run -A npm:create-fuwari@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
1. 通过配置文件 `src/config.ts` 自定义博客
|
||||||
|
2. 执行 `pnpm new-post <filename>` 创建新文章,并在 `src/content/posts/` 目录中编辑
|
||||||
|
3. 参考[官方指南](https://docs.astro.build/zh-cn/guides/deploy/)将博客部署至 Vercel, Netlify, GitHub Pages 等;部署前需编辑 `astro.config.mjs` 中的站点设置。
|
||||||
|
|
||||||
|
## 🚀 使用方法 2
|
||||||
|
|
||||||
1. 使用此模板[生成新仓库](https://github.com/saicaca/fuwari/generate)或 Fork 此仓库
|
1. 使用此模板[生成新仓库](https://github.com/saicaca/fuwari/generate)或 Fork 此仓库
|
||||||
2. 进行本地开发,Clone 新的仓库,执行 `pnpm install` 和 `pnpm add sharp` 以安装依赖
|
2. 进行本地开发,Clone 新的仓库,执行 `pnpm install` 和 `pnpm add sharp` 以安装依赖
|
||||||
|
|||||||
138
package.json
138
package.json
@@ -1,71 +1,71 @@
|
|||||||
{
|
{
|
||||||
"name": "fuwari",
|
"name": "fuwari",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
"build": "astro build && pagefind --site dist",
|
"build": "astro build && pagefind --site dist",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro",
|
"astro": "astro",
|
||||||
"type-check": "tsc --noEmit --isolatedDeclarations",
|
"type-check": "tsc --noEmit --isolatedDeclarations",
|
||||||
"new-post": "node scripts/new-post.js",
|
"new-post": "node scripts/new-post.js",
|
||||||
"format": "biome format --write ./src",
|
"format": "biome format --write ./src",
|
||||||
"lint": "biome check --write ./src",
|
"lint": "biome check --write ./src",
|
||||||
"preinstall": "npx only-allow pnpm"
|
"preinstall": "npx only-allow pnpm"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
"@astrojs/rss": "^4.0.11",
|
"@astrojs/rss": "^4.0.11",
|
||||||
"@astrojs/sitemap": "^3.3.1",
|
"@astrojs/sitemap": "^3.4.0",
|
||||||
"@astrojs/svelte": "7.0.11",
|
"@astrojs/svelte": "7.1.0",
|
||||||
"@fontsource-variable/jetbrains-mono": "^5.2.5",
|
"@fontsource-variable/jetbrains-mono": "^5.2.5",
|
||||||
"@fontsource/roboto": "^5.2.5",
|
"@fontsource/roboto": "^5.2.5",
|
||||||
"@iconify-json/fa6-brands": "^1.2.5",
|
"@iconify-json/fa6-brands": "^1.2.5",
|
||||||
"@iconify-json/fa6-regular": "^1.2.3",
|
"@iconify-json/fa6-regular": "^1.2.3",
|
||||||
"@iconify-json/fa6-solid": "^1.2.3",
|
"@iconify-json/fa6-solid": "^1.2.3",
|
||||||
"@iconify-json/material-symbols": "^1.2.20",
|
"@iconify-json/material-symbols": "^1.2.22",
|
||||||
"@iconify/svelte": "^4.2.0",
|
"@iconify/svelte": "^4.2.0",
|
||||||
"@swup/astro": "^1.6.0",
|
"@swup/astro": "^1.6.0",
|
||||||
"@tailwindcss/postcss": "^4.1.4",
|
"@tailwindcss/postcss": "^4.1.4",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@tailwindcss/vite": "^4.1.4",
|
"@tailwindcss/vite": "^4.1.4",
|
||||||
"astro": "5.7.5",
|
"astro": "5.8.1",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"hastscript": "^9.0.1",
|
"hastscript": "^9.0.1",
|
||||||
"katex": "^0.16.22",
|
"katex": "^0.16.22",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"overlayscrollbars": "^2.11.1",
|
"overlayscrollbars": "^2.11.3",
|
||||||
"pagefind": "^1.3.0",
|
"pagefind": "^1.3.0",
|
||||||
"photoswipe": "^5.4.4",
|
"photoswipe": "^5.4.4",
|
||||||
"reading-time": "^1.5.0",
|
"reading-time": "^1.5.0",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-components": "^0.3.0",
|
"rehype-components": "^0.3.0",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark-directive": "^3.0.1",
|
"remark-directive": "^3.0.1",
|
||||||
"remark-directive-rehype": "^0.4.2",
|
"remark-directive-rehype": "^0.4.2",
|
||||||
"remark-github-admonitions-to-directives": "^1.0.5",
|
"remark-github-admonitions-to-directives": "^1.0.5",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"remark-sectionize": "^2.1.0",
|
"remark-sectionize": "^2.1.0",
|
||||||
"sanitize-html": "^2.16.0",
|
"sanitize-html": "^2.17.0",
|
||||||
"sharp": "^0.34.1",
|
"sharp": "^0.34.2",
|
||||||
"stylus": "^0.64.0",
|
"stylus": "^0.64.0",
|
||||||
"svelte": "^5.28.2",
|
"svelte": "^5.33.10",
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/ts-plugin": "^1.10.4",
|
"@astrojs/ts-plugin": "^1.10.4",
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@rollup/plugin-yaml": "^4.1.2",
|
"@rollup/plugin-yaml": "^4.1.2",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@types/mdast": "^4.0.4",
|
"@types/mdast": "^4.0.4",
|
||||||
"@types/sanitize-html": "^2.15.0",
|
"@types/sanitize-html": "^2.16.0",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-nesting": "^13.0.1"
|
"postcss-nesting": "^13.0.1"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.14.4"
|
"packageManager": "pnpm@9.14.4"
|
||||||
}
|
}
|
||||||
|
|||||||
1375
pnpm-lock.yaml
generated
1375
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,118 +0,0 @@
|
|||||||
---
|
|
||||||
import { UNCATEGORIZED } from "@constants/constants";
|
|
||||||
import I18nKey from "../i18n/i18nKey";
|
|
||||||
import { i18n } from "../i18n/translation";
|
|
||||||
import { getSortedPosts } from "../utils/content-utils";
|
|
||||||
import { getPostUrlBySlug } from "../utils/url-utils";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
keyword?: string;
|
|
||||||
tags?: string[];
|
|
||||||
categories?: string[];
|
|
||||||
}
|
|
||||||
const { tags, categories } = Astro.props;
|
|
||||||
|
|
||||||
let posts = await getSortedPosts();
|
|
||||||
|
|
||||||
if (Array.isArray(tags) && tags.length > 0) {
|
|
||||||
posts = posts.filter(
|
|
||||||
(post) =>
|
|
||||||
Array.isArray(post.data.tags) &&
|
|
||||||
post.data.tags.some((tag) => tags.includes(tag)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(categories) && categories.length > 0) {
|
|
||||||
posts = posts.filter(
|
|
||||||
(post) =>
|
|
||||||
(post.data.category && categories.includes(post.data.category)) ||
|
|
||||||
(!post.data.category && categories.includes(UNCATEGORIZED)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const groups: { year: number; posts: typeof posts }[] = (() => {
|
|
||||||
const groupedPosts = posts.reduce(
|
|
||||||
(grouped: { [year: number]: typeof posts }, post) => {
|
|
||||||
const year = post.data.published.getFullYear();
|
|
||||||
if (!grouped[year]) {
|
|
||||||
grouped[year] = [];
|
|
||||||
}
|
|
||||||
grouped[year].push(post);
|
|
||||||
return grouped;
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
// convert the object to an array
|
|
||||||
const groupedPostsArray = Object.keys(groupedPosts).map((key) => ({
|
|
||||||
year: Number.parseInt(key),
|
|
||||||
posts: groupedPosts[Number.parseInt(key)],
|
|
||||||
}));
|
|
||||||
|
|
||||||
// sort years by latest first
|
|
||||||
groupedPostsArray.sort((a, b) => b.year - a.year);
|
|
||||||
return groupedPostsArray;
|
|
||||||
})();
|
|
||||||
|
|
||||||
function formatDate(date: Date) {
|
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
||||||
const day = date.getDate().toString().padStart(2, "0");
|
|
||||||
return `${month}-${day}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTag(tag: string[]) {
|
|
||||||
return tag.map((t) => `#${t}`).join(" ");
|
|
||||||
}
|
|
||||||
---
|
|
||||||
|
|
||||||
<div class="card-base px-8 py-6">
|
|
||||||
{
|
|
||||||
groups.map(group => (
|
|
||||||
<div>
|
|
||||||
<div class="flex flex-row w-full items-center h-[3.75rem]">
|
|
||||||
<div class="w-[15%] md:w-[10%] transition text-2xl font-bold text-right text-75">{group.year}</div>
|
|
||||||
<div class="w-[15%] md:w-[10%]">
|
|
||||||
<div class="h-3 w-3 bg-none rounded-full outline outline-[var(--primary)] mx-auto -outline-offset-[2px] z-50 outline-3"></div>
|
|
||||||
</div>
|
|
||||||
<div class="w-[70%] md:w-[80%] transition text-left text-50">{group.posts.length} {i18n(I18nKey.postsCount)}</div>
|
|
||||||
</div>
|
|
||||||
{group.posts.map(post => (
|
|
||||||
<a href={getPostUrlBySlug(post.slug)}
|
|
||||||
aria-label={post.data.title}
|
|
||||||
class="group btn-plain block! h-10 w-full rounded-lg hover:text-[initial]"
|
|
||||||
>
|
|
||||||
<div class="flex flex-row justify-start items-center h-full">
|
|
||||||
<!-- date -->
|
|
||||||
<div class="w-[15%] md:w-[10%] transition text-sm text-right text-50">
|
|
||||||
{formatDate(post.data.published)}
|
|
||||||
</div>
|
|
||||||
<!-- dot and line -->
|
|
||||||
<div class="w-[15%] md:w-[10%] relative dash-line h-full flex items-center">
|
|
||||||
<div class="transition-all mx-auto w-1 h-1 rounded group-hover:h-5
|
|
||||||
bg-[oklch(0.5_0.05_var(--hue))] group-hover:bg-[var(--primary)]
|
|
||||||
outline outline-4 z-50
|
|
||||||
outline-[var(--card-bg)]
|
|
||||||
group-hover:outline-[var(--btn-plain-bg-hover)]
|
|
||||||
group-active:outline-[var(--btn-plain-bg-active)]
|
|
||||||
"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<!-- post title -->
|
|
||||||
<div class="w-[70%] md:max-w-[65%] md:w-[65%] text-left font-bold
|
|
||||||
group-hover:translate-x-1 transition-all group-hover:text-[var(--primary)]
|
|
||||||
text-75 pr-8 whitespace-nowrap text-ellipsis overflow-hidden"
|
|
||||||
>
|
|
||||||
{post.data.title}
|
|
||||||
</div>
|
|
||||||
<!-- tag list -->
|
|
||||||
<div class="hidden md:block md:w-[15%] text-left text-sm transition
|
|
||||||
whitespace-nowrap text-ellipsis overflow-hidden
|
|
||||||
text-30"
|
|
||||||
>{formatTag(post.data.tags)}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
151
src/components/ArchivePanel.svelte
Normal file
151
src/components/ArchivePanel.svelte
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
import I18nKey from "../i18n/i18nKey";
|
||||||
|
import { i18n } from "../i18n/translation";
|
||||||
|
import { getPostUrlBySlug } from "../utils/url-utils";
|
||||||
|
|
||||||
|
export let tags: string[];
|
||||||
|
export let categories: string[];
|
||||||
|
export let sortedPosts: Post[] = [];
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
tags = params.has("tag") ? params.getAll("tag") : [];
|
||||||
|
categories = params.has("category") ? params.getAll("category") : [];
|
||||||
|
const uncategorized = params.get("uncategorized");
|
||||||
|
|
||||||
|
interface Post {
|
||||||
|
slug: string;
|
||||||
|
data: {
|
||||||
|
title: string;
|
||||||
|
tags: string[];
|
||||||
|
category?: string;
|
||||||
|
published: Date;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Group {
|
||||||
|
year: number;
|
||||||
|
posts: Post[];
|
||||||
|
}
|
||||||
|
|
||||||
|
let groups: Group[] = [];
|
||||||
|
|
||||||
|
function formatDate(date: Date) {
|
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||||
|
const day = date.getDate().toString().padStart(2, "0");
|
||||||
|
return `${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTag(tagList: string[]) {
|
||||||
|
return tagList.map((t) => `#${t}`).join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
let filteredPosts: Post[] = sortedPosts;
|
||||||
|
|
||||||
|
if (tags.length > 0) {
|
||||||
|
filteredPosts = filteredPosts.filter(
|
||||||
|
(post) =>
|
||||||
|
Array.isArray(post.data.tags) &&
|
||||||
|
post.data.tags.some((tag) => tags.includes(tag)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categories.length > 0) {
|
||||||
|
filteredPosts = filteredPosts.filter(
|
||||||
|
(post) => post.data.category && categories.includes(post.data.category),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uncategorized) {
|
||||||
|
filteredPosts = filteredPosts.filter((post) => !post.data.category);
|
||||||
|
}
|
||||||
|
|
||||||
|
const grouped = filteredPosts.reduce(
|
||||||
|
(acc, post) => {
|
||||||
|
const year = post.data.published.getFullYear();
|
||||||
|
if (!acc[year]) {
|
||||||
|
acc[year] = [];
|
||||||
|
}
|
||||||
|
acc[year].push(post);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<number, Post[]>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupedPostsArray = Object.keys(grouped).map((yearStr) => ({
|
||||||
|
year: Number.parseInt(yearStr),
|
||||||
|
posts: grouped[Number.parseInt(yearStr)],
|
||||||
|
}));
|
||||||
|
|
||||||
|
groupedPostsArray.sort((a, b) => b.year - a.year);
|
||||||
|
|
||||||
|
groups = groupedPostsArray;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="card-base px-8 py-6">
|
||||||
|
{#each groups as group}
|
||||||
|
<div>
|
||||||
|
<div class="flex flex-row w-full items-center h-[3.75rem]">
|
||||||
|
<div class="w-[15%] md:w-[10%] transition text-2xl font-bold text-right text-75">
|
||||||
|
{group.year}
|
||||||
|
</div>
|
||||||
|
<div class="w-[15%] md:w-[10%]">
|
||||||
|
<div
|
||||||
|
class="h-3 w-3 bg-none rounded-full outline outline-[var(--primary)] mx-auto
|
||||||
|
-outline-offset-[2px] z-50 outline-3"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div class="w-[70%] md:w-[80%] transition text-left text-50">
|
||||||
|
{group.posts.length} {i18n(I18nKey.postsCount)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#each group.posts as post}
|
||||||
|
<a
|
||||||
|
href={getPostUrlBySlug(post.slug)}
|
||||||
|
aria-label={post.data.title}
|
||||||
|
class="group btn-plain !block h-10 w-full rounded-lg hover:text-[initial]"
|
||||||
|
>
|
||||||
|
<div class="flex flex-row justify-start items-center h-full">
|
||||||
|
<!-- date -->
|
||||||
|
<div class="w-[15%] md:w-[10%] transition text-sm text-right text-50">
|
||||||
|
{formatDate(post.data.published)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- dot and line -->
|
||||||
|
<div class="w-[15%] md:w-[10%] relative dash-line h-full flex items-center">
|
||||||
|
<div
|
||||||
|
class="transition-all mx-auto w-1 h-1 rounded group-hover:h-5
|
||||||
|
bg-[oklch(0.5_0.05_var(--hue))] group-hover:bg-[var(--primary)]
|
||||||
|
outline outline-4 z-50
|
||||||
|
outline-[var(--card-bg)]
|
||||||
|
group-hover:outline-[var(--btn-plain-bg-hover)]
|
||||||
|
group-active:outline-[var(--btn-plain-bg-active)]"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- post title -->
|
||||||
|
<div
|
||||||
|
class="w-[70%] md:max-w-[65%] md:w-[65%] text-left font-bold
|
||||||
|
group-hover:translate-x-1 transition-all group-hover:text-[var(--primary)]
|
||||||
|
text-75 pr-8 whitespace-nowrap overflow-ellipsis overflow-hidden"
|
||||||
|
>
|
||||||
|
{post.data.title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- tag list -->
|
||||||
|
<div
|
||||||
|
class="hidden md:block md:w-[15%] text-left text-sm transition
|
||||||
|
whitespace-nowrap overflow-ellipsis overflow-hidden text-30"
|
||||||
|
>
|
||||||
|
{formatTag(post.data.tags)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
@@ -16,7 +16,7 @@ interface Props {
|
|||||||
published: Date;
|
published: Date;
|
||||||
updated?: Date;
|
updated?: Date;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
category: string;
|
category: string | null;
|
||||||
image: string;
|
image: string;
|
||||||
description: string;
|
description: string;
|
||||||
draft: boolean;
|
draft: boolean;
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { Icon } from "astro-icon/components";
|
|||||||
import I18nKey from "../i18n/i18nKey";
|
import I18nKey from "../i18n/i18nKey";
|
||||||
import { i18n } from "../i18n/translation";
|
import { i18n } from "../i18n/translation";
|
||||||
import { formatDateToYYYYMMDD } from "../utils/date-utils";
|
import { formatDateToYYYYMMDD } from "../utils/date-utils";
|
||||||
import { url } from "../utils/url-utils";
|
import { getCategoryUrl, getTagUrl } from "../utils/url-utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class: string;
|
class: string;
|
||||||
published: Date;
|
published: Date;
|
||||||
updated?: Date;
|
updated?: Date;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
category: string;
|
category: string | null;
|
||||||
hideTagsForMobile?: boolean;
|
hideTagsForMobile?: boolean;
|
||||||
hideUpdateDate?: boolean;
|
hideUpdateDate?: boolean;
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ const className = Astro.props.class;
|
|||||||
<Icon name="material-symbols:book-2-outline-rounded" class="text-xl"></Icon>
|
<Icon name="material-symbols:book-2-outline-rounded" class="text-xl"></Icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row flex-nowrap items-center">
|
<div class="flex flex-row flex-nowrap items-center">
|
||||||
<a href={url(`/archive/category/${category || 'uncategorized'}/`)} aria-label=`View all posts in the ${category} category`
|
<a href={getCategoryUrl(category)} aria-label={`View all posts in the ${category} category`}
|
||||||
class="link-lg transition text-50 text-sm font-medium
|
class="link-lg transition text-50 text-sm font-medium
|
||||||
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap">
|
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap">
|
||||||
{category || i18n(I18nKey.uncategorized)}
|
{category || i18n(I18nKey.uncategorized)}
|
||||||
@@ -70,10 +70,10 @@ const className = Astro.props.class;
|
|||||||
<div class="flex flex-row flex-nowrap items-center">
|
<div class="flex flex-row flex-nowrap items-center">
|
||||||
{(tags && tags.length > 0) && tags.map((tag, i) => (
|
{(tags && tags.length > 0) && tags.map((tag, i) => (
|
||||||
<div class:list={[{"hidden": i == 0}, "mx-1.5 text-[var(--meta-divider)] text-sm"]}>/</div>
|
<div class:list={[{"hidden": i == 0}, "mx-1.5 text-[var(--meta-divider)] text-sm"]}>/</div>
|
||||||
<a href={url(`/archive/tag/${tag}/`)} aria-label=`View all posts with the ${tag} tag`
|
<a href={getTagUrl(tag)} aria-label={`View all posts with the ${tag.trim()} tag`}
|
||||||
class="link-lg transition text-50 text-sm font-medium
|
class="link-lg transition text-50 text-sm font-medium
|
||||||
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap">
|
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap">
|
||||||
{tag}
|
{tag.trim()}
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
{!(tags && tags.length > 0) && <div class="transition text-50 text-sm font-medium">{i18n(I18nKey.noTags)}</div>}
|
{!(tags && tags.length > 0) && <div class="transition text-50 text-sm font-medium">{i18n(I18nKey.noTags)}</div>}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const getPageUrl = (p: number) => {
|
|||||||
>
|
>
|
||||||
{p}
|
{p}
|
||||||
</div>
|
</div>
|
||||||
return <a href={url(getPageUrl(p))} aria-label=`Page ${p}`
|
return <a href={url(getPageUrl(p))} aria-label={`Page ${p}`}
|
||||||
class="btn-card w-11 h-11 rounded-lg overflow-hidden active:scale-[0.85]"
|
class="btn-card w-11 h-11 rounded-lg overflow-hidden active:scale-[0.85]"
|
||||||
>{p}</a>
|
>{p}</a>
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const profileConf = profileConfig;
|
|||||||
const licenseConf = licenseConfig;
|
const licenseConf = licenseConfig;
|
||||||
const postUrl = decodeURIComponent(Astro.url.toString());
|
const postUrl = decodeURIComponent(Astro.url.toString());
|
||||||
---
|
---
|
||||||
<div class=`relative transition overflow-hidden bg-[var(--license-block-bg)] py-5 px-6 ${className}`>
|
<div class={`relative transition overflow-hidden bg-[var(--license-block-bg)] py-5 px-6 ${className}`}>
|
||||||
<div class="transition font-bold text-black/75 dark:text-white/75">
|
<div class="transition font-bold text-black/75 dark:text-white/75">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import WidgetLayout from "./WidgetLayout.astro";
|
|||||||
import I18nKey from "../../i18n/i18nKey";
|
import I18nKey from "../../i18n/i18nKey";
|
||||||
import { i18n } from "../../i18n/translation";
|
import { i18n } from "../../i18n/translation";
|
||||||
import { getCategoryList } from "../../utils/content-utils";
|
import { getCategoryList } from "../../utils/content-utils";
|
||||||
import { getCategoryUrl } from "../../utils/url-utils";
|
|
||||||
import ButtonLink from "../control/ButtonLink.astro";
|
import ButtonLink from "../control/ButtonLink.astro";
|
||||||
|
|
||||||
const categories = await getCategoryList();
|
const categories = await getCategoryList();
|
||||||
@@ -27,11 +26,11 @@ const style = Astro.props.style;
|
|||||||
>
|
>
|
||||||
{categories.map((c) =>
|
{categories.map((c) =>
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
url={getCategoryUrl(c.name)}
|
url={c.url}
|
||||||
badge={String(c.count)}
|
badge={String(c.count)}
|
||||||
label=`View all posts in the ${c.name} category`
|
label={`View all posts in the ${c.name.trim()} category`}
|
||||||
>
|
>
|
||||||
{c.name}
|
{c.name.trim()}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
)}
|
)}
|
||||||
</WidgetLayout>
|
</WidgetLayout>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import I18nKey from "../../i18n/i18nKey";
|
import I18nKey from "../../i18n/i18nKey";
|
||||||
import { i18n } from "../../i18n/translation";
|
import { i18n } from "../../i18n/translation";
|
||||||
import { getTagList } from "../../utils/content-utils";
|
import { getTagList } from "../../utils/content-utils";
|
||||||
import { url } from "../../utils/url-utils";
|
import { getTagUrl } from "../../utils/url-utils";
|
||||||
import ButtonTag from "../control/ButtonTag.astro";
|
import ButtonTag from "../control/ButtonTag.astro";
|
||||||
import WidgetLayout from "./WidgetLayout.astro";
|
import WidgetLayout from "./WidgetLayout.astro";
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ const style = Astro.props.style;
|
|||||||
<WidgetLayout name={i18n(I18nKey.tags)} id="tags" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT} class={className} style={style}>
|
<WidgetLayout name={i18n(I18nKey.tags)} id="tags" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT} class={className} style={style}>
|
||||||
<div class="flex gap-2 flex-wrap">
|
<div class="flex gap-2 flex-wrap">
|
||||||
{tags.map(t => (
|
{tags.map(t => (
|
||||||
<ButtonTag href={url(`/archive/tag/${t.name}/`)} label={`View all posts with the ${t.name} tag`}>
|
<ButtonTag href={getTagUrl(t.name)} label={`View all posts with the ${t.name.trim()} tag`}>
|
||||||
{t.name}
|
{t.name.trim()}
|
||||||
</ButtonTag>
|
</ButtonTag>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
export const UNCATEGORIZED = "__uncategorized__";
|
|
||||||
|
|
||||||
export const PAGE_SIZE = 8;
|
export const PAGE_SIZE = 8;
|
||||||
|
|
||||||
export const LIGHT_MODE = "light",
|
export const LIGHT_MODE = "light",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const postsCollection = defineCollection({
|
|||||||
description: z.string().optional().default(""),
|
description: z.string().optional().default(""),
|
||||||
image: z.string().optional().default(""),
|
image: z.string().optional().default(""),
|
||||||
tags: z.array(z.string()).optional().default([]),
|
tags: z.array(z.string()).optional().default([]),
|
||||||
category: z.string().optional().default(""),
|
category: z.string().optional().nullable().default(""),
|
||||||
lang: z.string().optional().default(""),
|
lang: z.string().optional().default(""),
|
||||||
|
|
||||||
/* For internal use */
|
/* For internal use */
|
||||||
|
|||||||
@@ -245,13 +245,49 @@ function initCustomScrollbar() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const katexElements = document.querySelectorAll('.katex-display') as NodeListOf<HTMLElement>;
|
const katexElements = document.querySelectorAll('.katex-display') as NodeListOf<HTMLElement>;
|
||||||
katexElements.forEach((ele) => {
|
|
||||||
OverlayScrollbars(ele, {
|
const katexObserverOptions = {
|
||||||
|
root: null,
|
||||||
|
rootMargin: '100px',
|
||||||
|
threshold: 0.1
|
||||||
|
};
|
||||||
|
|
||||||
|
const processKatexElement = (element: HTMLElement) => {
|
||||||
|
if (!element.parentNode) return;
|
||||||
|
if (element.hasAttribute('data-scrollbar-initialized')) return;
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = 'katex-display-container';
|
||||||
|
container.setAttribute('aria-label', 'scrollable container for formulas');
|
||||||
|
|
||||||
|
element.parentNode.insertBefore(container, element);
|
||||||
|
container.appendChild(element);
|
||||||
|
|
||||||
|
OverlayScrollbars(container, {
|
||||||
scrollbars: {
|
scrollbars: {
|
||||||
theme: 'scrollbar-base scrollbar-auto py-1',
|
theme: 'scrollbar-base scrollbar-auto',
|
||||||
|
autoHide: 'leave',
|
||||||
|
autoHideDelay: 500,
|
||||||
|
autoHideSuspend: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
element.setAttribute('data-scrollbar-initialized', 'true');
|
||||||
|
};
|
||||||
|
|
||||||
|
const katexObserver = new IntersectionObserver((entries, observer) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
processKatexElement(entry.target as HTMLElement);
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, katexObserverOptions);
|
||||||
|
|
||||||
|
katexElements.forEach(element => {
|
||||||
|
katexObserver.observe(element);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const mainPanelTop = siteConfig.banner.enable
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Banner -->
|
<!-- Banner -->
|
||||||
{siteConfig.banner.enable && <div id="banner-wrapper" class=`absolute z-10 w-full transition duration-700 overflow-hidden` style=`top: -${BANNER_HEIGHT_EXTEND}vh`>
|
{siteConfig.banner.enable && <div id="banner-wrapper" class={`absolute z-10 w-full transition duration-700 overflow-hidden`} style={`top: -${BANNER_HEIGHT_EXTEND}vh`}>
|
||||||
<ImageWrapper id="banner" alt="Banner image of the blog" class:list={["object-cover h-full transition duration-700 opacity-0 scale-105"]}
|
<ImageWrapper id="banner" alt="Banner image of the blog" class:list={["object-cover h-full transition duration-700 opacity-0 scale-105"]}
|
||||||
src={siteConfig.banner.src} position={siteConfig.banner.position}
|
src={siteConfig.banner.src} position={siteConfig.banner.position}
|
||||||
>
|
>
|
||||||
@@ -60,7 +60,7 @@ const mainPanelTop = siteConfig.banner.enable
|
|||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
<!-- Main content -->
|
<!-- Main content -->
|
||||||
<div class="absolute w-full z-30 pointer-events-none" style=`top: ${mainPanelTop}`>
|
<div class="absolute w-full z-30 pointer-events-none" style={`top: ${mainPanelTop}`}>
|
||||||
<!-- The pointer-events-none here prevent blocking the click event of the TOC -->
|
<!-- The pointer-events-none here prevent blocking the click event of the TOC -->
|
||||||
<div class="relative max-w-[var(--page-width)] mx-auto pointer-events-auto">
|
<div class="relative max-w-[var(--page-width)] mx-auto pointer-events-auto">
|
||||||
<div id="main-grid" class="transition duration-700 w-full left-0 right-0 grid grid-cols-[17.5rem_auto] grid-rows-[auto_1fr_auto] lg:grid-rows-[auto]
|
<div id="main-grid" class="transition duration-700 w-full left-0 right-0 grid grid-cols-[17.5rem_auto] grid-rows-[auto_1fr_auto] lg:grid-rows-[auto]
|
||||||
@@ -106,7 +106,7 @@ const mainPanelTop = siteConfig.banner.enable
|
|||||||
<div class="absolute w-full z-0 hidden 2xl:block">
|
<div class="absolute w-full z-0 hidden 2xl:block">
|
||||||
<div class="relative max-w-[var(--page-width)] mx-auto">
|
<div class="relative max-w-[var(--page-width)] mx-auto">
|
||||||
<!-- TOC component -->
|
<!-- TOC component -->
|
||||||
{siteConfig.toc.enable && <div id="toc-wrapper" class:list={["hidden lg:block transition absolute top-0 -right-[var(--toc-width)] w-[var(--toc-width)] flex items-center",
|
{siteConfig.toc.enable && <div id="toc-wrapper" class:list={["hidden lg:block transition absolute top-0 -right-[var(--toc-width)] w-[var(--toc-width)] items-center",
|
||||||
{"toc-hide": siteConfig.banner.enable}]}
|
{"toc-hide": siteConfig.banner.enable}]}
|
||||||
>
|
>
|
||||||
<div id="toc-inner-wrapper" class="fixed top-14 w-[var(--toc-width)] h-[calc(100vh_-_20rem)] overflow-y-scroll overflow-x-hidden hide-scrollbar">
|
<div id="toc-inner-wrapper" class="fixed top-14 w-[var(--toc-width)] h-[calc(100vh_-_20rem)] overflow-y-scroll overflow-x-hidden hide-scrollbar">
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ const len = page.data.length;
|
|||||||
|
|
||||||
<MainGridLayout>
|
<MainGridLayout>
|
||||||
<PostPage page={page}></PostPage>
|
<PostPage page={page}></PostPage>
|
||||||
<Pagination class="mx-auto onload-animation" page={page} style=`animation-delay: calc(var(--content-delay) + ${(len)*50}ms)`></Pagination>
|
<Pagination class="mx-auto onload-animation" page={page} style={`animation-delay: calc(var(--content-delay) + ${(len)*50}ms)`}></Pagination>
|
||||||
</MainGridLayout>
|
</MainGridLayout>
|
||||||
14
src/pages/archive.astro
Normal file
14
src/pages/archive.astro
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
import ArchivePanel from "@components/ArchivePanel.svelte";
|
||||||
|
import I18nKey from "@i18n/i18nKey";
|
||||||
|
import { i18n } from "@i18n/translation";
|
||||||
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
|
import { getSortedPosts } from "../utils/content-utils";
|
||||||
|
|
||||||
|
const sortedPosts = await getSortedPosts();
|
||||||
|
---
|
||||||
|
|
||||||
|
<MainGridLayout title={i18n(I18nKey.archive)}>
|
||||||
|
<ArchivePanel sortedPosts={sortedPosts} client:only="svelte"></ArchivePanel>
|
||||||
|
</MainGridLayout>
|
||||||
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
import ArchivePanel from "@components/ArchivePanel.astro";
|
|
||||||
import I18nKey from "@i18n/i18nKey";
|
|
||||||
import { i18n } from "@i18n/translation";
|
|
||||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
|
||||||
import { getCategoryList } from "@utils/content-utils";
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
|
||||||
const categories = await getCategoryList();
|
|
||||||
return categories.map((category) => {
|
|
||||||
return {
|
|
||||||
params: {
|
|
||||||
category: category.name,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const category = Astro.params.category as string;
|
|
||||||
---
|
|
||||||
|
|
||||||
<MainGridLayout title={i18n(I18nKey.archive)} description={i18n(I18nKey.archive)}>
|
|
||||||
<ArchivePanel categories={[category]}></ArchivePanel>
|
|
||||||
</MainGridLayout>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
import ArchivePanel from "@components/ArchivePanel.astro";
|
|
||||||
import { UNCATEGORIZED } from "@constants/constants";
|
|
||||||
import I18nKey from "@i18n/i18nKey";
|
|
||||||
import { i18n } from "@i18n/translation";
|
|
||||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<MainGridLayout title={i18n(I18nKey.archive)}>
|
|
||||||
<ArchivePanel categories={[UNCATEGORIZED]}></ArchivePanel>
|
|
||||||
</MainGridLayout>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
import ArchivePanel from "@components/ArchivePanel.astro";
|
|
||||||
import I18nKey from "@i18n/i18nKey";
|
|
||||||
import { i18n } from "@i18n/translation";
|
|
||||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<MainGridLayout title={i18n(I18nKey.archive)}>
|
|
||||||
<ArchivePanel></ArchivePanel>
|
|
||||||
</MainGridLayout>
|
|
||||||
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
import ArchivePanel from "@components/ArchivePanel.astro";
|
|
||||||
import I18nKey from "@i18n/i18nKey";
|
|
||||||
import { i18n } from "@i18n/translation";
|
|
||||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
|
||||||
import { getSortedPosts } from "@utils/content-utils";
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
|
||||||
const posts = await getSortedPosts();
|
|
||||||
|
|
||||||
// タグを集めるための Set の型を指定
|
|
||||||
const allTags = posts.reduce<Set<string>>((acc, post) => {
|
|
||||||
// biome-ignore lint/complexity/noForEach: <explanation>
|
|
||||||
post.data.tags.forEach((tag) => acc.add(tag));
|
|
||||||
return acc;
|
|
||||||
}, new Set());
|
|
||||||
|
|
||||||
const allTagsArray = Array.from(allTags);
|
|
||||||
|
|
||||||
return allTagsArray.map((tag) => ({
|
|
||||||
params: {
|
|
||||||
tag: tag,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const tag = Astro.params.tag as string;
|
|
||||||
---
|
|
||||||
|
|
||||||
<MainGridLayout title={i18n(I18nKey.archive)} description={i18n(I18nKey.archive)}>
|
|
||||||
<ArchivePanel tags={[tag]}></ArchivePanel>
|
|
||||||
</MainGridLayout>
|
|
||||||
@@ -91,4 +91,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.katex-display-container {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import I18nKey from "@i18n/i18nKey";
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import { i18n } from "@i18n/translation";
|
import { i18n } from "@i18n/translation";
|
||||||
|
import { getCategoryUrl } from "@utils/url-utils.ts";
|
||||||
|
|
||||||
export async function getSortedPosts() {
|
export async function getSortedPosts() {
|
||||||
const allBlogPosts = await getCollection("posts", ({ data }) => {
|
const allBlogPosts = await getCollection("posts", ({ data }) => {
|
||||||
@@ -54,6 +55,7 @@ export async function getTagList(): Promise<Tag[]> {
|
|||||||
export type Category = {
|
export type Category = {
|
||||||
name: string;
|
name: string;
|
||||||
count: number;
|
count: number;
|
||||||
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getCategoryList(): Promise<Category[]> {
|
export async function getCategoryList(): Promise<Category[]> {
|
||||||
@@ -61,15 +63,19 @@ export async function getCategoryList(): Promise<Category[]> {
|
|||||||
return import.meta.env.PROD ? data.draft !== true : true;
|
return import.meta.env.PROD ? data.draft !== true : true;
|
||||||
});
|
});
|
||||||
const count: { [key: string]: number } = {};
|
const count: { [key: string]: number } = {};
|
||||||
allBlogPosts.map((post: { data: { category: string | number } }) => {
|
allBlogPosts.map((post: { data: { category: string | null } }) => {
|
||||||
if (!post.data.category) {
|
if (!post.data.category) {
|
||||||
const ucKey = i18n(I18nKey.uncategorized);
|
const ucKey = i18n(I18nKey.uncategorized);
|
||||||
count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1;
|
count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
count[post.data.category] = count[post.data.category]
|
|
||||||
? count[post.data.category] + 1
|
const categoryName =
|
||||||
: 1;
|
typeof post.data.category === "string"
|
||||||
|
? post.data.category.trim()
|
||||||
|
: String(post.data.category).trim();
|
||||||
|
|
||||||
|
count[categoryName] = count[categoryName] ? count[categoryName] + 1 : 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
const lst = Object.keys(count).sort((a, b) => {
|
const lst = Object.keys(count).sort((a, b) => {
|
||||||
@@ -78,7 +84,11 @@ export async function getCategoryList(): Promise<Category[]> {
|
|||||||
|
|
||||||
const ret: Category[] = [];
|
const ret: Category[] = [];
|
||||||
for (const c of lst) {
|
for (const c of lst) {
|
||||||
ret.push({ name: c, count: count[c] });
|
ret.push({
|
||||||
|
name: c,
|
||||||
|
count: count[c],
|
||||||
|
url: getCategoryUrl(c),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import i18nKey from "@i18n/i18nKey";
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import { i18n } from "@i18n/translation";
|
import { i18n } from "@i18n/translation";
|
||||||
|
|
||||||
export function pathsEqual(path1: string, path2: string) {
|
export function pathsEqual(path1: string, path2: string) {
|
||||||
@@ -16,10 +16,19 @@ export function getPostUrlBySlug(slug: string): string {
|
|||||||
return url(`/posts/${slug}/`);
|
return url(`/posts/${slug}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCategoryUrl(category: string): string {
|
export function getTagUrl(tag: string): string {
|
||||||
if (category === i18n(i18nKey.uncategorized))
|
if (!tag) return url("/archive/");
|
||||||
return url("/archive/category/uncategorized/");
|
return url(`/archive/?tag=${encodeURIComponent(tag.trim())}`);
|
||||||
return url(`/archive/category/${category}/`);
|
}
|
||||||
|
|
||||||
|
export function getCategoryUrl(category: string | null): string {
|
||||||
|
if (
|
||||||
|
!category ||
|
||||||
|
category.trim() === "" ||
|
||||||
|
category.trim().toLowerCase() === i18n(I18nKey.uncategorized).toLowerCase()
|
||||||
|
)
|
||||||
|
return url("/archive/?uncategorized=true");
|
||||||
|
return url(`/archive/?category=${encodeURIComponent(category.trim())}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDir(path: string): string {
|
export function getDir(path: string): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user