2025-03-03 21:16:16 +08:00
|
|
|
|
// 1. 从 `astro:content` 导入工具函数
|
|
|
|
|
import { defineCollection, z } from 'astro:content';
|
|
|
|
|
import { glob } from 'astro/loaders';
|
|
|
|
|
import fs from 'node:fs';
|
|
|
|
|
import path from 'node:path';
|
|
|
|
|
|
|
|
|
|
// 2. 定义内容结构接口
|
|
|
|
|
export interface ContentStructure {
|
|
|
|
|
articles: string[];
|
|
|
|
|
sections: SectionStructure[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface SectionStructure {
|
|
|
|
|
name: string;
|
|
|
|
|
path: string;
|
|
|
|
|
articles: string[];
|
|
|
|
|
sections: SectionStructure[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 辅助函数:获取相对于content目录的路径
|
|
|
|
|
export function getRelativePath(fullPath: string, basePath = './src/content'): string {
|
|
|
|
|
// 统一路径分隔符
|
|
|
|
|
const normalizedPath = fullPath.replace(/\\/g, '/');
|
|
|
|
|
const normalizedBasePath = basePath.replace(/\\/g, '/');
|
|
|
|
|
|
|
|
|
|
// 移除基础路径
|
|
|
|
|
let relativePath = normalizedPath;
|
|
|
|
|
|
|
|
|
|
// 如果路径包含基础路径,则移除它
|
|
|
|
|
if (normalizedPath.includes(normalizedBasePath)) {
|
|
|
|
|
relativePath = normalizedPath.replace(normalizedBasePath, '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 移除开头的斜杠
|
|
|
|
|
relativePath = relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
|
|
|
|
|
|
|
|
|
|
// 如果路径以articles/开头,移除它(适配Astro内容集合)
|
|
|
|
|
if (relativePath.startsWith('articles/')) {
|
|
|
|
|
relativePath = relativePath.substring('articles/'.length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return relativePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 辅助函数:从文件路径中提取文件名(不带扩展名)
|
|
|
|
|
export function getBasename(filePath: string): string {
|
|
|
|
|
// 统一路径分隔符
|
|
|
|
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
|
|
|
|
|
|
|
|
// 分割路径并获取最后一部分(文件名)
|
|
|
|
|
const parts = normalizedPath.split('/');
|
|
|
|
|
const fileName = parts[parts.length - 1];
|
|
|
|
|
|
|
|
|
|
// 移除扩展名
|
|
|
|
|
const basename = fileName.replace(/\.(md|mdx)$/, '');
|
|
|
|
|
|
|
|
|
|
return basename;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 辅助函数:从文件路径中提取目录路径
|
|
|
|
|
export function getDirPath(filePath: string, basePath = './src/content'): string {
|
|
|
|
|
const basename = getBasename(filePath);
|
|
|
|
|
const relativePath = getRelativePath(filePath, basePath);
|
|
|
|
|
|
|
|
|
|
// 移除文件名部分,获取目录路径
|
|
|
|
|
const dirPath = relativePath.replace(`${basename}.md`, '').replace(/\/$/, '');
|
|
|
|
|
|
|
|
|
|
return dirPath;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-09 01:11:43 +08:00
|
|
|
|
// 辅助函数:获取原始文件路径(移除特殊前缀)
|
|
|
|
|
export function getOriginalPath(specialPath: string): string {
|
|
|
|
|
// 检查路径是否包含特殊前缀
|
|
|
|
|
const parts = specialPath.split('/');
|
|
|
|
|
const fileName = parts[parts.length - 1];
|
|
|
|
|
|
|
|
|
|
// 如果文件名以下划线开头,移除它
|
|
|
|
|
if (fileName.startsWith('_')) {
|
|
|
|
|
const originalFileName = fileName.substring(1);
|
|
|
|
|
const newParts = [...parts.slice(0, -1), originalFileName];
|
|
|
|
|
return newParts.join('/');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return specialPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 辅助函数:获取特殊文件路径(添加特殊前缀)
|
|
|
|
|
export function getSpecialPath(originalPath: string): string {
|
|
|
|
|
// 检查文件名是否与其所在目录名相同或包含目录名
|
|
|
|
|
const parts = originalPath.split('/');
|
|
|
|
|
const fileName = parts[parts.length - 1].replace(/\.md$/, '');
|
|
|
|
|
|
|
|
|
|
// 如果文件名与目录名相同或以目录名开头,则在文件名前添加特殊前缀
|
|
|
|
|
if (parts.length > 1) {
|
|
|
|
|
const dirName = parts[parts.length - 2];
|
|
|
|
|
if (fileName === dirName || fileName.startsWith(dirName)) {
|
|
|
|
|
// 创建一个新的路径,在文件名前添加下划线前缀
|
|
|
|
|
const newFileName = fileName.startsWith('_') ? fileName : `_${fileName}`;
|
|
|
|
|
const fileExt = originalPath.endsWith('.md') ? '.md' : '';
|
|
|
|
|
const newParts = [...parts.slice(0, -1), newFileName + fileExt];
|
|
|
|
|
return newParts.join('/');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return originalPath;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-03 21:16:16 +08:00
|
|
|
|
// 3. 定义目录结构处理函数
|
|
|
|
|
function getContentStructure(contentDir = './src/content', basePath = './src/content'): ContentStructure {
|
|
|
|
|
// 检查目录是否存在
|
|
|
|
|
if (!fs.existsSync(contentDir)) {
|
|
|
|
|
return { articles: [], sections: [] };
|
|
|
|
|
}
|
|
|
|
|
// 获取目录下的所有文件和文件夹
|
|
|
|
|
const items = fs.readdirSync(contentDir, { withFileTypes: true });
|
|
|
|
|
|
|
|
|
|
// 分离文章和目录
|
|
|
|
|
const articles = items
|
|
|
|
|
.filter(item => item.isFile() && item.name.endsWith('.md'))
|
|
|
|
|
.map(item => {
|
|
|
|
|
// 生成相对于content目录的路径,用于在页面中查找文章
|
|
|
|
|
const fullPath = path.join(contentDir, item.name);
|
2025-03-09 01:11:43 +08:00
|
|
|
|
// 将路径转换为相对于content目录的格式,并移除basePath
|
|
|
|
|
const relativePath = fullPath.replace(basePath, '').replace(/^[\/\\]/, '');
|
|
|
|
|
|
|
|
|
|
// 检查文件名是否与其所在目录名相同或包含目录名
|
|
|
|
|
const pathParts = relativePath.split(/[\/\\]/);
|
|
|
|
|
const fileName = pathParts[pathParts.length - 1].replace(/\.md$/, '');
|
|
|
|
|
|
|
|
|
|
// 如果文件名与目录名相同或以目录名开头,则在文件名前添加特殊前缀
|
|
|
|
|
if (pathParts.length > 1) {
|
|
|
|
|
const dirName = pathParts[pathParts.length - 2];
|
|
|
|
|
if (fileName === dirName || fileName.startsWith(dirName)) {
|
|
|
|
|
// 创建一个新的路径,在文件名前添加下划线前缀
|
|
|
|
|
const newFileName = `_${fileName}.md`;
|
|
|
|
|
const newPathParts = [...pathParts.slice(0, -1), newFileName];
|
|
|
|
|
return newPathParts.join('/');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return relativePath.replace(/\\/g, '/');
|
2025-03-03 21:16:16 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 获取子目录(作为章节)
|
|
|
|
|
const sections: SectionStructure[] = items
|
|
|
|
|
.filter(item => item.isDirectory())
|
|
|
|
|
.map(item => {
|
|
|
|
|
const sectionPath = path.join(contentDir, item.name);
|
|
|
|
|
// 递归获取子目录的结构
|
|
|
|
|
const sectionContent: ContentStructure = getContentStructure(sectionPath, basePath);
|
|
|
|
|
|
2025-03-09 01:11:43 +08:00
|
|
|
|
// 确保路径格式正确,并移除basePath
|
|
|
|
|
const relativePath = sectionPath.replace(basePath, '').replace(/^[\/\\]/, '');
|
|
|
|
|
const normalizedPath = relativePath.replace(/\\/g, '/');
|
2025-03-03 21:16:16 +08:00
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
name: item.name,
|
|
|
|
|
path: normalizedPath,
|
|
|
|
|
articles: sectionContent.articles,
|
|
|
|
|
sections: sectionContent.sections
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return { articles, sections };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 定义你的集合
|
|
|
|
|
const articles = defineCollection({
|
|
|
|
|
// 使用glob加载器从content目录加载所有markdown文件
|
2025-03-09 01:11:43 +08:00
|
|
|
|
loader: glob({
|
|
|
|
|
pattern: "**/*.md",
|
|
|
|
|
base: "./src/content"
|
|
|
|
|
}),
|
2025-03-03 21:16:16 +08:00
|
|
|
|
schema: z.object({
|
|
|
|
|
title: z.string(),
|
|
|
|
|
date: z.date(),
|
|
|
|
|
tags: z.array(z.string()).optional(),
|
|
|
|
|
summary: z.string().optional(),
|
|
|
|
|
image: z.string().optional(),
|
|
|
|
|
author: z.string().optional(),
|
|
|
|
|
draft: z.boolean().optional().default(false),
|
|
|
|
|
// 添加section字段,用于标识文章所属的目录
|
|
|
|
|
section: z.string().optional(),
|
|
|
|
|
// 添加weight字段,用于排序
|
|
|
|
|
weight: z.number().optional(),
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 6. 导出一个 `collections` 对象来注册你的集合
|
2025-03-09 01:11:43 +08:00
|
|
|
|
export const collections = { articles };
|
2025-03-03 21:16:16 +08:00
|
|
|
|
|
|
|
|
|
// 7. 导出内容结构,可以在构建时使用
|
|
|
|
|
export const contentStructure = getContentStructure();
|