vitepress-theme-base-teek
Version:
172 lines (150 loc) • 5.6 kB
text/typescript
import { SiteConfig } from "vitepress";
import { TkContentData, Post } from "./types";
import {
filterPosts,
getSortPostsByDateAndSticky,
getSortPostsByDate,
getGroupPosts,
getGroupCards,
groupByYear,
groupByYearMonth,
} from "./helper";
import { formatDate } from "../helper/date";
import matter from "gray-matter";
import type { FileContentLoaderData } from "vitepress-plugin-file-content-loader";
import { basename, join } from "node:path";
import { statSync } from "node:fs";
// !该文件只在 node 环境运行,无法直接在浏览器环境运行,因此浏览器环境的代码不要引入该文件
/**
* 从 md 文件中读取一级标题
* @param mdContent md 文件内容
*/
const getTitleFromMd = (mdContent: string) => {
// 切割换行符 \r\n 或 \n
const lines = mdContent.trimStart().split(/\r?\n/);
for (const line of lines) {
if (line.startsWith("# ")) {
return line.substring(2).trim();
}
}
return undefined;
};
/**
* 转换为文章数据
*/
export const transformData = (data: FileContentLoaderData): TkContentData => {
const siteConfig: SiteConfig = (globalThis as any).VITEPRESS_CONFIG;
const { themeConfig } = siteConfig.userConfig;
const { frontmatter, url } = data;
if (frontmatter.date) frontmatter.date = formatDate(frontmatter.date);
return {
url: frontmatter.permalink || url,
frontmatter: frontmatter,
author: themeConfig.author,
title: getTitle(data),
date: getDate(data, siteConfig.srcDir),
capture: getCaptureText(data),
};
};
/**
* 转换为各个文章不同类型的数据
*/
export const transformRaw = (posts: TkContentData[]): Post => {
const siteConfig: SiteConfig = (globalThis as any).VITEPRESS_CONFIG;
const { locales = {} } = siteConfig.userConfig;
const postsData = resolvePosts(posts);
const localesKeys = Object.keys(locales);
// 没有配置国际化,则返回所有 posts 数据
if (!localesKeys.length) return postsData;
// 国际化处理,计算每个语言目录下的 posts 数据
const postsLocale: Record<string, Post> = {};
localesKeys
.filter(localesKey => localesKey !== "root")
.forEach(localesKey => {
const localePosts = posts.filter(post => post.url.startsWith(`/${localesKey}`));
postsLocale[localesKey] = resolvePosts(localePosts);
});
// root 处理
const rootPosts = posts.filter(post => !localesKeys.some(localesKey => post.url.startsWith(`/${localesKey}`)));
postsLocale["root"] = resolvePosts(rootPosts);
return { ...postsData, locales: postsLocale };
};
const resolvePosts = (posts: TkContentData[]): Post => {
const originPosts = filterPosts(posts);
const sortPostsByDateAndSticky = getSortPostsByDateAndSticky(originPosts);
const sortPostsByDate = getSortPostsByDate(originPosts);
const groupPostsByYear = groupByYear(sortPostsByDate);
const groupPostsByYearMonth = groupByYearMonth(sortPostsByDate);
const groupPosts = getGroupPosts(sortPostsByDateAndSticky);
const groupCards = getGroupCards(groupPosts);
return {
originPosts,
sortPostsByDateAndSticky,
sortPostsByDate,
groupPostsByYear,
groupPostsByYearMonth,
groupPosts,
groupCards,
};
};
/**
* 获取文章标题,获取顺序:frontmatter.title > md 文件开头的一级标题 > 文件名
*
* @param post 文章数据
*/
// @ts-ignore
export function getTitle(post: RequiredKeyPartialOther<TkContentData, "frontmatter" | "url">) {
if (post.frontmatter.title) return post.frontmatter.title;
const { content = "" } = matter(post.src || "", {});
const splitName = basename(post.url).split(".");
// 如果目录下有 index.md 且没有一级标题,则使用目录名作为文章标题
const name = splitName.length > 1 ? splitName[1] : splitName[0];
return getTitleFromMd(content) || name || "";
}
/**
* 获取文章时间,获取顺序:frontmatter.date > 文件创建时间 > 当前时间
*
* @param post 文章数据
* @param srcDir 项目绝对路径
*/
// @ts-ignore
export function getDate(post: RequiredKeyPartialOther<TkContentData, "frontmatter" | "url">, srcDir: string) {
const { frontmatter, url } = post;
if (frontmatter.date) return frontmatter.date;
// 如果目录下面有 index.md,则 url 不是目录名/index,而是目录名/,因此通过后面的 / 来补 index.md
const filePath = join(srcDir, `${url.endsWith("/") ? `${url}/index` : url.replace(/\.html$/, "")}.md`);
const stat = statSync(filePath);
return formatDate(stat.birthtime || stat.atime);
}
/**
* 截取 markdown 文件前 count 数的内容
*/
export const getCaptureText = (post: TkContentData, count = 200) => {
const { content = "" } = matter(post.src || "", {});
return (
content
// 首个标题
?.replace(/^#+\s+.*/, "")
// 除去标题
?.replace(/#/g, "")
// 除去图片
?.replace(/!\[.*?\]\(.*?\)/g, "")
// 除去链接
?.replace(/\[(.*?)\]\(.*?\)/g, "$1")
// 除去加粗
?.replace(/\*\*(.*?)\*\*/g, "$1")
// 除去 [[TOC]]
?.replace(/\[\[TOC\]\]/g, "")
// 去除 ::: 及其后面的内容
?.replace(/:::.*?(\n|$)/g, "")
?.replace(/<!-- more -->/g, "")
?.split("\n")
?.filter(v => !!v)
?.join("\n")
?.replace(/>(.*)/, "")
?.replace(/</g, "<")
.replace(/>/g, ">")
?.trim()
?.slice(0, count)
);
};