157 lines
3.6 KiB
TypeScript
157 lines
3.6 KiB
TypeScript
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
import { relative } from "path";
|
|
import { execSync } from "child_process";
|
|
import {
|
|
buildDeckComponent,
|
|
titleFromCsvPath,
|
|
defaultMdPath,
|
|
readDeckConfig,
|
|
generateDeckPreviewMd,
|
|
} from "./deck-utils.js";
|
|
|
|
/**
|
|
* 预览 Deck 的参数
|
|
*/
|
|
export interface PreviewDeckParams {
|
|
/**
|
|
* CSV 文件路径
|
|
*/
|
|
csv_file: string;
|
|
/**
|
|
* Markdown 文件路径(可选,默认与 CSV 同名)
|
|
*/
|
|
md_file?: string;
|
|
/**
|
|
* 标题(可选,默认从 CSV 文件名推断)
|
|
*/
|
|
title?: string;
|
|
/**
|
|
* 描述(可选)
|
|
*/
|
|
description?: string;
|
|
}
|
|
|
|
/**
|
|
* 预览 Deck 的结果
|
|
*/
|
|
export interface PreviewDeckResult {
|
|
success: boolean;
|
|
message: string;
|
|
md_file?: string;
|
|
created?: boolean;
|
|
preview_url?: string;
|
|
}
|
|
|
|
/**
|
|
* 打开浏览器
|
|
*/
|
|
function openBrowser(url: string) {
|
|
try {
|
|
if (process.platform === "win32") {
|
|
execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
} else if (process.platform === "darwin") {
|
|
execSync(`open "${url}"`, { stdio: "ignore" });
|
|
} else {
|
|
execSync(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
}
|
|
console.error(`已打开浏览器:${url}`);
|
|
} catch (error) {
|
|
console.error("打开浏览器失败:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 计算预览 URL
|
|
*/
|
|
function computePreviewUrl(mdFilePath: string): string {
|
|
const relativeMdPath = relative(process.cwd(), mdFilePath)
|
|
.split("\\")
|
|
.join("/")
|
|
.replace(/\.md$/, "");
|
|
return `http://localhost:3000/${relativeMdPath}`;
|
|
}
|
|
|
|
/**
|
|
* 预览 Deck - 保存 Markdown 并打开浏览器
|
|
*/
|
|
export function previewDeck(params: PreviewDeckParams): PreviewDeckResult {
|
|
const { csv_file, md_file, title, description } = params;
|
|
|
|
// 检查 CSV 文件是否存在
|
|
if (!existsSync(csv_file)) {
|
|
return {
|
|
success: false,
|
|
message: `CSV 文件不存在:${csv_file}`,
|
|
};
|
|
}
|
|
|
|
// 确定 Markdown 文件路径
|
|
const mdFilePath = md_file || defaultMdPath(csv_file);
|
|
const created = !existsSync(mdFilePath);
|
|
|
|
// 如果文件已存在,检查是否已有 md-deck 组件
|
|
if (!created) {
|
|
try {
|
|
const existingContent = readFileSync(mdFilePath, "utf-8");
|
|
if (
|
|
existingContent.includes(`:md-deck[${csv_file}]`) ||
|
|
existingContent.includes(`:md-deck[./${csv_file.split("/").pop()}]`)
|
|
) {
|
|
const previewUrl = computePreviewUrl(mdFilePath);
|
|
openBrowser(previewUrl);
|
|
return {
|
|
success: true,
|
|
message: `预览文件 ${mdFilePath} 已存在`,
|
|
md_file: mdFilePath,
|
|
created: false,
|
|
preview_url: previewUrl,
|
|
};
|
|
}
|
|
} catch {
|
|
// 读取失败,继续创建
|
|
}
|
|
}
|
|
|
|
// 读取 CSV 的 frontmatter 获取配置
|
|
const deckConfig = readDeckConfig(csv_file);
|
|
|
|
// 确定标题
|
|
const deckTitle = title || titleFromCsvPath(csv_file);
|
|
|
|
// 构建 md-deck 组件代码
|
|
const deckComponent = buildDeckComponent(
|
|
`./${csv_file.split("/").pop()}`,
|
|
deckConfig,
|
|
);
|
|
|
|
// 生成 Markdown 内容
|
|
const mdContent = generateDeckPreviewMd(
|
|
deckTitle,
|
|
deckComponent,
|
|
description,
|
|
);
|
|
|
|
// 写入文件
|
|
try {
|
|
writeFileSync(mdFilePath, mdContent, "utf-8");
|
|
|
|
const previewUrl = computePreviewUrl(mdFilePath);
|
|
openBrowser(previewUrl);
|
|
|
|
return {
|
|
success: true,
|
|
message: created
|
|
? `创建预览文件 ${mdFilePath}`
|
|
: `更新预览文件 ${mdFilePath}`,
|
|
md_file: mdFilePath,
|
|
created,
|
|
preview_url: previewUrl,
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
message: `写入失败:${error instanceof Error ? error.message : "未知错误"}`,
|
|
};
|
|
}
|
|
}
|