This commit is contained in:
Han
2025-02-12 10:09:30 +08:00
parent 8deb0fe01e
commit d116e8fcd1
912 changed files with 5761 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
import { getCollection } from "astro:content";
// 格式化文章列表
const fmtArticleList = (articleList: any) => {
// 按年份分类
const groupedByYear = articleList.reduce((acc: any, item: any) => {
const year = item.data.date.getFullYear();
// 初始化
!acc[year] && (acc[year] = []);
acc[year].push(item.data);
return acc;
}, {});
// 转换为目标格式
return Object.keys(groupedByYear).map(year => ({ name: parseInt(year), data: groupedByYear[year] })).reverse();
}
// 获取分类下的文章列表
const getCategoriesList = async (categories: string) => {
const posts = await getCollection("blog");
const articleList = posts.filter((i: any) => i.data.categories == categories).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());;
return fmtArticleList(articleList);
}
// 获取标签下的文章列表
const getTagsList = async (tags: string) => {
const posts = await getCollection("blog");
const articleList = posts.filter((i: any) => i.data.tags.includes(tags)).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());;
return fmtArticleList(articleList);
}
// 获取归档列表
const getArchiveList = async () => {
const posts = await getCollection("blog");
const articleList = posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());;
return fmtArticleList(articleList);
}
export { getCategoriesList, getTagsList, getArchiveList };
+67
View File
@@ -0,0 +1,67 @@
import path from 'path';
import _fs from 'fs';
const fs = _fs.promises;
import { fileURLToPath } from 'url';
// 获取当前模块的目录路径
const __filename = fileURLToPath(import.meta.url); // 当前文件的绝对路径
const __dirname = path.dirname(__filename); // 当前文件所在的目录
// 支持的图片扩展名
const SUPPORTED_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']);
/**
* 获取目录中的所有图片文件
* @param {string} dir - 图片目录路径
* @returns {Promise<string[]>} - 图片文件名数组
*/
async function getImageFiles(dir: string) {
try {
const files = await fs.readdir(dir);
return files.filter(file => SUPPORTED_EXTENSIONS.has(path.extname(file).toLowerCase()));
} catch (error) {
console.error(`无法读取目录 ${dir}:`, error);
return [];
}
}
/**
* 随机打乱数组(Fisher-Yates 洗牌算法)
* @param {any[]} array - 需要打乱的数组
* @returns {any[]} - 打乱后的数组
*/
function shuffleArray(array: any[]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
/**
* 创建一个图片名称的无限迭代器
* @param {string} dir - 图片目录路径
* @returns {AsyncGenerator<string>} - 异步生成器
*/
async function* createImageIterator(dir: string) {
let images = await getImageFiles(dir);
if (images.length === 0) {
throw new Error(`目录 ${dir} 中没有图片文件`);
}
while (true) {
images = shuffleArray([...images]); // 打乱顺序
for (const image of images) {
yield image;
}
}
}
const targetDir = path.resolve(__dirname, '../../public/assets/images/banner/'); // 目标目录
const fileIter = createImageIterator(targetDir);
const getCover = async (filename: string | null | undefined) => {
if (filename) return filename;
const { value } = await fileIter.next();
return `/assets/images/banner/${value}`
}
export default getCover;
+17
View File
@@ -0,0 +1,17 @@
import { execSync } from "child_process";
const COMMITER_DATE_ISO_FORMAT = "%cI";
export default function getFileCreateTime(filePath: string, type: string): Date {
try {
const result = execSync(`git log --pretty=format:"${COMMITER_DATE_ISO_FORMAT}" ${filePath}`).toString();
if (!result) {
throw new Error('Git command failed to execute');
}
const commits = result.split(/\r?\n/g);
return new Date(commits[type == 'create' ? commits.length - 1 : 0]);
} catch (e) {
// not committed yet
return new Date();
}
}
+31
View File
@@ -0,0 +1,31 @@
import { getCollection } from "astro:content";
// 获取封面图
import getCover from "../utils/getCover";
const posts = (await getCollection("blog")).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
// 获取文章分类
const getCategories = () => {
const categoriesList = posts.reduce((acc: any, i: any) => {
acc[i.data.categories] = (acc[i.data.categories] || 0) + 1;
return acc;
}, {});
return Object.entries(categoriesList).map(([title, count]) => ({ title, count }));
}
// 获取文章标签
const getTags = () => {
const frequencyMap = new Map();
// 统计每个元素出现的频率
posts.forEach(i => {
i.data.tags.forEach(tag => frequencyMap.set(tag, (frequencyMap.get(tag) || 0) + 1));
});
const sortedArray = [...frequencyMap.entries()].sort((a, b) => b[1] - a[1]);
return sortedArray.slice(0, 16).map(item => item[0]);
}
// 获取推荐文章 (给文章添加 recommend: true 字段)
const getRecommendArticles = () => {
const recommendList = posts.filter(i => i.data.recommend).slice(0, 6);
return (recommendList.length > 0 ? recommendList : posts).slice(0, 6).map(async i => ({ title: i.data.title, date: i.data.date, id: i.data.id, cover: await getCover(i.data.cover) }))
};
export { getCategories, getTags, getRecommendArticles };
+92
View File
@@ -0,0 +1,92 @@
import dayjs from "dayjs";
// 获取文章的描述
const getDescription = (post: any, num: number = 150) => (post.rendered ? post.rendered.html.replace(/<[^>]+>/g, "").replace(/\s+/g, "") : post.body.replace(/\n/g, "").replace(/#/g, "")).slice(0, num) || '暂无简介'
//处理时间
const fmtTime = (time: any, fmt: string = 'MMMM D, YYYY') => dayjs(time).format(fmt)
// 处理日期
const fmtDate = (time: any) => {
const past = dayjs(time);
const now = dayjs();
const duration = now.diff(past, 'seconds');
if (duration < 60) {
return `${duration} 秒前`;
} else if (duration < 60 * 60) {
const minutes = Math.floor(duration / 60);
return `${minutes} 分钟前`;
} else if (duration < 60 * 60 * 24) {
const hours = Math.floor(duration / (60 * 60));
return `${hours} 小时前`;
} else if (duration < 60 * 60 * 24 * 30) {
const days = Math.floor(duration / (60 * 60 * 24));
return `${days} 天前`;
} else if (duration < 60 * 60 * 24 * 30 * 12) {
const months = Math.floor(duration / (60 * 60 * 24 * 30));
return `${months} 个月前`;
} else {
const years = Math.floor(duration / (60 * 60 * 24 * 30 * 12));
return `${years} 年前`;
}
}
// 处理页码展示
const fmtPage = (page: string | undefined) => page ? page.replace(/\//g, '') : null
// 加载外部脚本
const LoadScript = (src: string): Promise<HTMLScriptElement> => {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
document.head.appendChild(script);
});
}
// 加载外部CSS
const LoadStyle = (href: string): Promise<HTMLLinkElement> => {
return new Promise((resolve, reject) => {
const link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = href;
link.onload = () => resolve(link); // CSS 加载成功
link.onerror = () => reject(new Error(`Failed to load CSS: ${href}`)); // CSS 加载失败
document.head.appendChild(link); // 将 <link> 添加到文档中
});
}
// 请求封装
const $GET = async (url: string, headers: Record<string, string> = {}): Promise<any> => {
try {
const res = await fetch(url, {
method: "GET",
headers: headers,
});
if (!res.ok) {
throw new Error(`HTTP error! Status: ${res.status}`);
}
return res.json();
} catch (error) {
console.error("GET request failed:", error);
throw error; // 抛出错误以便调用者处理
}
};
const $POST = async (url: string, data: Record<string, any>, headers: Record<string, string> = {}): Promise<any> => {
try {
const res = await fetch(url, {
method: "POST",
headers: { ...headers, },
body: JSON.stringify(data),
});
if (!res.ok) {
throw new Error(`HTTP error! Status: ${res.status}`);
}
return res.json(); // 解析 JSON 数据
} catch (error) {
console.error("POST request failed:", error);
throw error; // 抛出错误以便调用者处理
}
};
export { $GET, $POST, getDescription, fmtTime, fmtDate, fmtPage, LoadScript, LoadStyle }
+17
View File
@@ -0,0 +1,17 @@
type RouterEventKey = "beforeCreate" | "created" | "beforeMount" | "mounted" | "afterMount";
type RouterEventName = "astro:before-preparation" | "astro:after-preparation" | "astro:before-swap" | "astro:after-swap" | "astro:page-load";
type EventHandler = (event: Event) => void;
// 路由事件映射
const routerFn: Record<RouterEventKey, RouterEventName> = { beforeCreate: 'astro:before-preparation', created: 'astro:after-preparation', beforeMount: 'astro:before-swap', mounted: 'astro:after-swap', afterMount: 'astro:page-load' };
const updateRouter = (key: RouterEventKey, handler: EventHandler) => {
const eventName = routerFn[key];
if (!eventName) {
throw new Error(`Invalid key ${key} for router update. Valid keys are: ${Object.keys(routerFn).join(", ")}.`);
}
document.removeEventListener(eventName, handler);
document.addEventListener(eventName, handler);
};
export default updateRouter;
+30
View File
@@ -0,0 +1,30 @@
import fs from 'fs/promises';
import path from 'path';
import * as cheerio from 'cheerio';
const setSearchJson = async (posts: any[]) => {
const searchIndex = posts.map(i => {
const $ = cheerio.load(`<body>${i.rendered.html}</body>`);
return {
title: i.data.title,
url: `/article/${i.data.id}`,
content: `${i.data.title} - ` + $('body').text().replace(/\n/g, '').replace(/<[^>]+>/g, '')
};
});
try {
await fs.writeFile(
path.join(process.cwd(), 'dist', 'vh-search.json'),
JSON.stringify(searchIndex)
);
await fs.writeFile(
path.join(process.cwd(), 'public', 'vh-search.json'),
JSON.stringify(searchIndex)
);
console.log('\x1b[32m%s\x1b[0m', '搜索文件vh-search文件已生成 successfully');
} catch (error) {
console.error('Error writing search index file:', error);
}
};
export default setSearchJson;