From 3093dcbf4a5d830207a3a107980bf9ec431a7a04 Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 17:26:08 +0100
Subject: [PATCH 01/14] Add convert strings
---
public/locales/de/tools.json | 15 ++++++++++-----
public/locales/en/tools.json | 15 ++++++++++-----
2 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/public/locales/de/tools.json b/public/locales/de/tools.json
index ea93f3a0..02335154 100644
--- a/public/locales/de/tools.json
+++ b/public/locales/de/tools.json
@@ -204,23 +204,28 @@
},
"pdfToJpg": {
"name": "PDF zu JPG",
- "subtitle": "Jede PDF-Seite in ein JPG-Bild konvertieren."
+ "subtitle": "Jede PDF-Seite in ein JPG-Bild konvertieren.",
+ "convertButton": "Konvertieren"
},
"pdfToPng": {
"name": "PDF zu PNG",
- "subtitle": "Jede PDF-Seite in ein PNG-Bild konvertieren."
+ "subtitle": "Jede PDF-Seite in ein PNG-Bild konvertieren.",
+ "convertButton": "Konvertieren"
},
"pdfToWebp": {
"name": "PDF zu WebP",
- "subtitle": "Jede PDF-Seite in ein WebP-Bild konvertieren."
+ "subtitle": "Jede PDF-Seite in ein WebP-Bild konvertieren.",
+ "convertButton": "Konvertieren"
},
"pdfToBmp": {
"name": "PDF zu BMP",
- "subtitle": "Jede PDF-Seite in ein BMP-Bild konvertieren."
+ "subtitle": "Jede PDF-Seite in ein BMP-Bild konvertieren.",
+ "convertButton": "Konvertieren"
},
"pdfToTiff": {
"name": "PDF zu TIFF",
- "subtitle": "Jede PDF-Seite in ein TIFF-Bild konvertieren."
+ "subtitle": "Jede PDF-Seite in ein TIFF-Bild konvertieren.",
+ "convertButton": "Konvertieren"
},
"pdfToGreyscale": {
"name": "PDF zu Graustufen",
diff --git a/public/locales/en/tools.json b/public/locales/en/tools.json
index 9783beef..fc2f1a82 100644
--- a/public/locales/en/tools.json
+++ b/public/locales/en/tools.json
@@ -204,23 +204,28 @@
},
"pdfToJpg": {
"name": "PDF to JPG",
- "subtitle": "Convert each PDF page into a JPG image."
+ "subtitle": "Convert each PDF page into a JPG image.",
+ "convertButton": "Convert"
},
"pdfToPng": {
"name": "PDF to PNG",
- "subtitle": "Convert each PDF page into a PNG image."
+ "subtitle": "Convert each PDF page into a PNG image.",
+ "convertButton": "Convert"
},
"pdfToWebp": {
"name": "PDF to WebP",
- "subtitle": "Convert each PDF page into a WebP image."
+ "subtitle": "Convert each PDF page into a WebP image.",
+ "convertButton": "Convert"
},
"pdfToBmp": {
"name": "PDF to BMP",
- "subtitle": "Convert each PDF page into a BMP image."
+ "subtitle": "Convert each PDF page into a BMP image.",
+ "convertButton": "Convert"
},
"pdfToTiff": {
"name": "PDF to TIFF",
- "subtitle": "Convert each PDF page into a TIFF image."
+ "subtitle": "Convert each PDF page into a TIFF image.",
+ "convertButton": "Convert"
},
"pdfToGreyscale": {
"name": "PDF to Greyscale",
From e21ee9605bdb1184b855d56bf819e7a4b6930103 Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 17:44:11 +0100
Subject: [PATCH 02/14] Centralize convert local string into common
This reverts commit 3093dcbf4a5d830207a3a107980bf9ec431a7a04.
---
public/locales/de/common.json | 3 ++-
public/locales/de/tools.json | 15 +++++----------
public/locales/en/common.json | 3 ++-
public/locales/en/tools.json | 15 +++++----------
4 files changed, 14 insertions(+), 22 deletions(-)
diff --git a/public/locales/de/common.json b/public/locales/de/common.json
index 70ec209b..1aa64f41 100644
--- a/public/locales/de/common.json
+++ b/public/locales/de/common.json
@@ -255,7 +255,8 @@
"success": "Erfolg",
"file": "Datei",
"files": "Dateien",
- "close": "Schließen"
+ "close": "Schließen",
+ "convert": "Konvertieren"
},
"about": {
"hero": {
diff --git a/public/locales/de/tools.json b/public/locales/de/tools.json
index 02335154..ea93f3a0 100644
--- a/public/locales/de/tools.json
+++ b/public/locales/de/tools.json
@@ -204,28 +204,23 @@
},
"pdfToJpg": {
"name": "PDF zu JPG",
- "subtitle": "Jede PDF-Seite in ein JPG-Bild konvertieren.",
- "convertButton": "Konvertieren"
+ "subtitle": "Jede PDF-Seite in ein JPG-Bild konvertieren."
},
"pdfToPng": {
"name": "PDF zu PNG",
- "subtitle": "Jede PDF-Seite in ein PNG-Bild konvertieren.",
- "convertButton": "Konvertieren"
+ "subtitle": "Jede PDF-Seite in ein PNG-Bild konvertieren."
},
"pdfToWebp": {
"name": "PDF zu WebP",
- "subtitle": "Jede PDF-Seite in ein WebP-Bild konvertieren.",
- "convertButton": "Konvertieren"
+ "subtitle": "Jede PDF-Seite in ein WebP-Bild konvertieren."
},
"pdfToBmp": {
"name": "PDF zu BMP",
- "subtitle": "Jede PDF-Seite in ein BMP-Bild konvertieren.",
- "convertButton": "Konvertieren"
+ "subtitle": "Jede PDF-Seite in ein BMP-Bild konvertieren."
},
"pdfToTiff": {
"name": "PDF zu TIFF",
- "subtitle": "Jede PDF-Seite in ein TIFF-Bild konvertieren.",
- "convertButton": "Konvertieren"
+ "subtitle": "Jede PDF-Seite in ein TIFF-Bild konvertieren."
},
"pdfToGreyscale": {
"name": "PDF zu Graustufen",
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 475ee649..50adeacb 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -264,7 +264,8 @@
"success": "Success",
"file": "File",
"files": "Files",
- "close": "Close"
+ "close": "Close",
+ "convert": "Convert"
},
"about": {
"hero": {
diff --git a/public/locales/en/tools.json b/public/locales/en/tools.json
index fc2f1a82..9783beef 100644
--- a/public/locales/en/tools.json
+++ b/public/locales/en/tools.json
@@ -204,28 +204,23 @@
},
"pdfToJpg": {
"name": "PDF to JPG",
- "subtitle": "Convert each PDF page into a JPG image.",
- "convertButton": "Convert"
+ "subtitle": "Convert each PDF page into a JPG image."
},
"pdfToPng": {
"name": "PDF to PNG",
- "subtitle": "Convert each PDF page into a PNG image.",
- "convertButton": "Convert"
+ "subtitle": "Convert each PDF page into a PNG image."
},
"pdfToWebp": {
"name": "PDF to WebP",
- "subtitle": "Convert each PDF page into a WebP image.",
- "convertButton": "Convert"
+ "subtitle": "Convert each PDF page into a WebP image."
},
"pdfToBmp": {
"name": "PDF to BMP",
- "subtitle": "Convert each PDF page into a BMP image.",
- "convertButton": "Convert"
+ "subtitle": "Convert each PDF page into a BMP image."
},
"pdfToTiff": {
"name": "PDF to TIFF",
- "subtitle": "Convert each PDF page into a TIFF image.",
- "convertButton": "Convert"
+ "subtitle": "Convert each PDF page into a TIFF image."
},
"pdfToGreyscale": {
"name": "PDF to Greyscale",
From b479abf6cfb08686f85fc4987f094183ce6630f4 Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 17:49:03 +0100
Subject: [PATCH 03/14] Add localization to "download" button
---
src/pages/pdf-to-bmp.html | 8 ++++++--
src/pages/pdf-to-jpg.html | 8 ++++++--
src/pages/pdf-to-png.html | 8 ++++++--
src/pages/pdf-to-tiff.html | 8 ++++++--
src/pages/pdf-to-webp.html | 8 ++++++--
5 files changed, 30 insertions(+), 10 deletions(-)
diff --git a/src/pages/pdf-to-bmp.html b/src/pages/pdf-to-bmp.html
index 31c8c87d..75c674c0 100644
--- a/src/pages/pdf-to-bmp.html
+++ b/src/pages/pdf-to-bmp.html
@@ -159,8 +159,12 @@
-
diff --git a/src/pages/pdf-to-jpg.html b/src/pages/pdf-to-jpg.html
index dcee91c8..2c8d63b3 100644
--- a/src/pages/pdf-to-jpg.html
+++ b/src/pages/pdf-to-jpg.html
@@ -189,8 +189,12 @@
-
- Download All
+
+ Convert
diff --git a/src/pages/pdf-to-png.html b/src/pages/pdf-to-png.html
index 08532930..c0d9c695 100644
--- a/src/pages/pdf-to-png.html
+++ b/src/pages/pdf-to-png.html
@@ -186,8 +186,12 @@
-
- Download All
+
+ Convert
diff --git a/src/pages/pdf-to-tiff.html b/src/pages/pdf-to-tiff.html
index f4292dc8..8980324e 100644
--- a/src/pages/pdf-to-tiff.html
+++ b/src/pages/pdf-to-tiff.html
@@ -154,8 +154,12 @@
-
- Download All
+
+ Convert
diff --git a/src/pages/pdf-to-webp.html b/src/pages/pdf-to-webp.html
index 0501482a..a1cc403f 100644
--- a/src/pages/pdf-to-webp.html
+++ b/src/pages/pdf-to-webp.html
@@ -186,8 +186,12 @@
-
- Download All
+
+ Convert
From 667385ee1c3008e5f876f0bbc62ec322b470ec9b Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 18:24:31 +0100
Subject: [PATCH 04/14] Add localization to fileDisplayArea
---
src/js/logic/adjust-colors-page.ts | 3 ++-
src/js/logic/pdf-to-bmp-page.ts | 3 ++-
src/js/logic/pdf-to-greyscale-page.ts | 3 ++-
src/js/logic/pdf-to-jpg-page.ts | 3 ++-
src/js/logic/pdf-to-png-page.ts | 3 ++-
src/js/logic/pdf-to-tiff-page.ts | 3 ++-
src/js/logic/pdf-to-webp-page.ts | 3 ++-
src/js/logic/scanner-effect-page.ts | 3 ++-
8 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/src/js/logic/adjust-colors-page.ts b/src/js/logic/adjust-colors-page.ts
index 59ece2dd..3f9ae890 100644
--- a/src/js/logic/adjust-colors-page.ts
+++ b/src/js/logic/adjust-colors-page.ts
@@ -10,6 +10,7 @@ import { PDFDocument } from 'pdf-lib';
import { applyColorAdjustments } from '../utils/image-effects.js';
import * as pdfjsLib from 'pdfjs-dist';
import type { AdjustColorsSettings } from '../types/adjust-colors-type.js';
+import { t } from '../i18n/index.js';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -146,7 +147,7 @@ const updateUI = () => {
return getPDFDocument(buffer).promise;
})
.then((pdf: pdfjsLib.PDFDocumentProxy) => {
- metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
+ metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} ${pdf.numPages !== 1 ? t('common.pages') : t('common.page')}`;
})
.catch(() => {
metaSpan.textContent = formatBytes(file.size);
diff --git a/src/js/logic/pdf-to-bmp-page.ts b/src/js/logic/pdf-to-bmp-page.ts
index 1f71b771..a16463d9 100644
--- a/src/js/logic/pdf-to-bmp-page.ts
+++ b/src/js/logic/pdf-to-bmp-page.ts
@@ -10,6 +10,7 @@ import { createIcons, icons } from 'lucide';
import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
import { PDFPageProxy } from 'pdfjs-dist';
+import { t } from '../i18n/index.js';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -67,7 +68,7 @@ const updateUI = () => {
return getPDFDocument(buffer).promise;
})
.then((pdf) => {
- metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
+ metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} ${pdf.numPages !== 1 ? t('common.pages') : t('common.page')}`;
})
.catch((e) => {
console.warn('Error loading PDF page count:', e);
diff --git a/src/js/logic/pdf-to-greyscale-page.ts b/src/js/logic/pdf-to-greyscale-page.ts
index 64c8df57..3d336fd9 100644
--- a/src/js/logic/pdf-to-greyscale-page.ts
+++ b/src/js/logic/pdf-to-greyscale-page.ts
@@ -9,6 +9,7 @@ import { createIcons, icons } from 'lucide';
import { PDFDocument } from 'pdf-lib';
import { applyGreyscale } from '../utils/image-effects.js';
import * as pdfjsLib from 'pdfjs-dist';
+import { t } from '../i18n/index.js';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -66,7 +67,7 @@ const updateUI = () => {
return getPDFDocument(buffer).promise;
})
.then((pdf) => {
- metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
+ metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} ${pdf.numPages !== 1 ? t('common.pages') : t('common.page')}`;
})
.catch((e) => {
console.warn('Error loading PDF page count:', e);
diff --git a/src/js/logic/pdf-to-jpg-page.ts b/src/js/logic/pdf-to-jpg-page.ts
index dc43566c..8ad97a3c 100644
--- a/src/js/logic/pdf-to-jpg-page.ts
+++ b/src/js/logic/pdf-to-jpg-page.ts
@@ -10,6 +10,7 @@ import { createIcons, icons } from 'lucide';
import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
import { PDFPageProxy } from 'pdfjs-dist';
+import { t } from '../i18n/index.js';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -66,7 +67,7 @@ const updateUI = () => {
return getPDFDocument(buffer).promise;
})
.then((pdf) => {
- metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
+ metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} ${pdf.numPages !== 1 ? t('common.pages') : t('common.page')}`;
})
.catch((e) => {
console.warn('Error loading PDF page count:', e);
diff --git a/src/js/logic/pdf-to-png-page.ts b/src/js/logic/pdf-to-png-page.ts
index 8f4995df..a079706d 100644
--- a/src/js/logic/pdf-to-png-page.ts
+++ b/src/js/logic/pdf-to-png-page.ts
@@ -10,6 +10,7 @@ import { createIcons, icons } from 'lucide';
import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
import { PDFPageProxy } from 'pdfjs-dist';
+import { t } from '../i18n/index.js';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -66,7 +67,7 @@ const updateUI = () => {
return getPDFDocument(buffer).promise;
})
.then((pdf) => {
- metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
+ metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} ${pdf.numPages !== 1 ? t('common.pages') : t('common.page')}`;
})
.catch((e) => {
console.warn('Error loading PDF page count:', e);
diff --git a/src/js/logic/pdf-to-tiff-page.ts b/src/js/logic/pdf-to-tiff-page.ts
index 10ad314b..f69f9f05 100644
--- a/src/js/logic/pdf-to-tiff-page.ts
+++ b/src/js/logic/pdf-to-tiff-page.ts
@@ -11,6 +11,7 @@ import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
import UTIF from 'utif';
import { PDFPageProxy } from 'pdfjs-dist';
+import { t } from '../i18n/index.js';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -67,7 +68,7 @@ const updateUI = () => {
return getPDFDocument(buffer).promise;
})
.then((pdf) => {
- metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
+ metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} ${pdf.numPages !== 1 ? t('common.pages') : t('common.page')}`;
})
.catch((e) => {
console.warn('Error loading PDF page count:', e);
diff --git a/src/js/logic/pdf-to-webp-page.ts b/src/js/logic/pdf-to-webp-page.ts
index c2c0fecd..4f8e59e3 100644
--- a/src/js/logic/pdf-to-webp-page.ts
+++ b/src/js/logic/pdf-to-webp-page.ts
@@ -10,6 +10,7 @@ import { createIcons, icons } from 'lucide';
import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
import { PDFPageProxy } from 'pdfjs-dist';
+import { t } from '../i18n/index.js';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -66,7 +67,7 @@ const updateUI = () => {
return getPDFDocument(buffer).promise;
})
.then((pdf) => {
- metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
+ metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} ${pdf.numPages !== 1 ? t('common.pages') : t('common.page')}`;
})
.catch((e) => {
console.warn('Error loading PDF page count:', e);
diff --git a/src/js/logic/scanner-effect-page.ts b/src/js/logic/scanner-effect-page.ts
index a93bb8c5..273cf958 100644
--- a/src/js/logic/scanner-effect-page.ts
+++ b/src/js/logic/scanner-effect-page.ts
@@ -10,6 +10,7 @@ import { PDFDocument } from 'pdf-lib';
import { applyScannerEffect } from '../utils/image-effects.js';
import * as pdfjsLib from 'pdfjs-dist';
import type { ScanSettings } from '../types/scanner-effect-type.js';
+import { t } from '../i18n/index.js';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -152,7 +153,7 @@ const updateUI = () => {
return getPDFDocument(buffer).promise;
})
.then((pdf: pdfjsLib.PDFDocumentProxy) => {
- metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} page${pdf.numPages !== 1 ? 's' : ''}`;
+ metaSpan.textContent = `${formatBytes(file.size)} • ${pdf.numPages} ${pdf.numPages !== 1 ? t('common.pages') : t('common.page')}`;
})
.catch(() => {
metaSpan.textContent = formatBytes(file.size);
From f1b03d076cba8c0ebd66a26eafa0ad9ab09ee4bc Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 18:31:27 +0100
Subject: [PATCH 05/14] Add localization for image quality
---
public/locales/de/tools.json | 4 +++-
public/locales/en/tools.json | 4 +++-
src/pages/pdf-to-webp.html | 6 +++++-
3 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/public/locales/de/tools.json b/public/locales/de/tools.json
index ea93f3a0..f6d91979 100644
--- a/public/locales/de/tools.json
+++ b/public/locales/de/tools.json
@@ -212,7 +212,9 @@
},
"pdfToWebp": {
"name": "PDF zu WebP",
- "subtitle": "Jede PDF-Seite in ein WebP-Bild konvertieren."
+ "subtitle": "Jede PDF-Seite in ein WebP-Bild konvertieren.",
+ "imageQuality": "Bildqualität",
+ "imageQualityExplanation": "Höhere Qualität = größere Dateigröße"
},
"pdfToBmp": {
"name": "PDF zu BMP",
diff --git a/public/locales/en/tools.json b/public/locales/en/tools.json
index 9783beef..e9485f7e 100644
--- a/public/locales/en/tools.json
+++ b/public/locales/en/tools.json
@@ -212,7 +212,9 @@
},
"pdfToWebp": {
"name": "PDF to WebP",
- "subtitle": "Convert each PDF page into a WebP image."
+ "subtitle": "Convert each PDF page into a WebP image.",
+ "imageQuality": "Image Quality",
+ "imageQualityExplanation": "Higher quality = larger file size"
},
"pdfToBmp": {
"name": "PDF to BMP",
diff --git a/src/pages/pdf-to-webp.html b/src/pages/pdf-to-webp.html
index a1cc403f..cb8c8700 100644
--- a/src/pages/pdf-to-webp.html
+++ b/src/pages/pdf-to-webp.html
@@ -163,6 +163,7 @@
@@ -181,7 +182,10 @@
>85%
-
+
Higher quality = larger file size
From 67e38871652ff9274dee74e6a03c2b84b223fb93 Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 18:35:17 +0100
Subject: [PATCH 06/14] Add localization for image quality
---
public/locales/de/tools.json | 4 +++-
public/locales/en/tools.json | 4 +++-
src/pages/pdf-to-jpg.html | 6 +++++-
3 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/public/locales/de/tools.json b/public/locales/de/tools.json
index f6d91979..9b2e1ddc 100644
--- a/public/locales/de/tools.json
+++ b/public/locales/de/tools.json
@@ -204,7 +204,9 @@
},
"pdfToJpg": {
"name": "PDF zu JPG",
- "subtitle": "Jede PDF-Seite in ein JPG-Bild konvertieren."
+ "subtitle": "Jede PDF-Seite in ein JPG-Bild konvertieren.",
+ "imageQuality": "Bildqualität",
+ "imageQualityExplanation": "Höhere Qualität = größere Dateigröße"
},
"pdfToPng": {
"name": "PDF zu PNG",
diff --git a/public/locales/en/tools.json b/public/locales/en/tools.json
index e9485f7e..ae20264b 100644
--- a/public/locales/en/tools.json
+++ b/public/locales/en/tools.json
@@ -204,7 +204,9 @@
},
"pdfToJpg": {
"name": "PDF to JPG",
- "subtitle": "Convert each PDF page into a JPG image."
+ "subtitle": "Convert each PDF page into a JPG image.",
+ "imageQuality": "Image Quality",
+ "imageQualityExplanation": "Higher quality = larger file size"
},
"pdfToPng": {
"name": "PDF to PNG",
diff --git a/src/pages/pdf-to-jpg.html b/src/pages/pdf-to-jpg.html
index 2c8d63b3..64ad002a 100644
--- a/src/pages/pdf-to-jpg.html
+++ b/src/pages/pdf-to-jpg.html
@@ -166,6 +166,7 @@
@@ -184,7 +185,10 @@
>90%
-
+
Higher quality = larger file size
From 4f869602656d24ecb8a0faafbf93227bf1ed513b Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 18:38:09 +0100
Subject: [PATCH 07/14] Add localization for image scale
---
public/locales/de/tools.json | 4 +++-
public/locales/en/tools.json | 4 +++-
src/pages/pdf-to-png.html | 6 +++++-
3 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/public/locales/de/tools.json b/public/locales/de/tools.json
index 9b2e1ddc..f89f6cfe 100644
--- a/public/locales/de/tools.json
+++ b/public/locales/de/tools.json
@@ -210,7 +210,9 @@
},
"pdfToPng": {
"name": "PDF zu PNG",
- "subtitle": "Jede PDF-Seite in ein PNG-Bild konvertieren."
+ "subtitle": "Jede PDF-Seite in ein PNG-Bild konvertieren.",
+ "imageScale": "Bildskalierung",
+ "imageScaleExplanation": "Höhere Skalierung = bessere Qualität, aber größere Dateigröße"
},
"pdfToWebp": {
"name": "PDF zu WebP",
diff --git a/public/locales/en/tools.json b/public/locales/en/tools.json
index ae20264b..16a825b4 100644
--- a/public/locales/en/tools.json
+++ b/public/locales/en/tools.json
@@ -210,7 +210,9 @@
},
"pdfToPng": {
"name": "PDF to PNG",
- "subtitle": "Convert each PDF page into a PNG image."
+ "subtitle": "Convert each PDF page into a PNG image.",
+ "imageScale": "Image Scale",
+ "imageScaleExplanation": "Higher scale = better quality but larger file size"
},
"pdfToWebp": {
"name": "PDF to WebP",
diff --git a/src/pages/pdf-to-png.html b/src/pages/pdf-to-png.html
index c0d9c695..e5c54a5a 100644
--- a/src/pages/pdf-to-png.html
+++ b/src/pages/pdf-to-png.html
@@ -163,6 +163,7 @@
@@ -181,7 +182,10 @@
>2.0x
-
+
Higher scale = better quality but larger file size
From d5244649a332c8efbbab2442fdacf88277afc172 Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 18:40:47 +0100
Subject: [PATCH 08/14] Update imports according to the translation guidelines
---
src/js/logic/adjust-colors-page.ts | 2 +-
src/js/logic/pdf-to-bmp-page.ts | 2 +-
src/js/logic/pdf-to-greyscale-page.ts | 2 +-
src/js/logic/pdf-to-jpg-page.ts | 2 +-
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/scanner-effect-page.ts | 2 +-
8 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/js/logic/adjust-colors-page.ts b/src/js/logic/adjust-colors-page.ts
index 3f9ae890..aeab10f2 100644
--- a/src/js/logic/adjust-colors-page.ts
+++ b/src/js/logic/adjust-colors-page.ts
@@ -10,7 +10,7 @@ import { PDFDocument } from 'pdf-lib';
import { applyColorAdjustments } from '../utils/image-effects.js';
import * as pdfjsLib from 'pdfjs-dist';
import type { AdjustColorsSettings } from '../types/adjust-colors-type.js';
-import { t } from '../i18n/index.js';
+import { t } from '../i18n/i18n';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
diff --git a/src/js/logic/pdf-to-bmp-page.ts b/src/js/logic/pdf-to-bmp-page.ts
index a16463d9..e5d41fe8 100644
--- a/src/js/logic/pdf-to-bmp-page.ts
+++ b/src/js/logic/pdf-to-bmp-page.ts
@@ -10,7 +10,7 @@ import { createIcons, icons } from 'lucide';
import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
import { PDFPageProxy } from 'pdfjs-dist';
-import { t } from '../i18n/index.js';
+import { t } from '../i18n/i18n';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
diff --git a/src/js/logic/pdf-to-greyscale-page.ts b/src/js/logic/pdf-to-greyscale-page.ts
index 3d336fd9..9a4f94a8 100644
--- a/src/js/logic/pdf-to-greyscale-page.ts
+++ b/src/js/logic/pdf-to-greyscale-page.ts
@@ -9,7 +9,7 @@ import { createIcons, icons } from 'lucide';
import { PDFDocument } from 'pdf-lib';
import { applyGreyscale } from '../utils/image-effects.js';
import * as pdfjsLib from 'pdfjs-dist';
-import { t } from '../i18n/index.js';
+import { t } from '../i18n/i18n';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
diff --git a/src/js/logic/pdf-to-jpg-page.ts b/src/js/logic/pdf-to-jpg-page.ts
index 8ad97a3c..af73aebd 100644
--- a/src/js/logic/pdf-to-jpg-page.ts
+++ b/src/js/logic/pdf-to-jpg-page.ts
@@ -10,7 +10,7 @@ import { createIcons, icons } from 'lucide';
import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
import { PDFPageProxy } from 'pdfjs-dist';
-import { t } from '../i18n/index.js';
+import { t } from '../i18n/i18n';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
diff --git a/src/js/logic/pdf-to-png-page.ts b/src/js/logic/pdf-to-png-page.ts
index a079706d..76a37064 100644
--- a/src/js/logic/pdf-to-png-page.ts
+++ b/src/js/logic/pdf-to-png-page.ts
@@ -10,7 +10,7 @@ import { createIcons, icons } from 'lucide';
import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
import { PDFPageProxy } from 'pdfjs-dist';
-import { t } from '../i18n/index.js';
+import { t } from '../i18n/i18n';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
diff --git a/src/js/logic/pdf-to-tiff-page.ts b/src/js/logic/pdf-to-tiff-page.ts
index f69f9f05..71886af9 100644
--- a/src/js/logic/pdf-to-tiff-page.ts
+++ b/src/js/logic/pdf-to-tiff-page.ts
@@ -11,7 +11,7 @@ import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
import UTIF from 'utif';
import { PDFPageProxy } from 'pdfjs-dist';
-import { t } from '../i18n/index.js';
+import { t } from '../i18n/i18n';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
diff --git a/src/js/logic/pdf-to-webp-page.ts b/src/js/logic/pdf-to-webp-page.ts
index 4f8e59e3..d4c74b18 100644
--- a/src/js/logic/pdf-to-webp-page.ts
+++ b/src/js/logic/pdf-to-webp-page.ts
@@ -10,7 +10,7 @@ import { createIcons, icons } from 'lucide';
import JSZip from 'jszip';
import * as pdfjsLib from 'pdfjs-dist';
import { PDFPageProxy } from 'pdfjs-dist';
-import { t } from '../i18n/index.js';
+import { t } from '../i18n/i18n';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
diff --git a/src/js/logic/scanner-effect-page.ts b/src/js/logic/scanner-effect-page.ts
index 273cf958..2954549e 100644
--- a/src/js/logic/scanner-effect-page.ts
+++ b/src/js/logic/scanner-effect-page.ts
@@ -10,7 +10,7 @@ import { PDFDocument } from 'pdf-lib';
import { applyScannerEffect } from '../utils/image-effects.js';
import * as pdfjsLib from 'pdfjs-dist';
import type { ScanSettings } from '../types/scanner-effect-type.js';
-import { t } from '../i18n/index.js';
+import { t } from '../i18n/i18n';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
From 01c4d3bd7c881079f085f723b7faa72ebc949acd Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 19:17:17 +0100
Subject: [PATCH 09/14] Add localization for PDF to JPG conversion alerts and
loader messages
---
public/locales/de/tools.json | 13 ++++++++++++-
public/locales/en/tools.json | 13 ++++++++++++-
src/js/logic/pdf-to-jpg-page.ts | 21 ++++++++++++---------
3 files changed, 36 insertions(+), 11 deletions(-)
diff --git a/public/locales/de/tools.json b/public/locales/de/tools.json
index f89f6cfe..09f6ebe6 100644
--- a/public/locales/de/tools.json
+++ b/public/locales/de/tools.json
@@ -206,7 +206,18 @@
"name": "PDF zu JPG",
"subtitle": "Jede PDF-Seite in ein JPG-Bild konvertieren.",
"imageQuality": "Bildqualität",
- "imageQualityExplanation": "Höhere Qualität = größere Dateigröße"
+ "imageQualityExplanation": "Höhere Qualität = größere Dateigröße",
+ "alert": {
+ "invalidFile": "Ungültige Datei",
+ "invalidFileExplanation": "Bitte wähle eine PDF Datei aus.",
+ "noFile": "Keine Datei",
+ "noFileExplanation": "Bitte lade zuerst eine PDF-Datei hoch.",
+ "conversionSuccess": "PDF erfolgreich in JPGs konvertiert!",
+ "conversionError": "Konvertierung in JPG fehlgeschlagen. Die Datei könnte beschädigt sein."
+ },
+ "loader": {
+ "converting": "Wird in JPG konvertiert..."
+ }
},
"pdfToPng": {
"name": "PDF zu PNG",
diff --git a/public/locales/en/tools.json b/public/locales/en/tools.json
index 16a825b4..f446c8da 100644
--- a/public/locales/en/tools.json
+++ b/public/locales/en/tools.json
@@ -206,7 +206,18 @@
"name": "PDF to JPG",
"subtitle": "Convert each PDF page into a JPG image.",
"imageQuality": "Image Quality",
- "imageQualityExplanation": "Higher quality = larger file size"
+ "imageQualityExplanation": "Higher quality = larger file size",
+ "alert": {
+ "invalidFile": "Invalid File",
+ "invalidFileExplanation": "Please upload a PDF file.",
+ "noFile": "No File",
+ "noFileExplanation": "Please upload a PDF file first.",
+ "conversionSuccess": "PDF converted to JPGs successfully!",
+ "conversionError": "Failed to convert PDF to JPG. The file might be corrupted."
+ },
+ "loader": {
+ "converting": "Converting to JPG..."
+ }
},
"pdfToPng": {
"name": "PDF to PNG",
diff --git a/src/js/logic/pdf-to-jpg-page.ts b/src/js/logic/pdf-to-jpg-page.ts
index af73aebd..dedebb3a 100644
--- a/src/js/logic/pdf-to-jpg-page.ts
+++ b/src/js/logic/pdf-to-jpg-page.ts
@@ -97,10 +97,13 @@ const resetState = () => {
async function convert() {
if (files.length === 0) {
- showAlert('No File', 'Please upload a PDF file first.');
+ showAlert(
+ t('tools:pdfToJpg.alert.noFile'),
+ t('tools:pdfToJpg.alert.noFileExplanation')
+ );
return;
}
- showLoader('Converting to JPG...');
+ showLoader(t('tools:pdfToJpg.loader.converting'));
try {
const pdf = await getPDFDocument(await readFileAsArrayBuffer(files[0]))
.promise;
@@ -129,8 +132,8 @@ async function convert() {
}
showAlert(
- 'Success',
- 'PDF converted to JPGs successfully!',
+ t('common.success'),
+ t('tools:pdfToJpg.alert.conversionSuccess'),
'success',
() => {
resetState();
@@ -138,10 +141,7 @@ async function convert() {
);
} catch (e) {
console.error(e);
- showAlert(
- 'Error',
- 'Failed to convert PDF to JPG. The file might be corrupted.'
- );
+ showAlert(t('common.error'), t('tools:pdfToJpg.alert.conversionError'));
} finally {
hideLoader();
}
@@ -198,7 +198,10 @@ document.addEventListener('DOMContentLoaded', () => {
);
if (validFiles.length === 0) {
- showAlert('Invalid File', 'Please upload a PDF file.');
+ showAlert(
+ t('tools:pdfToJpg.alert.invalidFile'),
+ t('tools:pdfToJpg.alert.invalidFileExplanation')
+ );
return;
}
From 37d95e249df6b0f45dda3bc1a6654acb9bce856b Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 19:18:39 +0100
Subject: [PATCH 10/14] Add localization for PDF to PNG conversion alerts and
loader messages
---
public/locales/de/tools.json | 13 ++++++++++++-
public/locales/en/tools.json | 13 ++++++++++++-
src/js/logic/pdf-to-png-page.ts | 21 ++++++++++++---------
3 files changed, 36 insertions(+), 11 deletions(-)
diff --git a/public/locales/de/tools.json b/public/locales/de/tools.json
index 09f6ebe6..eef2bf66 100644
--- a/public/locales/de/tools.json
+++ b/public/locales/de/tools.json
@@ -223,7 +223,18 @@
"name": "PDF zu PNG",
"subtitle": "Jede PDF-Seite in ein PNG-Bild konvertieren.",
"imageScale": "Bildskalierung",
- "imageScaleExplanation": "Höhere Skalierung = bessere Qualität, aber größere Dateigröße"
+ "imageScaleExplanation": "Höhere Skalierung = bessere Qualität, aber größere Dateigröße",
+ "alert": {
+ "invalidFile": "Ungültige Datei",
+ "invalidFileExplanation": "Bitte wähle eine PDF Datei aus.",
+ "noFile": "Keine Datei",
+ "noFileExplanation": "Bitte lade zuerst eine PDF-Datei hoch.",
+ "conversionSuccess": "PDF erfolgreich in PNGs konvertiert!",
+ "conversionError": "Konvertierung in PNG fehlgeschlagen. Die Datei könnte beschädigt sein."
+ },
+ "loader": {
+ "converting": "Wird in PNG konvertiert..."
+ }
},
"pdfToWebp": {
"name": "PDF zu WebP",
diff --git a/public/locales/en/tools.json b/public/locales/en/tools.json
index f446c8da..043c880f 100644
--- a/public/locales/en/tools.json
+++ b/public/locales/en/tools.json
@@ -223,7 +223,18 @@
"name": "PDF to PNG",
"subtitle": "Convert each PDF page into a PNG image.",
"imageScale": "Image Scale",
- "imageScaleExplanation": "Higher scale = better quality but larger file size"
+ "imageScaleExplanation": "Higher scale = better quality but larger file size",
+ "alert": {
+ "invalidFile": "Invalid File",
+ "invalidFileExplanation": "Please upload a PDF file.",
+ "noFile": "No File",
+ "noFileExplanation": "Please upload a PDF file first.",
+ "conversionSuccess": "PDF converted to PNGs successfully!",
+ "conversionError": "Failed to convert PDF to PNG. The file might be corrupted."
+ },
+ "loader": {
+ "converting": "Converting to PNG..."
+ }
},
"pdfToWebp": {
"name": "PDF to WebP",
diff --git a/src/js/logic/pdf-to-png-page.ts b/src/js/logic/pdf-to-png-page.ts
index 76a37064..84fe5e57 100644
--- a/src/js/logic/pdf-to-png-page.ts
+++ b/src/js/logic/pdf-to-png-page.ts
@@ -95,10 +95,13 @@ const resetState = () => {
async function convert() {
if (files.length === 0) {
- showAlert('No File', 'Please upload a PDF file first.');
+ showAlert(
+ t('tools:pdfToPng.alert.noFile'),
+ t('tools:pdfToPng.alert.noFileExplanation')
+ );
return;
}
- showLoader('Converting to PNG...');
+ showLoader(t('tools:pdfToPng.loader.converting'));
try {
const pdf = await getPDFDocument(await readFileAsArrayBuffer(files[0]))
.promise;
@@ -125,8 +128,8 @@ async function convert() {
}
showAlert(
- 'Success',
- 'PDF converted to PNGs successfully!',
+ t('common.success'),
+ t('tools:pdfToPng.alert.conversionSuccess'),
'success',
() => {
resetState();
@@ -134,10 +137,7 @@ async function convert() {
);
} catch (e) {
console.error(e);
- showAlert(
- 'Error',
- 'Failed to convert PDF to PNG. The file might be corrupted.'
- );
+ showAlert(t('common.error'), t('tools:pdfToPng.alert.conversionError'));
} finally {
hideLoader();
}
@@ -192,7 +192,10 @@ document.addEventListener('DOMContentLoaded', () => {
);
if (validFiles.length === 0) {
- showAlert('Invalid File', 'Please upload a PDF file.');
+ showAlert(
+ t('tools:pdfToPng.alert.invalidFile'),
+ t('tools:pdfToPng.alert.invalidFileExplanation')
+ );
return;
}
From 8d263786119c15952a3688c9e9f6dc695bff7936 Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 19:52:38 +0100
Subject: [PATCH 11/14] Add localization for PDF to WebP conversion alerts and
loader messages
---
public/locales/de/tools.json | 13 ++++++++++++-
public/locales/en/tools.json | 13 ++++++++++++-
src/js/logic/pdf-to-webp-page.ts | 21 ++++++++++++---------
3 files changed, 36 insertions(+), 11 deletions(-)
diff --git a/public/locales/de/tools.json b/public/locales/de/tools.json
index eef2bf66..a137e1a0 100644
--- a/public/locales/de/tools.json
+++ b/public/locales/de/tools.json
@@ -240,7 +240,18 @@
"name": "PDF zu WebP",
"subtitle": "Jede PDF-Seite in ein WebP-Bild konvertieren.",
"imageQuality": "Bildqualität",
- "imageQualityExplanation": "Höhere Qualität = größere Dateigröße"
+ "imageQualityExplanation": "Höhere Qualität = größere Dateigröße",
+ "alert": {
+ "invalidFile": "Ungültige Datei",
+ "invalidFileExplanation": "Bitte wähle eine PDF Datei aus.",
+ "noFile": "Keine Datei",
+ "noFileExplanation": "Bitte lade zuerst eine PDF-Datei hoch.",
+ "conversionSuccess": "PDF erfolgreich in WebPs konvertiert!",
+ "conversionError": "Konvertierung in WebP fehlgeschlagen. Die Datei könnte beschädigt sein."
+ },
+ "loader": {
+ "converting": "Wird in WebP konvertiert..."
+ }
},
"pdfToBmp": {
"name": "PDF zu BMP",
diff --git a/public/locales/en/tools.json b/public/locales/en/tools.json
index 043c880f..fcbb4625 100644
--- a/public/locales/en/tools.json
+++ b/public/locales/en/tools.json
@@ -240,7 +240,18 @@
"name": "PDF to WebP",
"subtitle": "Convert each PDF page into a WebP image.",
"imageQuality": "Image Quality",
- "imageQualityExplanation": "Higher quality = larger file size"
+ "imageQualityExplanation": "Higher quality = larger file size",
+ "alert": {
+ "invalidFile": "Invalid File",
+ "invalidFileExplanation": "Please upload a PDF file.",
+ "noFile": "No File",
+ "noFileExplanation": "Please upload a PDF file first.",
+ "conversionSuccess": "PDF converted to WebPs successfully!",
+ "conversionError": "Failed to convert PDF to WebP. The file might be corrupted."
+ },
+ "loader": {
+ "converting": "Converting to WebP..."
+ }
},
"pdfToBmp": {
"name": "PDF to BMP",
diff --git a/src/js/logic/pdf-to-webp-page.ts b/src/js/logic/pdf-to-webp-page.ts
index d4c74b18..57e4d5eb 100644
--- a/src/js/logic/pdf-to-webp-page.ts
+++ b/src/js/logic/pdf-to-webp-page.ts
@@ -97,10 +97,13 @@ const resetState = () => {
async function convert() {
if (files.length === 0) {
- showAlert('No File', 'Please upload a PDF file first.');
+ showAlert(
+ t('tools:pdfToWebp.alert.noFile'),
+ t('tools:pdfToWebp.alert.noFileExplanation')
+ );
return;
}
- showLoader('Converting to WebP...');
+ showLoader(t('tools:pdfToWebp.loader.converting'));
try {
const pdf = await getPDFDocument(await readFileAsArrayBuffer(files[0]))
.promise;
@@ -129,8 +132,8 @@ async function convert() {
}
showAlert(
- 'Success',
- 'PDF converted to WebPs successfully!',
+ t('common.success'),
+ t('tools:pdfToWebp.alert.conversionSuccess'),
'success',
() => {
resetState();
@@ -138,10 +141,7 @@ async function convert() {
);
} catch (e) {
console.error(e);
- showAlert(
- 'Error',
- 'Failed to convert PDF to WebP. The file might be corrupted.'
- );
+ showAlert(t('common.error'), t('tools:pdfToWebp.alert.conversionError'));
} finally {
hideLoader();
}
@@ -198,7 +198,10 @@ document.addEventListener('DOMContentLoaded', () => {
);
if (validFiles.length === 0) {
- showAlert('Invalid File', 'Please upload a PDF file.');
+ showAlert(
+ t('tools:pdfToWebp.alert.invalidFile'),
+ t('tools:pdfToWebp.alert.invalidFileExplanation')
+ );
return;
}
From 571ce07af66507662193001578a76b3395b29620 Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 19:55:21 +0100
Subject: [PATCH 12/14] Add localization for PDF to BMP and PDF to TIFF
conversion alerts and loader messages
---
public/locales/de/tools.json | 26 ++++++++++++++++++++++++--
public/locales/en/tools.json | 26 ++++++++++++++++++++++++--
src/js/logic/pdf-to-bmp-page.ts | 21 ++++++++++++---------
src/js/logic/pdf-to-tiff-page.ts | 21 ++++++++++++---------
4 files changed, 72 insertions(+), 22 deletions(-)
diff --git a/public/locales/de/tools.json b/public/locales/de/tools.json
index a137e1a0..ab0988c0 100644
--- a/public/locales/de/tools.json
+++ b/public/locales/de/tools.json
@@ -255,11 +255,33 @@
},
"pdfToBmp": {
"name": "PDF zu BMP",
- "subtitle": "Jede PDF-Seite in ein BMP-Bild konvertieren."
+ "subtitle": "Jede PDF-Seite in ein BMP-Bild konvertieren.",
+ "alert": {
+ "invalidFile": "Ungültige Datei",
+ "invalidFileExplanation": "Bitte wähle eine PDF Datei aus.",
+ "noFile": "Keine Datei",
+ "noFileExplanation": "Bitte lade zuerst eine PDF-Datei hoch.",
+ "conversionSuccess": "PDF erfolgreich in BMPs konvertiert!",
+ "conversionError": "Konvertierung in BMP fehlgeschlagen. Die Datei könnte beschädigt sein."
+ },
+ "loader": {
+ "converting": "Wird in BMP konvertiert..."
+ }
},
"pdfToTiff": {
"name": "PDF zu TIFF",
- "subtitle": "Jede PDF-Seite in ein TIFF-Bild konvertieren."
+ "subtitle": "Jede PDF-Seite in ein TIFF-Bild konvertieren.",
+ "alert": {
+ "invalidFile": "Ungültige Datei",
+ "invalidFileExplanation": "Bitte wähle eine PDF Datei aus.",
+ "noFile": "Keine Datei",
+ "noFileExplanation": "Bitte lade zuerst eine PDF-Datei hoch.",
+ "conversionSuccess": "PDF erfolgreich in TIFFs konvertiert!",
+ "conversionError": "Konvertierung in TIFF fehlgeschlagen. Die Datei könnte beschädigt sein."
+ },
+ "loader": {
+ "converting": "Wird in TIFF konvertiert..."
+ }
},
"pdfToGreyscale": {
"name": "PDF zu Graustufen",
diff --git a/public/locales/en/tools.json b/public/locales/en/tools.json
index fcbb4625..6fa24765 100644
--- a/public/locales/en/tools.json
+++ b/public/locales/en/tools.json
@@ -255,11 +255,33 @@
},
"pdfToBmp": {
"name": "PDF to BMP",
- "subtitle": "Convert each PDF page into a BMP image."
+ "subtitle": "Convert each PDF page into a BMP image.",
+ "alert": {
+ "invalidFile": "Invalid File",
+ "invalidFileExplanation": "Please upload a PDF file.",
+ "noFile": "No File",
+ "noFileExplanation": "Please upload a PDF file first.",
+ "conversionSuccess": "PDF converted to BMPs successfully!",
+ "conversionError": "Failed to convert PDF to BMP. The file might be corrupted."
+ },
+ "loader": {
+ "converting": "Converting to BMP..."
+ }
},
"pdfToTiff": {
"name": "PDF to TIFF",
- "subtitle": "Convert each PDF page into a TIFF image."
+ "subtitle": "Convert each PDF page into a TIFF image.",
+ "alert": {
+ "invalidFile": "Invalid File",
+ "invalidFileExplanation": "Please upload a PDF file.",
+ "noFile": "No File",
+ "noFileExplanation": "Please upload a PDF file first.",
+ "conversionSuccess": "PDF converted to TIFFs successfully!",
+ "conversionError": "Failed to convert PDF to TIFF. The file might be corrupted."
+ },
+ "loader": {
+ "converting": "Converting to TIFF..."
+ }
},
"pdfToGreyscale": {
"name": "PDF to Greyscale",
diff --git a/src/js/logic/pdf-to-bmp-page.ts b/src/js/logic/pdf-to-bmp-page.ts
index e5d41fe8..b38ac4d0 100644
--- a/src/js/logic/pdf-to-bmp-page.ts
+++ b/src/js/logic/pdf-to-bmp-page.ts
@@ -92,10 +92,13 @@ const resetState = () => {
async function convert() {
if (files.length === 0) {
- showAlert('No File', 'Please upload a PDF file first.');
+ showAlert(
+ t('tools:pdfToBmp.alert.noFile'),
+ t('tools:pdfToBmp.alert.noFileExplanation')
+ );
return;
}
- showLoader('Converting to BMP...');
+ showLoader(t('tools:pdfToBmp.loader.converting'));
try {
const pdf = await getPDFDocument(await readFileAsArrayBuffer(files[0]))
.promise;
@@ -119,8 +122,8 @@ async function convert() {
}
showAlert(
- 'Success',
- 'PDF converted to BMPs successfully!',
+ t('common.success'),
+ t('tools:pdfToBmp.alert.conversionSuccess'),
'success',
() => {
resetState();
@@ -128,10 +131,7 @@ async function convert() {
);
} catch (e) {
console.error(e);
- showAlert(
- 'Error',
- 'Failed to convert PDF to BMP. The file might be corrupted.'
- );
+ showAlert(t('common.error'), t('tools:pdfToBmp.alert.conversionError'));
} finally {
hideLoader();
}
@@ -175,7 +175,10 @@ document.addEventListener('DOMContentLoaded', () => {
);
if (validFiles.length === 0) {
- showAlert('Invalid File', 'Please upload a PDF file.');
+ showAlert(
+ t('tools:pdfToBmp.alert.invalidFile'),
+ t('tools:pdfToBmp.alert.invalidFileExplanation')
+ );
return;
}
diff --git a/src/js/logic/pdf-to-tiff-page.ts b/src/js/logic/pdf-to-tiff-page.ts
index 71886af9..c463124f 100644
--- a/src/js/logic/pdf-to-tiff-page.ts
+++ b/src/js/logic/pdf-to-tiff-page.ts
@@ -92,10 +92,13 @@ const resetState = () => {
async function convert() {
if (files.length === 0) {
- showAlert('No File', 'Please upload a PDF file first.');
+ showAlert(
+ t('tools:pdfToTiff.alert.noFile'),
+ t('tools:pdfToTiff.alert.noFileExplanation')
+ );
return;
}
- showLoader('Converting to TIFF...');
+ showLoader(t('tools:pdfToTiff.loader.converting'));
try {
const pdf = await getPDFDocument(await readFileAsArrayBuffer(files[0]))
.promise;
@@ -122,8 +125,8 @@ async function convert() {
}
showAlert(
- 'Success',
- 'PDF converted to TIFFs successfully!',
+ t('common.success'),
+ t('tools:pdfToTiff.alert.conversionSuccess'),
'success',
() => {
resetState();
@@ -131,10 +134,7 @@ async function convert() {
);
} catch (e) {
console.error(e);
- showAlert(
- 'Error',
- 'Failed to convert PDF to TIFF. The file might be corrupted.'
- );
+ showAlert(t('common.error'), t('tools:pdfToTiff.alert.conversionError'));
} finally {
hideLoader();
}
@@ -212,7 +212,10 @@ document.addEventListener('DOMContentLoaded', () => {
);
if (validFiles.length === 0) {
- showAlert('Invalid File', 'Please upload a PDF file.');
+ showAlert(
+ t('tools:pdfToTiff.alert.invalidFile'),
+ t('tools:pdfToTiff.alert.invalidFileExplanation')
+ );
return;
}
From 2ede256de2645e8ae9308665b03d43f7b66c3f1b Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 10 Mar 2026 20:13:13 +0100
Subject: [PATCH 13/14] 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 1aa64f41..ec04c7bb 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 50adeacb..c66aebde 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 aeab10f2..e46d4b15 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 306b22ac..42611d0f 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 36d7f0b8..aaab5d17 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 22ea4151..e1a3836a 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 b38ac4d0..046eea69 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 3850fbc2..101f748e 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 9a4f94a8..c1e7546c 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 dedebb3a..945d0224 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 f99294b8..17143c08 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 0de40736..07b3a7d8 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 84fe5e57..9b33813e 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 c463124f..f247a212 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 57e4d5eb..c991752b 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 464f5220..e65dd48a 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 fc9960be..8949c7b1 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 2954549e..bae63b68 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 8f04273a..f446f3f7 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 699c248a..a1308150 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);
From 6d19c5f2e41b6c0998cfbd7856c8ed0448dcd0c1 Mon Sep 17 00:00:00 2001
From: Sebastian Espei
Date: Tue, 24 Mar 2026 01:55:48 +0100
Subject: [PATCH 14/14] Revert formatting churn in signing pages
---
src/js/logic/digital-sign-pdf-page.ts | 1167 ++++++++++++-------------
src/js/logic/sign-pdf-page.ts | 537 ++++++------
2 files changed, 789 insertions(+), 915 deletions(-)
diff --git a/src/js/logic/digital-sign-pdf-page.ts b/src/js/logic/digital-sign-pdf-page.ts
index 42611d0f..1b2d607e 100644
--- a/src/js/logic/digital-sign-pdf-page.ts
+++ b/src/js/logic/digital-sign-pdf-page.ts
@@ -1,775 +1,690 @@
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 { t } from '../i18n/i18n';
import {
- signPdf,
- parsePfxFile,
- parseCombinedPem,
- getCertificateInfo,
+ 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();
- };
- fileDiv.append(infoContainer, removeBtn);
- fileDisplayArea.appendChild(fileDiv);
- createIcons({ icons });
+ const fileDiv = document.createElement('div');
+ fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg';
- try {
- if (state.pdfBytes) {
- const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() })
- .promise;
- metaSpan.textContent = `${formatBytes(state.pdfFile.size)} • ${pdfDoc.numPages} pages`;
+ 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();
+ };
+
+ 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)}`;
}
- } 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)
- );
-
- if (!hasValidExtension) {
- showAlert(
- 'Invalid Certificate',
- 'Please select a .pfx, .p12, or .pem certificate file.'
+ const validExtensions = ['.pfx', '.p12', '.pem'];
+ const hasValidExtension = validExtensions.some(ext =>
+ file.name.toLowerCase().endsWith(ext)
);
- return;
- }
- state.certFile = file;
- state.certData = null;
-
- updateCertDisplay();
-
- const isPemFile = file.name.toLowerCase().endsWith('.pem');
-
- 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';
- }
+ if (!hasValidExtension) {
+ showAlert('Invalid Certificate', 'Please select a .pfx, .p12, or .pem certificate file.');
+ return;
}
- } else {
- showPasswordSection();
- updatePasswordLabel('Certificate Password');
- }
- hideSignatureOptions();
- updateProcessButton();
+ state.certFile = file;
+ state.certData = null;
+
+ updateCertDisplay();
+
+ const isPemFile = file.name.toLowerCase().endsWith('.pem');
+
+ 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 {
+ 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();
- };
- certDiv.append(infoContainer, removeBtn);
- certDisplayArea.appendChild(certDiv);
- createIcons({ icons });
+ 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 });
}
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;
- }
-
- 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);
+ if (!state.certFile || !password) {
+ return;
}
- updateCertInfo();
- showSignatureOptions();
- updateProcessButton();
+ try {
+ const isPemFile = state.certFile.name.toLowerCase().endsWith('.pem');
- 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();
+ 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);
+ }
- 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';
+ 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';
+ }
}
- }
}
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