From 2ede256de2645e8ae9308665b03d43f7b66c3f1b Mon Sep 17 00:00:00 2001 From: Sebastian Espei Date: Tue, 10 Mar 2026 20:13:13 +0100 Subject: [PATCH] Add localization for loading page count in various PDF processing pages --- public/locales/de/common.json | 1 + public/locales/en/common.json | 1 + src/js/logic/adjust-colors-page.ts | 2 +- src/js/logic/digital-sign-pdf-page.ts | 1168 ++++++++++++----------- src/js/logic/extract-images-page.ts | 3 +- src/js/logic/pdf-layers-page.ts | 3 +- src/js/logic/pdf-to-bmp-page.ts | 2 +- src/js/logic/pdf-to-docx-page.ts | 3 +- src/js/logic/pdf-to-greyscale-page.ts | 2 +- src/js/logic/pdf-to-jpg-page.ts | 2 +- src/js/logic/pdf-to-markdown-page.ts | 3 +- src/js/logic/pdf-to-pdfa-page.ts | 3 +- src/js/logic/pdf-to-png-page.ts | 2 +- src/js/logic/pdf-to-tiff-page.ts | 2 +- src/js/logic/pdf-to-webp-page.ts | 2 +- src/js/logic/prepare-pdf-for-ai-page.ts | 3 +- src/js/logic/rasterize-pdf-page.ts | 3 +- src/js/logic/scanner-effect-page.ts | 2 +- src/js/logic/sign-pdf-page.ts | 540 ++++++----- src/js/logic/split-pdf-page.ts | 3 +- 20 files changed, 944 insertions(+), 806 deletions(-) diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 1aa64f4..ec04c7b 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -251,6 +251,7 @@ "add": "Hinzufügen", "remove": "Entfernen", "loading": "Laden...", + "loadingPageCount": "Seitenanzahl wird geladen...", "error": "Fehler", "success": "Erfolg", "file": "Datei", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 50adeac..c66aebd 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -260,6 +260,7 @@ "add": "Add", "remove": "Remove", "loading": "Loading...", + "loadingPageCount": "Loading pages...", "error": "Error", "success": "Success", "file": "File", diff --git a/src/js/logic/adjust-colors-page.ts b/src/js/logic/adjust-colors-page.ts index aeab10f..e46d4b1 100644 --- a/src/js/logic/adjust-colors-page.ts +++ b/src/js/logic/adjust-colors-page.ts @@ -124,7 +124,7 @@ const updateUI = () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/digital-sign-pdf-page.ts b/src/js/logic/digital-sign-pdf-page.ts index 306b22a..42611d0 100644 --- a/src/js/logic/digital-sign-pdf-page.ts +++ b/src/js/logic/digital-sign-pdf-page.ts @@ -1,689 +1,775 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; -import { readFileAsArrayBuffer, formatBytes, downloadFile, getPDFDocument } from '../utils/helpers.js'; import { - signPdf, - parsePfxFile, - parseCombinedPem, - getCertificateInfo, + readFileAsArrayBuffer, + formatBytes, + downloadFile, + getPDFDocument, +} from '../utils/helpers.js'; +import { t } from '../i18n/i18n'; +import { + signPdf, + parsePfxFile, + parseCombinedPem, + getCertificateInfo, } from './digital-sign-pdf.js'; -import { SignatureInfo, VisibleSignatureOptions, DigitalSignState } from '@/types'; +import { + SignatureInfo, + VisibleSignatureOptions, + DigitalSignState, +} from '@/types'; const state: DigitalSignState = { - pdfFile: null, - pdfBytes: null, - certFile: null, - certData: null, - sigImageData: null, - sigImageType: null, + pdfFile: null, + pdfBytes: null, + certFile: null, + certData: null, + sigImageData: null, + sigImageType: null, }; function resetState(): void { - state.pdfFile = null; - state.pdfBytes = null; - state.certFile = null; - state.certData = null; - state.sigImageData = null; - state.sigImageType = null; + state.pdfFile = null; + state.pdfBytes = null; + state.certFile = null; + state.certData = null; + state.sigImageData = null; + state.sigImageType = null; - const fileDisplayArea = getElement('file-display-area'); - if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + const fileDisplayArea = getElement('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; - const certDisplayArea = getElement('cert-display-area'); - if (certDisplayArea) certDisplayArea.innerHTML = ''; + const certDisplayArea = getElement('cert-display-area'); + if (certDisplayArea) certDisplayArea.innerHTML = ''; - const fileInput = getElement('file-input'); - if (fileInput) fileInput.value = ''; + const fileInput = getElement('file-input'); + if (fileInput) fileInput.value = ''; - const certInput = getElement('cert-input'); - if (certInput) certInput.value = ''; + const certInput = getElement('cert-input'); + if (certInput) certInput.value = ''; - const sigImageInput = getElement('sig-image-input'); - if (sigImageInput) sigImageInput.value = ''; + const sigImageInput = getElement('sig-image-input'); + if (sigImageInput) sigImageInput.value = ''; - const sigImagePreview = getElement('sig-image-preview'); - if (sigImagePreview) sigImagePreview.classList.add('hidden'); + const sigImagePreview = getElement('sig-image-preview'); + if (sigImagePreview) sigImagePreview.classList.add('hidden'); - const certSection = getElement('certificate-section'); - if (certSection) certSection.classList.add('hidden'); + const certSection = getElement('certificate-section'); + if (certSection) certSection.classList.add('hidden'); - hidePasswordSection(); - hideSignatureOptions(); - hideCertInfo(); - updateProcessButton(); + hidePasswordSection(); + hideSignatureOptions(); + hideCertInfo(); + updateProcessButton(); } function getElement(id: string): T | null { - return document.getElementById(id) as T | null; + return document.getElementById(id) as T | null; } function initializePage(): void { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = getElement('file-input'); - const dropZone = getElement('drop-zone'); - const certInput = getElement('cert-input'); - const certDropZone = getElement('cert-drop-zone'); - const certPassword = getElement('cert-password'); - const processBtn = getElement('process-btn'); - const backBtn = getElement('back-to-tools'); + const fileInput = getElement('file-input'); + const dropZone = getElement('drop-zone'); + const certInput = getElement('cert-input'); + const certDropZone = getElement('cert-drop-zone'); + const certPassword = getElement('cert-password'); + const processBtn = getElement('process-btn'); + const backBtn = getElement('back-to-tools'); - if (fileInput) { - fileInput.addEventListener('change', handlePdfUpload); - fileInput.addEventListener('click', () => { - fileInput.value = ''; - }); - } + if (fileInput) { + fileInput.addEventListener('change', handlePdfUpload); + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('bg-gray-700'); - }); + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handlePdfFile(droppedFiles[0]); - } - }); - } + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handlePdfFile(droppedFiles[0]); + } + }); + } - if (certInput) { - certInput.addEventListener('change', handleCertUpload); - certInput.addEventListener('click', () => { - certInput.value = ''; - }); - } + if (certInput) { + certInput.addEventListener('change', handleCertUpload); + certInput.addEventListener('click', () => { + certInput.value = ''; + }); + } - if (certDropZone) { - certDropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - certDropZone.classList.add('bg-gray-700'); - }); + if (certDropZone) { + certDropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + certDropZone.classList.add('bg-gray-700'); + }); - certDropZone.addEventListener('dragleave', () => { - certDropZone.classList.remove('bg-gray-700'); - }); + certDropZone.addEventListener('dragleave', () => { + certDropZone.classList.remove('bg-gray-700'); + }); - certDropZone.addEventListener('drop', (e) => { - e.preventDefault(); - certDropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handleCertFile(droppedFiles[0]); - } - }); - } + certDropZone.addEventListener('drop', (e) => { + e.preventDefault(); + certDropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleCertFile(droppedFiles[0]); + } + }); + } - if (certPassword) { - certPassword.addEventListener('input', handlePasswordInput); - certPassword.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - handlePasswordInput(); - } - }); - } + if (certPassword) { + certPassword.addEventListener('input', handlePasswordInput); + certPassword.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + handlePasswordInput(); + } + }); + } - if (processBtn) { - processBtn.addEventListener('click', processSignature); - } + if (processBtn) { + processBtn.addEventListener('click', processSignature); + } - if (backBtn) { - backBtn.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; - }); - } + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } - const enableVisibleSig = getElement('enable-visible-sig'); - const visibleSigOptions = getElement('visible-sig-options'); - const sigPage = getElement('sig-page'); - const customPageWrapper = getElement('custom-page-wrapper'); - const sigImageInput = getElement('sig-image-input'); - const sigImagePreview = getElement('sig-image-preview'); - const sigImageThumb = getElement('sig-image-thumb'); - const removeSigImage = getElement('remove-sig-image'); - const enableSigText = getElement('enable-sig-text'); - const sigTextOptions = getElement('sig-text-options'); + const enableVisibleSig = getElement('enable-visible-sig'); + const visibleSigOptions = getElement('visible-sig-options'); + const sigPage = getElement('sig-page'); + const customPageWrapper = getElement('custom-page-wrapper'); + const sigImageInput = getElement('sig-image-input'); + const sigImagePreview = getElement('sig-image-preview'); + const sigImageThumb = getElement('sig-image-thumb'); + const removeSigImage = getElement('remove-sig-image'); + const enableSigText = getElement('enable-sig-text'); + const sigTextOptions = getElement('sig-text-options'); - if (enableVisibleSig && visibleSigOptions) { - enableVisibleSig.addEventListener('change', () => { - if (enableVisibleSig.checked) { - visibleSigOptions.classList.remove('hidden'); - } else { - visibleSigOptions.classList.add('hidden'); - } - }); - } + if (enableVisibleSig && visibleSigOptions) { + enableVisibleSig.addEventListener('change', () => { + if (enableVisibleSig.checked) { + visibleSigOptions.classList.remove('hidden'); + } else { + visibleSigOptions.classList.add('hidden'); + } + }); + } - if (sigPage && customPageWrapper) { - sigPage.addEventListener('change', () => { - if (sigPage.value === 'custom') { - customPageWrapper.classList.remove('hidden'); - } else { - customPageWrapper.classList.add('hidden'); - } - }); - } + if (sigPage && customPageWrapper) { + sigPage.addEventListener('change', () => { + if (sigPage.value === 'custom') { + customPageWrapper.classList.remove('hidden'); + } else { + customPageWrapper.classList.add('hidden'); + } + }); + } - if (sigImageInput) { - sigImageInput.addEventListener('change', async (e) => { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - const file = input.files[0]; - const validTypes = ['image/png', 'image/jpeg', 'image/webp']; - if (!validTypes.includes(file.type)) { - showAlert('Invalid Image', 'Please select a PNG, JPG, or WebP image.'); - return; - } - state.sigImageData = await readFileAsArrayBuffer(file) as ArrayBuffer; - state.sigImageType = file.type.replace('image/', '') as 'png' | 'jpeg' | 'webp'; + if (sigImageInput) { + sigImageInput.addEventListener('change', async (e) => { + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + const file = input.files[0]; + const validTypes = ['image/png', 'image/jpeg', 'image/webp']; + if (!validTypes.includes(file.type)) { + showAlert( + 'Invalid Image', + 'Please select a PNG, JPG, or WebP image.' + ); + return; + } + state.sigImageData = (await readFileAsArrayBuffer(file)) as ArrayBuffer; + state.sigImageType = file.type.replace('image/', '') as + | 'png' + | 'jpeg' + | 'webp'; - if (sigImageThumb && sigImagePreview) { - const url = URL.createObjectURL(file); - sigImageThumb.src = url; - sigImagePreview.classList.remove('hidden'); - } - } - }); - } + if (sigImageThumb && sigImagePreview) { + const url = URL.createObjectURL(file); + sigImageThumb.src = url; + sigImagePreview.classList.remove('hidden'); + } + } + }); + } - if (removeSigImage && sigImagePreview) { - removeSigImage.addEventListener('click', () => { - state.sigImageData = null; - state.sigImageType = null; - sigImagePreview.classList.add('hidden'); - if (sigImageInput) sigImageInput.value = ''; - }); - } + if (removeSigImage && sigImagePreview) { + removeSigImage.addEventListener('click', () => { + state.sigImageData = null; + state.sigImageType = null; + sigImagePreview.classList.add('hidden'); + if (sigImageInput) sigImageInput.value = ''; + }); + } - if (enableSigText && sigTextOptions) { - enableSigText.addEventListener('change', () => { - if (enableSigText.checked) { - sigTextOptions.classList.remove('hidden'); - } else { - sigTextOptions.classList.add('hidden'); - } - }); - } + if (enableSigText && sigTextOptions) { + enableSigText.addEventListener('change', () => { + if (enableSigText.checked) { + sigTextOptions.classList.remove('hidden'); + } else { + sigTextOptions.classList.add('hidden'); + } + }); + } } function handlePdfUpload(e: Event): void { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handlePdfFile(input.files[0]); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handlePdfFile(input.files[0]); + } } async function handlePdfFile(file: File): Promise { - if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { - showAlert('Invalid File', 'Please select a PDF file.'); - return; - } + if ( + file.type !== 'application/pdf' && + !file.name.toLowerCase().endsWith('.pdf') + ) { + showAlert('Invalid File', 'Please select a PDF file.'); + return; + } - state.pdfFile = file; - state.pdfBytes = new Uint8Array(await readFileAsArrayBuffer(file) as ArrayBuffer); + state.pdfFile = file; + state.pdfBytes = new Uint8Array( + (await readFileAsArrayBuffer(file)) as ArrayBuffer + ); - updatePdfDisplay(); - showCertificateSection(); + updatePdfDisplay(); + showCertificateSection(); } async function updatePdfDisplay(): Promise { - const fileDisplayArea = getElement('file-display-area'); + const fileDisplayArea = getElement('file-display-area'); - if (!fileDisplayArea || !state.pdfFile) return; + if (!fileDisplayArea || !state.pdfFile) return; + fileDisplayArea.innerHTML = ''; + + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = state.pdfFile.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(state.pdfFile.size)} • ${t('common.loadingPageCount')}`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.pdfFile = null; + state.pdfBytes = null; fileDisplayArea.innerHTML = ''; + hideCertificateSection(); + updateProcessButton(); + }; - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col flex-1 min-w-0'; - - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = state.pdfFile.name; - - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(state.pdfFile.size)} • Loading pages...`; - - infoContainer.append(nameSpan, metaSpan); - - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - state.pdfFile = null; - state.pdfBytes = null; - fileDisplayArea.innerHTML = ''; - hideCertificateSection(); - updateProcessButton(); - }; - - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); - - try { - if (state.pdfBytes) { - const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() }).promise; - metaSpan.textContent = `${formatBytes(state.pdfFile.size)} • ${pdfDoc.numPages} pages`; - } - } catch (error) { - console.error('Error loading PDF:', error); - metaSpan.textContent = `${formatBytes(state.pdfFile.size)}`; + try { + if (state.pdfBytes) { + const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() }) + .promise; + metaSpan.textContent = `${formatBytes(state.pdfFile.size)} • ${pdfDoc.numPages} pages`; } + } catch (error) { + console.error('Error loading PDF:', error); + metaSpan.textContent = `${formatBytes(state.pdfFile.size)}`; + } } function showCertificateSection(): void { - const certSection = getElement('certificate-section'); - if (certSection) { - certSection.classList.remove('hidden'); - } + const certSection = getElement('certificate-section'); + if (certSection) { + certSection.classList.remove('hidden'); + } } function hideCertificateSection(): void { - const certSection = getElement('certificate-section'); - const signatureOptions = getElement('signature-options'); + const certSection = getElement('certificate-section'); + const signatureOptions = getElement('signature-options'); - if (certSection) { - certSection.classList.add('hidden'); - } - if (signatureOptions) { - signatureOptions.classList.add('hidden'); - } + if (certSection) { + certSection.classList.add('hidden'); + } + if (signatureOptions) { + signatureOptions.classList.add('hidden'); + } - state.certFile = null; - state.certData = null; + state.certFile = null; + state.certData = null; - const certDisplayArea = getElement('cert-display-area'); - if (certDisplayArea) { - certDisplayArea.innerHTML = ''; - } + const certDisplayArea = getElement('cert-display-area'); + if (certDisplayArea) { + certDisplayArea.innerHTML = ''; + } - const certInfo = getElement('cert-info'); - if (certInfo) { - certInfo.classList.add('hidden'); - } + const certInfo = getElement('cert-info'); + if (certInfo) { + certInfo.classList.add('hidden'); + } - const certPasswordSection = getElement('cert-password-section'); - if (certPasswordSection) { - certPasswordSection.classList.add('hidden'); - } + const certPasswordSection = getElement( + 'cert-password-section' + ); + if (certPasswordSection) { + certPasswordSection.classList.add('hidden'); + } } function handleCertUpload(e: Event): void { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handleCertFile(input.files[0]); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleCertFile(input.files[0]); + } } async function handleCertFile(file: File): Promise { - const validExtensions = ['.pfx', '.p12', '.pem']; - const hasValidExtension = validExtensions.some(ext => - file.name.toLowerCase().endsWith(ext) + const validExtensions = ['.pfx', '.p12', '.pem']; + const hasValidExtension = validExtensions.some((ext) => + file.name.toLowerCase().endsWith(ext) + ); + + if (!hasValidExtension) { + showAlert( + 'Invalid Certificate', + 'Please select a .pfx, .p12, or .pem certificate file.' ); + return; + } - if (!hasValidExtension) { - showAlert('Invalid Certificate', 'Please select a .pfx, .p12, or .pem certificate file.'); - return; - } + state.certFile = file; + state.certData = null; - state.certFile = file; - state.certData = null; + updateCertDisplay(); - updateCertDisplay(); + const isPemFile = file.name.toLowerCase().endsWith('.pem'); - const isPemFile = file.name.toLowerCase().endsWith('.pem'); + if (isPemFile) { + try { + const pemContent = await file.text(); + const isEncrypted = pemContent.includes('ENCRYPTED'); - if (isPemFile) { - try { - const pemContent = await file.text(); - const isEncrypted = pemContent.includes('ENCRYPTED'); - - if (isEncrypted) { - showPasswordSection(); - updatePasswordLabel('Private Key Password'); - } else { - state.certData = parseCombinedPem(pemContent); - updateCertInfo(); - showSignatureOptions(); - - const certStatus = getElement('cert-status'); - if (certStatus) { - certStatus.innerHTML = 'Certificate loaded '; - createIcons({ icons }); - certStatus.className = 'text-xs text-green-400'; - } - } - } catch (error) { - const certStatus = getElement('cert-status'); - if (certStatus) { - certStatus.textContent = 'Failed to parse PEM file'; - certStatus.className = 'text-xs text-red-400'; - } - } - } else { + if (isEncrypted) { showPasswordSection(); - updatePasswordLabel('Certificate Password'); - } + updatePasswordLabel('Private Key Password'); + } else { + state.certData = parseCombinedPem(pemContent); + updateCertInfo(); + showSignatureOptions(); - hideSignatureOptions(); - updateProcessButton(); + const certStatus = getElement('cert-status'); + if (certStatus) { + certStatus.innerHTML = + 'Certificate loaded '; + createIcons({ icons }); + certStatus.className = 'text-xs text-green-400'; + } + } + } catch (error) { + const certStatus = getElement('cert-status'); + if (certStatus) { + certStatus.textContent = 'Failed to parse PEM file'; + certStatus.className = 'text-xs text-red-400'; + } + } + } else { + showPasswordSection(); + updatePasswordLabel('Certificate Password'); + } + + hideSignatureOptions(); + updateProcessButton(); } function updateCertDisplay(): void { - const certDisplayArea = getElement('cert-display-area'); + const certDisplayArea = getElement('cert-display-area'); - if (!certDisplayArea || !state.certFile) return; + if (!certDisplayArea || !state.certFile) return; + certDisplayArea.innerHTML = ''; + + const certDiv = document.createElement('div'); + certDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = state.certFile.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.id = 'cert-status'; + metaSpan.textContent = 'Enter password to unlock'; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.certFile = null; + state.certData = null; certDisplayArea.innerHTML = ''; + hidePasswordSection(); + hideCertInfo(); + hideSignatureOptions(); + updateProcessButton(); + }; - const certDiv = document.createElement('div'); - certDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; - - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col flex-1 min-w-0'; - - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = state.certFile.name; - - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.id = 'cert-status'; - metaSpan.textContent = 'Enter password to unlock'; - - infoContainer.append(nameSpan, metaSpan); - - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - state.certFile = null; - state.certData = null; - certDisplayArea.innerHTML = ''; - hidePasswordSection(); - hideCertInfo(); - hideSignatureOptions(); - updateProcessButton(); - }; - - certDiv.append(infoContainer, removeBtn); - certDisplayArea.appendChild(certDiv); - createIcons({ icons }); + certDiv.append(infoContainer, removeBtn); + certDisplayArea.appendChild(certDiv); + createIcons({ icons }); } function showPasswordSection(): void { - const certPasswordSection = getElement('cert-password-section'); - if (certPasswordSection) { - certPasswordSection.classList.remove('hidden'); - } + const certPasswordSection = getElement( + 'cert-password-section' + ); + if (certPasswordSection) { + certPasswordSection.classList.remove('hidden'); + } - const certPassword = getElement('cert-password'); - if (certPassword) { - certPassword.value = ''; - certPassword.focus(); - } + const certPassword = getElement('cert-password'); + if (certPassword) { + certPassword.value = ''; + certPassword.focus(); + } } function updatePasswordLabel(labelText: string): void { - const label = document.querySelector('label[for="cert-password"]'); - if (label) { - label.textContent = labelText; - } + const label = document.querySelector('label[for="cert-password"]'); + if (label) { + label.textContent = labelText; + } } function hidePasswordSection(): void { - const certPasswordSection = getElement('cert-password-section'); - if (certPasswordSection) { - certPasswordSection.classList.add('hidden'); - } + const certPasswordSection = getElement( + 'cert-password-section' + ); + if (certPasswordSection) { + certPasswordSection.classList.add('hidden'); + } } function showSignatureOptions(): void { - const signatureOptions = getElement('signature-options'); - if (signatureOptions) { - signatureOptions.classList.remove('hidden'); - } - const visibleSigSection = getElement('visible-signature-section'); - if (visibleSigSection) { - visibleSigSection.classList.remove('hidden'); - } + const signatureOptions = getElement('signature-options'); + if (signatureOptions) { + signatureOptions.classList.remove('hidden'); + } + const visibleSigSection = getElement( + 'visible-signature-section' + ); + if (visibleSigSection) { + visibleSigSection.classList.remove('hidden'); + } } function hideSignatureOptions(): void { - const signatureOptions = getElement('signature-options'); - if (signatureOptions) { - signatureOptions.classList.add('hidden'); - } - const visibleSigSection = getElement('visible-signature-section'); - if (visibleSigSection) { - visibleSigSection.classList.add('hidden'); - } + const signatureOptions = getElement('signature-options'); + if (signatureOptions) { + signatureOptions.classList.add('hidden'); + } + const visibleSigSection = getElement( + 'visible-signature-section' + ); + if (visibleSigSection) { + visibleSigSection.classList.add('hidden'); + } } function hideCertInfo(): void { - const certInfo = getElement('cert-info'); - if (certInfo) { - certInfo.classList.add('hidden'); - } + const certInfo = getElement('cert-info'); + if (certInfo) { + certInfo.classList.add('hidden'); + } } async function handlePasswordInput(): Promise { - const certPassword = getElement('cert-password'); - const password = certPassword?.value ?? ''; + const certPassword = getElement('cert-password'); + const password = certPassword?.value ?? ''; - if (!state.certFile || !password) { - return; + if (!state.certFile || !password) { + return; + } + + try { + const isPemFile = state.certFile.name.toLowerCase().endsWith('.pem'); + + if (isPemFile) { + const pemContent = await state.certFile.text(); + state.certData = parseCombinedPem(pemContent, password); + } else { + const certBytes = (await readFileAsArrayBuffer( + state.certFile + )) as ArrayBuffer; + state.certData = parsePfxFile(certBytes, password); } - try { - const isPemFile = state.certFile.name.toLowerCase().endsWith('.pem'); + updateCertInfo(); + showSignatureOptions(); + updateProcessButton(); - if (isPemFile) { - const pemContent = await state.certFile.text(); - state.certData = parseCombinedPem(pemContent, password); - } else { - const certBytes = await readFileAsArrayBuffer(state.certFile) as ArrayBuffer; - state.certData = parsePfxFile(certBytes, password); - } - - updateCertInfo(); - showSignatureOptions(); - updateProcessButton(); - - const certStatus = getElement('cert-status'); - if (certStatus) { - certStatus.innerHTML = 'Certificate unlocked '; - createIcons({ icons }); - certStatus.className = 'text-xs text-green-400'; - } - } catch (error) { - state.certData = null; - hideSignatureOptions(); - updateProcessButton(); - - const certStatus = getElement('cert-status'); - if (certStatus) { - const errorMessage = error instanceof Error ? error.message : 'Invalid password or certificate'; - certStatus.textContent = errorMessage.includes('password') - ? 'Incorrect password' - : 'Failed to parse certificate'; - certStatus.className = 'text-xs text-red-400'; - } + const certStatus = getElement('cert-status'); + if (certStatus) { + certStatus.innerHTML = + 'Certificate unlocked '; + createIcons({ icons }); + certStatus.className = 'text-xs text-green-400'; } + } catch (error) { + state.certData = null; + hideSignatureOptions(); + updateProcessButton(); + + const certStatus = getElement('cert-status'); + if (certStatus) { + const errorMessage = + error instanceof Error + ? error.message + : 'Invalid password or certificate'; + certStatus.textContent = errorMessage.includes('password') + ? 'Incorrect password' + : 'Failed to parse certificate'; + certStatus.className = 'text-xs text-red-400'; + } + } } function updateCertInfo(): void { - if (!state.certData) return; + if (!state.certData) return; - const certInfo = getElement('cert-info'); - const certSubject = getElement('cert-subject'); - const certIssuer = getElement('cert-issuer'); - const certValidity = getElement('cert-validity'); + const certInfo = getElement('cert-info'); + const certSubject = getElement('cert-subject'); + const certIssuer = getElement('cert-issuer'); + const certValidity = getElement('cert-validity'); - if (!certInfo) return; + if (!certInfo) return; - const info = getCertificateInfo(state.certData.certificate); + const info = getCertificateInfo(state.certData.certificate); - if (certSubject) { - certSubject.textContent = info.subject; - } - if (certIssuer) { - certIssuer.textContent = info.issuer; - } - if (certValidity) { - const formatDate = (date: Date) => date.toLocaleDateString(); - certValidity.textContent = `${formatDate(info.validFrom)} - ${formatDate(info.validTo)}`; - } + if (certSubject) { + certSubject.textContent = info.subject; + } + if (certIssuer) { + certIssuer.textContent = info.issuer; + } + if (certValidity) { + const formatDate = (date: Date) => date.toLocaleDateString(); + certValidity.textContent = `${formatDate(info.validFrom)} - ${formatDate(info.validTo)}`; + } - certInfo.classList.remove('hidden'); + certInfo.classList.remove('hidden'); } function updateProcessButton(): void { - const processBtn = getElement('process-btn'); - if (!processBtn) return; + const processBtn = getElement('process-btn'); + if (!processBtn) return; - const canProcess = state.pdfBytes !== null && state.certData !== null; + const canProcess = state.pdfBytes !== null && state.certData !== null; - if (canProcess) { - processBtn.style.display = ''; - } else { - processBtn.style.display = 'none'; - } + if (canProcess) { + processBtn.style.display = ''; + } else { + processBtn.style.display = 'none'; + } } async function processSignature(): Promise { - if (!state.pdfBytes || !state.certData) { - showAlert('Missing Data', 'Please upload both a PDF and a valid certificate.'); - return; - } + if (!state.pdfBytes || !state.certData) { + showAlert( + 'Missing Data', + 'Please upload both a PDF and a valid certificate.' + ); + return; + } - const reason = getElement('sign-reason')?.value ?? ''; - const location = getElement('sign-location')?.value ?? ''; - const contactInfo = getElement('sign-contact')?.value ?? ''; + const reason = getElement('sign-reason')?.value ?? ''; + const location = getElement('sign-location')?.value ?? ''; + const contactInfo = getElement('sign-contact')?.value ?? ''; - const signatureInfo: SignatureInfo = {}; - if (reason) signatureInfo.reason = reason; - if (location) signatureInfo.location = location; - if (contactInfo) signatureInfo.contactInfo = contactInfo; + const signatureInfo: SignatureInfo = {}; + if (reason) signatureInfo.reason = reason; + if (location) signatureInfo.location = location; + if (contactInfo) signatureInfo.contactInfo = contactInfo; - let visibleSignature: VisibleSignatureOptions | undefined; + let visibleSignature: VisibleSignatureOptions | undefined; - const enableVisibleSig = getElement('enable-visible-sig'); - if (enableVisibleSig?.checked) { - const sigX = parseInt(getElement('sig-x')?.value ?? '25', 10); - const sigY = parseInt(getElement('sig-y')?.value ?? '700', 10); - const sigWidth = parseInt(getElement('sig-width')?.value ?? '150', 10); - const sigHeight = parseInt(getElement('sig-height')?.value ?? '70', 10); + const enableVisibleSig = getElement('enable-visible-sig'); + if (enableVisibleSig?.checked) { + const sigX = parseInt( + getElement('sig-x')?.value ?? '25', + 10 + ); + const sigY = parseInt( + getElement('sig-y')?.value ?? '700', + 10 + ); + const sigWidth = parseInt( + getElement('sig-width')?.value ?? '150', + 10 + ); + const sigHeight = parseInt( + getElement('sig-height')?.value ?? '70', + 10 + ); - const sigPageSelect = getElement('sig-page'); - let sigPage: number | string = 0; - let numPages = 1; - - try { - const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() }).promise; - numPages = pdfDoc.numPages; - } catch (error) { - console.error('Error getting PDF page count:', error); - } - - if (sigPageSelect) { - if (sigPageSelect.value === 'last') { - sigPage = (numPages - 1).toString(); - } else if (sigPageSelect.value === 'all') { - if (numPages === 1) { - sigPage = '0'; - } else { - sigPage = `0-${numPages - 1}`; - } - } else if (sigPageSelect.value === 'custom') { - sigPage = parseInt(getElement('sig-custom-page')?.value ?? '1', 10) - 1; - } else { - sigPage = parseInt(sigPageSelect.value, 10); - } - } - - const enableSigText = getElement('enable-sig-text'); - let sigText = enableSigText?.checked ? getElement('sig-text')?.value : undefined; - const sigTextColor = getElement('sig-text-color')?.value ?? '#000000'; - const sigTextSize = parseInt(getElement('sig-text-size')?.value ?? '12', 10); - - if (!state.sigImageData && !sigText && state.certData) { - const certInfo = getCertificateInfo(state.certData.certificate); - const date = new Date().toLocaleDateString(); - sigText = `Digitally signed by ${certInfo.subject}\n${date}`; - } - - let finalHeight = sigHeight; - if (sigText && !state.sigImageData) { - const lineCount = (sigText.match(/\n/g) || []).length + 1; - const lineHeightFactor = 1.4; - const padding = 16; - const calculatedHeight = Math.ceil(lineCount * sigTextSize * lineHeightFactor + padding); - finalHeight = Math.max(calculatedHeight, sigHeight); - } - - visibleSignature = { - enabled: true, - x: sigX, - y: sigY, - width: sigWidth, - height: finalHeight, - page: sigPage, - imageData: state.sigImageData ?? undefined, - imageType: state.sigImageType ?? undefined, - text: sigText, - textColor: sigTextColor, - textSize: sigTextSize, - }; - } - - showLoader('Applying digital signature...'); + const sigPageSelect = getElement('sig-page'); + let sigPage: number | string = 0; + let numPages = 1; try { - const signedPdfBytes = await signPdf(state.pdfBytes, state.certData, { - signatureInfo, - visibleSignature, - }); - - const blob = new Blob([signedPdfBytes.slice().buffer], { type: 'application/pdf' }); - const originalName = state.pdfFile?.name ?? 'document.pdf'; - const signedName = originalName.replace(/\.pdf$/i, '_signed.pdf'); - - downloadFile(blob, signedName); - - hideLoader(); - showAlert('Success', 'PDF signed successfully! The signature can be verified in any PDF reader.', 'success', () => { resetState(); }); + const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() }) + .promise; + numPages = pdfDoc.numPages; } catch (error) { - hideLoader(); - console.error('Signing error:', error); - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; - - // Check if this is a CORS/network error from certificate chain fetching - if (errorMessage.includes('Failed to fetch') || errorMessage.includes('CORS') || errorMessage.includes('NetworkError')) { - showAlert( - 'Signing Failed', - 'Failed to fetch certificate chain. This may be due to network issues or the certificate proxy being unavailable. Please check your internet connection and try again. If the issue persists, contact support.' - ); - } else { - showAlert('Signing Failed', `Failed to sign PDF: ${errorMessage}`); - } + console.error('Error getting PDF page count:', error); } + + if (sigPageSelect) { + if (sigPageSelect.value === 'last') { + sigPage = (numPages - 1).toString(); + } else if (sigPageSelect.value === 'all') { + if (numPages === 1) { + sigPage = '0'; + } else { + sigPage = `0-${numPages - 1}`; + } + } else if (sigPageSelect.value === 'custom') { + sigPage = + parseInt( + getElement('sig-custom-page')?.value ?? '1', + 10 + ) - 1; + } else { + sigPage = parseInt(sigPageSelect.value, 10); + } + } + + const enableSigText = getElement('enable-sig-text'); + let sigText = enableSigText?.checked + ? getElement('sig-text')?.value + : undefined; + const sigTextColor = + getElement('sig-text-color')?.value ?? '#000000'; + const sigTextSize = parseInt( + getElement('sig-text-size')?.value ?? '12', + 10 + ); + + if (!state.sigImageData && !sigText && state.certData) { + const certInfo = getCertificateInfo(state.certData.certificate); + const date = new Date().toLocaleDateString(); + sigText = `Digitally signed by ${certInfo.subject}\n${date}`; + } + + let finalHeight = sigHeight; + if (sigText && !state.sigImageData) { + const lineCount = (sigText.match(/\n/g) || []).length + 1; + const lineHeightFactor = 1.4; + const padding = 16; + const calculatedHeight = Math.ceil( + lineCount * sigTextSize * lineHeightFactor + padding + ); + finalHeight = Math.max(calculatedHeight, sigHeight); + } + + visibleSignature = { + enabled: true, + x: sigX, + y: sigY, + width: sigWidth, + height: finalHeight, + page: sigPage, + imageData: state.sigImageData ?? undefined, + imageType: state.sigImageType ?? undefined, + text: sigText, + textColor: sigTextColor, + textSize: sigTextSize, + }; + } + + showLoader('Applying digital signature...'); + + try { + const signedPdfBytes = await signPdf(state.pdfBytes, state.certData, { + signatureInfo, + visibleSignature, + }); + + const blob = new Blob([signedPdfBytes.slice().buffer], { + type: 'application/pdf', + }); + const originalName = state.pdfFile?.name ?? 'document.pdf'; + const signedName = originalName.replace(/\.pdf$/i, '_signed.pdf'); + + downloadFile(blob, signedName); + + hideLoader(); + showAlert( + 'Success', + 'PDF signed successfully! The signature can be verified in any PDF reader.', + 'success', + () => { + resetState(); + } + ); + } catch (error) { + hideLoader(); + console.error('Signing error:', error); + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred'; + + // Check if this is a CORS/network error from certificate chain fetching + if ( + errorMessage.includes('Failed to fetch') || + errorMessage.includes('CORS') || + errorMessage.includes('NetworkError') + ) { + showAlert( + 'Signing Failed', + 'Failed to fetch certificate chain. This may be due to network issues or the certificate proxy being unavailable. Please check your internet connection and try again. If the issue persists, contact support.' + ); + } else { + showAlert('Signing Failed', `Failed to sign PDF: ${errorMessage}`); + } + } } if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } diff --git a/src/js/logic/extract-images-page.ts b/src/js/logic/extract-images-page.ts index 36d7f0b..aaab5d1 100644 --- a/src/js/logic/extract-images-page.ts +++ b/src/js/logic/extract-images-page.ts @@ -1,4 +1,5 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { t } from '../i18n/i18n'; import { downloadFile, readFileAsArrayBuffer, @@ -66,7 +67,7 @@ document.addEventListener('DOMContentLoaded', () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/pdf-layers-page.ts b/src/js/logic/pdf-layers-page.ts index 22ea415..e1a3836 100644 --- a/src/js/logic/pdf-layers-page.ts +++ b/src/js/logic/pdf-layers-page.ts @@ -1,4 +1,5 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { t } from '../i18n/i18n'; import { downloadFile, readFileAsArrayBuffer, @@ -60,7 +61,7 @@ document.addEventListener('DOMContentLoaded', () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(currentFile.size)} • Loading pages...`; + metaSpan.textContent = `${formatBytes(currentFile.size)} • ${t('common.loadingPageCount')}`; infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/pdf-to-bmp-page.ts b/src/js/logic/pdf-to-bmp-page.ts index b38ac4d..046eea6 100644 --- a/src/js/logic/pdf-to-bmp-page.ts +++ b/src/js/logic/pdf-to-bmp-page.ts @@ -45,7 +45,7 @@ const updateUI = () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Initial state + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; // Initial state infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/pdf-to-docx-page.ts b/src/js/logic/pdf-to-docx-page.ts index 3850fbc..101f748 100644 --- a/src/js/logic/pdf-to-docx-page.ts +++ b/src/js/logic/pdf-to-docx-page.ts @@ -1,4 +1,5 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { t } from '../i18n/i18n'; import { downloadFile, readFileAsArrayBuffer, @@ -50,7 +51,7 @@ document.addEventListener('DOMContentLoaded', () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/pdf-to-greyscale-page.ts b/src/js/logic/pdf-to-greyscale-page.ts index 9a4f94a..c1e7546 100644 --- a/src/js/logic/pdf-to-greyscale-page.ts +++ b/src/js/logic/pdf-to-greyscale-page.ts @@ -45,7 +45,7 @@ const updateUI = () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Initial state + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; // Initial state infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/pdf-to-jpg-page.ts b/src/js/logic/pdf-to-jpg-page.ts index dedebb3..945d022 100644 --- a/src/js/logic/pdf-to-jpg-page.ts +++ b/src/js/logic/pdf-to-jpg-page.ts @@ -45,7 +45,7 @@ const updateUI = () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Initial state + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; // Initial state infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/pdf-to-markdown-page.ts b/src/js/logic/pdf-to-markdown-page.ts index f99294b..17143c0 100644 --- a/src/js/logic/pdf-to-markdown-page.ts +++ b/src/js/logic/pdf-to-markdown-page.ts @@ -1,4 +1,5 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { t } from '../i18n/i18n'; import { downloadFile, readFileAsArrayBuffer, @@ -53,7 +54,7 @@ document.addEventListener('DOMContentLoaded', () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/pdf-to-pdfa-page.ts b/src/js/logic/pdf-to-pdfa-page.ts index 0de4073..07b3a7d 100644 --- a/src/js/logic/pdf-to-pdfa-page.ts +++ b/src/js/logic/pdf-to-pdfa-page.ts @@ -1,4 +1,5 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { t } from '../i18n/i18n'; import { downloadFile, readFileAsArrayBuffer, @@ -53,7 +54,7 @@ document.addEventListener('DOMContentLoaded', () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/pdf-to-png-page.ts b/src/js/logic/pdf-to-png-page.ts index 84fe5e5..9b33813 100644 --- a/src/js/logic/pdf-to-png-page.ts +++ b/src/js/logic/pdf-to-png-page.ts @@ -45,7 +45,7 @@ const updateUI = () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Initial state + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; // Initial state infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/pdf-to-tiff-page.ts b/src/js/logic/pdf-to-tiff-page.ts index c463124..f247a21 100644 --- a/src/js/logic/pdf-to-tiff-page.ts +++ b/src/js/logic/pdf-to-tiff-page.ts @@ -46,7 +46,7 @@ const updateUI = () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Initial state + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; // Initial state infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/pdf-to-webp-page.ts b/src/js/logic/pdf-to-webp-page.ts index 57e4d5e..c991752 100644 --- a/src/js/logic/pdf-to-webp-page.ts +++ b/src/js/logic/pdf-to-webp-page.ts @@ -45,7 +45,7 @@ const updateUI = () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Initial state + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; // Initial state infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/prepare-pdf-for-ai-page.ts b/src/js/logic/prepare-pdf-for-ai-page.ts index 464f522..e65dd48 100644 --- a/src/js/logic/prepare-pdf-for-ai-page.ts +++ b/src/js/logic/prepare-pdf-for-ai-page.ts @@ -1,4 +1,5 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { t } from '../i18n/i18n'; import { downloadFile, readFileAsArrayBuffer, @@ -50,7 +51,7 @@ document.addEventListener('DOMContentLoaded', () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/rasterize-pdf-page.ts b/src/js/logic/rasterize-pdf-page.ts index fc9960b..8949c7b 100644 --- a/src/js/logic/rasterize-pdf-page.ts +++ b/src/js/logic/rasterize-pdf-page.ts @@ -1,4 +1,5 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { t } from '../i18n/i18n'; import { downloadFile, readFileAsArrayBuffer, @@ -50,7 +51,7 @@ document.addEventListener('DOMContentLoaded', () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/scanner-effect-page.ts b/src/js/logic/scanner-effect-page.ts index 2954549..bae63b6 100644 --- a/src/js/logic/scanner-effect-page.ts +++ b/src/js/logic/scanner-effect-page.ts @@ -130,7 +130,7 @@ const updateUI = () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; infoContainer.append(nameSpan, metaSpan); diff --git a/src/js/logic/sign-pdf-page.ts b/src/js/logic/sign-pdf-page.ts index 8f04273..f446f3f 100644 --- a/src/js/logic/sign-pdf-page.ts +++ b/src/js/logic/sign-pdf-page.ts @@ -1,312 +1,354 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; -import { readFileAsArrayBuffer, formatBytes, downloadFile, getPDFDocument } from '../utils/helpers.js'; +import { + readFileAsArrayBuffer, + formatBytes, + downloadFile, + getPDFDocument, +} from '../utils/helpers.js'; import { PDFDocument } from 'pdf-lib'; +import { t } from '../i18n/i18n'; interface SignState { - file: File | null; - pdfDoc: any; - viewerIframe: HTMLIFrameElement | null; - viewerReady: boolean; - blobUrl: string | null; + file: File | null; + pdfDoc: any; + viewerIframe: HTMLIFrameElement | null; + viewerReady: boolean; + blobUrl: string | null; } const signState: SignState = { - file: null, - pdfDoc: null, - viewerIframe: null, - viewerReady: false, - blobUrl: null, + file: null, + pdfDoc: null, + viewerIframe: null, + viewerReady: false, + blobUrl: null, }; if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } function initializePage() { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const processBtn = document.getElementById('process-btn'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); - if (fileInput) { - fileInput.addEventListener('change', handleFileUpload); - } + if (fileInput) { + fileInput.addEventListener('change', handleFileUpload); + } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); - - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handleFile(droppedFiles[0]); - } - }); - - // Clear value on click to allow re-selecting the same file - fileInput?.addEventListener('click', () => { - if (fileInput) fileInput.value = ''; - }); - } - - if (processBtn) { - processBtn.addEventListener('click', applyAndSaveSignatures); - } - - document.getElementById('back-to-tools')?.addEventListener('click', () => { - cleanup(); - window.location.href = import.meta.env.BASE_URL; + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleFile(droppedFiles[0]); + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput?.addEventListener('click', () => { + if (fileInput) fileInput.value = ''; + }); + } + + if (processBtn) { + processBtn.addEventListener('click', applyAndSaveSignatures); + } + + document.getElementById('back-to-tools')?.addEventListener('click', () => { + cleanup(); + window.location.href = import.meta.env.BASE_URL; + }); } function handleFileUpload(e: Event) { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handleFile(input.files[0]); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleFile(input.files[0]); + } } function handleFile(file: File) { - if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { - showAlert('Invalid File', 'Please select a PDF file.'); - return; - } + if ( + file.type !== 'application/pdf' && + !file.name.toLowerCase().endsWith('.pdf') + ) { + showAlert('Invalid File', 'Please select a PDF file.'); + return; + } - signState.file = file; - updateFileDisplay(); - setupSignTool(); + signState.file = file; + updateFileDisplay(); + setupSignTool(); } async function updateFileDisplay() { - const fileDisplayArea = document.getElementById('file-display-area'); + const fileDisplayArea = document.getElementById('file-display-area'); - if (!fileDisplayArea || !signState.file) return; + if (!fileDisplayArea || !signState.file) return; + fileDisplayArea.innerHTML = ''; + + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = signState.file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(signState.file.size)} • ${t('common.loadingPageCount')}`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + signState.file = null; + signState.pdfDoc = null; fileDisplayArea.innerHTML = ''; + document.getElementById('signature-editor')?.classList.add('hidden'); + }; - const fileDiv = document.createElement('div'); - fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col flex-1 min-w-0'; - - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = signState.file.name; - - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(signState.file.size)} • Loading pages...`; - - infoContainer.append(nameSpan, metaSpan); - - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - signState.file = null; - signState.pdfDoc = null; - fileDisplayArea.innerHTML = ''; - document.getElementById('signature-editor')?.classList.add('hidden'); - }; - - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); - - // Load page count - try { - const arrayBuffer = await readFileAsArrayBuffer(signState.file); - const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; - metaSpan.textContent = `${formatBytes(signState.file.size)} • ${pdfDoc.numPages} pages`; - } catch (error) { - console.error('Error loading PDF:', error); - } + // Load page count + try { + const arrayBuffer = await readFileAsArrayBuffer(signState.file); + const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; + metaSpan.textContent = `${formatBytes(signState.file.size)} • ${pdfDoc.numPages} pages`; + } catch (error) { + console.error('Error loading PDF:', error); + } } async function setupSignTool() { - const signatureEditor = document.getElementById('signature-editor'); - if (signatureEditor) { - signatureEditor.classList.remove('hidden'); - } + const signatureEditor = document.getElementById('signature-editor'); + if (signatureEditor) { + signatureEditor.classList.remove('hidden'); + } - showLoader('Loading PDF viewer...'); + showLoader('Loading PDF viewer...'); - const container = document.getElementById('canvas-container-sign'); - if (!container) { - console.error('Sign tool canvas container not found'); - hideLoader(); - return; - } + const container = document.getElementById('canvas-container-sign'); + if (!container) { + console.error('Sign tool canvas container not found'); + hideLoader(); + return; + } - if (!signState.file) { - console.error('No file loaded for signing'); - hideLoader(); - return; - } + if (!signState.file) { + console.error('No file loaded for signing'); + hideLoader(); + return; + } - container.textContent = ''; - const iframe = document.createElement('iframe'); - iframe.style.width = '100%'; - iframe.style.height = '100%'; - iframe.style.border = 'none'; - container.appendChild(iframe); - signState.viewerIframe = iframe; + container.textContent = ''; + const iframe = document.createElement('iframe'); + iframe.style.width = '100%'; + iframe.style.height = '100%'; + iframe.style.border = 'none'; + container.appendChild(iframe); + signState.viewerIframe = iframe; - const pdfBytes = await readFileAsArrayBuffer(signState.file); - const blob = new Blob([pdfBytes as BlobPart], { type: 'application/pdf' }); - signState.blobUrl = URL.createObjectURL(blob); + const pdfBytes = await readFileAsArrayBuffer(signState.file); + const blob = new Blob([pdfBytes as BlobPart], { type: 'application/pdf' }); + signState.blobUrl = URL.createObjectURL(blob); - try { - const existingPrefsRaw = localStorage.getItem('pdfjs.preferences'); - const existingPrefs = existingPrefsRaw ? JSON.parse(existingPrefsRaw) : {}; - delete (existingPrefs as any).annotationEditorMode; - const newPrefs = { - ...existingPrefs, - enableSignatureEditor: true, - enablePermissions: false, - }; - localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs)); - } catch { } - - const viewerUrl = new URL(`${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html`, window.location.origin); - const query = new URLSearchParams({ file: signState.blobUrl }); - iframe.src = `${viewerUrl.toString()}?${query.toString()}`; - - iframe.onload = () => { - hideLoader(); - signState.viewerReady = true; - try { - const viewerWindow: any = iframe.contentWindow; - if (viewerWindow && viewerWindow.PDFViewerApplication) { - const app = viewerWindow.PDFViewerApplication; - const doc = viewerWindow.document; - const eventBus = app.eventBus; - eventBus?._on('annotationeditoruimanager', () => { - const editorModeButtons = doc.getElementById('editorModeButtons'); - editorModeButtons?.classList.remove('hidden'); - const editorSignature = doc.getElementById('editorSignature'); - editorSignature?.removeAttribute('hidden'); - const editorSignatureButton = doc.getElementById('editorSignatureButton') as HTMLButtonElement | null; - if (editorSignatureButton) { - editorSignatureButton.disabled = false; - } - const editorStamp = doc.getElementById('editorStamp'); - editorStamp?.removeAttribute('hidden'); - const editorStampButton = doc.getElementById('editorStampButton') as HTMLButtonElement | null; - if (editorStampButton) { - editorStampButton.disabled = false; - } - try { - const highlightBtn = doc.getElementById('editorHighlightButton') as HTMLButtonElement | null; - highlightBtn?.click(); - } catch { } - }); - } - } catch (e) { - console.error('Could not initialize PDF.js viewer for signing:', e); - } - - const saveBtn = document.getElementById('process-btn') as HTMLButtonElement | null; - if (saveBtn) { - saveBtn.style.display = ''; - } + try { + const existingPrefsRaw = localStorage.getItem('pdfjs.preferences'); + const existingPrefs = existingPrefsRaw ? JSON.parse(existingPrefsRaw) : {}; + delete (existingPrefs as any).annotationEditorMode; + const newPrefs = { + ...existingPrefs, + enableSignatureEditor: true, + enablePermissions: false, }; + localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs)); + } catch {} + + const viewerUrl = new URL( + `${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html`, + window.location.origin + ); + const query = new URLSearchParams({ file: signState.blobUrl }); + iframe.src = `${viewerUrl.toString()}?${query.toString()}`; + + iframe.onload = () => { + hideLoader(); + signState.viewerReady = true; + try { + const viewerWindow: any = iframe.contentWindow; + if (viewerWindow && viewerWindow.PDFViewerApplication) { + const app = viewerWindow.PDFViewerApplication; + const doc = viewerWindow.document; + const eventBus = app.eventBus; + eventBus?._on('annotationeditoruimanager', () => { + const editorModeButtons = doc.getElementById('editorModeButtons'); + editorModeButtons?.classList.remove('hidden'); + const editorSignature = doc.getElementById('editorSignature'); + editorSignature?.removeAttribute('hidden'); + const editorSignatureButton = doc.getElementById( + 'editorSignatureButton' + ) as HTMLButtonElement | null; + if (editorSignatureButton) { + editorSignatureButton.disabled = false; + } + const editorStamp = doc.getElementById('editorStamp'); + editorStamp?.removeAttribute('hidden'); + const editorStampButton = doc.getElementById( + 'editorStampButton' + ) as HTMLButtonElement | null; + if (editorStampButton) { + editorStampButton.disabled = false; + } + try { + const highlightBtn = doc.getElementById( + 'editorHighlightButton' + ) as HTMLButtonElement | null; + highlightBtn?.click(); + } catch {} + }); + } + } catch (e) { + console.error('Could not initialize PDF.js viewer for signing:', e); + } + + const saveBtn = document.getElementById( + 'process-btn' + ) as HTMLButtonElement | null; + if (saveBtn) { + saveBtn.style.display = ''; + } + }; } async function applyAndSaveSignatures() { - if (!signState.viewerReady || !signState.viewerIframe) { - showAlert('Viewer not ready', 'Please wait for the PDF viewer to load.'); - return; + if (!signState.viewerReady || !signState.viewerIframe) { + showAlert('Viewer not ready', 'Please wait for the PDF viewer to load.'); + return; + } + + try { + const viewerWindow: any = signState.viewerIframe.contentWindow; + if (!viewerWindow || !viewerWindow.PDFViewerApplication) { + showAlert('Viewer not ready', 'The PDF viewer is still initializing.'); + return; } - try { - const viewerWindow: any = signState.viewerIframe.contentWindow; - if (!viewerWindow || !viewerWindow.PDFViewerApplication) { - showAlert('Viewer not ready', 'The PDF viewer is still initializing.'); - return; + const app = viewerWindow.PDFViewerApplication; + const flattenCheckbox = document.getElementById( + 'flatten-signature-toggle' + ) as HTMLInputElement | null; + const shouldFlatten = flattenCheckbox?.checked; + + if (shouldFlatten) { + showLoader('Flattening and saving PDF...'); + + const rawPdfBytes = await app.pdfDocument.saveDocument( + app.pdfDocument.annotationStorage + ); + const pdfBytes = new Uint8Array(rawPdfBytes); + const pdfDoc = await PDFDocument.load(pdfBytes); + pdfDoc.getForm().flatten(); + const flattenedPdfBytes = await pdfDoc.save(); + + const blob = new Blob([flattenedPdfBytes as BlobPart], { + type: 'application/pdf', + }); + downloadFile( + blob, + `signed_flattened_${signState.file?.name || 'document.pdf'}` + ); + + hideLoader(); + showAlert('Success', 'Signed PDF saved successfully!', 'success', () => { + resetState(); + }); + } else { + app.eventBus?.dispatch('download', { source: app }); + showAlert( + 'Success', + 'Signed PDF downloaded successfully!', + 'success', + () => { + resetState(); } - - const app = viewerWindow.PDFViewerApplication; - const flattenCheckbox = document.getElementById('flatten-signature-toggle') as HTMLInputElement | null; - const shouldFlatten = flattenCheckbox?.checked; - - if (shouldFlatten) { - showLoader('Flattening and saving PDF...'); - - const rawPdfBytes = await app.pdfDocument.saveDocument(app.pdfDocument.annotationStorage); - const pdfBytes = new Uint8Array(rawPdfBytes); - const pdfDoc = await PDFDocument.load(pdfBytes); - pdfDoc.getForm().flatten(); - const flattenedPdfBytes = await pdfDoc.save(); - - const blob = new Blob([flattenedPdfBytes as BlobPart], { type: 'application/pdf' }); - downloadFile(blob, `signed_flattened_${signState.file?.name || 'document.pdf'}`); - - hideLoader(); - showAlert('Success', 'Signed PDF saved successfully!', 'success', () => { - resetState(); - }); - } else { - app.eventBus?.dispatch('download', { source: app }); - showAlert('Success', 'Signed PDF downloaded successfully!', 'success', () => { - resetState(); - }); - } - } catch (error) { - console.error('Failed to export the signed PDF:', error); - hideLoader(); - showAlert('Export failed', 'Could not export the signed PDF. Please try again.'); + ); } + } catch (error) { + console.error('Failed to export the signed PDF:', error); + hideLoader(); + showAlert( + 'Export failed', + 'Could not export the signed PDF. Please try again.' + ); + } } function resetState() { - cleanup(); - signState.file = null; - signState.viewerIframe = null; - signState.viewerReady = false; + cleanup(); + signState.file = null; + signState.viewerIframe = null; + signState.viewerReady = false; - const signatureEditor = document.getElementById('signature-editor'); - if (signatureEditor) { - signatureEditor.classList.add('hidden'); - } + const signatureEditor = document.getElementById('signature-editor'); + if (signatureEditor) { + signatureEditor.classList.add('hidden'); + } - const container = document.getElementById('canvas-container-sign'); - if (container) { - container.textContent = ''; - } + const container = document.getElementById('canvas-container-sign'); + if (container) { + container.textContent = ''; + } - const fileDisplayArea = document.getElementById('file-display-area'); - if (fileDisplayArea) { - fileDisplayArea.innerHTML = ''; - } + const fileDisplayArea = document.getElementById('file-display-area'); + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + } - const processBtn = document.getElementById('process-btn') as HTMLButtonElement | null; - if (processBtn) { - processBtn.style.display = 'none'; - } + const processBtn = document.getElementById( + 'process-btn' + ) as HTMLButtonElement | null; + if (processBtn) { + processBtn.style.display = 'none'; + } - const flattenCheckbox = document.getElementById('flatten-signature-toggle') as HTMLInputElement | null; - if (flattenCheckbox) { - flattenCheckbox.checked = false; - } + const flattenCheckbox = document.getElementById( + 'flatten-signature-toggle' + ) as HTMLInputElement | null; + if (flattenCheckbox) { + flattenCheckbox.checked = false; + } } function cleanup() { - if (signState.blobUrl) { - URL.revokeObjectURL(signState.blobUrl); - signState.blobUrl = null; - } + if (signState.blobUrl) { + URL.revokeObjectURL(signState.blobUrl); + signState.blobUrl = null; + } } diff --git a/src/js/logic/split-pdf-page.ts b/src/js/logic/split-pdf-page.ts index 699c248..a130815 100644 --- a/src/js/logic/split-pdf-page.ts +++ b/src/js/logic/split-pdf-page.ts @@ -1,4 +1,5 @@ import { showLoader, hideLoader, showAlert } from '../ui.js'; +import { t } from '../i18n/i18n'; import { createIcons, icons } from 'lucide'; import * as pdfjsLib from 'pdfjs-dist'; import { @@ -71,7 +72,7 @@ document.addEventListener('DOMContentLoaded', () => { const metaSpan = document.createElement('div'); metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(file.size)} • Loading pages...`; // Placeholder + metaSpan.textContent = `${formatBytes(file.size)} • ${t('common.loadingPageCount')}`; // Placeholder infoContainer.append(nameSpan, metaSpan);