Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1595a1799d | ||
|
|
7d621a9966 |
@@ -516,8 +516,6 @@ public class EndpointConfiguration {
|
||||
addEndpointAlternative("compress-pdf", "qpdf");
|
||||
addEndpointAlternative("compress-pdf", "Ghostscript");
|
||||
addEndpointAlternative("compress-pdf", "Java");
|
||||
addEndpointAlternative("crop", "Ghostscript");
|
||||
addEndpointAlternative("crop", "Java");
|
||||
addEndpointAlternative("ocr-pdf", "tesseract");
|
||||
addEndpointAlternative("ocr-pdf", "OCRmyPDF");
|
||||
|
||||
|
||||
@@ -164,7 +164,6 @@ public class ApplicationProperties {
|
||||
private String customGlobalAPIKey;
|
||||
private Jwt jwt = new Jwt();
|
||||
private Validation validation = new Validation();
|
||||
private String xFrameOptions = "DENY";
|
||||
|
||||
public Boolean isAltLogin() {
|
||||
return saml2.getEnabled() || oauth2.getEnabled();
|
||||
|
||||
@@ -221,7 +221,6 @@ tasks.register('npmBuild', Exec) {
|
||||
commandLine = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c', 'npm', 'run', 'build'] : ['npm', 'run', 'build']
|
||||
dependsOn npmInstall
|
||||
inputs.dir(new File(frontendDir, 'src'))
|
||||
inputs.dir(new File(frontendDir, 'public'))
|
||||
inputs.file(new File(frontendDir, 'package.json'))
|
||||
outputs.dir(frontendDistDir)
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ security:
|
||||
revocation:
|
||||
mode: none # Revocation checking mode: 'none' (disabled), 'ocsp' (OCSP only), 'crl' (CRL only), 'ocsp+crl' (OCSP with CRL fallback)
|
||||
hardFail: false # Fail validation if revocation status cannot be determined (true=strict, false=soft-fail)
|
||||
xFrameOptions: DENY # X-Frame-Options header value. Options: 'DENY' (default, prevents all framing), 'SAMEORIGIN' (allows framing from same domain), 'DISABLED' (no X-Frame-Options header sent). Note: automatically set to DISABLED when login is disabled
|
||||
|
||||
premium:
|
||||
key: 00000000-0000-0000-0000-000000000000
|
||||
|
||||
-24
@@ -201,30 +201,6 @@ public class SecurityConfiguration {
|
||||
|
||||
http.csrf(CsrfConfigurer::disable);
|
||||
|
||||
// Configure X-Frame-Options based on settings.yml configuration
|
||||
// When login is disabled, automatically disable X-Frame-Options to allow embedding
|
||||
if (!loginEnabledValue) {
|
||||
http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.disable()));
|
||||
} else {
|
||||
String xFrameOption = securityProperties.getXFrameOptions();
|
||||
if (xFrameOption != null) {
|
||||
http.headers(
|
||||
headers -> {
|
||||
if ("DISABLED".equalsIgnoreCase(xFrameOption)) {
|
||||
headers.frameOptions(frameOptions -> frameOptions.disable());
|
||||
} else if ("SAMEORIGIN".equalsIgnoreCase(xFrameOption)) {
|
||||
headers.frameOptions(frameOptions -> frameOptions.sameOrigin());
|
||||
} else {
|
||||
// Default to DENY
|
||||
headers.frameOptions(frameOptions -> frameOptions.deny());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If not configured, use default DENY
|
||||
http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.deny()));
|
||||
}
|
||||
}
|
||||
|
||||
if (loginEnabledValue) {
|
||||
|
||||
http.addFilterBefore(
|
||||
|
||||
+2
-2
@@ -67,7 +67,7 @@ springBoot {
|
||||
|
||||
allprojects {
|
||||
group = 'stirling.software'
|
||||
version = '2.4.1'
|
||||
version = '2.4.0'
|
||||
|
||||
configurations.configureEach {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
@@ -537,4 +537,4 @@ tasks.register('buildRestartHelper', Jar) {
|
||||
doLast {
|
||||
println "restart-helper.jar created at: ${destinationDirectory.get()}/restart-helper.jar"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+802
-1165
File diff suppressed because it is too large
Load Diff
+31
-32
@@ -7,28 +7,27 @@
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@embedpdf/core": "^2.3.0",
|
||||
"@embedpdf/engines": "^2.3.0",
|
||||
"@embedpdf/models": "^2.3.0",
|
||||
"@embedpdf/plugin-annotation": "^2.3.0",
|
||||
"@embedpdf/plugin-bookmark": "^2.3.0",
|
||||
"@embedpdf/plugin-export": "^2.3.0",
|
||||
"@embedpdf/plugin-history": "^2.3.0",
|
||||
"@embedpdf/plugin-document-manager": "^2.3.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^2.3.0",
|
||||
"@embedpdf/plugin-pan": "^2.3.0",
|
||||
"@embedpdf/plugin-print": "^2.3.0",
|
||||
"@embedpdf/plugin-redaction": "^2.3.0",
|
||||
"@embedpdf/plugin-render": "^2.3.0",
|
||||
"@embedpdf/plugin-rotate": "^2.3.0",
|
||||
"@embedpdf/plugin-scroll": "^2.3.0",
|
||||
"@embedpdf/plugin-search": "^2.3.0",
|
||||
"@embedpdf/plugin-selection": "^2.3.0",
|
||||
"@embedpdf/plugin-spread": "^2.3.0",
|
||||
"@embedpdf/plugin-thumbnail": "^2.3.0",
|
||||
"@embedpdf/plugin-tiling": "^2.3.0",
|
||||
"@embedpdf/plugin-viewport": "^2.3.0",
|
||||
"@embedpdf/plugin-zoom": "^2.3.0",
|
||||
"@embedpdf/core": "^1.5.0",
|
||||
"@embedpdf/engines": "^1.5.0",
|
||||
"@embedpdf/plugin-annotation": "^1.5.0",
|
||||
"@embedpdf/plugin-bookmark": "^1.5.0",
|
||||
"@embedpdf/plugin-export": "^1.5.0",
|
||||
"@embedpdf/plugin-history": "^1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^1.5.0",
|
||||
"@embedpdf/plugin-loader": "^1.5.0",
|
||||
"@embedpdf/plugin-pan": "^1.5.0",
|
||||
"@embedpdf/plugin-print": "^1.5.0",
|
||||
"@embedpdf/plugin-redaction": "^1.5.0",
|
||||
"@embedpdf/plugin-render": "^1.5.0",
|
||||
"@embedpdf/plugin-rotate": "^1.5.0",
|
||||
"@embedpdf/plugin-scroll": "^1.5.0",
|
||||
"@embedpdf/plugin-search": "^1.5.0",
|
||||
"@embedpdf/plugin-selection": "^1.5.0",
|
||||
"@embedpdf/plugin-spread": "^1.5.0",
|
||||
"@embedpdf/plugin-thumbnail": "^1.5.0",
|
||||
"@embedpdf/plugin-tiling": "^1.5.0",
|
||||
"@embedpdf/plugin-viewport": "^1.5.0",
|
||||
"@embedpdf/plugin-zoom": "^1.5.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@iconify/react": "^6.0.2",
|
||||
@@ -44,13 +43,13 @@
|
||||
"@supabase/supabase-js": "^2.47.13",
|
||||
"@tailwindcss/postcss": "^4.1.13",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||
"@tauri-apps/plugin-http": "^2.5.6",
|
||||
"@tauri-apps/plugin-shell": "^2.3.4",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.4",
|
||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"axios": "^1.13.2",
|
||||
"globals": "^17.1.0",
|
||||
"axios": "^1.12.2",
|
||||
"globals": "^17.0.0",
|
||||
"i18next": "^25.5.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -59,7 +58,7 @@
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
"peerjs": "^1.5.5",
|
||||
"posthog-js": "^1.268.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"qrcode.react": "^4.1.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-i18next": "^15.7.3",
|
||||
@@ -127,10 +126,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@iconify-json/material-symbols": "^1.2.53",
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"@iconify-json/material-symbols": "^1.2.48",
|
||||
"@iconify/utils": "^3.0.2",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@tauri-apps/cli": "^2.5.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "تقسيم تلقائي بالحجم/العدد"
|
||||
desc = "تقسيم ملف PDF واحد إلى مستندات متعددة بناءً على الحجم أو عدد الصفحات أو عدد المستندات"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "استبدال وعكس الألوان"
|
||||
desc = "استبدال الألوان أو عكسها في مستندات PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "المسح الآلي للمجلدات"
|
||||
desc = "رابط إلى دليل المسح الآلي للمجلدات"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "دليل SSO"
|
||||
desc = "رابط إلى دليل SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "إعداد معزول"
|
||||
desc = "رابط إلى دليل الإعداد المعزول"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "إضافة كلمة مرور"
|
||||
desc = "تشفير مستند PDF الخاص بك بكلمة مرور."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "تغيير الأذونات"
|
||||
desc = "تغيير قيود المستند وأذوناته"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "أتمتة"
|
||||
desc = "ابنِ تدفّقات عمل متعددة الخطوات بسلسلة إجراءات PDF. مثالي للمهام المتكررة."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "تراكب ملف PDF فوق آخر"
|
||||
title = "تراكب ملفات PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "محرر نصوص PDF"
|
||||
desc = "مراجعة وتحرير صادرات Stirling PDF بصيغة JSON مع تحرير نصوص مجمّعة وإعادة إنشاء PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "الحد الأقصى لعدد محاولات تسجيل الدخ
|
||||
label = "وقت إعادة التعيين لتسجيل الدخول (بالدقائق)"
|
||||
description = "المدة قبل إعادة تعيين عدد المحاولات الفاشلة"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "تعطيل حماية CSRF"
|
||||
description = "تعطيل حماية تزوير طلبات المواقع (غير مستحسن)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Say/Ölçüyə Əsasən Avtomatik Ayır"
|
||||
desc = "PDF-i ölçüyə, səhifə sayına və ya sənəd sayına əsasən bir neçə PDF-ə ayır."
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Rəngi əvəz et və invert et"
|
||||
desc = "PDF sənədlərində rəngləri əvəz edin və ya invert edin"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Avtomatik qovluq skanı"
|
||||
desc = "Avtomatlaşdırılmış qovluq skan təlimatına keçid"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO təlimatı"
|
||||
desc = "SSO təlimatına keçid"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Şəbəkəsiz quraşdırma"
|
||||
desc = "Təcrid edilmiş quraşdırma təlimatına keçid"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Şifr Əlavə Et"
|
||||
desc = "Sənədini şifr ilə kilidlə."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "İcazələri Dəyişdir"
|
||||
desc = "Sənəd məhdudiyyətlərini və icazələrini dəyişin"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Avtomatlaşdır"
|
||||
desc = "PDF əməliyyatlarını zəncirləyərək çoxaddımlı iş axınları qurun. Təkrarlanan tapşırıqlar üçün idealdır."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Bir PDF-i digərinin üstünə qoyur"
|
||||
title = "Üst-Üstə Qoy"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF Mətn Redaktoru"
|
||||
desc = "Qruplaşdırılmış mətn redaktəsi və PDF yenidən yaradılması ilə Stirling PDF JSON ixraclarını nəzərdən keçirin və redaktə edin"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Hesabın bloklanmasından əvvəl maksimal uğursuz giriş cəhdl
|
||||
label = "Girişi sıfırlama vaxtı (dəqiqə)"
|
||||
description = "Uğursuz giriş cəhdlərinin sıfırlanmasına qədər olan vaxt"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF mühafizəsini söndür"
|
||||
description = "Cross-Site Request Forgery mühafizəsini söndür (tövsiyə olunmur)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Авто делене по размер/брой"
|
||||
desc = "Разделете един PDF на множество документи въз основа на размер, брой страници или брой документи"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Смени/обърни цветове"
|
||||
desc = "Заместване или инвертиране на цветове в PDF документи"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Авто сканиране на папки"
|
||||
desc = "Връзка към ръководство за автоматизирано сканиране на папки"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Ръководство за SSO"
|
||||
desc = "Връзка към SSO ръководство"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Настройка за air‑gapped среда"
|
||||
desc = "Връзка към ръководство за air‑gapped настройка"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Добавете парола"
|
||||
desc = "Шифровайте вашия PDF документ с парола."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Промяна на правата"
|
||||
desc = "Промяна на ограниченията и разрешенията на документа"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Автоматизация"
|
||||
desc = "Създавайте многостъпкови работни процеси чрез свързване на PDF действия. Идеално за повтарящи се задачи."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Наслагва PDF файлове върху друг PDF"
|
||||
title = "Наслагване PDF-и"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF текстов редактор"
|
||||
desc = "Преглеждайте и редактирайте JSON експорти на Stirling PDF с групово редактиране на текст и повторно генериране на PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Максимален брой неуспешни опити за
|
||||
label = "Време за нулиране на входа (минути)"
|
||||
description = "Време, преди неуспешните опити за вход да се нулират"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Деактивирай CSRF защита"
|
||||
description = "Деактивира защита срещу Cross-Site Request Forgery (не се препоръчва)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "ཆེ་ཆུང་/གྲངས་ཚད་ཀྱིས་རང་
|
||||
desc = "རང་འགུལ་གྱིས་ཡིག་ཆའི་ཆེ་ཆུང་དང་ཡང་ན་ཤོག་ངོས་གྲངས་ལ་གཞིགས་ནས་PDFsབགོད་དགོས།"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "ཁ་དོག་བརྗེ་ལེན་དང་བསྒྱུར་བ།"
|
||||
desc = "PDFཡིག་ཆའི་ནང་ཁ་དོག་བརྗེ་ལེན་བྱེད་པའམ་ཡང་ན་བསྒྱུར་བ།"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "རང་འགུལ་ཅན་གྱི་སྣོད་བཅུ
|
||||
desc = "རང་འགུལ་སྣོད་ཀྱི་པར་བཤེར་ལམ་སྟོན་ལ་འབྲེལ་མཐུད་བྱེད།"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO ལམ་སྟོན།"
|
||||
desc = "SSO ལམ་སྟོན་ལ་འབྲེལ་མཐུད་བྱེད།"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "མཁའ་དབྱིངས་ཀྱི་ཆ་སྒྲིག་སྒྲིག་བཀོད།"
|
||||
desc = "མཁའ་རླུང་གི་བར་ཐག་གིས་སྒྲིག་པའི་སྒྲིག་ལམ་ལ་འབྲེལ་མཐུད་བྱེད་པ།"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "གསང་གྲངས་ཁ་སྣོན་བྱེད།"
|
||||
desc = "གསང་གྲངས་སྲུང་སྐྱོབ་དང་ཚད་བཀག་PDFཡིག་ཆ་ལ་ཁ་སྣོན་བྱེད་པ།"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "འཕོ་འགྱུར།"
|
||||
desc = "ཡིག་ཆའི་ཚད་བཀག་དང་ཆོག་མཆན་བསྒྱུར་བ།"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "རང་འགུལ་ཅན།"
|
||||
desc = "གོམ་པ་མང་པོའི་ལས་ཀའི་འགྲོ་ལུགས་དེ་PDFབྱ་སྤྱོད་མཉམ་དུ་སྒྲིག་ནས་བཟོས། ལས་འགན་ཡང་བསྐྱར་འཚོག་པར་འཚམ་པོ་ཡོད།"
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "གཞན་ཞིག་གི་སྟེང་དུ་PDFགཅིག་བཀབ་དགོས།"
|
||||
title = "བཀབ་པའི་PDFs"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDFཡིག་འབྲིའི་རྩོམ་སྒྲིག་པ།"
|
||||
desc = "ད་ཡོད་ཀྱི་ཡིག་ཆ་དང་པར་རིས་PDFནང་དུ་རྩོམ་སྒྲིག་བྱས།"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "རྩིས་ཁྲའི་བཀག་སྡོམ་མ་
|
||||
label = "ནང་འཇུག་བསྐྱར་སྒྲིག་དུས་ཚོད།"
|
||||
description = "ནང་འཇུག་ཚོད་ལྟ་འཐུས་ཤོར་མ་བྱུང་གོང་གི་དུས་ཚོད།"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF སྲུང་སྐྱོབ།"
|
||||
description = "ས་ཁོངས་བརྒལ་བའི་རེ་འདུན་རྫུན་བཟོ་སྲུང་སྐྱོབ།"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Divideix auto per mida/pàg."
|
||||
desc = "Divideix un únic PDF en múltiples documents basant-se en la mida, el nombre de pàgines o el nombre de documents"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Substitueix i inverteix el color"
|
||||
desc = "Substitueix o inverteix colors en documents PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Escaneig automàtic de carpeta"
|
||||
desc = "Enllaç a la guia d'escaneig automàtic de carpetes"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Guia d'SSO"
|
||||
desc = "Enllaç a la guia d'SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Configuració air-gapped"
|
||||
desc = "Enllaç a la guia de configuració en entorn air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Afegir Contrasenya"
|
||||
desc = "Xifra el document PDF amb contrasenya."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Canviar Permissos"
|
||||
desc = "Canvia les restriccions i els permisos del document"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatitza"
|
||||
desc = "Construeix fluxos de treball multietapa enllaçant accions PDF. Ideal per a tasques recurrents."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Superposa PDFs sobre un altre PDF"
|
||||
title = "Superposar PDFs"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor de text PDF"
|
||||
desc = "Revisa i edita exportacions JSON de Stirling PDF amb edició de text agrupada i regeneració del PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Nombre màxim d'intents d'inici de sessió fallits abans de bloqu
|
||||
label = "Temps de reinicialització d'inici de sessió (minuts)"
|
||||
description = "Temps abans que es restableixin els intents d'inici de sessió fallits"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Desactiva la protecció CSRF"
|
||||
description = "Desactiva la protecció contra Cross-Site Request Forgery (no recomanat)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Automaticky rozdělit podle velikosti/počtu"
|
||||
desc = "Rozdělí jeden PDF na více dokumentů podle velikosti, počtu stránek nebo počtu dokumentů"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Nahradit a invertovat barvy"
|
||||
desc = "Nahradit nebo invertovat barvy v dokumentech PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Autom. skenování složek"
|
||||
desc = "Odkaz na průvodce automatizovaným skenováním složek"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Průvodce SSO"
|
||||
desc = "Odkaz na průvodce SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Offline nastavení"
|
||||
desc = "Odkaz na průvodce nastavením v odpojeném prostředí"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Přidat heslo"
|
||||
desc = "Zašifrovat váš PDF dokument heslem."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Změnit oprávnění"
|
||||
desc = "Změnit omezení a oprávnění dokumentu"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatizace"
|
||||
desc = "Vytvářejte vícekrokové workflow řetězením akcí PDF. Ideální pro opakující se úlohy."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Překryje PDF nad jiným PDF"
|
||||
title = "Překrýt PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor textu PDF"
|
||||
desc = "Prohlížejte a upravujte exporty JSON ze Stirling PDF se skupinovými úpravami textu a regenerací PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Maximální počet neúspěšných pokusů o přihlášení před
|
||||
label = "Čas pro reset přihlášení (minuty)"
|
||||
description = "Doba, po které se neúspěšné pokusy o přihlášení resetují"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Zakázat ochranu CSRF"
|
||||
description = "Zakázat ochranu proti Cross-Site Request Forgery (nedoporučuje se)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Auto-opdel størrelse/antal"
|
||||
desc = "Opdel en enkelt PDF i flere dokumenter baseret på størrelse, sideantal eller dokumentantal"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Erstat og inverter farver"
|
||||
desc = "Erstat eller inverter farver i PDF-dokumenter"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Automatiseret mappescanning"
|
||||
desc = "Link til guide for automatiseret mappescanning"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO-vejledning"
|
||||
desc = "Link til SSO-vejledning"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped opsætning"
|
||||
desc = "Link til guide for air-gapped opsætning"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Tilføj Adgangskode"
|
||||
desc = "Kryptér dit PDF-dokument med en adgangskode."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Ændre Tilladelser"
|
||||
desc = "Ændr dokumentbegrænsninger og tilladelser"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatiser"
|
||||
desc = "Byg flertrins-workflows ved at kæde PDF-handlinger sammen. Ideelt til tilbagevendende opgaver."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Overlejrer PDF'er oven på en anden PDF"
|
||||
title = "Overlejr PDF'er"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF-teksteditor"
|
||||
desc = "Gennemse og rediger Stirling PDF JSON-eksporter med grupperet tekstredigering og regenerering af PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Maksimalt antal mislykkede loginforsøg før kontolock"
|
||||
label = "Nulstil login (minutter)"
|
||||
description = "Tid før mislykkede loginforsøg nulstilles"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Deaktivér CSRF-beskyttelse"
|
||||
description = "Deaktivér Cross-Site Request Forgery-beskyttelse (anbefales ikke)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Teilen nach Größe/Anzahl"
|
||||
desc = "Teilen Sie ein einzelnes PDF basierend auf Größe, Seitenanzahl oder Dokumentanzahl in mehrere Dokumente auf"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Farbe ersetzen & invertieren"
|
||||
desc = "Farben in PDF-Dokumenten ersetzen oder invertieren"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Autom. Ordner-Scan"
|
||||
desc = "Link zum Leitfaden für automatisches Ordner-Scannen"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO-Anleitung"
|
||||
desc = "Link zum SSO-Leitfaden"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Offline-Setup"
|
||||
desc = "Link zum Air-Gap-Einrichtungsleitfaden"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Passwort hinzufügen"
|
||||
desc = "Das PDF mit einem Passwort verschlüsseln"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Berechtigungen ändern"
|
||||
desc = "Dokumentbeschränkungen und -berechtigungen ändern"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatisieren"
|
||||
desc = "Mehrstufige Arbeitsabläufe durch Verkettung von PDF-Aktionen erstellen. Ideal für wiederkehrende Aufgaben."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Ein PDF über ein anderes legen"
|
||||
title = "PDFs überlagern"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF-Texteditor"
|
||||
desc = "Vorhandenen Text und Bilder in PDFs bearbeiten"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Maximale Anzahl fehlgeschlagener Anmeldeversuche vor Kontosperre"
|
||||
label = "Zurücksetzungszeit für Anmeldungen (Minuten)"
|
||||
description = "Zeit, nach der fehlgeschlagene Anmeldeversuche zurückgesetzt werden"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF-Schutz deaktivieren"
|
||||
description = "Cross-Site Request Forgery-Schutz deaktivieren (nicht empfohlen)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Αυτόματο σπάσιμο με μέγεθος/σελ."
|
||||
desc = "Διαχωρισμός ενός PDF σε πολλαπλά έγγραφα βάσει μεγέθους, αριθμού σελίδων ή αριθμού εγγράφων"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Αλλαγή & αντιστροφή χρώματος"
|
||||
desc = "Αντικαταστήστε ή αντιστρέψτε χρώματα σε έγγραφα PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Αυτόματη σάρωση φακέλων"
|
||||
desc = "Σύνδεσμος προς τον οδηγό αυτοματοποιημένης σάρωσης φακέλων"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Οδηγός SSO"
|
||||
desc = "Σύνδεσμος προς τον οδηγό SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Ρύθμιση Air-gapped"
|
||||
desc = "Σύνδεσμος προς τον οδηγό ρύθμισης Air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Προσθήκη κωδικού"
|
||||
desc = "Κρυπτογράφηση του εγγράφου PDF με κωδικό."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Αλλαγή δικαιωμάτων"
|
||||
desc = "Αλλαγή περιορισμών και δικαιωμάτων εγγράφου"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Αυτοματοποίηση"
|
||||
desc = "Δημιουργήστε ροές πολλών βημάτων συνδέοντας ενέργειες PDF. Ιδανικό για επαναλαμβανόμενες εργασίες."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Επικάλυψη PDF πάνω σε άλλο PDF"
|
||||
title = "Επικάλυψη PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Επεξεργαστής κειμένου PDF"
|
||||
desc = "Επιθεωρήστε και επεξεργαστείτε εξαγωγές JSON του Stirling PDF με ομαδοποιημένη επεξεργασία κειμένου και αναδημιουργία PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Μέγιστος αριθμός αποτυχημένων προσ
|
||||
label = "Χρόνος επαναφοράς σύνδεσης (λεπτά)"
|
||||
description = "Χρόνος πριν μηδενιστούν οι αποτυχημένες προσπάθειες σύνδεσης"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Απενεργοποίηση προστασίας CSRF"
|
||||
description = "Απενεργοποίηση προστασίας Cross-Site Request Forgery (δεν συνιστάται)"
|
||||
|
||||
@@ -690,257 +690,257 @@ workbenchSlide = "Workspace panel"
|
||||
workspace = "Workspace"
|
||||
|
||||
[home.multiTool]
|
||||
tags = "multiple,tools,multi-tool,all-in-one,swiss army,page organizer,page editor,edit pages,manage pages,organize,reorganize"
|
||||
tags = "multiple,tools"
|
||||
title = "PDF Multi Tool"
|
||||
desc = "Merge, Rotate, Rearrange, Split, and Remove pages"
|
||||
|
||||
[home.merge]
|
||||
tags = "combine,join,unite,merge,merge PDFs,combine PDFs,join PDFs,concatenate,append,stitch,combine files,join files,merge documents"
|
||||
tags = "combine,join,unite"
|
||||
title = "Merge"
|
||||
desc = "Easily merge multiple PDFs into one."
|
||||
|
||||
[home.split]
|
||||
tags = "divide,separate,break,split,extract pages,separate pages,divide document,break apart,separate files,unbind,split by page,divide by chapter"
|
||||
tags = "divide,separate,break"
|
||||
title = "Split"
|
||||
desc = "Split PDFs into multiple documents"
|
||||
|
||||
[home.rotate]
|
||||
tags = "turn,flip,orient,rotate,orientation,landscape,portrait,90 degrees,180 degrees,clockwise,anticlockwise,counter-clockwise,fix orientation"
|
||||
tags = "turn,flip,orient"
|
||||
title = "Rotate"
|
||||
desc = "Easily rotate your PDFs."
|
||||
|
||||
[home.convert]
|
||||
tags = "transform,change,convert,PDF to Word,PDF to Excel,PDF to image,Word to PDF,Excel to PDF,PowerPoint to PDF,HTML to PDF,export,import,file conversion,format change,save as"
|
||||
tags = "transform,change"
|
||||
title = "Convert"
|
||||
desc = "Convert files between different formats"
|
||||
|
||||
[home.pdfOrganiser]
|
||||
tags = "organize,rearrange,reorder,organise,arrange pages,sort,move pages,delete pages,remove pages,page management,page organizer,page organiser,resequence"
|
||||
tags = "organize,rearrange,reorder"
|
||||
title = "Organise"
|
||||
desc = "Remove/Rearrange pages in any order"
|
||||
|
||||
[home.addImage]
|
||||
tags = "insert,embed,place,add image,insert image,place image,embed image,add photo,add picture,add logo,graphics,insert picture,place photo,PNG,JPG,JPEG"
|
||||
tags = "insert,embed,place"
|
||||
title = "Add image"
|
||||
desc = "Adds a image onto a set location on the PDF"
|
||||
|
||||
[home.addAttachments]
|
||||
tags = "embed,attach,include,attachments,attach files,embed files,include files,add files,file attachment,associated files,supplementary files"
|
||||
tags = "embed,attach,include"
|
||||
title = "Add Attachments"
|
||||
desc = "Add or remove embedded files (attachments) to/from a PDF"
|
||||
|
||||
[home.watermark]
|
||||
tags = "stamp,mark,overlay,watermark,branding,logo,confidential,draft,copyright,trademark,text overlay,image overlay,background text"
|
||||
tags = "stamp,mark,overlay"
|
||||
title = "Add Watermark"
|
||||
desc = "Add a custom watermark to your PDF document."
|
||||
|
||||
[home.removePassword]
|
||||
tags = "unlock,remove password,unlock PDF,decrypt,remove encryption,unprotect,open protected PDF,password removal,unlock protected,disable password,remove security,remove owner password"
|
||||
tags = "unlock"
|
||||
title = "Remove Password"
|
||||
desc = "Remove password protection from your PDF document."
|
||||
|
||||
[home.compress]
|
||||
tags = "shrink,reduce,optimize,compress,smaller,downsize,file size,reduce size,minimize,make smaller,decrease size,optimize size"
|
||||
tags = "shrink,reduce,optimize"
|
||||
title = "Compress"
|
||||
desc = "Compress PDFs to reduce their file size."
|
||||
|
||||
[home.unlockPDFForms]
|
||||
tags = "unlock,enable,edit,unlock forms,enable forms,editable forms,remove read only,make editable,unlock fields,enable editing,form fields,fillable,unprotect forms"
|
||||
tags = "unlock,enable,edit"
|
||||
title = "Unlock PDF Forms"
|
||||
desc = "Remove read-only property of form fields in a PDF document."
|
||||
|
||||
[home.changeMetadata]
|
||||
tags = "edit,modify,update,metadata,properties,document properties,author,title,subject,keywords,creator,producer,info,document info,file properties"
|
||||
tags = "edit,modify,update"
|
||||
title = "Change Metadata"
|
||||
desc = "Change/Remove/Add metadata from a PDF document"
|
||||
|
||||
[home.ocr]
|
||||
tags = "extract,scan,OCR,optical character recognition,text recognition,scan to text,image to text,scanned document,searchable PDF,make searchable,extract text,recognize text,read scanned"
|
||||
tags = "extract,scan"
|
||||
title = "OCR / Cleanup scans"
|
||||
desc = "Cleanup scans and detects text from images within a PDF and re-adds it as text."
|
||||
|
||||
[home.extractImages]
|
||||
tags = "pull,save,export,extract images,get images,save images,export images,extract photos,extract pictures,pull images,download images,rip images,extract graphics,save photos"
|
||||
tags = "pull,save,export"
|
||||
title = "Extract Images"
|
||||
desc = "Extracts all images from a PDF and saves them to zip"
|
||||
|
||||
[home.scannerImageSplit]
|
||||
tags = "detect,split,photos,auto detect,detect photos,split photos,separate photos,split scanned images,multiple photos,auto split,photo detection,image detection,scan separation"
|
||||
tags = "detect,split,photos"
|
||||
title = "Detect & Split Scanned Photos"
|
||||
desc = "Detect and split scanned photos into separate pages"
|
||||
|
||||
[home.sign]
|
||||
tags = "signature,autograph,e-sign,electronic signature,digital signature,sign document,approval,signoff,authorize,endorse,ink signature,handwriting"
|
||||
tags = "signature,autograph"
|
||||
title = "Sign"
|
||||
desc = "Adds signature to PDF by drawing, text or image"
|
||||
|
||||
[home.annotate]
|
||||
tags = "annotate,highlight,draw,markup,comment,notes,review,redline,feedback,markup tools,sticky notes,shapes,arrows,text box,freehand"
|
||||
tags = "annotate,highlight,draw"
|
||||
title = "Annotate"
|
||||
desc = "Highlight, draw, add notes and shapes in the viewer"
|
||||
|
||||
[home.flatten]
|
||||
tags = "simplify,remove,interactive,flatten,flatten form,remove form fields,make static,finalize form,lock form,disable editing,convert to image,non-editable"
|
||||
tags = "simplify,remove,interactive"
|
||||
title = "Flatten"
|
||||
desc = "Remove all interactive elements and forms from a PDF"
|
||||
|
||||
[home.certSign]
|
||||
tags = "authenticate,PEM,P12,official,encrypt,sign,certificate,PKCS12,JKS,server,manual,auto,digital certificate,certificate signature,PKI,cryptographic signature,trusted signature"
|
||||
tags = "authenticate,PEM,P12,official,encrypt,sign,certificate,PKCS12,JKS,server,manual,auto"
|
||||
title = "Sign with Certificate"
|
||||
desc = "Signs a PDF with a Certificate/Key (PEM/P12)"
|
||||
|
||||
[home.repair]
|
||||
tags = "fix,restore,repair,fix PDF,fix broken,fix corrupt,repair PDF,repair corrupt,broken PDF,corrupt PDF,damaged PDF,recover,fix errors,PDF won't open,can't open PDF,PDF errors,troubleshoot,restore PDF,rebuild,corrupted"
|
||||
tags = "fix,restore"
|
||||
title = "Repair"
|
||||
desc = "Tries to repair a corrupt/broken PDF"
|
||||
|
||||
[home.removeBlanks]
|
||||
tags = "delete,clean,empty,remove blank,delete blank pages,empty pages,white pages,remove empty,clean up,cleanup blank"
|
||||
tags = "delete,clean,empty"
|
||||
title = "Remove Blank pages"
|
||||
desc = "Detects and removes blank pages from a document"
|
||||
|
||||
[home.removeAnnotations]
|
||||
tags = "delete,clean,strip,remove annotations,remove comments,delete comments,remove markup,remove highlights,clean annotations,strip comments,remove notes,delete markup,clear comments"
|
||||
tags = "delete,clean,strip"
|
||||
title = "Remove Annotations"
|
||||
desc = "Removes all comments/annotations from a PDF"
|
||||
|
||||
[home.compare]
|
||||
tags = "difference,compare,diff,compare PDFs,compare documents,find differences,show differences,changes,what changed,track changes,revisions,version compare,side by side,contrast,delta"
|
||||
tags = "difference"
|
||||
title = "Compare"
|
||||
desc = "Compares and shows the differences between 2 PDF Documents"
|
||||
|
||||
[home.removeCertSign]
|
||||
tags = "remove,delete,unlock,remove certificate,remove signature,delete signature,unsigned,remove digital signature,strip signature,remove cert,unsign"
|
||||
tags = "remove,delete,unlock"
|
||||
title = "Remove Certificate Sign"
|
||||
desc = "Remove certificate signature from PDF"
|
||||
|
||||
[home.pageLayout]
|
||||
tags = "layout,arrange,combine,N-up,2-up,4-up,multiple per page,pages per sheet,layout pages,tile,grid layout,multi-page layout,combine on page,handout"
|
||||
tags = "layout,arrange,combine"
|
||||
title = "Multi-Page Layout"
|
||||
desc = "Merge multiple pages of a PDF document into a single page"
|
||||
|
||||
[home.bookletImposition]
|
||||
tags = "booklet,print,binding,imposition,booklet printing,saddle stitch,fold,pamphlet,brochure,print booklet,duplex,two-sided,signature,book layout,page imposition,print layout"
|
||||
tags = "booklet,print,binding"
|
||||
title = "Booklet Imposition"
|
||||
desc = "Create booklets with proper page ordering and multi-page layout for printing and binding"
|
||||
|
||||
[home.scalePages]
|
||||
tags = "resize,adjust,scale,page size,resize page,scale page,change size,adjust size,enlarge,shrink page,fit to page,A4,letter size"
|
||||
tags = "resize,adjust,scale"
|
||||
title = "Adjust page size/scale"
|
||||
desc = "Change the size/scale of a page and/or its contents."
|
||||
|
||||
[home.addPageNumbers]
|
||||
tags = "number,pagination,count,add page numbers,page numbering,page numbers,footer,header,number pages,sequential,pagination tool"
|
||||
tags = "number,pagination,count"
|
||||
title = "Add Page Numbers"
|
||||
desc = "Add Page numbers throughout a document in a set location"
|
||||
|
||||
[home.autoRename]
|
||||
tags = "auto-detect,header-based,organize,relabel,auto rename,automatic rename,smart rename,rename by content,filename,file naming,detect title"
|
||||
tags = "auto-detect,header-based,organize,relabel"
|
||||
title = "Auto Rename PDF File"
|
||||
desc = "Auto renames a PDF file based on its detected header"
|
||||
|
||||
[home.adjustContrast]
|
||||
tags = "contrast,brightness,saturation,adjust colors,color correction,enhance,lighten,darken,improve quality,color balance,hue,vibrance"
|
||||
tags = "contrast,brightness,saturation"
|
||||
title = "Adjust Colours/Contrast"
|
||||
desc = "Adjust Colors/Contrast, Saturation and Brightness of a PDF"
|
||||
|
||||
[home.crop]
|
||||
tags = "trim,cut,resize,crop,crop PDF,trim PDF,trim margins,remove margins,cut edges,trim borders,remove white space,crop pages,trim pages,reduce margins,set margins"
|
||||
tags = "trim,cut,resize"
|
||||
title = "Crop PDF"
|
||||
desc = "Crop a PDF to reduce its size (maintains text!)"
|
||||
|
||||
[home.autoSplitPDF]
|
||||
tags = "auto,split,QR,auto split,QR code,QR split,barcode,automatic split,divider page,separator page,scan divider,batch scanning"
|
||||
tags = "auto,split,QR"
|
||||
title = "Auto Split Pages"
|
||||
desc = "Auto Split Scanned PDF with physical scanned page splitter QR Code"
|
||||
|
||||
[home.sanitize]
|
||||
tags = "clean,purge,remove,sanitize,sanitise,remove scripts,remove javascript,remove metadata,strip metadata,security,clean document,remove hidden data,privacy"
|
||||
tags = "clean,purge,remove"
|
||||
title = "Sanitise"
|
||||
desc = "Remove potentially harmful elements from PDF files"
|
||||
|
||||
[home.getPdfInfo]
|
||||
tags = "info,metadata,details,PDF info,document info,properties,file info,get info,show info,view properties,document properties,statistics,page count,file details,inspect"
|
||||
tags = "info,metadata,details"
|
||||
title = "Get ALL Info on PDF"
|
||||
desc = "Grabs any and all information possible on PDFs"
|
||||
|
||||
[home.pdfToSinglePage]
|
||||
tags = "combine,merge,single,single page,one page,merge to single,combine all,stitch pages,concatenate vertical,long page,poster"
|
||||
tags = "combine,merge,single"
|
||||
title = "PDF to Single Large Page"
|
||||
desc = "Merges all PDF pages into one large single page"
|
||||
|
||||
[home.showJS]
|
||||
tags = "javascript,code,script,show javascript,show JS,find javascript,detect javascript,view javascript,embedded scripts,malware,security,inspect,debug"
|
||||
tags = "javascript,code,script"
|
||||
title = "Show Javascript"
|
||||
desc = "Searches and displays any JS injected into a PDF"
|
||||
|
||||
[home.redact]
|
||||
tags = "censor,blackout,hide,redact,redaction,black out,block out,remove sensitive,hide text,privacy,confidential,GDPR,PII,sensitive data,permanently remove,cover up,legal redaction"
|
||||
tags = "censor,blackout,hide"
|
||||
title = "Redact"
|
||||
desc = "Redacts (blacks out) a PDF based on selected text, drawn shapes and/or selected page(s)"
|
||||
|
||||
[home.splitBySections]
|
||||
tags = "split,sections,divide,split by sections,grid split,divide pages,split into sections,cut pages,divide grid,section split,horizontal split,vertical split"
|
||||
tags = "split,sections,divide"
|
||||
title = "Split PDF by Sections"
|
||||
desc = "Divide each page of a PDF into smaller horizontal and vertical sections"
|
||||
|
||||
[home.addStamp]
|
||||
tags = "stamp,mark,seal,approved,rejected,confidential,stamp tool,rubber stamp,date stamp,approval stamp,received,void,copy,original"
|
||||
tags = "stamp,mark,seal"
|
||||
title = "Add Stamp to PDF"
|
||||
desc = "Add text or add image stamps at set locations"
|
||||
|
||||
[home.removeImage]
|
||||
tags = "remove,delete,clean,remove image,delete image,strip images,remove pictures,delete photos,clean images,reduce size,remove graphics"
|
||||
tags = "remove,delete,clean"
|
||||
title = "Remove image"
|
||||
desc = "Remove image from PDF to reduce file size"
|
||||
|
||||
[home.splitByChapters]
|
||||
tags = "split,chapters,structure,split by chapters,split by bookmarks,bookmarks,outline,table of contents,TOC split,chapter split,divide by sections"
|
||||
tags = "split,chapters,structure"
|
||||
title = "Split PDF by Chapters"
|
||||
desc = "Split a PDF into multiple files based on its chapter structure."
|
||||
|
||||
[home.validateSignature]
|
||||
tags = "validate,verify,certificate,validate signature,verify signature,check signature,digital signature,certificate verification,signature validation,authentic,trust,signed,verify certificate"
|
||||
tags = "validate,verify,certificate"
|
||||
title = "Validate PDF Signature"
|
||||
desc = "Verify digital signatures and certificates in PDF documents"
|
||||
|
||||
[home.swagger]
|
||||
tags = "API,documentation,test,swagger,API docs,REST API,endpoints,developer,API reference,API testing,OpenAPI,integration,developer docs"
|
||||
tags = "API,documentation,test"
|
||||
title = "API Documentation"
|
||||
desc = "View API documentation and test endpoints"
|
||||
|
||||
[home.scannerEffect]
|
||||
tags = "scan,simulate,create,fake scan,look scanned,scanner effect,make look scanned,photocopy effect,simulate scanner,realistic scan"
|
||||
tags = "scan,simulate,create"
|
||||
title = "Scanner Effect"
|
||||
desc = "Create a PDF that looks like it was scanned"
|
||||
|
||||
[home.editTableOfContents]
|
||||
tags = "bookmarks,contents,edit,table of contents,TOC,outline,navigation,chapters,sections,add bookmarks,edit bookmarks,PDF outline"
|
||||
tags = "bookmarks,contents,edit"
|
||||
title = "Edit Table of Contents"
|
||||
desc = "Add or edit bookmarks and table of contents in PDF documents"
|
||||
|
||||
[home.manageCertificates]
|
||||
tags = "certificates,import,export,manage certificates,digital certificates,certificate management,PFX,P12,keystore,import certificate,export certificate,certificate store,PKI"
|
||||
tags = "certificates,import,export"
|
||||
title = "Manage Certificates"
|
||||
desc = "Import, export, or delete digital certificate files used for signing PDFs."
|
||||
|
||||
[home.read]
|
||||
tags = "view,open,display,read,viewer,PDF viewer,PDF reader,open PDF,view PDF,display PDF,preview,browse"
|
||||
tags = "view,open,display"
|
||||
title = "Read"
|
||||
desc = "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."
|
||||
|
||||
[home.reorganizePages]
|
||||
tags = "rearrange,reorder,organize,reorganize,move pages,page order,sort pages,arrange pages,shuffle,resequence"
|
||||
tags = "rearrange,reorder,organize"
|
||||
title = "Reorganize Pages"
|
||||
desc = "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
|
||||
|
||||
[home.extractPages]
|
||||
tags = "pull,select,copy,extract,extract pages,get pages,pull out,save pages,export pages,copy pages,select pages,specific pages"
|
||||
tags = "pull,select,copy"
|
||||
title = "Extract Pages"
|
||||
desc = "Extract specific pages from a PDF document"
|
||||
|
||||
[home.removePages]
|
||||
tags = "delete,extract,exclude,remove pages,delete pages,remove page,delete page,exclude pages,take out pages,discard pages,drop pages"
|
||||
tags = "delete,extract,exclude"
|
||||
title = "Remove Pages"
|
||||
desc = "Remove specific pages from a PDF document"
|
||||
|
||||
@@ -950,57 +950,50 @@ title = "Auto Split by Size/Count"
|
||||
desc = "Automatically split PDFs by file size or page count"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Replace & Invert Colour"
|
||||
desc = "Replace or invert colours in PDF documents"
|
||||
|
||||
[home.devApi]
|
||||
tags = "API,development,documentation,developer,REST,integration,endpoints,programmatic,automation,scripting"
|
||||
tags = "API,development,documentation"
|
||||
title = "API"
|
||||
desc = "Link to API documentation"
|
||||
|
||||
[home.devFolderScanning]
|
||||
tags = "automation,folder,scanning,watch folder,hot folder,automatic processing,batch,monitor folder,auto process,folder monitoring"
|
||||
tags = "automation,folder,scanning"
|
||||
title = "Automated Folder Scanning"
|
||||
desc = "Link to automated folder scanning guide"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO Guide"
|
||||
desc = "Link to SSO guide"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped Setup"
|
||||
desc = "Link to air-gapped setup guide"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Add Password"
|
||||
desc = "Add password protection and restrictions to PDF files"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Change Permissions"
|
||||
desc = "Change document restrictions and permissions"
|
||||
|
||||
[home.automate]
|
||||
tags = "workflow,sequence,automation,automate,batch,batch processing,pipeline,chain,multi-step,recurring,scheduled,automatic,process multiple,bulk operations"
|
||||
tags = "workflow,sequence,automation"
|
||||
title = "Automate"
|
||||
desc = "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Overlay one PDF on top of another"
|
||||
title = "Overlay PDFs"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF Text Editor"
|
||||
desc = "Edit existing text and images inside PDFs"
|
||||
|
||||
[home.addText]
|
||||
tags = "text,annotation,label,add text,insert text,place text,text box,add label,add caption,type on PDF,write on PDF,add words,add note,text overlay,typewriter"
|
||||
tags = "text,annotation,label"
|
||||
title = "Add Text"
|
||||
desc = "Add custom text anywhere in your PDF"
|
||||
|
||||
@@ -4534,13 +4527,6 @@ description = "Maximum number of failed login attempts before account lockout"
|
||||
label = "Login Reset Time (minutes)"
|
||||
description = "Time before failed login attempts are reset"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Disable CSRF Protection"
|
||||
description = "Disable Cross-Site Request Forgery protection (not recommended)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Auto dividir por tamaño/conteo"
|
||||
desc = "Divide un solo PDF en múltiples documentos según su tamaño, número de páginas, o número de documento"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Reemplazar e Invertir Color"
|
||||
desc = "Reemplace o invierta colores en documentos PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Escaneo Automatizado de Carpetas"
|
||||
desc = "Enlace a la guía de escaneo automatizado de carpetas"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Guía de SSO"
|
||||
desc = "Enlace a la guía de SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Configuración Aislada"
|
||||
desc = "Enlace a la guía de configuración aislada"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Proteger con contraseña"
|
||||
desc = "Cifrar documento PDF con contraseña"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Cambiar permisos"
|
||||
desc = "Modificar restricciones y permisos del documento"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatizar"
|
||||
desc = "Crear flujos de trabajo de múltiples pasos encadenando acciones de PDF. Ideal para tareas recurrentes."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Superponer PDFs encima de otro PDF"
|
||||
title = "Superponer PDFs"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor de texto PDF"
|
||||
desc = "Revise y edite exportaciones JSON de Stirling PDF con edición de texto agrupada y regeneración de PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Número máximo de intentos fallidos antes de bloquear la cuenta"
|
||||
label = "Tiempo de restablecimiento del inicio de sesión (minutos)"
|
||||
description = "Tiempo antes de que se restablezcan los intentos fallidos de inicio de sesión"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Desactivar la protección CSRF"
|
||||
description = "Desactivar la protección contra Cross-Site Request Forgery (no recomendado)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Autom. zatitu tamaina/kop."
|
||||
desc = "Split a single PDF into multiple documents based on size, page count, or document count"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Ordeztu eta inbertitu kolorea"
|
||||
desc = "Ordeztu edo alderantzikatu koloreak PDF dokumentuetan"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Karpeta eskaneatze autom."
|
||||
desc = "Esteka karpeta eskaneatze automatizatuaren gidara"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO gida"
|
||||
desc = "Esteka SSO gidara"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Sare isolatuko konfigurazioa"
|
||||
desc = "Esteka sare isolatutako konfigurazio gidara"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Gehitu pasahitza"
|
||||
desc = "Enkriptatu PDF dokumentua pasahitz batekin"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Aldatu baimenak"
|
||||
desc = "Aldatu dokumentuaren murrizketak eta baimenak"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatizatu"
|
||||
desc = "Eraiki hainbat pausotako workflowak PDF ekintzak kateatuz. Egokia zeregin errepikakorretarako."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Overlays PDFs on-top of another PDF"
|
||||
title = "Gainjarri PDFak"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF testu-editorea"
|
||||
desc = "Berrikusi eta editatu Stirling PDF JSON esportazioak taldekatutako testu-edizioarekin eta PDF birsorkuntzarekin"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Kontua blokeatu aurretik huts egindako saio-saiakeren gehienezko
|
||||
label = "Saio-hasieraren berrezartze denbora (minutuak)"
|
||||
description = "Huts egindako saio-saiakerak berrezarri aurreko denbora"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF babesa desgaitu"
|
||||
description = "Desgaitu Cross-Site Request Forgery babesa (ez da gomendatzen)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "تقسیم خودکار بر اساس اندازه/تعداد"
|
||||
desc = "تقسیم یک PDF به چند سند بر اساس اندازه، تعداد صفحات، یا تعداد اسناد"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "جایگزینی و معکوس کردن رنگ"
|
||||
desc = "جایگزینی یا معکوس کردن رنگها در اسناد PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "اسکن خودکار پوشه"
|
||||
desc = "پیوند به راهنمای اسکن خودکار پوشه"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "راهنمای SSO"
|
||||
desc = "پیوند به راهنمای SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "راهاندازی Air-gapped"
|
||||
desc = "پیوند به راهنمای راهاندازی Air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "افزودن رمز عبور"
|
||||
desc = "رمزگذاری سند PDF شما با رمز عبور."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "تغییر مجوزها"
|
||||
desc = "تغییر محدودیتها و مجوزهای سند"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "اتوماسیون"
|
||||
desc = "ساخت گردشکارهای چندمرحلهای با زنجیره کردن اقدامات PDF. مناسب برای کارهای تکرارشونده."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "PDFها را بر روی PDF دیگری همپوشانی میکند"
|
||||
title = "همپوشانی PDFها"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "ویرایشگر متن PDF"
|
||||
desc = "بازبینی و ویرایش خروجیهای JSON Stirling PDF با ویرایش گروهی متن و بازتولید PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "حداکثر تعداد تلاش ناموفق ورود قبل ا
|
||||
label = "زمان بازنشانی ورود (دقیقه)"
|
||||
description = "مدتی که پس از آن تلاشهای ناموفق ورود بازنشانی میشوند"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "غیرفعال کردن محافظت CSRF"
|
||||
description = "غیرفعال کردن محافظت Cross-Site Request Forgery (توصیه نمیشود)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Scinder auto taille/pages"
|
||||
desc = "Séparer un PDF unique en plusieurs documents en fonction de la taille, du nombre de pages ou du nombre de documents."
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Remplacer & inverser couleurs"
|
||||
desc = "Remplacer ou inverser les couleurs dans les documents PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Scan auto de dossiers"
|
||||
desc = "Lien vers le guide d’analyse de dossier automatisée"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Guide SSO"
|
||||
desc = "Lien vers le guide SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Installation isolée"
|
||||
desc = "Lien vers le guide d’installation isolée (air-gapped)"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Ajouter un mot de passe"
|
||||
desc = "Chiffrez votre PDF avec un mot de passe."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Modifier les permissions"
|
||||
desc = "Modifier les restrictions et permissions du document"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatiser"
|
||||
desc = "Créez des workflows multi-étapes en enchaînant des actions PDF. Idéal pour les tâches récurrentes."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Superposer un PDF sur un autre"
|
||||
title = "Superposer des PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Éditeur de texte PDF"
|
||||
desc = "Afficher et modifier les exports JSON de Stirling PDF avec édition de texte groupée et régénération du PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Nombre maximal de tentatives de connexion échouées avant le ver
|
||||
label = "Délai de réinitialisation (minutes)"
|
||||
description = "Délai avant la réinitialisation du compteur de tentatives de connexion échouées"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Désactiver la protection CSRF"
|
||||
description = "Désactiver la protection contre la falsification de requête intersites (non recommandé)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Scoilt auto: méid/líon"
|
||||
desc = "Scoilt PDF amháin i ndoiciméid iolracha bunaithe ar mhéid, líon na leathanach, nó comhaireamh doiciméad"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Ionadaigh & Inbhéartaigh Dath"
|
||||
desc = "Ionadaigh nó inbhéartaigh dathanna i gcáipéisí PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Scanadh Fillteán Uathoibríoch"
|
||||
desc = "Nasc le treoir scantha fillteán uathoibrithe"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Treoir SSO"
|
||||
desc = "Nasc le treoir SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Socrú Air-gapped"
|
||||
desc = "Nasc le treoir socraithe Air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Cuir Pasfhocal leis"
|
||||
desc = "Criptigh do dhoiciméad PDF le focal faire."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Athrú Ceadanna"
|
||||
desc = "Athraigh srianta agus ceadanna cáipéise"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Uathoibrigh"
|
||||
desc = "Tóg sreafaí oibre ilchéime trí ghníomhartha PDF a nascadh le chéile. Foirfe do thascanna athfhillteacha."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Forleagain PDF ar bharr PDF eile"
|
||||
title = "Forleagan PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Eagarthóir Téacs PDF"
|
||||
desc = "Cuir téacs agus íomhánna atá ann cheana in eagar laistigh de PDFanna"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Uasmhéid iarrachtaí logála isteach teipthe sula gcuirtear an c
|
||||
label = "Am Athshocraithe Logála Isteach (nóiméid)"
|
||||
description = "Am sula n‑athshocraítear iarrachtaí logála isteach teipthe"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Díchumasaigh Cosaint CSRF"
|
||||
description = "Díchumasaigh cosaint Cross-Site Request Forgery (ní mholtar)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "आकार/गिनती से ऑटो बाँटें"
|
||||
desc = "एक PDF को आकार, पृष्ठ संख्या, या दस्तावेज़ संख्या के आधार पर कई दस्तावेज़ों में विभाजित करें"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "रंग बदलें/उलटें"
|
||||
desc = "PDF दस्तावेज़ों में रंगों को प्रतिस्थापित या उलटें"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "स्वचालित फ़ोल्डर स्कैनिंग
|
||||
desc = "स्वचालित फ़ोल्डर स्कैनिंग गाइड के लिए लिंक"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO गाइड"
|
||||
desc = "SSO गाइड के लिए लिंक"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "एयर-गैप्ड सेटअप"
|
||||
desc = "एयर-गैप्ड सेटअप गाइड के लिए लिंक"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "पासवर्ड जोड़ें"
|
||||
desc = "पासवर्ड के साथ अपने PDF दस्तावेज को एन्क्रिप्ट करें।"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "अनुमतियां बदलें"
|
||||
desc = "दस्तावेज़ प्रतिबंध और अनुमतियाँ बदलें"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "स्वचालित करें"
|
||||
desc = "PDF क्रियाओं को जोड़कर बहु-चरणीय वर्कफ़्लो बनाएँ। बार-बार होने वाले कार्यों के लिए उपयुक्त।"
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "PDF को दूसरी PDF के ऊपर ओवरले करें"
|
||||
title = "PDF ओवरले करें"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF टेक्स्ट एडिटर"
|
||||
desc = "ग्रुप्ड टेक्स्ट एडिटिंग और PDF पुनर्जनन के साथ Stirling PDF JSON एक्सपोर्ट की समीक्षा व संपादन करें"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "खाते को लॉक करने से पहले
|
||||
label = "लॉगिन रीसेट समय (मिनट)"
|
||||
description = "वह समय जिसके बाद असफल लॉगिन प्रयास रीसेट हो जाते हैं"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF सुरक्षा निष्क्रिय करें"
|
||||
description = "Cross-Site Request Forgery सुरक्षा निष्क्रिय करें (अनुशंसित नहीं)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Auto dijeli po veličini/broju"
|
||||
desc = "Podijelite jedan PDF na više dokumenata na temelju veličine, broja stranica ili broja dokumenata"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Zamijeni i invertiraj boju"
|
||||
desc = "Zamijenite ili invertirajte boje u PDF dokumentima"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Auto skeniranje mapa"
|
||||
desc = "Poveznica na vodič za automatizirano skeniranje mapa"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO vodič"
|
||||
desc = "Poveznica na SSO vodič"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped postavljanje"
|
||||
desc = "Poveznica na vodič za air-gapped postavljanje"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Dodaj lozinku"
|
||||
desc = "Šifrirajte svoj PDF dokument lozinkom.."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Promjena dopuštenja"
|
||||
desc = "Promijenite ograničenja dokumenta i dopuštenja"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatiziraj"
|
||||
desc = "Izgradite višekoračne tijekove rada povezivanjem PDF radnji. Idealno za ponavljajuće zadatke."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Preklapa PDF-ove na drugi PDF"
|
||||
title = "Preklapanje PDF-ova"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF uređivač teksta"
|
||||
desc = "Pregledajte i uredite Stirling PDF JSON izvoze s grupnim uređivanjem teksta i ponovnim generiranjem PDF-a"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Maksimalan broj neuspjelih pokušaja prijave prije zaključavanja
|
||||
label = "Vrijeme resetiranja prijave (minute)"
|
||||
description = "Vrijeme prije resetiranja neuspjelih pokušaja prijave"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Onemogući zaštitu od CSRF-a"
|
||||
description = "Onemogući zaštitu od Cross-Site Request Forgery (ne preporučuje se)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Auto felosztás méret/darab"
|
||||
desc = "Egyetlen PDF felosztása több dokumentumra méret, oldalszám vagy dokumentumszám alapján"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Színek cseréje és invertálása"
|
||||
desc = "Színek cseréje vagy invertálása PDF dokumentumokban"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Automatikus mappaszkennelés"
|
||||
desc = "Hivatkozás az automatikus mappaszkennelés útmutatójára"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO útmutató"
|
||||
desc = "Hivatkozás az SSO útmutatóra"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped beállítás"
|
||||
desc = "Hivatkozás az elszigetelt környezet beállítási útmutatójára"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Jelszó hozzáadása"
|
||||
desc = "PDF dokumentum jelszavas védelme"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Jogosultságok módosítása"
|
||||
desc = "Dokumentumkorlátozások és jogosultságok módosítása"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatizálás"
|
||||
desc = "Többlépéses munkafolyamatok összeállítása PDF műveletek összefűzésével. Ideális ismétlődő feladatokhoz."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "PDF-ek egymásra helyezése egy másik PDF-en"
|
||||
title = "PDF-ek egymásra helyezése"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF szövegszerkesztő"
|
||||
desc = "Nézze át és szerkessze a Stirling PDF JSON exportokat csoportosított szövegszerkesztéssel és PDF-újragenerálással"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Sikertelen bejelentkezési kísérletek maximális száma fiókz
|
||||
label = "Visszaállítás ideje (perc)"
|
||||
description = "Idő, ami után a sikertelen bejelentkezési kísérletek számlálója lenullázódik"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF védelem letiltása"
|
||||
description = "Cross-Site Request Forgery védelem letiltása (nem ajánlott)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Pisah otomatis ukuran/jumlah"
|
||||
desc = "Membagi satu PDF menjadi beberapa dokumen berdasarkan ukuran, jumlah halaman, atau jumlah dokumen"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Ganti & Balik Warna"
|
||||
desc = "Ganti atau balik warna dalam dokumen PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Pemindaian Folder Otomatis"
|
||||
desc = "Tautan ke panduan pemindaian folder otomatis"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Panduan SSO"
|
||||
desc = "Tautan ke panduan SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Penyiapan Air-gapped"
|
||||
desc = "Tautan ke panduan penyiapan air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Tambahkan Kata Sandi"
|
||||
desc = "Enkripsi dokumen PDF Anda dengan kata sandi."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Ganti Perizinan"
|
||||
desc = "Ubah pembatasan dan izin dokumen"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Otomasi"
|
||||
desc = "Bangun alur kerja multi-langkah dengan merangkai tindakan PDF. Ideal untuk tugas berulang."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Menumpuk PDF di atas PDF lain"
|
||||
title = "Tumpuk PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor Teks PDF"
|
||||
desc = "Tinjau dan edit ekspor Stirling PDF JSON dengan pengeditan teks terkelompok dan pembuatan ulang PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Jumlah maksimum kegagalan login sebelum akun terkunci"
|
||||
label = "Waktu Reset Login (menit)"
|
||||
description = "Waktu sebelum upaya login yang gagal direset"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Nonaktifkan Perlindungan CSRF"
|
||||
description = "Nonaktifkan perlindungan Cross-Site Request Forgery (tidak disarankan)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Dividi auto per peso/pagine"
|
||||
desc = "Dividi un singolo PDF in più documenti in base alle dimensioni, al numero di pagine o al numero di documenti"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Sostituisci e inverti colore"
|
||||
desc = "Sostituisci o inverti i colori nei documenti PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Scansione automatica cartelle"
|
||||
desc = "Link alla guida per scansione cartelle automatizzata"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Guida SSO"
|
||||
desc = "Link alla guida SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Setup isolato (air‑gapped)"
|
||||
desc = "Link alla guida per setup air‑gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Aggiungi Password"
|
||||
desc = "Crittografa il tuo PDF con una password."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Cambia Permessi"
|
||||
desc = "Modifica restrizioni e permessi del documento"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatizza"
|
||||
desc = "Crea flussi multi‑step concatenando azioni PDF. Ideale per attività ricorrenti."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Sovrapponi un PDF sopra un altro"
|
||||
title = "Sovrapponi PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor di testo PDF"
|
||||
desc = "Modifica testo e immagini esistenti nei PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Numero massimo di tentativi di accesso falliti prima del blocco d
|
||||
label = "Tempo di reset accessi (minuti)"
|
||||
description = "Tempo prima che i tentativi di accesso falliti vengano azzerati"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Disabilita protezione CSRF"
|
||||
description = "Disabilita la protezione Cross-Site Request Forgery (non raccomandato)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "サイズ・数による自動分割"
|
||||
desc = "サイズ・ページ数またはドキュメント数に基づいて、1つのPDFを複数のドキュメントに分割します。"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "色の置換と反転"
|
||||
desc = "PDF 文書の色を置換または反転"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "自動フォルダスキャン"
|
||||
desc = "自動フォルダスキャン ガイドへのリンク"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO ガイド"
|
||||
desc = "SSO ガイドへのリンク"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "エアギャップ設定"
|
||||
desc = "エアギャップ環境のセットアップガイドへのリンク"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "パスワードの追加"
|
||||
desc = "PDFをパスワードで暗号化します。"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "権限の変更"
|
||||
desc = "文書の制限と権限を変更"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "自動化"
|
||||
desc = "PDF アクションを連結して複数ステップのワークフローを構築。繰り返し作業に最適です。"
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "1つのPDFを別のPDFの上に重ねます"
|
||||
title = "PDFを重ね合わせ"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDFテキストエディター"
|
||||
desc = "グループ化されたテキスト編集とPDF再生成で、Stirling PDF の JSON エクスポートをレビュー・編集します。"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "アカウントロックまでの最大失敗回数"
|
||||
label = "ログインリセット時間(分)"
|
||||
description = "失敗回数がリセットされるまでの時間"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF 保護を無効化"
|
||||
description = "クロスサイトリクエストフォージェリ保護を無効化(非推奨)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "크기/개수별 자동 분할"
|
||||
desc = "단일 PDF를 크기, 페이지 수 또는 문서 수를 기준으로 여러 문서로 분할"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "색상 교체 및 반전"
|
||||
desc = "PDF 문서의 색상을 교체하거나 반전합니다."
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "자동 폴더 스캔"
|
||||
desc = "자동 폴더 스캔 가이드로 이동"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO 가이드"
|
||||
desc = "SSO 가이드로 이동"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "격리 환경 설정"
|
||||
desc = "격리 환경 설정 가이드로 이동"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "비밀번호 추가"
|
||||
desc = "PDF 문서를 비밀번호로 암호화합니다."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "권한 변경"
|
||||
desc = "문서 제한 및 권한 변경"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "자동화"
|
||||
desc = "PDF 작업을 연결하여 다단계 워크플로를 구성하세요. 반복 작업에 이상적입니다."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "PDF를 다른 PDF 위에 오버레이"
|
||||
title = "PDF 오버레이"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF 텍스트 편집기"
|
||||
desc = "그룹화된 텍스트 편집과 PDF 재생성으로 Stirling PDF의 JSON 내보내기를 검토하고 편집하세요"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "계정 잠금 전 허용되는 최대 로그인 실패 횟수"
|
||||
label = "로그인 재설정 시간(분)"
|
||||
description = "로그인 실패 횟수가 재설정되기까지의 시간"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF 보호 비활성화"
|
||||
description = "교차 사이트 요청 위조 보호를 비활성화합니다(권장하지 않음)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "സൈസ്/കൗണ്ട് ഓട്ടോ സ്പ്ലിറ
|
||||
desc = "ഫയൽ വലുപ്പം അല്ലെങ്കിൽ പേജ് എണ്ണം പ്രകാരം PDF-കൾ സ്വയം വിഭജിക്കുക"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "നിറം മാറ്റുക / ഇൻവേർട്ട്"
|
||||
desc = "PDF ഡോക്യുമെന്റുകളിൽ നിറങ്ങൾ പകരംവെക്കുകയോ ഇൻവേർട്ട് ചെയ്യുകയോ ചെയ്യുക"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "ഓട്ടോമേറ്റഡ് ഫോൾഡർ സ്കാനി
|
||||
desc = "ഓട്ടോമേറ്റഡ് ഫോൾഡർ സ്കാനിംഗ് ഗൈഡിലേക്കുള്ള ലിങ്ക്"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO ഗൈഡ്"
|
||||
desc = "SSO ഗൈഡിലേക്കുള്ള ലിങ്ക്"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped സെറ്റപ്പ്"
|
||||
desc = "എയർ-ഗ്യാപ്ഡ് സെറ്റപ്പ് ഗൈഡിലേക്കുള്ള ലിങ്ക്"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "പാസ്വേഡ് ചേർക്കുക"
|
||||
desc = "നിങ്ങളുടെ PDF പ്രമാണം ഒരു പാസ്വേഡ് ഉപയോഗിച്ച് എൻക്രിപ്റ്റ് ചെയ്യുക."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "അനുമതികൾ മാറ്റുക"
|
||||
desc = "ഡോക്യുമെന്റ് നിയന്ത്രണങ്ങളും അനുമതികളും മാറ്റുക"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "ഓട്ടോമേറ്റ്"
|
||||
desc = "PDF പ്രവർത്തനങ്ങൾ ബന്ധിപ്പിച്ച് മൾട്ടി-സ്റ്റെപ്പ് വർക്ഫ്ലോകൾ നിർമ്മിക്കുക. ആവർത്തിക്കുന്ന ജോലികൾക്ക് അനുയോജ്യം."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "മറ്റൊരു PDF-ന് മുകളിൽ PDF-കൾ ഓവർലേ ചെയ്യുന്നു"
|
||||
title = "PDF-കൾ ഓവർലേ ചെയ്യുക"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF ടെക്സ്റ്റ് എഡിറ്റർ"
|
||||
desc = "ഗ്രൂപ്പുചെയ്ത ടെക്സ്റ്റ് എഡിറ്റിംഗിനോടും PDF വീണ്ടും സൃഷ്ടിക്കുന്നതോടും കൂടി Stirling PDF JSON എക്സ്പോർട്ടുകൾ റിവ്യൂ ചെയ്ത് എഡിറ്റ് ചെയ്യുക"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "അക്കൗണ്ട് ലോക്കാകുന്നത
|
||||
label = "ലോഗിൻ റീസെറ്റ് സമയം (മിനിറ്റ്)"
|
||||
description = "പരാജയപ്പെട്ട ലോഗിൻ ശ്രമങ്ങൾ റീസെറ്റ് ചെയ്യുന്നതിന് മുമ്പുള്ള സമയം"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF സംരക്ഷണം അപ്രാപ്തമാക്കുക"
|
||||
description = "Cross-Site Request Forgery സംരക്ഷണം അപ്രാപ്തമാക്കുക (ശുപാർശ ചെയ്യുന്നതല്ല)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Automatisch splitsen op grootte/aantal"
|
||||
desc = "Splits een enkele PDF in meerdere documenten op basis van grootte, aantal pagina's of aantal documenten"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Kleur vervangen en inverteren"
|
||||
desc = "Kleuren in PDF-documenten vervangen of inverteren"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Geautomatiseerd mappenscannen"
|
||||
desc = "Link naar handleiding voor geautomatiseerd mappenscannen"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO-gids"
|
||||
desc = "Link naar SSO-gids"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped-installatie"
|
||||
desc = "Link naar handleiding voor air-gapped-installatie"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Wachtwoord toevoegen"
|
||||
desc = "Versleutel uw PDF-document met een wachtwoord."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Rechten wijzigen"
|
||||
desc = "Documentbeperkingen en machtigingen wijzigen"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatiseren"
|
||||
desc = "Bouw workflows met meerdere stappen door PDF-acties te koppelen. Ideaal voor terugkerende taken."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Plaatst PDF's over een andere PDF heen"
|
||||
title = "PDF's overlappen"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF-teksteditor"
|
||||
desc = "Bewerk bestaande tekst en afbeeldingen in PDF's"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Maximaal aantal mislukte inlogpogingen voordat het account wordt
|
||||
label = "Resetperiode voor inloggen (minuten)"
|
||||
description = "Tijd voordat mislukte inlogpogingen worden gereset"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF-bescherming uitschakelen"
|
||||
description = "Cross-Site Request Forgery-bescherming uitschakelen (niet aanbevolen)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Auto-del størrelse/antall"
|
||||
desc = "Del en enkelt PDF i flere dokumenter basert på størrelse, antall sider eller dokumenter"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Erstatt og inverter farger"
|
||||
desc = "Erstatt eller inverter farger i PDF-dokumenter"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Automatisert mappeskanning"
|
||||
desc = "Lenke til veiledning for automatisert mappeskanning"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO-veiledning"
|
||||
desc = "Lenke til SSO-veiledning"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Luftgap-oppsett"
|
||||
desc = "Lenke til veiledning for luftgap-oppsett"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Legg til Passord"
|
||||
desc = "Krypter din PDF-dokument med et passord."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Endre tillatelser"
|
||||
desc = "Endre dokumentbegrensninger og tillatelser"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatiser"
|
||||
desc = "Bygg flertrinns arbeidsflyter ved å lenke sammen PDF-handlinger. Ideelt for gjentakende oppgaver."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Legger PDF-er over hverandre"
|
||||
title = "Overlay PDF-er"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF-tekstredigerer"
|
||||
desc = "Gå gjennom og rediger Stirling PDF JSON-eksporter med gruppert tekstredigering og regenerering av PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Maksimalt antall mislykkede innloggingsforsøk før kontolåsing"
|
||||
label = "Tilbakestillingstid for innlogging (minutter)"
|
||||
description = "Tid før mislykkede innloggingsforsøk nullstilles"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Deaktiver CSRF-beskyttelse"
|
||||
description = "Deaktiver Cross-Site Request Forgery-beskyttelse (anbefales ikke)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Podziel (Rozmiar/Ilość stron)"
|
||||
desc = "Rozdziela dokument PDF na wiele dokumentów bazując na podanym rozmiarze, ilości stron bądź ilości dokumentów"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Zastąp i odwróć kolor"
|
||||
desc = "Zastępuj lub odwracaj kolory w dokumentach PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Auto skan folderów"
|
||||
desc = "Link do przewodnika automatycznego skanowania folderów"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Przewodnik SSO"
|
||||
desc = "Link do przewodnika SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Konfiguracja odizolowana"
|
||||
desc = "Link do przewodnika konfiguracji odizolowanej"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Dodaj hasło"
|
||||
desc = "Zaszyfruj dokument PDF za pomocą hasła."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Zmień uprawnienia"
|
||||
desc = "Zmień ograniczenia i uprawnienia dokumentu"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatyzuj"
|
||||
desc = "Buduj wieloetapowe przepływy, łącząc akcje PDF. Idealne do powtarzających się zadań."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Nakłada dokumenty PDF na siebie"
|
||||
title = "Nałóż PDFa"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Edytor tekstu PDF"
|
||||
desc = "Przeglądaj i edytuj eksporty JSON z Stirling PDF z grupową edycją tekstu i ponowną generacją PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Maksymalna liczba nieudanych prób logowania przed zablokowaniem
|
||||
label = "Czas resetu prób logowania (minuty)"
|
||||
description = "Czas, po którym licznik nieudanych prób logowania jest resetowany"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Wyłącz ochronę CSRF"
|
||||
description = "Wyłącz ochronę przed Cross-Site Request Forgery (niezalecane)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Divisão Manual do PDF"
|
||||
desc = "Divida um PDF em vários, com base no tamanho, contagem de páginas ou contagem de documentos."
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Substituir e inverter cor"
|
||||
desc = "Substituir ou inverter cores em documentos PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Varredura automática de pasta"
|
||||
desc = "Link para o guia de varredura automática de pastas"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Guia de SSO"
|
||||
desc = "Link para o guia de SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Configuração Air-gapped"
|
||||
desc = "Link para o guia de configuração isolada (air-gapped)"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Proteger PDF"
|
||||
desc = "Criptografar seu PDF com uma senha podendo realizar alterações de permissões."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Alterar Permissões"
|
||||
desc = "Alterar restrições e permissões do documento"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatizar"
|
||||
desc = "Crie fluxos de trabalho de várias etapas encadeando ações de PDF. Ideal para tarefas recorrentes."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Sobrepor um PDF sobre outro"
|
||||
title = "Sobrepor PDFs"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor de texto de PDF"
|
||||
desc = "Revise e edite exportações JSON do Stirling PDF com edição de texto agrupada e regeneração do PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Número máximo de tentativas de login com falha antes do bloquei
|
||||
label = "Tempo para redefinir tentativas (minutos)"
|
||||
description = "Tempo antes que as tentativas de login com falha sejam redefinidas"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Desativar proteção CSRF"
|
||||
description = "Desativar a proteção contra Cross-Site Request Forgery (não recomendado)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Auto-dividir por tamanho/n.º"
|
||||
desc = "Dividir um único PDF em múltiplos documentos baseado em tamanho, contagem de páginas, ou contagem de documentos"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Substituir e inverter cor"
|
||||
desc = "Substituir ou inverter cores em documentos PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Varrimento auto de pastas"
|
||||
desc = "Ligação para o guia de varrimento automático de pastas"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Guia de SSO"
|
||||
desc = "Ligação para o guia de SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Configuração Air-gapped"
|
||||
desc = "Ligação para o guia de configuração Air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Adicionar Palavra-passe"
|
||||
desc = "Encriptar o seu documento PDF com uma palavra-passe."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Alterar Permissões"
|
||||
desc = "Alterar restrições e permissões do documento"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatizar"
|
||||
desc = "Crie fluxos de trabalho de vários passos encadeando ações de PDF. Ideal para tarefas recorrentes."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Sobrepõe PDFs em cima de outro PDF"
|
||||
title = "Sobrepor PDFs"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor de texto PDF"
|
||||
desc = "Revise e edite exportações JSON do Stirling PDF com edição de texto agrupado e regeneração de PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Número máximo de tentativas falhadas de início de sessão ante
|
||||
label = "Tempo de reposição do início de sessão (minutos)"
|
||||
description = "Tempo até que as tentativas falhadas de início de sessão sejam repostas"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Desativar proteção CSRF"
|
||||
description = "Desativar proteção Cross-Site Request Forgery (não recomendado)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Auto-împărțire mărime/pagini"
|
||||
desc = "Împarte un singur PDF în mai multe documente bazat pe dimensiune, număr de pagini sau număr de documente"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Schimbă și inversează culori"
|
||||
desc = "Înlocuiți sau inversați culorile în documente PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Scanare automată foldere"
|
||||
desc = "Link către ghidul de scanare automată a folderelor"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Ghid SSO"
|
||||
desc = "Link către ghidul SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Configurare air-gapped"
|
||||
desc = "Link către ghidul de configurare izolată de rețea"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Adaugă Parolă"
|
||||
desc = "Criptează documentul PDF cu o parolă."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Schimbă Permisiunile"
|
||||
desc = "Schimbați restricțiile și permisiunile documentului"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatizare"
|
||||
desc = "Construiți fluxuri cu mai mulți pași legând acțiuni PDF. Ideal pentru sarcini recurente."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Suprapune PDF-uri peste alt PDF"
|
||||
title = "Suprapune PDF-uri"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor text PDF"
|
||||
desc = "Revizuiește și editează exporturile JSON Stirling PDF cu editare de text grupată și regenerare PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Numărul maxim de încercări eșuate înainte de blocarea contul
|
||||
label = "Timp resetare autentificare (minute)"
|
||||
description = "Timpul înainte de resetarea încercărilor eșuate"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Dezactivează protecția CSRF"
|
||||
description = "Dezactivează protecția Cross-Site Request Forgery (nerecomandat)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Авторазбить по размеру/стр."
|
||||
desc = "Разделяет один PDF на несколько документов на основе размера, количества страниц или количества документов"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Заменить и инвертировать цвет"
|
||||
desc = "Заменяйте или инвертируйте цвета в PDF-документах"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Автосканирование папок"
|
||||
desc = "Ссылка на руководство по автоматическому сканированию папок"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Руководство по SSO"
|
||||
desc = "Ссылка на руководство по SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Изолированная установка"
|
||||
desc = "Ссылка на руководство по изолированной установке"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Добавить пароль"
|
||||
desc = "Зашифруйте ваш PDF-документ паролем."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Изменить разрешения"
|
||||
desc = "Изменение ограничений и разрешений документа"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Автоматизация"
|
||||
desc = "Создавайте многошаговые процессы, связывая PDF-действия. Идеально для повторяющихся задач."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Наложить один PDF поверх другого"
|
||||
title = "Наложение PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Редактор текста в PDF"
|
||||
desc = "Просмотр и редактирование экспортов Stirling PDF в JSON с групповым редактированием текста и регенерацией PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Максимальное число неудачных попыт
|
||||
label = "Время сброса попыток (минуты)"
|
||||
description = "Время до сброса счетчика неудачных попыток входа"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Отключить защиту CSRF"
|
||||
description = "Отключить защиту от межсайтовой подделки запросов (не рекомендуется)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Auto rozdeliť veľkosť/počet"
|
||||
desc = "Rozdelí jeden PDF na viacero dokumentov na základe veľkosti, počtu stránok alebo počtu dokumentov"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Nahradiť a invertovať farby"
|
||||
desc = "Nahradiť alebo invertovať farby v dokumentoch PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Auto skenovanie priečinkov"
|
||||
desc = "Odkaz na príručku k automatizovanému skenovaniu priečinkov"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO príručka"
|
||||
desc = "Odkaz na SSO príručku"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped nastavenie"
|
||||
desc = "Odkaz na príručku k air-gapped nastaveniu"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Pridať heslo"
|
||||
desc = "Šifrovať váš PDF dokument heslom."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Zmeniť povolenia"
|
||||
desc = "Zmeniť obmedzenia a povolenia dokumentu"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatizovať"
|
||||
desc = "Stavať viacstupňové pracovné postupy spájaním akcií PDF. Ideálne pre opakované úlohy."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Prekrýva PDF súbory na iný PDF"
|
||||
title = "Prekrývanie PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor textu PDF"
|
||||
desc = "Kontrolujte a upravujte Stirling PDF JSON exporty so skupinovými úpravami textu a opätovným vytvorením PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Maximálny počet neúspešných pokusov o prihlásenie pred zabl
|
||||
label = "Reset prihlásenia (minúty)"
|
||||
description = "Čas, po ktorom sa zlyhané pokusy o prihlásenie vynulujú"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Zakázať ochranu CSRF"
|
||||
description = "Zakázať ochranu proti Cross-Site Request Forgery (neodporúča sa)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Samodejno razdeli po vel./št."
|
||||
desc = "Razdeli en PDF na več dokumentov glede na velikost, število strani ali število dokumentov"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Zamenjaj in invertiraj barve"
|
||||
desc = "Zamenjajte ali invertirajte barve v dokumentih PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Samodejno skeniranje map"
|
||||
desc = "Povezava do vodiča za avtomatizirano skeniranje map"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Vodič za SSO"
|
||||
desc = "Povezava do vodiča za SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Izolirana namestitev"
|
||||
desc = "Povezava do vodiča za namestitev v izoliranem omrežju"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Dodaj geslo"
|
||||
desc = "Šifrirajte svoj dokument PDF z geslom."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Spremeni dovoljenja"
|
||||
desc = "Spremenite omejitve in dovoljenja dokumenta"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Avtomatiziraj"
|
||||
desc = "Sestavite večkorakovne poteke z veriženjem dejanj PDF. Idealno za ponavljajoče se naloge."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Prekriva PDF-je na vrhu drugega PDF-ja"
|
||||
title = "Prekrivanje PDF-jev"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Urejevalnik besedila PDF"
|
||||
desc = "Pregledujte in urejajte Stirling PDF JSON izvoze z urejanjem združenega besedila in ponovnim ustvarjanjem PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Največje število neuspelih poskusov prijave pred zaklepom raču
|
||||
label = "Čas ponastavitve prijave (minute)"
|
||||
description = "Čas do ponastavitve neuspelih poskusov prijave"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Onemogoči zaščito CSRF"
|
||||
description = "Onemogoči zaščito pred ponarejanjem zahtev (ni priporočljivo)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Automatsko deljenje po veličini/broju"
|
||||
desc = "Deljenje jednog PDF-a na više na osnovu veličine, broja stranica ili broja dokumenata"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Zameni i invertiraj boju"
|
||||
desc = "Zamenite ili invertirajte boje u PDF dokumentima"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Automatsko skeniranje fascikli"
|
||||
desc = "Link ka vodiču za automatsko skeniranje fascikli"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO vodič"
|
||||
desc = "Link ka SSO vodiču"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped podešavanje"
|
||||
desc = "Link ka vodiču za air-gapped podešavanje"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Dodaj lozinku"
|
||||
desc = "Enkriptujte vaš PDF dokument lozinkom."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Promeni dozvole"
|
||||
desc = "Promenite ograničenja dokumenta i dozvole"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatizuj"
|
||||
desc = "Gradite višekorake tokove rada povezivanjem PDF akcija. Idealno za ponavljajuće zadatke."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Preklapa PDF-ove jedan preko drugog"
|
||||
title = "Preklapanje PDF-ova"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF uređivač teksta"
|
||||
desc = "Pregledajte i uređujte Stirling PDF JSON izvoze uz grupisano uređivanje teksta i ponovno generisanje PDF-a"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Maksimalan broj neuspelih pokušaja prijave pre zaključavanja na
|
||||
label = "Vreme resetovanja (minute)"
|
||||
description = "Vreme posle kog se neuspešni pokušaji prijave resetuju"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Onemogući CSRF zaštitu"
|
||||
description = "Onemogući Cross-Site Request Forgery zaštitu (nije preporučeno)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Auto-dela efter storlek/antal"
|
||||
desc = "Dela en enda PDF till flera dokument baserat på storlek, sidantal eller dokumentantal"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Ersätt och invertera färg"
|
||||
desc = "Ersätt eller invertera färger i PDF‑dokument"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Automatiserad mappskanning"
|
||||
desc = "Länk till guide för automatiserad mappskanning"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO‑guide"
|
||||
desc = "Länk till SSO‑guide"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Isolerad installation"
|
||||
desc = "Länk till guide för isolerad installation"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Lägg till lösenord"
|
||||
desc = "Kryptera ditt PDF-dokument med ett lösenord."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Ändra behörigheter"
|
||||
desc = "Ändra dokumentrestriktioner och behörigheter"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Automatisera"
|
||||
desc = "Skapa flerstegade arbetsflöden genom att kedja ihop PDF‑åtgärder. Perfekt för återkommande uppgifter."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Överlagrar PDF:er ovanpå en annan PDF"
|
||||
title = "Överlagra PDF:er"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF-textredigerare"
|
||||
desc = "Granska och redigera Stirling PDF JSON-exporter med grupperad textredigering och PDF-återgenerering"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Maximalt antal misslyckade inloggningsförsök innan kontot låse
|
||||
label = "Återställningstid för inloggning (minuter)"
|
||||
description = "Tid innan misslyckade inloggningsförsök nollställs"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Inaktivera CSRF-skydd"
|
||||
description = "Inaktivera skydd mot Cross-Site Request Forgery (rekommenderas inte)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "แยกตามขนาด/จำนวน"
|
||||
desc = "แยก PDF เป็นเอกสารหลายฉบับตามขนาด จำนวนหน้า หรือจำนวนเอกสาร"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "แทนที่และกลับสี"
|
||||
desc = "แทนที่หรือกลับสีในเอกสาร PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "สแกน โฟลเดอร์ อัตโนมัติ"
|
||||
desc = "ลิงก์ไปยังคู่มือการสแกนโฟลเดอร์อัตโนมัติ"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "คู่มือ SSO"
|
||||
desc = "ลิงก์ไปยังคู่มือ SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "การตั้งค่า Air-gapped"
|
||||
desc = "ลิงก์ไปยังคู่มือการตั้งค่า Air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "เพิ่มรหัสผ่าน"
|
||||
desc = "เข้ารหัสเอกสาร PDF ของคุณด้วยรหัสผ่าน"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "เปลี่ยนสิทธิ์"
|
||||
desc = "เปลี่ยนข้อจำกัดและสิทธิ์ของเอกสาร"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "ทำให้อัตโนมัติ"
|
||||
desc = "สร้างเวิร์กโฟลว์หลายขั้นตอนโดยเชื่อมโยงการกระทำ PDF เข้าด้วยกัน เหมาะสำหรับงานที่เกิดซ้ำ"
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "ซ้อนทับ PDF บน PDF อีกไฟล์หนึ่ง"
|
||||
title = "ซ้อนทับ PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "แก้ไขข้อความ PDF"
|
||||
desc = "ตรวจทานและแก้ไขไฟล์ JSON ที่ส่งออกจาก Stirling PDF ด้วยการแก้ไขข้อความแบบกลุ่มและการสร้าง PDF ใหม่"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "จำนวนครั้งสูงสุดของกา
|
||||
label = "เวลาล้างจำนวนครั้ง (นาที)"
|
||||
description = "ระยะเวลาก่อนที่จะรีเซ็ตจำนวนครั้งที่เข้าสู่ระบบล้มเหลว"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "ปิดการป้องกัน CSRF"
|
||||
description = "ปิดการป้องกัน Cross-Site Request Forgery (ไม่แนะนำ)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Otomatik Boyut/Sayıya Böl"
|
||||
desc = "Tek bir PDF'yi boyut, sayfa sayısı veya belge sayısına göre birden fazla belgeye bölün"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Rengi Değiştir & Ters Çevir"
|
||||
desc = "PDF belgelerindeki renkleri değiştirin veya tersine çevirin"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Otomatik Klasör Tarama"
|
||||
desc = "Otomatik klasör tarama kılavuzuna bağlantı"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO Kılavuzu"
|
||||
desc = "SSO kılavuzuna bağlantı"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Ağdan İzole Kurulum"
|
||||
desc = "Ağdan izole kurulum kılavuzuna bağlantı"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Parola Ekle"
|
||||
desc = "PDF belgenizi bir parola ile şifreleyin."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "İzinleri Değiştir"
|
||||
desc = "Belge kısıtlamalarını ve izinleri değiştirin"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Otomatikleştir"
|
||||
desc = "PDF eylemlerini birbirine bağlayarak çok adımlı iş akışları oluşturun. Tekrarlayan görevler için idealdir."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "PDF'leri başka bir PDF'nin üzerine bindirir"
|
||||
title = "PDF'leri Bindirme"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF Metin Düzenleyici"
|
||||
desc = "Gruplu metin düzenleme ve PDF yeniden oluşturma ile Stirling PDF JSON dışa aktarımlarını gözden geçirin ve düzenleyin"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Hesap kilitlenmeden önceki maksimum başarısız oturum açma de
|
||||
label = "Oturum Açma Sıfırlama Süresi (dakika)"
|
||||
description = "Başarısız oturum açma denemelerinin sıfırlanacağı süre"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF Korumasını Devre Dışı Bırak"
|
||||
description = "Siteler Arası İstek Sahteciliği (CSRF) korumasını devre dışı bırak (önerilmez)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Автоподіл за розміром/стор."
|
||||
desc = "Розділяє один PDF на кілька документів на основі розміру, кількості сторінок або кількості документів"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Замінити й інвертувати колір"
|
||||
desc = "Замінювати або інвертувати кольори в PDF-документах"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Автоматичне сканування папок"
|
||||
desc = "Посилання на посібник із автоматичного сканування папок"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Посібник з SSO"
|
||||
desc = "Посилання на посібник з SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Ізольоване розгортання"
|
||||
desc = "Посилання на посібник з налаштування ізольованого середовища"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Додати пароль"
|
||||
desc = "Зашифруйте документ PDF паролем."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Змінити дозволи"
|
||||
desc = "Змінити обмеження та дозволи документа"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Автоматизація"
|
||||
desc = "Створюйте багатокрокові робочі процеси, поєднуючи дії з PDF. Ідеально для повторюваних завдань."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Накладення одного PDF поверх іншого PDF"
|
||||
title = "Накладення PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Редактор тексту PDF"
|
||||
desc = "Переглядайте й редагуйте JSON-експорти Stirling PDF з груповим редагуванням тексту та повторною генерацією PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Максимальна кількість невдалих спр
|
||||
label = "Час скидання спроб (хвилини)"
|
||||
description = "Час, після якого лічильник невдалих спроб входу скидається"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Вимкнути захист CSRF"
|
||||
description = "Вимкнути захист від Cross-Site Request Forgery (не рекомендовано)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "Tự động chia theo kích thước/số lượng"
|
||||
desc = "Chia một tệp PDF thành nhiều tài liệu dựa trên kích thước, số trang hoặc số lượng tài liệu"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Thay thế & Đảo ngược Màu"
|
||||
desc = "Thay thế hoặc đảo ngược màu trong tài liệu PDF"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "Quét thư mục tự động"
|
||||
desc = "Liên kết tới hướng dẫn quét thư mục tự động"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Hướng dẫn SSO"
|
||||
desc = "Liên kết tới hướng dẫn SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Thiết lập cách ly mạng"
|
||||
desc = "Liên kết tới hướng dẫn thiết lập cách ly mạng"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Thêm mật khẩu"
|
||||
desc = "Mã hóa tài liệu PDF của bạn bằng mật khẩu."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Thay đổi quyền"
|
||||
desc = "Thay đổi hạn chế và quyền của tài liệu"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "Tự động hóa"
|
||||
desc = "Xây dựng quy trình nhiều bước bằng cách xâu chuỗi các thao tác PDF. Lý tưởng cho các tác vụ lặp lại."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Chồng lớp PDF lên trên PDF khác"
|
||||
title = "Chồng lớp PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Trình chỉnh sửa văn bản PDF"
|
||||
desc = "Xem và chỉnh sửa xuất JSON của Stirling PDF với chỉnh sửa văn bản theo nhóm và tái tạo PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "Số lần đăng nhập thất bại tối đa trước khi khó
|
||||
label = "Thời gian đặt lại đăng nhập (phút)"
|
||||
description = "Thời gian trước khi đặt lại số lần đăng nhập thất bại"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Tắt bảo vệ CSRF"
|
||||
description = "Tắt bảo vệ Cross-Site Request Forgery (không khuyến nghị)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "按大小/页数自动拆分"
|
||||
desc = "按文件大小或页数自动拆分 PDF"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "替换与反转颜色"
|
||||
desc = "替换或反转 PDF 文档中的颜色"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "自动化文件夹扫描"
|
||||
desc = "链接到自动化文件夹扫描指南"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO 指南"
|
||||
desc = "链接到 SSO 指南"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "离线隔离部署"
|
||||
desc = "链接到离线隔离部署指南"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "གསང་ཚིག་སྣོན་པ།"
|
||||
desc = "PDF ཡིག་ཆར་གསང་ཚིག་གིས་གསང་སྡོམ་བྱེད་པ།"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "更改权限"
|
||||
desc = "更改文档限制与权限"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "自动化"
|
||||
desc = "通过串联 PDF 动作构建多步工作流。适合重复性任务。"
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "PDF གཞན་ཞིག་གི་སྟེང་དུ་ PDF བརྩེགས་པ།"
|
||||
title = "PDF སྟེང་བརྩེགས།"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF 文本编辑器"
|
||||
desc = "审阅并编辑 Stirling PDF 的 JSON 导出,支持分组文本编辑与 PDF 再生成"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "在账户锁定前允许的最大失败登录次数"
|
||||
label = "登录重置时间(分钟)"
|
||||
description = "失败登录尝试被重置前的时间"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "禁用 CSRF 保护"
|
||||
description = "禁用跨站请求伪造保护(不推荐)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "自动根据大小/数目拆分 PDF"
|
||||
desc = "将单个 PDF 拆分为多个文档,基于大小、页数或文档数"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "替换和反转颜色"
|
||||
desc = "替换或反转 PDF 文档中的颜色"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "自动文件夹扫描"
|
||||
desc = "跳转至自动文件夹扫描指南"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO 指南"
|
||||
desc = "跳转至 SSO 指南"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "离线/隔离部署"
|
||||
desc = "跳转至隔离部署指南"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "添加密码"
|
||||
desc = "使用密码对 PDF 文档进行加密。"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "更改权限"
|
||||
desc = "更改文档限制与权限"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "自动化"
|
||||
desc = "通过串联 PDF 操作构建多步工作流。适合重复性任务。"
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "将一个 PDF 叠加在另一个之上"
|
||||
title = "叠加 PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF 文本编辑器"
|
||||
desc = "审阅并编辑 Stirling PDF 导出的 JSON,支持分组文本编辑并重新生成 PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "达到该失败次数后将锁定账户"
|
||||
label = "登录重置时间(分钟)"
|
||||
description = "重置失败尝试计数的时间"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "禁用CSRF保护"
|
||||
description = "禁用跨站请求伪造保护(不建议)"
|
||||
|
||||
@@ -950,7 +950,6 @@ title = "根據大小/數量自動分割"
|
||||
desc = "根據大小、頁數或文件數將單一 PDF 分割為多個文件"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "取代與反轉顏色"
|
||||
desc = "在 PDF 文件中取代或反轉顏色"
|
||||
|
||||
@@ -965,22 +964,18 @@ title = "自動化資料夾掃描"
|
||||
desc = "連結至自動化資料夾掃描指南"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO 指南"
|
||||
desc = "連結至 SSO 指南"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "隔離網路設定"
|
||||
desc = "連結至隔離網路設定指南"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "新增密碼"
|
||||
desc = "用密碼加密您的 PDF 檔案。"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "變更權限"
|
||||
desc = "變更文件限制與權限"
|
||||
|
||||
@@ -990,12 +985,10 @@ title = "自動化"
|
||||
desc = "將多個 PDF 動作串接,建立多步驟工作流程。適合重複性工作。"
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "將 PDF 覆蓋在另一個 PDF 上"
|
||||
title = "覆蓋 PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF 文字編輯器"
|
||||
desc = "檢視與編輯 Stirling PDF 的 JSON 匯出,支援群組文字編輯與重新產生 PDF"
|
||||
|
||||
@@ -4533,13 +4526,6 @@ description = "帳戶被鎖定前允許的最大登入失敗次數"
|
||||
label = "登入重設時間(分鐘)"
|
||||
description = "登入失敗次數重設所需的時間"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "停用 CSRF 保護"
|
||||
description = "停用跨站請求偽造保護(不建議)"
|
||||
|
||||
Generated
+685
-601
File diff suppressed because it is too large
Load Diff
@@ -18,24 +18,24 @@ name = "app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.5.3", features = [] }
|
||||
tauri-build = { version = "2.2.0", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.9.0", features = [ "devtools"] }
|
||||
tauri-plugin-log = "2.8.0"
|
||||
tauri-plugin-shell = "2.3.4"
|
||||
tauri-plugin-fs = "2.4.5"
|
||||
tauri-plugin-http = { version = "2.5.6", features = ["dangerous-settings"] }
|
||||
tauri-plugin-single-instance = { version = "2.3.7", features = ["deep-link"] }
|
||||
tauri-plugin-store = "2.4.2"
|
||||
tauri-plugin-opener = "2.5.3"
|
||||
tauri-plugin-deep-link = "2.4.6"
|
||||
tauri-plugin-log = "2.0.0-rc"
|
||||
tauri-plugin-shell = "2.1.0"
|
||||
tauri-plugin-fs = "2.4.4"
|
||||
tauri-plugin-http = { version = "2.4.4", features = ["dangerous-settings"] }
|
||||
tauri-plugin-single-instance = { version = "2.3.6", features = ["deep-link"] }
|
||||
tauri-plugin-store = "2.1.0"
|
||||
tauri-plugin-opener = "2.0.0"
|
||||
tauri-plugin-deep-link = "2.4.5"
|
||||
keyring = { version = "3.6.1", features = ["apple-native", "windows-native"] }
|
||||
tokio = { version = "1.0", features = ["time", "sync"] }
|
||||
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls", "rustls-tls-native-roots"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
tiny_http = "0.12"
|
||||
url = "2.5"
|
||||
urlencoding = "2.1"
|
||||
|
||||
@@ -213,20 +213,8 @@ pub async fn login(
|
||||
// Detect if this is Supabase (SaaS) or Spring Boot (self-hosted)
|
||||
let is_supabase = server_url.trim_end_matches('/') == saas_server_url.trim_end_matches('/');
|
||||
|
||||
// Create HTTP client with certificate bypass
|
||||
// This handles:
|
||||
// - Self-signed certificates
|
||||
// - Missing intermediate certificates
|
||||
// - Certificate hostname mismatches
|
||||
// Note: Rustls only supports TLS 1.2 and TLS 1.3
|
||||
let client = reqwest::Client::builder()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to create HTTP client: {}", e);
|
||||
format!("Failed to create HTTP client: {}", e)
|
||||
})?;
|
||||
// Create HTTP client
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
if is_supabase {
|
||||
// Supabase authentication flow
|
||||
@@ -247,24 +235,7 @@ pub async fn login(
|
||||
.json(&request_body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let error_msg = e.to_string();
|
||||
let error_lower = error_msg.to_lowercase();
|
||||
log::error!("Supabase login network error: {}", e);
|
||||
|
||||
// Detect TLS version mismatch
|
||||
if error_lower.contains("peer is incompatible") ||
|
||||
error_lower.contains("protocol version") ||
|
||||
error_lower.contains("peerincompatible") ||
|
||||
(error_lower.contains("handshake") && (error_lower.contains("tls") || error_lower.contains("ssl"))) {
|
||||
format!(
|
||||
"TLS version not supported: The Supabase server appears to require an unsupported TLS version. \
|
||||
Please contact support. Technical details: {}", e
|
||||
)
|
||||
} else {
|
||||
format!("Network error connecting to Supabase: {}", e)
|
||||
}
|
||||
})?;
|
||||
.map_err(|e| format!("Network error: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
@@ -325,39 +296,7 @@ pub async fn login(
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let error_msg = e.to_string();
|
||||
let error_lower = error_msg.to_lowercase();
|
||||
log::error!("Spring Boot login network error: {}", e);
|
||||
|
||||
// Detect TLS version mismatch (server using TLS 1.0/1.1)
|
||||
if error_lower.contains("peer is incompatible") ||
|
||||
error_lower.contains("protocol version") ||
|
||||
error_lower.contains("peerincompatible") ||
|
||||
(error_lower.contains("handshake") && (error_lower.contains("tls") || error_lower.contains("ssl"))) {
|
||||
format!(
|
||||
"TLS version not supported: The server appears to be using TLS 1.0 or TLS 1.1, which are not supported by this desktop app. \
|
||||
Please upgrade your server to use TLS 1.2 or higher, or use the web version of Stirling-PDF instead. \
|
||||
Technical details: {}", e
|
||||
)
|
||||
// Other TLS/SSL errors (certificate issues)
|
||||
} else if error_lower.contains("tls") || error_lower.contains("ssl") ||
|
||||
error_lower.contains("certificate") || error_lower.contains("decrypt") {
|
||||
format!(
|
||||
"TLS/SSL connection error: This usually means the server has certificate issues. \
|
||||
The desktop app accepts self-signed certificates, so this might be a TLS version issue. \
|
||||
Technical details: {}", e
|
||||
)
|
||||
} else if error_lower.contains("connection refused") {
|
||||
format!("Connection refused: Server is not reachable at {}. Check if the server is running and the URL is correct.", login_url)
|
||||
} else if error_lower.contains("timeout") {
|
||||
format!("Connection timeout: Server at {} is not responding. Check your network connection.", login_url)
|
||||
} else if error_lower.contains("dns") || error_lower.contains("resolve") {
|
||||
format!("DNS resolution failed: Cannot resolve hostname. Check if the server URL is correct.")
|
||||
} else {
|
||||
format!("Network error: {}", e)
|
||||
}
|
||||
})?;
|
||||
.map_err(|e| format!("Network error: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
log::debug!("Spring Boot login response status: {}", status);
|
||||
@@ -566,20 +505,7 @@ async fn exchange_code_for_token(
|
||||
) -> Result<OAuthCallbackResult, String> {
|
||||
log::info!("Exchanging authorization code for access token with PKCE");
|
||||
|
||||
// Create HTTP client with certificate bypass
|
||||
// This handles:
|
||||
// - Self-signed certificates
|
||||
// - Missing intermediate certificates
|
||||
// - Certificate hostname mismatches
|
||||
// Note: Rustls only supports TLS 1.2 and TLS 1.3
|
||||
let client = reqwest::Client::builder()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to create HTTP client: {}", e);
|
||||
format!("Failed to create HTTP client: {}", e)
|
||||
})?;
|
||||
let client = reqwest::Client::new();
|
||||
// grant_type goes in query string, not body!
|
||||
let token_url = format!("{}/auth/v1/token?grant_type=pkce", auth_server_url.trim_end_matches('/'));
|
||||
|
||||
@@ -600,24 +526,7 @@ async fn exchange_code_for_token(
|
||||
.json(&body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let error_msg = e.to_string();
|
||||
let error_lower = error_msg.to_lowercase();
|
||||
log::error!("OAuth token exchange network error: {}", e);
|
||||
|
||||
// Detect TLS version mismatch
|
||||
if error_lower.contains("peer is incompatible") ||
|
||||
error_lower.contains("protocol version") ||
|
||||
error_lower.contains("peerincompatible") ||
|
||||
(error_lower.contains("handshake") && (error_lower.contains("tls") || error_lower.contains("ssl"))) {
|
||||
format!(
|
||||
"TLS version not supported: The authentication server appears to require an unsupported TLS version. \
|
||||
Please contact support. Technical details: {}", e
|
||||
)
|
||||
} else {
|
||||
format!("Failed to exchange code for token: {}", e)
|
||||
}
|
||||
})?;
|
||||
.map_err(|e| format!("Failed to exchange code for token: {}", e))?;
|
||||
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
|
||||
@@ -11,8 +11,6 @@ pub enum ConnectionMode {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerConfig {
|
||||
pub url: String,
|
||||
#[serde(default)]
|
||||
pub allow_invalid_certs: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "Stirling-PDF",
|
||||
"version": "2.4.1",
|
||||
"version": "2.4.0",
|
||||
"identifier": "stirling.pdf.dev",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
@@ -16,8 +16,7 @@
|
||||
"width": 1280,
|
||||
"height": 800,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"additionalBrowserArgs": "--enable-features=CertVerifierBuiltinFeature"
|
||||
"fullscreen": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Box } from '@mantine/core';
|
||||
import { useRainbowThemeContext } from '@app/components/shared/RainbowThemeProvider';
|
||||
import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext';
|
||||
import { useFileHandler } from '@app/hooks/useFileHandler';
|
||||
import { useFileState } from '@app/contexts/FileContext';
|
||||
import { useFileState, useFileActions } from '@app/contexts/FileContext';
|
||||
import { useNavigationState, useNavigationActions, useNavigationGuard } from '@app/contexts/NavigationContext';
|
||||
import { isBaseWorkbench } from '@app/types/workbench';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
@@ -26,6 +26,7 @@ export default function Workbench() {
|
||||
|
||||
// Use context-based hooks to eliminate all prop drilling
|
||||
const { selectors } = useFileState();
|
||||
const { actions: fileActions } = useFileActions();
|
||||
const { workbench: currentView } = useNavigationState();
|
||||
const { actions: navActions } = useNavigationActions();
|
||||
const setCurrentView = navActions.setWorkbench;
|
||||
@@ -61,13 +62,35 @@ export default function Workbench() {
|
||||
const handleFileSelect = useCallback((index: number) => {
|
||||
// Don't do anything if selecting the same file
|
||||
if (index === activeFileIndex) return;
|
||||
|
||||
|
||||
// requestNavigation handles the unsaved changes check internally
|
||||
requestNavigation(() => {
|
||||
setActiveFileIndex(index);
|
||||
});
|
||||
}, [activeFileIndex, requestNavigation, setActiveFileIndex]);
|
||||
|
||||
// Handle file removal from dropdown
|
||||
const handleFileRemove = useCallback(async (fileId: string, index: number) => {
|
||||
// Remove the file from FileContext (handles memory cleanup)
|
||||
await fileActions.removeFiles([fileId]);
|
||||
|
||||
// Adjust activeFileIndex if needed
|
||||
if (activeFiles.length > 1) {
|
||||
if (index === activeFileIndex) {
|
||||
// Removing the active file - switch to the next file (or previous if it's the last)
|
||||
const newIndex = index >= activeFiles.length - 1 ? Math.max(0, index - 1) : index;
|
||||
setActiveFileIndex(newIndex);
|
||||
} else if (index < activeFileIndex) {
|
||||
// Removing a file before the active one - decrement active index
|
||||
setActiveFileIndex(activeFileIndex - 1);
|
||||
}
|
||||
// If removing a file after the active one, no index adjustment needed
|
||||
} else {
|
||||
// Last file removed - reset to index 0 (will show landing page)
|
||||
setActiveFileIndex(0);
|
||||
}
|
||||
}, [activeFiles.length, activeFileIndex, fileActions, setActiveFileIndex]);
|
||||
|
||||
const handlePreviewClose = () => {
|
||||
setPreviewFile(null);
|
||||
const previousMode = sessionStorage.getItem('previousMode');
|
||||
@@ -199,6 +222,7 @@ export default function Workbench() {
|
||||
})}
|
||||
currentFileIndex={activeFileIndex}
|
||||
onFileSelect={handleFileSelect}
|
||||
onFileRemove={handleFileRemove}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Menu, Loader, Group, Text } from '@mantine/core';
|
||||
import { Menu, Loader, Group, Text, ActionIcon, Tooltip } from '@mantine/core';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import FitText from '@app/components/shared/FitText';
|
||||
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface FileDropdownMenuProps {
|
||||
displayName: string;
|
||||
activeFiles: Array<{ fileId: string; name: string; versionNumber?: number }>;
|
||||
currentFileIndex: number;
|
||||
onFileSelect?: (index: number) => void;
|
||||
onFileRemove?: (fileId: string, index: number) => void;
|
||||
switchingTo?: string | null;
|
||||
viewOptionStyle: React.CSSProperties;
|
||||
pillRef?: React.RefObject<HTMLDivElement>;
|
||||
@@ -20,9 +23,12 @@ export const FileDropdownMenu: React.FC<FileDropdownMenuProps> = ({
|
||||
activeFiles,
|
||||
currentFileIndex,
|
||||
onFileSelect,
|
||||
onFileRemove,
|
||||
switchingTo,
|
||||
viewOptionStyle,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Menu trigger="click" position="bottom" width="30rem">
|
||||
<Menu.Target>
|
||||
@@ -68,11 +74,39 @@ export const FileDropdownMenu: React.FC<FileDropdownMenuProps> = ({
|
||||
<FitText text={itemName} fontSize={14} minimumFontScale={0.7} />
|
||||
</PrivateContent>
|
||||
</div>
|
||||
{file.versionNumber && file.versionNumber > 1 && (
|
||||
<Text size="xs" c="dimmed">
|
||||
v{file.versionNumber}
|
||||
</Text>
|
||||
)}
|
||||
<Group gap="xs" style={{ flexShrink: 0 }}>
|
||||
{file.versionNumber && file.versionNumber > 1 && (
|
||||
<Text size="xs" c="dimmed">
|
||||
v{file.versionNumber}
|
||||
</Text>
|
||||
)}
|
||||
{onFileRemove && (
|
||||
<Tooltip label={t('close', 'Close')} position="top" withArrow>
|
||||
<ActionIcon
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onFileRemove(file.fileId, index);
|
||||
}}
|
||||
style={{
|
||||
opacity: 0.6,
|
||||
transition: 'opacity 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.opacity = '1';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.opacity = '0.6';
|
||||
}}
|
||||
aria-label={t('close', 'Close')}
|
||||
>
|
||||
<CloseIcon style={{ fontSize: 14 }} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
</Menu.Item>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,6 @@ import WarningRoundedIcon from '@mui/icons-material/WarningRounded';
|
||||
import { Z_INDEX_OVER_FILE_MANAGER_MODAL } from '@app/styles/zIndex';
|
||||
import { withBasePath } from '@app/constants/app';
|
||||
import { convertImageToPdf, isImageFile } from '@app/utils/imageToPdfUtils';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
|
||||
interface MobileUploadModalProps {
|
||||
opened: boolean;
|
||||
@@ -76,27 +75,26 @@ export default function MobileUploadModal({ opened, onClose, onFilesReceived }:
|
||||
|
||||
// Use configured frontendUrl if set, otherwise use current origin
|
||||
// Combine with base path and mobile-scanner route
|
||||
const baseUrl = localStorage.getItem('server_url') || '';
|
||||
const frontendUrl = baseUrl || config?.frontendUrl || window.location.origin;
|
||||
const frontendUrl = config?.frontendUrl || window.location.origin;
|
||||
const mobileUrl = `${frontendUrl}${withBasePath('/mobile-scanner')}?session=${sessionId}`;
|
||||
|
||||
// Create session on backend
|
||||
const createSession = useCallback(async (newSessionId: string) => {
|
||||
try {
|
||||
const response = await apiClient.post<SessionInfo>(`/api/v1/mobile-scanner/create-session/${newSessionId}`, undefined, {
|
||||
responseType: 'json',
|
||||
const response = await fetch(`/api/v1/mobile-scanner/create-session/${newSessionId}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (!response.status || response.status !== 200) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to create session');
|
||||
}
|
||||
|
||||
const data = response.data;
|
||||
const data = await response.json();
|
||||
setSessionInfo(data);
|
||||
setError(null);
|
||||
console.log('[MobileUploadModal] Session created:', data);
|
||||
console.log('Session created:', data);
|
||||
} catch (err) {
|
||||
console.error('[MobileUploadModal] Failed to create session:', err);
|
||||
console.error('Failed to create session:', err);
|
||||
setError(t('mobileUpload.sessionCreateError', 'Failed to create session'));
|
||||
}
|
||||
}, [t]);
|
||||
@@ -115,12 +113,12 @@ export default function MobileUploadModal({ opened, onClose, onFilesReceived }:
|
||||
if (!opened) return;
|
||||
|
||||
try {
|
||||
const response = await apiClient.get(`/api/v1/mobile-scanner/files/${sessionId}`);
|
||||
if (!response.status || response.status !== 200) {
|
||||
const response = await fetch(`/api/v1/mobile-scanner/files/${sessionId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to check for files');
|
||||
}
|
||||
|
||||
const data = response.data;
|
||||
const data = await response.json();
|
||||
const files = data.files || [];
|
||||
|
||||
// Download only files we haven't processed yet
|
||||
@@ -129,14 +127,12 @@ export default function MobileUploadModal({ opened, onClose, onFilesReceived }:
|
||||
if (newFiles.length > 0) {
|
||||
for (const fileMetadata of newFiles) {
|
||||
try {
|
||||
const downloadResponse = await apiClient.get(
|
||||
`/api/v1/mobile-scanner/download/${sessionId}/${fileMetadata.filename}`, {
|
||||
responseType: 'blob',
|
||||
}
|
||||
const downloadResponse = await fetch(
|
||||
`/api/v1/mobile-scanner/download/${sessionId}/${fileMetadata.filename}`
|
||||
);
|
||||
|
||||
if (downloadResponse.status === 200) {
|
||||
const blob = downloadResponse.data;
|
||||
if (downloadResponse.ok) {
|
||||
const blob = await downloadResponse.blob();
|
||||
let file = new File([blob], fileMetadata.filename, {
|
||||
type: fileMetadata.contentType || 'image/jpeg'
|
||||
});
|
||||
@@ -149,9 +145,9 @@ export default function MobileUploadModal({ opened, onClose, onFilesReceived }:
|
||||
pageFormat: config?.mobileScannerPageFormat as 'keep' | 'A4' | 'letter' | undefined,
|
||||
stretchToFit: config?.mobileScannerStretchToFit,
|
||||
});
|
||||
console.log('[MobileUploadModal] Converted image to PDF:', file.name);
|
||||
console.log('Converted image to PDF:', file.name);
|
||||
} catch (convertError) {
|
||||
console.warn('[MobileUploadModal] Failed to convert image to PDF, using original file:', convertError);
|
||||
console.warn('Failed to convert image to PDF, using original file:', convertError);
|
||||
// Continue with original image file if conversion fails
|
||||
}
|
||||
}
|
||||
@@ -161,21 +157,21 @@ export default function MobileUploadModal({ opened, onClose, onFilesReceived }:
|
||||
onFilesReceived([file]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[MobileUploadModal] Failed to download file:', fileMetadata.filename, err);
|
||||
console.error('Failed to download file:', fileMetadata.filename, err);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the entire session immediately after downloading all files
|
||||
// This ensures files are only on server for ~1 second
|
||||
try {
|
||||
await apiClient.delete(`/api/v1/mobile-scanner/session/${sessionId}`);
|
||||
console.log('[MobileUploadModal] Session cleaned up after file download');
|
||||
await fetch(`/api/v1/mobile-scanner/session/${sessionId}`, { method: 'DELETE' });
|
||||
console.log('Session cleaned up after file download');
|
||||
} catch (cleanupErr) {
|
||||
console.warn('[MobileUploadModal] Failed to cleanup session after download:', cleanupErr);
|
||||
console.warn('Failed to cleanup session after download:', cleanupErr);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[MobileUploadModal] Error polling for files:', err);
|
||||
console.error('Error polling for files:', err);
|
||||
setError(t('mobileUpload.pollingError', 'Error checking for files'));
|
||||
}
|
||||
}, [opened, sessionId, onFilesReceived, t]);
|
||||
@@ -188,24 +184,14 @@ export default function MobileUploadModal({ opened, onClose, onFilesReceived }:
|
||||
setError(null);
|
||||
setShowExpiryWarning(false);
|
||||
processedFiles.current.clear();
|
||||
} else {
|
||||
// Clean up session when modal closes
|
||||
if (sessionId) {
|
||||
fetch(`/api/v1/mobile-scanner/session/${sessionId}`, { method: 'DELETE' })
|
||||
.catch(err => console.warn('Failed to cleanup session on close:', err));
|
||||
}
|
||||
}
|
||||
}, [opened, sessionId]); // Only run when opened changes
|
||||
|
||||
useEffect(() => {
|
||||
if (!opened) return;
|
||||
|
||||
createSession(sessionId);
|
||||
setFilesReceived(0);
|
||||
setError(null);
|
||||
setShowExpiryWarning(false);
|
||||
processedFiles.current.clear();
|
||||
|
||||
return () => {
|
||||
console.log('Cleaning up session on unmount/close:', sessionId);
|
||||
apiClient.delete(`/api/v1/mobile-scanner/session/${sessionId}`)
|
||||
.catch(err => console.warn('[MobileUploadModal] Cleanup failed:', err));
|
||||
};
|
||||
}, [opened, sessionId, createSession]);
|
||||
}, [opened]); // Only run when opened changes
|
||||
|
||||
// Start polling for files when modal opens
|
||||
useEffect(() => {
|
||||
|
||||
@@ -29,6 +29,7 @@ const createViewOptions = (
|
||||
activeFiles: Array<{ fileId: string; name: string; versionNumber?: number }>,
|
||||
currentFileIndex: number,
|
||||
onFileSelect?: (index: number) => void,
|
||||
onFileRemove?: (fileId: string, index: number) => void,
|
||||
pageEditorState?: PageEditorDropdownState,
|
||||
customViews?: CustomWorkbenchViewInstance[]
|
||||
) => {
|
||||
@@ -47,6 +48,7 @@ const createViewOptions = (
|
||||
activeFiles={activeFiles}
|
||||
currentFileIndex={currentFileIndex}
|
||||
onFileSelect={onFileSelect}
|
||||
onFileRemove={onFileRemove}
|
||||
switchingTo={switchingTo}
|
||||
viewOptionStyle={viewOptionStyle}
|
||||
/>
|
||||
@@ -132,6 +134,7 @@ interface TopControlsProps {
|
||||
activeFiles?: Array<{ fileId: string; name: string; versionNumber?: number }>;
|
||||
currentFileIndex?: number;
|
||||
onFileSelect?: (index: number) => void;
|
||||
onFileRemove?: (fileId: string, index: number) => void;
|
||||
}
|
||||
|
||||
const TopControls = ({
|
||||
@@ -141,6 +144,7 @@ const TopControls = ({
|
||||
activeFiles = [],
|
||||
currentFileIndex = 0,
|
||||
onFileSelect,
|
||||
onFileRemove,
|
||||
}: TopControlsProps) => {
|
||||
const { isRainbowMode } = useRainbowThemeContext();
|
||||
const [switchingTo, setSwitchingTo] = useState<WorkbenchType | null>(null);
|
||||
@@ -176,9 +180,10 @@ const TopControls = ({
|
||||
activeFiles,
|
||||
currentFileIndex,
|
||||
onFileSelect,
|
||||
onFileRemove,
|
||||
pageEditorState,
|
||||
customViews
|
||||
), [currentView, switchingTo, activeFiles, currentFileIndex, onFileSelect, pageEditorState, customViews]);
|
||||
), [currentView, switchingTo, activeFiles, currentFileIndex, onFileSelect, onFileRemove, pageEditorState, customViews]);
|
||||
|
||||
return (
|
||||
<div className="absolute left-0 w-full top-0 z-[100] pointer-events-none">
|
||||
|
||||
@@ -123,7 +123,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({ hideTitle = false }) =>
|
||||
{t('settings.general.enableFeatures.benefit', 'Enables user roles, team collaboration, admin controls, and enterprise features.')}
|
||||
</Text>
|
||||
<Anchor
|
||||
href="https://docs.stirlingpdf.com/Advanced%20Configuration/System%20and%20Security"
|
||||
href="https://docs.stirlingpdf.com/Configuration/System%20and%20Security/"
|
||||
target="_blank"
|
||||
size="sm"
|
||||
style={{ color: 'var(--mantine-color-blue-6)' }}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import React, { createContext, useContext, useState, useEffect, useRef } from 'react';
|
||||
import { useDocumentManagerPlugin } from '@embedpdf/plugin-document-manager/react';
|
||||
|
||||
interface ActiveDocumentContextType {
|
||||
documentId: string | null;
|
||||
}
|
||||
|
||||
const ActiveDocumentContext = createContext<ActiveDocumentContextType>({ documentId: null });
|
||||
|
||||
export function ActiveDocumentProvider({ children }: { children: React.ReactNode }) {
|
||||
const { plugin, isLoading } = useDocumentManagerPlugin();
|
||||
const [documentId, setDocumentId] = useState<string | null>(null);
|
||||
const unsubscribeRef = useRef<(() => void) | null>(null);
|
||||
const documentIdRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading || !plugin) return;
|
||||
|
||||
const docManagerApi = plugin.provides?.();
|
||||
if (!docManagerApi) return;
|
||||
|
||||
// Get initial active document (synchronously if available)
|
||||
const activeDoc = docManagerApi.getActiveDocument?.();
|
||||
if (activeDoc?.id && activeDoc.id !== documentIdRef.current) {
|
||||
documentIdRef.current = activeDoc.id;
|
||||
setDocumentId(activeDoc.id);
|
||||
}
|
||||
|
||||
// Subscribe to document changes (only if not already subscribed)
|
||||
if (!unsubscribeRef.current && docManagerApi.onDocumentOpened) {
|
||||
unsubscribeRef.current = docManagerApi.onDocumentOpened((event: any) => {
|
||||
const docId = event?.documentId || event?.id || event?.document?.id;
|
||||
if (docId && docId !== documentIdRef.current) {
|
||||
documentIdRef.current = docId;
|
||||
setDocumentId(docId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Note: We don't unsubscribe on effect cleanup to avoid re-subscribing on every render
|
||||
// Cleanup happens only on unmount
|
||||
}, [plugin, isLoading]);
|
||||
|
||||
// Cleanup on unmount only
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (unsubscribeRef.current) {
|
||||
unsubscribeRef.current();
|
||||
unsubscribeRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ActiveDocumentContext.Provider value={{ documentId }}>
|
||||
{children}
|
||||
</ActiveDocumentContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useActiveDocument(): string | null {
|
||||
const context = useContext(ActiveDocumentContext);
|
||||
return context.documentId;
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import type {
|
||||
} from '@app/components/viewer/viewerTypes';
|
||||
|
||||
type NoteIcon = NonNullable<AnnotationToolOptions['icon']>;
|
||||
|
||||
type AnnotationDefaults =
|
||||
| {
|
||||
type:
|
||||
@@ -18,15 +17,13 @@ type AnnotationDefaults =
|
||||
| PdfAnnotationSubtype.UNDERLINE
|
||||
| PdfAnnotationSubtype.STRIKEOUT
|
||||
| PdfAnnotationSubtype.SQUIGGLY;
|
||||
strokeColor: string;
|
||||
color?: string;
|
||||
color: string;
|
||||
opacity: number;
|
||||
customData?: Record<string, unknown>;
|
||||
}
|
||||
| {
|
||||
type: PdfAnnotationSubtype.INK;
|
||||
strokeColor: string;
|
||||
color?: string;
|
||||
color: string;
|
||||
opacity?: number;
|
||||
borderWidth?: number;
|
||||
strokeWidth?: number;
|
||||
@@ -40,8 +37,7 @@ type AnnotationDefaults =
|
||||
fontFamily?: string;
|
||||
textAlign?: number;
|
||||
opacity?: number;
|
||||
color?: string;
|
||||
interiorColor?: string;
|
||||
backgroundColor?: string;
|
||||
borderWidth?: number;
|
||||
contents?: string;
|
||||
icon?: PdfAnnotationIcon;
|
||||
@@ -110,7 +106,7 @@ const DEFAULTS = {
|
||||
ink: '#1f2933',
|
||||
inkHighlighter: '#ffd54f',
|
||||
text: '#111111',
|
||||
note: '#ffd54f',
|
||||
note: '#ffd54f', // match highlight color
|
||||
shapeFill: '#0000ff',
|
||||
shapeStroke: '#cf5b5b',
|
||||
shapeOpacity: 0.5,
|
||||
@@ -128,62 +124,42 @@ const buildStampDefaults: ToolDefaultsBuilder = (options) => ({
|
||||
...withCustomData(options),
|
||||
});
|
||||
|
||||
const buildInkDefaults = (options?: AnnotationToolOptions, opacityOverride?: number): AnnotationDefaults => {
|
||||
const colorValue = options?.color ?? (opacityOverride ? DEFAULTS.inkHighlighter : DEFAULTS.ink);
|
||||
return {
|
||||
type: PdfAnnotationSubtype.INK,
|
||||
strokeColor: colorValue,
|
||||
color: colorValue,
|
||||
opacity: options?.opacity ?? opacityOverride ?? 1,
|
||||
borderWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
||||
strokeWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
||||
lineWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
||||
...withCustomData(options),
|
||||
};
|
||||
};
|
||||
const buildInkDefaults = (options?: AnnotationToolOptions, opacityOverride?: number): AnnotationDefaults => ({
|
||||
type: PdfAnnotationSubtype.INK,
|
||||
color: options?.color ?? (opacityOverride ? DEFAULTS.inkHighlighter : DEFAULTS.ink),
|
||||
opacity: options?.opacity ?? opacityOverride ?? 1,
|
||||
borderWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
||||
strokeWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
||||
lineWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
||||
...withCustomData(options),
|
||||
});
|
||||
|
||||
const TOOL_DEFAULT_BUILDERS: Record<AnnotationToolId, ToolDefaultsBuilder> = {
|
||||
select: () => null,
|
||||
highlight: (options) => {
|
||||
const colorValue = options?.color ?? DEFAULTS.highlight;
|
||||
return {
|
||||
type: PdfAnnotationSubtype.HIGHLIGHT,
|
||||
strokeColor: colorValue,
|
||||
color: colorValue,
|
||||
opacity: options?.opacity ?? 0.6,
|
||||
...withCustomData(options),
|
||||
};
|
||||
},
|
||||
underline: (options) => {
|
||||
const colorValue = options?.color ?? DEFAULTS.underline;
|
||||
return {
|
||||
type: PdfAnnotationSubtype.UNDERLINE,
|
||||
strokeColor: colorValue,
|
||||
color: colorValue,
|
||||
opacity: options?.opacity ?? 1,
|
||||
...withCustomData(options),
|
||||
};
|
||||
},
|
||||
strikeout: (options) => {
|
||||
const colorValue = options?.color ?? DEFAULTS.strikeout;
|
||||
return {
|
||||
type: PdfAnnotationSubtype.STRIKEOUT,
|
||||
strokeColor: colorValue,
|
||||
color: colorValue,
|
||||
opacity: options?.opacity ?? 1,
|
||||
...withCustomData(options),
|
||||
};
|
||||
},
|
||||
squiggly: (options) => {
|
||||
const colorValue = options?.color ?? DEFAULTS.squiggly;
|
||||
return {
|
||||
type: PdfAnnotationSubtype.SQUIGGLY,
|
||||
strokeColor: colorValue,
|
||||
color: colorValue,
|
||||
opacity: options?.opacity ?? 1,
|
||||
...withCustomData(options),
|
||||
};
|
||||
},
|
||||
highlight: (options) => ({
|
||||
type: PdfAnnotationSubtype.HIGHLIGHT,
|
||||
color: options?.color ?? DEFAULTS.highlight,
|
||||
opacity: options?.opacity ?? 0.6,
|
||||
...withCustomData(options),
|
||||
}),
|
||||
underline: (options) => ({
|
||||
type: PdfAnnotationSubtype.UNDERLINE,
|
||||
color: options?.color ?? DEFAULTS.underline,
|
||||
opacity: options?.opacity ?? 1,
|
||||
...withCustomData(options),
|
||||
}),
|
||||
strikeout: (options) => ({
|
||||
type: PdfAnnotationSubtype.STRIKEOUT,
|
||||
color: options?.color ?? DEFAULTS.strikeout,
|
||||
opacity: options?.opacity ?? 1,
|
||||
...withCustomData(options),
|
||||
}),
|
||||
squiggly: (options) => ({
|
||||
type: PdfAnnotationSubtype.SQUIGGLY,
|
||||
color: options?.color ?? DEFAULTS.squiggly,
|
||||
opacity: options?.opacity ?? 1,
|
||||
...withCustomData(options),
|
||||
}),
|
||||
ink: (options) => buildInkDefaults(options),
|
||||
inkHighlighter: (options) => buildInkDefaults(options, options?.opacity ?? 0.6),
|
||||
text: (options) => ({
|
||||
@@ -194,21 +170,21 @@ const TOOL_DEFAULT_BUILDERS: Record<AnnotationToolId, ToolDefaultsBuilder> = {
|
||||
textAlign: options?.textAlign ?? 0,
|
||||
opacity: options?.opacity ?? 1,
|
||||
borderWidth: options?.thickness ?? 1,
|
||||
...(options?.fillColor ? { color: options.fillColor, interiorColor: options.fillColor } : {}),
|
||||
...(options?.fillColor ? { backgroundColor: options.fillColor } : {}),
|
||||
...withCustomData(options),
|
||||
}),
|
||||
note: (options) => {
|
||||
const bgColor = options?.fillColor ?? DEFAULTS.note;
|
||||
const backgroundColor = options?.fillColor ?? DEFAULTS.note;
|
||||
const fontColor = options?.color ?? DEFAULTS.text;
|
||||
return {
|
||||
type: PdfAnnotationSubtype.FREETEXT,
|
||||
fontColor,
|
||||
color: fontColor,
|
||||
fontFamily: options?.fontFamily ?? 'Helvetica',
|
||||
textAlign: options?.textAlign ?? 0,
|
||||
fontSize: options?.fontSize ?? 12,
|
||||
opacity: options?.opacity ?? 1,
|
||||
color: bgColor,
|
||||
interiorColor: bgColor,
|
||||
backgroundColor,
|
||||
borderWidth: options?.thickness ?? 0,
|
||||
contents: options?.contents ?? 'Note',
|
||||
icon: getIconEnum(options?.icon),
|
||||
@@ -341,11 +317,7 @@ export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(function Annotation
|
||||
// `this`/state dependency (e.g. reading `selectedUid` from undefined).
|
||||
// If that happens, fail gracefully and treat it as "no selection"
|
||||
// instead of crashing the entire annotations tool.
|
||||
// Only log unexpected errors - "No active document" is a common expected state during init
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
if (!errorMessage.includes('No active document')) {
|
||||
console.error('[AnnotationAPIBridge] getSelectedAnnotation failed:', error);
|
||||
}
|
||||
console.error('[AnnotationAPIBridge] getSelectedAnnotation failed:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useMemo, useState, useCallback } from 'react';
|
||||
import { useBookmarkCapability, BookmarkCapability } from '@embedpdf/plugin-bookmark/react';
|
||||
import { useBookmarkCapability } from '@embedpdf/plugin-bookmark/react';
|
||||
import { BookmarkCapability } from '@embedpdf/plugin-bookmark';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
import { BookmarkState, BookmarkAPIWrapper } from '@app/contexts/viewer/viewerBridges';
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useDocumentState } from '@embedpdf/core/react';
|
||||
import { useSearch } from '@embedpdf/plugin-search/react';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
import { SEARCH_CONSTANTS } from '@app/components/viewer/constants/search';
|
||||
|
||||
interface SearchLayerProps {
|
||||
documentId?: string;
|
||||
pageIndex: number;
|
||||
scale?: number;
|
||||
scale: number;
|
||||
highlightColor?: string;
|
||||
activeHighlightColor?: string;
|
||||
opacity?: number;
|
||||
@@ -26,22 +25,18 @@ interface SearchResultState {
|
||||
}
|
||||
|
||||
export function CustomSearchLayer({
|
||||
documentId = '',
|
||||
pageIndex,
|
||||
scale: scaleProp,
|
||||
scale,
|
||||
highlightColor = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.BACKGROUND,
|
||||
activeHighlightColor = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.ACTIVE_BACKGROUND,
|
||||
opacity = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.OPACITY,
|
||||
padding = SEARCH_CONSTANTS.UI.HIGHLIGHT_PADDING,
|
||||
borderRadius = 4
|
||||
}: SearchLayerProps) {
|
||||
const { provides: searchProvides } = useSearch(documentId);
|
||||
const documentState = useDocumentState(documentId);
|
||||
const { provides: searchProvides } = useSearch();
|
||||
const { scrollActions } = useViewer();
|
||||
const [searchResultState, setSearchResultState] = useState<SearchResultState | null>(null);
|
||||
|
||||
// Use document scale from EmbedPDF state, fallback to prop, then to 1
|
||||
const scale = documentState?.scale ?? scaleProp ?? 1;
|
||||
|
||||
// Subscribe to search result state changes
|
||||
useEffect(() => {
|
||||
if (!searchProvides) {
|
||||
@@ -50,8 +45,16 @@ export function CustomSearchLayer({
|
||||
|
||||
const unsubscribe = searchProvides.onSearchResultStateChange?.((state: SearchResultState) => {
|
||||
if (!state) return;
|
||||
// Only update state - do NOT auto-scroll here
|
||||
// Scrolling should only happen when user explicitly navigates via next/previous
|
||||
|
||||
// Auto-scroll to active search result
|
||||
if (state.results && state.activeResultIndex !== undefined && state.activeResultIndex >= 0) {
|
||||
const activeResult = state.results[state.activeResultIndex];
|
||||
if (activeResult) {
|
||||
const pageNumber = activeResult.pageIndex + 1; // Convert to 1-based page number
|
||||
scrollActions.scrollToPage(pageNumber);
|
||||
}
|
||||
}
|
||||
|
||||
setSearchResultState(state);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
import {
|
||||
PdfPermissionFlag,
|
||||
DocumentPermissionsState,
|
||||
DocumentPermissionsAPIWrapper,
|
||||
} from '@app/contexts/viewer/viewerBridges';
|
||||
|
||||
function hasPermissionFlag(permissions: number, flag: PdfPermissionFlag): boolean {
|
||||
if (permissions === 0 || permissions === PdfPermissionFlag.AllowAll) {
|
||||
return true;
|
||||
}
|
||||
return (permissions & flag) !== 0;
|
||||
}
|
||||
|
||||
interface DocumentPermissionsAPIBridgeProps {
|
||||
isEncrypted?: boolean;
|
||||
isOwnerUnlocked?: boolean;
|
||||
permissions?: number;
|
||||
}
|
||||
|
||||
export function DocumentPermissionsAPIBridge({
|
||||
isEncrypted = false,
|
||||
isOwnerUnlocked = false,
|
||||
permissions = PdfPermissionFlag.AllowAll,
|
||||
}: DocumentPermissionsAPIBridgeProps) {
|
||||
const { registerBridge } = useViewer();
|
||||
|
||||
const state = useMemo<DocumentPermissionsState>(() => ({
|
||||
isEncrypted,
|
||||
isOwnerUnlocked,
|
||||
permissions,
|
||||
canPrint: hasPermissionFlag(permissions, PdfPermissionFlag.Print),
|
||||
canModifyContents: hasPermissionFlag(permissions, PdfPermissionFlag.ModifyContents),
|
||||
canCopyContents: hasPermissionFlag(permissions, PdfPermissionFlag.CopyContents),
|
||||
canModifyAnnotations: hasPermissionFlag(permissions, PdfPermissionFlag.ModifyAnnotations),
|
||||
canFillForms: hasPermissionFlag(permissions, PdfPermissionFlag.FillForms),
|
||||
canExtractForAccessibility: hasPermissionFlag(permissions, PdfPermissionFlag.ExtractForAccessibility),
|
||||
canAssembleDocument: hasPermissionFlag(permissions, PdfPermissionFlag.AssembleDocument),
|
||||
canPrintHighQuality: hasPermissionFlag(permissions, PdfPermissionFlag.PrintHighQuality),
|
||||
}), [isEncrypted, isOwnerUnlocked, permissions]);
|
||||
|
||||
const api = useMemo<DocumentPermissionsAPIWrapper>(() => ({
|
||||
hasPermission: (flag: PdfPermissionFlag) => hasPermissionFlag(permissions, flag),
|
||||
hasAllPermissions: (flags: PdfPermissionFlag[]) =>
|
||||
flags.every(flag => hasPermissionFlag(permissions, flag)),
|
||||
getEffectivePermission: (flag: PdfPermissionFlag) => {
|
||||
if (isOwnerUnlocked) return true;
|
||||
return hasPermissionFlag(permissions, flag);
|
||||
},
|
||||
}), [permissions, isOwnerUnlocked]);
|
||||
|
||||
useEffect(() => {
|
||||
registerBridge('permissions', {
|
||||
state,
|
||||
api,
|
||||
});
|
||||
}, [registerBridge, state, api]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export { PdfPermissionFlag };
|
||||
@@ -1,56 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useDocumentManagerPlugin } from '@embedpdf/plugin-document-manager/react';
|
||||
|
||||
interface DocumentReadyWrapperProps {
|
||||
children: (documentId: string) => React.ReactNode;
|
||||
fallback?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function DocumentReadyWrapper({ children, fallback = null }: DocumentReadyWrapperProps) {
|
||||
const { plugin, isLoading, ready } = useDocumentManagerPlugin();
|
||||
const [activeDocumentId, setActiveDocumentId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading || !plugin) return;
|
||||
|
||||
const checkActiveDocument = async () => {
|
||||
await ready;
|
||||
|
||||
// Try to get the active document from the plugin's provides()
|
||||
const docManagerApi = plugin.provides?.();
|
||||
if (docManagerApi) {
|
||||
// Try different methods to get the active document
|
||||
const activeDoc = docManagerApi.getActiveDocument?.();
|
||||
if (activeDoc?.id) {
|
||||
setActiveDocumentId(activeDoc.id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkActiveDocument();
|
||||
|
||||
// Subscribe to document changes
|
||||
const docManagerApi = plugin.provides?.();
|
||||
if (docManagerApi?.onDocumentOpened) {
|
||||
const unsubscribe = docManagerApi.onDocumentOpened((event: any) => {
|
||||
const docId = event?.documentId || event?.id || event?.document?.id;
|
||||
if (docId) {
|
||||
setActiveDocumentId(docId);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (typeof unsubscribe === 'function') {
|
||||
unsubscribe();
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [plugin, isLoading, ready]);
|
||||
|
||||
if (!activeDocumentId) {
|
||||
return <>{fallback}</>;
|
||||
}
|
||||
|
||||
return <>{children(activeDocumentId)}</>;
|
||||
}
|
||||
@@ -345,6 +345,21 @@ const EmbedPdfViewerContent = ({
|
||||
// Always consider applied redactions as unsaved until export
|
||||
const hasAppliedRedactions = redactionsApplied;
|
||||
|
||||
// When in redact mode, still include annotation changes (users may draw)
|
||||
if (isManualRedactMode) {
|
||||
console.log('[Viewer] Checking for unsaved changes (redact mode):', {
|
||||
hasPendingRedactions,
|
||||
hasAppliedRedactions,
|
||||
hasAnnotationChanges,
|
||||
});
|
||||
} else {
|
||||
console.log('[Viewer] Checking for unsaved changes:', {
|
||||
hasAnnotationChanges,
|
||||
hasPendingRedactions,
|
||||
hasAppliedRedactions,
|
||||
});
|
||||
}
|
||||
|
||||
return hasAnnotationChanges || hasPendingRedactions || hasAppliedRedactions;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import { useDocumentState } from '@embedpdf/core/react';
|
||||
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
|
||||
import { useScroll } from '@embedpdf/plugin-scroll/react';
|
||||
import { PdfAnnotationSubtype } from '@embedpdf/models';
|
||||
|
||||
enum PDFActionType {
|
||||
GoTo = 0,
|
||||
GoToR = 1,
|
||||
GoToE = 2,
|
||||
URI = 3,
|
||||
Launch = 4,
|
||||
Named = 5,
|
||||
JavaScript = 6,
|
||||
// Add other types as needed
|
||||
}
|
||||
|
||||
interface PDFRect {
|
||||
@@ -50,242 +46,24 @@ function isURIAction(action: PDFAction): boolean {
|
||||
return action.type === 'URI' || action.type === PDFActionType.URI;
|
||||
}
|
||||
|
||||
// Utility functions for link type detection - prefixed to indicate future use
|
||||
function _isInternalLink(link: LinkAnnotation): boolean {
|
||||
function isInternalLink(link: LinkAnnotation): boolean {
|
||||
return Boolean(link.target?.type === 'destination' ||
|
||||
(link.target?.type === 'action' && link.target.action && isGoToAction(link.target.action)));
|
||||
}
|
||||
|
||||
function _isExternalLink(link: LinkAnnotation): boolean {
|
||||
function isExternalLink(link: LinkAnnotation): boolean {
|
||||
return Boolean(link.target?.type === 'uri' ||
|
||||
(link.target?.type === 'action' && link.target.action && isURIAction(link.target.action)));
|
||||
}
|
||||
|
||||
interface LinkLayerProps {
|
||||
documentId: string;
|
||||
pageIndex: number;
|
||||
pageWidth: number;
|
||||
pageHeight: number;
|
||||
scale: number;
|
||||
document?: any;
|
||||
pdfFile?: File | Blob;
|
||||
onLinkClick?: (target: any) => void;
|
||||
}
|
||||
|
||||
export const LinkLayer: React.FC<LinkLayerProps> = ({
|
||||
documentId,
|
||||
pageIndex,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
}) => {
|
||||
const { provides: annotation } = useAnnotationCapability();
|
||||
const { provides: scroll } = useScroll(documentId);
|
||||
const documentState = useDocumentState(documentId);
|
||||
const [links, setLinks] = useState<LinkAnnotation[]>([]);
|
||||
const [isNavigating, setIsNavigating] = useState(false);
|
||||
|
||||
// Get original PDF page dimensions from document state
|
||||
const pdfPage = documentState?.document?.pages?.[pageIndex];
|
||||
const pdfPageWidth = pdfPage?.size?.width ?? 0;
|
||||
const pdfPageHeight = pdfPage?.size?.height ?? 0;
|
||||
|
||||
// Process links with proper coordinate transformation
|
||||
const processedLinks = useMemo(() => {
|
||||
if (!pageWidth || !pageHeight || !pdfPageWidth || !pdfPageHeight) return [];
|
||||
|
||||
// Use the document scale like EmbedPDF's AnnotationLayer does
|
||||
const scale = documentState?.scale ?? 1;
|
||||
|
||||
|
||||
|
||||
return links.map(link => {
|
||||
const { origin, size } = link.rect;
|
||||
|
||||
// Use document scale like EmbedPDF does
|
||||
const scaledLeft = origin.x * scale;
|
||||
const scaledTop = origin.y * scale;
|
||||
const scaledWidth = size.width * scale;
|
||||
const scaledHeight = size.height * scale;
|
||||
|
||||
return {
|
||||
...link,
|
||||
scaledRect: {
|
||||
left: scaledLeft,
|
||||
top: scaledTop,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [links, pageWidth, pageHeight, pdfPageWidth, pdfPageHeight, documentState?.scale]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLinks = async () => {
|
||||
if (!annotation) return;
|
||||
|
||||
try {
|
||||
const pageAnnotationsResult = annotation.getPageAnnotations({
|
||||
pageIndex,
|
||||
});
|
||||
|
||||
const resolveAnnotations = async (result: unknown): Promise<any[]> => {
|
||||
if (result && typeof (result as any).toPromise === 'function') {
|
||||
return (result as any).toPromise();
|
||||
}
|
||||
if (result && typeof (result as any).then === 'function') {
|
||||
return result as Promise<any[]>;
|
||||
}
|
||||
if (Array.isArray(result)) {
|
||||
return result;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const pageAnnotations = await resolveAnnotations(pageAnnotationsResult);
|
||||
|
||||
const linkAnnotations = pageAnnotations.filter(
|
||||
(ann: any) => ann.type === 2 || ann.type === PdfAnnotationSubtype.LINK
|
||||
) as LinkAnnotation[];
|
||||
|
||||
if (linkAnnotations.length > 0) {
|
||||
setLinks(linkAnnotations);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[LinkLayer] Failed to fetch links from annotation API:', {
|
||||
error,
|
||||
pageIndex,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fetchLinks();
|
||||
}, [annotation, pageIndex]);
|
||||
|
||||
const handleLinkClick = useCallback(async (link: LinkAnnotation) => {
|
||||
if (isNavigating) return;
|
||||
|
||||
try {
|
||||
setIsNavigating(true);
|
||||
|
||||
// Try to extract destination from various possible locations in the link object
|
||||
const linkData = link as any;
|
||||
|
||||
// Check for destination in various locations
|
||||
let targetPage: number | undefined;
|
||||
let uri: string | undefined;
|
||||
|
||||
// Try target.destination first
|
||||
if (link.target?.destination?.pageIndex !== undefined) {
|
||||
targetPage = link.target.destination.pageIndex;
|
||||
}
|
||||
// Try target.action.destination
|
||||
else if (link.target?.action?.destination?.pageIndex !== undefined) {
|
||||
targetPage = link.target.action.destination.pageIndex;
|
||||
}
|
||||
// Try direct dest property (PDF.js style)
|
||||
else if (linkData.dest?.pageIndex !== undefined) {
|
||||
targetPage = linkData.dest.pageIndex;
|
||||
}
|
||||
// Try destination at root level
|
||||
else if (linkData.destination?.pageIndex !== undefined) {
|
||||
targetPage = linkData.destination.pageIndex;
|
||||
}
|
||||
// Try action at root level
|
||||
else if (linkData.action?.destination?.pageIndex !== undefined) {
|
||||
targetPage = linkData.action.destination.pageIndex;
|
||||
}
|
||||
|
||||
// Check for URI in various locations
|
||||
if (link.target?.uri) {
|
||||
uri = link.target.uri;
|
||||
} else if (link.target?.action?.uri) {
|
||||
uri = link.target.action.uri;
|
||||
} else if (linkData.uri) {
|
||||
uri = linkData.uri;
|
||||
} else if (linkData.url) {
|
||||
uri = linkData.url;
|
||||
} else if (linkData.action?.uri) {
|
||||
uri = linkData.action.uri;
|
||||
}
|
||||
|
||||
if (targetPage !== undefined && scroll) {
|
||||
scroll.scrollToPage({
|
||||
pageNumber: targetPage + 1,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
} else if (uri) {
|
||||
try {
|
||||
const url = new URL(uri, window.location.href);
|
||||
if (['http:', 'https:', 'mailto:'].includes(url.protocol)) {
|
||||
window.open(uri, '_blank', 'noopener,noreferrer');
|
||||
} else {
|
||||
console.warn('[LinkLayer] Blocked potentially unsafe URL protocol:', url.protocol);
|
||||
}
|
||||
} catch {
|
||||
window.open(uri, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
} else {
|
||||
console.warn('[LinkLayer] Could not extract destination or URI from link:', link);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[LinkLayer] Navigation failed:', error);
|
||||
} finally {
|
||||
setIsNavigating(false);
|
||||
}
|
||||
}, [isNavigating, scroll]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
zIndex: 10, // Above selection layer but below UI controls
|
||||
}}
|
||||
>
|
||||
{processedLinks.map((link) => {
|
||||
const { id } = link;
|
||||
const { left, top, width, height } = link.scaledRect;
|
||||
|
||||
return (
|
||||
<a
|
||||
key={id}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleLinkClick(link);
|
||||
}}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
className={`absolute block cursor-pointer ${isNavigating ? 'cursor-not-allowed' : ''}`}
|
||||
style={{
|
||||
left: `${left}px`,
|
||||
top: `${top}px`,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
minWidth: '8px',
|
||||
minHeight: '8px',
|
||||
pointerEvents: 'auto',
|
||||
backgroundColor: 'transparent',
|
||||
zIndex: 11,
|
||||
border: '2px solid transparent',
|
||||
borderRadius: '2px',
|
||||
transition: 'background-color 0.15s ease-in-out, border-color 0.15s ease-in-out',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'rgba(59, 130, 246, 0.15)';
|
||||
e.currentTarget.style.borderColor = 'rgba(59, 130, 246, 0.5)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
e.currentTarget.style.borderColor = 'transparent';
|
||||
}}
|
||||
title={getLinkTitle(link)}
|
||||
aria-label={getLinkAriaLabel(link)}
|
||||
>
|
||||
{/* Invisible clickable area */}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getLinkTitle = (link: LinkAnnotation): string => {
|
||||
if (link.target?.type === 'destination') {
|
||||
return `Go to page ${(link.target.destination?.pageIndex ?? 0) + 1}`;
|
||||
@@ -314,3 +92,153 @@ const getLinkAriaLabel = (link: LinkAnnotation): string => {
|
||||
}
|
||||
return 'Open external link';
|
||||
};
|
||||
|
||||
export const LinkLayer: React.FC<LinkLayerProps> = ({
|
||||
pageIndex,
|
||||
scale,
|
||||
document: pdfDocument,
|
||||
onLinkClick
|
||||
}) => {
|
||||
const { provides: annotation } = useAnnotationCapability();
|
||||
const { provides: scroll } = useScroll();
|
||||
const [links, setLinks] = useState<LinkAnnotation[]>([]);
|
||||
const [isNavigating, setIsNavigating] = useState(false);
|
||||
|
||||
const processedLinks = useMemo(() => {
|
||||
return links.map(link => ({
|
||||
...link,
|
||||
scaledRect: {
|
||||
left: link.rect.origin.x * scale,
|
||||
top: link.rect.origin.y * scale,
|
||||
width: link.rect.size.width * scale,
|
||||
height: link.rect.size.height * scale,
|
||||
}
|
||||
}));
|
||||
}, [links, scale]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLinks = async () => {
|
||||
if (!annotation) return;
|
||||
|
||||
try {
|
||||
// Use the annotation API's built-in filtering if available
|
||||
const pageAnnotations = await annotation
|
||||
.getPageAnnotations({
|
||||
pageIndex,
|
||||
// Try to filter for link annotations (type 2) if the API supports it
|
||||
...(annotation.getPageAnnotations.length > 1 ? { types: [2] } : {})
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
// Filter for link annotations (type 2 is LINK in PDF spec) as fallback
|
||||
const linkAnnotations = pageAnnotations.filter(
|
||||
(ann: any) => ann.type === 2
|
||||
) as LinkAnnotation[];
|
||||
|
||||
if (linkAnnotations.length > 0) {
|
||||
setLinks(linkAnnotations);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[LinkLayer] Failed to fetch links from annotation API:', error);
|
||||
}
|
||||
|
||||
if (pdfDocument) {
|
||||
try {
|
||||
// Try different methods to get link annotations
|
||||
let pdfLinks: any[] = [];
|
||||
|
||||
if (pdfDocument.getPageAnnotations && typeof pdfDocument.getPageAnnotations === 'function') {
|
||||
pdfLinks = await pdfDocument.getPageAnnotations(pageIndex);
|
||||
} else if (pdfDocument.getAnnotations && typeof pdfDocument.getAnnotations === 'function') {
|
||||
const allAnnotations = await pdfDocument.getAnnotations();
|
||||
pdfLinks = allAnnotations.filter((ann: any) => ann.pageIndex === pageIndex && ann.type === 2);
|
||||
} else if (pdfDocument.pages && pdfDocument.pages[pageIndex]) {
|
||||
const page = pdfDocument.pages[pageIndex];
|
||||
if (page.getAnnotations && typeof page.getAnnotations === 'function') {
|
||||
pdfLinks = await page.getAnnotations();
|
||||
}
|
||||
}
|
||||
|
||||
const convertedLinks = pdfLinks.map((ann: any) => ({
|
||||
id: ann.id || `pdf-link-${pageIndex}-${Math.random()}`,
|
||||
type: ann.type || 2,
|
||||
rect: ann.rect || ann,
|
||||
target: ann.target || ann.action
|
||||
})) as LinkAnnotation[];
|
||||
|
||||
setLinks(convertedLinks);
|
||||
} catch (error) {
|
||||
console.warn('[LinkLayer] Failed to get annotations from PDF document:', error);
|
||||
}
|
||||
} else {
|
||||
console.warn('[LinkLayer] No annotation API or PDF document available');
|
||||
}
|
||||
};
|
||||
|
||||
fetchLinks();
|
||||
}, [annotation, pageIndex, pdfDocument]);
|
||||
|
||||
const handleLinkClick = useCallback(async (link: LinkAnnotation) => {
|
||||
if (isNavigating) return; // Prevent multiple simultaneous navigations
|
||||
|
||||
try {
|
||||
setIsNavigating(true);
|
||||
|
||||
if (onLinkClick) {
|
||||
onLinkClick(link.target);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInternalLink(link)) {
|
||||
const targetPage = link.target?.destination?.pageIndex ??
|
||||
link.target?.action?.destination?.pageIndex;
|
||||
if (targetPage !== undefined && scroll) {
|
||||
await scroll.scrollToPage({
|
||||
pageNumber: targetPage + 1, // PDF pages are 1-indexed
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
} else if (isExternalLink(link)) {
|
||||
const uri = link.target?.uri ?? link.target?.action?.uri;
|
||||
if (uri) {
|
||||
window.open(uri, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unsupported link type: ${link.target?.type}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[LinkLayer] Navigation failed:', error);
|
||||
} finally {
|
||||
setIsNavigating(false);
|
||||
}
|
||||
}, [isNavigating, onLinkClick, scroll]);
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
{processedLinks.map((link) => {
|
||||
const { id } = link;
|
||||
const { left, top, width, height } = link.scaledRect;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
onClick={() => handleLinkClick(link)}
|
||||
disabled={isNavigating}
|
||||
className={`absolute opacity-0 hover:opacity-20 bg-blue-500 transition-opacity cursor-pointer pointer-events-auto border border-blue-400 hover:border-blue-600 ${isNavigating ? 'cursor-not-allowed opacity-50' : ''}`}
|
||||
style={{
|
||||
left: `${left}px`,
|
||||
top: `${top}px`,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
minWidth: '8px',
|
||||
minHeight: '8px',
|
||||
}}
|
||||
title={getLinkTitle(link)}
|
||||
aria-label={getLinkAriaLabel(link)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,8 +6,8 @@ import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||
|
||||
// Import the essential plugins
|
||||
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
|
||||
import { Scroller, ScrollPluginPackage } from '@embedpdf/plugin-scroll/react';
|
||||
import { DocumentManagerPluginPackage } from '@embedpdf/plugin-document-manager/react';
|
||||
import { Scroller, ScrollPluginPackage, ScrollStrategy } from '@embedpdf/plugin-scroll/react';
|
||||
import { LoaderPluginPackage } from '@embedpdf/plugin-loader/react';
|
||||
import { RenderPluginPackage } from '@embedpdf/plugin-render/react';
|
||||
import { ZoomPluginPackage, ZoomMode } from '@embedpdf/plugin-zoom/react';
|
||||
import { InteractionManagerPluginPackage, PagePointerProvider, GlobalPointerProvider } from '@embedpdf/plugin-interaction-manager/react';
|
||||
@@ -19,7 +19,7 @@ import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
|
||||
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
|
||||
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
|
||||
import { ExportPluginPackage } from '@embedpdf/plugin-export/react';
|
||||
import { BookmarkPluginPackage } from '@embedpdf/plugin-bookmark/react';
|
||||
import { BookmarkPluginPackage } from '@embedpdf/plugin-bookmark';
|
||||
import { PrintPluginPackage } from '@embedpdf/plugin-print/react';
|
||||
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
|
||||
import { AnnotationLayer, AnnotationPluginPackage } from '@embedpdf/plugin-annotation/react';
|
||||
@@ -49,13 +49,8 @@ import { LinkLayer } from '@app/components/viewer/LinkLayer';
|
||||
import { RedactionSelectionMenu } from '@app/components/viewer/RedactionSelectionMenu';
|
||||
import { RedactionPendingTracker, RedactionPendingTrackerAPI } from '@app/components/viewer/RedactionPendingTracker';
|
||||
import { RedactionAPIBridge } from '@app/components/viewer/RedactionAPIBridge';
|
||||
import { DocumentPermissionsAPIBridge } from '@app/components/viewer/DocumentPermissionsAPIBridge';
|
||||
import { DocumentReadyWrapper } from '@app/components/viewer/DocumentReadyWrapper';
|
||||
import { ActiveDocumentProvider } from '@app/components/viewer/ActiveDocumentContext';
|
||||
import { absoluteWithBasePath } from '@app/constants/app';
|
||||
|
||||
const DOCUMENT_NAME = 'stirling-pdf-viewer';
|
||||
|
||||
interface LocalEmbedPDFProps {
|
||||
file?: File | Blob;
|
||||
url?: string | null;
|
||||
@@ -95,16 +90,22 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
const viewportGap = rootFontSize * 3.5;
|
||||
|
||||
return [
|
||||
createPluginRegistration(DocumentManagerPluginPackage, {
|
||||
initialDocuments: [{
|
||||
url: pdfUrl,
|
||||
name: DOCUMENT_NAME,
|
||||
}],
|
||||
createPluginRegistration(LoaderPluginPackage, {
|
||||
loadingOptions: {
|
||||
type: 'url',
|
||||
pdfFile: {
|
||||
id: 'stirling-pdf-viewer',
|
||||
url: pdfUrl,
|
||||
},
|
||||
},
|
||||
}),
|
||||
createPluginRegistration(ViewportPluginPackage, {
|
||||
viewportGap,
|
||||
}),
|
||||
createPluginRegistration(ScrollPluginPackage),
|
||||
createPluginRegistration(ScrollPluginPackage, {
|
||||
strategy: ScrollStrategy.Vertical,
|
||||
initialPage: 0,
|
||||
}),
|
||||
createPluginRegistration(RenderPluginPackage, {
|
||||
withForms: true,
|
||||
withAnnotations: showBakedAnnotations && !enableAnnotations, // Show baked annotations only when: visibility is ON and annotation layer is OFF
|
||||
@@ -256,8 +257,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
<EmbedPDF
|
||||
engine={engine}
|
||||
plugins={plugins}
|
||||
onInitialized={async (registry: any) => {
|
||||
// v2.0: Use registry.getPlugin() to access plugin APIs
|
||||
onInitialized={async (registry) => {
|
||||
const annotationPlugin = registry.getPlugin('annotation');
|
||||
if (!annotationPlugin || !annotationPlugin.provides) return;
|
||||
|
||||
@@ -279,7 +279,6 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.HIGHLIGHT ? 10 : 0),
|
||||
defaults: {
|
||||
type: PdfAnnotationSubtype.HIGHLIGHT,
|
||||
strokeColor: '#ffd54f',
|
||||
color: '#ffd54f',
|
||||
opacity: 0.6,
|
||||
},
|
||||
@@ -296,7 +295,6 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.UNDERLINE ? 10 : 0),
|
||||
defaults: {
|
||||
type: PdfAnnotationSubtype.UNDERLINE,
|
||||
strokeColor: '#ffb300',
|
||||
color: '#ffb300',
|
||||
opacity: 1,
|
||||
},
|
||||
@@ -313,7 +311,6 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.STRIKEOUT ? 10 : 0),
|
||||
defaults: {
|
||||
type: PdfAnnotationSubtype.STRIKEOUT,
|
||||
strokeColor: '#e53935',
|
||||
color: '#e53935',
|
||||
opacity: 1,
|
||||
},
|
||||
@@ -330,7 +327,6 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.SQUIGGLY ? 10 : 0),
|
||||
defaults: {
|
||||
type: PdfAnnotationSubtype.SQUIGGLY,
|
||||
strokeColor: '#00acc1',
|
||||
color: '#00acc1',
|
||||
opacity: 1,
|
||||
},
|
||||
@@ -347,7 +343,6 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK ? 10 : 0),
|
||||
defaults: {
|
||||
type: PdfAnnotationSubtype.INK,
|
||||
strokeColor: '#1f2933',
|
||||
color: '#1f2933',
|
||||
opacity: 1,
|
||||
borderWidth: 2,
|
||||
@@ -364,10 +359,9 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
id: 'inkHighlighter',
|
||||
name: 'Ink Highlighter',
|
||||
interaction: { exclusive: true, cursor: 'crosshair' },
|
||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK && (annotation.strokeColor === '#ffd54f' || annotation.color === '#ffd54f') ? 8 : 0),
|
||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK && annotation.color === '#ffd54f' ? 8 : 0),
|
||||
defaults: {
|
||||
type: PdfAnnotationSubtype.INK,
|
||||
strokeColor: '#ffd54f',
|
||||
color: '#ffd54f',
|
||||
opacity: 0.5,
|
||||
borderWidth: 6,
|
||||
@@ -596,7 +590,6 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
matchScore: () => 0,
|
||||
defaults: {
|
||||
type: PdfAnnotationSubtype.INK,
|
||||
strokeColor: '#000000',
|
||||
color: '#000000',
|
||||
opacity: 1.0,
|
||||
borderWidth: 2,
|
||||
@@ -629,7 +622,6 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ActiveDocumentProvider>
|
||||
<ZoomAPIBridge />
|
||||
<ScrollAPIBridge />
|
||||
<SelectionAPIBridge />
|
||||
@@ -649,98 +641,87 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
||||
<ExportAPIBridge />
|
||||
<BookmarkAPIBridge />
|
||||
<PrintAPIBridge />
|
||||
<DocumentPermissionsAPIBridge />
|
||||
<DocumentReadyWrapper
|
||||
fallback={
|
||||
<Center style={{ height: '100%', width: '100%' }}>
|
||||
<ToolLoadingFallback />
|
||||
</Center>
|
||||
}
|
||||
>
|
||||
{(documentId) => (
|
||||
<GlobalPointerProvider documentId={documentId}>
|
||||
<Viewport
|
||||
documentId={documentId}
|
||||
style={{
|
||||
backgroundColor: 'var(--bg-background)',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
maxHeight: '100%',
|
||||
maxWidth: '100%',
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
contain: 'strict',
|
||||
}}
|
||||
>
|
||||
<Scroller
|
||||
documentId={documentId}
|
||||
renderPage={({ width, height, pageIndex }) => {
|
||||
return (
|
||||
<Rotate key={`${documentId}-${pageIndex}`} documentId={documentId} pageIndex={pageIndex}>
|
||||
<PagePointerProvider documentId={documentId} pageIndex={pageIndex}>
|
||||
<div
|
||||
data-page-index={pageIndex}
|
||||
data-page-width={width}
|
||||
data-page-height={height}
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
position: 'relative',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
msUserSelect: 'none',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)'
|
||||
}}
|
||||
draggable={false}
|
||||
onDragStart={(e) => e.preventDefault()}
|
||||
onDrop={(e) => e.preventDefault()}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
>
|
||||
<TilingLayer documentId={documentId} pageIndex={pageIndex} />
|
||||
<GlobalPointerProvider>
|
||||
<Viewport
|
||||
style={{
|
||||
backgroundColor: 'var(--bg-background)',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
maxHeight: '100%',
|
||||
maxWidth: '100%',
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
contain: 'strict',
|
||||
}}
|
||||
>
|
||||
<Scroller
|
||||
renderPage={({ document, width, height, pageIndex, scale, rotation }) => {
|
||||
return (
|
||||
<Rotate key={document?.id} pageSize={{ width, height }}>
|
||||
<PagePointerProvider pageIndex={pageIndex} pageWidth={width} pageHeight={height} scale={scale} rotation={rotation}>
|
||||
<div
|
||||
data-page-index={pageIndex}
|
||||
data-page-width={width}
|
||||
data-page-height={height}
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
position: 'relative',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
msUserSelect: 'none',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)'
|
||||
}}
|
||||
draggable={false}
|
||||
onDragStart={(e) => e.preventDefault()}
|
||||
onDrop={(e) => e.preventDefault()}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
>
|
||||
{/* High-resolution tile layer */}
|
||||
<TilingLayer pageIndex={pageIndex} scale={scale} />
|
||||
|
||||
<CustomSearchLayer documentId={documentId} pageIndex={pageIndex} />
|
||||
{/* Search highlight layer */}
|
||||
<CustomSearchLayer pageIndex={pageIndex} scale={scale} />
|
||||
|
||||
<SelectionLayer documentId={documentId} pageIndex={pageIndex} />
|
||||
{/* Selection layer for text interaction */}
|
||||
<SelectionLayer pageIndex={pageIndex} scale={scale} />
|
||||
|
||||
{/* AnnotationLayer for annotation editing (only when enabled) */}
|
||||
{enableAnnotations && (
|
||||
<AnnotationLayer
|
||||
documentId={documentId}
|
||||
pageIndex={pageIndex}
|
||||
selectionOutlineColor="#007ACC"
|
||||
/>
|
||||
)}
|
||||
{/* Link layer for clickable PDF links */}
|
||||
<LinkLayer pageIndex={pageIndex} scale={scale} document={document} pdfFile={file} />
|
||||
|
||||
{enableRedaction && (
|
||||
<RedactionLayer
|
||||
documentId={documentId}
|
||||
pageIndex={pageIndex}
|
||||
selectionMenu={(props) => <RedactionSelectionMenu {...props} />}
|
||||
/>
|
||||
)}
|
||||
{/* Annotation layer for signatures (only when enabled) */}
|
||||
{enableAnnotations && (
|
||||
<AnnotationLayer
|
||||
pageIndex={pageIndex}
|
||||
scale={scale}
|
||||
pageWidth={width}
|
||||
pageHeight={height}
|
||||
rotation={rotation}
|
||||
selectionOutlineColor="#007ACC"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* LinkLayer on top to handle link navigation - must be last for click priority */}
|
||||
<LinkLayer
|
||||
documentId={documentId}
|
||||
pageIndex={pageIndex}
|
||||
pageWidth={width}
|
||||
pageHeight={height}
|
||||
/>
|
||||
</div>
|
||||
</PagePointerProvider>
|
||||
</Rotate>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Viewport>
|
||||
</GlobalPointerProvider>
|
||||
)}
|
||||
</DocumentReadyWrapper>
|
||||
</ActiveDocumentProvider>
|
||||
{/* Redaction layer for marking areas to redact (only when enabled) */}
|
||||
{enableRedaction && (
|
||||
<RedactionLayer
|
||||
pageIndex={pageIndex}
|
||||
scale={scale}
|
||||
rotation={rotation}
|
||||
selectionMenu={(props) => <RedactionSelectionMenu {...props} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</PagePointerProvider>
|
||||
</Rotate>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Viewport>
|
||||
</GlobalPointerProvider>
|
||||
</EmbedPDF>
|
||||
</div>
|
||||
</PrivateContent>
|
||||
|
||||
@@ -5,8 +5,8 @@ import { usePdfiumEngine } from '@embedpdf/engines/react';
|
||||
|
||||
// Import the essential plugins
|
||||
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
|
||||
import { Scroller, ScrollPluginPackage } from '@embedpdf/plugin-scroll/react';
|
||||
import { DocumentManagerPluginPackage } from '@embedpdf/plugin-document-manager/react';
|
||||
import { Scroller, ScrollPluginPackage, ScrollStrategy } from '@embedpdf/plugin-scroll/react';
|
||||
import { LoaderPluginPackage } from '@embedpdf/plugin-loader/react';
|
||||
import { RenderPluginPackage } from '@embedpdf/plugin-render/react';
|
||||
import { ZoomPluginPackage } from '@embedpdf/plugin-zoom/react';
|
||||
import { InteractionManagerPluginPackage, PagePointerProvider, GlobalPointerProvider } from '@embedpdf/plugin-interaction-manager/react';
|
||||
@@ -17,11 +17,12 @@ import { SpreadPluginPackage, SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||
import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
|
||||
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
|
||||
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
|
||||
import { Rotation, PdfAnnotationSubtype } from '@embedpdf/models';
|
||||
import { Rotation } from '@embedpdf/models';
|
||||
|
||||
// Import annotation plugins
|
||||
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
|
||||
import { AnnotationLayer, AnnotationPluginPackage } from '@embedpdf/plugin-annotation/react';
|
||||
import { PdfAnnotationSubtype } from '@embedpdf/models';
|
||||
|
||||
import { CustomSearchLayer } from '@app/components/viewer/CustomSearchLayer';
|
||||
import { ZoomAPIBridge } from '@app/components/viewer/ZoomAPIBridge';
|
||||
@@ -34,9 +35,6 @@ import { SpreadAPIBridge } from '@app/components/viewer/SpreadAPIBridge';
|
||||
import { SearchAPIBridge } from '@app/components/viewer/SearchAPIBridge';
|
||||
import { ThumbnailAPIBridge } from '@app/components/viewer/ThumbnailAPIBridge';
|
||||
import { RotateAPIBridge } from '@app/components/viewer/RotateAPIBridge';
|
||||
import { DocumentReadyWrapper } from '@app/components/viewer/DocumentReadyWrapper';
|
||||
|
||||
const DOCUMENT_NAME = 'stirling-pdf-signing-viewer';
|
||||
|
||||
interface LocalEmbedPDFWithAnnotationsProps {
|
||||
file?: File | Blob;
|
||||
@@ -71,16 +69,22 @@ export function LocalEmbedPDFWithAnnotations({
|
||||
const viewportGap = rootFontSize * 3.5;
|
||||
|
||||
return [
|
||||
createPluginRegistration(DocumentManagerPluginPackage, {
|
||||
initialDocuments: [{
|
||||
url: pdfUrl,
|
||||
name: DOCUMENT_NAME,
|
||||
}],
|
||||
createPluginRegistration(LoaderPluginPackage, {
|
||||
loadingOptions: {
|
||||
type: 'url',
|
||||
pdfFile: {
|
||||
id: 'stirling-pdf-signing-viewer',
|
||||
url: pdfUrl,
|
||||
},
|
||||
},
|
||||
}),
|
||||
createPluginRegistration(ViewportPluginPackage, {
|
||||
viewportGap,
|
||||
}),
|
||||
createPluginRegistration(ScrollPluginPackage),
|
||||
createPluginRegistration(ScrollPluginPackage, {
|
||||
strategy: ScrollStrategy.Vertical,
|
||||
initialPage: 0,
|
||||
}),
|
||||
createPluginRegistration(RenderPluginPackage, {
|
||||
withForms: true,
|
||||
withAnnotations: true,
|
||||
@@ -187,8 +191,7 @@ export function LocalEmbedPDFWithAnnotations({
|
||||
<EmbedPDF
|
||||
engine={engine}
|
||||
plugins={plugins}
|
||||
onInitialized={async (registry: any) => {
|
||||
// v2.0: Use registry.getPlugin() to access plugin APIs
|
||||
onInitialized={async (registry) => {
|
||||
const annotationPlugin = registry.getPlugin('annotation');
|
||||
if (!annotationPlugin || !annotationPlugin.provides) return;
|
||||
|
||||
@@ -241,71 +244,78 @@ export function LocalEmbedPDFWithAnnotations({
|
||||
<SearchAPIBridge />
|
||||
<ThumbnailAPIBridge />
|
||||
<RotateAPIBridge />
|
||||
<DocumentReadyWrapper
|
||||
fallback={
|
||||
<Center style={{ height: '100%', width: '100%' }}>
|
||||
<ToolLoadingFallback />
|
||||
</Center>
|
||||
}
|
||||
>
|
||||
{(documentId) => (
|
||||
<GlobalPointerProvider documentId={documentId}>
|
||||
<Viewport
|
||||
documentId={documentId}
|
||||
style={{
|
||||
backgroundColor: 'var(--bg-surface)',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
maxHeight: '100%',
|
||||
maxWidth: '100%',
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
contain: 'strict',
|
||||
}}
|
||||
>
|
||||
<Scroller
|
||||
documentId={documentId}
|
||||
renderPage={({ width, height, pageIndex }) => (
|
||||
<Rotate key={`${documentId}-${pageIndex}`} documentId={documentId} pageIndex={pageIndex}>
|
||||
<PagePointerProvider documentId={documentId} pageIndex={pageIndex}>
|
||||
<div
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
position: 'relative',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
msUserSelect: 'none'
|
||||
}}
|
||||
draggable={false}
|
||||
onDragStart={(e) => e.preventDefault()}
|
||||
onDrop={(e) => e.preventDefault()}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
>
|
||||
<TilingLayer documentId={documentId} pageIndex={pageIndex} />
|
||||
<GlobalPointerProvider>
|
||||
<Viewport
|
||||
style={{
|
||||
backgroundColor: 'var(--bg-surface)',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
maxHeight: '100%',
|
||||
maxWidth: '100%',
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
contain: 'strict',
|
||||
}}
|
||||
>
|
||||
<Scroller
|
||||
renderPage={({ width, height, pageIndex, scale, rotation }: {
|
||||
width: number;
|
||||
height: number;
|
||||
pageIndex: number;
|
||||
scale: number;
|
||||
rotation?: number;
|
||||
}) => (
|
||||
<Rotate pageSize={{ width, height }}>
|
||||
<PagePointerProvider {...{
|
||||
pageWidth: width,
|
||||
pageHeight: height,
|
||||
pageIndex,
|
||||
scale,
|
||||
rotation: rotation || 0
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
position: 'relative',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
msUserSelect: 'none'
|
||||
}}
|
||||
draggable={false}
|
||||
onDragStart={(e) => e.preventDefault()}
|
||||
onDrop={(e) => e.preventDefault()}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
>
|
||||
{/* High-resolution tile layer */}
|
||||
<TilingLayer pageIndex={pageIndex} scale={scale} />
|
||||
|
||||
<CustomSearchLayer documentId={documentId} pageIndex={pageIndex} />
|
||||
{/* Search highlight layer */}
|
||||
<CustomSearchLayer pageIndex={pageIndex} scale={scale} />
|
||||
|
||||
<SelectionLayer documentId={documentId} pageIndex={pageIndex} />
|
||||
{/* Selection layer for text interaction */}
|
||||
<SelectionLayer pageIndex={pageIndex} scale={scale} />
|
||||
|
||||
<AnnotationLayer
|
||||
documentId={documentId}
|
||||
pageIndex={pageIndex}
|
||||
selectionOutlineColor="#007ACC"
|
||||
/>
|
||||
</div>
|
||||
</PagePointerProvider>
|
||||
</Rotate>
|
||||
)}
|
||||
/>
|
||||
</Viewport>
|
||||
</GlobalPointerProvider>
|
||||
)}
|
||||
</DocumentReadyWrapper>
|
||||
{/* Annotation layer for signatures */}
|
||||
<AnnotationLayer
|
||||
pageIndex={pageIndex}
|
||||
scale={scale}
|
||||
pageWidth={width}
|
||||
pageHeight={height}
|
||||
rotation={rotation || 0}
|
||||
selectionOutlineColor="#007ACC"
|
||||
/>
|
||||
</div>
|
||||
</PagePointerProvider>
|
||||
</Rotate>
|
||||
)}
|
||||
/>
|
||||
</Viewport>
|
||||
</GlobalPointerProvider>
|
||||
</EmbedPDF>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,60 +1,44 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { usePan } from '@embedpdf/plugin-pan/react';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||
|
||||
/**
|
||||
* Component that runs inside EmbedPDF context and updates pan state in ViewerContext
|
||||
*/
|
||||
export function PanAPIBridge() {
|
||||
const activeDocumentId = useActiveDocumentId();
|
||||
|
||||
// Don't render the inner component until we have a valid document ID
|
||||
if (!activeDocumentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <PanAPIBridgeInner documentId={activeDocumentId} />;
|
||||
}
|
||||
|
||||
function PanAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
const { provides: pan, isPanning } = usePan(documentId);
|
||||
const { provides: pan, isPanning } = usePan();
|
||||
const { registerBridge, triggerImmediatePanUpdate } = useViewer();
|
||||
|
||||
// Keep pan ref updated to avoid re-running effect when object reference changes
|
||||
const panRef = useRef(pan);
|
||||
useEffect(() => {
|
||||
panRef.current = pan;
|
||||
}, [pan]);
|
||||
// Store state locally
|
||||
const [_localState, setLocalState] = useState({
|
||||
isPanning: false
|
||||
});
|
||||
|
||||
// Track previous isPanning value to detect changes
|
||||
const prevIsPanningRef = useRef<boolean>(isPanning);
|
||||
|
||||
useEffect(() => {
|
||||
const currentPan = panRef.current;
|
||||
if (currentPan) {
|
||||
if (pan) {
|
||||
// Update local state
|
||||
const newState = {
|
||||
isPanning
|
||||
};
|
||||
setLocalState(newState);
|
||||
|
||||
// Register this bridge with ViewerContext
|
||||
registerBridge('pan', {
|
||||
state: newState,
|
||||
api: {
|
||||
enable: () => {
|
||||
currentPan.enablePan();
|
||||
pan.enablePan();
|
||||
},
|
||||
disable: () => {
|
||||
currentPan.disablePan();
|
||||
pan.disablePan();
|
||||
},
|
||||
toggle: () => {
|
||||
currentPan.togglePan();
|
||||
},
|
||||
makePanDefault: () => {
|
||||
// v2.3.0: makePanDefault may not exist, enable pan as fallback
|
||||
if ('makePanDefault' in currentPan && typeof (currentPan as any).makePanDefault === 'function') {
|
||||
(currentPan as any).makePanDefault();
|
||||
} else {
|
||||
currentPan.enablePan();
|
||||
}
|
||||
pan.togglePan();
|
||||
},
|
||||
makePanDefault: () => pan.makePanDefault(),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -64,7 +48,7 @@ function PanAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
triggerImmediatePanUpdate(isPanning);
|
||||
}
|
||||
}
|
||||
}, [isPanning, registerBridge, triggerImmediatePanUpdate]);
|
||||
}, [pan, isPanning, triggerImmediatePanUpdate]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import { useEffect, useImperativeHandle } from 'react';
|
||||
import { useRedaction as useEmbedPdfRedaction } from '@embedpdf/plugin-redaction/react';
|
||||
import { useRedaction } from '@app/contexts/RedactionContext';
|
||||
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||
|
||||
/**
|
||||
* RedactionAPIBridge connects the EmbedPDF redaction plugin to our RedactionContext.
|
||||
* It must be rendered inside the EmbedPDF context to access the plugin API.
|
||||
*
|
||||
* It does two things:
|
||||
* 1. Syncs EmbedPDF state (pendingCount, activeType, isRedacting) to our context
|
||||
* 2. Exposes the EmbedPDF API through our context's ref so outside components can call it
|
||||
*/
|
||||
export function RedactionAPIBridge() {
|
||||
const activeDocumentId = useActiveDocumentId();
|
||||
|
||||
// Don't render the inner component until we have a valid document ID
|
||||
if (!activeDocumentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <RedactionAPIBridgeInner documentId={activeDocumentId} />;
|
||||
}
|
||||
|
||||
function RedactionAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
const { state, provides } = useEmbedPdfRedaction(documentId);
|
||||
const { state, provides } = useEmbedPdfRedaction();
|
||||
const {
|
||||
redactionApiRef,
|
||||
setPendingCount,
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
import { useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
|
||||
import { useRedaction as useEmbedPdfRedaction } from '@embedpdf/plugin-redaction/react';
|
||||
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||
|
||||
export interface RedactionPendingTrackerAPI {
|
||||
commitAllPending: () => void;
|
||||
getPendingCount: () => number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RedactionPendingTracker monitors pending redactions and exposes an API
|
||||
* for committing and checking pending redactions.
|
||||
* Must be rendered inside the EmbedPDF context.
|
||||
*
|
||||
* Note: The unsaved changes checker is registered by EmbedPdfViewer, not here,
|
||||
* to avoid conflicts and allow the viewer to check both annotations and redactions.
|
||||
*/
|
||||
export const RedactionPendingTracker = forwardRef<RedactionPendingTrackerAPI>(
|
||||
function RedactionPendingTracker(_, ref) {
|
||||
const activeDocumentId = useActiveDocumentId();
|
||||
|
||||
// Don't render the inner component until we have a valid document ID
|
||||
if (!activeDocumentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <RedactionPendingTrackerInner documentId={activeDocumentId} ref={ref} />;
|
||||
}
|
||||
);
|
||||
|
||||
const RedactionPendingTrackerInner = forwardRef<RedactionPendingTrackerAPI, { documentId: string }>(
|
||||
function RedactionPendingTrackerInner({ documentId }, ref) {
|
||||
const { state, provides } = useEmbedPdfRedaction(documentId);
|
||||
const { state, provides } = useEmbedPdfRedaction();
|
||||
|
||||
const pendingCountRef = useRef(0);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRedaction as useEmbedPdfRedaction, RedactionSelectionMenuProps } from '@embedpdf/plugin-redaction/react';
|
||||
import { useRedaction as useEmbedPdfRedaction, SelectionMenuProps } from '@embedpdf/plugin-redaction/react';
|
||||
import { ActionIcon, Tooltip, Button, Group } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { createPortal } from 'react-dom';
|
||||
@@ -6,63 +6,46 @@ import { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import { useRedaction } from '@app/contexts/RedactionContext';
|
||||
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||
|
||||
// Use the official EmbedPDF v2.3.0 types
|
||||
export type { RedactionSelectionMenuProps };
|
||||
|
||||
export function RedactionSelectionMenu(props: RedactionSelectionMenuProps) {
|
||||
const activeDocumentId = useActiveDocumentId();
|
||||
|
||||
// Don't render until we have a valid document ID
|
||||
if (!activeDocumentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RedactionSelectionMenuInner
|
||||
documentId={activeDocumentId}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function RedactionSelectionMenuInner({
|
||||
documentId,
|
||||
context,
|
||||
selected,
|
||||
menuWrapperProps,
|
||||
}: RedactionSelectionMenuProps & { documentId: string }) {
|
||||
// Extract item and pageIndex from context (EmbedPDF v2.3.0 API)
|
||||
const item = context?.item;
|
||||
const pageIndex = context?.pageIndex;
|
||||
/**
|
||||
* Custom menu component that appears when a pending redaction mark is selected.
|
||||
* Allows users to remove or apply individual pending marks.
|
||||
* Uses a portal to ensure it appears above all content, including next pages.
|
||||
*/
|
||||
export function RedactionSelectionMenu({ item, selected, menuWrapperProps }: SelectionMenuProps) {
|
||||
const { t } = useTranslation();
|
||||
const { provides } = useEmbedPdfRedaction(documentId);
|
||||
const { provides } = useEmbedPdfRedaction();
|
||||
const { setRedactionsApplied } = useRedaction();
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const [menuPosition, setMenuPosition] = useState<{ top: number; left: number } | null>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Merge refs - menuWrapperProps.ref is a callback ref
|
||||
// Merge refs if menuWrapperProps has a ref
|
||||
const setRef = useCallback((node: HTMLDivElement | null) => {
|
||||
wrapperRef.current = node;
|
||||
// Call the EmbedPDF ref callback
|
||||
menuWrapperProps?.ref?.(node);
|
||||
if (menuWrapperProps?.ref) {
|
||||
const ref = menuWrapperProps.ref;
|
||||
if (typeof ref === 'function') {
|
||||
ref(node);
|
||||
} else if (ref && 'current' in ref) {
|
||||
(ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
||||
}
|
||||
}
|
||||
}, [menuWrapperProps]);
|
||||
|
||||
const handleRemove = useCallback(() => {
|
||||
if (provides?.removePending && item && pageIndex !== undefined) {
|
||||
provides.removePending(pageIndex, item.id);
|
||||
if (provides?.removePending && item) {
|
||||
provides.removePending(item.page, item.id);
|
||||
}
|
||||
}, [provides, item, pageIndex]);
|
||||
}, [provides, item]);
|
||||
|
||||
const handleApply = useCallback(() => {
|
||||
if (provides?.commitPending && item && pageIndex !== undefined) {
|
||||
provides.commitPending(pageIndex, item.id);
|
||||
if (provides?.commitPending && item) {
|
||||
provides.commitPending(item.page, item.id);
|
||||
// Mark redactions as applied (but not yet saved) so the Save Changes button stays enabled
|
||||
// This ensures the button doesn't become disabled when pendingCount decreases
|
||||
setRedactionsApplied(true);
|
||||
}
|
||||
}, [provides, item, pageIndex, setRedactionsApplied]);
|
||||
}, [provides, item, setRedactionsApplied]);
|
||||
|
||||
// Calculate position for portal based on wrapper element
|
||||
useEffect(() => {
|
||||
@@ -78,13 +61,13 @@ function RedactionSelectionMenuInner({
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapperRect = wrapper.getBoundingClientRect();
|
||||
const rect = wrapper.getBoundingClientRect();
|
||||
// Position menu below the wrapper, centered
|
||||
// Use getBoundingClientRect which gives viewport-relative coordinates
|
||||
// Since we're using fixed positioning in the portal, we don't need to add scroll offsets
|
||||
setMenuPosition({
|
||||
top: wrapperRect.bottom + 8,
|
||||
left: wrapperRect.left + wrapperRect.width / 2,
|
||||
top: rect.bottom + 8,
|
||||
left: rect.left + rect.width / 2,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -169,16 +152,19 @@ function RedactionSelectionMenuInner({
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
// Extract ref from menuWrapperProps to avoid conflicts
|
||||
const { ref: _, ...wrapperPropsWithoutRef } = menuWrapperProps || {};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Invisible wrapper that provides positioning - uses EmbedPDF's menuWrapperProps */}
|
||||
<div
|
||||
ref={setRef}
|
||||
{...wrapperPropsWithoutRef}
|
||||
style={{
|
||||
// Use EmbedPDF's positioning styles
|
||||
...menuWrapperProps?.style,
|
||||
// Keep the wrapper invisible but still occupying space for positioning
|
||||
opacity: 0,
|
||||
// Preserve the original positioning from menuWrapperProps
|
||||
...(wrapperPropsWithoutRef?.style || {}),
|
||||
// Override visibility to hide the wrapper (we only need its position)
|
||||
visibility: 'hidden',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
@@ -188,3 +174,4 @@ function RedactionSelectionMenuInner({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +1,39 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRotate } from '@embedpdf/plugin-rotate/react';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||
|
||||
/**
|
||||
* Component that runs inside EmbedPDF context and updates rotation state in ViewerContext
|
||||
*/
|
||||
export function RotateAPIBridge() {
|
||||
const activeDocumentId = useActiveDocumentId();
|
||||
|
||||
// Don't render the inner component until we have a valid document ID
|
||||
if (!activeDocumentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <RotateAPIBridgeInner documentId={activeDocumentId} />;
|
||||
}
|
||||
|
||||
function RotateAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
const { provides: rotate, rotation } = useRotate(documentId);
|
||||
const { provides: rotate, rotation } = useRotate();
|
||||
const { registerBridge } = useViewer();
|
||||
|
||||
// Keep rotate ref updated to avoid re-running effect when object reference changes
|
||||
const rotateRef = useRef(rotate);
|
||||
useEffect(() => {
|
||||
rotateRef.current = rotate;
|
||||
}, [rotate]);
|
||||
|
||||
// Store state locally
|
||||
const [_localState, setLocalState] = useState({
|
||||
rotation: 0
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const currentRotate = rotateRef.current;
|
||||
if (currentRotate) {
|
||||
if (rotate) {
|
||||
// Update local state
|
||||
const newState = {
|
||||
rotation
|
||||
};
|
||||
setLocalState(newState);
|
||||
|
||||
// Register this bridge with ViewerContext
|
||||
registerBridge('rotation', {
|
||||
state: newState,
|
||||
api: {
|
||||
rotateForward: () => currentRotate.rotateForward(),
|
||||
rotateBackward: () => currentRotate.rotateBackward(),
|
||||
setRotation: (rotationValue: number) => currentRotate.setRotation(rotationValue),
|
||||
rotateForward: () => rotate.rotateForward(),
|
||||
rotateBackward: () => rotate.rotateBackward(),
|
||||
setRotation: (rotationValue: number) => rotate.setRotation(rotationValue),
|
||||
getRotation: () => rotation,
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [rotation, registerBridge]);
|
||||
}, [rotate, rotation]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,39 +1,20 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useScroll } from '@embedpdf/plugin-scroll/react';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||
|
||||
/**
|
||||
* ScrollAPIBridge manages scroll state and exposes scroll actions.
|
||||
* Registers with ViewerContext to provide scroll functionality to UI components.
|
||||
*/
|
||||
export function ScrollAPIBridge() {
|
||||
const activeDocumentId = useActiveDocumentId();
|
||||
|
||||
// Don't render the inner component until we have a valid document ID
|
||||
if (!activeDocumentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ScrollAPIBridgeInner documentId={activeDocumentId} />;
|
||||
}
|
||||
|
||||
function ScrollAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
const { provides: scroll, state: scrollState } = useScroll(documentId);
|
||||
const { provides: scroll, state: scrollState } = useScroll();
|
||||
const { registerBridge, triggerImmediateScrollUpdate } = useViewer();
|
||||
|
||||
// Keep scroll ref updated to avoid re-running effect when object reference changes
|
||||
const scrollRef = useRef(scroll);
|
||||
useEffect(() => {
|
||||
scrollRef.current = scroll;
|
||||
}, [scroll]);
|
||||
|
||||
// Extract primitive values to avoid dependency on object references
|
||||
const currentPage = scrollState?.currentPage;
|
||||
const totalPages = scrollState?.totalPages;
|
||||
|
||||
useEffect(() => {
|
||||
const currentScroll = scrollRef.current;
|
||||
if (currentScroll && currentPage !== undefined && totalPages !== undefined) {
|
||||
if (scroll && scrollState) {
|
||||
const newState = {
|
||||
currentPage,
|
||||
totalPages,
|
||||
currentPage: scrollState.currentPage,
|
||||
totalPages: scrollState.totalPages,
|
||||
};
|
||||
|
||||
// Trigger immediate update for responsive UI
|
||||
@@ -41,10 +22,10 @@ function ScrollAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
|
||||
registerBridge('scroll', {
|
||||
state: newState,
|
||||
api: currentScroll
|
||||
api: scroll
|
||||
});
|
||||
}
|
||||
}, [currentPage, totalPages, registerBridge, triggerImmediateScrollUpdate]);
|
||||
}, [scroll, scrollState]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearch } from '@embedpdf/plugin-search/react';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||
|
||||
interface SearchResult {
|
||||
pageIndex: number;
|
||||
@@ -11,29 +10,13 @@ interface SearchResult {
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchAPIBridge manages search state and provides search functionality.
|
||||
* Listens for search result changes from EmbedPDF and maintains local state.
|
||||
*/
|
||||
export function SearchAPIBridge() {
|
||||
const activeDocumentId = useActiveDocumentId();
|
||||
|
||||
// Don't render the inner component until we have a valid document ID
|
||||
if (!activeDocumentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SearchAPIBridgeInner documentId={activeDocumentId} />;
|
||||
}
|
||||
|
||||
function SearchAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
const { provides: search } = useSearch(documentId);
|
||||
const { registerBridge, scrollActions } = useViewer();
|
||||
|
||||
// Keep search ref updated to avoid re-running effects when object reference changes
|
||||
const searchRef = useRef(search);
|
||||
const isSearchingRef = useRef(false);
|
||||
const lastScrolledIndexRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
searchRef.current = search;
|
||||
}, [search]);
|
||||
const { provides: search } = useSearch();
|
||||
const { registerBridge } = useViewer();
|
||||
|
||||
const [localState, setLocalState] = useState({
|
||||
results: null as SearchResult[] | null,
|
||||
@@ -41,18 +24,10 @@ function SearchAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
});
|
||||
|
||||
// Subscribe to search result changes from EmbedPDF
|
||||
const subscriptionRef = useRef<(() => void) | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Cleanup previous subscription
|
||||
if (subscriptionRef.current) {
|
||||
subscriptionRef.current();
|
||||
subscriptionRef.current = null;
|
||||
}
|
||||
|
||||
if (!search) return;
|
||||
|
||||
subscriptionRef.current = search.onSearchResultStateChange?.((state: any) => {
|
||||
const unsubscribe = search.onSearchResultStateChange?.((state: any) => {
|
||||
if (!state) return;
|
||||
|
||||
const newState = {
|
||||
@@ -67,80 +42,27 @@ function SearchAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
}
|
||||
return prevState;
|
||||
});
|
||||
}) ?? null;
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (subscriptionRef.current) {
|
||||
subscriptionRef.current();
|
||||
subscriptionRef.current = null;
|
||||
}
|
||||
};
|
||||
return unsubscribe;
|
||||
}, [search]);
|
||||
|
||||
// Extract primitive values from localState to avoid object reference dependencies
|
||||
const localResults = localState.results;
|
||||
const localActiveIndex = localState.activeIndex;
|
||||
|
||||
// Scroll to active result when it changes (for next/previous navigation)
|
||||
// Register bridge whenever search API or state changes
|
||||
useEffect(() => {
|
||||
if (!localResults || localResults.length === 0) {
|
||||
lastScrolledIndexRef.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Only scroll if the active index actually changed
|
||||
const activeResultIndex = localActiveIndex - 1; // Convert back to 0-based
|
||||
if (activeResultIndex >= 0 &&
|
||||
activeResultIndex < localResults.length &&
|
||||
lastScrolledIndexRef.current !== activeResultIndex) {
|
||||
const activeResult = localResults[activeResultIndex];
|
||||
if (activeResult) {
|
||||
const pageNumber = activeResult.pageIndex + 1; // Convert to 1-based page number
|
||||
scrollActions.scrollToPage(pageNumber);
|
||||
lastScrolledIndexRef.current = activeResultIndex;
|
||||
}
|
||||
}
|
||||
}, [localResults, localActiveIndex, scrollActions]);
|
||||
|
||||
// Register bridge whenever state changes
|
||||
useEffect(() => {
|
||||
const currentSearch = searchRef.current;
|
||||
if (currentSearch) {
|
||||
if (search) {
|
||||
registerBridge('search', {
|
||||
state: { results: localResults, activeIndex: localActiveIndex },
|
||||
state: localState,
|
||||
api: {
|
||||
search: async (query: string) => {
|
||||
// Prevent overlapping searches
|
||||
if (isSearchingRef.current) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!currentSearch?.startSearch || !currentSearch?.searchAllPages) {
|
||||
return null;
|
||||
}
|
||||
|
||||
isSearchingRef.current = true;
|
||||
|
||||
try {
|
||||
currentSearch.startSearch();
|
||||
const results = await currentSearch.searchAllPages(query);
|
||||
return results;
|
||||
} catch (error: any) {
|
||||
// Handle abort errors gracefully - these occur when searches overlap
|
||||
if (error?.type === 'abort' || error?.message?.includes('abort')) {
|
||||
// Silently handle abort - this is expected when user types quickly
|
||||
return null;
|
||||
}
|
||||
console.error('Search failed:', error);
|
||||
return null;
|
||||
} finally {
|
||||
isSearchingRef.current = false;
|
||||
if (search?.startSearch && search?.searchAllPages) {
|
||||
search.startSearch();
|
||||
return search.searchAllPages(query);
|
||||
}
|
||||
},
|
||||
clear: () => {
|
||||
try {
|
||||
if (currentSearch?.stopSearch) {
|
||||
currentSearch.stopSearch();
|
||||
if (search?.stopSearch) {
|
||||
search.stopSearch();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error stopping search:', error);
|
||||
@@ -149,21 +71,21 @@ function SearchAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
},
|
||||
next: () => {
|
||||
try {
|
||||
currentSearch?.nextResult?.();
|
||||
search?.nextResult?.();
|
||||
} catch (error) {
|
||||
console.warn('Error navigating to next result:', error);
|
||||
}
|
||||
},
|
||||
previous: () => {
|
||||
try {
|
||||
currentSearch?.previousResult?.();
|
||||
search?.previousResult?.();
|
||||
} catch (error) {
|
||||
console.warn('Error navigating to previous result:', error);
|
||||
}
|
||||
},
|
||||
goToResult: (index: number) => {
|
||||
try {
|
||||
currentSearch?.goToResult?.(index);
|
||||
search?.goToResult?.(index);
|
||||
} catch (error) {
|
||||
console.warn('Error going to result:', error);
|
||||
}
|
||||
@@ -171,7 +93,7 @@ function SearchAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [localResults, localActiveIndex, registerBridge]);
|
||||
}, [search, localState]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelectionCapability } from '@embedpdf/plugin-selection/react';
|
||||
import { useSelectionCapability, SelectionRangeX } from '@embedpdf/plugin-selection/react';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
|
||||
/**
|
||||
* Component that runs inside EmbedPDF context and updates selection state in ViewerContext
|
||||
*/
|
||||
export function SelectionAPIBridge() {
|
||||
const { provides: selection } = useSelectionCapability();
|
||||
const { registerBridge } = useViewer();
|
||||
@@ -13,6 +16,7 @@ export function SelectionAPIBridge() {
|
||||
hasSelection
|
||||
};
|
||||
|
||||
// Register this bridge with ViewerContext
|
||||
registerBridge('selection', {
|
||||
state: newState,
|
||||
api: {
|
||||
@@ -22,10 +26,12 @@ export function SelectionAPIBridge() {
|
||||
}
|
||||
});
|
||||
|
||||
const unsubscribe = selection.onSelectionChange((event: any) => {
|
||||
const hasText = !!(event?.selection || event);
|
||||
// Listen for selection changes to track when text is selected
|
||||
const unsubscribe = selection.onSelectionChange((sel: SelectionRangeX | null) => {
|
||||
const hasText = !!sel;
|
||||
setHasSelection(hasText);
|
||||
const updatedState = { hasSelection: hasText };
|
||||
// Re-register with updated state
|
||||
registerBridge('selection', {
|
||||
state: updatedState,
|
||||
api: {
|
||||
|
||||
@@ -1,32 +1,16 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useSpread, SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||
|
||||
/**
|
||||
* Component that runs inside EmbedPDF context and updates spread state in ViewerContext
|
||||
*/
|
||||
export function SpreadAPIBridge() {
|
||||
const activeDocumentId = useActiveDocumentId();
|
||||
|
||||
// Don't render the inner component until we have a valid document ID
|
||||
if (!activeDocumentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SpreadAPIBridgeInner documentId={activeDocumentId} />;
|
||||
}
|
||||
|
||||
function SpreadAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
const { provides: spread, spreadMode } = useSpread(documentId);
|
||||
const { provides: spread, spreadMode } = useSpread();
|
||||
const { registerBridge, triggerImmediateSpreadUpdate } = useViewer();
|
||||
|
||||
// Keep spread ref updated to avoid re-running effect when object reference changes
|
||||
const spreadRef = useRef(spread);
|
||||
useEffect(() => {
|
||||
spreadRef.current = spread;
|
||||
}, [spread]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentSpread = spreadRef.current;
|
||||
if (!currentSpread || spreadMode === undefined) {
|
||||
if (!spread) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,20 +23,20 @@ function SpreadAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
state: newState,
|
||||
api: {
|
||||
setSpreadMode: (mode: SpreadMode) => {
|
||||
currentSpread.setSpreadMode(mode);
|
||||
spread.setSpreadMode(mode);
|
||||
},
|
||||
getSpreadMode: () => currentSpread.getSpreadMode(),
|
||||
getSpreadMode: () => spread.getSpreadMode(),
|
||||
toggleSpreadMode: () => {
|
||||
const current = currentSpread.getSpreadMode();
|
||||
const current = spread.getSpreadMode();
|
||||
const nextMode = current === SpreadMode.None ? SpreadMode.Odd : SpreadMode.None;
|
||||
currentSpread.setSpreadMode(nextMode);
|
||||
spread.setSpreadMode(nextMode);
|
||||
},
|
||||
SpreadMode,
|
||||
},
|
||||
});
|
||||
|
||||
triggerImmediateSpreadUpdate(spreadMode);
|
||||
}, [spreadMode, registerBridge, triggerImmediateSpreadUpdate]);
|
||||
}, [spread, spreadMode, registerBridge, triggerImmediateSpreadUpdate]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useZoom, ZoomMode } from '@embedpdf/plugin-zoom/react';
|
||||
import { useSpread, SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||
import { useFileState } from '@app/contexts/FileContext';
|
||||
import {
|
||||
determineAutoZoom,
|
||||
@@ -13,19 +12,8 @@ import {
|
||||
import { getFirstPageAspectRatioFromStub } from '@app/utils/pageMetadata';
|
||||
|
||||
export function ZoomAPIBridge() {
|
||||
const activeDocumentId = useActiveDocumentId();
|
||||
|
||||
// Don't render the inner component until we have a valid document ID
|
||||
if (!activeDocumentId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ZoomAPIBridgeInner documentId={activeDocumentId} />;
|
||||
}
|
||||
|
||||
function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
const { provides: zoom, state: zoomState } = useZoom(documentId);
|
||||
const { spreadMode } = useSpread(documentId);
|
||||
const { provides: zoom, state: zoomState } = useZoom();
|
||||
const { spreadMode } = useSpread();
|
||||
const { registerBridge, triggerImmediateZoomUpdate } = useViewer();
|
||||
const { selectors } = useFileState();
|
||||
|
||||
@@ -33,14 +21,8 @@ function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
const lastSpreadMode = useRef(spreadMode ?? SpreadMode.None);
|
||||
const lastFileId = useRef<string | undefined>(undefined);
|
||||
const lastAppliedZoom = useRef<number | null>(null);
|
||||
const zoomRef = useRef(zoom);
|
||||
const [autoZoomTick, setAutoZoomTick] = useState(0);
|
||||
|
||||
// Keep zoom ref updated
|
||||
useEffect(() => {
|
||||
zoomRef.current = zoom;
|
||||
}, [zoom]);
|
||||
|
||||
const scheduleAutoZoom = useCallback(() => {
|
||||
hasSetInitialZoom.current = false;
|
||||
lastAppliedZoom.current = null;
|
||||
@@ -48,22 +30,15 @@ function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
}, []);
|
||||
|
||||
const requestFitWidth = useCallback(() => {
|
||||
if (zoomRef.current) {
|
||||
zoomRef.current.requestZoom(ZoomMode.FitWidth, { vx: 0.5, vy: 0 });
|
||||
if (zoom) {
|
||||
zoom.requestZoom(ZoomMode.FitWidth, { vx: 0.5, vy: 0 });
|
||||
}
|
||||
}, []);
|
||||
}, [zoom]);
|
||||
|
||||
const stubs = selectors.getStirlingFileStubs();
|
||||
const firstFileStub = stubs[0];
|
||||
const firstFileId = firstFileStub?.id;
|
||||
|
||||
// Extract primitive values from zoomState for dependency arrays
|
||||
const zoomLevel = zoomState?.zoomLevel;
|
||||
const currentZoomLevel = zoomState?.currentZoomLevel;
|
||||
|
||||
// Extract metadata aspect ratio as a primitive to avoid object reference issues
|
||||
const metadataAspectRatio = getFirstPageAspectRatioFromStub(firstFileStub);
|
||||
|
||||
useEffect(() => {
|
||||
if (!firstFileId) {
|
||||
hasSetInitialZoom.current = false;
|
||||
@@ -84,6 +59,7 @@ function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
lastSpreadMode.current = currentSpreadMode;
|
||||
|
||||
const hadTrackedAutoZoom = lastAppliedZoom.current !== null;
|
||||
const zoomLevel = zoomState?.zoomLevel;
|
||||
if (
|
||||
zoomLevel === ZoomMode.FitWidth ||
|
||||
zoomLevel === ZoomMode.Automatic ||
|
||||
@@ -93,13 +69,13 @@ function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
scheduleAutoZoom();
|
||||
}
|
||||
}
|
||||
}, [spreadMode, zoomLevel, scheduleAutoZoom, requestFitWidth]);
|
||||
}, [spreadMode, zoomState?.zoomLevel, scheduleAutoZoom, requestFitWidth]);
|
||||
|
||||
|
||||
const isManagedZoom =
|
||||
!!zoom &&
|
||||
(zoomLevel === ZoomMode.FitWidth ||
|
||||
zoomLevel === ZoomMode.Automatic ||
|
||||
(zoomState?.zoomLevel === ZoomMode.FitWidth ||
|
||||
zoomState?.zoomLevel === ZoomMode.Automatic ||
|
||||
lastAppliedZoom.current !== null);
|
||||
|
||||
useFitWidthResize({
|
||||
@@ -109,7 +85,7 @@ function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!zoom || zoomLevel === undefined || currentZoomLevel === undefined) {
|
||||
if (!zoom || !zoomState) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,14 +97,14 @@ function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (zoomLevel !== ZoomMode.FitWidth) {
|
||||
if (zoomLevel === ZoomMode.Automatic) {
|
||||
if (zoomState.zoomLevel !== ZoomMode.FitWidth) {
|
||||
if (zoomState.zoomLevel === ZoomMode.Automatic) {
|
||||
requestFitWidth();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const fitWidthZoom = currentZoomLevel;
|
||||
const fitWidthZoom = zoomState.currentZoomLevel;
|
||||
if (!fitWidthZoom || fitWidthZoom <= 0 || fitWidthZoom === 1) {
|
||||
return;
|
||||
}
|
||||
@@ -145,6 +121,7 @@ function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
const applyAutoZoom = async () => {
|
||||
const currentSpreadMode = spreadMode ?? SpreadMode.None;
|
||||
const pagesPerSpread = currentSpreadMode !== SpreadMode.None ? 2 : 1;
|
||||
const metadataAspectRatio = getFirstPageAspectRatioFromStub(firstFileStub);
|
||||
|
||||
if (cancelled) {
|
||||
return;
|
||||
@@ -188,31 +165,21 @@ function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
};
|
||||
}, [
|
||||
zoom,
|
||||
zoomLevel,
|
||||
currentZoomLevel,
|
||||
zoomState,
|
||||
firstFileId,
|
||||
metadataAspectRatio,
|
||||
firstFileStub,
|
||||
requestFitWidth,
|
||||
autoZoomTick,
|
||||
spreadMode,
|
||||
triggerImmediateZoomUpdate,
|
||||
]);
|
||||
|
||||
// Subscribe to zoom changes - use ref to avoid re-subscribing when zoom reference changes
|
||||
const zoomSubscriptionRef = useRef<(() => void) | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Cleanup previous subscription if any
|
||||
if (zoomSubscriptionRef.current) {
|
||||
zoomSubscriptionRef.current();
|
||||
zoomSubscriptionRef.current = null;
|
||||
}
|
||||
|
||||
if (!zoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
zoomSubscriptionRef.current = zoom.onZoomChange((event: { newZoom?: number }) => {
|
||||
const unsubscribe = zoom.onZoomChange((event: { newZoom?: number }) => {
|
||||
if (typeof event?.newZoom !== 'number') {
|
||||
return;
|
||||
}
|
||||
@@ -221,25 +188,17 @@ function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (zoomSubscriptionRef.current) {
|
||||
zoomSubscriptionRef.current();
|
||||
zoomSubscriptionRef.current = null;
|
||||
}
|
||||
unsubscribe();
|
||||
};
|
||||
}, [zoom, triggerImmediateZoomUpdate]);
|
||||
|
||||
// Extract primitive values to avoid dependency on object reference
|
||||
const zoomStateCurrentZoomLevel = zoomState?.currentZoomLevel;
|
||||
|
||||
// Register bridge - only re-run when actual values change
|
||||
useEffect(() => {
|
||||
const currentZoom = zoomRef.current;
|
||||
if (!currentZoom || zoomStateCurrentZoomLevel === undefined) {
|
||||
if (!zoom || !zoomState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentZoomLevel =
|
||||
lastAppliedZoom.current ?? zoomStateCurrentZoomLevel ?? 1;
|
||||
lastAppliedZoom.current ?? zoomState.currentZoomLevel ?? 1;
|
||||
|
||||
const newState = {
|
||||
currentZoom: currentZoomLevel,
|
||||
@@ -250,9 +209,9 @@ function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||
|
||||
registerBridge('zoom', {
|
||||
state: newState,
|
||||
api: currentZoom,
|
||||
api: zoom,
|
||||
});
|
||||
}, [zoomStateCurrentZoomLevel, registerBridge, triggerImmediateZoomUpdate]);
|
||||
}, [zoom, zoomState, registerBridge, triggerImmediateZoomUpdate]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { useActiveDocument } from '@app/components/viewer/ActiveDocumentContext';
|
||||
|
||||
/**
|
||||
* Hook to get the currently active document ID.
|
||||
* Uses a shared context to avoid multiple subscriptions.
|
||||
*/
|
||||
export function useActiveDocumentId(): string | null {
|
||||
return useActiveDocument();
|
||||
}
|
||||
@@ -38,8 +38,6 @@ import {
|
||||
ExportState,
|
||||
ThumbnailAPIWrapper,
|
||||
BookmarkState,
|
||||
DocumentPermissionsState,
|
||||
PdfPermissionFlag,
|
||||
} from '@app/contexts/viewer/viewerBridges';
|
||||
import { SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||
|
||||
@@ -114,8 +112,6 @@ interface ViewerContextType {
|
||||
getExportState: () => ExportState;
|
||||
getBookmarkState: () => BookmarkState;
|
||||
hasBookmarkSupport: () => boolean;
|
||||
getDocumentPermissions: () => DocumentPermissionsState;
|
||||
hasPermission: (flag: PdfPermissionFlag) => boolean;
|
||||
|
||||
// Immediate update callbacks
|
||||
registerImmediateZoomUpdate: (callback: (percent: number) => void) => () => void;
|
||||
@@ -312,34 +308,6 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
|
||||
const hasBookmarkSupport = () => Boolean(bridgeRefs.current.bookmark);
|
||||
|
||||
const getDocumentPermissions = (): DocumentPermissionsState => {
|
||||
return bridgeRefs.current.permissions?.state || {
|
||||
isEncrypted: false,
|
||||
isOwnerUnlocked: false,
|
||||
permissions: PdfPermissionFlag.AllowAll,
|
||||
canPrint: true,
|
||||
canModifyContents: true,
|
||||
canCopyContents: true,
|
||||
canModifyAnnotations: true,
|
||||
canFillForms: true,
|
||||
canExtractForAccessibility: true,
|
||||
canAssembleDocument: true,
|
||||
canPrintHighQuality: true,
|
||||
};
|
||||
};
|
||||
|
||||
const hasPermission = (flag: PdfPermissionFlag): boolean => {
|
||||
const api = bridgeRefs.current.permissions?.api;
|
||||
if (api?.hasPermission) {
|
||||
return api.hasPermission(flag);
|
||||
}
|
||||
// Default: allow all permissions - warn in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('[ViewerContext] Permissions API not available, defaulting to allow');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Action handlers - call APIs directly
|
||||
const {
|
||||
scrollActions,
|
||||
@@ -392,8 +360,6 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
getExportState,
|
||||
getBookmarkState,
|
||||
hasBookmarkSupport,
|
||||
getDocumentPermissions,
|
||||
hasPermission,
|
||||
|
||||
// Immediate updates
|
||||
registerImmediateZoomUpdate,
|
||||
|
||||
@@ -100,63 +100,32 @@ export function createViewerActions({
|
||||
scrollToPage: (page: number, behavior?: 'smooth' | 'instant') => {
|
||||
const api = registry.current.scroll?.api;
|
||||
if (api?.scrollToPage) {
|
||||
try {
|
||||
api.scrollToPage({ pageNumber: page, behavior: behavior || 'smooth' });
|
||||
} catch (error) {
|
||||
// Silently handle "Strategy not found" errors that occur during document transitions
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('[ScrollActions] scrollToPage failed (document may be transitioning):', error);
|
||||
}
|
||||
}
|
||||
api.scrollToPage({ pageNumber: page, behavior: behavior || 'smooth' });
|
||||
}
|
||||
},
|
||||
scrollToFirstPage: () => {
|
||||
const api = registry.current.scroll?.api;
|
||||
if (api?.scrollToPage) {
|
||||
try {
|
||||
api.scrollToPage({ pageNumber: 1 });
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('[ScrollActions] scrollToFirstPage failed:', error);
|
||||
}
|
||||
}
|
||||
api.scrollToPage({ pageNumber: 1 });
|
||||
}
|
||||
},
|
||||
scrollToPreviousPage: () => {
|
||||
const api = registry.current.scroll?.api;
|
||||
if (api?.scrollToPreviousPage) {
|
||||
try {
|
||||
api.scrollToPreviousPage();
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('[ScrollActions] scrollToPreviousPage failed:', error);
|
||||
}
|
||||
}
|
||||
api.scrollToPreviousPage();
|
||||
}
|
||||
},
|
||||
scrollToNextPage: () => {
|
||||
const api = registry.current.scroll?.api;
|
||||
if (api?.scrollToNextPage) {
|
||||
try {
|
||||
api.scrollToNextPage();
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('[ScrollActions] scrollToNextPage failed:', error);
|
||||
}
|
||||
}
|
||||
api.scrollToNextPage();
|
||||
}
|
||||
},
|
||||
scrollToLastPage: () => {
|
||||
const api = registry.current.scroll?.api;
|
||||
const state = getScrollState();
|
||||
if (api?.scrollToPage && state.totalPages > 0) {
|
||||
try {
|
||||
api.scrollToPage({ pageNumber: state.totalPages });
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('[ScrollActions] scrollToLastPage failed:', error);
|
||||
}
|
||||
}
|
||||
api.scrollToPage({ pageNumber: state.totalPages });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,38 +1,6 @@
|
||||
import { SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||
import { PdfBookmarkObject } from '@embedpdf/models';
|
||||
|
||||
export enum PdfPermissionFlag {
|
||||
Print = 0x0004,
|
||||
ModifyContents = 0x0008,
|
||||
CopyContents = 0x0010,
|
||||
ModifyAnnotations = 0x0020,
|
||||
FillForms = 0x0100,
|
||||
ExtractForAccessibility = 0x0200,
|
||||
AssembleDocument = 0x0400,
|
||||
PrintHighQuality = 0x0800,
|
||||
AllowAll = 0x0f3c,
|
||||
}
|
||||
|
||||
export interface DocumentPermissionsState {
|
||||
isEncrypted: boolean;
|
||||
isOwnerUnlocked: boolean;
|
||||
permissions: number;
|
||||
canPrint: boolean;
|
||||
canModifyContents: boolean;
|
||||
canCopyContents: boolean;
|
||||
canModifyAnnotations: boolean;
|
||||
canFillForms: boolean;
|
||||
canExtractForAccessibility: boolean;
|
||||
canAssembleDocument: boolean;
|
||||
canPrintHighQuality: boolean;
|
||||
}
|
||||
|
||||
export interface DocumentPermissionsAPIWrapper {
|
||||
hasPermission: (flag: PdfPermissionFlag) => boolean;
|
||||
hasAllPermissions: (flags: PdfPermissionFlag[]) => boolean;
|
||||
getEffectivePermission: (flag: PdfPermissionFlag) => boolean;
|
||||
}
|
||||
|
||||
export interface ScrollAPIWrapper {
|
||||
scrollToPage: (params: { pageNumber: number; behavior?: ScrollBehavior }) => void;
|
||||
scrollToPreviousPage: () => void;
|
||||
@@ -169,7 +137,6 @@ export interface BridgeStateMap {
|
||||
export: ExportState;
|
||||
bookmark: BookmarkState;
|
||||
print: unknown;
|
||||
permissions: DocumentPermissionsState;
|
||||
}
|
||||
|
||||
export interface BridgeApiMap {
|
||||
@@ -184,7 +151,6 @@ export interface BridgeApiMap {
|
||||
export: ExportAPIWrapper;
|
||||
bookmark: BookmarkAPIWrapper;
|
||||
print: PrintAPIWrapper;
|
||||
permissions: DocumentPermissionsAPIWrapper;
|
||||
}
|
||||
|
||||
export type BridgeKey = keyof BridgeStateMap;
|
||||
@@ -205,7 +171,6 @@ export const createBridgeRegistry = (): ViewerBridgeRegistry => ({
|
||||
export: null,
|
||||
bookmark: null,
|
||||
print: null,
|
||||
permissions: null,
|
||||
});
|
||||
|
||||
export function registerBridge<K extends BridgeKey>(
|
||||
|
||||
@@ -811,7 +811,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
|
||||
description: t("home.devFolderScanning.desc", "Link to automated folder scanning guide"),
|
||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Folder%20Scanning/",
|
||||
link: "https://docs.stirlingpdf.com/Configuration/Folder%20Scanning/",
|
||||
synonyms: getSynonyms(t, "devFolderScanning"),
|
||||
supportsAutomate: false,
|
||||
automationSettings: null
|
||||
@@ -823,7 +823,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
|
||||
description: t("home.devSsoGuide.desc", "Link to SSO guide"),
|
||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration",
|
||||
link: "https://docs.stirlingpdf.com/Configuration/Single%20Sign-On%20Configuration/",
|
||||
synonyms: getSynonyms(t, "devSsoGuide"),
|
||||
supportsAutomate: false,
|
||||
automationSettings: null
|
||||
|
||||
@@ -38,7 +38,7 @@ const FREE_LICENSE_INFO: LicenseInfo = {
|
||||
|
||||
const BASE_NO_LOGIN_CONFIG: AppConfig = {
|
||||
enableAnalytics: true,
|
||||
appVersion: '2.4.1',
|
||||
appVersion: '2.4.0',
|
||||
serverCertificateEnabled: false,
|
||||
enableAlphaFunctionality: false,
|
||||
serverPort: 8080,
|
||||
|
||||
@@ -270,11 +270,7 @@ export function useAnnotationSelection({
|
||||
// internal selection state (e.g., accessing `selectedUid` on
|
||||
// an undefined object). Treat this as "no current selection"
|
||||
// instead of crashing the annotations tool.
|
||||
// Only log unexpected errors - "No active document" is a common expected state during init
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
if (!errorMessage.includes('No active document')) {
|
||||
console.error('[useAnnotationSelection] getSelectedAnnotation failed:', error);
|
||||
}
|
||||
console.error('[useAnnotationSelection] getSelectedAnnotation failed:', error);
|
||||
ann = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,26 +8,16 @@ export const getSynonyms = (t: TFunction, toolId: string): string[] => {
|
||||
|
||||
// If the translation key doesn't exist or returns the key itself, return empty array
|
||||
if (!tags || tags === tagsKey) {
|
||||
console.warn(`[Tags] Missing tags for tool: ${toolId}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Split by comma and clean up the tags
|
||||
const cleanedTags = tags
|
||||
return tags
|
||||
.split(',')
|
||||
.map((tag: string) => tag.trim())
|
||||
.filter((tag: string) => tag.length > 0);
|
||||
|
||||
// Log the tags found for this tool
|
||||
if (cleanedTags.length > 0) {
|
||||
console.info(`[Tags] Tool "${toolId}" has ${cleanedTags.length} tags:`, cleanedTags);
|
||||
} else {
|
||||
console.warn(`[Tags] Tool "${toolId}" has empty tags value`);
|
||||
}
|
||||
|
||||
return cleanedTags;
|
||||
} catch (error) {
|
||||
console.error(`[Tags] Failed to get translated synonyms for tool ${toolId}:`, error);
|
||||
console.warn(`Failed to get translated synonyms for tool ${toolId}:`, error);
|
||||
return [];
|
||||
}};
|
||||
|
||||
|
||||
@@ -69,28 +69,11 @@ export const ServerSelection: React.FC<ServerSelectionProps> = ({ onSelect, load
|
||||
|
||||
console.log('[ServerSelection] ✅ Connection test successful');
|
||||
|
||||
// Check if the connection requires accepting invalid certificates
|
||||
const allowInvalidCerts = testResult.requiresInvalidCertAcceptance || false;
|
||||
if (allowInvalidCerts) {
|
||||
console.warn('[ServerSelection] ⚠️ Server uses self-signed/invalid certificate - will accept for all requests');
|
||||
}
|
||||
|
||||
// Fetch OAuth providers and check if login is enabled
|
||||
const enabledProviders: SSOProviderConfig[] = [];
|
||||
try {
|
||||
console.log('[ServerSelection] Fetching login configuration...');
|
||||
|
||||
// Prepare fetch options with danger mode if needed
|
||||
const fetchOptions: any = {};
|
||||
if (allowInvalidCerts) {
|
||||
fetchOptions.danger = {
|
||||
acceptInvalidCerts: true,
|
||||
acceptInvalidHostnames: true,
|
||||
};
|
||||
console.debug('[ServerSelection] Using danger mode for login config fetch');
|
||||
}
|
||||
|
||||
const response = await fetch(`${url}/api/v1/proprietary/ui-data/login`, fetchOptions);
|
||||
const response = await fetch(`${url}/api/v1/proprietary/ui-data/login`);
|
||||
|
||||
// Check if security is disabled (status 403, 401, or 404 - endpoint doesn't exist)
|
||||
if (!response.ok) {
|
||||
@@ -164,12 +147,11 @@ export const ServerSelection: React.FC<ServerSelectionProps> = ({ onSelect, load
|
||||
return;
|
||||
}
|
||||
|
||||
// Connection successful - pass URL, OAuth providers, and cert settings
|
||||
// Connection successful - pass URL and OAuth providers
|
||||
console.log('[ServerSelection] ✅ Server selection complete, proceeding to login');
|
||||
onSelect({
|
||||
url,
|
||||
enabledOAuthProviders: enabledProviders.length > 0 ? enabledProviders : undefined,
|
||||
allow_invalid_certs: allowInvalidCerts,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[ServerSelection] ❌ Unexpected error during connection test:', error);
|
||||
|
||||
@@ -45,8 +45,6 @@ export function setupApiInterceptors(client: AxiosInstance): void {
|
||||
extendedConfig.url = `${baseUrl}${extendedConfig.url}`;
|
||||
}
|
||||
|
||||
localStorage.setItem('server_url', baseUrl);
|
||||
|
||||
// Debug logging
|
||||
console.debug(`[apiClientSetup] Request to: ${extendedConfig.url}`);
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ export interface SSOProviderConfig {
|
||||
export interface ServerConfig {
|
||||
url: string;
|
||||
enabledOAuthProviders?: SSOProviderConfig[];
|
||||
allow_invalid_certs?: boolean;
|
||||
}
|
||||
|
||||
export interface ConnectionConfig {
|
||||
@@ -32,7 +31,6 @@ export interface ConnectionTestResult {
|
||||
error?: string;
|
||||
errorCode?: string;
|
||||
diagnostics?: DiagnosticResult[];
|
||||
requiresInvalidCertAcceptance?: boolean;
|
||||
}
|
||||
|
||||
export class ConnectionModeService {
|
||||
@@ -253,16 +251,11 @@ export class ConnectionModeService {
|
||||
diagnostics.push(stage2Result);
|
||||
|
||||
if (stage2Result.success) {
|
||||
console.log(`[ConnectionModeService] ⚠️ Certificate issue detected - but connection works with bypass enabled`);
|
||||
console.log(`[ConnectionModeService] ==================== DIAGNOSTIC SUMMARY ====================`);
|
||||
console.log(`[ConnectionModeService] ✅ CONNECTION SUCCESSFUL (with certificate bypass)`);
|
||||
console.log(`[ConnectionModeService] Protocol: HTTPS with certificate validation disabled`);
|
||||
console.log(`[ConnectionModeService] Duration: ${stage2Result.duration}ms`);
|
||||
console.log(`[ConnectionModeService] Note: Server has self-signed or invalid certificate - allowing connection`);
|
||||
console.log(`[ConnectionModeService] ==================== DIAGNOSTIC SESSION END ====================`);
|
||||
console.log(`[ConnectionModeService] ⚠️ Certificate issue detected - works without validation`);
|
||||
return {
|
||||
success: true,
|
||||
requiresInvalidCertAcceptance: true,
|
||||
success: false,
|
||||
error: 'SSL certificate validation failed. The server has an invalid, self-signed, or untrusted certificate.',
|
||||
errorCode: 'SSL_CERTIFICATE_INVALID',
|
||||
diagnostics,
|
||||
};
|
||||
}
|
||||
@@ -494,13 +487,7 @@ export class ConnectionModeService {
|
||||
|
||||
let detailedMessage = `Failed: ${errorMsg}`;
|
||||
|
||||
// Check for TLS version mismatch (TLS 1.0/1.1 not supported)
|
||||
if (errorLower.includes('peer is incompatible') ||
|
||||
errorLower.includes('protocol version') ||
|
||||
errorLower.includes('peerincompatible') ||
|
||||
(errorLower.includes('handshake') && (errorLower.includes('tls') || errorLower.includes('ssl')))) {
|
||||
detailedMessage = `TLS version not supported - Server appears to use TLS 1.0 or 1.1 (desktop app requires TLS 1.2+). Please upgrade your server's TLS configuration or use the web version.`;
|
||||
} else if (errorLower.includes('timeout') || errorLower.includes('timed out')) {
|
||||
if (errorLower.includes('timeout') || errorLower.includes('timed out')) {
|
||||
detailedMessage = `Timeout after ${duration}ms - server not responding`;
|
||||
} else if (errorLower.includes('certificate') || errorLower.includes('cert') || errorLower.includes('ssl') || errorLower.includes('tls')) {
|
||||
detailedMessage = `SSL/TLS error - ${errorMsg}`;
|
||||
|
||||
@@ -193,26 +193,12 @@ class TauriHttpClient {
|
||||
const credentials: RequestCredentials = finalConfig.withCredentials ? 'include' : 'omit';
|
||||
|
||||
// Make the request using Tauri's native HTTP client (standard Fetch API)
|
||||
// Enable certificate bypass for HTTPS to handle missing intermediate certs and self-signed certs
|
||||
const fetchOptions: any = {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
credentials,
|
||||
};
|
||||
|
||||
// Always enable dangerous settings for HTTPS to allow connections to servers with:
|
||||
// - Missing intermediate certificates
|
||||
// - Self-signed certificates
|
||||
// - Certificate hostname mismatches
|
||||
if (url.startsWith('https://')) {
|
||||
fetchOptions.danger = {
|
||||
acceptInvalidCerts: true,
|
||||
acceptInvalidHostnames: true,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await fetch(url, fetchOptions);
|
||||
});
|
||||
|
||||
// Parse response based on responseType
|
||||
let data: T;
|
||||
|
||||
-23
@@ -16,7 +16,6 @@ interface SecuritySettingsData {
|
||||
loginMethod?: string;
|
||||
loginAttemptCount?: number;
|
||||
loginResetTimeMinutes?: number;
|
||||
xFrameOptions?: string;
|
||||
jwt?: {
|
||||
persistence?: boolean;
|
||||
enableKeyRotation?: boolean;
|
||||
@@ -126,7 +125,6 @@ export default function AdminSecuritySection() {
|
||||
'security.loginMethod': securitySettings.loginMethod,
|
||||
'security.loginAttemptCount': securitySettings.loginAttemptCount,
|
||||
'security.loginResetTimeMinutes': securitySettings.loginResetTimeMinutes,
|
||||
'security.xFrameOptions': securitySettings.xFrameOptions,
|
||||
// JWT settings
|
||||
'security.jwt.persistence': securitySettings.jwt?.persistence,
|
||||
'security.jwt.enableKeyRotation': securitySettings.jwt?.enableKeyRotation,
|
||||
@@ -282,27 +280,6 @@ export default function AdminSecuritySection() {
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Select
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.security.xFrameOptions.label', 'X-Frame-Options')}</span>
|
||||
<PendingBadge show={isFieldPending('xFrameOptions')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.security.xFrameOptions.description', 'Controls whether the application can be embedded in iframes')}
|
||||
value={settings?.xFrameOptions || 'DENY'}
|
||||
onChange={(value) => setSettings({ ...settings, xFrameOptions: value || 'DENY' })}
|
||||
data={[
|
||||
{ value: 'DENY', label: t('admin.settings.security.xFrameOptions.deny', 'Deny (Prevents all framing)') },
|
||||
{ value: 'SAMEORIGIN', label: t('admin.settings.security.xFrameOptions.sameorigin', 'Same Origin (Allow framing from same domain)') },
|
||||
{ value: 'DISABLED', label: t('admin.settings.security.xFrameOptions.disabled', 'Disabled (No X-Frame-Options header)') },
|
||||
]}
|
||||
comboboxProps={{ zIndex: 1400 }}
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ const FREE_LICENSE_INFO: LicenseInfo = {
|
||||
|
||||
const BASE_NO_LOGIN_CONFIG: AppConfig = {
|
||||
enableAnalytics: true,
|
||||
appVersion: '2.4.1',
|
||||
appVersion: '2.4.0',
|
||||
serverCertificateEnabled: false,
|
||||
enableAlphaFunctionality: false,
|
||||
enableDesktopInstallSlide: true,
|
||||
|
||||
@@ -291,9 +291,9 @@ pycryptodome==3.23.0 \
|
||||
--hash=sha256:e3f2d0aaf8080bda0587d58fc9fe4766e012441e2eed4269a77de6aea981c8be \
|
||||
--hash=sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7
|
||||
# via -r requirements.in
|
||||
pypdf==6.6.2 \
|
||||
--hash=sha256:0a3ea3b3303982333404e22d8f75d7b3144f9cf4b2970b96856391a516f9f016 \
|
||||
--hash=sha256:44c0c9811cfb3b83b28f1c3d054531d5b8b81abaedee0d8cb403650d023832ba
|
||||
pypdf==6.6.0 \
|
||||
--hash=sha256:4c887ef2ea38d86faded61141995a3c7d068c9d6ae8477be7ae5de8a8e16592f \
|
||||
--hash=sha256:bca9091ef6de36c7b1a81e09327c554b7ce51e88dad68f5890c2b4a4417f1fd7
|
||||
# via -r requirements.in
|
||||
reportlab==4.4.9 \
|
||||
--hash=sha256:68e2d103ae8041a37714e8896ec9b79a1c1e911d68c3bd2ea17546568cf17bfd \
|
||||
|
||||
Reference in New Issue
Block a user