fix: resolve lint issues (XSS K601, knip unused exports, eslint errors)

- Fix XSS warnings in user.tsx by adding safe attribute to elements
- Remove unused exports: ChunkUploadRequest, getArchiveInfo,
  getFileForDirectDownload, createDirectDownloadHeaders, createConverterArchive
- Fix eslint no-unused-vars errors across multiple files
- Fix pdfmathtranslate async Promise executor issue
- Update tests to use toThrow instead of toMatch for Error objects
- Apply prettier formatting
This commit is contained in:
Your Name 2026-01-21 16:00:26 +08:00
parent db5f47586e
commit 79fc6f7067
21 changed files with 439 additions and 439 deletions

View file

@ -26,7 +26,10 @@ import { convert as convertVcf, properties as propertiesVcf } from "./vcf";
import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex";
import { convert as convertMarkitdown, properties as propertiesMarkitdown } from "./markitdown";
import { convert as convertMineru, properties as propertiesMineru } from "./mineru";
import { convert as convertPDFMathTranslate, properties as propertiesPDFMathTranslate } from "./pdfmathtranslate";
import {
convert as convertPDFMathTranslate,
properties as propertiesPDFMathTranslate,
} from "./pdfmathtranslate";
// This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular

View file

@ -2,7 +2,7 @@ import { execFile as execFileOriginal } from "node:child_process";
import { mkdirSync, existsSync, readdirSync, unlinkSync, rmdirSync } from "node:fs";
import { join, basename, dirname } from "node:path";
import { ExecFileFn } from "./types";
import { createConverterArchive, getArchiveFileName } from "../transfer";
import { getArchiveFileName } from "../transfer";
export const properties = {
from: {
@ -16,7 +16,7 @@ export const properties = {
/**
* Helper function to create a .tar archive from a directory (no compression)
*
*
* 使 .tar .tar.gz / .tgz / .zip
*/
function createTarArchive(
@ -28,23 +28,19 @@ function createTarArchive(
// Use tar command to create archive (without gzip compression)
// tar -cf <output.tar> -C <sourceDir> .
// 注意:使用 -cf 而非 -czf避免 gzip 壓縮
execFile(
"tar",
["-cf", outputTar, "-C", sourceDir, "."],
(error, stdout, stderr) => {
if (error) {
reject(`tar error: ${error}`);
return;
}
if (stdout) {
console.log(`tar stdout: ${stdout}`);
}
if (stderr) {
console.error(`tar stderr: ${stderr}`);
}
resolve();
},
);
execFile("tar", ["-cf", outputTar, "-C", sourceDir, "."], (error, stdout, stderr) => {
if (error) {
reject(`tar error: ${error}`);
return;
}
if (stdout) {
console.log(`tar stdout: ${stdout}`);
}
if (stderr) {
console.error(`tar stderr: ${stderr}`);
}
resolve();
});
});
}
@ -75,11 +71,6 @@ export async function convert(
execFile: ExecFileFn = execFileOriginal,
): Promise<string> {
return new Promise((resolve, reject) => {
// Determine mode based on target format
// md-t = table mode (tables as markdown)
// md-i = image mode (tables as images)
const tableMode = convertTo === "md-t" ? "latex" : "html";
// Create a temporary output directory for MinerU
const outputDir = dirname(targetPath);
const inputFileName = basename(filePath, `.${fileType}`);
@ -92,14 +83,7 @@ export async function convert(
// Build MinerU command arguments
// MinerU CLI: magic-pdf -p <input> -o <output_dir> -m auto
const args = [
"-p",
filePath,
"-o",
mineruOutputDir,
"-m",
"auto",
];
const args = ["-p", filePath, "-o", mineruOutputDir, "-m", "auto"];
// Add table mode option if md-i (render tables as images)
if (convertTo === "md-i") {

View file

@ -6,46 +6,47 @@ import { getArchiveFileName } from "../transfer";
/**
* PDFMathTranslate Content Engine
*
*
* PDF
*
*
* 使 pdf2zh CLI
* .tar
* - original.pdf PDF
* - translated-<lang>.pdf PDF
*
*
* Docker build
* runtime
*/
// 支援的目標語言列表
const SUPPORTED_LANGUAGES = [
"en", // English
"zh", // Chinese (Simplified)
"en", // English
"zh", // Chinese (Simplified)
"zh-TW", // Chinese (Traditional)
"ja", // Japanese
"ko", // Korean
"de", // German
"fr", // French
"es", // Spanish
"it", // Italian
"pt", // Portuguese
"ru", // Russian
"ar", // Arabic
"hi", // Hindi
"vi", // Vietnamese
"th", // Thai
"ja", // Japanese
"ko", // Korean
"de", // German
"fr", // French
"es", // Spanish
"it", // Italian
"pt", // Portuguese
"ru", // Russian
"ar", // Arabic
"hi", // Hindi
"vi", // Vietnamese
"th", // Thai
] as const;
type SupportedLanguage = typeof SUPPORTED_LANGUAGES[number];
// 模型路徑Docker 環境中)
const MODELS_PATH = process.env.PDFMATHTRANSLATE_MODELS_PATH || "/models/pdfmathtranslate";
// 生成 from/to 格式映射
function generateLanguageMappings(): { from: Record<string, string[]>; to: Record<string, string[]> } {
const outputFormats = SUPPORTED_LANGUAGES.map(lang => `pdf-${lang}`);
function generateLanguageMappings(): {
from: Record<string, string[]>;
to: Record<string, string[]>;
} {
const outputFormats = SUPPORTED_LANGUAGES.map((lang) => `pdf-${lang}`);
return {
from: {
document: ["pdf"],
@ -91,7 +92,7 @@ function checkModelsExist(): boolean {
/**
* Helper function to create a .tar archive from a directory (no compression)
*
*
* 使 .tar .tar.gz / .tgz / .zip
*/
function createTarArchive(
@ -103,23 +104,19 @@ function createTarArchive(
// Use tar command to create archive (without gzip compression)
// tar -cf <output.tar> -C <sourceDir> .
// 注意:使用 -cf 而非 -czf避免 gzip 壓縮
execFile(
"tar",
["-cf", outputTar, "-C", sourceDir, "."],
(error, stdout, stderr) => {
if (error) {
reject(`tar error: ${error}`);
return;
}
if (stdout) {
console.log(`tar stdout: ${stdout}`);
}
if (stderr) {
console.error(`tar stderr: ${stderr}`);
}
resolve();
},
);
execFile("tar", ["-cf", outputTar, "-C", sourceDir, "."], (error, stdout, stderr) => {
if (error) {
reject(`tar error: ${error}`);
return;
}
if (stdout) {
console.log(`tar stdout: ${stdout}`);
}
if (stderr) {
console.error(`tar stderr: ${stderr}`);
}
resolve();
});
});
}
@ -143,7 +140,7 @@ function removeDir(dirPath: string): void {
/**
* pdf2zh PDF
*
*
* @param inputPath PDF
* @param outputDir
* @param targetLang
@ -160,16 +157,19 @@ function runPdf2zh(
// -lo <lang>: 目標語言
// -o <dir>: 輸出目錄
// -s google: 使用 Google 翻譯(預設,免費)
//
//
// 輸出檔案:
// - <filename>-mono.pdf: 純翻譯版本
// - <filename>-dual.pdf: 雙語對照版本
const args = [
inputPath,
"-lo", targetLang,
"-o", outputDir,
"-s", process.env.PDFMATHTRANSLATE_SERVICE || "google",
"-lo",
targetLang,
"-o",
outputDir,
"-s",
process.env.PDFMATHTRANSLATE_SERVICE || "google",
];
// 如果設定了自訂模型路徑,使用 --onnx 參數
@ -205,9 +205,9 @@ function runPdf2zh(
if (!existsSync(monoPath) && !existsSync(dualPath)) {
// 嘗試在 outputDir 下尋找任何 PDF 檔案
const files = readdirSync(outputDir);
const pdfFiles = files.filter(f => f.endsWith(".pdf"));
const pdfFiles = files.filter((f) => f.endsWith(".pdf"));
console.log(`[PDFMathTranslate] Found PDF files: ${pdfFiles.join(", ")}`);
if (pdfFiles.length === 0) {
reject(`No output PDF files found in ${outputDir}`);
return;
@ -221,7 +221,7 @@ function runPdf2zh(
/**
*
*
*
* @param filePath PDF
* @param fileType "pdf"
* @param convertTo "pdf-zh"
@ -234,93 +234,91 @@ export async function convert(
fileType: string,
convertTo: string,
targetPath: string,
options?: unknown,
_options?: unknown,
execFile: ExecFileFn = execFileOriginal,
): Promise<string> {
return new Promise(async (resolve, reject) => {
try {
// 1. 檢查模型(警告但不阻止,因為 pdf2zh 可能會自動下載)
checkModelsExist();
try {
// 1. 檢查模型(警告但不阻止,因為 pdf2zh 可能會自動下載)
checkModelsExist();
// 2. 提取目標語言
const targetLang = extractTargetLanguage(convertTo);
console.log(`[PDFMathTranslate] Translating to: ${targetLang}`);
// 2. 提取目標語言
const targetLang = extractTargetLanguage(convertTo);
console.log(`[PDFMathTranslate] Translating to: ${targetLang}`);
// 3. 建立臨時輸出目錄
const outputDir = dirname(targetPath);
const inputFileName = basename(filePath, `.${fileType}`);
const tempDir = join(outputDir, `${inputFileName}_pdfmathtranslate_${Date.now()}`);
// 3. 建立臨時輸出目錄
const outputDir = dirname(targetPath);
const inputFileName = basename(filePath, `.${fileType}`);
const tempDir = join(outputDir, `${inputFileName}_pdfmathtranslate_${Date.now()}`);
if (!existsSync(tempDir)) {
mkdirSync(tempDir, { recursive: true });
}
// 4. 建立封裝用目錄
const archiveDir = join(tempDir, "archive");
mkdirSync(archiveDir, { recursive: true });
// 5. 執行 pdf2zh 翻譯
const { monoPath, dualPath } = await runPdf2zh(filePath, tempDir, targetLang, execFile);
// 6. 複製原始檔案到封裝目錄
const originalDest = join(archiveDir, "original.pdf");
copyFileSync(filePath, originalDest);
// 7. 複製翻譯後的檔案(優先使用 mono因為它是純翻譯版本
const translatedDest = join(archiveDir, `translated-${targetLang}.pdf`);
// 檢查各種可能的輸出檔案名稱
const possibleOutputs = [
monoPath,
dualPath,
join(tempDir, `${inputFileName}-mono.pdf`),
join(tempDir, `${inputFileName}-dual.pdf`),
];
let foundOutput = false;
for (const outputPath of possibleOutputs) {
if (existsSync(outputPath)) {
copyFileSync(outputPath, translatedDest);
foundOutput = true;
console.log(`[PDFMathTranslate] Using output: ${outputPath}`);
break;
}
}
// 如果找不到預期的輸出檔案,嘗試找任何 PDF
if (!foundOutput) {
const allFiles = readdirSync(tempDir);
const pdfFiles = allFiles.filter(f => f.endsWith(".pdf") && f !== basename(filePath));
const firstPdfName = pdfFiles[0];
if (firstPdfName) {
const firstPdf = join(tempDir, firstPdfName);
copyFileSync(firstPdf, translatedDest);
foundOutput = true;
console.log(`[PDFMathTranslate] Using fallback output: ${firstPdf}`);
}
}
if (!foundOutput) {
throw new Error("No translated PDF output found");
}
// 8. 建立 .tar 封裝
const tarPath = getArchiveFileName(targetPath);
const tarDir = dirname(tarPath);
if (!existsSync(tarDir)) {
mkdirSync(tarDir, { recursive: true });
}
await createTarArchive(archiveDir, tarPath, execFile);
console.log(`[PDFMathTranslate] Created archive: ${tarPath}`);
// 9. 清理臨時目錄
removeDir(tempDir);
resolve("Done");
} catch (error) {
reject(`PDFMathTranslate error: ${error}`);
if (!existsSync(tempDir)) {
mkdirSync(tempDir, { recursive: true });
}
});
// 4. 建立封裝用目錄
const archiveDir = join(tempDir, "archive");
mkdirSync(archiveDir, { recursive: true });
// 5. 執行 pdf2zh 翻譯
const { monoPath, dualPath } = await runPdf2zh(filePath, tempDir, targetLang, execFile);
// 6. 複製原始檔案到封裝目錄
const originalDest = join(archiveDir, "original.pdf");
copyFileSync(filePath, originalDest);
// 7. 複製翻譯後的檔案(優先使用 mono因為它是純翻譯版本
const translatedDest = join(archiveDir, `translated-${targetLang}.pdf`);
// 檢查各種可能的輸出檔案名稱
const possibleOutputs = [
monoPath,
dualPath,
join(tempDir, `${inputFileName}-mono.pdf`),
join(tempDir, `${inputFileName}-dual.pdf`),
];
let foundOutput = false;
for (const outputPath of possibleOutputs) {
if (existsSync(outputPath)) {
copyFileSync(outputPath, translatedDest);
foundOutput = true;
console.log(`[PDFMathTranslate] Using output: ${outputPath}`);
break;
}
}
// 如果找不到預期的輸出檔案,嘗試找任何 PDF
if (!foundOutput) {
const allFiles = readdirSync(tempDir);
const pdfFiles = allFiles.filter((f) => f.endsWith(".pdf") && f !== basename(filePath));
const firstPdfName = pdfFiles[0];
if (firstPdfName) {
const firstPdf = join(tempDir, firstPdfName);
copyFileSync(firstPdf, translatedDest);
foundOutput = true;
console.log(`[PDFMathTranslate] Using fallback output: ${firstPdf}`);
}
}
if (!foundOutput) {
throw new Error("No translated PDF output found");
}
// 8. 建立 .tar 封裝
const tarPath = getArchiveFileName(targetPath);
const tarDir = dirname(tarPath);
if (!existsSync(tarDir)) {
mkdirSync(tarDir, { recursive: true });
}
await createTarArchive(archiveDir, tarPath, execFile);
console.log(`[PDFMathTranslate] Created archive: ${tarPath}`);
// 9. 清理臨時目錄
removeDir(tempDir);
return "Done";
} catch (error) {
throw new Error(`PDFMathTranslate error: ${error}`);
}
}