307 lines
8.9 KiB
JavaScript
307 lines
8.9 KiB
JavaScript
const fs = require("node:fs");
|
||
const path = require("node:path");
|
||
const { exec, spawn } = require("node:child_process");
|
||
const treeKill = require("tree-kill");
|
||
|
||
// 辅助函数:递归获取所有文件
|
||
const getAllFiles = (dirPath, arrayOfFiles) => {
|
||
const files = fs.readdirSync(dirPath);
|
||
|
||
arrayOfFiles = arrayOfFiles || [];
|
||
|
||
files.forEach((file) => {
|
||
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
|
||
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles);
|
||
} else {
|
||
arrayOfFiles.push(path.join(dirPath, "/", file));
|
||
}
|
||
});
|
||
|
||
return arrayOfFiles;
|
||
};
|
||
|
||
window.services = {
|
||
// --- File Service ---
|
||
|
||
// 选择目录
|
||
selectDirectory() {
|
||
try {
|
||
const result = window.utools.showOpenDialog({
|
||
title: "选择项目目录",
|
||
properties: ["openDirectory"],
|
||
});
|
||
return result;
|
||
} catch (error) {
|
||
console.error("选择目录失败:", error);
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// 读取目录结构 (单层,用于懒加载)
|
||
async readDirectory(dirPath) {
|
||
try {
|
||
if (!fs.existsSync(dirPath)) {
|
||
return [];
|
||
}
|
||
const items = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
||
return items.map((item) => ({
|
||
id: path.join(dirPath, item.name), // 使用路径作为 ID
|
||
name: item.name,
|
||
path: path.join(dirPath, item.name),
|
||
type: item.isDirectory() ? "directory" : "file",
|
||
children: item.isDirectory() ? [] : undefined, // 目录初始化为空数组,标记为可展开
|
||
}));
|
||
} catch (error) {
|
||
console.error("读取目录失败:", error);
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// 读取文件内容
|
||
async readFile(filePath) {
|
||
return await fs.promises.readFile(filePath, { encoding: "utf-8" });
|
||
},
|
||
|
||
// 写入文件内容
|
||
async writeFile(filePath, content) {
|
||
await fs.promises.writeFile(filePath, content, { encoding: "utf-8" });
|
||
return true;
|
||
},
|
||
|
||
// 创建文件夹
|
||
async createDirectory(dirPath) {
|
||
if (!fs.existsSync(dirPath)) {
|
||
await fs.promises.mkdir(dirPath, { recursive: true });
|
||
}
|
||
return true;
|
||
},
|
||
|
||
// 删除文件/文件夹
|
||
async deleteItem(itemPath) {
|
||
await fs.promises.rm(itemPath, { recursive: true, force: true });
|
||
return true;
|
||
},
|
||
|
||
// 重命名
|
||
async renameItem(oldPath, newPath) {
|
||
await fs.promises.rename(oldPath, newPath);
|
||
return true;
|
||
},
|
||
|
||
// 复制文件/文件夹
|
||
async copyItem(sourcePath, destPath) {
|
||
await fs.promises.cp(sourcePath, destPath, { recursive: true });
|
||
return true;
|
||
},
|
||
|
||
// 检查路径是否存在
|
||
pathExists(itemPath) {
|
||
return fs.existsSync(itemPath);
|
||
},
|
||
|
||
// 打开所在目录
|
||
openItemLocation(itemPath) {
|
||
window.utools.shellShowItemInFolder(itemPath);
|
||
},
|
||
|
||
// --- Image Service ---
|
||
|
||
// 保存图片
|
||
// imageData: base64 字符串
|
||
// mdFilePath: 当前 Markdown 文件的路径
|
||
async saveImage(imageData, mdFilePath) {
|
||
try {
|
||
const mdDir = path.dirname(mdFilePath);
|
||
const mdName = path.basename(mdFilePath, path.extname(mdFilePath));
|
||
const assetsDir = path.join(mdDir, mdName);
|
||
|
||
// 创建图片目录
|
||
if (!fs.existsSync(assetsDir)) {
|
||
await fs.promises.mkdir(assetsDir, { recursive: true });
|
||
}
|
||
|
||
// 生成文件名 (时间戳)
|
||
const timestamp = Date.now();
|
||
// 简单判断图片类型,默认 png
|
||
let ext = "png";
|
||
if (imageData.startsWith("data:image/jpeg")) ext = "jpg";
|
||
else if (imageData.startsWith("data:image/gif")) ext = "gif";
|
||
else if (imageData.startsWith("data:image/webp")) ext = "webp";
|
||
|
||
const imageName = `${timestamp}.${ext}`;
|
||
const imagePath = path.join(assetsDir, imageName);
|
||
|
||
// 写入文件 (去掉 base64 头部)
|
||
const base64Data = imageData.replace(/^data:image\/\w+;base64,/, "");
|
||
await fs.promises.writeFile(imagePath, base64Data, { encoding: "base64" });
|
||
|
||
// 返回相对路径,用于 Markdown 引用
|
||
// 使用 forward slashes for markdown compatibility
|
||
return `./${mdName}/${imageName}`;
|
||
} catch (error) {
|
||
console.error("保存图片失败:", error);
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// --- Git Service ---
|
||
|
||
// 执行 Git 命令
|
||
async execGit(command, cwd) {
|
||
return new Promise((resolve, reject) => {
|
||
exec(`git ${command}`, { cwd }, (error, stdout, stderr) => {
|
||
if (error) {
|
||
// git status 即使有变动有时也会返回非0,需要特殊处理,但通常 error 是命令执行失败
|
||
// 这里简单返回
|
||
resolve({ success: false, error: error.message, stderr });
|
||
} else {
|
||
resolve({ success: true, stdout });
|
||
}
|
||
});
|
||
});
|
||
},
|
||
|
||
async isGitRepository(dirPath) {
|
||
const gitDir = path.join(dirPath, ".git");
|
||
return fs.existsSync(gitDir);
|
||
},
|
||
|
||
async gitStatus(dirPath) {
|
||
return this.execGit("status --short", dirPath);
|
||
},
|
||
|
||
async gitAdd(dirPath, files = ".") {
|
||
return this.execGit(`add ${files}`, dirPath);
|
||
},
|
||
|
||
async gitCommit(dirPath, message) {
|
||
// 简单处理 message 中的引号
|
||
const safeMessage = message.replace(/"/g, '\\"');
|
||
return this.execGit(`commit -m "${safeMessage}"`, dirPath);
|
||
},
|
||
|
||
async gitPush(dirPath) {
|
||
return this.execGit("push", dirPath);
|
||
},
|
||
|
||
async gitPull(dirPath) {
|
||
return this.execGit("pull", dirPath);
|
||
},
|
||
|
||
// --- Search Service ---
|
||
|
||
// 搜索文件 (文件名)
|
||
async searchFileName(rootDir, keyword) {
|
||
try {
|
||
const allFiles = [];
|
||
const traverse = async (currentDir) => {
|
||
const items = await fs.promises.readdir(currentDir, { withFileTypes: true });
|
||
for (const item of items) {
|
||
const fullPath = path.join(currentDir, item.name);
|
||
if (item.isDirectory()) {
|
||
if (item.name === ".git" || item.name === "node_modules") continue; // 忽略特定目录
|
||
await traverse(fullPath);
|
||
} else {
|
||
if (item.name.toLowerCase().includes(keyword.toLowerCase())) {
|
||
allFiles.push({
|
||
name: item.name,
|
||
path: fullPath,
|
||
type: "file",
|
||
});
|
||
}
|
||
}
|
||
}
|
||
};
|
||
await traverse(rootDir);
|
||
return allFiles;
|
||
} catch (error) {
|
||
console.error("搜索文件名失败:", error);
|
||
return [];
|
||
}
|
||
},
|
||
|
||
// 搜索内容
|
||
async searchContent(rootDir, keyword, useRegex = false) {
|
||
try {
|
||
const results = [];
|
||
const traverse = async (currentDir) => {
|
||
const items = await fs.promises.readdir(currentDir, { withFileTypes: true });
|
||
for (const item of items) {
|
||
const fullPath = path.join(currentDir, item.name);
|
||
if (item.isDirectory()) {
|
||
if (item.name === ".git" || item.name === "node_modules") continue;
|
||
await traverse(fullPath);
|
||
} else if (item.isFile() && item.name.endsWith(".md")) { // 仅搜索 markdown 文件
|
||
const content = await fs.promises.readFile(fullPath, "utf-8");
|
||
let match = false;
|
||
let matches = [];
|
||
|
||
if (useRegex) {
|
||
try {
|
||
const regex = new RegExp(keyword, "g");
|
||
let m;
|
||
while ((m = regex.exec(content)) !== null) {
|
||
match = true;
|
||
matches.push({ index: m.index, match: m[0] });
|
||
if (matches.length > 10) break; // 限制每个文件的匹配数
|
||
}
|
||
} catch (e) {
|
||
// 正则错误,忽略
|
||
}
|
||
} else {
|
||
if (content.includes(keyword)) {
|
||
match = true;
|
||
// 简单定位第一个匹配
|
||
const index = content.indexOf(keyword);
|
||
matches.push({ index, match: keyword });
|
||
}
|
||
}
|
||
|
||
if (match) {
|
||
results.push({
|
||
name: item.name,
|
||
path: fullPath,
|
||
matches,
|
||
preview: content.substring(0, 200) + "..." // 简单预览
|
||
});
|
||
}
|
||
}
|
||
}
|
||
};
|
||
await traverse(rootDir);
|
||
return results;
|
||
} catch (error) {
|
||
console.error("搜索内容失败:", error);
|
||
return [];
|
||
}
|
||
},
|
||
|
||
// --- Config Service ---
|
||
|
||
// 读取配置文件 (.md-helper.json)
|
||
async loadConfig(rootDir) {
|
||
const configPath = path.join(rootDir, ".md-helper.json");
|
||
try {
|
||
if (fs.existsSync(configPath)) {
|
||
const content = await fs.promises.readFile(configPath, "utf-8");
|
||
return JSON.parse(content);
|
||
}
|
||
} catch (e) {
|
||
console.error("读取配置失败", e);
|
||
}
|
||
return null;
|
||
},
|
||
|
||
// 保存配置文件
|
||
async saveConfig(rootDir, config) {
|
||
const configPath = path.join(rootDir, ".md-helper.json");
|
||
try {
|
||
await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
||
return true;
|
||
} catch (e) {
|
||
console.error("保存配置失败", e);
|
||
return false;
|
||
}
|
||
}
|
||
};
|