优化结构
@@ -1,4 +1,4 @@
|
||||
name: 自动部署
|
||||
name: 鑷姩閮ㄧ讲
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -13,11 +13,11 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 检查分支
|
||||
- name: 妫€鏌ュ垎鏀?
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
- name: 缓存项目 npm 包
|
||||
- name: 缂撳瓨椤圭洰 npm 鍖?
|
||||
id: cache-node-modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@@ -25,32 +25,29 @@ jobs:
|
||||
key: ${{ runner.os }}-nodeModules-${{ hashFiles('package-lock.json') }}-${{ hashFiles('package.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nodeModules-
|
||||
- name: 安装 Node
|
||||
- name: 瀹夎 Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22.x"
|
||||
- name: 安装 Hexo
|
||||
run: |
|
||||
npm install hexo-cli --global
|
||||
- name: 安装依赖
|
||||
- name: 瀹夎渚濊禆
|
||||
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
npm install
|
||||
- name: 清理文件树
|
||||
- name: 娓呯悊鏂囦欢鏍?
|
||||
run: |
|
||||
npm run clean
|
||||
- name: 生成静态文件并压缩
|
||||
- name: 鐢熸垚闈欐€佹枃浠跺苟鍘嬬缉
|
||||
run: |
|
||||
npm run build
|
||||
- name: 部署
|
||||
- name: 閮ㄧ讲
|
||||
run: |
|
||||
cd ./public
|
||||
cd ./dist
|
||||
git init
|
||||
git config user.name "${{ gitea.actor }}"
|
||||
git config user.email "${{ gitea.actor }}@noreply.gitea.io"
|
||||
git add .
|
||||
git commit -m "${{ gitea.event.head_commit.message }}··[$(date +"%Z %Y-%m-%d %A %H:%M:%S")]"
|
||||
git commit -m "${{ gitea.event.head_commit.message }}路路[$(date +"%Z %Y-%m-%d %A %H:%M:%S")]"
|
||||
git push --force --quiet "https://${{ gitea.actor }}:${{ secrets.DEPLOY_TOKEN }}@git.biss.click/biss/blog.git" master:page
|
||||
- name: Deploy to Server
|
||||
run: |
|
||||
curl -k -X POST "https://panel.biss.click/hook?access_key=1XJG8IvYTSZVvD5dpm86GYIpQxgxBcucULnX1MFskZSKayXU"
|
||||
curl -k -X POST "https://panel.biss.click/hook?access_key=1XJG8IvYTSZVvD5dpm86GYIpQxgxBcucULnX1MFskZSKayXU"
|
||||
|
||||
@@ -4,7 +4,7 @@ db.json
|
||||
*.log
|
||||
node_modules/
|
||||
.astro/
|
||||
public/
|
||||
dist/
|
||||
.deploy*/
|
||||
_multiconfig.yml
|
||||
.hintrc
|
||||
|
||||
@@ -8,8 +8,6 @@ import remarkHexoLinkCards from './src/lib/remarkHexoLinkCards.mjs';
|
||||
export default defineConfig({
|
||||
site: 'https://blog.biss.click',
|
||||
output: 'static',
|
||||
outDir: './public',
|
||||
publicDir: './static',
|
||||
integrations: [sitemap()],
|
||||
markdown: {
|
||||
processor: unified({
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 457 KiB After Width: | Height: | Size: 457 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
@@ -1,7 +1,7 @@
|
||||
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { basename, extname, join, relative } from 'node:path';
|
||||
|
||||
const POSTS_DIR = join(process.cwd(), 'posts');
|
||||
const POSTS_DIR = join(process.cwd(), 'src', 'content', 'posts');
|
||||
|
||||
const CRC32_TABLE = new Uint32Array(256);
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
import { glob } from 'astro/loaders';
|
||||
|
||||
const posts = defineCollection({
|
||||
loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
|
||||
schema: z.object({
|
||||
title: z.any().optional(),
|
||||
date: z.any().optional(),
|
||||
abbrlink: z.any().optional(),
|
||||
tags: z.any().optional(),
|
||||
categories: z.any().optional(),
|
||||
summary: z.any().optional(),
|
||||
description: z.any().optional(),
|
||||
comments: z.any().optional(),
|
||||
cover: z.any().optional(),
|
||||
}).passthrough(),
|
||||
});
|
||||
|
||||
export const collections = { posts };
|
||||
@@ -1,37 +0,0 @@
|
||||
type PageModule = {
|
||||
frontmatter: Record<string, unknown>;
|
||||
Content: unknown;
|
||||
rawContent?: () => string;
|
||||
};
|
||||
|
||||
export type SitePage = PageModule & {
|
||||
title: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
const modules = import.meta.glob<PageModule>('/source/**/index.md', { eager: true });
|
||||
|
||||
export function getSitePages(): SitePage[] {
|
||||
return Object.entries(modules)
|
||||
.filter(([path]) => {
|
||||
if (path.includes('/_posts/') || path.includes('/_drafts/')) return false;
|
||||
return ![
|
||||
'/source/about/index.md',
|
||||
'/source/cc/index.md',
|
||||
'/source/cookie/index.md',
|
||||
'/source/link/index.md',
|
||||
'/source/privacy/index.md',
|
||||
'/source/categories/index.md',
|
||||
'/source/tags/index.md',
|
||||
'/source/shuoshuo/index.md',
|
||||
].includes(path);
|
||||
})
|
||||
.map(([path, module]) => {
|
||||
const slug = path.replace('/source/', '').replace('/index.md', '');
|
||||
return {
|
||||
...module,
|
||||
title: String(module.frontmatter.title ?? slug),
|
||||
slug,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
type MarkdownModule = {
|
||||
frontmatter: Record<string, unknown>;
|
||||
Content: unknown;
|
||||
rawContent?: () => string;
|
||||
file?: string;
|
||||
};
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
export type BlogPost = MarkdownModule & {
|
||||
export type BlogPost = {
|
||||
entry: CollectionEntry<'posts'>;
|
||||
title: string;
|
||||
slug: string;
|
||||
date: Date;
|
||||
@@ -16,10 +13,9 @@ export type BlogPost = MarkdownModule & {
|
||||
comments: boolean;
|
||||
cover?: string;
|
||||
href: string;
|
||||
body: string;
|
||||
};
|
||||
|
||||
const modules = import.meta.glob<MarkdownModule>('/posts/**/*.md', { eager: true });
|
||||
|
||||
function asArray(value: unknown): string[] {
|
||||
if (Array.isArray(value)) return value.map(String).filter(Boolean);
|
||||
if (typeof value === 'string' && value.trim()) return [value.trim()];
|
||||
@@ -43,20 +39,19 @@ function formatDate(date: Date): string {
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
function fallbackSlug(path: string): string {
|
||||
return path
|
||||
function fallbackSlug(id: string): string {
|
||||
return id
|
||||
.split('/')
|
||||
.pop()
|
||||
?.replace(/\.md$/, '')
|
||||
.toLowerCase() ?? 'post';
|
||||
}
|
||||
|
||||
function makeSummary(module: MarkdownModule): string {
|
||||
const explicit = module.frontmatter.summary ?? module.frontmatter.description;
|
||||
function makeSummary(entry: CollectionEntry<'posts'>): string {
|
||||
const explicit = entry.data.summary ?? entry.data.description;
|
||||
if (typeof explicit === 'string' && explicit.trim()) return explicit.trim();
|
||||
|
||||
const raw = module.rawContent?.() ?? '';
|
||||
return raw
|
||||
return entry.body
|
||||
.replace(/```[\s\S]*?```/g, '')
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/[#>*_`~\-\[\]\(\)]/g, '')
|
||||
@@ -65,34 +60,37 @@ function makeSummary(module: MarkdownModule): string {
|
||||
.slice(0, 120);
|
||||
}
|
||||
|
||||
export function getAllPosts(): BlogPost[] {
|
||||
return Object.entries(modules)
|
||||
.map(([path, module]) => {
|
||||
const date = toDate(module.frontmatter.date);
|
||||
const slug = String(module.frontmatter.abbrlink ?? fallbackSlug(path));
|
||||
const title = String(module.frontmatter.title ?? slug);
|
||||
export async function getAllPosts(): Promise<BlogPost[]> {
|
||||
const entries = await getCollection('posts');
|
||||
|
||||
return entries
|
||||
.map((entry) => {
|
||||
const date = toDate(entry.data.date);
|
||||
const slug = String(entry.data.abbrlink ?? fallbackSlug(entry.id));
|
||||
const title = String(entry.data.title ?? slug);
|
||||
|
||||
return {
|
||||
...module,
|
||||
entry,
|
||||
title,
|
||||
slug,
|
||||
date,
|
||||
dateText: formatDate(date),
|
||||
tags: asArray(module.frontmatter.tags),
|
||||
categories: asArray(module.frontmatter.categories),
|
||||
summary: makeSummary(module),
|
||||
comments: module.frontmatter.comments !== false,
|
||||
cover: typeof module.frontmatter.cover === 'string' ? module.frontmatter.cover : undefined,
|
||||
tags: asArray(entry.data.tags),
|
||||
categories: asArray(entry.data.categories),
|
||||
summary: makeSummary(entry),
|
||||
comments: entry.data.comments !== false,
|
||||
cover: typeof entry.data.cover === 'string' ? entry.data.cover : undefined,
|
||||
href: `/posts/${slug}/`,
|
||||
body: entry.body,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.date.valueOf() - a.date.valueOf());
|
||||
}
|
||||
|
||||
export function getAllTags(): string[] {
|
||||
return [...new Set(getAllPosts().flatMap((post) => post.tags))].sort((a, b) => a.localeCompare(b, 'zh-CN'));
|
||||
export async function getAllTags(): Promise<string[]> {
|
||||
return [...new Set((await getAllPosts()).flatMap((post) => post.tags))].sort((a, b) => a.localeCompare(b, 'zh-CN'));
|
||||
}
|
||||
|
||||
export function getAllCategories(): string[] {
|
||||
return [...new Set(getAllPosts().flatMap((post) => post.categories))].sort((a, b) => a.localeCompare(b, 'zh-CN'));
|
||||
export async function getAllCategories(): Promise<string[]> {
|
||||
return [...new Set((await getAllPosts()).flatMap((post) => post.categories))].sort((a, b) => a.localeCompare(b, 'zh-CN'));
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||
import { getSitePages } from '@/lib/pages';
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getSitePages().map((page) => ({
|
||||
params: { slug: page.slug },
|
||||
props: { page },
|
||||
}));
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const { Content } = page;
|
||||
---
|
||||
|
||||
<BaseLayout title={page.title}>
|
||||
<section class="content-wrap narrow page-with-card">
|
||||
<PageHeaderCard title={page.title} />
|
||||
</section>
|
||||
<article class="article page-article">
|
||||
<div class="prose">
|
||||
<Content />
|
||||
</div>
|
||||
</article>
|
||||
</BaseLayout>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getAllPosts } from '@/lib/posts';
|
||||
|
||||
export function GET() {
|
||||
const posts = getAllPosts().map((post) => ({
|
||||
export async function GET() {
|
||||
const posts = (await getAllPosts()).map((post) => ({
|
||||
title: post.title,
|
||||
href: post.href,
|
||||
}));
|
||||
|
||||
@@ -5,7 +5,7 @@ import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||
import { getAllPosts } from '@/lib/posts';
|
||||
import { getPageSlice } from '@/lib/pagination';
|
||||
|
||||
const posts = getAllPosts();
|
||||
const posts = await getAllPosts();
|
||||
const pageData = getPageSlice(posts, 1);
|
||||
---
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||
import { getAllPosts } from '@/lib/posts';
|
||||
import { getPageSlice, getPaginationPaths } from '@/lib/pagination';
|
||||
|
||||
export function getStaticPaths() {
|
||||
const posts = getAllPosts();
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getAllPosts();
|
||||
|
||||
return getPaginationPaths(posts).map((page) => ({
|
||||
params: { page: String(page) },
|
||||
@@ -15,7 +15,7 @@ export function getStaticPaths() {
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const posts = getAllPosts();
|
||||
const posts = await getAllPosts();
|
||||
const pageData = getPageSlice(posts, page);
|
||||
---
|
||||
|
||||
|
||||
@@ -4,15 +4,15 @@ import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||
import PostCard from '@/components/PostCard.astro';
|
||||
import { getAllCategories, getAllPosts } from '@/lib/posts';
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getAllCategories().map((category) => ({
|
||||
export async function getStaticPaths() {
|
||||
return (await getAllCategories()).map((category) => ({
|
||||
params: { category },
|
||||
props: { category },
|
||||
}));
|
||||
}
|
||||
|
||||
const { category } = Astro.props;
|
||||
const posts = getAllPosts().filter((post) => post.categories.includes(category));
|
||||
const posts = (await getAllPosts()).filter((post) => post.categories.includes(category));
|
||||
---
|
||||
|
||||
<BaseLayout title={category}>
|
||||
|
||||
@@ -3,8 +3,8 @@ import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||
import { getAllCategories, getAllPosts } from '@/lib/posts';
|
||||
|
||||
const posts = getAllPosts();
|
||||
const categories = getAllCategories();
|
||||
const posts = await getAllPosts();
|
||||
const categories = await getAllCategories();
|
||||
---
|
||||
|
||||
<BaseLayout title="分类">
|
||||
|
||||
@@ -6,9 +6,9 @@ import PostCard from '@/components/PostCard.astro';
|
||||
import { getAllCategories, getAllPosts, getAllTags } from '@/lib/posts';
|
||||
import { getPageSlice } from '@/lib/pagination';
|
||||
|
||||
const posts = getAllPosts();
|
||||
const tags = getAllTags();
|
||||
const categories = getAllCategories();
|
||||
const posts = await getAllPosts();
|
||||
const tags = await getAllTags();
|
||||
const categories = await getAllCategories();
|
||||
const pageData = getPageSlice(posts, 1);
|
||||
---
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import { getAllCategories, getAllPosts, getAllTags } from '@/lib/posts';
|
||||
import { getPageSlice, getPaginationPaths } from '@/lib/pagination';
|
||||
|
||||
export function getStaticPaths() {
|
||||
const posts = getAllPosts();
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getAllPosts();
|
||||
|
||||
return getPaginationPaths(posts).map((page) => ({
|
||||
params: { page: String(page) },
|
||||
@@ -16,9 +16,9 @@ export function getStaticPaths() {
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const posts = getAllPosts();
|
||||
const tags = getAllTags();
|
||||
const categories = getAllCategories();
|
||||
const posts = await getAllPosts();
|
||||
const tags = await getAllTags();
|
||||
const categories = await getAllCategories();
|
||||
const pageData = getPageSlice(posts, page);
|
||||
---
|
||||
|
||||
|
||||
@@ -3,19 +3,20 @@ import ArticleSidebar from '@/components/ArticleSidebar.astro';
|
||||
import PostCopyrightCard from '@/components/PostCopyrightCard.astro';
|
||||
import TwikooComments from '@/components/TwikooComments.astro';
|
||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import { render } from 'astro:content';
|
||||
import { getAllPosts } from '@/lib/posts';
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getAllPosts().map((post) => ({
|
||||
export async function getStaticPaths() {
|
||||
return (await getAllPosts()).map((post) => ({
|
||||
params: { slug: post.slug },
|
||||
props: { post },
|
||||
}));
|
||||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { Content } = post;
|
||||
const allPosts = getAllPosts();
|
||||
const rawContent = post.rawContent?.() ?? '';
|
||||
const { Content } = await render(post.entry);
|
||||
const allPosts = await getAllPosts();
|
||||
const rawContent = post.body;
|
||||
const textContent = rawContent
|
||||
.replace(/```[\s\S]*?```/g, '')
|
||||
.replace(/<[^>]+>/g, '')
|
||||
@@ -69,7 +70,7 @@ function makeToc(raw: string) {
|
||||
});
|
||||
}
|
||||
|
||||
const toc = makeToc(post.rawContent?.() ?? '');
|
||||
const toc = makeToc(post.body);
|
||||
const relatedPosts = allPosts
|
||||
.filter((item) => item.slug !== post.slug)
|
||||
.filter((item) => item.categories.some((category) => post.categories.includes(category)))
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import rss from '@astrojs/rss';
|
||||
import { getAllPosts } from '@/lib/posts';
|
||||
|
||||
export function GET(context) {
|
||||
const posts = getAllPosts();
|
||||
export async function GET(context) {
|
||||
const posts = await getAllPosts();
|
||||
|
||||
return rss({
|
||||
title: "Bi's Blog",
|
||||
|
||||
@@ -4,15 +4,15 @@ import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||
import PostCard from '@/components/PostCard.astro';
|
||||
import { getAllPosts, getAllTags } from '@/lib/posts';
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getAllTags().map((tag) => ({
|
||||
export async function getStaticPaths() {
|
||||
return (await getAllTags()).map((tag) => ({
|
||||
params: { tag },
|
||||
props: { tag },
|
||||
}));
|
||||
}
|
||||
|
||||
const { tag } = Astro.props;
|
||||
const posts = getAllPosts().filter((post) => post.tags.includes(tag));
|
||||
const posts = (await getAllPosts()).filter((post) => post.tags.includes(tag));
|
||||
---
|
||||
|
||||
<BaseLayout title={tag}>
|
||||
|
||||
@@ -3,8 +3,8 @@ import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||
import { getAllPosts, getAllTags } from '@/lib/posts';
|
||||
|
||||
const posts = getAllPosts();
|
||||
const tags = getAllTags();
|
||||
const posts = await getAllPosts();
|
||||
const tags = await getAllTags();
|
||||
---
|
||||
|
||||
<BaseLayout title="标签">
|
||||
|
||||