import { PDFDocument, PDFName } from 'pdf-lib';
import { createIcons, icons } from 'lucide';
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
import { loadPdfDocument } from '../utils/load-pdf-document.js';
import { escapeHtml } from '../utils/helpers.js';
// State management
const pageState: { pdfDoc: PDFDocument | null; file: File | null } = {
pdfDoc: null,
file: null,
};
// UI helpers
function showLoader(message: string = 'Processing...') {
const loader = document.getElementById('loader-modal');
const loaderText = document.getElementById('loader-text');
if (loader) loader.classList.remove('hidden');
if (loaderText) loaderText.textContent = message;
}
function hideLoader() {
const loader = document.getElementById('loader-modal');
if (loader) loader.classList.add('hidden');
}
function showAlert(
title: string,
message: string,
type: string = 'error',
callback?: () => void
) {
const modal = document.getElementById('alert-modal');
const alertTitle = document.getElementById('alert-title');
const alertMessage = document.getElementById('alert-message');
const okBtn = document.getElementById('alert-ok');
if (alertTitle) alertTitle.textContent = title;
if (alertMessage) alertMessage.textContent = message;
if (modal) modal.classList.remove('hidden');
if (okBtn) {
const newOkBtn = okBtn.cloneNode(true) as HTMLElement;
okBtn.replaceWith(newOkBtn);
newOkBtn.addEventListener('click', () => {
modal?.classList.add('hidden');
if (callback) callback();
});
}
}
function downloadFile(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
function updateFileDisplay() {
const displayArea = document.getElementById('file-display-area');
if (!displayArea || !pageState.file || !pageState.pdfDoc) return;
const fileSize =
pageState.file.size < 1024 * 1024
? `${(pageState.file.size / 1024).toFixed(1)} KB`
: `${(pageState.file.size / 1024 / 1024).toFixed(2)} MB`;
const pageCount = pageState.pdfDoc.getPageCount();
displayArea.innerHTML = `
${escapeHtml(pageState.file.name)}
${fileSize} • ${pageCount} page${pageCount !== 1 ? 's' : ''}
`;
createIcons({ icons });
document
.getElementById('remove-file')
?.addEventListener('click', () => resetState());
}
function resetState() {
pageState.pdfDoc = null;
pageState.file = null;
const displayArea = document.getElementById('file-display-area');
if (displayArea) displayArea.innerHTML = '';
document.getElementById('options-panel')?.classList.add('hidden');
const fileInput = document.getElementById('file-input') as HTMLInputElement;
if (fileInput) fileInput.value = '';
}
// File handling
async function handleFileUpload(file: File) {
if (!file || file.type !== 'application/pdf') {
showAlert('Error', 'Please upload a valid PDF file.');
return;
}
try {
const result = await loadPdfWithPasswordPrompt(file);
if (!result) return;
showLoader('Loading PDF...');
result.pdf.destroy();
pageState.pdfDoc = await loadPdfDocument(result.bytes);
pageState.file = result.file;
updateFileDisplay();
document.getElementById('options-panel')?.classList.remove('hidden');
} catch (error) {
console.error(error);
showAlert('Error', 'Failed to load PDF file.');
} finally {
hideLoader();
}
}
// Process function
async function processRemoveAnnotations() {
if (!pageState.pdfDoc) {
showAlert('Error', 'Please upload a PDF file first.');
return;
}
showLoader('Removing annotations...');
try {
const pages = pageState.pdfDoc.getPages();
// Remove all annotations from all pages
for (let i = 0; i < pages.length; i++) {
const page = pages[i];
const annotRefs = page.node.Annots()?.asArray() || [];
if (annotRefs.length > 0) {
page.node.delete(PDFName.of('Annots'));
}
}
const newPdfBytes = await pageState.pdfDoc.save();
downloadFile(
new Blob([new Uint8Array(newPdfBytes)], { type: 'application/pdf' }),
pageState.file?.name || 'document.pdf'
);
showAlert('Success', 'Annotations removed successfully!', 'success', () => {
resetState();
});
} catch (e) {
console.error(e);
showAlert('Error', 'Could not remove annotations.');
} finally {
hideLoader();
}
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
const fileInput = document.getElementById('file-input') as HTMLInputElement;
const dropZone = document.getElementById('drop-zone');
const processBtn = document.getElementById('process-btn');
const backBtn = document.getElementById('back-to-tools');
fileInput?.addEventListener('change', (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) handleFileUpload(file);
});
dropZone?.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('border-indigo-500');
});
dropZone?.addEventListener('dragleave', () => {
dropZone.classList.remove('border-indigo-500');
});
dropZone?.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('border-indigo-500');
const file = e.dataTransfer?.files[0];
if (file) handleFileUpload(file);
});
processBtn?.addEventListener('click', processRemoveAnnotations);
backBtn?.addEventListener('click', () => {
window.location.href = '../../index.html';
});
});