235 lines
7.3 KiB
JavaScript
235 lines
7.3 KiB
JavaScript
import {
|
|
AlreadyExistsError,
|
|
BadRequestError,
|
|
NotFoundError,
|
|
ValidationError,
|
|
resolveNotebookPath
|
|
} from "./chunk-ER4KPD22.js";
|
|
import {
|
|
asyncHandler,
|
|
createApiModule,
|
|
defineApiModule,
|
|
defineEndpoints,
|
|
successResponse
|
|
} from "./chunk-74TMTGBG.js";
|
|
|
|
// shared/modules/recycle-bin/api.ts
|
|
var RECYCLE_BIN_ENDPOINTS = defineEndpoints({
|
|
list: { path: "/", method: "GET" },
|
|
restore: { path: "/restore", method: "POST" },
|
|
permanent: { path: "/permanent", method: "DELETE" },
|
|
empty: { path: "/empty", method: "DELETE" }
|
|
});
|
|
|
|
// shared/modules/recycle-bin/index.ts
|
|
var RECYCLE_BIN_MODULE = defineApiModule({
|
|
id: "recycle-bin",
|
|
name: "\u56DE\u6536\u7AD9",
|
|
basePath: "/recycle-bin",
|
|
order: 40,
|
|
version: "1.0.0",
|
|
endpoints: RECYCLE_BIN_ENDPOINTS
|
|
});
|
|
|
|
// api/modules/recycle-bin/routes.ts
|
|
import express from "express";
|
|
import fs2 from "fs/promises";
|
|
import path2 from "path";
|
|
|
|
// api/modules/recycle-bin/recycleBinService.ts
|
|
import fs from "fs/promises";
|
|
import path from "path";
|
|
async function restoreFile(srcPath, destPath, deletedDate, year, month, day) {
|
|
const { fullPath: imagesDir } = resolveNotebookPath(`images/${year}/${month}/${day}`);
|
|
let content = await fs.readFile(srcPath, "utf-8");
|
|
const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
let match;
|
|
const imageReplacements = [];
|
|
while ((match = imageRegex.exec(content)) !== null) {
|
|
const imagePath = match[2];
|
|
const imageName = path.basename(imagePath);
|
|
const rbImageName = `${deletedDate}_${imageName}`;
|
|
const { fullPath: srcImagePath } = resolveNotebookPath(`RB/${rbImageName}`);
|
|
try {
|
|
await fs.access(srcImagePath);
|
|
await fs.mkdir(imagesDir, { recursive: true });
|
|
const destImagePath = path.join(imagesDir, imageName);
|
|
await fs.rename(srcImagePath, destImagePath);
|
|
const newImagePath = `images/${year}/${month}/${day}/${imageName}`;
|
|
imageReplacements.push({ oldPath: imagePath, newPath: newImagePath });
|
|
} catch {
|
|
}
|
|
}
|
|
for (const { oldPath, newPath } of imageReplacements) {
|
|
content = content.replace(new RegExp(oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), newPath);
|
|
}
|
|
await fs.writeFile(destPath, content, "utf-8");
|
|
await fs.unlink(srcPath);
|
|
}
|
|
async function restoreFolder(srcPath, destPath, deletedDate, year, month, day) {
|
|
await fs.mkdir(destPath, { recursive: true });
|
|
const entries = await fs.readdir(srcPath, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const srcEntryPath = path.join(srcPath, entry.name);
|
|
const destEntryPath = path.join(destPath, entry.name);
|
|
if (entry.isDirectory()) {
|
|
await restoreFolder(srcEntryPath, destEntryPath, deletedDate, year, month, day);
|
|
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
await restoreFile(srcEntryPath, destEntryPath, deletedDate, year, month, day);
|
|
} else {
|
|
await fs.rename(srcEntryPath, destEntryPath);
|
|
}
|
|
}
|
|
const remaining = await fs.readdir(srcPath);
|
|
if (remaining.length === 0) {
|
|
await fs.rmdir(srcPath);
|
|
}
|
|
}
|
|
|
|
// api/modules/recycle-bin/routes.ts
|
|
var router = express.Router();
|
|
router.get(
|
|
"/",
|
|
asyncHandler(async (req, res) => {
|
|
const { fullPath: rbDir } = resolveNotebookPath("RB");
|
|
try {
|
|
await fs2.access(rbDir);
|
|
} catch {
|
|
successResponse(res, { groups: [] });
|
|
return;
|
|
}
|
|
const entries = await fs2.readdir(rbDir, { withFileTypes: true });
|
|
const items = [];
|
|
for (const entry of entries) {
|
|
const match = entry.name.match(/^(\d{8})_(.+)$/);
|
|
if (!match) continue;
|
|
const [, dateStr, originalName] = match;
|
|
if (entry.isDirectory()) {
|
|
items.push({
|
|
name: entry.name,
|
|
originalName,
|
|
type: "dir",
|
|
deletedDate: dateStr,
|
|
path: `RB/${entry.name}`
|
|
});
|
|
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
items.push({
|
|
name: entry.name,
|
|
originalName,
|
|
type: "file",
|
|
deletedDate: dateStr,
|
|
path: `RB/${entry.name}`
|
|
});
|
|
}
|
|
}
|
|
const groupedMap = /* @__PURE__ */ new Map();
|
|
for (const item of items) {
|
|
const existing = groupedMap.get(item.deletedDate) || [];
|
|
existing.push(item);
|
|
groupedMap.set(item.deletedDate, existing);
|
|
}
|
|
const groups = Array.from(groupedMap.entries()).map(([date, items2]) => ({
|
|
date,
|
|
items: items2.sort((a, b) => a.originalName.localeCompare(b.originalName))
|
|
})).sort((a, b) => b.date.localeCompare(a.date));
|
|
successResponse(res, { groups });
|
|
})
|
|
);
|
|
router.post(
|
|
"/restore",
|
|
asyncHandler(async (req, res) => {
|
|
const { path: relPath, type } = req.body;
|
|
if (!relPath || !type) {
|
|
throw new ValidationError("Path and type are required");
|
|
}
|
|
const { fullPath: itemPath } = resolveNotebookPath(relPath);
|
|
try {
|
|
await fs2.access(itemPath);
|
|
} catch {
|
|
throw new NotFoundError("Item not found in recycle bin");
|
|
}
|
|
const match = path2.basename(itemPath).match(/^(\d{8})_(.+)$/);
|
|
if (!match) {
|
|
throw new BadRequestError("Invalid recycle bin item name");
|
|
}
|
|
const [, dateStr, originalName] = match;
|
|
const year = dateStr.substring(0, 4);
|
|
const month = dateStr.substring(4, 6);
|
|
const day = dateStr.substring(6, 8);
|
|
const { fullPath: markdownsDir } = resolveNotebookPath("markdowns");
|
|
await fs2.mkdir(markdownsDir, { recursive: true });
|
|
const destPath = path2.join(markdownsDir, originalName);
|
|
const existing = await fs2.stat(destPath).catch(() => null);
|
|
if (existing) {
|
|
throw new AlreadyExistsError("A file or folder with this name already exists");
|
|
}
|
|
if (type === "dir") {
|
|
await restoreFolder(itemPath, destPath, dateStr, year, month, day);
|
|
} else {
|
|
await restoreFile(itemPath, destPath, dateStr, year, month, day);
|
|
}
|
|
successResponse(res, null);
|
|
})
|
|
);
|
|
router.delete(
|
|
"/permanent",
|
|
asyncHandler(async (req, res) => {
|
|
const { path: relPath, type } = req.body;
|
|
if (!relPath || !type) {
|
|
throw new ValidationError("Path and type are required");
|
|
}
|
|
const { fullPath: itemPath } = resolveNotebookPath(relPath);
|
|
try {
|
|
await fs2.access(itemPath);
|
|
} catch {
|
|
throw new NotFoundError("Item not found in recycle bin");
|
|
}
|
|
if (type === "dir") {
|
|
await fs2.rm(itemPath, { recursive: true, force: true });
|
|
} else {
|
|
await fs2.unlink(itemPath);
|
|
}
|
|
successResponse(res, null);
|
|
})
|
|
);
|
|
router.delete(
|
|
"/empty",
|
|
asyncHandler(async (req, res) => {
|
|
const { fullPath: rbDir } = resolveNotebookPath("RB");
|
|
try {
|
|
await fs2.access(rbDir);
|
|
} catch {
|
|
successResponse(res, null);
|
|
return;
|
|
}
|
|
const entries = await fs2.readdir(rbDir, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const entryPath = path2.join(rbDir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
await fs2.rm(entryPath, { recursive: true, force: true });
|
|
} else {
|
|
await fs2.unlink(entryPath);
|
|
}
|
|
}
|
|
successResponse(res, null);
|
|
})
|
|
);
|
|
var routes_default = router;
|
|
|
|
// api/modules/recycle-bin/index.ts
|
|
var createRecycleBinModule = () => {
|
|
return createApiModule(RECYCLE_BIN_MODULE, {
|
|
routes: (_container) => {
|
|
return routes_default;
|
|
}
|
|
});
|
|
};
|
|
var recycle_bin_default = createRecycleBinModule;
|
|
|
|
export {
|
|
restoreFile,
|
|
restoreFolder,
|
|
createRecycleBinModule,
|
|
recycle_bin_default
|
|
};
|