191 lines
9.2 KiB
JavaScript
191 lines
9.2 KiB
JavaScript
// 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;
|
||
}
|