feat: implement PDF attachment editing functionality with web worker support
- Added a new worker script to handle editing of embedded attachments in PDF files. - Created TypeScript definitions for message structures and response types related to attachment editing. - Updated the main logic to utilize the worker for improved performance and responsiveness during attachment management. - Integrated the editing feature into the UI, allowing users to view, remove, or replace attachments in their PDFs. - Enhanced error handling and user feedback during the editing process.
This commit is contained in:
42
public/workers/edit-attachments.worker.d.ts
vendored
Normal file
42
public/workers/edit-attachments.worker.d.ts
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
declare const coherentpdf: typeof import('../../src/types/coherentpdf.global').coherentpdf;
|
||||||
|
|
||||||
|
interface GetAttachmentsMessage {
|
||||||
|
command: 'get-attachments';
|
||||||
|
fileBuffer: ArrayBuffer;
|
||||||
|
fileName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditAttachmentsMessage {
|
||||||
|
command: 'edit-attachments';
|
||||||
|
fileBuffer: ArrayBuffer;
|
||||||
|
fileName: string;
|
||||||
|
attachmentsToRemove: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type EditAttachmentsWorkerMessage = GetAttachmentsMessage | EditAttachmentsMessage;
|
||||||
|
|
||||||
|
interface GetAttachmentsSuccessResponse {
|
||||||
|
status: 'success';
|
||||||
|
attachments: Array<{ index: number; name: string; page: number; data: ArrayBuffer }>;
|
||||||
|
fileName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetAttachmentsErrorResponse {
|
||||||
|
status: 'error';
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditAttachmentsSuccessResponse {
|
||||||
|
status: 'success';
|
||||||
|
modifiedPDF: ArrayBuffer;
|
||||||
|
fileName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditAttachmentsErrorResponse {
|
||||||
|
status: 'error';
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAttachmentsResponse = GetAttachmentsSuccessResponse | GetAttachmentsErrorResponse;
|
||||||
|
type EditAttachmentsResponse = EditAttachmentsSuccessResponse | EditAttachmentsErrorResponse;
|
||||||
|
type EditAttachmentsWorkerResponse = GetAttachmentsResponse | EditAttachmentsResponse;
|
||||||
151
public/workers/edit-attachments.worker.js
Normal file
151
public/workers/edit-attachments.worker.js
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
self.importScripts('/coherentpdf.browser.min.js');
|
||||||
|
|
||||||
|
function getAttachmentsFromPDFInWorker(fileBuffer, fileName) {
|
||||||
|
try {
|
||||||
|
const uint8Array = new Uint8Array(fileBuffer);
|
||||||
|
|
||||||
|
let pdf;
|
||||||
|
try {
|
||||||
|
pdf = coherentpdf.fromMemory(uint8Array, '');
|
||||||
|
} catch (error) {
|
||||||
|
self.postMessage({
|
||||||
|
status: 'error',
|
||||||
|
message: `Failed to load PDF: ${fileName}. Error: ${error.message || error}`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
coherentpdf.startGetAttachments(pdf);
|
||||||
|
const attachmentCount = coherentpdf.numberGetAttachments();
|
||||||
|
|
||||||
|
if (attachmentCount === 0) {
|
||||||
|
self.postMessage({
|
||||||
|
status: 'success',
|
||||||
|
attachments: [],
|
||||||
|
fileName: fileName
|
||||||
|
});
|
||||||
|
coherentpdf.deletePdf(pdf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachments = [];
|
||||||
|
for (let i = 0; i < attachmentCount; i++) {
|
||||||
|
try {
|
||||||
|
const name = coherentpdf.getAttachmentName(i);
|
||||||
|
const page = coherentpdf.getAttachmentPage(i);
|
||||||
|
const attachmentData = coherentpdf.getAttachmentData(i);
|
||||||
|
|
||||||
|
const dataArray = new Uint8Array(attachmentData);
|
||||||
|
const buffer = dataArray.buffer.slice(dataArray.byteOffset, dataArray.byteOffset + dataArray.byteLength);
|
||||||
|
|
||||||
|
attachments.push({
|
||||||
|
index: i,
|
||||||
|
name: String(name),
|
||||||
|
page: Number(page),
|
||||||
|
data: buffer
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to get attachment ${i} from ${fileName}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coherentpdf.endGetAttachments();
|
||||||
|
coherentpdf.deletePdf(pdf);
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
status: 'success',
|
||||||
|
attachments: attachments,
|
||||||
|
fileName: fileName
|
||||||
|
};
|
||||||
|
|
||||||
|
const transferBuffers = attachments.map(att => att.data);
|
||||||
|
self.postMessage(response, transferBuffers);
|
||||||
|
} catch (error) {
|
||||||
|
self.postMessage({
|
||||||
|
status: 'error',
|
||||||
|
message: error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error occurred during attachment listing.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function editAttachmentsInPDFInWorker(fileBuffer, fileName, attachmentsToRemove) {
|
||||||
|
try {
|
||||||
|
const uint8Array = new Uint8Array(fileBuffer);
|
||||||
|
|
||||||
|
let pdf;
|
||||||
|
try {
|
||||||
|
pdf = coherentpdf.fromMemory(uint8Array, '');
|
||||||
|
} catch (error) {
|
||||||
|
self.postMessage({
|
||||||
|
status: 'error',
|
||||||
|
message: `Failed to load PDF: ${fileName}. Error: ${error.message || error}`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachmentsToRemove && attachmentsToRemove.length > 0) {
|
||||||
|
coherentpdf.startGetAttachments(pdf);
|
||||||
|
const attachmentCount = coherentpdf.numberGetAttachments();
|
||||||
|
const attachmentsToKeep = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < attachmentCount; i++) {
|
||||||
|
if (!attachmentsToRemove.includes(i)) {
|
||||||
|
const name = coherentpdf.getAttachmentName(i);
|
||||||
|
const page = coherentpdf.getAttachmentPage(i);
|
||||||
|
const data = coherentpdf.getAttachmentData(i);
|
||||||
|
|
||||||
|
const dataCopy = new Uint8Array(data.length);
|
||||||
|
dataCopy.set(new Uint8Array(data));
|
||||||
|
|
||||||
|
attachmentsToKeep.push({
|
||||||
|
name: String(name),
|
||||||
|
page: Number(page),
|
||||||
|
data: dataCopy
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coherentpdf.endGetAttachments();
|
||||||
|
|
||||||
|
coherentpdf.removeAttachedFiles(pdf);
|
||||||
|
|
||||||
|
for (const attachment of attachmentsToKeep) {
|
||||||
|
if (attachment.page === 0) {
|
||||||
|
coherentpdf.attachFileFromMemory(attachment.data, attachment.name, pdf);
|
||||||
|
} else {
|
||||||
|
coherentpdf.attachFileToPageFromMemory(attachment.data, attachment.name, pdf, attachment.page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifiedBytes = coherentpdf.toMemory(pdf, false, true);
|
||||||
|
coherentpdf.deletePdf(pdf);
|
||||||
|
|
||||||
|
const buffer = modifiedBytes.buffer.slice(modifiedBytes.byteOffset, modifiedBytes.byteOffset + modifiedBytes.byteLength);
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
status: 'success',
|
||||||
|
modifiedPDF: buffer,
|
||||||
|
fileName: fileName
|
||||||
|
};
|
||||||
|
|
||||||
|
self.postMessage(response, [response.modifiedPDF]);
|
||||||
|
} catch (error) {
|
||||||
|
self.postMessage({
|
||||||
|
status: 'error',
|
||||||
|
message: error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error occurred during attachment editing.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onmessage = (e) => {
|
||||||
|
if (e.data.command === 'get-attachments') {
|
||||||
|
getAttachmentsFromPDFInWorker(e.data.fileBuffer, e.data.fileName);
|
||||||
|
} else if (e.data.command === 'edit-attachments') {
|
||||||
|
editAttachmentsInPDFInWorker(e.data.fileBuffer, e.data.fileName, e.data.attachmentsToRemove);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -324,12 +324,12 @@ export const categories = [
|
|||||||
icon: 'download',
|
icon: 'download',
|
||||||
subtitle: 'Extract all embedded files from PDF(s) as a ZIP.',
|
subtitle: 'Extract all embedded files from PDF(s) as a ZIP.',
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// id: 'edit-attachments',
|
id: 'edit-attachments',
|
||||||
// name: 'Edit Attachments',
|
name: 'Edit Attachments',
|
||||||
// icon: 'file-edit',
|
icon: 'file-edit',
|
||||||
// subtitle: 'View, remove, or replace attachments in your PDF.',
|
subtitle: 'View or remove attachments in your PDF.',
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
href: '/src/pages/pdf-multi-tool.html',
|
href: '/src/pages/pdf-multi-tool.html',
|
||||||
name: 'PDF Multi Tool',
|
name: 'PDF Multi Tool',
|
||||||
|
|||||||
@@ -1,207 +1,218 @@
|
|||||||
// TODO@ALAM - USE CPDF HERE
|
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||||
|
import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||||
|
import { state } from '../state.js';
|
||||||
|
|
||||||
// import { showLoader, hideLoader, showAlert } from '../ui.js';
|
const worker = new Worker('/workers/edit-attachments.worker.js');
|
||||||
// import { downloadFile, readFileAsArrayBuffer } from '../utils/helpers.js';
|
|
||||||
// import { state } from '../state.js';
|
|
||||||
// import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
|
||||||
|
|
||||||
// let currentAttachments: Array<{ name: string; index: number; size: number }> = [];
|
let allAttachments: Array<{ index: number; name: string; page: number; data: Uint8Array }> = [];
|
||||||
// let attachmentsToRemove: Set<number> = new Set();
|
let attachmentsToRemove: Set<number> = new Set();
|
||||||
// let attachmentsToReplace: Map<number, File> = new Map();
|
|
||||||
|
|
||||||
// export async function setupEditAttachmentsTool() {
|
export async function setupEditAttachmentsTool() {
|
||||||
// const optionsDiv = document.getElementById('edit-attachments-options');
|
const optionsDiv = document.getElementById('edit-attachments-options');
|
||||||
// if (!optionsDiv || !state.pdfDoc) return;
|
if (!optionsDiv || !state.files || state.files.length === 0) return;
|
||||||
|
|
||||||
// optionsDiv.classList.remove('hidden');
|
optionsDiv.classList.remove('hidden');
|
||||||
// await loadAttachmentsList();
|
await loadAttachmentsList();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// async function loadAttachmentsList() {
|
async function loadAttachmentsList() {
|
||||||
// const attachmentsList = document.getElementById('attachments-list');
|
const attachmentsList = document.getElementById('attachments-list');
|
||||||
// if (!attachmentsList || !state.pdfDoc) return;
|
if (!attachmentsList || !state.files || state.files.length === 0) return;
|
||||||
|
|
||||||
// attachmentsList.innerHTML = '';
|
attachmentsList.innerHTML = '';
|
||||||
// currentAttachments = [];
|
attachmentsToRemove.clear();
|
||||||
// attachmentsToRemove.clear();
|
allAttachments = [];
|
||||||
// attachmentsToReplace.clear();
|
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// // Get embedded files from PDF
|
showLoader('Loading attachments...');
|
||||||
// const embeddedFiles = state.pdfDoc.context.enumerateIndirectObjects()
|
|
||||||
// .filter(([ref, obj]: any) => {
|
|
||||||
// const dict = obj instanceof PDFLibDocument.context.dict ? obj : null;
|
|
||||||
// return dict && dict.get('Type')?.toString() === '/Filespec';
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (embeddedFiles.length === 0) {
|
const file = state.files[0];
|
||||||
// attachmentsList.innerHTML = '<p class="text-gray-400 text-center py-4">No attachments found in this PDF.</p>';
|
const fileBuffer = await readFileAsArrayBuffer(file);
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let index = 0;
|
const message = {
|
||||||
// for (const [ref, fileSpec] of embeddedFiles) {
|
command: 'get-attachments',
|
||||||
// try {
|
fileBuffer: fileBuffer,
|
||||||
// const fileSpecDict = fileSpec as any;
|
fileName: file.name
|
||||||
// const fileName = fileSpecDict.get('UF')?.decodeText() ||
|
};
|
||||||
// fileSpecDict.get('F')?.decodeText() ||
|
|
||||||
// `attachment-${index + 1}`;
|
|
||||||
|
|
||||||
// const ef = fileSpecDict.get('EF');
|
worker.postMessage(message, [fileBuffer]);
|
||||||
// let fileSize = 0;
|
} catch (error) {
|
||||||
// if (ef) {
|
console.error('Error loading attachments:', error);
|
||||||
// const fRef = ef.get('F') || ef.get('UF');
|
hideLoader();
|
||||||
// if (fRef) {
|
showAlert('Error', 'Failed to load attachments from PDF.');
|
||||||
// const fileStream = state.pdfDoc.context.lookup(fRef);
|
}
|
||||||
// if (fileStream) {
|
}
|
||||||
// fileSize = (fileStream as any).getContents().length;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// currentAttachments.push({ name: fileName, index, size: fileSize });
|
|
||||||
|
|
||||||
// const attachmentDiv = document.createElement('div');
|
worker.onmessage = (e) => {
|
||||||
// attachmentDiv.className = 'flex items-center justify-between p-3 bg-gray-800 rounded-lg border border-gray-700';
|
const data = e.data;
|
||||||
// attachmentDiv.dataset.attachmentIndex = index.toString();
|
|
||||||
|
|
||||||
// const infoDiv = document.createElement('div');
|
if (data.status === 'success' && data.attachments !== undefined) {
|
||||||
// infoDiv.className = 'flex-1';
|
const attachments = data.attachments;
|
||||||
// const nameSpan = document.createElement('span');
|
allAttachments = attachments.map(att => ({
|
||||||
// nameSpan.className = 'text-white font-medium block';
|
...att,
|
||||||
// nameSpan.textContent = fileName;
|
data: new Uint8Array(att.data)
|
||||||
// const sizeSpan = document.createElement('span');
|
}));
|
||||||
// sizeSpan.className = 'text-gray-400 text-sm';
|
|
||||||
// sizeSpan.textContent = `${Math.round(fileSize / 1024)} KB`;
|
|
||||||
// infoDiv.append(nameSpan, sizeSpan);
|
|
||||||
|
|
||||||
// const actionsDiv = document.createElement('div');
|
displayAttachments(attachments);
|
||||||
// actionsDiv.className = 'flex items-center gap-2';
|
hideLoader();
|
||||||
|
} else if (data.status === 'success' && data.modifiedPDF !== undefined) {
|
||||||
|
hideLoader();
|
||||||
|
|
||||||
// // Remove button
|
downloadFile(
|
||||||
// const removeBtn = document.createElement('button');
|
new Blob([new Uint8Array(data.modifiedPDF)], { type: 'application/pdf' }),
|
||||||
// removeBtn.className = 'btn bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded text-sm';
|
`edited-attachments-${data.fileName}`
|
||||||
// removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
);
|
||||||
// removeBtn.title = 'Remove attachment';
|
|
||||||
// removeBtn.onclick = () => {
|
|
||||||
// attachmentsToRemove.add(index);
|
|
||||||
// attachmentDiv.classList.add('opacity-50', 'line-through');
|
|
||||||
// removeBtn.disabled = true;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Replace button
|
showAlert('Success', 'Attachments updated successfully!');
|
||||||
// const replaceBtn = document.createElement('button');
|
} else if (data.status === 'error') {
|
||||||
// replaceBtn.className = 'btn bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1 rounded text-sm';
|
hideLoader();
|
||||||
// replaceBtn.innerHTML = '<i data-lucide="refresh-cw" class="w-4 h-4"></i>';
|
showAlert('Error', data.message || 'Unknown error occurred.');
|
||||||
// replaceBtn.title = 'Replace attachment';
|
}
|
||||||
// replaceBtn.onclick = () => {
|
};
|
||||||
// const input = document.createElement('input');
|
|
||||||
// input.type = 'file';
|
|
||||||
// input.onchange = async (e) => {
|
|
||||||
// const file = (e.target as HTMLInputElement).files?.[0];
|
|
||||||
// if (file) {
|
|
||||||
// attachmentsToReplace.set(index, file);
|
|
||||||
// nameSpan.textContent = `${fileName} → ${file.name}`;
|
|
||||||
// nameSpan.classList.add('text-yellow-400');
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// input.click();
|
|
||||||
// };
|
|
||||||
|
|
||||||
// actionsDiv.append(replaceBtn, removeBtn);
|
worker.onerror = (error) => {
|
||||||
// attachmentDiv.append(infoDiv, actionsDiv);
|
hideLoader();
|
||||||
// attachmentsList.appendChild(attachmentDiv);
|
console.error('Worker error:', error);
|
||||||
// index++;
|
showAlert('Error', 'Worker error occurred. Check console for details.');
|
||||||
// } catch (e) {
|
};
|
||||||
// console.warn(`Failed to process attachment ${index}:`, e);
|
|
||||||
// index++;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } catch (e) {
|
|
||||||
// console.error('Error loading attachments:', e);
|
|
||||||
// showAlert('Error', 'Failed to load attachments from PDF.');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export async function editAttachments() {
|
function displayAttachments(attachments) {
|
||||||
// if (!state.pdfDoc) {
|
const attachmentsList = document.getElementById('attachments-list');
|
||||||
// showAlert('Error', 'PDF is not loaded.');
|
if (!attachmentsList) return;
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// showLoader('Updating attachments...');
|
const existingControls = attachmentsList.querySelector('.attachments-controls');
|
||||||
// try {
|
attachmentsList.innerHTML = '';
|
||||||
// // Create a new PDF document
|
if (existingControls) {
|
||||||
// const newPdfDoc = await PDFLibDocument.create();
|
attachmentsList.appendChild(existingControls);
|
||||||
|
}
|
||||||
|
|
||||||
// // Copy all pages
|
if (attachments.length === 0) {
|
||||||
// const pages = await newPdfDoc.copyPages(state.pdfDoc, state.pdfDoc.getPageIndices());
|
const noAttachments = document.createElement('p');
|
||||||
// pages.forEach((page: any) => newPdfDoc.addPage(page));
|
noAttachments.className = 'text-gray-400 text-center py-4';
|
||||||
|
noAttachments.textContent = 'No attachments found in this PDF.';
|
||||||
|
attachmentsList.appendChild(noAttachments);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// // Handle attachments
|
const controlsContainer = document.createElement('div');
|
||||||
// const embeddedFiles = state.pdfDoc.context.enumerateIndirectObjects()
|
controlsContainer.className = 'attachments-controls mb-4 flex justify-end';
|
||||||
// .filter(([ref, obj]: any) => {
|
const removeAllBtn = document.createElement('button');
|
||||||
// const dict = obj instanceof PDFLibDocument.context.dict ? obj : null;
|
removeAllBtn.className = 'btn bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded text-sm';
|
||||||
// return dict && dict.get('Type')?.toString() === '/Filespec';
|
removeAllBtn.textContent = 'Remove All Attachments';
|
||||||
// });
|
removeAllBtn.onclick = () => {
|
||||||
|
if (allAttachments.length === 0) return;
|
||||||
|
|
||||||
// let attachmentIndex = 0;
|
const allSelected = allAttachments.every(attachment => attachmentsToRemove.has(attachment.index));
|
||||||
// for (const [ref, fileSpec] of embeddedFiles) {
|
|
||||||
// if (attachmentsToRemove.has(attachmentIndex)) {
|
|
||||||
// attachmentIndex++;
|
|
||||||
// continue; // Skip removed attachments
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (attachmentsToReplace.has(attachmentIndex)) {
|
if (allSelected) {
|
||||||
// // Replace attachment
|
allAttachments.forEach(attachment => {
|
||||||
// const replacementFile = attachmentsToReplace.get(attachmentIndex)!;
|
attachmentsToRemove.delete(attachment.index);
|
||||||
// const fileBytes = await readFileAsArrayBuffer(replacementFile);
|
const element = document.querySelector(`[data-attachment-index="${attachment.index}"]`);
|
||||||
// await newPdfDoc.attach(fileBytes as ArrayBuffer, replacementFile.name, {
|
if (element) {
|
||||||
// mimeType: replacementFile.type || 'application/octet-stream',
|
element.classList.remove('opacity-50', 'line-through');
|
||||||
// description: `Attached file: ${replacementFile.name}`,
|
const removeBtn = element.querySelector('button');
|
||||||
// creationDate: new Date(),
|
if (removeBtn) {
|
||||||
// modificationDate: new Date(replacementFile.lastModified),
|
removeBtn.classList.remove('bg-gray-600');
|
||||||
// });
|
removeBtn.classList.add('bg-red-600');
|
||||||
// } else {
|
}
|
||||||
// // Keep existing attachment - copy it
|
}
|
||||||
// try {
|
});
|
||||||
// const fileSpecDict = fileSpec as any;
|
removeAllBtn.textContent = 'Remove All Attachments';
|
||||||
// const fileName = fileSpecDict.get('UF')?.decodeText() ||
|
} else {
|
||||||
// fileSpecDict.get('F')?.decodeText() ||
|
allAttachments.forEach(attachment => {
|
||||||
// `attachment-${attachmentIndex + 1}`;
|
attachmentsToRemove.add(attachment.index);
|
||||||
|
const element = document.querySelector(`[data-attachment-index="${attachment.index}"]`);
|
||||||
|
if (element) {
|
||||||
|
element.classList.add('opacity-50', 'line-through');
|
||||||
|
const removeBtn = element.querySelector('button');
|
||||||
|
if (removeBtn) {
|
||||||
|
removeBtn.classList.add('bg-gray-600');
|
||||||
|
removeBtn.classList.remove('bg-red-600');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
removeAllBtn.textContent = 'Deselect All';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// const ef = fileSpecDict.get('EF');
|
controlsContainer.appendChild(removeAllBtn);
|
||||||
// if (ef) {
|
attachmentsList.appendChild(controlsContainer);
|
||||||
// const fRef = ef.get('F') || ef.get('UF');
|
|
||||||
// if (fRef) {
|
|
||||||
// const fileStream = state.pdfDoc.context.lookup(fRef);
|
|
||||||
// if (fileStream) {
|
|
||||||
// const fileData = (fileStream as any).getContents();
|
|
||||||
// await newPdfDoc.attach(fileData, fileName, {
|
|
||||||
// mimeType: 'application/octet-stream',
|
|
||||||
// description: `Attached file: ${fileName}`,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } catch (e) {
|
|
||||||
// console.warn(`Failed to copy attachment ${attachmentIndex}:`, e);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// attachmentIndex++;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const pdfBytes = await newPdfDoc.save();
|
for (const attachment of attachments) {
|
||||||
// downloadFile(
|
const attachmentDiv = document.createElement('div');
|
||||||
// new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' }),
|
attachmentDiv.className = 'flex items-center justify-between p-3 bg-gray-800 rounded-lg border border-gray-700';
|
||||||
// `edited-attachments-${state.files[0].name}`
|
attachmentDiv.dataset.attachmentIndex = attachment.index.toString();
|
||||||
// );
|
|
||||||
// showAlert('Success', 'Attachments updated successfully!');
|
|
||||||
// } catch (e) {
|
|
||||||
// console.error(e);
|
|
||||||
// showAlert('Error', 'Failed to edit attachments.');
|
|
||||||
// } finally {
|
|
||||||
// hideLoader();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
const infoDiv = document.createElement('div');
|
||||||
|
infoDiv.className = 'flex-1';
|
||||||
|
|
||||||
|
const nameSpan = document.createElement('span');
|
||||||
|
nameSpan.className = 'text-white font-medium block';
|
||||||
|
nameSpan.textContent = attachment.name;
|
||||||
|
|
||||||
|
const levelSpan = document.createElement('span');
|
||||||
|
levelSpan.className = 'text-gray-400 text-sm block';
|
||||||
|
if (attachment.page === 0) {
|
||||||
|
levelSpan.textContent = 'Document-level attachment';
|
||||||
|
} else {
|
||||||
|
levelSpan.textContent = `Page ${attachment.page} attachment`;
|
||||||
|
}
|
||||||
|
|
||||||
|
infoDiv.append(nameSpan, levelSpan);
|
||||||
|
|
||||||
|
const actionsDiv = document.createElement('div');
|
||||||
|
actionsDiv.className = 'flex items-center gap-2';
|
||||||
|
|
||||||
|
const removeBtn = document.createElement('button');
|
||||||
|
removeBtn.className = `btn ${attachmentsToRemove.has(attachment.index) ? 'bg-gray-600' : 'bg-red-600'} hover:bg-red-700 text-white px-3 py-1 rounded text-sm`;
|
||||||
|
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||||
|
removeBtn.title = 'Remove attachment';
|
||||||
|
removeBtn.onclick = () => {
|
||||||
|
if (attachmentsToRemove.has(attachment.index)) {
|
||||||
|
attachmentsToRemove.delete(attachment.index);
|
||||||
|
attachmentDiv.classList.remove('opacity-50', 'line-through');
|
||||||
|
removeBtn.classList.remove('bg-gray-600');
|
||||||
|
removeBtn.classList.add('bg-red-600');
|
||||||
|
} else {
|
||||||
|
attachmentsToRemove.add(attachment.index);
|
||||||
|
attachmentDiv.classList.add('opacity-50', 'line-through');
|
||||||
|
removeBtn.classList.add('bg-gray-600');
|
||||||
|
removeBtn.classList.remove('bg-red-600');
|
||||||
|
}
|
||||||
|
const allSelected = allAttachments.every(attachment => attachmentsToRemove.has(attachment.index));
|
||||||
|
removeAllBtn.textContent = allSelected ? 'Deselect All' : 'Remove All Attachments';
|
||||||
|
};
|
||||||
|
|
||||||
|
actionsDiv.append(removeBtn);
|
||||||
|
attachmentDiv.append(infoDiv, actionsDiv);
|
||||||
|
attachmentsList.appendChild(attachmentDiv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editAttachments() {
|
||||||
|
if (!state.files || state.files.length === 0) {
|
||||||
|
showAlert('Error', 'No PDF file loaded.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoader('Processing attachments...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = state.files[0];
|
||||||
|
const fileBuffer = await readFileAsArrayBuffer(file);
|
||||||
|
|
||||||
|
const message = {
|
||||||
|
command: 'edit-attachments',
|
||||||
|
fileBuffer: fileBuffer,
|
||||||
|
fileName: file.name,
|
||||||
|
attachmentsToRemove: Array.from(attachmentsToRemove)
|
||||||
|
};
|
||||||
|
|
||||||
|
worker.postMessage(message, [fileBuffer]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error editing attachments:', error);
|
||||||
|
hideLoader();
|
||||||
|
showAlert('Error', 'Failed to edit attachments.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ import { alternateMerge, setupAlternateMergeTool } from './alternate-merge.js';
|
|||||||
import { linearizePdf } from './linearize.js';
|
import { linearizePdf } from './linearize.js';
|
||||||
import { addAttachments, setupAddAttachmentsTool } from './add-attachments.js';
|
import { addAttachments, setupAddAttachmentsTool } from './add-attachments.js';
|
||||||
import { extractAttachments } from './extract-attachments.js';
|
import { extractAttachments } from './extract-attachments.js';
|
||||||
// import { editAttachments, setupEditAttachmentsTool } from './edit-attachments.js';
|
import { editAttachments, setupEditAttachmentsTool } from './edit-attachments.js';
|
||||||
import { sanitizePdf } from './sanitize-pdf.js';
|
import { sanitizePdf } from './sanitize-pdf.js';
|
||||||
import { removeRestrictions } from './remove-restrictions.js';
|
import { removeRestrictions } from './remove-restrictions.js';
|
||||||
|
|
||||||
@@ -141,9 +141,9 @@ export const toolLogic = {
|
|||||||
setup: setupAddAttachmentsTool,
|
setup: setupAddAttachmentsTool,
|
||||||
},
|
},
|
||||||
'extract-attachments': extractAttachments,
|
'extract-attachments': extractAttachments,
|
||||||
// 'edit-attachments': {
|
'edit-attachments': {
|
||||||
// process: editAttachments,
|
process: editAttachments,
|
||||||
// setup: setupEditAttachmentsTool,
|
setup: setupEditAttachmentsTool,
|
||||||
// },
|
},
|
||||||
'sanitize-pdf': sanitizePdf,
|
'sanitize-pdf': sanitizePdf,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user