Add localization for loading page count in various PDF processing pages

This commit is contained in:
Sebastian Espei
2026-03-10 20:13:13 +01:00
parent 571ce07af6
commit 2ede256de2
20 changed files with 944 additions and 806 deletions

View File

@@ -251,6 +251,7 @@
"add": "Hinzufügen", "add": "Hinzufügen",
"remove": "Entfernen", "remove": "Entfernen",
"loading": "Laden...", "loading": "Laden...",
"loadingPageCount": "Seitenanzahl wird geladen...",
"error": "Fehler", "error": "Fehler",
"success": "Erfolg", "success": "Erfolg",
"file": "Datei", "file": "Datei",

View File

@@ -260,6 +260,7 @@
"add": "Add", "add": "Add",
"remove": "Remove", "remove": "Remove",
"loading": "Loading...", "loading": "Loading...",
"loadingPageCount": "Loading pages...",
"error": "Error", "error": "Error",
"success": "Success", "success": "Success",
"file": "File", "file": "File",

View File

@@ -124,7 +124,7 @@ const updateUI = () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -1,13 +1,23 @@
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { showAlert, showLoader, hideLoader } from '../ui.js'; 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 { t } from '../i18n/i18n';
import { import {
signPdf, signPdf,
parsePfxFile, parsePfxFile,
parseCombinedPem, parseCombinedPem,
getCertificateInfo, getCertificateInfo,
} from './digital-sign-pdf.js'; } from './digital-sign-pdf.js';
import { SignatureInfo, VisibleSignatureOptions, DigitalSignState } from '@/types'; import {
SignatureInfo,
VisibleSignatureOptions,
DigitalSignState,
} from '@/types';
const state: DigitalSignState = { const state: DigitalSignState = {
pdfFile: null, pdfFile: null,
@@ -179,11 +189,17 @@ function initializePage(): void {
const file = input.files[0]; const file = input.files[0];
const validTypes = ['image/png', 'image/jpeg', 'image/webp']; const validTypes = ['image/png', 'image/jpeg', 'image/webp'];
if (!validTypes.includes(file.type)) { if (!validTypes.includes(file.type)) {
showAlert('Invalid Image', 'Please select a PNG, JPG, or WebP image.'); showAlert(
'Invalid Image',
'Please select a PNG, JPG, or WebP image.'
);
return; return;
} }
state.sigImageData = await readFileAsArrayBuffer(file) as ArrayBuffer; state.sigImageData = (await readFileAsArrayBuffer(file)) as ArrayBuffer;
state.sigImageType = file.type.replace('image/', '') as 'png' | 'jpeg' | 'webp'; state.sigImageType = file.type.replace('image/', '') as
| 'png'
| 'jpeg'
| 'webp';
if (sigImageThumb && sigImagePreview) { if (sigImageThumb && sigImagePreview) {
const url = URL.createObjectURL(file); const url = URL.createObjectURL(file);
@@ -222,13 +238,18 @@ function handlePdfUpload(e: Event): void {
} }
async function handlePdfFile(file: File): Promise<void> { async function handlePdfFile(file: File): Promise<void> {
if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { if (
file.type !== 'application/pdf' &&
!file.name.toLowerCase().endsWith('.pdf')
) {
showAlert('Invalid File', 'Please select a PDF file.'); showAlert('Invalid File', 'Please select a PDF file.');
return; return;
} }
state.pdfFile = file; state.pdfFile = file;
state.pdfBytes = new Uint8Array(await readFileAsArrayBuffer(file) as ArrayBuffer); state.pdfBytes = new Uint8Array(
(await readFileAsArrayBuffer(file)) as ArrayBuffer
);
updatePdfDisplay(); updatePdfDisplay();
showCertificateSection(); showCertificateSection();
@@ -242,7 +263,8 @@ async function updatePdfDisplay(): Promise<void> {
fileDisplayArea.innerHTML = ''; fileDisplayArea.innerHTML = '';
const fileDiv = document.createElement('div'); const fileDiv = document.createElement('div');
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; fileDiv.className =
'flex items-center justify-between bg-gray-700 p-3 rounded-lg';
const infoContainer = document.createElement('div'); const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col flex-1 min-w-0'; infoContainer.className = 'flex flex-col flex-1 min-w-0';
@@ -253,7 +275,7 @@ async function updatePdfDisplay(): Promise<void> {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; metaSpan.className = 'text-xs text-gray-400';
metaSpan.textContent = `${formatBytes(state.pdfFile.size)}Loading pages...`; metaSpan.textContent = `${formatBytes(state.pdfFile.size)}${t('common.loadingPageCount')}`;
infoContainer.append(nameSpan, metaSpan); infoContainer.append(nameSpan, metaSpan);
@@ -274,7 +296,8 @@ async function updatePdfDisplay(): Promise<void> {
try { try {
if (state.pdfBytes) { if (state.pdfBytes) {
const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() }).promise; const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() })
.promise;
metaSpan.textContent = `${formatBytes(state.pdfFile.size)}${pdfDoc.numPages} pages`; metaSpan.textContent = `${formatBytes(state.pdfFile.size)}${pdfDoc.numPages} pages`;
} }
} catch (error) { } catch (error) {
@@ -314,7 +337,9 @@ function hideCertificateSection(): void {
certInfo.classList.add('hidden'); certInfo.classList.add('hidden');
} }
const certPasswordSection = getElement<HTMLDivElement>('cert-password-section'); const certPasswordSection = getElement<HTMLDivElement>(
'cert-password-section'
);
if (certPasswordSection) { if (certPasswordSection) {
certPasswordSection.classList.add('hidden'); certPasswordSection.classList.add('hidden');
} }
@@ -329,12 +354,15 @@ function handleCertUpload(e: Event): void {
async function handleCertFile(file: File): Promise<void> { async function handleCertFile(file: File): Promise<void> {
const validExtensions = ['.pfx', '.p12', '.pem']; const validExtensions = ['.pfx', '.p12', '.pem'];
const hasValidExtension = validExtensions.some(ext => const hasValidExtension = validExtensions.some((ext) =>
file.name.toLowerCase().endsWith(ext) file.name.toLowerCase().endsWith(ext)
); );
if (!hasValidExtension) { if (!hasValidExtension) {
showAlert('Invalid Certificate', 'Please select a .pfx, .p12, or .pem certificate file.'); showAlert(
'Invalid Certificate',
'Please select a .pfx, .p12, or .pem certificate file.'
);
return; return;
} }
@@ -360,7 +388,8 @@ async function handleCertFile(file: File): Promise<void> {
const certStatus = getElement<HTMLDivElement>('cert-status'); const certStatus = getElement<HTMLDivElement>('cert-status');
if (certStatus) { if (certStatus) {
certStatus.innerHTML = 'Certificate loaded <i data-lucide="check" class="inline w-4 h-4"></i>'; certStatus.innerHTML =
'Certificate loaded <i data-lucide="check" class="inline w-4 h-4"></i>';
createIcons({ icons }); createIcons({ icons });
certStatus.className = 'text-xs text-green-400'; certStatus.className = 'text-xs text-green-400';
} }
@@ -389,7 +418,8 @@ function updateCertDisplay(): void {
certDisplayArea.innerHTML = ''; certDisplayArea.innerHTML = '';
const certDiv = document.createElement('div'); const certDiv = document.createElement('div');
certDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; certDiv.className =
'flex items-center justify-between bg-gray-700 p-3 rounded-lg';
const infoContainer = document.createElement('div'); const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col flex-1 min-w-0'; infoContainer.className = 'flex flex-col flex-1 min-w-0';
@@ -424,7 +454,9 @@ function updateCertDisplay(): void {
} }
function showPasswordSection(): void { function showPasswordSection(): void {
const certPasswordSection = getElement<HTMLDivElement>('cert-password-section'); const certPasswordSection = getElement<HTMLDivElement>(
'cert-password-section'
);
if (certPasswordSection) { if (certPasswordSection) {
certPasswordSection.classList.remove('hidden'); certPasswordSection.classList.remove('hidden');
} }
@@ -444,7 +476,9 @@ function updatePasswordLabel(labelText: string): void {
} }
function hidePasswordSection(): void { function hidePasswordSection(): void {
const certPasswordSection = getElement<HTMLDivElement>('cert-password-section'); const certPasswordSection = getElement<HTMLDivElement>(
'cert-password-section'
);
if (certPasswordSection) { if (certPasswordSection) {
certPasswordSection.classList.add('hidden'); certPasswordSection.classList.add('hidden');
} }
@@ -455,7 +489,9 @@ function showSignatureOptions(): void {
if (signatureOptions) { if (signatureOptions) {
signatureOptions.classList.remove('hidden'); signatureOptions.classList.remove('hidden');
} }
const visibleSigSection = getElement<HTMLDivElement>('visible-signature-section'); const visibleSigSection = getElement<HTMLDivElement>(
'visible-signature-section'
);
if (visibleSigSection) { if (visibleSigSection) {
visibleSigSection.classList.remove('hidden'); visibleSigSection.classList.remove('hidden');
} }
@@ -466,7 +502,9 @@ function hideSignatureOptions(): void {
if (signatureOptions) { if (signatureOptions) {
signatureOptions.classList.add('hidden'); signatureOptions.classList.add('hidden');
} }
const visibleSigSection = getElement<HTMLDivElement>('visible-signature-section'); const visibleSigSection = getElement<HTMLDivElement>(
'visible-signature-section'
);
if (visibleSigSection) { if (visibleSigSection) {
visibleSigSection.classList.add('hidden'); visibleSigSection.classList.add('hidden');
} }
@@ -494,7 +532,9 @@ async function handlePasswordInput(): Promise<void> {
const pemContent = await state.certFile.text(); const pemContent = await state.certFile.text();
state.certData = parseCombinedPem(pemContent, password); state.certData = parseCombinedPem(pemContent, password);
} else { } else {
const certBytes = await readFileAsArrayBuffer(state.certFile) as ArrayBuffer; const certBytes = (await readFileAsArrayBuffer(
state.certFile
)) as ArrayBuffer;
state.certData = parsePfxFile(certBytes, password); state.certData = parsePfxFile(certBytes, password);
} }
@@ -504,7 +544,8 @@ async function handlePasswordInput(): Promise<void> {
const certStatus = getElement<HTMLDivElement>('cert-status'); const certStatus = getElement<HTMLDivElement>('cert-status');
if (certStatus) { if (certStatus) {
certStatus.innerHTML = 'Certificate unlocked <i data-lucide="check-circle" class="inline w-4 h-4"></i>'; certStatus.innerHTML =
'Certificate unlocked <i data-lucide="check-circle" class="inline w-4 h-4"></i>';
createIcons({ icons }); createIcons({ icons });
certStatus.className = 'text-xs text-green-400'; certStatus.className = 'text-xs text-green-400';
} }
@@ -515,7 +556,10 @@ async function handlePasswordInput(): Promise<void> {
const certStatus = getElement<HTMLDivElement>('cert-status'); const certStatus = getElement<HTMLDivElement>('cert-status');
if (certStatus) { if (certStatus) {
const errorMessage = error instanceof Error ? error.message : 'Invalid password or certificate'; const errorMessage =
error instanceof Error
? error.message
: 'Invalid password or certificate';
certStatus.textContent = errorMessage.includes('password') certStatus.textContent = errorMessage.includes('password')
? 'Incorrect password' ? 'Incorrect password'
: 'Failed to parse certificate'; : 'Failed to parse certificate';
@@ -565,7 +609,10 @@ function updateProcessButton(): void {
async function processSignature(): Promise<void> { async function processSignature(): Promise<void> {
if (!state.pdfBytes || !state.certData) { if (!state.pdfBytes || !state.certData) {
showAlert('Missing Data', 'Please upload both a PDF and a valid certificate.'); showAlert(
'Missing Data',
'Please upload both a PDF and a valid certificate.'
);
return; return;
} }
@@ -582,17 +629,30 @@ async function processSignature(): Promise<void> {
const enableVisibleSig = getElement<HTMLInputElement>('enable-visible-sig'); const enableVisibleSig = getElement<HTMLInputElement>('enable-visible-sig');
if (enableVisibleSig?.checked) { if (enableVisibleSig?.checked) {
const sigX = parseInt(getElement<HTMLInputElement>('sig-x')?.value ?? '25', 10); const sigX = parseInt(
const sigY = parseInt(getElement<HTMLInputElement>('sig-y')?.value ?? '700', 10); getElement<HTMLInputElement>('sig-x')?.value ?? '25',
const sigWidth = parseInt(getElement<HTMLInputElement>('sig-width')?.value ?? '150', 10); 10
const sigHeight = parseInt(getElement<HTMLInputElement>('sig-height')?.value ?? '70', 10); );
const sigY = parseInt(
getElement<HTMLInputElement>('sig-y')?.value ?? '700',
10
);
const sigWidth = parseInt(
getElement<HTMLInputElement>('sig-width')?.value ?? '150',
10
);
const sigHeight = parseInt(
getElement<HTMLInputElement>('sig-height')?.value ?? '70',
10
);
const sigPageSelect = getElement<HTMLSelectElement>('sig-page'); const sigPageSelect = getElement<HTMLSelectElement>('sig-page');
let sigPage: number | string = 0; let sigPage: number | string = 0;
let numPages = 1; let numPages = 1;
try { try {
const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() }).promise; const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() })
.promise;
numPages = pdfDoc.numPages; numPages = pdfDoc.numPages;
} catch (error) { } catch (error) {
console.error('Error getting PDF page count:', error); console.error('Error getting PDF page count:', error);
@@ -608,16 +668,26 @@ async function processSignature(): Promise<void> {
sigPage = `0-${numPages - 1}`; sigPage = `0-${numPages - 1}`;
} }
} else if (sigPageSelect.value === 'custom') { } else if (sigPageSelect.value === 'custom') {
sigPage = parseInt(getElement<HTMLInputElement>('sig-custom-page')?.value ?? '1', 10) - 1; sigPage =
parseInt(
getElement<HTMLInputElement>('sig-custom-page')?.value ?? '1',
10
) - 1;
} else { } else {
sigPage = parseInt(sigPageSelect.value, 10); sigPage = parseInt(sigPageSelect.value, 10);
} }
} }
const enableSigText = getElement<HTMLInputElement>('enable-sig-text'); const enableSigText = getElement<HTMLInputElement>('enable-sig-text');
let sigText = enableSigText?.checked ? getElement<HTMLInputElement>('sig-text')?.value : undefined; let sigText = enableSigText?.checked
const sigTextColor = getElement<HTMLInputElement>('sig-text-color')?.value ?? '#000000'; ? getElement<HTMLInputElement>('sig-text')?.value
const sigTextSize = parseInt(getElement<HTMLInputElement>('sig-text-size')?.value ?? '12', 10); : undefined;
const sigTextColor =
getElement<HTMLInputElement>('sig-text-color')?.value ?? '#000000';
const sigTextSize = parseInt(
getElement<HTMLInputElement>('sig-text-size')?.value ?? '12',
10
);
if (!state.sigImageData && !sigText && state.certData) { if (!state.sigImageData && !sigText && state.certData) {
const certInfo = getCertificateInfo(state.certData.certificate); const certInfo = getCertificateInfo(state.certData.certificate);
@@ -630,7 +700,9 @@ async function processSignature(): Promise<void> {
const lineCount = (sigText.match(/\n/g) || []).length + 1; const lineCount = (sigText.match(/\n/g) || []).length + 1;
const lineHeightFactor = 1.4; const lineHeightFactor = 1.4;
const padding = 16; const padding = 16;
const calculatedHeight = Math.ceil(lineCount * sigTextSize * lineHeightFactor + padding); const calculatedHeight = Math.ceil(
lineCount * sigTextSize * lineHeightFactor + padding
);
finalHeight = Math.max(calculatedHeight, sigHeight); finalHeight = Math.max(calculatedHeight, sigHeight);
} }
@@ -657,21 +729,35 @@ async function processSignature(): Promise<void> {
visibleSignature, visibleSignature,
}); });
const blob = new Blob([signedPdfBytes.slice().buffer], { type: 'application/pdf' }); const blob = new Blob([signedPdfBytes.slice().buffer], {
type: 'application/pdf',
});
const originalName = state.pdfFile?.name ?? 'document.pdf'; const originalName = state.pdfFile?.name ?? 'document.pdf';
const signedName = originalName.replace(/\.pdf$/i, '_signed.pdf'); const signedName = originalName.replace(/\.pdf$/i, '_signed.pdf');
downloadFile(blob, signedName); downloadFile(blob, signedName);
hideLoader(); hideLoader();
showAlert('Success', 'PDF signed successfully! The signature can be verified in any PDF reader.', 'success', () => { resetState(); }); showAlert(
'Success',
'PDF signed successfully! The signature can be verified in any PDF reader.',
'success',
() => {
resetState();
}
);
} catch (error) { } catch (error) {
hideLoader(); hideLoader();
console.error('Signing error:', error); console.error('Signing error:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; const errorMessage =
error instanceof Error ? error.message : 'Unknown error occurred';
// Check if this is a CORS/network error from certificate chain fetching // Check if this is a CORS/network error from certificate chain fetching
if (errorMessage.includes('Failed to fetch') || errorMessage.includes('CORS') || errorMessage.includes('NetworkError')) { if (
errorMessage.includes('Failed to fetch') ||
errorMessage.includes('CORS') ||
errorMessage.includes('NetworkError')
) {
showAlert( showAlert(
'Signing Failed', '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.' '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.'

View File

@@ -1,4 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { t } from '../i18n/i18n';
import { import {
downloadFile, downloadFile,
readFileAsArrayBuffer, readFileAsArrayBuffer,
@@ -66,7 +67,7 @@ document.addEventListener('DOMContentLoaded', () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -1,4 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { t } from '../i18n/i18n';
import { import {
downloadFile, downloadFile,
readFileAsArrayBuffer, readFileAsArrayBuffer,
@@ -60,7 +61,7 @@ document.addEventListener('DOMContentLoaded', () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -45,7 +45,7 @@ const updateUI = () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -1,4 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { t } from '../i18n/i18n';
import { import {
downloadFile, downloadFile,
readFileAsArrayBuffer, readFileAsArrayBuffer,
@@ -50,7 +51,7 @@ document.addEventListener('DOMContentLoaded', () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -45,7 +45,7 @@ const updateUI = () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -45,7 +45,7 @@ const updateUI = () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -1,4 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { t } from '../i18n/i18n';
import { import {
downloadFile, downloadFile,
readFileAsArrayBuffer, readFileAsArrayBuffer,
@@ -53,7 +54,7 @@ document.addEventListener('DOMContentLoaded', () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -1,4 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { t } from '../i18n/i18n';
import { import {
downloadFile, downloadFile,
readFileAsArrayBuffer, readFileAsArrayBuffer,
@@ -53,7 +54,7 @@ document.addEventListener('DOMContentLoaded', () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -45,7 +45,7 @@ const updateUI = () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -46,7 +46,7 @@ const updateUI = () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -45,7 +45,7 @@ const updateUI = () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -1,4 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { t } from '../i18n/i18n';
import { import {
downloadFile, downloadFile,
readFileAsArrayBuffer, readFileAsArrayBuffer,
@@ -50,7 +51,7 @@ document.addEventListener('DOMContentLoaded', () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -1,4 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { t } from '../i18n/i18n';
import { import {
downloadFile, downloadFile,
readFileAsArrayBuffer, readFileAsArrayBuffer,
@@ -50,7 +51,7 @@ document.addEventListener('DOMContentLoaded', () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -130,7 +130,7 @@ const updateUI = () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);

View File

@@ -1,7 +1,13 @@
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import { showAlert, showLoader, hideLoader } from '../ui.js'; 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 { PDFDocument } from 'pdf-lib';
import { t } from '../i18n/i18n';
interface SignState { interface SignState {
file: File | null; file: File | null;
@@ -79,7 +85,10 @@ function handleFileUpload(e: Event) {
} }
function handleFile(file: File) { function handleFile(file: File) {
if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { if (
file.type !== 'application/pdf' &&
!file.name.toLowerCase().endsWith('.pdf')
) {
showAlert('Invalid File', 'Please select a PDF file.'); showAlert('Invalid File', 'Please select a PDF file.');
return; return;
} }
@@ -97,7 +106,8 @@ async function updateFileDisplay() {
fileDisplayArea.innerHTML = ''; fileDisplayArea.innerHTML = '';
const fileDiv = document.createElement('div'); const fileDiv = document.createElement('div');
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; fileDiv.className =
'flex items-center justify-between bg-gray-700 p-3 rounded-lg';
const infoContainer = document.createElement('div'); const infoContainer = document.createElement('div');
infoContainer.className = 'flex flex-col flex-1 min-w-0'; infoContainer.className = 'flex flex-col flex-1 min-w-0';
@@ -108,7 +118,7 @@ async function updateFileDisplay() {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; metaSpan.className = 'text-xs text-gray-400';
metaSpan.textContent = `${formatBytes(signState.file.size)}Loading pages...`; metaSpan.textContent = `${formatBytes(signState.file.size)}${t('common.loadingPageCount')}`;
infoContainer.append(nameSpan, metaSpan); infoContainer.append(nameSpan, metaSpan);
@@ -181,7 +191,10 @@ async function setupSignTool() {
localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs)); localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs));
} catch {} } catch {}
const viewerUrl = new URL(`${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html`, window.location.origin); const viewerUrl = new URL(
`${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html`,
window.location.origin
);
const query = new URLSearchParams({ file: signState.blobUrl }); const query = new URLSearchParams({ file: signState.blobUrl });
iframe.src = `${viewerUrl.toString()}?${query.toString()}`; iframe.src = `${viewerUrl.toString()}?${query.toString()}`;
@@ -199,18 +212,24 @@ async function setupSignTool() {
editorModeButtons?.classList.remove('hidden'); editorModeButtons?.classList.remove('hidden');
const editorSignature = doc.getElementById('editorSignature'); const editorSignature = doc.getElementById('editorSignature');
editorSignature?.removeAttribute('hidden'); editorSignature?.removeAttribute('hidden');
const editorSignatureButton = doc.getElementById('editorSignatureButton') as HTMLButtonElement | null; const editorSignatureButton = doc.getElementById(
'editorSignatureButton'
) as HTMLButtonElement | null;
if (editorSignatureButton) { if (editorSignatureButton) {
editorSignatureButton.disabled = false; editorSignatureButton.disabled = false;
} }
const editorStamp = doc.getElementById('editorStamp'); const editorStamp = doc.getElementById('editorStamp');
editorStamp?.removeAttribute('hidden'); editorStamp?.removeAttribute('hidden');
const editorStampButton = doc.getElementById('editorStampButton') as HTMLButtonElement | null; const editorStampButton = doc.getElementById(
'editorStampButton'
) as HTMLButtonElement | null;
if (editorStampButton) { if (editorStampButton) {
editorStampButton.disabled = false; editorStampButton.disabled = false;
} }
try { try {
const highlightBtn = doc.getElementById('editorHighlightButton') as HTMLButtonElement | null; const highlightBtn = doc.getElementById(
'editorHighlightButton'
) as HTMLButtonElement | null;
highlightBtn?.click(); highlightBtn?.click();
} catch {} } catch {}
}); });
@@ -219,7 +238,9 @@ async function setupSignTool() {
console.error('Could not initialize PDF.js viewer for signing:', e); console.error('Could not initialize PDF.js viewer for signing:', e);
} }
const saveBtn = document.getElementById('process-btn') as HTMLButtonElement | null; const saveBtn = document.getElementById(
'process-btn'
) as HTMLButtonElement | null;
if (saveBtn) { if (saveBtn) {
saveBtn.style.display = ''; saveBtn.style.display = '';
} }
@@ -240,20 +261,29 @@ async function applyAndSaveSignatures() {
} }
const app = viewerWindow.PDFViewerApplication; const app = viewerWindow.PDFViewerApplication;
const flattenCheckbox = document.getElementById('flatten-signature-toggle') as HTMLInputElement | null; const flattenCheckbox = document.getElementById(
'flatten-signature-toggle'
) as HTMLInputElement | null;
const shouldFlatten = flattenCheckbox?.checked; const shouldFlatten = flattenCheckbox?.checked;
if (shouldFlatten) { if (shouldFlatten) {
showLoader('Flattening and saving PDF...'); showLoader('Flattening and saving PDF...');
const rawPdfBytes = await app.pdfDocument.saveDocument(app.pdfDocument.annotationStorage); const rawPdfBytes = await app.pdfDocument.saveDocument(
app.pdfDocument.annotationStorage
);
const pdfBytes = new Uint8Array(rawPdfBytes); const pdfBytes = new Uint8Array(rawPdfBytes);
const pdfDoc = await PDFDocument.load(pdfBytes); const pdfDoc = await PDFDocument.load(pdfBytes);
pdfDoc.getForm().flatten(); pdfDoc.getForm().flatten();
const flattenedPdfBytes = await pdfDoc.save(); const flattenedPdfBytes = await pdfDoc.save();
const blob = new Blob([flattenedPdfBytes as BlobPart], { type: 'application/pdf' }); const blob = new Blob([flattenedPdfBytes as BlobPart], {
downloadFile(blob, `signed_flattened_${signState.file?.name || 'document.pdf'}`); type: 'application/pdf',
});
downloadFile(
blob,
`signed_flattened_${signState.file?.name || 'document.pdf'}`
);
hideLoader(); hideLoader();
showAlert('Success', 'Signed PDF saved successfully!', 'success', () => { showAlert('Success', 'Signed PDF saved successfully!', 'success', () => {
@@ -261,14 +291,22 @@ async function applyAndSaveSignatures() {
}); });
} else { } else {
app.eventBus?.dispatch('download', { source: app }); app.eventBus?.dispatch('download', { source: app });
showAlert('Success', 'Signed PDF downloaded successfully!', 'success', () => { showAlert(
'Success',
'Signed PDF downloaded successfully!',
'success',
() => {
resetState(); resetState();
}); }
);
} }
} catch (error) { } catch (error) {
console.error('Failed to export the signed PDF:', error); console.error('Failed to export the signed PDF:', error);
hideLoader(); hideLoader();
showAlert('Export failed', 'Could not export the signed PDF. Please try again.'); showAlert(
'Export failed',
'Could not export the signed PDF. Please try again.'
);
} }
} }
@@ -293,12 +331,16 @@ function resetState() {
fileDisplayArea.innerHTML = ''; fileDisplayArea.innerHTML = '';
} }
const processBtn = document.getElementById('process-btn') as HTMLButtonElement | null; const processBtn = document.getElementById(
'process-btn'
) as HTMLButtonElement | null;
if (processBtn) { if (processBtn) {
processBtn.style.display = 'none'; processBtn.style.display = 'none';
} }
const flattenCheckbox = document.getElementById('flatten-signature-toggle') as HTMLInputElement | null; const flattenCheckbox = document.getElementById(
'flatten-signature-toggle'
) as HTMLInputElement | null;
if (flattenCheckbox) { if (flattenCheckbox) {
flattenCheckbox.checked = false; flattenCheckbox.checked = false;
} }

View File

@@ -1,4 +1,5 @@
import { showLoader, hideLoader, showAlert } from '../ui.js'; import { showLoader, hideLoader, showAlert } from '../ui.js';
import { t } from '../i18n/i18n';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';
import { import {
@@ -71,7 +72,7 @@ document.addEventListener('DOMContentLoaded', () => {
const metaSpan = document.createElement('div'); const metaSpan = document.createElement('div');
metaSpan.className = 'text-xs text-gray-400'; 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); infoContainer.append(nameSpan, metaSpan);