diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties
index aff70807e..1202616bf 100644
--- a/src/main/resources/messages_ar_AR.properties
+++ b/src/main/resources/messages_ar_AR.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=تحريك إلى اليسار
multiTool.moveRight=تحريك إلى اليمين
multiTool.delete=حذف
multiTool.dragDropMessage=الصفحات المحددة
+multiTool.undo=تراجع
+multiTool.redo=إعادة إجراء
#multiTool-advert
multiTool-advert.message=هذه الميزة متوفرة في صفحة الأدوات المتعددة لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
diff --git a/src/main/resources/messages_az_AZ.properties b/src/main/resources/messages_az_AZ.properties
index 572659baf..aabd13723 100644
--- a/src/main/resources/messages_az_AZ.properties
+++ b/src/main/resources/messages_az_AZ.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Seçilmiş Səhifə(lər)
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=Bu xüsusiyyət bizim multi-alət səhifəmizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin!
diff --git a/src/main/resources/messages_bg_BG.properties b/src/main/resources/messages_bg_BG.properties
index 9e2d727b6..f1e5fc3bd 100644
--- a/src/main/resources/messages_bg_BG.properties
+++ b/src/main/resources/messages_bg_BG.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties
index 38ae93a9f..aa860cb78 100644
--- a/src/main/resources/messages_ca_CA.properties
+++ b/src/main/resources/messages_ca_CA.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_cs_CZ.properties b/src/main/resources/messages_cs_CZ.properties
index 10c5ce88b..3c50d3875 100644
--- a/src/main/resources/messages_cs_CZ.properties
+++ b/src/main/resources/messages_cs_CZ.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_da_DK.properties b/src/main/resources/messages_da_DK.properties
index 86e4b3ccb..ddaf3c607 100644
--- a/src/main/resources/messages_da_DK.properties
+++ b/src/main/resources/messages_da_DK.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties
index 1c9609c55..087b7be7b 100644
--- a/src/main/resources/messages_de_DE.properties
+++ b/src/main/resources/messages_de_DE.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=Diese Funktion ist auch auf unserer PDF-Multitool-Seite verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!
diff --git a/src/main/resources/messages_el_GR.properties b/src/main/resources/messages_el_GR.properties
index b6be4be12..757203774 100644
--- a/src/main/resources/messages_el_GR.properties
+++ b/src/main/resources/messages_el_GR.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties
index 8a471ade7..e2f74fbc9 100644
--- a/src/main/resources/messages_en_GB.properties
+++ b/src/main/resources/messages_en_GB.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties
index cd43508ce..cde361d99 100644
--- a/src/main/resources/messages_en_US.properties
+++ b/src/main/resources/messages_en_US.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties
index 6869b4f45..db712d420 100644
--- a/src/main/resources/messages_es_ES.properties
+++ b/src/main/resources/messages_es_ES.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties
index 6022513c8..0daedb195 100644
--- a/src/main/resources/messages_eu_ES.properties
+++ b/src/main/resources/messages_eu_ES.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties
index 9b902ae05..7ac4a8a9a 100644
--- a/src/main/resources/messages_fr_FR.properties
+++ b/src/main/resources/messages_fr_FR.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Déplacer vers la gauche
multiTool.moveRight=Déplacer vers la droite
multiTool.delete=Supprimer
multiTool.dragDropMessage=Page(s) sélectionnées
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la page de l'outil multifonction. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !
diff --git a/src/main/resources/messages_ga_IE.properties b/src/main/resources/messages_ga_IE.properties
index c1d46c038..8a95af6da 100644
--- a/src/main/resources/messages_ga_IE.properties
+++ b/src/main/resources/messages_ga_IE.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_hi_IN.properties b/src/main/resources/messages_hi_IN.properties
index 490d8bf9c..1c61b7ec9 100644
--- a/src/main/resources/messages_hi_IN.properties
+++ b/src/main/resources/messages_hi_IN.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_hr_HR.properties b/src/main/resources/messages_hr_HR.properties
index 1e76ba926..efc41e4f6 100644
--- a/src/main/resources/messages_hr_HR.properties
+++ b/src/main/resources/messages_hr_HR.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_hu_HU.properties b/src/main/resources/messages_hu_HU.properties
index 4bef02014..841c71fdb 100644
--- a/src/main/resources/messages_hu_HU.properties
+++ b/src/main/resources/messages_hu_HU.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_id_ID.properties b/src/main/resources/messages_id_ID.properties
index b04de8947..fdd3816b7 100644
--- a/src/main/resources/messages_id_ID.properties
+++ b/src/main/resources/messages_id_ID.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties
index a34a050b1..1b2a55fc4 100644
--- a/src/main/resources/messages_it_IT.properties
+++ b/src/main/resources/messages_it_IT.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Sposta a sinistra
multiTool.moveRight=Sposta a destra
multiTool.delete=Elimina
multiTool.dragDropMessage=Pagina(e) selezionata(e)
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=Questa funzione è disponibile anche nella nostra pagina multi-strumento. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!
diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties
index 5574521c0..e893029b7 100644
--- a/src/main/resources/messages_ja_JP.properties
+++ b/src/main/resources/messages_ja_JP.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties
index 27db7dd6f..aa77af639 100644
--- a/src/main/resources/messages_ko_KR.properties
+++ b/src/main/resources/messages_ko_KR.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_nl_NL.properties b/src/main/resources/messages_nl_NL.properties
index b6f0ef0e5..23ac465ab 100644
--- a/src/main/resources/messages_nl_NL.properties
+++ b/src/main/resources/messages_nl_NL.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_no_NB.properties b/src/main/resources/messages_no_NB.properties
index 0a4461cc6..8d1220e6d 100644
--- a/src/main/resources/messages_no_NB.properties
+++ b/src/main/resources/messages_no_NB.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties
index ffc2e33a9..02469e1f3 100755
--- a/src/main/resources/messages_pl_PL.properties
+++ b/src/main/resources/messages_pl_PL.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties
index aba81922f..b7af46b15 100644
--- a/src/main/resources/messages_pt_BR.properties
+++ b/src/main/resources/messages_pt_BR.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_pt_PT.properties b/src/main/resources/messages_pt_PT.properties
index 0f3830c23..f5f2137b9 100644
--- a/src/main/resources/messages_pt_PT.properties
+++ b/src/main/resources/messages_pt_PT.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties
index d289ff961..6be97127a 100644
--- a/src/main/resources/messages_ro_RO.properties
+++ b/src/main/resources/messages_ro_RO.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties
index 35aefdd03..1908496b2 100644
--- a/src/main/resources/messages_ru_RU.properties
+++ b/src/main/resources/messages_ru_RU.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_sk_SK.properties b/src/main/resources/messages_sk_SK.properties
index 0c78dad13..57bb598ac 100644
--- a/src/main/resources/messages_sk_SK.properties
+++ b/src/main/resources/messages_sk_SK.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_sr_LATN_RS.properties b/src/main/resources/messages_sr_LATN_RS.properties
index 9ac101ad8..34b737d1a 100644
--- a/src/main/resources/messages_sr_LATN_RS.properties
+++ b/src/main/resources/messages_sr_LATN_RS.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties
index c2a7de326..98ccaaa10 100644
--- a/src/main/resources/messages_sv_SE.properties
+++ b/src/main/resources/messages_sv_SE.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_th_TH.properties b/src/main/resources/messages_th_TH.properties
index 1b58842bc..08e0281bf 100644
--- a/src/main/resources/messages_th_TH.properties
+++ b/src/main/resources/messages_th_TH.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_tr_TR.properties b/src/main/resources/messages_tr_TR.properties
index 79130da2c..e71165368 100644
--- a/src/main/resources/messages_tr_TR.properties
+++ b/src/main/resources/messages_tr_TR.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_uk_UA.properties b/src/main/resources/messages_uk_UA.properties
index 4b9cbd26c..1c2b60cf7 100644
--- a/src/main/resources/messages_uk_UA.properties
+++ b/src/main/resources/messages_uk_UA.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_vi_VN.properties b/src/main/resources/messages_vi_VN.properties
index 14e78b028..bfb7d9c7b 100644
--- a/src/main/resources/messages_vi_VN.properties
+++ b/src/main/resources/messages_vi_VN.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties
index 12f2b69b1..dfec712bf 100644
--- a/src/main/resources/messages_zh_CN.properties
+++ b/src/main/resources/messages_zh_CN.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/messages_zh_TW.properties b/src/main/resources/messages_zh_TW.properties
index fd3529a77..c1b3929ad 100644
--- a/src/main/resources/messages_zh_TW.properties
+++ b/src/main/resources/messages_zh_TW.properties
@@ -958,6 +958,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right
multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected
+multiTool.undo=Undo
+multiTool.redo=Redo
#multiTool-advert
multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features!
diff --git a/src/main/resources/static/js/multitool/PdfActionsManager.js b/src/main/resources/static/js/multitool/PdfActionsManager.js
index b83beb5eb..17386fd79 100644
--- a/src/main/resources/static/js/multitool/PdfActionsManager.js
+++ b/src/main/resources/static/js/multitool/PdfActionsManager.js
@@ -1,12 +1,20 @@
+import { DeletePageCommand } from "./commands/delete-page.js";
+import { SelectPageCommand } from "./commands/select.js";
+import { SplitFileCommand } from "./commands/split.js";
+import { UndoManager } from "./UndoManager.js";
+
class PdfActionsManager {
pageDirection;
pagesContainer;
static selectedPages = []; // Static property shared across all instances
+ undoManager;
- constructor(id) {
+ constructor(id, undoManager) {
this.pagesContainer = document.getElementById(id);
this.pageDirection = document.documentElement.getAttribute("dir");
+ this.undoManager = undoManager || new UndoManager();
+
var styleElement = document.createElement("link");
styleElement.rel = "stylesheet";
styleElement.href = "css/pdfActions.css";
@@ -27,7 +35,8 @@ class PdfActionsManager {
const sibling = imgContainer.previousSibling;
if (sibling) {
- this.movePageTo(imgContainer, sibling, true);
+ let movePageCommand = this.movePageTo(imgContainer, sibling, true, true);
+ this._pushUndoClearRedo(movePageCommand);
}
}
@@ -35,7 +44,12 @@ class PdfActionsManager {
var imgContainer = this.getPageContainer(e.target);
const sibling = imgContainer.nextSibling;
if (sibling) {
- this.movePageTo(imgContainer, sibling.nextSibling, true);
+ let movePageCommand = this.movePageTo(
+ imgContainer,
+ sibling.nextSibling,
+ true
+ );
+ this._pushUndoClearRedo(movePageCommand);
}
}
@@ -43,30 +57,27 @@ class PdfActionsManager {
var imgContainer = this.getPageContainer(e.target);
const img = imgContainer.querySelector("img");
- this.rotateElement(img, -90);
+ let rotateCommand = this.rotateElement(img, -90);
+ this._pushUndoClearRedo(rotateCommand);
}
rotateCWButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target);
const img = imgContainer.querySelector("img");
- this.rotateElement(img, 90);
+ let rotateCommand = this.rotateElement(img, 90);
+ this._pushUndoClearRedo(rotateCommand);
}
deletePageButtonCallback(e) {
- var imgContainer = this.getPageContainer(e.target);
- this.pagesContainer.removeChild(imgContainer);
- if (this.pagesContainer.childElementCount === 0) {
- const filenameInput = document.getElementById("filename-input");
- const filenameParagraph = document.getElementById("filename");
- const downloadBtn = document.getElementById("export-button");
+ let imgContainer = this.getPageContainer(e.target);
+ let deletePageCommand = new DeletePageCommand(
+ imgContainer,
+ this.pagesContainer
+ );
+ deletePageCommand.execute();
- filenameInput.disabled = true;
- filenameInput.value = "";
- filenameParagraph.innerText = "";
-
- downloadBtn.disabled = true;
- }
+ this._pushUndoClearRedo(deletePageCommand);
}
insertFileButtonCallback(e) {
@@ -81,7 +92,15 @@ class PdfActionsManager {
splitFileButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target);
- imgContainer.classList.toggle("split-before");
+
+ let splitFileCommand = new SplitFileCommand(imgContainer, "split-before");
+ splitFileCommand.execute();
+
+ this._pushUndoClearRedo(splitFileCommand);
+ }
+
+ _pushUndoClearRedo(command) {
+ this.undoManager.pushUndoClearRedo(command);
}
setActions({ movePageTo, addFiles, rotateElement }) {
@@ -159,25 +178,10 @@ class PdfActionsManager {
selectCheckbox.onchange = () => {
const pageNumber = Array.from(div.parentNode.children).indexOf(div) + 1;
- if (selectCheckbox.checked) {
- //adds to array of selected pages
- window.selectedPages.push(pageNumber);
- } else {
- //remove page from selected pages array
- const index = window.selectedPages.indexOf(pageNumber);
- if (index !== -1) {
- window.selectedPages.splice(index, 1);
- }
- }
+ let selectPageCommand = new SelectPageCommand(pageNumber, selectCheckbox);
+ selectPageCommand.execute();
- if (window.selectedPages.length > 0 && !window.selectPage) {
- window.toggleSelectPageVisibility();
- }
- if (window.selectedPages.length == 0 && window.selectPage) {
- window.toggleSelectPageVisibility();
- }
-
- window.updateSelectedPagesDisplay();
+ this._pushUndoClearRedo(selectPageCommand);
};
const insertFileButtonContainer = document.createElement("div");
diff --git a/src/main/resources/static/js/multitool/PdfContainer.js b/src/main/resources/static/js/multitool/PdfContainer.js
index 1fa836c31..b5721dfac 100644
--- a/src/main/resources/static/js/multitool/PdfContainer.js
+++ b/src/main/resources/static/js/multitool/PdfContainer.js
@@ -1,11 +1,18 @@
+import { MovePageUpCommand, MovePageDownCommand } from "./commands/move-page.js";
+import { RemoveSelectedCommand } from "./commands/remove.js";
+import { RotateAllCommand, RotateElementCommand } from "./commands/rotate.js";
+import { SplitAllCommand } from "./commands/split.js";
+import { UndoManager } from "./UndoManager.js";
+
class PdfContainer {
fileName;
pagesContainer;
pagesContainerWrapper;
pdfAdapters;
downloadLink;
+ undoManager;
- constructor(id, wrapperId, pdfAdapters) {
+ constructor(id, wrapperId, pdfAdapters, undoManager) {
this.pagesContainer = document.getElementById(id);
this.pagesContainerWrapper = document.getElementById(wrapperId);
this.downloadLink = null;
@@ -31,6 +38,8 @@ class PdfContainer {
this.removeAllElements = this.removeAllElements.bind(this);
this.resetPages = this.resetPages.bind(this);
+ this.undoManager = undoManager || new UndoManager();
+
this.pdfAdapters = pdfAdapters;
this.pdfAdapters.forEach((adapter) => {
@@ -58,6 +67,33 @@ class PdfContainer {
window.removeAllElements = this.removeAllElements;
window.resetPages = this.resetPages;
+ let undoBtn = document.getElementById('undo-btn');
+ let redoBtn = document.getElementById('redo-btn');
+
+ document.addEventListener('undo-manager-update', (e) => {
+ let canUndo = e.detail.canUndo;
+ let canRedo = e.detail.canRedo;
+
+ undoBtn.disabled = !canUndo;
+ redoBtn.disabled = !canRedo;
+ })
+
+ window.undo = () => {
+ if (undoManager.canUndo()) undoManager.undo();
+ else {
+ undoBtn.disabled = !undoManager.canUndo();
+ redoBtn.disabled = !undoManager.canRedo();
+ }
+ }
+
+ window.redo = () => {
+ if (undoManager.canRedo()) undoManager.redo();
+ else {
+ undoBtn.disabled = !undoManager.canUndo();
+ redoBtn.disabled = !undoManager.canRedo();
+ }
+ }
+
const filenameInput = document.getElementById("filename-input");
const downloadBtn = document.getElementById("export-button");
@@ -68,32 +104,28 @@ class PdfContainer {
downloadBtn.disabled = true;
}
- movePageTo(startElement, endElement, scrollTo = false) {
- const childArray = Array.from(this.pagesContainer.childNodes);
- const startIndex = childArray.indexOf(startElement);
- const endIndex = childArray.indexOf(endElement);
-
- // Check & remove page number elements here too if they exist because Firefox doesn't fire the relevant event on page move.
- const pageNumberElement = startElement.querySelector(".page-number");
- if (pageNumberElement) {
- startElement.removeChild(pageNumberElement);
- }
-
- this.pagesContainer.removeChild(startElement);
- if (!endElement) {
- this.pagesContainer.append(startElement);
+ movePageTo(startElement, endElement, scrollTo = false, moveUp = false) {
+ let movePageCommand;
+ if (moveUp) {
+ movePageCommand = new MovePageUpCommand(
+ startElement,
+ endElement,
+ this.pagesContainer,
+ this.pagesContainerWrapper,
+ scrollTo
+ );
} else {
- this.pagesContainer.insertBefore(startElement, endElement);
+ movePageCommand = new MovePageDownCommand(
+ startElement,
+ endElement,
+ this.pagesContainer,
+ this.pagesContainerWrapper,
+ scrollTo
+ );
}
- if (scrollTo) {
- const { width } = startElement.getBoundingClientRect();
- const vector = endIndex !== -1 && startIndex > endIndex ? 0 - width : width;
-
- this.pagesContainerWrapper.scroll({
- left: this.pagesContainerWrapper.scrollLeft + vector,
- });
- }
+ movePageCommand.execute();
+ return movePageCommand;
}
addFiles(nextSiblingElement, blank = false) {
@@ -177,15 +209,10 @@ class PdfContainer {
rotateElement(element, deg) {
- var lastTransform = element.style.rotate;
- if (!lastTransform) {
- lastTransform = "0";
- }
- const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
- const newAngle = lastAngle + deg;
-
- element.style.rotate = newAngle + "deg";
+ let rotateCommand = new RotateElementCommand(element, deg);
+ rotateCommand.execute();
+ return rotateCommand;
}
async addPdfFile(renderer, pdfDocument, nextSiblingElement) {
@@ -280,6 +307,7 @@ class PdfContainer {
}
rotateAll(deg) {
+ let elementsToRotate = [];
for (let i = 0; i < this.pagesContainer.childNodes.length; i++) {
const child = this.pagesContainer.children[i];
if (!child) continue;
@@ -291,8 +319,13 @@ class PdfContainer {
const img = child.querySelector("img");
if (!img) continue;
- this.rotateElement(img, deg);
+ elementsToRotate.push(img);
}
+
+ let rotateAllCommand = new RotateAllCommand(elementsToRotate, deg);
+ rotateAllCommand.execute();
+
+ this.undoManager.pushUndoClearRedo(rotateAllCommand);
}
removeAllElements(){
@@ -307,34 +340,13 @@ class PdfContainer {
deleteSelected() {
window.selectedPages.sort((a, b) => a - b);
- let deletions = 0;
+ let removeSelectedCommand = new RemoveSelectedCommand(
+ this.pagesContainer,
+ window.selectedPages,
+ this.updatePageNumbersAndCheckboxes
+ );
- window.selectedPages.forEach((pageIndex) => {
- const adjustedIndex = pageIndex - 1 - deletions;
- const child = this.pagesContainer.children[adjustedIndex];
- if (child) {
- this.pagesContainer.removeChild(child);
- deletions++;
- }
- });
-
- if (this.pagesContainer.childElementCount === 0) {
- const filenameInput = document.getElementById("filename-input");
- const filenameParagraph = document.getElementById("filename");
- const downloadBtn = document.getElementById("export-button");
-
- if (filenameInput)
- filenameInput.disabled = true;
- filenameInput.value = "";
- if (filenameParagraph)
- filenameParagraph.innerText = "";
-
- downloadBtn.disabled = true;
- }
-
- window.selectedPages = [];
- this.updatePageNumbersAndCheckboxes();
- document.dispatchEvent(new Event("selectedPagesUpdated"));
+ this.undoManager.pushUndoClearRedo(removeSelectedCommand);
}
toggleSelectAll() {
@@ -502,34 +514,17 @@ class PdfContainer {
splitAll() {
const allPages = this.pagesContainer.querySelectorAll(".page-container");
+ let splitAllCommand = new SplitAllCommand(
+ allPages,
+ window.selectPage,
+ window.selectedPages,
+ "split-before"
+ );
+ splitAllCommand.execute();
- if (!window.selectPage) {
- const hasSplit = this.pagesContainer.querySelectorAll(".split-before").length > 0;
- if (hasSplit) {
- allPages.forEach(page => {
- page.classList.remove("split-before");
- });
- } else {
- allPages.forEach(page => {
- page.classList.add("split-before");
- });
- }
- return;
- }
-
- allPages.forEach((page, index) => {
- const pageIndex = index;
- if (window.selectPage && !window.selectedPages.includes(pageIndex)) return;
-
- if (page.classList.contains("split-before")) {
- page.classList.remove("split-before");
- } else {
- page.classList.add("split-before");
- }
- });
+ this.undoManager.pushUndoClearRedo(splitAllCommand);
}
-
async splitPDF(baseDocBytes, splitters) {
const baseDocument = await PDFLib.PDFDocument.load(baseDocBytes);
const pageNum = baseDocument.getPages().length;
diff --git a/src/main/resources/static/js/multitool/UndoManager.js b/src/main/resources/static/js/multitool/UndoManager.js
new file mode 100644
index 000000000..39202f3f6
--- /dev/null
+++ b/src/main/resources/static/js/multitool/UndoManager.js
@@ -0,0 +1,65 @@
+export class UndoManager {
+ _undoStack;
+ _redoStack;
+
+ constructor() {
+ this._undoStack = [];
+ this._redoStack = [];
+ }
+
+ pushUndo(command) {
+ this._undoStack.push(command);
+ this._dispatchStateChange();
+ }
+
+ pushRedo(command) {
+ this._redoStack.push(command);
+ this._dispatchStateChange();
+ }
+
+ pushUndoClearRedo(command) {
+ this._undoStack.push(command);
+ this._redoStack = [];
+ this._dispatchStateChange();
+ }
+
+ undo() {
+ if (!this.canUndo()) return;
+
+ let cmd = this._undoStack.pop();
+ cmd.undo();
+
+ this._redoStack.push(cmd);
+ this._dispatchStateChange();
+ }
+
+ canUndo() {
+ return this._undoStack && this._undoStack.length > 0;
+ }
+
+ redo() {
+ if (!this.canRedo()) return;
+
+ let cmd = this._redoStack.pop();
+ cmd.redo();
+
+ this._undoStack.push(cmd);
+ this._dispatchStateChange();
+ }
+
+ canRedo() {
+ return this._redoStack && this._redoStack.length > 0;
+ }
+
+ _dispatchStateChange() {
+ document.dispatchEvent(
+ new CustomEvent("undo-manager-update", {
+ bubbles: true,
+ detail: {
+ canUndo: this.canUndo(),
+ canRedo: this.canRedo(),
+ },
+ })
+ );
+ }
+}
diff --git a/src/main/resources/static/js/multitool/commands/command.js b/src/main/resources/static/js/multitool/commands/command.js
new file mode 100644
index 000000000..a4ae5fdf0
--- /dev/null
+++ b/src/main/resources/static/js/multitool/commands/command.js
@@ -0,0 +1,5 @@
+export class Command {
+ execute() {}
+ undo() {}
+ redo() {}
+}
diff --git a/src/main/resources/static/js/multitool/commands/delete-page.js b/src/main/resources/static/js/multitool/commands/delete-page.js
new file mode 100644
index 000000000..f9a00e7a9
--- /dev/null
+++ b/src/main/resources/static/js/multitool/commands/delete-page.js
@@ -0,0 +1,74 @@
+import { Command } from "./command.js";
+
+export class DeletePageCommand extends Command {
+ constructor(element, pagesContainer) {
+ super();
+
+ this.element = element;
+ this.pagesContainer = pagesContainer;
+
+ this.filenameInputValue = document.getElementById("filename-input").value;
+
+ const filenameParagraph = document.getElementById("filename");
+ this.filenameParagraphText = filenameParagraph ? filenameParagraph.innerText : "";
+ }
+
+ execute() {
+ this.nextSibling = this.element.nextSibling;
+
+ this.pagesContainer.removeChild(this.element);
+ if (this.pagesContainer.childElementCount === 0) {
+ const filenameInput = document.getElementById("filename-input");
+ const filenameParagraph = document.getElementById("filename");
+ const downloadBtn = document.getElementById("export-button");
+
+ filenameInput.disabled = true;
+ filenameInput.value = "";
+ filenameParagraph.innerText = "";
+
+ downloadBtn.disabled = true;
+ }
+ }
+
+ undo() {
+ let node = this.nextSibling;
+ if (node) this.pagesContainer.insertBefore(this.element, node);
+ else this.pagesContainer.appendChild(this.element);
+
+ const pageNumberElement = this.element.querySelector(".page-number");
+ if (pageNumberElement) {
+ this.element.removeChild(pageNumberElement);
+ }
+
+ const filenameInput = document.getElementById("filename-input");
+ const filenameParagraph = document.getElementById("filename");
+ const downloadBtn = document.getElementById("export-button");
+
+ filenameInput.disabled = false;
+ filenameInput.value = this.filenameInputValue;
+ if (this.filenameParagraph)
+ filenameParagraph.innerText = this.filenameParagraphText;
+
+ downloadBtn.disabled = false;
+ }
+
+ redo() {
+ const pageNumberElement = this.element.querySelector(".page-number");
+ if (pageNumberElement) {
+ this.element.removeChild(pageNumberElement);
+ }
+
+ this.pagesContainer.removeChild(this.element);
+ if (this.pagesContainer.childElementCount === 0) {
+ const filenameInput = document.getElementById("filename-input");
+ const filenameParagraph = document.getElementById("filename");
+ const downloadBtn = document.getElementById("export-button");
+
+ filenameInput.disabled = true;
+ filenameInput.value = "";
+ filenameParagraph.innerText = "";
+
+ downloadBtn.disabled = true;
+ }
+ }
+}
diff --git a/src/main/resources/static/js/multitool/commands/move-page.js b/src/main/resources/static/js/multitool/commands/move-page.js
new file mode 100644
index 000000000..6f7b185ea
--- /dev/null
+++ b/src/main/resources/static/js/multitool/commands/move-page.js
@@ -0,0 +1,133 @@
+import { Command } from "./command.js";
+
+export class AbstractMovePageCommand extends Command {
+ constructor(
+ startElement,
+ endElement,
+ pagesContainer,
+ pagesContainerWrapper,
+ scrollTo = false
+ ) {
+ super();
+
+ this.pagesContainer = pagesContainer;
+ const childArray = Array.from(this.pagesContainer.childNodes);
+
+ this.startIndex = childArray.indexOf(startElement);
+ this.endIndex = childArray.indexOf(endElement);
+
+ this.startElement = startElement;
+ this.endElement = endElement;
+
+ this.scrollTo = scrollTo;
+ this.pagesContainerWrapper = pagesContainerWrapper;
+ }
+
+ execute() {
+ // Check & remove page number elements here too if they exist because Firefox doesn't fire the relevant event on page move.
+ const pageNumberElement = this.startElement.querySelector(".page-number");
+ if (pageNumberElement) {
+ this.startElement.removeChild(pageNumberElement);
+ }
+
+ this.pagesContainer.removeChild(this.startElement);
+ if (!this.endElement) {
+ this.pagesContainer.append(this.startElement);
+ } else {
+ this.pagesContainer.insertBefore(this.startElement, this.endElement);
+ }
+
+ if (this.scrollTo) {
+ const { width } = this.startElement.getBoundingClientRect();
+ const vector =
+ this.endIndex !== -1 && this.startIndex > this.endIndex
+ ? 0 - width
+ : width;
+
+ this.pagesContainerWrapper.scroll({
+ left: this.pagesContainerWrapper.scrollLeft + vector,
+ });
+ }
+ }
+
+ undo() {
+ // Requires overriding in child classes
+
+ }
+
+ redo() {
+ this.execute();
+ }
+}
+
+export class MovePageUpCommand extends AbstractMovePageCommand {
+ constructor(
+ startElement,
+ endElement,
+ pagesContainer,
+ pagesContainerWrapper,
+ scrollTo = false
+ ) {
+ super(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo);
+ }
+
+ undo() {
+ if (this.endElement) {
+ this.pagesContainer.removeChild(this.endElement);
+ this.startElement.insertAdjacentElement("beforebegin", this.endElement);
+ }
+
+ if (this.scrollTo) {
+ const { width } = this.startElement.getBoundingClientRect();
+ const vector =
+ this.endIndex === -1 || this.startIndex <= this.endIndex
+ ? 0 - width
+ : width;
+
+ this.pagesContainerWrapper.scroll({
+ left: this.pagesContainerWrapper.scrollLeft - vector,
+ });
+ }
+ }
+
+ redo() {
+ this.execute();
+ }
+}
+
+export class MovePageDownCommand extends AbstractMovePageCommand {
+ constructor(
+ startElement,
+ endElement,
+ pagesContainer,
+ pagesContainerWrapper,
+ scrollTo = false
+ ) {
+ super(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo);
+ }
+
+ undo() {
+ let previousElement = this.startElement.previousSibling;
+
+ if (this.startElement) {
+ this.pagesContainer.removeChild(this.startElement);
+ previousElement.insertAdjacentElement("beforebegin", this.startElement);
+ }
+
+ if (this.scrollTo) {
+ const { width } = this.startElement.getBoundingClientRect();
+ const vector =
+ this.endIndex === -1 || this.startIndex <= this.endIndex
+ ? 0 - width
+ : width;
+
+ this.pagesContainerWrapper.scroll({
+ left: this.pagesContainerWrapper.scrollLeft - vector,
+ });
+ }
+ }
+
+ redo() {
+ this.execute();
+ }
+}
diff --git a/src/main/resources/static/js/multitool/commands/remove.js b/src/main/resources/static/js/multitool/commands/remove.js
new file mode 100644
index 000000000..0c2dc14e1
--- /dev/null
+++ b/src/main/resources/static/js/multitool/commands/remove.js
@@ -0,0 +1,101 @@
+import { Command } from "./command.js";
+
+export class RemoveSelectedCommand extends Command {
+ constructor(pagesContainer, selectedPages, updatePageNumbersAndCheckboxes) {
+ super();
+ this.pagesContainer = pagesContainer;
+ this.selectedPages = selectedPages;
+
+ this.deletedChildren = [];
+
+ if (updatePageNumbersAndCheckboxes) {
+ this.updatePageNumbersAndCheckboxes = updatePageNumbersAndCheckboxes;
+ } else {
+ const pageDivs = document.querySelectorAll(".pdf-actions_container");
+
+ pageDivs.forEach((div, index) => {
+ const pageNumber = index + 1;
+ const checkbox = div.querySelector(".pdf-actions_checkbox");
+ checkbox.id = `selectPageCheckbox-${pageNumber}`;
+ checkbox.setAttribute("data-page-number", pageNumber);
+ checkbox.checked = window.selectedPages.includes(pageNumber);
+ });
+ }
+
+ const filenameInput = document.getElementById("filename-input");
+ const filenameParagraph = document.getElementById("filename");
+
+ this.originalFilenameInputValue = filenameInput ? filenameInput.value : "";
+ if (filenameParagraph)
+ this.originalFilenameParagraphText = filenameParagraph.innerText;
+ }
+
+ execute() {
+ let deletions = 0;
+
+ this.selectedPages.forEach((pageIndex) => {
+ const adjustedIndex = pageIndex - 1 - deletions;
+ const child = this.pagesContainer.children[adjustedIndex];
+ if (child) {
+ this.pagesContainer.removeChild(child);
+ deletions++;
+
+ this.deletedChildren.push({
+ idx: adjustedIndex,
+ childNode: child,
+ });
+ }
+ });
+
+ if (this.pagesContainer.childElementCount === 0) {
+ const filenameInput = document.getElementById("filename-input");
+ const filenameParagraph = document.getElementById("filename");
+ const downloadBtn = document.getElementById("export-button");
+
+ if (filenameInput) filenameInput.disabled = true;
+ filenameInput.value = "";
+ if (filenameParagraph) filenameParagraph.innerText = "";
+
+ downloadBtn.disabled = true;
+ }
+
+ window.selectedPages = [];
+ this.updatePageNumbersAndCheckboxes();
+ document.dispatchEvent(new Event("selectedPagesUpdated"));
+ }
+
+ undo() {
+ while (this.deletedChildren.length > 0) {
+ let deletedChild = this.deletedChildren.pop();
+ if (this.pagesContainer.children.length <= deletedChild.idx)
+ this.pagesContainer.appendChild(deletedChild.childNode);
+ else {
+ this.pagesContainer.insertBefore(
+ deletedChild.childNode,
+ this.pagesContainer.children[deletedChild.idx]
+ );
+ }
+ }
+
+ if (this.pagesContainer.childElementCount > 0) {
+ const filenameInput = document.getElementById("filename-input");
+ const filenameParagraph = document.getElementById("filename");
+ const downloadBtn = document.getElementById("export-button");
+
+ if (filenameInput) filenameInput.disabled = false;
+ filenameInput.value = this.originalFilenameInputValue;
+ if (filenameParagraph)
+ filenameParagraph.innerText = this.originalFilenameParagraphText;
+
+ downloadBtn.disabled = false;
+ }
+
+ window.selectedPages = this.selectedPages;
+ this.updatePageNumbersAndCheckboxes();
+ document.dispatchEvent(new Event("selectedPagesUpdated"));
+ }
+
+ redo() {
+ this.execute();
+ }
+}
diff --git a/src/main/resources/static/js/multitool/commands/rotate.js b/src/main/resources/static/js/multitool/commands/rotate.js
new file mode 100644
index 000000000..6fb08cb94
--- /dev/null
+++ b/src/main/resources/static/js/multitool/commands/rotate.js
@@ -0,0 +1,74 @@
+import { Command } from "./command.js";
+
+export class RotateElementCommand extends Command {
+ constructor(element, degree) {
+ super();
+ this.element = element;
+ this.degree = degree;
+ }
+
+ execute() {
+ let lastTransform = this.element.style.rotate;
+ if (!lastTransform) {
+ lastTransform = "0";
+ }
+ const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
+ const newAngle = lastAngle + parseInt(this.degree);
+
+ this.element.style.rotate = newAngle + "deg";
+ }
+
+ undo() {
+ let lastTransform = this.element.style.rotate;
+ if (!lastTransform) {
+ lastTransform = "0";
+ }
+
+ const currentAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
+ const undoAngle = currentAngle + -parseInt(this.degree);
+
+ this.element.style.rotate = undoAngle + "deg";
+ }
+
+ redo() {
+ this.execute();
+ }
+}
+
+export class RotateAllCommand extends Command {
+ constructor(elements, degree) {
+ super();
+ this.elements = elements;
+ this.degree = degree;
+ }
+
+ execute() {
+ for (let element of this.elements) {
+ let lastTransform = element.style.rotate;
+ if (!lastTransform) {
+ lastTransform = "0";
+ }
+ const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
+ const newAngle = lastAngle + this.degree;
+
+ element.style.rotate = newAngle + "deg";
+ }
+ }
+
+ undo() {
+ for (let element of this.elements) {
+ let lastTransform = element.style.rotate;
+ if (!lastTransform) {
+ lastTransform = "0";
+ }
+ const currentAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
+ const undoAngle = currentAngle + -this.degree;
+
+ element.style.rotate = undoAngle + "deg";
+ }
+ }
+
+ redo() {
+ this.execute();
+ }
+}
diff --git a/src/main/resources/static/js/multitool/commands/select.js b/src/main/resources/static/js/multitool/commands/select.js
new file mode 100644
index 000000000..b76a25ca1
--- /dev/null
+++ b/src/main/resources/static/js/multitool/commands/select.js
@@ -0,0 +1,59 @@
+import { Command } from "./command.js";
+
+export class SelectPageCommand extends Command {
+ constructor(pageNumber, checkbox) {
+ super();
+ this.pageNumber = pageNumber;
+ this.selectCheckbox = checkbox;
+ }
+
+ execute() {
+ if (this.selectCheckbox.checked) {
+ //adds to array of selected pages
+ window.selectedPages.push(this.pageNumber);
+ } else {
+ //remove page from selected pages array
+ const index = window.selectedPages.indexOf(this.pageNumber);
+ if (index !== -1) {
+ window.selectedPages.splice(index, 1);
+ }
+ }
+
+ if (window.selectedPages.length > 0 && !window.selectPage) {
+ window.toggleSelectPageVisibility();
+ }
+ if (window.selectedPages.length == 0 && window.selectPage) {
+ window.toggleSelectPageVisibility();
+ }
+
+ window.updateSelectedPagesDisplay();
+ }
+
+ undo() {
+ this.selectCheckbox.checked = !this.selectCheckbox.checked;
+ if (this.selectCheckbox.checked) {
+ //adds to array of selected pages
+ window.selectedPages.push(this.pageNumber);
+ } else {
+ //remove page from selected pages array
+ const index = window.selectedPages.indexOf(this.pageNumber);
+ if (index !== -1) {
+ window.selectedPages.splice(index, 1);
+ }
+ }
+
+ if (window.selectedPages.length > 0 && !window.selectPage) {
+ window.toggleSelectPageVisibility();
+ }
+ if (window.selectedPages.length == 0 && window.selectPage) {
+ window.toggleSelectPageVisibility();
+ }
+
+ window.updateSelectedPagesDisplay();
+ }
+
+ redo() {
+ this.selectCheckbox.checked = !this.selectCheckbox.checked;
+ this.execute();
+ }
+}
diff --git a/src/main/resources/static/js/multitool/commands/split.js b/src/main/resources/static/js/multitool/commands/split.js
new file mode 100644
index 000000000..3cbc32cde
--- /dev/null
+++ b/src/main/resources/static/js/multitool/commands/split.js
@@ -0,0 +1,101 @@
+import { Command } from "./command.js";
+
+export class SplitFileCommand extends Command {
+ constructor(element, splitClass) {
+ super();
+ this.element = element;
+ this.splitClass = splitClass;
+ }
+
+ execute() {
+ this.element.classList.toggle(this.splitClass);
+ }
+
+ undo() {
+ this.element.classList.toggle(this.splitClass);
+ }
+
+ redo() {
+ this.execute();
+ }
+}
+
+export class SplitAllCommand extends Command {
+ constructor(elements, isSelectedInWindow, selectedPages, splitClass) {
+ super();
+ this.elements = elements;
+ this.isSelectedInWindow = isSelectedInWindow;
+ this.selectedPages = selectedPages;
+ this.splitClass = splitClass;
+ }
+
+ execute() {
+ if (!this.isSelectedInWindow) {
+ const hasSplit = this._hasSplit(this.elements, this.splitClass);
+ if (hasSplit) {
+ this.elements.forEach((page) => {
+ page.classList.remove(this.splitClass);
+ });
+ } else {
+ this.elements.forEach((page) => {
+ page.classList.add(this.splitClass);
+ });
+ }
+ return;
+ }
+
+ this.elements.forEach((page, index) => {
+ const pageIndex = index;
+ if (this.isSelectedInWindow && !this.selectedPages.includes(pageIndex))
+ return;
+
+ if (page.classList.contains(this.splitClass)) {
+ page.classList.remove(this.splitClass);
+ } else {
+ page.classList.add(this.splitClass);
+ }
+ });
+ }
+
+ _hasSplit() {
+ if (!this.elements || this.elements.length == 0) return false;
+
+ for (const node of this.elements) {
+ if (node.classList.contains(this.splitClass)) return true;
+ }
+
+ return false;
+ }
+
+ undo() {
+ if (!this.isSelectedInWindow) {
+ const hasSplit = this._hasSplit(this.elements, this.splitClass);
+ if (hasSplit) {
+ this.elements.forEach((page) => {
+ page.classList.remove(this.splitClass);
+ });
+ } else {
+ this.elements.forEach((page) => {
+ page.classList.add(this.splitClass);
+ });
+ }
+ return;
+ }
+
+ this.elements.forEach((page, index) => {
+ const pageIndex = index;
+ if (this.isSelectedInWindow && !this.selectedPages.includes(pageIndex))
+ return;
+
+ if (page.classList.contains(this.splitClass)) {
+ page.classList.remove(this.splitClass);
+ } else {
+ page.classList.add(this.splitClass);
+ }
+ });
+ }
+
+ redo() {
+ this.execute();
+ }
+}
diff --git a/src/main/resources/templates/multi-tool.html b/src/main/resources/templates/multi-tool.html
index 2a47a1e92..14e5a85ec 100644
--- a/src/main/resources/templates/multi-tool.html
+++ b/src/main/resources/templates/multi-tool.html
@@ -57,6 +57,27 @@
+
+
+
+
+