paper-burner/js/process/download.js

191 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// process/download.js
/**
* 将所有成功处理的文档结果打包成一个 ZIP 文件并触发下载。
*
* 主要流程:
* 1. **筛选结果**:从 `allResultsData` 中筛选出没有错误、包含 Markdown 内容且未被跳过的成功处理结果。
* 2. **空结果检查**:如果没有成功的处理结果,则显示通知并退出。
* 3. **JSZip 依赖检查**:如果 `JSZip` 库未加载,则显示错误通知并退出。
* 4. **创建 ZIP 实例**:初始化一个新的 `JSZip` 对象。
* 5. **遍历并添加文件到 ZIP**
* - 对每个成功的处理结果:
* - 根据原始 PDF 文件名创建一个安全的文件夹名 (`safeFolderName`)。
* - 在 ZIP 内创建此文件夹。
* - 将处理得到的 Markdown 内容保存为 `document.md`。
* - 如果存在翻译内容 (`result.translation`)
* - 构建包含免责声明的翻译内容 (`contentToDownload`)。
* - 将其保存为 `translation.md`。
* - 如果存在图片数据 (`result.images`)
* - 在当前文件夹内创建一个 `images` 子文件夹。
* - 遍历图片数据将每张图片Base64 编码)保存为 PNG 文件到 `images` 文件夹中。
* - 对图片数据进行有效性检查,跳过无效数据并记录日志。
* - 捕获并记录添加图片到 ZIP 时的潜在错误。
* 6. **最终文件数检查**:如果最终没有文件被添加到 ZIP 包 (例如,所有结果都只有文件夹),则显示警告并退出。
* 7. **生成并下载 ZIP**
* - 使用 `zip.generateAsync` 以 DEFLATE 压缩方式生成 ZIP 文件的 Blob 数据。
* - 生成带时间戳的文件名 (如 `PaperBurner_Results_YYYY-MM-DDTHH-MM-SS-mmmZ.zip`)。
* - 使用 `saveAs` 函数 (FileSaver.js 提供) 触发浏览器下载该 Blob。
* - 如果 `saveAs` 未定义,则记录错误。
* 8. **错误处理**:捕获在创建或下载 ZIP 文件过程中可能发生的任何错误,并显示通知。
* 9. **日志记录**:在关键步骤通过 `addProgressLog` (如果可用) 输出日志。
*
* @param {Array<Object>} allResultsData - 包含所有文件处理结果的对象数组。
* 每个对象应包含 `file`, `error`, `markdown`, `translation`, `images`, `skipped` 等属性。
* @returns {Promise<void>} 函数没有显式返回值,主要副作用是触发文件下载。
*/
function sanitizeFileName(name) {
return (name || 'document').replace(/[\\/:*?"<>|]/g, '_');
}
function sanitizePath(path) {
return (path || '').split('/').map(segment => sanitizeFileName(segment)).filter(Boolean).join('/');
}
function removeExtension(name) {
if (!name) return '';
const idx = name.lastIndexOf('.');
return idx === -1 ? name : name.slice(0, idx);
}
function ensureFileName(baseName, ext) {
const sanitized = sanitizeFileName(baseName || 'document');
if (!ext) return sanitized;
if (sanitized.toLowerCase().endsWith(`.${ext.toLowerCase()}`)) {
return sanitized;
}
return `${sanitized}.${ext}`;
}
async function downloadAllResults(allResultsData) {
const successfulResults = allResultsData.filter(result => result && !result.error && result.markdown && !result.skipped);
if (successfulResults.length === 0) {
if (typeof showNotification === "function") {
showNotification('没有成功的处理结果可供下载', 'warning');
}
return;
}
if (typeof addProgressLog === "function") {
addProgressLog('开始打包下载结果...');
}
if (typeof JSZip === 'undefined') {
if (typeof showNotification === "function") {
showNotification('JSZip 加载失败,无法打包下载', 'error');
}
return;
}
const zip = new JSZip();
let filesAdded = 0;
for (const result of successfulResults) {
const relativePath = (result.relativePath || (result.file && result.file.pbxRelativePath) || (result.file && result.file.name) || 'document').replace(/\\/g, '/');
const dirPath = relativePath.includes('/') ? relativePath.slice(0, relativePath.lastIndexOf('/')) : '';
const baseName = relativePath.includes('/') ? relativePath.slice(relativePath.lastIndexOf('/') + 1) : relativePath;
const baseWithoutExt = removeExtension(baseName);
const sanitizedDir = sanitizePath(dirPath);
const sanitizedBase = sanitizeFileName(baseWithoutExt || 'document').substring(0, 120) || 'document';
const folderPath = sanitizedDir ? `${sanitizedDir}/${sanitizedBase}` : sanitizedBase;
const folder = zip.folder(folderPath);
folder.file('document.md', result.markdown);
if (result.translation) {
const currentDate = new Date().toISOString().split('T')[0];
const headerDeclaration = `> *本文档由 Paper Burner 工具制作 (${currentDate})。内容由 AI 大模型翻译生成,不保证翻译内容的准确性和完整性。*\n\n`;
const footerDeclaration = `\n\n---\n> *免责声明本文档内容由大模型API自动翻译生成Paper Burner 工具不对翻译内容的准确性、完整性和合法性负责。*`;
const contentToDownload = headerDeclaration + result.translation + footerDeclaration;
folder.file('translation.md', contentToDownload);
}
if (result.images && result.images.length > 0) {
const imagesFolder = folder.folder('images');
for (let i = 0; i < result.images.length; i++) {
const img = result.images[i];
try {
const raw = img.data || '';
const base64Data = raw.includes(',') ? raw.split(',')[1] : raw;
if (!base64Data) {
console.warn(`Skipping image ${img.id} in ${folderPath} due to missing data.`);
if (typeof addProgressLog === "function") {
addProgressLog(`警告: 跳过图片 ${img.id} (文件: ${folderPath}),数据缺失。`);
}
continue;
}
let filename = (img.name || img.id || `img-${i+1}.jpg`).toString();
// 确保有扩展名
if (!/\.[a-z0-9]+$/i.test(filename)) {
// 从 data URI 推断
const mime = (raw.split(';')[0] || '').replace(/^data:/, '').toLowerCase();
let ext = 'jpg';
if (mime.includes('png')) ext = 'png';
else if (mime.includes('gif')) ext = 'gif';
else if (mime.includes('webp')) ext = 'webp';
else if (mime.includes('bmp')) ext = 'bmp';
else if (mime.includes('svg')) ext = 'svg';
filename = `${filename}.${ext}`;
}
imagesFolder.file(filename, base64Data, { base64: true });
} catch (imgError) {
console.error(`Error adding image ${img.id} to zip for ${folderPath}:`, imgError);
if (typeof addProgressLog === "function") {
addProgressLog(`警告: 打包图片 ${img.id} (文件: ${folderPath}) 时出错: ${imgError.message}`);
}
}
}
}
filesAdded++;
}
if (filesAdded === 0) {
if (typeof showNotification === "function") {
showNotification('没有成功处理的文件可以打包下载', 'warning');
}
if (typeof addProgressLog === "function") {
addProgressLog('没有可打包的文件。');
}
return;
}
try {
if (typeof addProgressLog === "function") {
addProgressLog(`正在生成包含 ${filesAdded} 个文件结果的 ZIP 包...`);
}
const zipBlob = await zip.generateAsync({
type: 'blob',
compression: "DEFLATE",
compressionOptions: { level: 6 }
});
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
if (typeof saveAs === "function") {
saveAs(zipBlob, `PaperBurner_Results_${timestamp}.zip`);
if (typeof addProgressLog === "function") {
addProgressLog('ZIP 文件生成完毕,开始下载。');
}
} else {
console.error('saveAs 函数未定义,无法下载文件');
if (typeof addProgressLog === "function") {
addProgressLog('错误: saveAs 函数未定义,无法下载文件');
}
}
} catch (error) {
console.error('创建或下载 ZIP 文件失败:', error);
if (typeof showNotification === "function") {
showNotification('创建 ZIP 文件失败: ' + error.message, 'error');
}
if (typeof addProgressLog === "function") {
addProgressLog('错误: 创建 ZIP 文件失败 - ' + error.message);
}
}
}
// 将函数添加到processModule对象
if (typeof processModule !== 'undefined') {
processModule.downloadAllResults = downloadAllResults;
}