mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-22 04:09:22 +00:00
Merge branch 'main' into org
This commit is contained in:
commit
877caee396
@ -49,7 +49,7 @@
|
|||||||
"java.configuration.updateBuildConfiguration": "interactive",
|
"java.configuration.updateBuildConfiguration": "interactive",
|
||||||
"java.format.enabled": true,
|
"java.format.enabled": true,
|
||||||
"java.format.settings.profile": "GoogleStyle",
|
"java.format.settings.profile": "GoogleStyle",
|
||||||
"java.format.settings.google.version": "1.26.0",
|
"java.format.settings.google.version": "1.28.0",
|
||||||
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
|
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
|
||||||
"java.saveActions.cleanup": true,
|
"java.saveActions.cleanup": true,
|
||||||
"java.cleanup.actions": [
|
"java.cleanup.actions": [
|
||||||
@ -79,9 +79,17 @@
|
|||||||
".venv*/",
|
".venv*/",
|
||||||
".vscode/",
|
".vscode/",
|
||||||
"bin/",
|
"bin/",
|
||||||
|
"app/core/bin/",
|
||||||
|
"app/common/bin/",
|
||||||
|
"app/proprietary/bin/",
|
||||||
"build/",
|
"build/",
|
||||||
|
"app/core/build/",
|
||||||
|
"app/common/build/",
|
||||||
|
"app/proprietary/build/",
|
||||||
"configs/",
|
"configs/",
|
||||||
|
"app/core/configs/",
|
||||||
"customFiles/",
|
"customFiles/",
|
||||||
|
"app/core/customFiles/",
|
||||||
"docs/",
|
"docs/",
|
||||||
"exampleYmlFiles",
|
"exampleYmlFiles",
|
||||||
"gradle/",
|
"gradle/",
|
||||||
@ -93,6 +101,9 @@
|
|||||||
".git-blame-ignore-revs",
|
".git-blame-ignore-revs",
|
||||||
".gitattributes",
|
".gitattributes",
|
||||||
".gitignore",
|
".gitignore",
|
||||||
|
"app/core/.gitignore",
|
||||||
|
"app/common/.gitignore",
|
||||||
|
"app/proprietary/.gitignore",
|
||||||
".pre-commit-config.yaml"
|
".pre-commit-config.yaml"
|
||||||
],
|
],
|
||||||
"java.signatureHelp.enabled": true,
|
"java.signatureHelp.enabled": true,
|
||||||
|
@ -31,18 +31,12 @@ indent_size = 2
|
|||||||
# CSS files typically use an indent size of 2 spaces for better readability and alignment with community standards.
|
# CSS files typically use an indent size of 2 spaces for better readability and alignment with community standards.
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.yaml]
|
[*.{yml,yaml}]
|
||||||
# YAML files use an indent size of 2 spaces to maintain consistency with common YAML formatting practices.
|
# YAML files use an indent size of 2 spaces to maintain consistency with common YAML formatting practices.
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[*.yml]
|
|
||||||
# YML files follow the same conventions as YAML files, using an indent size of 2 spaces.
|
|
||||||
indent_size = 2
|
|
||||||
insert_final_newline = false
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
[*.json]
|
[*.json]
|
||||||
# JSON files use an indent size of 2 spaces, which is the standard for JSON formatting.
|
# JSON files use an indent size of 2 spaces, which is the standard for JSON formatting.
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
23
.github/CODEOWNERS
vendored
23
.github/CODEOWNERS
vendored
@ -1,2 +1,21 @@
|
|||||||
# All PRs to V1 must be approved by Frooodle
|
# All PRs must be approved by Frooodle or Ludy87
|
||||||
* @Frooodle @reecebrowne @Ludy87 @DarioGii @ConnorYoh @EthanHealy01
|
* @Frooodle @Ludy87 @jbrunton96 @ConnorYoh
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
/app/** @DarioGii @Frooodle @Ludy87 @jbrunton96 @ConnorYoh
|
||||||
|
|
||||||
|
#V1 frontend
|
||||||
|
/app/core/src/main/resources/static/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle @Ludy87
|
||||||
|
/app/core/src/main/resources/templates/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle @Ludy87
|
||||||
|
|
||||||
|
#V2 frontend
|
||||||
|
/frontend/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle
|
||||||
|
|
||||||
|
#V2 docker
|
||||||
|
/docker/backend/** @Frooodle @Ludy87 @DarioGii @Ludy87
|
||||||
|
/docker/frontend/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle @Ludy87
|
||||||
|
/docker/compose/** @reecebrowne @ConnorYoh @EthanHealy01 @DarioGii @jbrunton96 @Frooodle @Ludy87
|
||||||
|
|
||||||
|
|
||||||
|
#GHA (All users)
|
||||||
|
/.github/** @reecebrowne @ConnorYoh @EthanHealy01 @DarioGii @jbrunton96 @Frooodle @Ludy87
|
||||||
|
2
.github/config/.files.yaml
vendored
2
.github/config/.files.yaml
vendored
@ -27,3 +27,5 @@ project: &project
|
|||||||
- gradlew.bat
|
- gradlew.bat
|
||||||
- launch4jConfig.xml
|
- launch4jConfig.xml
|
||||||
- settings.gradle
|
- settings.gradle
|
||||||
|
- frontend/**
|
||||||
|
- docker/**
|
||||||
|
5
.github/labels.yml
vendored
5
.github/labels.yml
vendored
@ -42,6 +42,7 @@
|
|||||||
- name: "Front End"
|
- name: "Front End"
|
||||||
color: "BBD2F1"
|
color: "BBD2F1"
|
||||||
description: "Issues or pull requests related to front-end development"
|
description: "Issues or pull requests related to front-end development"
|
||||||
|
from_name: "frontend"
|
||||||
- name: "github-actions"
|
- name: "github-actions"
|
||||||
description: "Pull requests that update GitHub Actions code"
|
description: "Pull requests that update GitHub Actions code"
|
||||||
color: "999999"
|
color: "999999"
|
||||||
@ -77,6 +78,7 @@
|
|||||||
- name: "Translation"
|
- name: "Translation"
|
||||||
color: "9FABF9"
|
color: "9FABF9"
|
||||||
from_name: "translation"
|
from_name: "translation"
|
||||||
|
description: "Issues or pull requests related to translation"
|
||||||
- name: "upstream"
|
- name: "upstream"
|
||||||
color: "DEDEDE"
|
color: "DEDEDE"
|
||||||
- name: "v2"
|
- name: "v2"
|
||||||
@ -178,3 +180,6 @@
|
|||||||
- name: "pr-deployed"
|
- name: "pr-deployed"
|
||||||
color: "00FF00"
|
color: "00FF00"
|
||||||
description: "Pull request has been deployed to a test environment"
|
description: "Pull request has been deployed to a test environment"
|
||||||
|
- name: "codex"
|
||||||
|
color: "ededed"
|
||||||
|
description: "chatgpt AI generated code"
|
||||||
|
@ -196,7 +196,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_HUB_API }}
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
2
.github/workflows/ai_pr_title_review.yml
vendored
2
.github/workflows/ai_pr_title_review.yml
vendored
@ -87,7 +87,7 @@ jobs:
|
|||||||
- name: AI PR Title Analysis
|
- name: AI PR Title Analysis
|
||||||
if: steps.actor.outputs.is_repo_dev == 'true'
|
if: steps.actor.outputs.is_repo_dev == 'true'
|
||||||
id: ai-title-analysis
|
id: ai-title-analysis
|
||||||
uses: actions/ai-inference@9693b137b6566bb66055a713613bf4f0493701eb # v1.2.3
|
uses: actions/ai-inference@0cbed4a10641c75090de5968e66d70eb4660f751 # v1.2.7
|
||||||
with:
|
with:
|
||||||
model: openai/gpt-4o
|
model: openai/gpt-4o
|
||||||
system-prompt-file: ".github/config/system-prompt.txt"
|
system-prompt-file: ".github/config/system-prompt.txt"
|
||||||
|
4
.github/workflows/push-docker.yml
vendored
4
.github/workflows/push-docker.yml
vendored
@ -67,13 +67,13 @@ jobs:
|
|||||||
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_HUB_API }}
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
2
.github/workflows/testdriver.yml
vendored
2
.github/workflows/testdriver.yml
vendored
@ -57,7 +57,7 @@ jobs:
|
|||||||
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_HUB_API }}
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -2,6 +2,7 @@
|
|||||||
"editor.wordSegmenterLocales": "",
|
"editor.wordSegmenterLocales": "",
|
||||||
"editor.guides.bracketPairs": "active",
|
"editor.guides.bracketPairs": "active",
|
||||||
"editor.guides.bracketPairsHorizontal": "active",
|
"editor.guides.bracketPairsHorizontal": "active",
|
||||||
|
"editor.defaultFormatter": "EditorConfig.EditorConfig",
|
||||||
"cSpell.enabled": false,
|
"cSpell.enabled": false,
|
||||||
"[feature]": {
|
"[feature]": {
|
||||||
"editor.defaultFormatter": "alexkrechik.cucumberautocomplete"
|
"editor.defaultFormatter": "alexkrechik.cucumberautocomplete"
|
||||||
@ -40,7 +41,7 @@
|
|||||||
"java.configuration.updateBuildConfiguration": "interactive",
|
"java.configuration.updateBuildConfiguration": "interactive",
|
||||||
"java.format.enabled": true,
|
"java.format.enabled": true,
|
||||||
"java.format.settings.profile": "GoogleStyle",
|
"java.format.settings.profile": "GoogleStyle",
|
||||||
"java.format.settings.google.version": "1.27.0",
|
"java.format.settings.google.version": "1.28.0",
|
||||||
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
|
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
|
||||||
// (DE) Aktiviert Kommentare im Java-Format.
|
// (DE) Aktiviert Kommentare im Java-Format.
|
||||||
// (EN) Enables comments in Java formatting.
|
// (EN) Enables comments in Java formatting.
|
||||||
|
74
README.md
74
README.md
@ -116,47 +116,47 @@ Stirling-PDF currently supports 40 languages!
|
|||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| -------------------------------------------- | -------------------------------------- |
|
| -------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Persian (فارسی) (fa_IR) |  |
|
| Persian (فارسی) (fa_IR) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Slovenian (Slovenščina) (sl_SI) |  |
|
| Slovenian (Slovenščina) (sl_SI) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Tibetan (བོད་ཡིག་) (bo_CN) |  |
|
| Tibetan (བོད་ཡིག་) (bo_CN) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
| Malayalam (മലയാളം) (ml_IN) |  |
|
| Malayalam (മലയാളം) (ml_IN) |  |
|
||||||
|
|
||||||
## Stirling PDF Enterprise
|
## Stirling PDF Enterprise
|
||||||
|
|
||||||
|
@ -197,7 +197,7 @@ public class ApplicationProperties {
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public InputStream getIdpMetadataUri() throws IOException {
|
public InputStream getIdpMetadataUri() throws IOException {
|
||||||
if (idpMetadataUri.startsWith("classpath:")) {
|
if (idpMetadataUri.startsWith("classpath:")) {
|
||||||
return new ClassPathResource(idpMetadataUri.substring("classpath".length()))
|
return new ClassPathResource(idpMetadataUri.substring("classpath:".length()))
|
||||||
.getInputStream();
|
.getInputStream();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -233,6 +233,7 @@ public class ApplicationProperties {
|
|||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public Resource getPrivateKey() {
|
public Resource getPrivateKey() {
|
||||||
|
if (privateKey == null) return null;
|
||||||
if (privateKey.startsWith("classpath:")) {
|
if (privateKey.startsWith("classpath:")) {
|
||||||
return new ClassPathResource(privateKey.substring("classpath:".length()));
|
return new ClassPathResource(privateKey.substring("classpath:".length()));
|
||||||
} else {
|
} else {
|
||||||
@ -311,6 +312,7 @@ public class ApplicationProperties {
|
|||||||
private Boolean enableAnalytics;
|
private Boolean enableAnalytics;
|
||||||
private Datasource datasource;
|
private Datasource datasource;
|
||||||
private Boolean disableSanitize;
|
private Boolean disableSanitize;
|
||||||
|
private int maxDPI;
|
||||||
private Boolean enableUrlToPDF;
|
private Boolean enableUrlToPDF;
|
||||||
private Html html = new Html();
|
private Html html = new Html();
|
||||||
private CustomPaths customPaths = new CustomPaths();
|
private CustomPaths customPaths = new CustomPaths();
|
||||||
|
@ -0,0 +1,652 @@
|
|||||||
|
package stirling.software.common.util;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import stirling.software.common.model.api.converters.EmlToPdfRequest;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class EmlParser {
|
||||||
|
|
||||||
|
private static volatile Boolean jakartaMailAvailable = null;
|
||||||
|
private static volatile Method mimeUtilityDecodeTextMethod = null;
|
||||||
|
private static volatile boolean mimeUtilityChecked = false;
|
||||||
|
|
||||||
|
private static final Pattern MIME_ENCODED_PATTERN =
|
||||||
|
Pattern.compile("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
|
||||||
|
|
||||||
|
private static final String DISPOSITION_ATTACHMENT = "attachment";
|
||||||
|
private static final String TEXT_PLAIN = "text/plain";
|
||||||
|
private static final String TEXT_HTML = "text/html";
|
||||||
|
private static final String MULTIPART_PREFIX = "multipart/";
|
||||||
|
|
||||||
|
private static final String HEADER_CONTENT_TYPE = "content-type:";
|
||||||
|
private static final String HEADER_CONTENT_DISPOSITION = "content-disposition:";
|
||||||
|
private static final String HEADER_CONTENT_TRANSFER_ENCODING = "content-transfer-encoding:";
|
||||||
|
private static final String HEADER_CONTENT_ID = "Content-ID";
|
||||||
|
private static final String HEADER_SUBJECT = "Subject:";
|
||||||
|
private static final String HEADER_FROM = "From:";
|
||||||
|
private static final String HEADER_TO = "To:";
|
||||||
|
private static final String HEADER_CC = "Cc:";
|
||||||
|
private static final String HEADER_BCC = "Bcc:";
|
||||||
|
private static final String HEADER_DATE = "Date:";
|
||||||
|
|
||||||
|
private static synchronized boolean isJakartaMailAvailable() {
|
||||||
|
if (jakartaMailAvailable == null) {
|
||||||
|
try {
|
||||||
|
Class.forName("jakarta.mail.internet.MimeMessage");
|
||||||
|
Class.forName("jakarta.mail.Session");
|
||||||
|
Class.forName("jakarta.mail.internet.MimeUtility");
|
||||||
|
Class.forName("jakarta.mail.internet.MimePart");
|
||||||
|
Class.forName("jakarta.mail.internet.MimeMultipart");
|
||||||
|
Class.forName("jakarta.mail.Multipart");
|
||||||
|
Class.forName("jakarta.mail.Part");
|
||||||
|
jakartaMailAvailable = true;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
jakartaMailAvailable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jakartaMailAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EmailContent extractEmailContent(
|
||||||
|
byte[] emlBytes, EmlToPdfRequest request, CustomHtmlSanitizer customHtmlSanitizer)
|
||||||
|
throws IOException {
|
||||||
|
EmlProcessingUtils.validateEmlInput(emlBytes);
|
||||||
|
|
||||||
|
if (isJakartaMailAvailable()) {
|
||||||
|
return extractEmailContentAdvanced(emlBytes, request, customHtmlSanitizer);
|
||||||
|
} else {
|
||||||
|
return extractEmailContentBasic(emlBytes, request, customHtmlSanitizer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EmailContent extractEmailContentBasic(
|
||||||
|
byte[] emlBytes, EmlToPdfRequest request, CustomHtmlSanitizer customHtmlSanitizer) {
|
||||||
|
String emlContent = new String(emlBytes, StandardCharsets.UTF_8);
|
||||||
|
EmailContent content = new EmailContent();
|
||||||
|
|
||||||
|
content.setSubject(extractBasicHeader(emlContent, HEADER_SUBJECT));
|
||||||
|
content.setFrom(extractBasicHeader(emlContent, HEADER_FROM));
|
||||||
|
content.setTo(extractBasicHeader(emlContent, HEADER_TO));
|
||||||
|
content.setCc(extractBasicHeader(emlContent, HEADER_CC));
|
||||||
|
content.setBcc(extractBasicHeader(emlContent, HEADER_BCC));
|
||||||
|
|
||||||
|
String dateStr = extractBasicHeader(emlContent, HEADER_DATE);
|
||||||
|
if (!dateStr.isEmpty()) {
|
||||||
|
content.setDateString(dateStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
String htmlBody = extractHtmlBody(emlContent);
|
||||||
|
if (htmlBody != null) {
|
||||||
|
content.setHtmlBody(htmlBody);
|
||||||
|
} else {
|
||||||
|
String textBody = extractTextBody(emlContent);
|
||||||
|
content.setTextBody(textBody != null ? textBody : "Email content could not be parsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
content.getAttachments().addAll(extractAttachmentsBasic(emlContent));
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EmailContent extractEmailContentAdvanced(
|
||||||
|
byte[] emlBytes, EmlToPdfRequest request, CustomHtmlSanitizer customHtmlSanitizer) {
|
||||||
|
try {
|
||||||
|
Class<?> sessionClass = Class.forName("jakarta.mail.Session");
|
||||||
|
Class<?> mimeMessageClass = Class.forName("jakarta.mail.internet.MimeMessage");
|
||||||
|
|
||||||
|
Method getDefaultInstance =
|
||||||
|
sessionClass.getMethod("getDefaultInstance", Properties.class);
|
||||||
|
Object session = getDefaultInstance.invoke(null, new Properties());
|
||||||
|
|
||||||
|
Class<?>[] constructorArgs = new Class<?>[] {sessionClass, InputStream.class};
|
||||||
|
Constructor<?> mimeMessageConstructor =
|
||||||
|
mimeMessageClass.getConstructor(constructorArgs);
|
||||||
|
Object message =
|
||||||
|
mimeMessageConstructor.newInstance(session, new ByteArrayInputStream(emlBytes));
|
||||||
|
|
||||||
|
return extractFromMimeMessage(message, request, customHtmlSanitizer);
|
||||||
|
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
return extractEmailContentBasic(emlBytes, request, customHtmlSanitizer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EmailContent extractFromMimeMessage(
|
||||||
|
Object message, EmlToPdfRequest request, CustomHtmlSanitizer customHtmlSanitizer) {
|
||||||
|
EmailContent content = new EmailContent();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> messageClass = message.getClass();
|
||||||
|
|
||||||
|
Method getSubject = messageClass.getMethod("getSubject");
|
||||||
|
String subject = (String) getSubject.invoke(message);
|
||||||
|
content.setSubject(subject != null ? safeMimeDecode(subject) : "No Subject");
|
||||||
|
|
||||||
|
Method getFrom = messageClass.getMethod("getFrom");
|
||||||
|
Object[] fromAddresses = (Object[]) getFrom.invoke(message);
|
||||||
|
content.setFrom(buildAddressString(fromAddresses));
|
||||||
|
|
||||||
|
extractRecipients(message, messageClass, content);
|
||||||
|
|
||||||
|
Method getSentDate = messageClass.getMethod("getSentDate");
|
||||||
|
content.setDate((Date) getSentDate.invoke(message));
|
||||||
|
|
||||||
|
Method getContent = messageClass.getMethod("getContent");
|
||||||
|
Object messageContent = getContent.invoke(message);
|
||||||
|
|
||||||
|
processMessageContent(message, messageContent, content, request, customHtmlSanitizer);
|
||||||
|
|
||||||
|
} catch (ReflectiveOperationException | RuntimeException e) {
|
||||||
|
content.setSubject("Email Conversion");
|
||||||
|
content.setFrom("Unknown");
|
||||||
|
content.setTo("Unknown");
|
||||||
|
content.setCc("");
|
||||||
|
content.setBcc("");
|
||||||
|
content.setTextBody("Email content could not be parsed with advanced processing");
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void extractRecipients(
|
||||||
|
Object message, Class<?> messageClass, EmailContent content) {
|
||||||
|
try {
|
||||||
|
Method getRecipients =
|
||||||
|
messageClass.getMethod(
|
||||||
|
"getRecipients", Class.forName("jakarta.mail.Message$RecipientType"));
|
||||||
|
Class<?> recipientTypeClass = Class.forName("jakarta.mail.Message$RecipientType");
|
||||||
|
|
||||||
|
Object toType = recipientTypeClass.getField("TO").get(null);
|
||||||
|
Object[] toRecipients = (Object[]) getRecipients.invoke(message, toType);
|
||||||
|
content.setTo(buildAddressString(toRecipients));
|
||||||
|
|
||||||
|
Object ccType = recipientTypeClass.getField("CC").get(null);
|
||||||
|
Object[] ccRecipients = (Object[]) getRecipients.invoke(message, ccType);
|
||||||
|
content.setCc(buildAddressString(ccRecipients));
|
||||||
|
|
||||||
|
Object bccType = recipientTypeClass.getField("BCC").get(null);
|
||||||
|
Object[] bccRecipients = (Object[]) getRecipients.invoke(message, bccType);
|
||||||
|
content.setBcc(buildAddressString(bccRecipients));
|
||||||
|
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
try {
|
||||||
|
Method getAllRecipients = messageClass.getMethod("getAllRecipients");
|
||||||
|
Object[] recipients = (Object[]) getAllRecipients.invoke(message);
|
||||||
|
content.setTo(buildAddressString(recipients));
|
||||||
|
content.setCc("");
|
||||||
|
content.setBcc("");
|
||||||
|
} catch (ReflectiveOperationException ex) {
|
||||||
|
content.setTo("");
|
||||||
|
content.setCc("");
|
||||||
|
content.setBcc("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildAddressString(Object[] addresses) {
|
||||||
|
if (addresses == null || addresses.length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (int i = 0; i < addresses.length; i++) {
|
||||||
|
if (i > 0) builder.append(", ");
|
||||||
|
builder.append(safeMimeDecode(addresses[i].toString()));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processMessageContent(
|
||||||
|
Object message,
|
||||||
|
Object messageContent,
|
||||||
|
EmailContent content,
|
||||||
|
EmlToPdfRequest request,
|
||||||
|
CustomHtmlSanitizer customHtmlSanitizer) {
|
||||||
|
try {
|
||||||
|
if (messageContent instanceof String stringContent) {
|
||||||
|
Method getContentType = message.getClass().getMethod("getContentType");
|
||||||
|
String contentType = (String) getContentType.invoke(message);
|
||||||
|
|
||||||
|
if (contentType != null && contentType.toLowerCase().contains(TEXT_HTML)) {
|
||||||
|
content.setHtmlBody(stringContent);
|
||||||
|
} else {
|
||||||
|
content.setTextBody(stringContent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Class<?> multipartClass = Class.forName("jakarta.mail.Multipart");
|
||||||
|
if (multipartClass.isInstance(messageContent)) {
|
||||||
|
processMultipart(messageContent, content, request, customHtmlSanitizer, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ReflectiveOperationException | ClassCastException e) {
|
||||||
|
content.setTextBody("Email content could not be parsed with advanced processing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processMultipart(
|
||||||
|
Object multipart,
|
||||||
|
EmailContent content,
|
||||||
|
EmlToPdfRequest request,
|
||||||
|
CustomHtmlSanitizer customHtmlSanitizer,
|
||||||
|
int depth) {
|
||||||
|
|
||||||
|
final int MAX_MULTIPART_DEPTH = 10;
|
||||||
|
if (depth > MAX_MULTIPART_DEPTH) {
|
||||||
|
content.setHtmlBody("<div class=\"error\">Maximum multipart depth exceeded</div>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> multipartClass = multipart.getClass();
|
||||||
|
Method getCount = multipartClass.getMethod("getCount");
|
||||||
|
int count = (Integer) getCount.invoke(multipart);
|
||||||
|
|
||||||
|
Method getBodyPart = multipartClass.getMethod("getBodyPart", int.class);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
Object part = getBodyPart.invoke(multipart, i);
|
||||||
|
processPart(part, content, request, customHtmlSanitizer, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (ReflectiveOperationException | ClassCastException e) {
|
||||||
|
content.setHtmlBody("<div class=\"error\">Error processing multipart content</div>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processPart(
|
||||||
|
Object part,
|
||||||
|
EmailContent content,
|
||||||
|
EmlToPdfRequest request,
|
||||||
|
CustomHtmlSanitizer customHtmlSanitizer,
|
||||||
|
int depth) {
|
||||||
|
try {
|
||||||
|
Class<?> partClass = part.getClass();
|
||||||
|
|
||||||
|
Method isMimeType = partClass.getMethod("isMimeType", String.class);
|
||||||
|
Method getContent = partClass.getMethod("getContent");
|
||||||
|
Method getDisposition = partClass.getMethod("getDisposition");
|
||||||
|
Method getFileName = partClass.getMethod("getFileName");
|
||||||
|
Method getContentType = partClass.getMethod("getContentType");
|
||||||
|
Method getHeader = partClass.getMethod("getHeader", String.class);
|
||||||
|
|
||||||
|
Object disposition = getDisposition.invoke(part);
|
||||||
|
String filename = (String) getFileName.invoke(part);
|
||||||
|
String contentType = (String) getContentType.invoke(part);
|
||||||
|
|
||||||
|
String normalizedDisposition =
|
||||||
|
disposition != null ? ((String) disposition).toLowerCase() : null;
|
||||||
|
|
||||||
|
if ((Boolean) isMimeType.invoke(part, TEXT_PLAIN) && normalizedDisposition == null) {
|
||||||
|
Object partContent = getContent.invoke(part);
|
||||||
|
if (partContent instanceof String stringContent) {
|
||||||
|
content.setTextBody(stringContent);
|
||||||
|
}
|
||||||
|
} else if ((Boolean) isMimeType.invoke(part, TEXT_HTML)
|
||||||
|
&& normalizedDisposition == null) {
|
||||||
|
Object partContent = getContent.invoke(part);
|
||||||
|
if (partContent instanceof String stringContent) {
|
||||||
|
String htmlBody =
|
||||||
|
customHtmlSanitizer != null
|
||||||
|
? customHtmlSanitizer.sanitize(stringContent)
|
||||||
|
: stringContent;
|
||||||
|
content.setHtmlBody(htmlBody);
|
||||||
|
}
|
||||||
|
} else if ((normalizedDisposition != null
|
||||||
|
&& normalizedDisposition.contains(DISPOSITION_ATTACHMENT))
|
||||||
|
|| (filename != null && !filename.trim().isEmpty())) {
|
||||||
|
|
||||||
|
processAttachment(
|
||||||
|
part, content, request, getHeader, getContent, filename, contentType);
|
||||||
|
} else if ((Boolean) isMimeType.invoke(part, "multipart/*")) {
|
||||||
|
Object multipartContent = getContent.invoke(part);
|
||||||
|
if (multipartContent != null) {
|
||||||
|
Class<?> multipartClass = Class.forName("jakarta.mail.Multipart");
|
||||||
|
if (multipartClass.isInstance(multipartContent)) {
|
||||||
|
processMultipart(
|
||||||
|
multipartContent, content, request, customHtmlSanitizer, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (ReflectiveOperationException | RuntimeException e) {
|
||||||
|
// Continue processing other parts if one fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processAttachment(
|
||||||
|
Object part,
|
||||||
|
EmailContent content,
|
||||||
|
EmlToPdfRequest request,
|
||||||
|
Method getHeader,
|
||||||
|
Method getContent,
|
||||||
|
String filename,
|
||||||
|
String contentType) {
|
||||||
|
|
||||||
|
content.setAttachmentCount(content.getAttachmentCount() + 1);
|
||||||
|
|
||||||
|
if (filename != null && !filename.trim().isEmpty()) {
|
||||||
|
EmailAttachment attachment = new EmailAttachment();
|
||||||
|
attachment.setFilename(safeMimeDecode(filename));
|
||||||
|
attachment.setContentType(contentType);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String[] contentIdHeaders = (String[]) getHeader.invoke(part, HEADER_CONTENT_ID);
|
||||||
|
if (contentIdHeaders != null) {
|
||||||
|
for (String contentIdHeader : contentIdHeaders) {
|
||||||
|
if (contentIdHeader != null && !contentIdHeader.trim().isEmpty()) {
|
||||||
|
attachment.setEmbedded(true);
|
||||||
|
String contentId = contentIdHeader.trim().replaceAll("[<>]", "");
|
||||||
|
attachment.setContentId(contentId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((request != null && request.isIncludeAttachments()) || attachment.isEmbedded()) {
|
||||||
|
extractAttachmentData(part, attachment, getContent, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
content.getAttachments().add(attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void extractAttachmentData(
|
||||||
|
Object part, EmailAttachment attachment, Method getContent, EmlToPdfRequest request) {
|
||||||
|
try {
|
||||||
|
Object attachmentContent = getContent.invoke(part);
|
||||||
|
byte[] attachmentData = null;
|
||||||
|
|
||||||
|
if (attachmentContent instanceof InputStream inputStream) {
|
||||||
|
try (InputStream stream = inputStream) {
|
||||||
|
attachmentData = stream.readAllBytes();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (attachment.isEmbedded()) {
|
||||||
|
attachmentData = new byte[0];
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (attachmentContent instanceof byte[] byteArray) {
|
||||||
|
attachmentData = byteArray;
|
||||||
|
} else if (attachmentContent instanceof String stringContent) {
|
||||||
|
attachmentData = stringContent.getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachmentData != null) {
|
||||||
|
long maxSizeMB = request != null ? request.getMaxAttachmentSizeMB() : 10L;
|
||||||
|
long maxSizeBytes = maxSizeMB * 1024 * 1024;
|
||||||
|
|
||||||
|
if (attachmentData.length <= maxSizeBytes || attachment.isEmbedded()) {
|
||||||
|
attachment.setData(attachmentData);
|
||||||
|
attachment.setSizeBytes(attachmentData.length);
|
||||||
|
} else {
|
||||||
|
attachment.setSizeBytes(attachmentData.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ReflectiveOperationException | RuntimeException e) {
|
||||||
|
// Continue without attachment data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractBasicHeader(String emlContent, String headerName) {
|
||||||
|
try {
|
||||||
|
String[] lines = emlContent.split("\r?\n");
|
||||||
|
for (int i = 0; i < lines.length; i++) {
|
||||||
|
String line = lines[i];
|
||||||
|
if (line.toLowerCase().startsWith(headerName.toLowerCase())) {
|
||||||
|
StringBuilder value =
|
||||||
|
new StringBuilder(line.substring(headerName.length()).trim());
|
||||||
|
for (int j = i + 1; j < lines.length; j++) {
|
||||||
|
if (lines[j].startsWith(" ") || lines[j].startsWith("\t")) {
|
||||||
|
value.append(" ").append(lines[j].trim());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return safeMimeDecode(value.toString());
|
||||||
|
}
|
||||||
|
if (line.trim().isEmpty()) break;
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Ignore errors in header extraction
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractHtmlBody(String emlContent) {
|
||||||
|
try {
|
||||||
|
String lowerContent = emlContent.toLowerCase();
|
||||||
|
int htmlStart = lowerContent.indexOf(HEADER_CONTENT_TYPE + " " + TEXT_HTML);
|
||||||
|
if (htmlStart == -1) return null;
|
||||||
|
|
||||||
|
int bodyStart = emlContent.indexOf("\r\n\r\n", htmlStart);
|
||||||
|
if (bodyStart == -1) bodyStart = emlContent.indexOf("\n\n", htmlStart);
|
||||||
|
if (bodyStart == -1) return null;
|
||||||
|
|
||||||
|
bodyStart += (emlContent.charAt(bodyStart + 1) == '\r') ? 4 : 2;
|
||||||
|
int bodyEnd = findPartEnd(emlContent, bodyStart);
|
||||||
|
|
||||||
|
return emlContent.substring(bodyStart, bodyEnd).trim();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractTextBody(String emlContent) {
|
||||||
|
try {
|
||||||
|
String lowerContent = emlContent.toLowerCase();
|
||||||
|
int textStart = lowerContent.indexOf(HEADER_CONTENT_TYPE + " " + TEXT_PLAIN);
|
||||||
|
if (textStart == -1) {
|
||||||
|
int bodyStart = emlContent.indexOf("\r\n\r\n");
|
||||||
|
if (bodyStart == -1) bodyStart = emlContent.indexOf("\n\n");
|
||||||
|
if (bodyStart != -1) {
|
||||||
|
bodyStart += (emlContent.charAt(bodyStart + 1) == '\r') ? 4 : 2;
|
||||||
|
int bodyEnd = findPartEnd(emlContent, bodyStart);
|
||||||
|
return emlContent.substring(bodyStart, bodyEnd).trim();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bodyStart = emlContent.indexOf("\r\n\r\n", textStart);
|
||||||
|
if (bodyStart == -1) bodyStart = emlContent.indexOf("\n\n", textStart);
|
||||||
|
if (bodyStart == -1) return null;
|
||||||
|
|
||||||
|
bodyStart += (emlContent.charAt(bodyStart + 1) == '\r') ? 4 : 2;
|
||||||
|
int bodyEnd = findPartEnd(emlContent, bodyStart);
|
||||||
|
|
||||||
|
return emlContent.substring(bodyStart, bodyEnd).trim();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int findPartEnd(String content, int start) {
|
||||||
|
String[] lines = content.substring(start).split("\r?\n");
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
for (String line : lines) {
|
||||||
|
if (line.startsWith("--") && line.length() > 10) break;
|
||||||
|
result.append(line).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return start + result.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<EmailAttachment> extractAttachmentsBasic(String emlContent) {
|
||||||
|
List<EmailAttachment> attachments = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
String[] lines = emlContent.split("\r?\n");
|
||||||
|
boolean inHeaders = true;
|
||||||
|
String currentContentType = "";
|
||||||
|
String currentDisposition = "";
|
||||||
|
String currentFilename = "";
|
||||||
|
String currentEncoding = "";
|
||||||
|
|
||||||
|
for (String line : lines) {
|
||||||
|
String lowerLine = line.toLowerCase().trim();
|
||||||
|
|
||||||
|
if (line.trim().isEmpty()) {
|
||||||
|
inHeaders = false;
|
||||||
|
if (isAttachment(currentDisposition, currentFilename, currentContentType)) {
|
||||||
|
EmailAttachment attachment = new EmailAttachment();
|
||||||
|
attachment.setFilename(currentFilename);
|
||||||
|
attachment.setContentType(currentContentType);
|
||||||
|
attachment.setTransferEncoding(currentEncoding);
|
||||||
|
attachments.add(attachment);
|
||||||
|
}
|
||||||
|
currentContentType = "";
|
||||||
|
currentDisposition = "";
|
||||||
|
currentFilename = "";
|
||||||
|
currentEncoding = "";
|
||||||
|
inHeaders = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inHeaders) continue;
|
||||||
|
|
||||||
|
if (lowerLine.startsWith(HEADER_CONTENT_TYPE)) {
|
||||||
|
currentContentType = line.substring(HEADER_CONTENT_TYPE.length()).trim();
|
||||||
|
} else if (lowerLine.startsWith(HEADER_CONTENT_DISPOSITION)) {
|
||||||
|
currentDisposition = line.substring(HEADER_CONTENT_DISPOSITION.length()).trim();
|
||||||
|
currentFilename = extractFilenameFromDisposition(currentDisposition);
|
||||||
|
} else if (lowerLine.startsWith(HEADER_CONTENT_TRANSFER_ENCODING)) {
|
||||||
|
currentEncoding =
|
||||||
|
line.substring(HEADER_CONTENT_TRANSFER_ENCODING.length()).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Continue with empty list
|
||||||
|
}
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAttachment(String disposition, String filename, String contentType) {
|
||||||
|
return (disposition.toLowerCase().contains(DISPOSITION_ATTACHMENT) && !filename.isEmpty())
|
||||||
|
|| (!filename.isEmpty() && !contentType.toLowerCase().startsWith("text/"))
|
||||||
|
|| (contentType.toLowerCase().contains("application/") && !filename.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractFilenameFromDisposition(String disposition) {
|
||||||
|
if (disposition == null || !disposition.contains("filename=")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle filename*= (RFC 2231 encoded filename)
|
||||||
|
if (disposition.toLowerCase().contains("filename*=")) {
|
||||||
|
int filenameStarStart = disposition.toLowerCase().indexOf("filename*=") + 10;
|
||||||
|
int filenameStarEnd = disposition.indexOf(";", filenameStarStart);
|
||||||
|
if (filenameStarEnd == -1) filenameStarEnd = disposition.length();
|
||||||
|
String extendedFilename =
|
||||||
|
disposition.substring(filenameStarStart, filenameStarEnd).trim();
|
||||||
|
extendedFilename = extendedFilename.replaceAll("^\"|\"$", "");
|
||||||
|
|
||||||
|
if (extendedFilename.contains("'")) {
|
||||||
|
String[] parts = extendedFilename.split("'", 3);
|
||||||
|
if (parts.length == 3) {
|
||||||
|
return EmlProcessingUtils.decodeUrlEncoded(parts[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle regular filename=
|
||||||
|
int filenameStart = disposition.toLowerCase().indexOf("filename=") + 9;
|
||||||
|
int filenameEnd = disposition.indexOf(";", filenameStart);
|
||||||
|
if (filenameEnd == -1) filenameEnd = disposition.length();
|
||||||
|
String filename = disposition.substring(filenameStart, filenameEnd).trim();
|
||||||
|
filename = filename.replaceAll("^\"|\"$", "");
|
||||||
|
return safeMimeDecode(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String safeMimeDecode(String headerValue) {
|
||||||
|
if (headerValue == null || headerValue.trim().isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mimeUtilityChecked) {
|
||||||
|
synchronized (EmlParser.class) {
|
||||||
|
if (!mimeUtilityChecked) {
|
||||||
|
initializeMimeUtilityDecoding();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeUtilityDecodeTextMethod != null) {
|
||||||
|
try {
|
||||||
|
return (String) mimeUtilityDecodeTextMethod.invoke(null, headerValue.trim());
|
||||||
|
} catch (ReflectiveOperationException | RuntimeException e) {
|
||||||
|
// Fall through to custom implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EmlProcessingUtils.decodeMimeHeader(headerValue.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initializeMimeUtilityDecoding() {
|
||||||
|
try {
|
||||||
|
Class<?> mimeUtilityClass = Class.forName("jakarta.mail.internet.MimeUtility");
|
||||||
|
mimeUtilityDecodeTextMethod = mimeUtilityClass.getMethod("decodeText", String.class);
|
||||||
|
} catch (ClassNotFoundException | NoSuchMethodException e) {
|
||||||
|
mimeUtilityDecodeTextMethod = null;
|
||||||
|
}
|
||||||
|
mimeUtilityChecked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class EmailContent {
|
||||||
|
private String subject;
|
||||||
|
private String from;
|
||||||
|
private String to;
|
||||||
|
private String cc;
|
||||||
|
private String bcc;
|
||||||
|
private Date date;
|
||||||
|
private String dateString; // For basic parsing fallback
|
||||||
|
private String htmlBody;
|
||||||
|
private String textBody;
|
||||||
|
private int attachmentCount;
|
||||||
|
private List<EmailAttachment> attachments = new ArrayList<>();
|
||||||
|
|
||||||
|
public void setHtmlBody(String htmlBody) {
|
||||||
|
this.htmlBody = htmlBody != null ? htmlBody.replaceAll("\r", "") : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextBody(String textBody) {
|
||||||
|
this.textBody = textBody != null ? textBody.replaceAll("\r", "") : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class EmailAttachment {
|
||||||
|
private String filename;
|
||||||
|
private String contentType;
|
||||||
|
private byte[] data;
|
||||||
|
private boolean embedded;
|
||||||
|
private String embeddedFilename;
|
||||||
|
private long sizeBytes;
|
||||||
|
private String contentId;
|
||||||
|
private String disposition;
|
||||||
|
private String transferEncoding;
|
||||||
|
|
||||||
|
public void setData(byte[] data) {
|
||||||
|
this.data = data;
|
||||||
|
if (data != null) {
|
||||||
|
this.sizeBytes = data.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,601 @@
|
|||||||
|
package stirling.software.common.util;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import stirling.software.common.model.api.converters.EmlToPdfRequest;
|
||||||
|
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class EmlProcessingUtils {
|
||||||
|
|
||||||
|
// Style constants
|
||||||
|
private static final int DEFAULT_FONT_SIZE = 12;
|
||||||
|
private static final String DEFAULT_FONT_FAMILY = "Helvetica, sans-serif";
|
||||||
|
private static final float DEFAULT_LINE_HEIGHT = 1.4f;
|
||||||
|
private static final String DEFAULT_ZOOM = "1.0";
|
||||||
|
private static final String DEFAULT_TEXT_COLOR = "#202124";
|
||||||
|
private static final String DEFAULT_BACKGROUND_COLOR = "#ffffff";
|
||||||
|
private static final String DEFAULT_BORDER_COLOR = "#e8eaed";
|
||||||
|
private static final String ATTACHMENT_BACKGROUND_COLOR = "#f9f9f9";
|
||||||
|
private static final String ATTACHMENT_BORDER_COLOR = "#eeeeee";
|
||||||
|
|
||||||
|
private static final int EML_CHECK_LENGTH = 8192;
|
||||||
|
private static final int MIN_HEADER_COUNT_FOR_VALID_EML = 2;
|
||||||
|
|
||||||
|
// MIME type detection
|
||||||
|
private static final Map<String, String> EXTENSION_TO_MIME_TYPE =
|
||||||
|
Map.of(
|
||||||
|
".png", "image/png",
|
||||||
|
".jpg", "image/jpeg",
|
||||||
|
".jpeg", "image/jpeg",
|
||||||
|
".gif", "image/gif",
|
||||||
|
".bmp", "image/bmp",
|
||||||
|
".webp", "image/webp",
|
||||||
|
".svg", "image/svg+xml",
|
||||||
|
".ico", "image/x-icon",
|
||||||
|
".tiff", "image/tiff",
|
||||||
|
".tif", "image/tiff");
|
||||||
|
|
||||||
|
public static void validateEmlInput(byte[] emlBytes) {
|
||||||
|
if (emlBytes == null || emlBytes.length == 0) {
|
||||||
|
throw new IllegalArgumentException("EML file is empty or null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInvalidEmlFormat(emlBytes)) {
|
||||||
|
throw new IllegalArgumentException("Invalid EML file format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isInvalidEmlFormat(byte[] emlBytes) {
|
||||||
|
try {
|
||||||
|
int checkLength = Math.min(emlBytes.length, EML_CHECK_LENGTH);
|
||||||
|
String content;
|
||||||
|
|
||||||
|
try {
|
||||||
|
content = new String(emlBytes, 0, checkLength, StandardCharsets.UTF_8);
|
||||||
|
if (content.contains("\uFFFD")) {
|
||||||
|
content = new String(emlBytes, 0, checkLength, StandardCharsets.ISO_8859_1);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
content = new String(emlBytes, 0, checkLength, StandardCharsets.ISO_8859_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String lowerContent = content.toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
|
boolean hasFrom =
|
||||||
|
lowerContent.contains("from:") || lowerContent.contains("return-path:");
|
||||||
|
boolean hasSubject = lowerContent.contains("subject:");
|
||||||
|
boolean hasMessageId = lowerContent.contains("message-id:");
|
||||||
|
boolean hasDate = lowerContent.contains("date:");
|
||||||
|
boolean hasTo =
|
||||||
|
lowerContent.contains("to:")
|
||||||
|
|| lowerContent.contains("cc:")
|
||||||
|
|| lowerContent.contains("bcc:");
|
||||||
|
boolean hasMimeStructure =
|
||||||
|
lowerContent.contains("multipart/")
|
||||||
|
|| lowerContent.contains("text/plain")
|
||||||
|
|| lowerContent.contains("text/html")
|
||||||
|
|| lowerContent.contains("boundary=");
|
||||||
|
|
||||||
|
int headerCount = 0;
|
||||||
|
if (hasFrom) headerCount++;
|
||||||
|
if (hasSubject) headerCount++;
|
||||||
|
if (hasMessageId) headerCount++;
|
||||||
|
if (hasDate) headerCount++;
|
||||||
|
if (hasTo) headerCount++;
|
||||||
|
|
||||||
|
return headerCount < MIN_HEADER_COUNT_FOR_VALID_EML && !hasMimeStructure;
|
||||||
|
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generateEnhancedEmailHtml(
|
||||||
|
EmlParser.EmailContent content,
|
||||||
|
EmlToPdfRequest request,
|
||||||
|
CustomHtmlSanitizer customHtmlSanitizer) {
|
||||||
|
StringBuilder html = new StringBuilder();
|
||||||
|
|
||||||
|
html.append(
|
||||||
|
String.format(
|
||||||
|
"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"><head><meta charset="UTF-8">
|
||||||
|
<title>%s</title>
|
||||||
|
<style>
|
||||||
|
""",
|
||||||
|
sanitizeText(content.getSubject(), customHtmlSanitizer)));
|
||||||
|
|
||||||
|
appendEnhancedStyles(html);
|
||||||
|
|
||||||
|
html.append(
|
||||||
|
"""
|
||||||
|
</style>
|
||||||
|
</head><body>
|
||||||
|
""");
|
||||||
|
|
||||||
|
html.append(
|
||||||
|
String.format(
|
||||||
|
"""
|
||||||
|
<div class="email-container">
|
||||||
|
<div class="email-header">
|
||||||
|
<h1>%s</h1>
|
||||||
|
<div class="email-meta">
|
||||||
|
<div><strong>From:</strong> %s</div>
|
||||||
|
<div><strong>To:</strong> %s</div>
|
||||||
|
""",
|
||||||
|
sanitizeText(content.getSubject(), customHtmlSanitizer),
|
||||||
|
sanitizeText(content.getFrom(), customHtmlSanitizer),
|
||||||
|
sanitizeText(content.getTo(), customHtmlSanitizer)));
|
||||||
|
|
||||||
|
if (content.getCc() != null && !content.getCc().trim().isEmpty()) {
|
||||||
|
html.append(
|
||||||
|
String.format(
|
||||||
|
"<div><strong>CC:</strong> %s</div>\n",
|
||||||
|
sanitizeText(content.getCc(), customHtmlSanitizer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.getBcc() != null && !content.getBcc().trim().isEmpty()) {
|
||||||
|
html.append(
|
||||||
|
String.format(
|
||||||
|
"<div><strong>BCC:</strong> %s</div>\n",
|
||||||
|
sanitizeText(content.getBcc(), customHtmlSanitizer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.getDate() != null) {
|
||||||
|
html.append(
|
||||||
|
String.format(
|
||||||
|
"<div><strong>Date:</strong> %s</div>\n",
|
||||||
|
PdfAttachmentHandler.formatEmailDate(content.getDate())));
|
||||||
|
} else if (content.getDateString() != null && !content.getDateString().trim().isEmpty()) {
|
||||||
|
html.append(
|
||||||
|
String.format(
|
||||||
|
"<div><strong>Date:</strong> %s</div>\n",
|
||||||
|
sanitizeText(content.getDateString(), customHtmlSanitizer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
html.append("</div></div>\n");
|
||||||
|
|
||||||
|
html.append("<div class=\"email-body\">\n");
|
||||||
|
if (content.getHtmlBody() != null && !content.getHtmlBody().trim().isEmpty()) {
|
||||||
|
String processedHtml =
|
||||||
|
processEmailHtmlBody(content.getHtmlBody(), content, customHtmlSanitizer);
|
||||||
|
html.append(processedHtml);
|
||||||
|
} else if (content.getTextBody() != null && !content.getTextBody().trim().isEmpty()) {
|
||||||
|
html.append(
|
||||||
|
String.format(
|
||||||
|
"<div class=\"text-body\">%s</div>",
|
||||||
|
convertTextToHtml(content.getTextBody(), customHtmlSanitizer)));
|
||||||
|
} else {
|
||||||
|
html.append("<div class=\"no-content\"><p><em>No content available</em></p></div>");
|
||||||
|
}
|
||||||
|
html.append("</div>\n");
|
||||||
|
|
||||||
|
if (content.getAttachmentCount() > 0 || !content.getAttachments().isEmpty()) {
|
||||||
|
appendAttachmentsSection(html, content, request, customHtmlSanitizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.append("</div>\n</body></html>");
|
||||||
|
return html.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String processEmailHtmlBody(
|
||||||
|
String htmlBody,
|
||||||
|
EmlParser.EmailContent emailContent,
|
||||||
|
CustomHtmlSanitizer customHtmlSanitizer) {
|
||||||
|
if (htmlBody == null) return "";
|
||||||
|
|
||||||
|
String processed =
|
||||||
|
customHtmlSanitizer != null ? customHtmlSanitizer.sanitize(htmlBody) : htmlBody;
|
||||||
|
|
||||||
|
processed = processed.replaceAll("(?i)\\s*position\\s*:\\s*fixed[^;]*;?", "");
|
||||||
|
processed = processed.replaceAll("(?i)\\s*position\\s*:\\s*absolute[^;]*;?", "");
|
||||||
|
|
||||||
|
if (emailContent != null && !emailContent.getAttachments().isEmpty()) {
|
||||||
|
processed = PdfAttachmentHandler.processInlineImages(processed, emailContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String convertTextToHtml(
|
||||||
|
String textBody, CustomHtmlSanitizer customHtmlSanitizer) {
|
||||||
|
if (textBody == null) return "";
|
||||||
|
|
||||||
|
String html =
|
||||||
|
customHtmlSanitizer != null
|
||||||
|
? customHtmlSanitizer.sanitize(textBody)
|
||||||
|
: escapeHtml(textBody);
|
||||||
|
|
||||||
|
html = html.replace("\r\n", "\n").replace("\r", "\n");
|
||||||
|
html = html.replace("\n", "<br>\n");
|
||||||
|
|
||||||
|
html =
|
||||||
|
html.replaceAll(
|
||||||
|
"(https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)",
|
||||||
|
"<a href=\"$1\" style=\"color: #1a73e8; text-decoration: underline;\">$1</a>");
|
||||||
|
|
||||||
|
html =
|
||||||
|
html.replaceAll(
|
||||||
|
"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,63})",
|
||||||
|
"<a href=\"mailto:$1\" style=\"color: #1a73e8; text-decoration: underline;\">$1</a>");
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendEnhancedStyles(StringBuilder html) {
|
||||||
|
String css =
|
||||||
|
String.format(
|
||||||
|
"""
|
||||||
|
body {
|
||||||
|
font-family: %s;
|
||||||
|
font-size: %dpx;
|
||||||
|
line-height: %s;
|
||||||
|
color: %s;
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: %s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-container {
|
||||||
|
width: 100%%;
|
||||||
|
max-width: 100%%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid %s;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header h1 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: %dpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-meta div {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
font-size: %dpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-body {
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-section {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: %s;
|
||||||
|
border: 1px solid %s;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-section h3 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: %dpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-item {
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-details, .attachment-type {
|
||||||
|
font-size: %dpx;
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-inclusion-note, .attachment-info-note {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 6px;
|
||||||
|
font-size: %dpx;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-inclusion-note {
|
||||||
|
background-color: #e6ffed;
|
||||||
|
border: 1px solid #d4f7dc;
|
||||||
|
color: #006420;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-info-note {
|
||||||
|
background-color: #fff9e6;
|
||||||
|
border: 1px solid #fff0c2;
|
||||||
|
color: #664d00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-link-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-link-container:hover {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-note {
|
||||||
|
font-size: %dpx;
|
||||||
|
color: #6c757d;
|
||||||
|
font-style: italic;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-content {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-body {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
DEFAULT_FONT_FAMILY,
|
||||||
|
DEFAULT_FONT_SIZE,
|
||||||
|
DEFAULT_LINE_HEIGHT,
|
||||||
|
DEFAULT_TEXT_COLOR,
|
||||||
|
DEFAULT_BACKGROUND_COLOR,
|
||||||
|
DEFAULT_BORDER_COLOR,
|
||||||
|
DEFAULT_FONT_SIZE + 4,
|
||||||
|
DEFAULT_FONT_SIZE - 1,
|
||||||
|
ATTACHMENT_BACKGROUND_COLOR,
|
||||||
|
ATTACHMENT_BORDER_COLOR,
|
||||||
|
DEFAULT_FONT_SIZE + 1,
|
||||||
|
DEFAULT_FONT_SIZE - 2,
|
||||||
|
DEFAULT_FONT_SIZE - 2,
|
||||||
|
DEFAULT_FONT_SIZE - 3);
|
||||||
|
|
||||||
|
html.append(css);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendAttachmentsSection(
|
||||||
|
StringBuilder html,
|
||||||
|
EmlParser.EmailContent content,
|
||||||
|
EmlToPdfRequest request,
|
||||||
|
CustomHtmlSanitizer customHtmlSanitizer) {
|
||||||
|
html.append("<div class=\"attachment-section\">\n");
|
||||||
|
int displayedAttachmentCount =
|
||||||
|
content.getAttachmentCount() > 0
|
||||||
|
? content.getAttachmentCount()
|
||||||
|
: content.getAttachments().size();
|
||||||
|
html.append("<h3>Attachments (").append(displayedAttachmentCount).append(")</h3>\n");
|
||||||
|
|
||||||
|
if (!content.getAttachments().isEmpty()) {
|
||||||
|
for (int i = 0; i < content.getAttachments().size(); i++) {
|
||||||
|
EmlParser.EmailAttachment attachment = content.getAttachments().get(i);
|
||||||
|
|
||||||
|
String embeddedFilename =
|
||||||
|
attachment.getFilename() != null
|
||||||
|
? attachment.getFilename()
|
||||||
|
: ("attachment_" + i);
|
||||||
|
attachment.setEmbeddedFilename(embeddedFilename);
|
||||||
|
|
||||||
|
String sizeStr = GeneralUtils.formatBytes(attachment.getSizeBytes());
|
||||||
|
String contentType =
|
||||||
|
attachment.getContentType() != null
|
||||||
|
&& !attachment.getContentType().isEmpty()
|
||||||
|
? ", " + escapeHtml(attachment.getContentType())
|
||||||
|
: "";
|
||||||
|
|
||||||
|
String attachmentId = "attachment_" + i;
|
||||||
|
html.append(
|
||||||
|
String.format(
|
||||||
|
"""
|
||||||
|
<div class="attachment-item" id="%s">
|
||||||
|
<span class="attachment-icon" data-filename="%s">@</span>
|
||||||
|
<span class="attachment-name">%s</span>
|
||||||
|
<span class="attachment-details">(%s%s)</span>
|
||||||
|
</div>
|
||||||
|
""",
|
||||||
|
attachmentId,
|
||||||
|
escapeHtml(embeddedFilename),
|
||||||
|
escapeHtml(EmlParser.safeMimeDecode(attachment.getFilename())),
|
||||||
|
sizeStr,
|
||||||
|
contentType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request != null && request.isIncludeAttachments()) {
|
||||||
|
html.append(
|
||||||
|
"""
|
||||||
|
<div class="attachment-info-note">
|
||||||
|
<p><em>Attachments are embedded in the file.</em></p>
|
||||||
|
</div>
|
||||||
|
""");
|
||||||
|
} else {
|
||||||
|
html.append(
|
||||||
|
"""
|
||||||
|
<div class="attachment-info-note">
|
||||||
|
<p><em>Attachment information displayed - files not included in PDF.</em></p>
|
||||||
|
</div>
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
html.append("</div>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HTMLToPdfRequest createHtmlRequest(EmlToPdfRequest request) {
|
||||||
|
HTMLToPdfRequest htmlRequest = new HTMLToPdfRequest();
|
||||||
|
|
||||||
|
if (request != null) {
|
||||||
|
htmlRequest.setFileInput(request.getFileInput());
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlRequest.setZoom(Float.parseFloat(DEFAULT_ZOOM));
|
||||||
|
return htmlRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String detectMimeType(String filename, String existingMimeType) {
|
||||||
|
if (existingMimeType != null && !existingMimeType.isEmpty()) {
|
||||||
|
return existingMimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename != null) {
|
||||||
|
String lowerFilename = filename.toLowerCase();
|
||||||
|
for (Map.Entry<String, String> entry : EXTENSION_TO_MIME_TYPE.entrySet()) {
|
||||||
|
if (lowerFilename.endsWith(entry.getKey())) {
|
||||||
|
return entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "image/png";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String decodeUrlEncoded(String encoded) {
|
||||||
|
try {
|
||||||
|
return java.net.URLDecoder.decode(encoded, StandardCharsets.UTF_8);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return encoded; // Return original if decoding fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String decodeMimeHeader(String encodedText) {
|
||||||
|
if (encodedText == null || encodedText.trim().isEmpty()) {
|
||||||
|
return encodedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
Pattern concatenatedPattern =
|
||||||
|
Pattern.compile(
|
||||||
|
"(=\\?[^?]+\\?[BbQq]\\?[^?]*\\?=)(\\s*=\\?[^?]+\\?[BbQq]\\?[^?]*\\?=)+");
|
||||||
|
Matcher concatenatedMatcher = concatenatedPattern.matcher(encodedText);
|
||||||
|
String processedText =
|
||||||
|
concatenatedMatcher.replaceAll(
|
||||||
|
match -> match.group().replaceAll("\\s+(?==\\?)", ""));
|
||||||
|
|
||||||
|
Pattern mimePattern = Pattern.compile("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
|
||||||
|
Matcher matcher = mimePattern.matcher(processedText);
|
||||||
|
int lastEnd = 0;
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
result.append(processedText, lastEnd, matcher.start());
|
||||||
|
|
||||||
|
String charset = matcher.group(1);
|
||||||
|
String encoding = matcher.group(2).toUpperCase();
|
||||||
|
String encodedValue = matcher.group(3);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String decodedValue =
|
||||||
|
switch (encoding) {
|
||||||
|
case "B" -> {
|
||||||
|
String cleanBase64 = encodedValue.replaceAll("\\s", "");
|
||||||
|
byte[] decodedBytes = Base64.getDecoder().decode(cleanBase64);
|
||||||
|
Charset targetCharset;
|
||||||
|
try {
|
||||||
|
targetCharset = Charset.forName(charset);
|
||||||
|
} catch (Exception e) {
|
||||||
|
targetCharset = StandardCharsets.UTF_8;
|
||||||
|
}
|
||||||
|
yield new String(decodedBytes, targetCharset);
|
||||||
|
}
|
||||||
|
case "Q" -> decodeQuotedPrintable(encodedValue, charset);
|
||||||
|
default -> matcher.group(0); // Return original if unknown encoding
|
||||||
|
};
|
||||||
|
result.append(decodedValue);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
result.append(matcher.group(0)); // Keep original on decode error
|
||||||
|
}
|
||||||
|
|
||||||
|
lastEnd = matcher.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append(processedText.substring(lastEnd));
|
||||||
|
return result.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return encodedText; // Return original on any parsing error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String decodeQuotedPrintable(String encodedText, String charset) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
for (int i = 0; i < encodedText.length(); i++) {
|
||||||
|
char c = encodedText.charAt(i);
|
||||||
|
switch (c) {
|
||||||
|
case '=' -> {
|
||||||
|
if (i + 2 < encodedText.length()) {
|
||||||
|
String hex = encodedText.substring(i + 1, i + 3);
|
||||||
|
try {
|
||||||
|
int value = Integer.parseInt(hex, 16);
|
||||||
|
result.append((char) value);
|
||||||
|
i += 2;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
result.append(c);
|
||||||
|
}
|
||||||
|
} else if (i + 1 == encodedText.length()
|
||||||
|
|| (i + 2 == encodedText.length()
|
||||||
|
&& encodedText.charAt(i + 1) == '\n')) {
|
||||||
|
if (i + 1 < encodedText.length() && encodedText.charAt(i + 1) == '\n') {
|
||||||
|
i++; // Skip the newline too
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case '_' -> result.append(' '); // Space encoding in Q encoding
|
||||||
|
default -> result.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = result.toString().getBytes(StandardCharsets.ISO_8859_1);
|
||||||
|
try {
|
||||||
|
Charset targetCharset = Charset.forName(charset);
|
||||||
|
return new String(bytes, targetCharset);
|
||||||
|
} catch (Exception e) {
|
||||||
|
try {
|
||||||
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
} catch (Exception fallbackException) {
|
||||||
|
return new String(bytes, StandardCharsets.ISO_8859_1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String escapeHtml(String text) {
|
||||||
|
if (text == null) return "";
|
||||||
|
return text.replace("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("\"", """)
|
||||||
|
.replace("'", "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sanitizeText(String text, CustomHtmlSanitizer customHtmlSanitizer) {
|
||||||
|
if (customHtmlSanitizer != null) {
|
||||||
|
return customHtmlSanitizer.sanitize(text);
|
||||||
|
} else {
|
||||||
|
return escapeHtml(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String simplifyHtmlContent(String htmlContent) {
|
||||||
|
String simplified = htmlContent.replaceAll("(?i)<script[^>]*>.*?</script>", "");
|
||||||
|
simplified = simplified.replaceAll("(?i)<style[^>]*>.*?</style>", "");
|
||||||
|
return simplified;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -5,8 +5,11 @@ import java.awt.image.*;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
@ -115,7 +118,36 @@ public class ImageProcessingUtils {
|
|||||||
|
|
||||||
public static BufferedImage loadImageWithExifOrientation(MultipartFile file)
|
public static BufferedImage loadImageWithExifOrientation(MultipartFile file)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
BufferedImage image = ImageIO.read(file.getInputStream());
|
BufferedImage image = null;
|
||||||
|
String filename = file.getOriginalFilename();
|
||||||
|
|
||||||
|
if (filename != null && filename.toLowerCase().endsWith(".psd")) {
|
||||||
|
// For PSD files, try explicit ImageReader
|
||||||
|
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("PSD");
|
||||||
|
if (readers.hasNext()) {
|
||||||
|
ImageReader reader = readers.next();
|
||||||
|
try (ImageInputStream iis = ImageIO.createImageInputStream(file.getInputStream())) {
|
||||||
|
reader.setInput(iis);
|
||||||
|
image = reader.read(0);
|
||||||
|
} finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (image == null) {
|
||||||
|
throw new IOException(
|
||||||
|
"Unable to read image from file: "
|
||||||
|
+ filename
|
||||||
|
+ ". Supported PSD formats: RGB/CMYK/Gray 8-32 bit, RLE/ZIP compression");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For non-PSD files, use standard ImageIO
|
||||||
|
image = ImageIO.read(file.getInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image == null) {
|
||||||
|
throw new IOException("Unable to read image from file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
double orientation = extractImageOrientation(file.getInputStream());
|
double orientation = extractImageOrientation(file.getInputStream());
|
||||||
return applyOrientation(image, orientation);
|
return applyOrientation(image, orientation);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,680 @@
|
|||||||
|
package stirling.software.common.util;
|
||||||
|
|
||||||
|
import static stirling.software.common.util.AttachmentUtils.setCatalogViewerPreferences;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PageMode;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
|
||||||
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
|
import org.apache.pdfbox.text.TextPosition;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class PdfAttachmentHandler {
|
||||||
|
// Note: This class is designed for EML attachments, not general PDF attachments.
|
||||||
|
|
||||||
|
private static final String ATTACHMENT_MARKER = "@";
|
||||||
|
private static final float ATTACHMENT_ICON_WIDTH = 12f;
|
||||||
|
private static final float ATTACHMENT_ICON_HEIGHT = 14f;
|
||||||
|
private static final float ANNOTATION_X_OFFSET = 2f;
|
||||||
|
private static final float ANNOTATION_Y_OFFSET = 10f;
|
||||||
|
|
||||||
|
public static byte[] attachFilesToPdf(
|
||||||
|
byte[] pdfBytes,
|
||||||
|
List<EmlParser.EmailAttachment> attachments,
|
||||||
|
CustomPDFDocumentFactory pdfDocumentFactory)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
if (attachments == null || attachments.isEmpty()) {
|
||||||
|
return pdfBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (PDDocument document = pdfDocumentFactory.load(pdfBytes);
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||||
|
|
||||||
|
List<MultipartFile> multipartAttachments = new ArrayList<>(attachments.size());
|
||||||
|
for (int i = 0; i < attachments.size(); i++) {
|
||||||
|
EmlParser.EmailAttachment attachment = attachments.get(i);
|
||||||
|
if (attachment.getData() != null && attachment.getData().length > 0) {
|
||||||
|
String embeddedFilename =
|
||||||
|
attachment.getFilename() != null
|
||||||
|
? attachment.getFilename()
|
||||||
|
: ("attachment_" + i);
|
||||||
|
attachment.setEmbeddedFilename(embeddedFilename);
|
||||||
|
multipartAttachments.add(createMultipartFile(attachment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!multipartAttachments.isEmpty()) {
|
||||||
|
Map<Integer, String> indexToFilenameMap =
|
||||||
|
addAttachmentsToDocumentWithMapping(
|
||||||
|
document, multipartAttachments, attachments);
|
||||||
|
setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS);
|
||||||
|
addAttachmentAnnotationsToDocumentWithMapping(
|
||||||
|
document, attachments, indexToFilenameMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.save(outputStream);
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw new IOException(
|
||||||
|
"Invalid PDF structure or processing error: " + e.getMessage(), e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException("Error attaching files to PDF: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MultipartFile createMultipartFile(EmlParser.EmailAttachment attachment) {
|
||||||
|
return new MultipartFile() {
|
||||||
|
@Override
|
||||||
|
public @NotNull String getName() {
|
||||||
|
return "attachment";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOriginalFilename() {
|
||||||
|
return attachment.getFilename() != null
|
||||||
|
? attachment.getFilename()
|
||||||
|
: "attachment_" + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
return attachment.getContentType() != null
|
||||||
|
? attachment.getContentType()
|
||||||
|
: "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return attachment.getData() == null || attachment.getData().length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSize() {
|
||||||
|
return attachment.getData() != null ? attachment.getData().length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte @NotNull [] getBytes() {
|
||||||
|
return attachment.getData() != null ? attachment.getData() : new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull InputStream getInputStream() {
|
||||||
|
byte[] data = attachment.getData();
|
||||||
|
return new ByteArrayInputStream(data != null ? data : new byte[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transferTo(@NotNull File dest) throws IOException, IllegalStateException {
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(dest)) {
|
||||||
|
byte[] data = attachment.getData();
|
||||||
|
if (data != null) {
|
||||||
|
fos.write(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String ensureUniqueFilename(String filename, Set<String> existingNames) {
|
||||||
|
if (!existingNames.contains(filename)) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
String baseName;
|
||||||
|
String extension = "";
|
||||||
|
int lastDot = filename.lastIndexOf('.');
|
||||||
|
if (lastDot > 0) {
|
||||||
|
baseName = filename.substring(0, lastDot);
|
||||||
|
extension = filename.substring(lastDot);
|
||||||
|
} else {
|
||||||
|
baseName = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter = 1;
|
||||||
|
String uniqueName;
|
||||||
|
do {
|
||||||
|
uniqueName = baseName + "_" + counter + extension;
|
||||||
|
counter++;
|
||||||
|
} while (existingNames.contains(uniqueName));
|
||||||
|
|
||||||
|
return uniqueName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull PDRectangle calculateAnnotationRectangle(
|
||||||
|
PDPage page, float x, float y) {
|
||||||
|
PDRectangle cropBox = page.getCropBox();
|
||||||
|
|
||||||
|
// ISO 32000-1:2008 Section 8.3: PDF coordinate system transforms
|
||||||
|
int rotation = page.getRotation();
|
||||||
|
float pdfX = x;
|
||||||
|
float pdfY = cropBox.getHeight() - y;
|
||||||
|
|
||||||
|
switch (rotation) {
|
||||||
|
case 90 -> {
|
||||||
|
float temp = pdfX;
|
||||||
|
pdfX = pdfY;
|
||||||
|
pdfY = cropBox.getWidth() - temp;
|
||||||
|
}
|
||||||
|
case 180 -> {
|
||||||
|
pdfX = cropBox.getWidth() - pdfX;
|
||||||
|
pdfY = y;
|
||||||
|
}
|
||||||
|
case 270 -> {
|
||||||
|
float temp = pdfX;
|
||||||
|
pdfX = cropBox.getHeight() - pdfY;
|
||||||
|
pdfY = temp;
|
||||||
|
}
|
||||||
|
default -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
float iconHeight = ATTACHMENT_ICON_HEIGHT;
|
||||||
|
float paddingX = 2.0f;
|
||||||
|
float paddingY = 2.0f;
|
||||||
|
|
||||||
|
PDRectangle rect =
|
||||||
|
new PDRectangle(
|
||||||
|
pdfX + ANNOTATION_X_OFFSET + paddingX,
|
||||||
|
pdfY - iconHeight + ANNOTATION_Y_OFFSET + paddingY,
|
||||||
|
ATTACHMENT_ICON_WIDTH,
|
||||||
|
iconHeight);
|
||||||
|
|
||||||
|
PDRectangle mediaBox = page.getMediaBox();
|
||||||
|
if (rect.getLowerLeftX() < mediaBox.getLowerLeftX()
|
||||||
|
|| rect.getLowerLeftY() < mediaBox.getLowerLeftY()
|
||||||
|
|| rect.getUpperRightX() > mediaBox.getUpperRightX()
|
||||||
|
|| rect.getUpperRightY() > mediaBox.getUpperRightY()) {
|
||||||
|
|
||||||
|
float adjustedX =
|
||||||
|
Math.max(
|
||||||
|
mediaBox.getLowerLeftX(),
|
||||||
|
Math.min(
|
||||||
|
rect.getLowerLeftX(),
|
||||||
|
mediaBox.getUpperRightX() - rect.getWidth()));
|
||||||
|
float adjustedY =
|
||||||
|
Math.max(
|
||||||
|
mediaBox.getLowerLeftY(),
|
||||||
|
Math.min(
|
||||||
|
rect.getLowerLeftY(),
|
||||||
|
mediaBox.getUpperRightY() - rect.getHeight()));
|
||||||
|
rect = new PDRectangle(adjustedX, adjustedY, rect.getWidth(), rect.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String processInlineImages(
|
||||||
|
String htmlContent, EmlParser.EmailContent emailContent) {
|
||||||
|
if (htmlContent == null || emailContent == null) return htmlContent;
|
||||||
|
|
||||||
|
Map<String, EmlParser.EmailAttachment> contentIdMap = new HashMap<>();
|
||||||
|
for (EmlParser.EmailAttachment attachment : emailContent.getAttachments()) {
|
||||||
|
if (attachment.isEmbedded()
|
||||||
|
&& attachment.getContentId() != null
|
||||||
|
&& attachment.getData() != null) {
|
||||||
|
contentIdMap.put(attachment.getContentId(), attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentIdMap.isEmpty()) return htmlContent;
|
||||||
|
|
||||||
|
Pattern cidPattern =
|
||||||
|
Pattern.compile(
|
||||||
|
"(?i)<img[^>]*\\ssrc\\s*=\\s*['\"]cid:([^'\"]+)['\"][^>]*>",
|
||||||
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher matcher = cidPattern.matcher(htmlContent);
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
while (matcher.find()) {
|
||||||
|
String contentId = matcher.group(1);
|
||||||
|
EmlParser.EmailAttachment attachment = contentIdMap.get(contentId);
|
||||||
|
|
||||||
|
if (attachment != null && attachment.getData() != null) {
|
||||||
|
String mimeType =
|
||||||
|
EmlProcessingUtils.detectMimeType(
|
||||||
|
attachment.getFilename(), attachment.getContentType());
|
||||||
|
|
||||||
|
String base64Data = Base64.getEncoder().encodeToString(attachment.getData());
|
||||||
|
String dataUri = "data:" + mimeType + ";base64," + base64Data;
|
||||||
|
|
||||||
|
String replacement =
|
||||||
|
matcher.group(0).replaceFirst("cid:" + Pattern.quote(contentId), dataUri);
|
||||||
|
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
|
||||||
|
} else {
|
||||||
|
matcher.appendReplacement(result, Matcher.quoteReplacement(matcher.group(0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatEmailDate(Date date) {
|
||||||
|
if (date == null) return "";
|
||||||
|
|
||||||
|
SimpleDateFormat formatter =
|
||||||
|
new SimpleDateFormat("EEE, MMM d, yyyy 'at' h:mm a z", Locale.ENGLISH);
|
||||||
|
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
return formatter.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class MarkerPosition {
|
||||||
|
private int pageIndex;
|
||||||
|
private float x;
|
||||||
|
private float y;
|
||||||
|
private String character;
|
||||||
|
private String filename;
|
||||||
|
|
||||||
|
public MarkerPosition(int pageIndex, float x, float y, String character, String filename) {
|
||||||
|
this.pageIndex = pageIndex;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.character = character;
|
||||||
|
this.filename = filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AttachmentMarkerPositionFinder extends PDFTextStripper {
|
||||||
|
@Getter private final List<MarkerPosition> positions = new ArrayList<>();
|
||||||
|
private int currentPageIndex;
|
||||||
|
protected boolean sortByPosition;
|
||||||
|
private boolean isInAttachmentSection;
|
||||||
|
private boolean attachmentSectionFound;
|
||||||
|
private final StringBuilder currentText = new StringBuilder();
|
||||||
|
|
||||||
|
private static final Pattern ATTACHMENT_SECTION_PATTERN =
|
||||||
|
Pattern.compile("attachments\\s*\\(\\d+\\)", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
private static final Pattern FILENAME_PATTERN =
|
||||||
|
Pattern.compile("@\\s*([^\\s\\(]+(?:\\.[a-zA-Z0-9]+)?)");
|
||||||
|
|
||||||
|
public AttachmentMarkerPositionFinder() {
|
||||||
|
super();
|
||||||
|
this.currentPageIndex = 0;
|
||||||
|
this.sortByPosition = false; // Disable sorting to preserve document order
|
||||||
|
this.isInAttachmentSection = false;
|
||||||
|
this.attachmentSectionFound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText(PDDocument document) throws IOException {
|
||||||
|
super.getText(document);
|
||||||
|
|
||||||
|
if (sortByPosition) {
|
||||||
|
positions.sort(
|
||||||
|
(a, b) -> {
|
||||||
|
int pageCompare = Integer.compare(a.getPageIndex(), b.getPageIndex());
|
||||||
|
if (pageCompare != 0) return pageCompare;
|
||||||
|
return Float.compare(
|
||||||
|
b.getY(), a.getY()); // Descending Y per PDF coordinate system
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""; // Return empty string as we only need positions
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startPage(PDPage page) throws IOException {
|
||||||
|
super.startPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void endPage(PDPage page) throws IOException {
|
||||||
|
currentPageIndex++;
|
||||||
|
super.endPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeString(String string, List<TextPosition> textPositions)
|
||||||
|
throws IOException {
|
||||||
|
String lowerString = string.toLowerCase();
|
||||||
|
|
||||||
|
if (ATTACHMENT_SECTION_PATTERN.matcher(lowerString).find()) {
|
||||||
|
isInAttachmentSection = true;
|
||||||
|
attachmentSectionFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInAttachmentSection
|
||||||
|
&& (lowerString.contains("</body>")
|
||||||
|
|| lowerString.contains("</html>")
|
||||||
|
|| (attachmentSectionFound
|
||||||
|
&& lowerString.trim().isEmpty()
|
||||||
|
&& string.length() > 50))) {
|
||||||
|
isInAttachmentSection = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInAttachmentSection) {
|
||||||
|
currentText.append(string);
|
||||||
|
|
||||||
|
for (int i = 0; (i = string.indexOf(ATTACHMENT_MARKER, i)) != -1; i++) {
|
||||||
|
if (i < textPositions.size()) {
|
||||||
|
TextPosition textPosition = textPositions.get(i);
|
||||||
|
|
||||||
|
String filename = extractFilenameAfterMarker(string, i);
|
||||||
|
|
||||||
|
MarkerPosition position =
|
||||||
|
new MarkerPosition(
|
||||||
|
currentPageIndex,
|
||||||
|
textPosition.getXDirAdj(),
|
||||||
|
textPosition.getYDirAdj(),
|
||||||
|
ATTACHMENT_MARKER,
|
||||||
|
filename);
|
||||||
|
positions.add(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.writeString(string, textPositions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSortByPosition(boolean sortByPosition) {
|
||||||
|
this.sortByPosition = sortByPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractFilenameAfterMarker(String text, int markerIndex) {
|
||||||
|
String afterMarker = text.substring(markerIndex + 1);
|
||||||
|
|
||||||
|
Matcher matcher = FILENAME_PATTERN.matcher("@" + afterMarker);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = afterMarker.split("[\\s\\(\\)]+");
|
||||||
|
for (String part : parts) {
|
||||||
|
part = part.trim();
|
||||||
|
if (part.length() > 3 && part.contains(".")) {
|
||||||
|
return part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<Integer, String> addAttachmentsToDocumentWithMapping(
|
||||||
|
PDDocument document,
|
||||||
|
List<MultipartFile> attachments,
|
||||||
|
List<EmlParser.EmailAttachment> originalAttachments)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||||
|
|
||||||
|
if (catalog == null) {
|
||||||
|
throw new IOException("PDF document catalog is not accessible");
|
||||||
|
}
|
||||||
|
|
||||||
|
PDDocumentNameDictionary documentNames = catalog.getNames();
|
||||||
|
if (documentNames == null) {
|
||||||
|
documentNames = new PDDocumentNameDictionary(catalog);
|
||||||
|
catalog.setNames(documentNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
PDEmbeddedFilesNameTreeNode embeddedFilesTree = documentNames.getEmbeddedFiles();
|
||||||
|
if (embeddedFilesTree == null) {
|
||||||
|
embeddedFilesTree = new PDEmbeddedFilesNameTreeNode();
|
||||||
|
documentNames.setEmbeddedFiles(embeddedFilesTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, PDComplexFileSpecification> existingNames = embeddedFilesTree.getNames();
|
||||||
|
if (existingNames == null) {
|
||||||
|
existingNames = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Integer, String> indexToFilenameMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < attachments.size(); i++) {
|
||||||
|
MultipartFile attachment = attachments.get(i);
|
||||||
|
String filename = attachment.getOriginalFilename();
|
||||||
|
if (filename == null || filename.trim().isEmpty()) {
|
||||||
|
filename = "attachment_" + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalizedFilename =
|
||||||
|
isAscii(filename)
|
||||||
|
? filename
|
||||||
|
: java.text.Normalizer.normalize(
|
||||||
|
filename, java.text.Normalizer.Form.NFC);
|
||||||
|
String uniqueFilename =
|
||||||
|
ensureUniqueFilename(normalizedFilename, existingNames.keySet());
|
||||||
|
|
||||||
|
indexToFilenameMap.put(i, uniqueFilename);
|
||||||
|
|
||||||
|
PDEmbeddedFile embeddedFile = new PDEmbeddedFile(document, attachment.getInputStream());
|
||||||
|
embeddedFile.setSize((int) attachment.getSize());
|
||||||
|
|
||||||
|
GregorianCalendar currentTime = new GregorianCalendar();
|
||||||
|
embeddedFile.setCreationDate(currentTime);
|
||||||
|
embeddedFile.setModDate(currentTime);
|
||||||
|
|
||||||
|
String contentType = attachment.getContentType();
|
||||||
|
if (contentType != null && !contentType.trim().isEmpty()) {
|
||||||
|
embeddedFile.setSubtype(contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
PDComplexFileSpecification fileSpecification = new PDComplexFileSpecification();
|
||||||
|
fileSpecification.setFile(uniqueFilename);
|
||||||
|
fileSpecification.setFileUnicode(uniqueFilename);
|
||||||
|
fileSpecification.setEmbeddedFile(embeddedFile);
|
||||||
|
fileSpecification.setEmbeddedFileUnicode(embeddedFile);
|
||||||
|
|
||||||
|
existingNames.put(uniqueFilename, fileSpecification);
|
||||||
|
}
|
||||||
|
|
||||||
|
embeddedFilesTree.setNames(existingNames);
|
||||||
|
documentNames.setEmbeddedFiles(embeddedFilesTree);
|
||||||
|
catalog.setNames(documentNames);
|
||||||
|
|
||||||
|
return indexToFilenameMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addAttachmentAnnotationsToDocumentWithMapping(
|
||||||
|
PDDocument document,
|
||||||
|
List<EmlParser.EmailAttachment> attachments,
|
||||||
|
Map<Integer, String> indexToFilenameMap)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
if (document.getNumberOfPages() == 0 || attachments == null || attachments.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentMarkerPositionFinder finder = new AttachmentMarkerPositionFinder();
|
||||||
|
finder.setSortByPosition(false); // Keep document order to maintain pairing
|
||||||
|
finder.getText(document);
|
||||||
|
List<MarkerPosition> markerPositions = finder.getPositions();
|
||||||
|
|
||||||
|
int annotationsToAdd = Math.min(markerPositions.size(), attachments.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < annotationsToAdd; i++) {
|
||||||
|
MarkerPosition position = markerPositions.get(i);
|
||||||
|
|
||||||
|
String filenameNearMarker = position.getFilename();
|
||||||
|
|
||||||
|
EmlParser.EmailAttachment matchingAttachment =
|
||||||
|
findAttachmentByFilename(attachments, filenameNearMarker);
|
||||||
|
|
||||||
|
if (matchingAttachment != null) {
|
||||||
|
String embeddedFilename =
|
||||||
|
findEmbeddedFilenameForAttachment(matchingAttachment, indexToFilenameMap);
|
||||||
|
|
||||||
|
if (embeddedFilename != null) {
|
||||||
|
PDPage page = document.getPage(position.getPageIndex());
|
||||||
|
addAttachmentAnnotationToPageWithMapping(
|
||||||
|
document,
|
||||||
|
page,
|
||||||
|
matchingAttachment,
|
||||||
|
embeddedFilename,
|
||||||
|
position.getX(),
|
||||||
|
position.getY(),
|
||||||
|
i);
|
||||||
|
} else {
|
||||||
|
// No embedded filename found for attachment
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No matching attachment found for filename near marker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EmlParser.EmailAttachment findAttachmentByFilename(
|
||||||
|
List<EmlParser.EmailAttachment> attachments, String targetFilename) {
|
||||||
|
if (targetFilename == null || targetFilename.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalizedTarget = normalizeFilename(targetFilename);
|
||||||
|
|
||||||
|
// First try exact match
|
||||||
|
for (EmlParser.EmailAttachment attachment : attachments) {
|
||||||
|
if (attachment.getFilename() != null) {
|
||||||
|
String normalizedAttachment = normalizeFilename(attachment.getFilename());
|
||||||
|
if (normalizedAttachment.equals(normalizedTarget)) {
|
||||||
|
return attachment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try contains match
|
||||||
|
for (EmlParser.EmailAttachment attachment : attachments) {
|
||||||
|
if (attachment.getFilename() != null) {
|
||||||
|
String normalizedAttachment = normalizeFilename(attachment.getFilename());
|
||||||
|
if (normalizedAttachment.contains(normalizedTarget)
|
||||||
|
|| normalizedTarget.contains(normalizedAttachment)) {
|
||||||
|
return attachment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String findEmbeddedFilenameForAttachment(
|
||||||
|
EmlParser.EmailAttachment attachment, Map<Integer, String> indexToFilenameMap) {
|
||||||
|
|
||||||
|
String attachmentFilename = attachment.getFilename();
|
||||||
|
if (attachmentFilename == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<Integer, String> entry : indexToFilenameMap.entrySet()) {
|
||||||
|
String embeddedFilename = entry.getValue();
|
||||||
|
if (embeddedFilename != null
|
||||||
|
&& (embeddedFilename.equals(attachmentFilename)
|
||||||
|
|| embeddedFilename.contains(attachmentFilename)
|
||||||
|
|| attachmentFilename.contains(embeddedFilename))) {
|
||||||
|
return embeddedFilename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeFilename(String filename) {
|
||||||
|
if (filename == null) return "";
|
||||||
|
return filename.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replaceAll("\\s+", " ")
|
||||||
|
.replaceAll("[^a-zA-Z0-9._-]", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addAttachmentAnnotationToPageWithMapping(
|
||||||
|
PDDocument document,
|
||||||
|
PDPage page,
|
||||||
|
EmlParser.EmailAttachment attachment,
|
||||||
|
String embeddedFilename,
|
||||||
|
float x,
|
||||||
|
float y,
|
||||||
|
int attachmentIndex)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
PDAnnotationFileAttachment fileAnnotation = new PDAnnotationFileAttachment();
|
||||||
|
|
||||||
|
PDRectangle rect = calculateAnnotationRectangle(page, x, y);
|
||||||
|
fileAnnotation.setRectangle(rect);
|
||||||
|
|
||||||
|
fileAnnotation.setPrinted(false);
|
||||||
|
fileAnnotation.setHidden(false);
|
||||||
|
fileAnnotation.setNoView(false);
|
||||||
|
fileAnnotation.setNoZoom(true);
|
||||||
|
fileAnnotation.setNoRotate(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
PDAppearanceDictionary appearance = new PDAppearanceDictionary();
|
||||||
|
PDAppearanceStream normalAppearance = new PDAppearanceStream(document);
|
||||||
|
normalAppearance.setBBox(new PDRectangle(0, 0, rect.getWidth(), rect.getHeight()));
|
||||||
|
appearance.setNormalAppearance(normalAppearance);
|
||||||
|
fileAnnotation.setAppearance(appearance);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
fileAnnotation.setAppearance(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
PDEmbeddedFilesNameTreeNode efTree =
|
||||||
|
document.getDocumentCatalog().getNames().getEmbeddedFiles();
|
||||||
|
if (efTree != null) {
|
||||||
|
Map<String, PDComplexFileSpecification> efMap = efTree.getNames();
|
||||||
|
if (efMap != null) {
|
||||||
|
PDComplexFileSpecification fileSpec = efMap.get(embeddedFilename);
|
||||||
|
if (fileSpec != null) {
|
||||||
|
fileAnnotation.setFile(fileSpec);
|
||||||
|
} else {
|
||||||
|
// Could not find embedded file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileAnnotation.setContents(
|
||||||
|
"Attachment " + (attachmentIndex + 1) + ": " + attachment.getFilename());
|
||||||
|
fileAnnotation.setAnnotationName(
|
||||||
|
"EmbeddedFile_" + attachmentIndex + "_" + embeddedFilename);
|
||||||
|
|
||||||
|
page.getAnnotations().add(fileAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAscii(String str) {
|
||||||
|
if (str == null) return true;
|
||||||
|
for (int i = 0; i < str.length(); i++) {
|
||||||
|
if (str.charAt(i) > 127) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,7 @@ import io.github.pixee.security.Filenames;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -145,13 +146,18 @@ public class PdfUtils {
|
|||||||
throws IOException, Exception {
|
throws IOException, Exception {
|
||||||
|
|
||||||
// Validate and limit DPI to prevent excessive memory usage
|
// Validate and limit DPI to prevent excessive memory usage
|
||||||
final int MAX_SAFE_DPI = 500; // Maximum safe DPI to prevent memory issues
|
int maxSafeDpi = 500; // Default maximum safe DPI
|
||||||
if (DPI > MAX_SAFE_DPI) {
|
ApplicationProperties properties =
|
||||||
|
ApplicationContextProvider.getBean(ApplicationProperties.class);
|
||||||
|
if (properties != null && properties.getSystem() != null) {
|
||||||
|
maxSafeDpi = properties.getSystem().getMaxDPI();
|
||||||
|
}
|
||||||
|
if (DPI > maxSafeDpi) {
|
||||||
throw ExceptionUtils.createIllegalArgumentException(
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
"error.dpiExceedsLimit",
|
"error.dpiExceedsLimit",
|
||||||
"DPI value {0} exceeds maximum safe limit of {1}. High DPI values can cause memory issues and crashes. Please use a lower DPI value.",
|
"DPI value {0} exceeds maximum safe limit of {1}. High DPI values can cause memory issues and crashes. Please use a lower DPI value.",
|
||||||
DPI,
|
DPI,
|
||||||
MAX_SAFE_DPI);
|
maxSafeDpi);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (PDDocument document = pdfDocumentFactory.load(inputStream)) {
|
try (PDDocument document = pdfDocumentFactory.load(inputStream)) {
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
package stirling.software.common.model;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
|
import org.springframework.core.env.StandardEnvironment;
|
||||||
|
|
||||||
|
import stirling.software.common.configuration.InstallationPathConfig;
|
||||||
|
|
||||||
|
class ApplicationPropertiesDynamicYamlPropertySourceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void loads_yaml_into_environment() throws Exception {
|
||||||
|
// YAML-Config in Temp-Datei schreiben
|
||||||
|
String yaml =
|
||||||
|
""
|
||||||
|
+ "ui:\n"
|
||||||
|
+ " appName: \"My App\"\n"
|
||||||
|
+ "system:\n"
|
||||||
|
+ " enableAnalytics: true\n";
|
||||||
|
Path tmp = Files.createTempFile("spdf-settings-", ".yml");
|
||||||
|
Files.writeString(tmp, yaml);
|
||||||
|
|
||||||
|
// Pfad per statischem Mock liefern
|
||||||
|
try (MockedStatic<InstallationPathConfig> mocked =
|
||||||
|
Mockito.mockStatic(InstallationPathConfig.class)) {
|
||||||
|
mocked.when(InstallationPathConfig::getSettingsPath).thenReturn(tmp.toString());
|
||||||
|
|
||||||
|
ConfigurableEnvironment env = new StandardEnvironment();
|
||||||
|
ApplicationProperties props = new ApplicationProperties();
|
||||||
|
|
||||||
|
props.dynamicYamlPropertySource(env); // fügt PropertySource an erster Stelle ein
|
||||||
|
|
||||||
|
assertEquals("My App", env.getProperty("ui.appName"));
|
||||||
|
assertEquals("true", env.getProperty("system.enableAnalytics"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void throws_when_settings_file_missing() throws Exception {
|
||||||
|
String missing = "/path/does/not/exist/spdf.yml";
|
||||||
|
try (MockedStatic<InstallationPathConfig> mocked =
|
||||||
|
Mockito.mockStatic(InstallationPathConfig.class)) {
|
||||||
|
mocked.when(InstallationPathConfig::getSettingsPath).thenReturn(missing);
|
||||||
|
|
||||||
|
ConfigurableEnvironment env = new StandardEnvironment();
|
||||||
|
ApplicationProperties props = new ApplicationProperties();
|
||||||
|
|
||||||
|
assertThrows(IOException.class, () -> props.dynamicYamlPropertySource(env));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,248 @@
|
|||||||
|
package stirling.software.common.model;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import stirling.software.common.model.ApplicationProperties.Driver;
|
||||||
|
import stirling.software.common.model.ApplicationProperties.Premium;
|
||||||
|
import stirling.software.common.model.ApplicationProperties.Security;
|
||||||
|
import stirling.software.common.model.exception.UnsupportedProviderException;
|
||||||
|
|
||||||
|
class ApplicationPropertiesLogicTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void system_isAnalyticsEnabled_null_false_true() {
|
||||||
|
ApplicationProperties.System sys = new ApplicationProperties.System();
|
||||||
|
|
||||||
|
sys.setEnableAnalytics(null);
|
||||||
|
assertFalse(sys.isAnalyticsEnabled());
|
||||||
|
|
||||||
|
sys.setEnableAnalytics(Boolean.FALSE);
|
||||||
|
assertFalse(sys.isAnalyticsEnabled());
|
||||||
|
|
||||||
|
sys.setEnableAnalytics(Boolean.TRUE);
|
||||||
|
assertTrue(sys.isAnalyticsEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tempFileManagement_defaults_and_overrides() {
|
||||||
|
ApplicationProperties.TempFileManagement tfm =
|
||||||
|
new ApplicationProperties.TempFileManagement();
|
||||||
|
|
||||||
|
String expectedBase =
|
||||||
|
java.lang.System.getProperty("java.io.tmpdir").replaceAll("/+$", "")
|
||||||
|
+ "/stirling-pdf";
|
||||||
|
assertEquals(expectedBase, tfm.getBaseTmpDir());
|
||||||
|
|
||||||
|
String expectedLibre = expectedBase + "/libreoffice";
|
||||||
|
assertEquals(expectedLibre, tfm.getLibreofficeDir());
|
||||||
|
|
||||||
|
tfm.setBaseTmpDir("/custom/base");
|
||||||
|
assertEquals("/custom/base", tfm.getBaseTmpDir());
|
||||||
|
|
||||||
|
tfm.setLibreofficeDir("/opt/libre");
|
||||||
|
assertEquals("/opt/libre", tfm.getLibreofficeDir());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void oauth2_scope_parsing_and_validity() {
|
||||||
|
Security.OAUTH2 oauth2 = new Security.OAUTH2();
|
||||||
|
oauth2.setIssuer("https://issuer");
|
||||||
|
oauth2.setClientId("client");
|
||||||
|
oauth2.setClientSecret("secret");
|
||||||
|
oauth2.setUseAsUsername("email");
|
||||||
|
oauth2.setScopes("openid, profile ,email");
|
||||||
|
assertTrue(oauth2.isSettingsValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void security_login_method_flags() {
|
||||||
|
Security sec = new Security();
|
||||||
|
|
||||||
|
sec.getOauth2().setEnabled(true);
|
||||||
|
sec.getSaml2().setEnabled(true);
|
||||||
|
|
||||||
|
assertTrue(sec.isUserPass());
|
||||||
|
assertTrue(sec.isOauth2Active());
|
||||||
|
assertTrue(sec.isSaml2Active());
|
||||||
|
|
||||||
|
sec.setLoginMethod(Security.LoginMethods.NORMAL.toString());
|
||||||
|
assertTrue(sec.isUserPass());
|
||||||
|
assertFalse(sec.isOauth2Active());
|
||||||
|
assertFalse(sec.isSaml2Active());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void security_isAltLogin_reflects_oauth2_or_saml2() {
|
||||||
|
Security sec = new Security();
|
||||||
|
|
||||||
|
assertFalse(sec.isAltLogin());
|
||||||
|
|
||||||
|
sec.getOauth2().setEnabled(true);
|
||||||
|
sec.getSaml2().setEnabled(false);
|
||||||
|
assertTrue(sec.isAltLogin());
|
||||||
|
|
||||||
|
sec.getOauth2().setEnabled(false);
|
||||||
|
sec.getSaml2().setEnabled(true);
|
||||||
|
assertTrue(sec.isAltLogin());
|
||||||
|
|
||||||
|
sec.getOauth2().setEnabled(true);
|
||||||
|
sec.getSaml2().setEnabled(true);
|
||||||
|
assertTrue(sec.isAltLogin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void oauth2_client_provider_mapping_and_unsupported() throws UnsupportedProviderException {
|
||||||
|
Security.OAUTH2.Client client = new Security.OAUTH2.Client();
|
||||||
|
|
||||||
|
assertNotNull(client.get("google"));
|
||||||
|
assertNotNull(client.get("github"));
|
||||||
|
assertNotNull(client.get("keycloak"));
|
||||||
|
|
||||||
|
UnsupportedProviderException ex =
|
||||||
|
assertThrows(UnsupportedProviderException.class, () -> client.get("unknown"));
|
||||||
|
assertTrue(ex.getMessage().toLowerCase().contains("not supported"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void premium_google_drive_getters_return_empty_string_on_null_or_blank() {
|
||||||
|
Premium.ProFeatures.GoogleDrive gd = new Premium.ProFeatures.GoogleDrive();
|
||||||
|
|
||||||
|
assertEquals("", gd.getClientId());
|
||||||
|
assertEquals("", gd.getApiKey());
|
||||||
|
assertEquals("", gd.getAppId());
|
||||||
|
|
||||||
|
gd.setClientId(" id ");
|
||||||
|
gd.setApiKey(" key ");
|
||||||
|
gd.setAppId(" app ");
|
||||||
|
assertEquals(" id ", gd.getClientId());
|
||||||
|
assertEquals(" key ", gd.getApiKey());
|
||||||
|
assertEquals(" app ", gd.getAppId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void ui_getters_return_null_for_blank() {
|
||||||
|
ApplicationProperties.Ui ui = new ApplicationProperties.Ui();
|
||||||
|
ui.setAppName(" ");
|
||||||
|
ui.setHomeDescription("");
|
||||||
|
ui.setAppNameNavbar(null);
|
||||||
|
|
||||||
|
assertNull(ui.getAppName());
|
||||||
|
assertNull(ui.getHomeDescription());
|
||||||
|
assertNull(ui.getAppNameNavbar());
|
||||||
|
|
||||||
|
ui.setAppName("Stirling-PDF");
|
||||||
|
ui.setHomeDescription("Home");
|
||||||
|
ui.setAppNameNavbar("Nav");
|
||||||
|
assertEquals("Stirling-PDF", ui.getAppName());
|
||||||
|
assertEquals("Home", ui.getHomeDescription());
|
||||||
|
assertEquals("Nav", ui.getAppNameNavbar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void driver_toString_contains_driver_name() {
|
||||||
|
assertTrue(Driver.H2.toString().contains("h2"));
|
||||||
|
assertTrue(Driver.POSTGRESQL.toString().contains("postgresql"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void session_limits_and_timeouts_have_reasonable_defaults() {
|
||||||
|
ApplicationProperties.ProcessExecutor pe = new ApplicationProperties.ProcessExecutor();
|
||||||
|
|
||||||
|
ApplicationProperties.ProcessExecutor.SessionLimit s = pe.getSessionLimit();
|
||||||
|
assertEquals(2, s.getQpdfSessionLimit());
|
||||||
|
assertEquals(1, s.getTesseractSessionLimit());
|
||||||
|
assertEquals(1, s.getLibreOfficeSessionLimit());
|
||||||
|
assertEquals(1, s.getPdfToHtmlSessionLimit());
|
||||||
|
assertEquals(8, s.getPythonOpenCvSessionLimit());
|
||||||
|
assertEquals(16, s.getWeasyPrintSessionLimit());
|
||||||
|
assertEquals(1, s.getInstallAppSessionLimit());
|
||||||
|
assertEquals(1, s.getCalibreSessionLimit());
|
||||||
|
assertEquals(8, s.getGhostscriptSessionLimit());
|
||||||
|
assertEquals(2, s.getOcrMyPdfSessionLimit());
|
||||||
|
|
||||||
|
ApplicationProperties.ProcessExecutor.TimeoutMinutes t = pe.getTimeoutMinutes();
|
||||||
|
assertEquals(30, t.getTesseractTimeoutMinutes());
|
||||||
|
assertEquals(30, t.getQpdfTimeoutMinutes());
|
||||||
|
assertEquals(30, t.getLibreOfficeTimeoutMinutes());
|
||||||
|
assertEquals(20, t.getPdfToHtmlTimeoutMinutes());
|
||||||
|
assertEquals(30, t.getPythonOpenCvTimeoutMinutes());
|
||||||
|
assertEquals(30, t.getWeasyPrintTimeoutMinutes());
|
||||||
|
assertEquals(60, t.getInstallAppTimeoutMinutes());
|
||||||
|
assertEquals(30, t.getCalibreTimeoutMinutes());
|
||||||
|
assertEquals(30, t.getGhostscriptTimeoutMinutes());
|
||||||
|
assertEquals(30, t.getOcrMyPdfTimeoutMinutes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Test
|
||||||
|
void enterprise_metadata_defaults() {
|
||||||
|
ApplicationProperties.EnterpriseEdition ee = new ApplicationProperties.EnterpriseEdition();
|
||||||
|
ApplicationProperties.EnterpriseEdition.CustomMetadata eMeta = ee.getCustomMetadata();
|
||||||
|
eMeta.setCreator(" ");
|
||||||
|
eMeta.setProducer(null);
|
||||||
|
assertEquals("Stirling-PDF", eMeta.getCreator());
|
||||||
|
assertEquals("Stirling-PDF", eMeta.getProducer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void premium_metadata_defaults() {
|
||||||
|
Premium.ProFeatures pf = new Premium.ProFeatures();
|
||||||
|
Premium.ProFeatures.CustomMetadata pMeta = pf.getCustomMetadata();
|
||||||
|
pMeta.setCreator("");
|
||||||
|
pMeta.setProducer("");
|
||||||
|
assertEquals("Stirling-PDF", pMeta.getCreator());
|
||||||
|
assertEquals("Stirling-PDF", pMeta.getProducer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void premium_metadata_awesome() {
|
||||||
|
Premium.ProFeatures pf = new Premium.ProFeatures();
|
||||||
|
Premium.ProFeatures.CustomMetadata pMeta = pf.getCustomMetadata();
|
||||||
|
pMeta.setCreator("Awesome PDF Tool");
|
||||||
|
pMeta.setProducer("Awesome PDF Tool");
|
||||||
|
assertEquals("Awesome PDF Tool", pMeta.getCreator());
|
||||||
|
assertEquals("Awesome PDF Tool", pMeta.getProducer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void string_isValid_handles_null_empty_blank_and_trimmed() {
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth2 = new ApplicationProperties.Security.OAUTH2();
|
||||||
|
|
||||||
|
assertFalse(oauth2.isValid((String) null, "issuer"));
|
||||||
|
assertFalse(oauth2.isValid("", "issuer"));
|
||||||
|
assertFalse(oauth2.isValid(" ", "issuer"));
|
||||||
|
|
||||||
|
assertTrue(oauth2.isValid("x", "issuer"));
|
||||||
|
assertTrue(oauth2.isValid(" x ", "issuer")); // trimmt intern
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void collection_isValid_handles_null_and_empty() {
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth2 = new ApplicationProperties.Security.OAUTH2();
|
||||||
|
|
||||||
|
Collection<String> nullColl = null;
|
||||||
|
Collection<String> empty = List.of();
|
||||||
|
|
||||||
|
assertFalse(oauth2.isValid(nullColl, "scopes"));
|
||||||
|
assertFalse(oauth2.isValid(empty, "scopes"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void collection_isValid_true_when_non_empty_even_if_element_is_blank() {
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth2 = new ApplicationProperties.Security.OAUTH2();
|
||||||
|
|
||||||
|
// Aktuelles Verhalten: prüft NUR !isEmpty(), nicht Inhalt
|
||||||
|
Collection<String> oneBlank = new ArrayList<>();
|
||||||
|
oneBlank.add(" ");
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
oauth2.isValid(oneBlank, "scopes"),
|
||||||
|
"Dokumentiert aktuelles Verhalten: nicht-leere Liste gilt als gültig, auch wenn Element leer/blank ist");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package stirling.software.common.model;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
|
import okhttp3.mockwebserver.MockResponse;
|
||||||
|
import okhttp3.mockwebserver.MockWebServer;
|
||||||
|
|
||||||
|
class ApplicationPropertiesSaml2HttpTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void idpMetadataUri_http_is_resolved_via_mockwebserver() throws Exception {
|
||||||
|
try (MockWebServer server = new MockWebServer()) {
|
||||||
|
server.enqueue(
|
||||||
|
new MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.addHeader("Content-Type", "application/xml")
|
||||||
|
.setBody("<EntityDescriptor/>"));
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
String url = server.url("/meta").toString();
|
||||||
|
|
||||||
|
var s = new ApplicationProperties.Security.SAML2();
|
||||||
|
s.setIdpMetadataUri(url);
|
||||||
|
|
||||||
|
try (InputStream in = s.getIdpMetadataUri()) {
|
||||||
|
String body = new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
assertTrue(body.contains("EntityDescriptor"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void idpMetadataUri_invalidUri_triggers_catch_and_throwsIOException() {
|
||||||
|
// Ungültige URI -> new URI(...) wirft URISyntaxException -> catch -> IOException
|
||||||
|
var s = new ApplicationProperties.Security.SAML2();
|
||||||
|
s.setIdpMetadataUri("http:##invalid uri"); // absichtlich kaputt (Leerzeichen + ##)
|
||||||
|
|
||||||
|
assertThrows(IOException.class, s::getIdpMetadataUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void spCert_else_branch_returns_FileSystemResource_for_filesystem_path() throws Exception {
|
||||||
|
var s = new ApplicationProperties.Security.SAML2();
|
||||||
|
|
||||||
|
// temporäre Datei simuliert "Filesystem"-Pfad (-> else-Zweig)
|
||||||
|
Path tmp = Files.createTempFile("spdf-spcert-", ".crt");
|
||||||
|
Files.writeString(tmp, "CERT");
|
||||||
|
|
||||||
|
s.setSpCert(tmp.toString());
|
||||||
|
Resource r = s.getSpCert();
|
||||||
|
|
||||||
|
assertNotNull(r);
|
||||||
|
assertTrue(r instanceof FileSystemResource, "Expected FileSystemResource for FS path");
|
||||||
|
assertTrue(r.exists(), "Temp file should exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void idpCert_else_branch_returns_FileSystemResource_even_if_missing() {
|
||||||
|
var s = new ApplicationProperties.Security.SAML2();
|
||||||
|
|
||||||
|
// bewusst nicht existierender Pfad -> else-Zweig wird trotzdem genommen
|
||||||
|
String missing = "/this/path/does/not/exist/idp.crt";
|
||||||
|
s.setIdpCert(missing);
|
||||||
|
Resource r = s.getIdpCert();
|
||||||
|
|
||||||
|
assertNotNull(r);
|
||||||
|
assertTrue(r instanceof FileSystemResource, "Expected FileSystemResource for FS path");
|
||||||
|
assertFalse(r.exists(), "Resource should not exist for missing file");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package stirling.software.common.model;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
|
class ApplicationPropertiesSaml2ResourceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void idpMetadataUri_classpath_is_resolved() throws Exception {
|
||||||
|
var s = new ApplicationProperties.Security.SAML2();
|
||||||
|
s.setIdpMetadataUri("classpath:saml/dummy.txt");
|
||||||
|
|
||||||
|
try (InputStream in = s.getIdpMetadataUri()) {
|
||||||
|
assertNotNull(in, "Classpath InputStream should not be null");
|
||||||
|
String txt = new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
assertTrue(txt.contains("ok"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void spCert_idpCert_privateKey_null_classpath_and_filesystem() throws Exception {
|
||||||
|
var s = new ApplicationProperties.Security.SAML2();
|
||||||
|
|
||||||
|
s.setSpCert(null);
|
||||||
|
s.setIdpCert(null);
|
||||||
|
s.setPrivateKey(null);
|
||||||
|
assertNull(s.getSpCert());
|
||||||
|
assertNull(s.getIdpCert());
|
||||||
|
assertNull(s.getPrivateKey());
|
||||||
|
|
||||||
|
s.setSpCert("classpath:saml/dummy.txt");
|
||||||
|
s.setIdpCert("classpath:saml/dummy.txt");
|
||||||
|
s.setPrivateKey("classpath:saml/dummy.txt");
|
||||||
|
Resource sp = s.getSpCert();
|
||||||
|
Resource idp = s.getIdpCert();
|
||||||
|
Resource pk = s.getPrivateKey();
|
||||||
|
assertTrue(sp.exists());
|
||||||
|
assertTrue(idp.exists());
|
||||||
|
assertTrue(pk.exists());
|
||||||
|
|
||||||
|
Path tmp = Files.createTempFile("spdf-key-", ".pem");
|
||||||
|
Files.writeString(tmp, "KEY");
|
||||||
|
s.setPrivateKey(tmp.toString());
|
||||||
|
Resource pkFs = s.getPrivateKey();
|
||||||
|
assertNotNull(pkFs);
|
||||||
|
assertTrue(pkFs.exists());
|
||||||
|
}
|
||||||
|
}
|
1
app/common/src/test/resources/saml/dummy.txt
Normal file
1
app/common/src/test/resources/saml/dummy.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
ok
|
@ -91,7 +91,7 @@ dependencies {
|
|||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pcx:$imageioVersion@
|
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pcx:$imageioVersion@
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pict:$imageioVersion"
|
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pict:$imageioVersion"
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pnm:$imageioVersion"
|
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pnm:$imageioVersion"
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-psd:$imageioVersion"
|
runtimeOnly "com.twelvemonkeys.imageio:imageio-psd:$imageioVersion"
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-sgi:$imageioVersion"
|
// runtimeOnly "com.twelvemonkeys.imageio:imageio-sgi:$imageioVersion"
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-tga:$imageioVersion"
|
// runtimeOnly "com.twelvemonkeys.imageio:imageio-tga:$imageioVersion"
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-thumbsdb:$imageioVersion"
|
// runtimeOnly "com.twelvemonkeys.imageio:imageio-thumbsdb:$imageioVersion"
|
||||||
|
@ -8,6 +8,8 @@ import org.springframework.web.servlet.ModelAndView;
|
|||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
|
import stirling.software.common.util.ApplicationContextProvider;
|
||||||
import stirling.software.common.util.CheckProgramInstall;
|
import stirling.software.common.util.CheckProgramInstall;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@ -62,6 +64,13 @@ public class ConverterWebController {
|
|||||||
@Hidden
|
@Hidden
|
||||||
public String pdfToimgForm(Model model) {
|
public String pdfToimgForm(Model model) {
|
||||||
boolean isPython = CheckProgramInstall.isPythonAvailable();
|
boolean isPython = CheckProgramInstall.isPythonAvailable();
|
||||||
|
ApplicationProperties properties =
|
||||||
|
ApplicationContextProvider.getBean(ApplicationProperties.class);
|
||||||
|
if (properties != null && properties.getSystem() != null) {
|
||||||
|
model.addAttribute("maxDPI", properties.getSystem().getMaxDPI());
|
||||||
|
} else {
|
||||||
|
model.addAttribute("maxDPI", 500); // Default value if not set
|
||||||
|
}
|
||||||
model.addAttribute("isPython", isPython);
|
model.addAttribute("isPython", isPython);
|
||||||
model.addAttribute("currentPage", "pdf-to-img");
|
model.addAttribute("currentPage", "pdf-to-img");
|
||||||
return "convert/pdf-to-img";
|
return "convert/pdf-to-img";
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=المفضل
|
|||||||
settings.title=الإعدادات
|
settings.title=الإعدادات
|
||||||
settings.update=التحديث متاح
|
settings.update=التحديث متاح
|
||||||
settings.updateAvailable={0} هو الإصدار المثبت حاليًا. إصدار جديد ({1}) متاح.
|
settings.updateAvailable={0} هو الإصدار المثبت حاليًا. إصدار جديد ({1}) متاح.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=إصدار التطبيق:
|
settings.appVersion=إصدار التطبيق:
|
||||||
settings.downloadOption.title=تحديد خيار التنزيل (للتنزيلات ذات الملف الواحد غير المضغوط):
|
settings.downloadOption.title=تحديد خيار التنزيل (للتنزيلات ذات الملف الواحد غير المضغوط):
|
||||||
settings.downloadOption.1=فتح في نفس النافذة
|
settings.downloadOption.1=فتح في نفس النافذة
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=نوع اللون
|
|||||||
pdfToImage.color=اللون
|
pdfToImage.color=اللون
|
||||||
pdfToImage.grey=تدرج الرمادي
|
pdfToImage.grey=تدرج الرمادي
|
||||||
pdfToImage.blackwhite=أبيض وأسود (قد يفقد البيانات!)
|
pdfToImage.blackwhite=أبيض وأسود (قد يفقد البيانات!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=تحويل
|
pdfToImage.submit=تحويل
|
||||||
pdfToImage.info=Python غير مثبت. مطلوب لتحويل WebP.
|
pdfToImage.info=Python غير مثبت. مطلوب لتحويل WebP.
|
||||||
pdfToImage.placeholder=(مثال: 1,2,8 أو 4,7,12-16 أو 2n-1)
|
pdfToImage.placeholder=(مثال: 1,2,8 أو 4,7,12-16 أو 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Populyar
|
|||||||
settings.title=Parametrlər
|
settings.title=Parametrlər
|
||||||
settings.update=Yeniləmə mövcuddur
|
settings.update=Yeniləmə mövcuddur
|
||||||
settings.updateAvailable={0} cari quraşdırılmış versiyadır. Yeni ({1}) versiyası mövcuddur.
|
settings.updateAvailable={0} cari quraşdırılmış versiyadır. Yeni ({1}) versiyası mövcuddur.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Proqram Versiyası:
|
settings.appVersion=Proqram Versiyası:
|
||||||
settings.downloadOption.title=Yükləmə versiyasını seçin (Tək fayllı zip olmayan yükləmələr üçün):
|
settings.downloadOption.title=Yükləmə versiyasını seçin (Tək fayllı zip olmayan yükləmələr üçün):
|
||||||
settings.downloadOption.1=Eyni pəncərədə açın
|
settings.downloadOption.1=Eyni pəncərədə açın
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Rəng Tipi
|
|||||||
pdfToImage.color=Rəng
|
pdfToImage.color=Rəng
|
||||||
pdfToImage.grey=Boz Tonlama
|
pdfToImage.grey=Boz Tonlama
|
||||||
pdfToImage.blackwhite=Qara və Ağ (Data İtə Bilər)
|
pdfToImage.blackwhite=Qara və Ağ (Data İtə Bilər)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Çevir
|
pdfToImage.submit=Çevir
|
||||||
pdfToImage.info=Python Yüklü Deyil.WebP Çevirməsi Üçün Vacibdir
|
pdfToImage.info=Python Yüklü Deyil.WebP Çevirməsi Üçün Vacibdir
|
||||||
pdfToImage.placeholder=(məsələn, 1,2,8 və ya 4,7,12-16 və ya 2n-1)
|
pdfToImage.placeholder=(məsələn, 1,2,8 və ya 4,7,12-16 və ya 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Популярни
|
|||||||
settings.title=Настройки
|
settings.title=Настройки
|
||||||
settings.update=Налична актуализация
|
settings.update=Налична актуализация
|
||||||
settings.updateAvailable={0} е текущата инсталирана версия. Налична е нова версия ({1}).
|
settings.updateAvailable={0} е текущата инсталирана версия. Налична е нова версия ({1}).
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Версия на приложението:
|
settings.appVersion=Версия на приложението:
|
||||||
settings.downloadOption.title=Изберете опция за изтегляне (за изтегляния на един файл без да е архивиран):
|
settings.downloadOption.title=Изберете опция за изтегляне (за изтегляния на един файл без да е архивиран):
|
||||||
settings.downloadOption.1=Отваряне в същия прозорец
|
settings.downloadOption.1=Отваряне в същия прозорец
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Тип цвят
|
|||||||
pdfToImage.color=Цвят
|
pdfToImage.color=Цвят
|
||||||
pdfToImage.grey=Скала на сивото
|
pdfToImage.grey=Скала на сивото
|
||||||
pdfToImage.blackwhite=Черно и бяло (може да загубите данни!)
|
pdfToImage.blackwhite=Черно и бяло (може да загубите данни!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Преобразуване
|
pdfToImage.submit=Преобразуване
|
||||||
pdfToImage.info=Python не е инсталиран. Изисква се за конвертиране на WebP.
|
pdfToImage.info=Python не е инсталиран. Изисква се за конвертиране на WebP.
|
||||||
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=སྤྱི་མོས།
|
|||||||
settings.title=སྒྲིག་འགོད།
|
settings.title=སྒྲིག་འགོད།
|
||||||
settings.update=གསར་སྒྱུར་ཡོད།
|
settings.update=གསར་སྒྱུར་ཡོད།
|
||||||
settings.updateAvailable={0} ནི་ད་ལྟ་སྒྲིག་འཇུག་བྱས་པའི་པར་གཞི་ཡིན། པར་གཞི་གསར་པ་ ({1}) ཡོད།
|
settings.updateAvailable={0} ནི་ད་ལྟ་སྒྲིག་འཇུག་བྱས་པའི་པར་གཞི་ཡིན། པར་གཞི་གསར་པ་ ({1}) ཡོད།
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=མཉེན་ཆས་པར་གཞི།
|
settings.appVersion=མཉེན་ཆས་པར་གཞི།
|
||||||
settings.downloadOption.title=ཕབ་ལེན་གདམ་ག་འདེམས་རོགས། (ཡིག་ཆ་རྐྱང་པ་ zip མིན་པའི་ཕབ་ལེན་ཆེད།):
|
settings.downloadOption.title=ཕབ་ལེན་གདམ་ག་འདེམས་རོགས། (ཡིག་ཆ་རྐྱང་པ་ zip མིན་པའི་ཕབ་ལེན་ཆེད།):
|
||||||
settings.downloadOption.1=སྒེའུ་ཁུང་གཅིག་པའི་ནང་ཁ་ཕྱེ།
|
settings.downloadOption.1=སྒེའུ་ཁུང་གཅིག་པའི་ནང་ཁ་ཕྱེ།
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=ཚོས་མདོག་གི་རིགས།
|
|||||||
pdfToImage.color=ཚོས་མདོག
|
pdfToImage.color=ཚོས་མདོག
|
||||||
pdfToImage.grey=སྐྱ་མདོག
|
pdfToImage.grey=སྐྱ་མདོག
|
||||||
pdfToImage.blackwhite=དཀར་ནག (གནས་ཚུལ་བརླག་སྲིད།)
|
pdfToImage.blackwhite=དཀར་ནག (གནས་ཚུལ་བརླག་སྲིད།)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=བསྒྱུར་བ།
|
pdfToImage.submit=བསྒྱུར་བ།
|
||||||
pdfToImage.info=Python སྒྲིག་འཇུག་བྱས་མི་འདུག WebP བསྒྱུར་བར་དགོས་མཁོ་ཡིན།
|
pdfToImage.info=Python སྒྲིག་འཇུག་བྱས་མི་འདུག WebP བསྒྱུར་བར་དགོས་མཁོ་ཡིན།
|
||||||
pdfToImage.placeholder=(དཔེར་ན། 1,2,8 ཡང་ན་ 4,7,12-16 ཡང་ན་ 2n-1)
|
pdfToImage.placeholder=(དཔེར་ན། 1,2,8 ཡང་ན་ 4,7,12-16 ཡང་ན་ 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popular
|
|||||||
settings.title=Opcions
|
settings.title=Opcions
|
||||||
settings.update=Actualització Disponible
|
settings.update=Actualització Disponible
|
||||||
settings.updateAvailable=La versió actual instal·lada és {0}. Una nova versió ({1}) està disponible.
|
settings.updateAvailable=La versió actual instal·lada és {0}. Una nova versió ({1}) està disponible.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Versió de l'App:
|
settings.appVersion=Versió de l'App:
|
||||||
settings.downloadOption.title=Trieu l'opció de descàrrega (per a descàrregues d'un sol fitxer no comprimit):
|
settings.downloadOption.title=Trieu l'opció de descàrrega (per a descàrregues d'un sol fitxer no comprimit):
|
||||||
settings.downloadOption.1=Obre en la mateixa finestra
|
settings.downloadOption.1=Obre en la mateixa finestra
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Tipus de Color
|
|||||||
pdfToImage.color=Color
|
pdfToImage.color=Color
|
||||||
pdfToImage.grey=Escala de Grisos
|
pdfToImage.grey=Escala de Grisos
|
||||||
pdfToImage.blackwhite=Blanc i Negre (Pot perdre dades!)
|
pdfToImage.blackwhite=Blanc i Negre (Pot perdre dades!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Converteix
|
pdfToImage.submit=Converteix
|
||||||
pdfToImage.info=Python no està instal·lat. És necessari per a la conversió a WebP.
|
pdfToImage.info=Python no està instal·lat. És necessari per a la conversió a WebP.
|
||||||
pdfToImage.placeholder=(p. ex. 1,2,8 o 4,7,12-16 o 2n-1)
|
pdfToImage.placeholder=(p. ex. 1,2,8 o 4,7,12-16 o 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Oblíbené
|
|||||||
settings.title=Nastavení
|
settings.title=Nastavení
|
||||||
settings.update=K dispozici je aktualizace
|
settings.update=K dispozici je aktualizace
|
||||||
settings.updateAvailable={0} je aktuálně nainstalovaná verze. Je k dispozici nová verze ({1}).
|
settings.updateAvailable={0} je aktuálně nainstalovaná verze. Je k dispozici nová verze ({1}).
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Verze aplikace:
|
settings.appVersion=Verze aplikace:
|
||||||
settings.downloadOption.title=Vyberte možnost stahování (Pro stahování jednoho souboru bez zipu):
|
settings.downloadOption.title=Vyberte možnost stahování (Pro stahování jednoho souboru bez zipu):
|
||||||
settings.downloadOption.1=Otevřít ve stejném okně
|
settings.downloadOption.1=Otevřít ve stejném okně
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Typ barev
|
|||||||
pdfToImage.color=Barevný
|
pdfToImage.color=Barevný
|
||||||
pdfToImage.grey=Stupně šedi
|
pdfToImage.grey=Stupně šedi
|
||||||
pdfToImage.blackwhite=Černobílý (Může dojít ke ztrátě dat!)
|
pdfToImage.blackwhite=Černobílý (Může dojít ke ztrátě dat!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Převést
|
pdfToImage.submit=Převést
|
||||||
pdfToImage.info=Python není nainstalován. Vyžadován pro konverzi do WebP.
|
pdfToImage.info=Python není nainstalován. Vyžadován pro konverzi do WebP.
|
||||||
pdfToImage.placeholder=(např. 1,2,8 nebo 4,7,12-16 nebo 2n-1)
|
pdfToImage.placeholder=(např. 1,2,8 nebo 4,7,12-16 nebo 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Populære
|
|||||||
settings.title=Indstillinger
|
settings.title=Indstillinger
|
||||||
settings.update=Opdatering tilgængelig
|
settings.update=Opdatering tilgængelig
|
||||||
settings.updateAvailable={0} er den aktuelt installerede version. En ny version ({1}) er tilgængelig.
|
settings.updateAvailable={0} er den aktuelt installerede version. En ny version ({1}) er tilgængelig.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=App Version:
|
settings.appVersion=App Version:
|
||||||
settings.downloadOption.title=Vælg download mulighed (For enkelt fil ikke-zip downloads):
|
settings.downloadOption.title=Vælg download mulighed (For enkelt fil ikke-zip downloads):
|
||||||
settings.downloadOption.1=Åbn i samme vindue
|
settings.downloadOption.1=Åbn i samme vindue
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Farvetype
|
|||||||
pdfToImage.color=Farve
|
pdfToImage.color=Farve
|
||||||
pdfToImage.grey=Gråtone
|
pdfToImage.grey=Gråtone
|
||||||
pdfToImage.blackwhite=Sort og Hvid (Kan miste data!)
|
pdfToImage.blackwhite=Sort og Hvid (Kan miste data!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Konvertér
|
pdfToImage.submit=Konvertér
|
||||||
pdfToImage.info=Python er ikke installeret. Påkrævet for WebP-konvertering.
|
pdfToImage.info=Python er ikke installeret. Påkrævet for WebP-konvertering.
|
||||||
pdfToImage.placeholder=(f.eks. 1,2,8 eller 4,7,12-16 eller 2n-1)
|
pdfToImage.placeholder=(f.eks. 1,2,8 eller 4,7,12-16 eller 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Beliebt
|
|||||||
settings.title=Einstellungen
|
settings.title=Einstellungen
|
||||||
settings.update=Update verfügbar
|
settings.update=Update verfügbar
|
||||||
settings.updateAvailable={0} ist die aktuelle installierte Version. Eine neue Version ({1}) ist verfügbar.
|
settings.updateAvailable={0} ist die aktuelle installierte Version. Eine neue Version ({1}) ist verfügbar.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=App-Version:
|
settings.appVersion=App-Version:
|
||||||
settings.downloadOption.title=Download-Option wählen (für einzelne Dateien, die keine Zip-Downloads sind):
|
settings.downloadOption.title=Download-Option wählen (für einzelne Dateien, die keine Zip-Downloads sind):
|
||||||
settings.downloadOption.1=Im selben Fenster öffnen
|
settings.downloadOption.1=Im selben Fenster öffnen
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Farbtyp
|
|||||||
pdfToImage.color=Farbe
|
pdfToImage.color=Farbe
|
||||||
pdfToImage.grey=Graustufen
|
pdfToImage.grey=Graustufen
|
||||||
pdfToImage.blackwhite=Schwarzweiß (Datenverlust möglich!)
|
pdfToImage.blackwhite=Schwarzweiß (Datenverlust möglich!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Umwandeln
|
pdfToImage.submit=Umwandeln
|
||||||
pdfToImage.info=Python ist nicht installiert. Erforderlich für die WebP-Konvertierung.
|
pdfToImage.info=Python ist nicht installiert. Erforderlich für die WebP-Konvertierung.
|
||||||
pdfToImage.placeholder=(z.B. 1,2,8 oder 4,7,12-16 oder 2n-1)
|
pdfToImage.placeholder=(z.B. 1,2,8 oder 4,7,12-16 oder 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Vorhandene Lesezeichen ersetzen (deaktiviere
|
|||||||
editTableOfContents.editorTitle=Lesezeichen-Editor
|
editTableOfContents.editorTitle=Lesezeichen-Editor
|
||||||
editTableOfContents.editorDesc=Fügen unten Lesezeichen hinzu und ordne sie an. Klicke auf +, um das untergeordnete Lesezeichen hinzuzufügen.
|
editTableOfContents.editorDesc=Fügen unten Lesezeichen hinzu und ordne sie an. Klicke auf +, um das untergeordnete Lesezeichen hinzuzufügen.
|
||||||
editTableOfContents.addBookmark=Neues Lesezeichen hinzufügen
|
editTableOfContents.addBookmark=Neues Lesezeichen hinzufügen
|
||||||
|
editTableOfContents.importBookmarksDefault=Importieren
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=JSON-Datei hochladen
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Aus Zwischenablage einfügen
|
||||||
|
editTableOfContents.exportBookmarksDefault=Exportieren
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Als JSON herunterladen
|
||||||
|
editTableOfContents.exportBookmarksAsText=Als Text kopieren
|
||||||
editTableOfContents.desc.1=Mit diesem Werkzeug können Sie das Inhaltsverzeichnis (Lesezeichen) eines PDF-Dokuments hinzufügen oder bearbeiten.
|
editTableOfContents.desc.1=Mit diesem Werkzeug können Sie das Inhaltsverzeichnis (Lesezeichen) eines PDF-Dokuments hinzufügen oder bearbeiten.
|
||||||
editTableOfContents.desc.2=Sie können eine hierarchische Struktur erstellen, indem Sie untergeordnete Lesezeichen zu übergeordneten hinzufügen.
|
editTableOfContents.desc.2=Sie können eine hierarchische Struktur erstellen, indem Sie untergeordnete Lesezeichen zu übergeordneten hinzufügen.
|
||||||
editTableOfContents.desc.3=Jedes Lesezeichen benötigt einen Titel und eine Seitenzahl.
|
editTableOfContents.desc.3=Jedes Lesezeichen benötigt einen Titel und eine Seitenzahl.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Δημοφιλή
|
|||||||
settings.title=Ρυθμίσεις
|
settings.title=Ρυθμίσεις
|
||||||
settings.update=Διαθέσιμη ενημέρωση
|
settings.update=Διαθέσιμη ενημέρωση
|
||||||
settings.updateAvailable={0} είναι η τρέχουσα εγκατεστημένη έκδοση. Μια νέα έκδοση ({1}) είναι διαθέσιμη.
|
settings.updateAvailable={0} είναι η τρέχουσα εγκατεστημένη έκδοση. Μια νέα έκδοση ({1}) είναι διαθέσιμη.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Έκδοση εφαρμογής:
|
settings.appVersion=Έκδοση εφαρμογής:
|
||||||
settings.downloadOption.title=Επιλογή λήψης (Για μεμονωμένη λήψη αρχείων χωρίς συμπίεση):
|
settings.downloadOption.title=Επιλογή λήψης (Για μεμονωμένη λήψη αρχείων χωρίς συμπίεση):
|
||||||
settings.downloadOption.1=Άνοιγμα στο ίδιο παράθυρο
|
settings.downloadOption.1=Άνοιγμα στο ίδιο παράθυρο
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Τύπος χρώματος
|
|||||||
pdfToImage.color=Έγχρωμο
|
pdfToImage.color=Έγχρωμο
|
||||||
pdfToImage.grey=Κλίμακα του γκρι
|
pdfToImage.grey=Κλίμακα του γκρι
|
||||||
pdfToImage.blackwhite=Ασπρόμαυρο (Μπορεί να χαθούν δεδομένα!)
|
pdfToImage.blackwhite=Ασπρόμαυρο (Μπορεί να χαθούν δεδομένα!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Μετατροπή
|
pdfToImage.submit=Μετατροπή
|
||||||
pdfToImage.info=Η Python δεν είναι εγκατεστημένη. Απαιτείται για μετατροπή WebP.
|
pdfToImage.info=Η Python δεν είναι εγκατεστημένη. Απαιτείται για μετατροπή WebP.
|
||||||
pdfToImage.placeholder=(π.χ. 1,2,8 ή 4,7,12-16 ή 2n-1)
|
pdfToImage.placeholder=(π.χ. 1,2,8 ή 4,7,12-16 ή 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popular
|
|||||||
settings.title=Settings
|
settings.title=Settings
|
||||||
settings.update=Update available
|
settings.update=Update available
|
||||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=App Version:
|
settings.appVersion=App Version:
|
||||||
settings.downloadOption.title=Choose download option (For single file non zip downloads):
|
settings.downloadOption.title=Choose download option (For single file non zip downloads):
|
||||||
settings.downloadOption.1=Open in same window
|
settings.downloadOption.1=Open in same window
|
||||||
@ -569,12 +601,12 @@ rotate.tags=server side
|
|||||||
|
|
||||||
|
|
||||||
home.imageToPdf.title=Image to PDF
|
home.imageToPdf.title=Image to PDF
|
||||||
home.imageToPdf.desc=Convert a image (PNG, JPEG, GIF) to PDF.
|
home.imageToPdf.desc=Convert a image (PNG, JPEG, GIF, PSD) to PDF.
|
||||||
imageToPdf.tags=conversion,img,jpg,picture,photo
|
imageToPdf.tags=conversion,img,jpg,picture,photo,psd,photoshop
|
||||||
|
|
||||||
home.pdfToImage.title=PDF to Image
|
home.pdfToImage.title=PDF to Image
|
||||||
home.pdfToImage.desc=Convert a PDF to a image. (PNG, JPEG, GIF)
|
home.pdfToImage.desc=Convert a PDF to a image. (PNG, JPEG, GIF, PSD)
|
||||||
pdfToImage.tags=conversion,img,jpg,picture,photo
|
pdfToImage.tags=conversion,img,jpg,picture,photo,psd,photoshop
|
||||||
|
|
||||||
home.pdfOrganiser.title=Organise
|
home.pdfOrganiser.title=Organise
|
||||||
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
|
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
|
||||||
@ -1402,6 +1434,7 @@ pdfToImage.colorType=Colour type
|
|||||||
pdfToImage.color=Colour
|
pdfToImage.color=Colour
|
||||||
pdfToImage.grey=Greyscale
|
pdfToImage.grey=Greyscale
|
||||||
pdfToImage.blackwhite=Black and White (May lose data!)
|
pdfToImage.blackwhite=Black and White (May lose data!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Convert
|
pdfToImage.submit=Convert
|
||||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||||
@ -1859,6 +1892,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popular
|
|||||||
settings.title=Settings
|
settings.title=Settings
|
||||||
settings.update=Update available
|
settings.update=Update available
|
||||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=App Version:
|
settings.appVersion=App Version:
|
||||||
settings.downloadOption.title=Choose download option (For single file non zip downloads):
|
settings.downloadOption.title=Choose download option (For single file non zip downloads):
|
||||||
settings.downloadOption.1=Open in same window
|
settings.downloadOption.1=Open in same window
|
||||||
@ -566,12 +598,12 @@ rotate.tags=server side
|
|||||||
|
|
||||||
|
|
||||||
home.imageToPdf.title=Image to PDF
|
home.imageToPdf.title=Image to PDF
|
||||||
home.imageToPdf.desc=Convert a image (PNG, JPEG, GIF) to PDF.
|
home.imageToPdf.desc=Convert a image (PNG, JPEG, GIF, PSD) to PDF.
|
||||||
imageToPdf.tags=conversion,img,jpg,picture,photo
|
imageToPdf.tags=conversion,img,jpg,picture,photo,psd,photoshop
|
||||||
|
|
||||||
home.pdfToImage.title=PDF to Image
|
home.pdfToImage.title=PDF to Image
|
||||||
home.pdfToImage.desc=Convert a PDF to a image. (PNG, JPEG, GIF)
|
home.pdfToImage.desc=Convert a PDF to a image. (PNG, JPEG, GIF, PSD)
|
||||||
pdfToImage.tags=conversion,img,jpg,picture,photo
|
pdfToImage.tags=conversion,img,jpg,picture,photo,psd,photoshop
|
||||||
|
|
||||||
home.pdfOrganiser.title=Organize
|
home.pdfOrganiser.title=Organize
|
||||||
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
|
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Color type
|
|||||||
pdfToImage.color=Color
|
pdfToImage.color=Color
|
||||||
pdfToImage.grey=Grayscale
|
pdfToImage.grey=Grayscale
|
||||||
pdfToImage.blackwhite=Black and White (May lose data!)
|
pdfToImage.blackwhite=Black and White (May lose data!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Convert
|
pdfToImage.submit=Convert
|
||||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Populares
|
|||||||
settings.title=Configuración
|
settings.title=Configuración
|
||||||
settings.update=Actualización disponible
|
settings.update=Actualización disponible
|
||||||
settings.updateAvailable={0} es la versión instalada. Hay disponible una versión nueva ({1}).
|
settings.updateAvailable={0} es la versión instalada. Hay disponible una versión nueva ({1}).
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Versión de la aplicación:
|
settings.appVersion=Versión de la aplicación:
|
||||||
settings.downloadOption.title=Elegir la opción de descarga (para descargas de un solo archivo sin ZIP):
|
settings.downloadOption.title=Elegir la opción de descarga (para descargas de un solo archivo sin ZIP):
|
||||||
settings.downloadOption.1=Abrir en la misma ventana
|
settings.downloadOption.1=Abrir en la misma ventana
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Tipo de color
|
|||||||
pdfToImage.color=Color
|
pdfToImage.color=Color
|
||||||
pdfToImage.grey=Escala de grises
|
pdfToImage.grey=Escala de grises
|
||||||
pdfToImage.blackwhite=Blanco y Negro (¡Puede perder datos!)
|
pdfToImage.blackwhite=Blanco y Negro (¡Puede perder datos!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Convertir
|
pdfToImage.submit=Convertir
|
||||||
pdfToImage.info=Python no está instalado. Se requiere para la conversión WebP.
|
pdfToImage.info=Python no está instalado. Se requiere para la conversión WebP.
|
||||||
pdfToImage.placeholder=(por ejemplo 1,2,8 o 4,7,12-16 o 2n-1)
|
pdfToImage.placeholder=(por ejemplo 1,2,8 o 4,7,12-16 o 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popular
|
|||||||
settings.title=Ezarpenak
|
settings.title=Ezarpenak
|
||||||
settings.update=Eguneratze eskuragarria
|
settings.update=Eguneratze eskuragarria
|
||||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Aplikazioaren bertsioa:
|
settings.appVersion=Aplikazioaren bertsioa:
|
||||||
settings.downloadOption.title=Hautatu deskargatzeko aukera (fitxategi bakarra deskargatzeko ZIP gabe):
|
settings.downloadOption.title=Hautatu deskargatzeko aukera (fitxategi bakarra deskargatzeko ZIP gabe):
|
||||||
settings.downloadOption.1=Ireki leiho berean
|
settings.downloadOption.1=Ireki leiho berean
|
||||||
@ -567,11 +599,11 @@ rotate.tags=server side
|
|||||||
|
|
||||||
home.imageToPdf.title=Irudia PDF bihurtu
|
home.imageToPdf.title=Irudia PDF bihurtu
|
||||||
home.imageToPdf.desc=Irudi bat(PNG, JPEG, GIF)PDF bihurtu
|
home.imageToPdf.desc=Irudi bat(PNG, JPEG, GIF)PDF bihurtu
|
||||||
imageToPdf.tags=conversion,img,jpg,picture,photo
|
imageToPdf.tags=conversion,img,jpg,picture,photo,psd,photoshop
|
||||||
|
|
||||||
home.pdfToImage.title=PDFa irudi bihurtu
|
home.pdfToImage.title=PDFa irudi bihurtu
|
||||||
home.pdfToImage.desc=PDF bat irudi (PNG, JPEG, GIF) bihurtu
|
home.pdfToImage.desc=PDF bat irudi (PNG, JPEG, GIF) bihurtu
|
||||||
pdfToImage.tags=conversion,img,jpg,picture,photo
|
pdfToImage.tags=conversion,img,jpg,picture,photo,psd,photoshop
|
||||||
|
|
||||||
home.pdfOrganiser.title=Antolatzailea
|
home.pdfOrganiser.title=Antolatzailea
|
||||||
home.pdfOrganiser.desc=Ezabatu/Berrantolatu orrialdeak edozein ordenatan
|
home.pdfOrganiser.desc=Ezabatu/Berrantolatu orrialdeak edozein ordenatan
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Kolore-mota
|
|||||||
pdfToImage.color=Kolorea
|
pdfToImage.color=Kolorea
|
||||||
pdfToImage.grey=Gris-eskala
|
pdfToImage.grey=Gris-eskala
|
||||||
pdfToImage.blackwhite=Zuria eta Beltza (Datuak galdu ditzake!)
|
pdfToImage.blackwhite=Zuria eta Beltza (Datuak galdu ditzake!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Bihurtu
|
pdfToImage.submit=Bihurtu
|
||||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=محبوب
|
|||||||
settings.title=تنظیمات
|
settings.title=تنظیمات
|
||||||
settings.update=بهروزرسانی موجود است
|
settings.update=بهروزرسانی موجود است
|
||||||
settings.updateAvailable={0} نسخه نصب شده فعلی است. یک نسخه جدید ({1}) موجود است.
|
settings.updateAvailable={0} نسخه نصب شده فعلی است. یک نسخه جدید ({1}) موجود است.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=نسخه برنامه:
|
settings.appVersion=نسخه برنامه:
|
||||||
settings.downloadOption.title=گزینه دانلود را انتخاب کنید (برای دانلود یک فایل غیر فشرده):
|
settings.downloadOption.title=گزینه دانلود را انتخاب کنید (برای دانلود یک فایل غیر فشرده):
|
||||||
settings.downloadOption.1=باز کردن در همان پنجره
|
settings.downloadOption.1=باز کردن در همان پنجره
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=نوع رنگ
|
|||||||
pdfToImage.color=رنگ
|
pdfToImage.color=رنگ
|
||||||
pdfToImage.grey=خاکستری
|
pdfToImage.grey=خاکستری
|
||||||
pdfToImage.blackwhite=سیاه و سفید (ممکن است اطلاعات از دست برود!)
|
pdfToImage.blackwhite=سیاه و سفید (ممکن است اطلاعات از دست برود!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=تبدیل
|
pdfToImage.submit=تبدیل
|
||||||
pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
|
pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
|
||||||
pdfToImage.placeholder=(مثال: 1,2,8 یا 4,7,12-16 یا 2n-1)
|
pdfToImage.placeholder=(مثال: 1,2,8 یا 4,7,12-16 یا 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Populaire
|
|||||||
settings.title=Paramètres
|
settings.title=Paramètres
|
||||||
settings.update=Mise à jour disponible
|
settings.update=Mise à jour disponible
|
||||||
settings.updateAvailable={0} est la version actuellement installée. Une nouvelle version ({1}) est disponible.
|
settings.updateAvailable={0} est la version actuellement installée. Une nouvelle version ({1}) est disponible.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Version de l'application :
|
settings.appVersion=Version de l'application :
|
||||||
settings.downloadOption.title=Choisissez l'option de téléchargement (pour les téléchargements à fichier unique non ZIP) :
|
settings.downloadOption.title=Choisissez l'option de téléchargement (pour les téléchargements à fichier unique non ZIP) :
|
||||||
settings.downloadOption.1=Ouvrir dans la même fenêtre
|
settings.downloadOption.1=Ouvrir dans la même fenêtre
|
||||||
@ -566,7 +598,7 @@ rotate.tags=pivoter,server side,rotate
|
|||||||
|
|
||||||
|
|
||||||
home.imageToPdf.title=Image en PDF
|
home.imageToPdf.title=Image en PDF
|
||||||
home.imageToPdf.desc=Convertissez une image (PNG, JPEG, GIF) en PDF.
|
home.imageToPdf.desc=Convertissez une image (PNG, JPEG, GIF, PSD) en PDF.
|
||||||
imageToPdf.tags=pdf,conversion,img,jpg,image,photo
|
imageToPdf.tags=pdf,conversion,img,jpg,image,photo
|
||||||
|
|
||||||
home.pdfToImage.title=PDF en image
|
home.pdfToImage.title=PDF en image
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Type d'impression
|
|||||||
pdfToImage.color=Couleur
|
pdfToImage.color=Couleur
|
||||||
pdfToImage.grey=Niveaux de gris
|
pdfToImage.grey=Niveaux de gris
|
||||||
pdfToImage.blackwhite=Noir et blanc (peut engendrer une perte de données !)
|
pdfToImage.blackwhite=Noir et blanc (peut engendrer une perte de données !)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Convertir
|
pdfToImage.submit=Convertir
|
||||||
pdfToImage.info=Python n'est pas installé. Nécessaire pour la conversion WebP.
|
pdfToImage.info=Python n'est pas installé. Nécessaire pour la conversion WebP.
|
||||||
pdfToImage.placeholder=(par exemple : 1,2,8 ou 4,7,12-16 ou 2n-1)
|
pdfToImage.placeholder=(par exemple : 1,2,8 ou 4,7,12-16 ou 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Remplacer les signets existants (décocher p
|
|||||||
editTableOfContents.editorTitle=Éditeur de signets
|
editTableOfContents.editorTitle=Éditeur de signets
|
||||||
editTableOfContents.editorDesc=Ajoutez et organisez les signets ci-dessous. Cliquez sur + pour ajouter des signets enfants.
|
editTableOfContents.editorDesc=Ajoutez et organisez les signets ci-dessous. Cliquez sur + pour ajouter des signets enfants.
|
||||||
editTableOfContents.addBookmark=Ajouter un nouveau signet
|
editTableOfContents.addBookmark=Ajouter un nouveau signet
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=Cet outil vous permet d'ajouter ou de modifier la table des matières (signets) dans un document PDF.
|
editTableOfContents.desc.1=Cet outil vous permet d'ajouter ou de modifier la table des matières (signets) dans un document PDF.
|
||||||
editTableOfContents.desc.2=Vous pouvez créer une structure hiérarchique en ajoutant des signets enfants à des signets parents.
|
editTableOfContents.desc.2=Vous pouvez créer une structure hiérarchique en ajoutant des signets enfants à des signets parents.
|
||||||
editTableOfContents.desc.3=Chaque signet nécessite un titre et un numéro de page cible.
|
editTableOfContents.desc.3=Chaque signet nécessite un titre et un numéro de page cible.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Coitianta
|
|||||||
settings.title=Socruithe
|
settings.title=Socruithe
|
||||||
settings.update=Nuashonrú ar fáil
|
settings.update=Nuashonrú ar fáil
|
||||||
settings.updateAvailable=Is é {0} an leagan suiteáilte reatha. Tá leagan nua ({1}) ar fáil.
|
settings.updateAvailable=Is é {0} an leagan suiteáilte reatha. Tá leagan nua ({1}) ar fáil.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Leagan Aipe:
|
settings.appVersion=Leagan Aipe:
|
||||||
settings.downloadOption.title=Roghnaigh rogha íoslódála (Le haghaidh íoslódálacha comhad amháin seachas zip):
|
settings.downloadOption.title=Roghnaigh rogha íoslódála (Le haghaidh íoslódálacha comhad amháin seachas zip):
|
||||||
settings.downloadOption.1=Oscail sa bhfuinneog chéanna
|
settings.downloadOption.1=Oscail sa bhfuinneog chéanna
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Cineál dath
|
|||||||
pdfToImage.color=Dath
|
pdfToImage.color=Dath
|
||||||
pdfToImage.grey=Scála Liath
|
pdfToImage.grey=Scála Liath
|
||||||
pdfToImage.blackwhite=Dubh agus Bán (D’fhéadfadh sonraí a chailleadh!)
|
pdfToImage.blackwhite=Dubh agus Bán (D’fhéadfadh sonraí a chailleadh!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Tiontaigh
|
pdfToImage.submit=Tiontaigh
|
||||||
pdfToImage.info=Níl Python suiteáilte. Ag teastáil le haghaidh comhshó WebP.
|
pdfToImage.info=Níl Python suiteáilte. Ag teastáil le haghaidh comhshó WebP.
|
||||||
pdfToImage.placeholder=(m.sh. 1,2,8 nó 4,7,12-16 nó 2n-1)
|
pdfToImage.placeholder=(m.sh. 1,2,8 nó 4,7,12-16 nó 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=लोकप्रिय
|
|||||||
settings.title=सेटिंग्स
|
settings.title=सेटिंग्स
|
||||||
settings.update=अपडेट उपलब्ध है
|
settings.update=अपडेट उपलब्ध है
|
||||||
settings.updateAvailable={0} वर्तमान स्थापित संस्करण है। एक नया संस्करण ({1}) उपलब्ध है।
|
settings.updateAvailable={0} वर्तमान स्थापित संस्करण है। एक नया संस्करण ({1}) उपलब्ध है।
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=ऐप संस्करण:
|
settings.appVersion=ऐप संस्करण:
|
||||||
settings.downloadOption.title=डाउनलोड विकल्प चुनें (एकल फ़ाइल गैर-ज़िप डाउनलोड के लिए):
|
settings.downloadOption.title=डाउनलोड विकल्प चुनें (एकल फ़ाइल गैर-ज़िप डाउनलोड के लिए):
|
||||||
settings.downloadOption.1=उसी विंडो में खोलें
|
settings.downloadOption.1=उसी विंडो में खोलें
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=रंग प्रकार
|
|||||||
pdfToImage.color=रंग
|
pdfToImage.color=रंग
|
||||||
pdfToImage.grey=ग्रेस्केल
|
pdfToImage.grey=ग्रेस्केल
|
||||||
pdfToImage.blackwhite=काला और सफेद (डेटा खो सकता है!)
|
pdfToImage.blackwhite=काला और सफेद (डेटा खो सकता है!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=बदलें
|
pdfToImage.submit=बदलें
|
||||||
pdfToImage.info=Python स्थापित नहीं है। WebP रूपांतरण के लिए आवश्यक है।
|
pdfToImage.info=Python स्थापित नहीं है। WebP रूपांतरण के लिए आवश्यक है।
|
||||||
pdfToImage.placeholder=(जैसे 1,2,8 या 4,7,12-16 या 2n-1)
|
pdfToImage.placeholder=(जैसे 1,2,8 या 4,7,12-16 या 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popularno
|
|||||||
settings.title=Postavke
|
settings.title=Postavke
|
||||||
settings.update=Dostupno ažuriranje
|
settings.update=Dostupno ažuriranje
|
||||||
settings.updateAvailable={0} je trenutno instalirana verzija. Dostupna je nova verzija ({1}).
|
settings.updateAvailable={0} je trenutno instalirana verzija. Dostupna je nova verzija ({1}).
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Verzija aplikacije:
|
settings.appVersion=Verzija aplikacije:
|
||||||
settings.downloadOption.title=Odaberite opciju preuzimanja (Za preuzimanje pojedinačnih datoteka bez zip formata):
|
settings.downloadOption.title=Odaberite opciju preuzimanja (Za preuzimanje pojedinačnih datoteka bez zip formata):
|
||||||
settings.downloadOption.1=Otvori u istom prozoru
|
settings.downloadOption.1=Otvori u istom prozoru
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Tip boje
|
|||||||
pdfToImage.color=Boja
|
pdfToImage.color=Boja
|
||||||
pdfToImage.grey=Sivi tonovi
|
pdfToImage.grey=Sivi tonovi
|
||||||
pdfToImage.blackwhite=Crno-bijelo (mogu se izgubiti podaci!)
|
pdfToImage.blackwhite=Crno-bijelo (mogu se izgubiti podaci!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Pretvori
|
pdfToImage.submit=Pretvori
|
||||||
pdfToImage.info=Python nije instaliran. Treba je za konverziju na WebP.
|
pdfToImage.info=Python nije instaliran. Treba je za konverziju na WebP.
|
||||||
pdfToImage.placeholder=(t.j. 1,2,8 ili 4,7,12-16 ili 2n-1)
|
pdfToImage.placeholder=(t.j. 1,2,8 ili 4,7,12-16 ili 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Népszerű
|
|||||||
settings.title=Beállítások
|
settings.title=Beállítások
|
||||||
settings.update=Frissítés elérhető
|
settings.update=Frissítés elérhető
|
||||||
settings.updateAvailable=A jelenlegi telepített verzió: {0}. Új verzió ({1}) érhető el.
|
settings.updateAvailable=A jelenlegi telepített verzió: {0}. Új verzió ({1}) érhető el.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Sürgős frissítés érhető el
|
||||||
|
update.updateAvailable=Frissítés érhető el
|
||||||
|
update.modalTitle=Frissítés érhető el
|
||||||
|
update.current=Jelenlegi verzió
|
||||||
|
update.latest=Legújabb verzió
|
||||||
|
update.latestStable=Legújabb stabil verzió
|
||||||
|
update.priority=Fontosság
|
||||||
|
update.recommendedAction=Ajánlott lépés
|
||||||
|
update.breakingChangesDetected=⚠️ Jelentős változások észlelve
|
||||||
|
update.breakingChangesMessage=Ez a frissítés jelentős változásokat tartalmaz. Kérjük, olvassa el az alábbi migrációs útmutatót.
|
||||||
|
update.migrationGuides=Migrációs útmutatók:
|
||||||
|
update.viewGuide=Útmutató megtekintése
|
||||||
|
update.loadingDetailedInfo=Részletes verzióinformációk betöltése folyamatban...
|
||||||
|
update.close=Bezárás
|
||||||
|
update.viewAllReleases=Összes kiadás megtekintése
|
||||||
|
update.downloadLatest=Legújabb verzió letöltése
|
||||||
|
update.availableUpdates=Elérhető frissítések:
|
||||||
|
update.unableToLoadDetails=Nem sikerült betölteni a részletes verzióinformációkat.
|
||||||
|
update.version=Verzió
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=SÜRGETŐ
|
||||||
|
update.priority.normal=NORMÁL
|
||||||
|
update.priority.minor=KISEBB
|
||||||
|
update.priority.low=ALACSONY
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Megszakító változások:
|
||||||
|
update.breakingChangesDefault=Ez a verzió megszakító változásokat tartalmaz
|
||||||
|
update.migrationGuide=Migrációs útmutató
|
||||||
settings.appVersion=Alkalmazás verziója:
|
settings.appVersion=Alkalmazás verziója:
|
||||||
settings.downloadOption.title=Letöltési beállítás (egyetlen fájl, nem tömörített letöltések esetén):
|
settings.downloadOption.title=Letöltési beállítás (egyetlen fájl, nem tömörített letöltések esetén):
|
||||||
settings.downloadOption.1=Megnyitás ugyanabban az ablakban
|
settings.downloadOption.1=Megnyitás ugyanabban az ablakban
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Színtípus
|
|||||||
pdfToImage.color=Színes
|
pdfToImage.color=Színes
|
||||||
pdfToImage.grey=Szürkeárnyalatos
|
pdfToImage.grey=Szürkeárnyalatos
|
||||||
pdfToImage.blackwhite=Fekete-fehér (adatvesztéssel járhat!)
|
pdfToImage.blackwhite=Fekete-fehér (adatvesztéssel járhat!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Konvertálás
|
pdfToImage.submit=Konvertálás
|
||||||
pdfToImage.info=Python nincs telepítve. WebP konverzióhoz szükséges.
|
pdfToImage.info=Python nincs telepítve. WebP konverzióhoz szükséges.
|
||||||
pdfToImage.placeholder=(pl. 1,2,8 vagy 4,7,12-16 vagy 2n-1)
|
pdfToImage.placeholder=(pl. 1,2,8 vagy 4,7,12-16 vagy 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Meglévő könyvjelzők cseréje (törölje
|
|||||||
editTableOfContents.editorTitle=Könyvjelző szerkesztő
|
editTableOfContents.editorTitle=Könyvjelző szerkesztő
|
||||||
editTableOfContents.editorDesc=Könyvjelzők hozzáadása és rendezése lent. Kattintson a + gombra gyermek könyvjelzők hozzáadásához.
|
editTableOfContents.editorDesc=Könyvjelzők hozzáadása és rendezése lent. Kattintson a + gombra gyermek könyvjelzők hozzáadásához.
|
||||||
editTableOfContents.addBookmark=Új könyvjelző hozzáadása
|
editTableOfContents.addBookmark=Új könyvjelző hozzáadása
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=Ez az eszköz lehetővé teszi a tartalomjegyzék (könyvjelzők) hozzáadását vagy szerkesztését egy PDF dokumentumban.
|
editTableOfContents.desc.1=Ez az eszköz lehetővé teszi a tartalomjegyzék (könyvjelzők) hozzáadását vagy szerkesztését egy PDF dokumentumban.
|
||||||
editTableOfContents.desc.2=Hierarchikus struktúrákat hozhat létre, ha gyermek könyvjelzőket ad a szülő könyvjelzőkhöz.
|
editTableOfContents.desc.2=Hierarchikus struktúrákat hozhat létre, ha gyermek könyvjelzőket ad a szülő könyvjelzőkhöz.
|
||||||
editTableOfContents.desc.3=Minden könyvjelzőhöz szükséges egy cím és egy céloldalszám.
|
editTableOfContents.desc.3=Minden könyvjelzőhöz szükséges egy cím és egy céloldalszám.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Populer
|
|||||||
settings.title=Pengaturan
|
settings.title=Pengaturan
|
||||||
settings.update=Pembaruan tersedia
|
settings.update=Pembaruan tersedia
|
||||||
settings.updateAvailable={0} adalah versi yang terpasang saat ini. Versi baru ({1}) tersedia.
|
settings.updateAvailable={0} adalah versi yang terpasang saat ini. Versi baru ({1}) tersedia.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Versi Aplikasi:
|
settings.appVersion=Versi Aplikasi:
|
||||||
settings.downloadOption.title=Pilih opsi unduhan (Untuk unduhan berkas tunggal non zip):
|
settings.downloadOption.title=Pilih opsi unduhan (Untuk unduhan berkas tunggal non zip):
|
||||||
settings.downloadOption.1=Buka di jendela yang sama
|
settings.downloadOption.1=Buka di jendela yang sama
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Tipe warna
|
|||||||
pdfToImage.color=Warna
|
pdfToImage.color=Warna
|
||||||
pdfToImage.grey=Skala abu-abu
|
pdfToImage.grey=Skala abu-abu
|
||||||
pdfToImage.blackwhite=Black and White (Bisa kehilangan data!)
|
pdfToImage.blackwhite=Black and White (Bisa kehilangan data!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Konversi
|
pdfToImage.submit=Konversi
|
||||||
pdfToImage.info=Python tidak terinstal. Diperlukan untuk konversi WebP.
|
pdfToImage.info=Python tidak terinstal. Diperlukan untuk konversi WebP.
|
||||||
pdfToImage.placeholder=(misalnya 1,2,8 atau 4,7,12-16 atau 2n-1)
|
pdfToImage.placeholder=(misalnya 1,2,8 atau 4,7,12-16 atau 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popolare
|
|||||||
settings.title=Impostazioni
|
settings.title=Impostazioni
|
||||||
settings.update=Aggiornamento disponibile
|
settings.update=Aggiornamento disponibile
|
||||||
settings.updateAvailable={0} è la versione attualmente installata. Una nuova versione ({1}) è disponibile.
|
settings.updateAvailable={0} è la versione attualmente installata. Una nuova versione ({1}) è disponibile.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Aggiornamento disponibile
|
||||||
|
update.updateAvailable=Aggiornamento disponibile
|
||||||
|
update.modalTitle=Aggiornamento disponibile
|
||||||
|
update.current=Corrente
|
||||||
|
update.latest=Ultimo
|
||||||
|
update.latestStable=Ultima versione stabile
|
||||||
|
update.priority=Priorità
|
||||||
|
update.recommendedAction=Azione consigliata
|
||||||
|
update.breakingChangesDetected=⚠️ Rilevate modifiche sostanziali
|
||||||
|
update.breakingChangesMessage=Questo aggiornamento contiene modifiche sostanziali. Consulta le guide alla migrazione riportate di seguito.
|
||||||
|
update.migrationGuides=Guide alla migrazione:
|
||||||
|
update.viewGuide=Visualizza la guida
|
||||||
|
update.loadingDetailedInfo=Caricamento delle informazioni dettagliate sulla versione...
|
||||||
|
update.close=Chiudi
|
||||||
|
update.viewAllReleases=Visualizza tutte le versioni
|
||||||
|
update.downloadLatest=Scarica l'ultima
|
||||||
|
update.availableUpdates=Aggiornamenti disponibili:
|
||||||
|
update.unableToLoadDetails=Impossibile caricare informazioni dettagliate sulla versione.
|
||||||
|
update.version=Versione
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENTE
|
||||||
|
update.priority.normal=NORMALE
|
||||||
|
update.priority.minor=MINORE
|
||||||
|
update.priority.low=BASSA
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Modifiche sostanziali:
|
||||||
|
update.breakingChangesDefault=Questa versione contiene modifiche sostanziali
|
||||||
|
update.migrationGuide=Guida alla migrazione
|
||||||
settings.appVersion=Versione App:
|
settings.appVersion=Versione App:
|
||||||
settings.downloadOption.title=Scegli opzione di download (Per file singoli non compressi):
|
settings.downloadOption.title=Scegli opzione di download (Per file singoli non compressi):
|
||||||
settings.downloadOption.1=Apri in questa finestra
|
settings.downloadOption.1=Apri in questa finestra
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Tipo di colore
|
|||||||
pdfToImage.color=A colori
|
pdfToImage.color=A colori
|
||||||
pdfToImage.grey=Scala di grigi
|
pdfToImage.grey=Scala di grigi
|
||||||
pdfToImage.blackwhite=Bianco e Nero (potresti perdere dettagli!)
|
pdfToImage.blackwhite=Bianco e Nero (potresti perdere dettagli!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Converti
|
pdfToImage.submit=Converti
|
||||||
pdfToImage.info=Python non è installato.È richiesto per la conversione WebP.
|
pdfToImage.info=Python non è installato.È richiesto per la conversione WebP.
|
||||||
pdfToImage.placeholder=(es. 1,2,8 o 4,7,12-16 o 2n-1)
|
pdfToImage.placeholder=(es. 1,2,8 o 4,7,12-16 o 2n-1)
|
||||||
@ -1660,7 +1693,7 @@ fileChooser.dragAndDrop=Trascina & Rilascia
|
|||||||
fileChooser.dragAndDropPDF=Trascina & rilascia il file PDF
|
fileChooser.dragAndDropPDF=Trascina & rilascia il file PDF
|
||||||
fileChooser.dragAndDropImage=Trascina & rilascia il file immagine
|
fileChooser.dragAndDropImage=Trascina & rilascia il file immagine
|
||||||
fileChooser.hoveredDragAndDrop=Trascina & rilascia i file qui
|
fileChooser.hoveredDragAndDrop=Trascina & rilascia i file qui
|
||||||
fileChooser.extractPDF=Estraendo...
|
fileChooser.extractPDF=Estrazione...
|
||||||
fileChooser.addAttachments=trascina & rilascia gli allegati qui
|
fileChooser.addAttachments=trascina & rilascia gli allegati qui
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Sostituisci i segnalibri esistenti (deselezi
|
|||||||
editTableOfContents.editorTitle=Editor segnalibri
|
editTableOfContents.editorTitle=Editor segnalibri
|
||||||
editTableOfContents.editorDesc=Aggiungi e disponi i segnalibri qui sotto. Fai clic su + per aggiungere segnalibri secondari.
|
editTableOfContents.editorDesc=Aggiungi e disponi i segnalibri qui sotto. Fai clic su + per aggiungere segnalibri secondari.
|
||||||
editTableOfContents.addBookmark=Aggiungi nuovo segnalibro
|
editTableOfContents.addBookmark=Aggiungi nuovo segnalibro
|
||||||
|
editTableOfContents.importBookmarksDefault=Importa
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Carica file JSON
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Incolla dagli appunti
|
||||||
|
editTableOfContents.exportBookmarksDefault=Esporta
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Scarica come JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copia come testo
|
||||||
editTableOfContents.desc.1=Questo strumento consente di aggiungere o modificare il sommario (segnalibri) in un documento PDF.
|
editTableOfContents.desc.1=Questo strumento consente di aggiungere o modificare il sommario (segnalibri) in un documento PDF.
|
||||||
editTableOfContents.desc.2=È possibile creare una struttura gerarchica aggiungendo segnalibri secondari a quelli principali.
|
editTableOfContents.desc.2=È possibile creare una struttura gerarchica aggiungendo segnalibri secondari a quelli principali.
|
||||||
editTableOfContents.desc.3=Ogni segnalibro richiede un titolo e un numero di pagina di destinazione.
|
editTableOfContents.desc.3=Ogni segnalibro richiede un titolo e un numero di pagina di destinazione.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=人気
|
|||||||
settings.title=設定
|
settings.title=設定
|
||||||
settings.update=利用可能なアップデート
|
settings.update=利用可能なアップデート
|
||||||
settings.updateAvailable=バージョン {0} がインストールされています。 新しいバージョン ({1}) が利用可能です。
|
settings.updateAvailable=バージョン {0} がインストールされています。 新しいバージョン ({1}) が利用可能です。
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Appバージョン:
|
settings.appVersion=Appバージョン:
|
||||||
settings.downloadOption.title=ダウンロードオプション(zip以外の単一ファイル):
|
settings.downloadOption.title=ダウンロードオプション(zip以外の単一ファイル):
|
||||||
settings.downloadOption.1=同じウィンドウで開く
|
settings.downloadOption.1=同じウィンドウで開く
|
||||||
@ -567,11 +599,11 @@ rotate.tags=server side
|
|||||||
|
|
||||||
home.imageToPdf.title=画像をPDFに変換
|
home.imageToPdf.title=画像をPDFに変換
|
||||||
home.imageToPdf.desc=画像 (PNG, JPEG, GIF) をPDFに変換します。
|
home.imageToPdf.desc=画像 (PNG, JPEG, GIF) をPDFに変換します。
|
||||||
imageToPdf.tags=conversion,img,jpg,picture,photo
|
imageToPdf.tags=conversion,img,jpg,picture,photo,psd,photoshop
|
||||||
|
|
||||||
home.pdfToImage.title=PDFを画像に変換
|
home.pdfToImage.title=PDFを画像に変換
|
||||||
home.pdfToImage.desc=PDFを画像 (PNG, JPEG, GIF) に変換します。
|
home.pdfToImage.desc=PDFを画像 (PNG, JPEG, GIF) に変換します。
|
||||||
pdfToImage.tags=conversion,img,jpg,picture,photo
|
pdfToImage.tags=conversion,img,jpg,picture,photo,psd,photoshop
|
||||||
|
|
||||||
home.pdfOrganiser.title=整理
|
home.pdfOrganiser.title=整理
|
||||||
home.pdfOrganiser.desc=ページの削除/並べ替えします。
|
home.pdfOrganiser.desc=ページの削除/並べ替えします。
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=カラーモード
|
|||||||
pdfToImage.color=カラー
|
pdfToImage.color=カラー
|
||||||
pdfToImage.grey=グレースケール
|
pdfToImage.grey=グレースケール
|
||||||
pdfToImage.blackwhite=白黒(データが失われる可能性があります!)
|
pdfToImage.blackwhite=白黒(データが失われる可能性があります!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=変換
|
pdfToImage.submit=変換
|
||||||
pdfToImage.info=Pythonがインストールされていません。WebPの変換に必要です。
|
pdfToImage.info=Pythonがインストールされていません。WebPの変換に必要です。
|
||||||
pdfToImage.placeholder=(例:1,2,8、4,7,12-16、2n-1)
|
pdfToImage.placeholder=(例:1,2,8、4,7,12-16、2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=既存のしおりを置き換える(既
|
|||||||
editTableOfContents.editorTitle=しおりエディター
|
editTableOfContents.editorTitle=しおりエディター
|
||||||
editTableOfContents.editorDesc=以下にしおりを追加して配置します。+をクリックして、子のしおりを追加します。
|
editTableOfContents.editorDesc=以下にしおりを追加して配置します。+をクリックして、子のしおりを追加します。
|
||||||
editTableOfContents.addBookmark=新しいしおりを追加
|
editTableOfContents.addBookmark=新しいしおりを追加
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=このツールを使用すると、PDFドキュメントに目次(しおり)を追加または編集できます。
|
editTableOfContents.desc.1=このツールを使用すると、PDFドキュメントに目次(しおり)を追加または編集できます。
|
||||||
editTableOfContents.desc.2=親しおりに子しおりを追加することで階層構造を作成できます。
|
editTableOfContents.desc.2=親しおりに子しおりを追加することで階層構造を作成できます。
|
||||||
editTableOfContents.desc.3=各しおりにはタイトルと対象のページ番号が必要です。
|
editTableOfContents.desc.3=各しおりにはタイトルと対象のページ番号が必要です。
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=인기
|
|||||||
settings.title=설정
|
settings.title=설정
|
||||||
settings.update=업데이트 가능
|
settings.update=업데이트 가능
|
||||||
settings.updateAvailable={0}은(는) 현재 설치된 버전입니다. 새 버전({1})이 사용 가능합니다.
|
settings.updateAvailable={0}은(는) 현재 설치된 버전입니다. 새 버전({1})이 사용 가능합니다.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=앱 버전:
|
settings.appVersion=앱 버전:
|
||||||
settings.downloadOption.title=다운로드 옵션 선택 (단일 파일 비압축 다운로드용):
|
settings.downloadOption.title=다운로드 옵션 선택 (단일 파일 비압축 다운로드용):
|
||||||
settings.downloadOption.1=같은 창에서 열기
|
settings.downloadOption.1=같은 창에서 열기
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=색상 유형
|
|||||||
pdfToImage.color=컬러
|
pdfToImage.color=컬러
|
||||||
pdfToImage.grey=그레이스케일
|
pdfToImage.grey=그레이스케일
|
||||||
pdfToImage.blackwhite=흑백 (데이터 손실 가능성 있음!)
|
pdfToImage.blackwhite=흑백 (데이터 손실 가능성 있음!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=변환
|
pdfToImage.submit=변환
|
||||||
pdfToImage.info=WebP 변환에는 Python이 필요합니다. Python이 설치되지 않았습니다.
|
pdfToImage.info=WebP 변환에는 Python이 필요합니다. Python이 설치되지 않았습니다.
|
||||||
pdfToImage.placeholder=(예: 1,2,8 또는 4,7,12-16 또는 2n-1)
|
pdfToImage.placeholder=(예: 1,2,8 또는 4,7,12-16 또는 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=ജനപ്രിയം
|
|||||||
settings.title=ക്രമീകരണങ്ങൾ
|
settings.title=ക്രമീകരണങ്ങൾ
|
||||||
settings.update=അപ്ഡേറ്റ് ലഭ്യമാണ്
|
settings.update=അപ്ഡേറ്റ് ലഭ്യമാണ്
|
||||||
settings.updateAvailable={0} നിലവിൽ ഇൻസ്റ്റാൾ ചെയ്ത പതിപ്പാണ്. ഒരു പുതിയ പതിപ്പ് ({1}) ലഭ്യമാണ്.
|
settings.updateAvailable={0} നിലവിൽ ഇൻസ്റ്റാൾ ചെയ്ത പതിപ്പാണ്. ഒരു പുതിയ പതിപ്പ് ({1}) ലഭ്യമാണ്.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=ആപ്പ് പതിപ്പ്:
|
settings.appVersion=ആപ്പ് പതിപ്പ്:
|
||||||
settings.downloadOption.title=ഡൗൺലോഡ് ഓപ്ഷൻ തിരഞ്ഞെടുക്കുക (സിംഗിൾ ഫയൽ നോൺ-സിപ്പ് ഡൗൺലോഡുകൾക്ക്):
|
settings.downloadOption.title=ഡൗൺലോഡ് ഓപ്ഷൻ തിരഞ്ഞെടുക്കുക (സിംഗിൾ ഫയൽ നോൺ-സിപ്പ് ഡൗൺലോഡുകൾക്ക്):
|
||||||
settings.downloadOption.1=ഒരേ വിൻഡോയിൽ തുറക്കുക
|
settings.downloadOption.1=ഒരേ വിൻഡോയിൽ തുറക്കുക
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=നിറ തരം
|
|||||||
pdfToImage.color=നിറം
|
pdfToImage.color=നിറം
|
||||||
pdfToImage.grey=ഗ്രേസ്കെയിൽ
|
pdfToImage.grey=ഗ്രേസ്കെയിൽ
|
||||||
pdfToImage.blackwhite=കറുപ്പും വെളുപ്പും (ഡാറ്റ നഷ്ടപ്പെട്ടേക്കാം!)
|
pdfToImage.blackwhite=കറുപ്പും വെളുപ്പും (ഡാറ്റ നഷ്ടപ്പെട്ടേക്കാം!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=പരിവർത്തനം ചെയ്യുക
|
pdfToImage.submit=പരിവർത്തനം ചെയ്യുക
|
||||||
pdfToImage.info=പൈത്തൺ ഇൻസ്റ്റാൾ ചെയ്തിട്ടില്ല. WebP പരിവർത്തനത്തിന് ആവശ്യമാണ്.
|
pdfToImage.info=പൈത്തൺ ഇൻസ്റ്റാൾ ചെയ്തിട്ടില്ല. WebP പരിവർത്തനത്തിന് ആവശ്യമാണ്.
|
||||||
pdfToImage.placeholder=(ഉദാ. 1,2,8 അല്ലെങ്കിൽ 4,7,12-16 അല്ലെങ്കിൽ 2n-1)
|
pdfToImage.placeholder=(ഉദാ. 1,2,8 അല്ലെങ്കിൽ 4,7,12-16 അല്ലെങ്കിൽ 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popular
|
|||||||
settings.title=Instellingen
|
settings.title=Instellingen
|
||||||
settings.update=Update beschikbaar
|
settings.update=Update beschikbaar
|
||||||
settings.updateAvailable={0} is de huidig geïnstalleerde versie. Een nieuwe versie ({1}) is beschikbaar.
|
settings.updateAvailable={0} is de huidig geïnstalleerde versie. Een nieuwe versie ({1}) is beschikbaar.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=App versie:
|
settings.appVersion=App versie:
|
||||||
settings.downloadOption.title=Kies download optie (Voor enkelvoudige bestanddownloads zonder zip):
|
settings.downloadOption.title=Kies download optie (Voor enkelvoudige bestanddownloads zonder zip):
|
||||||
settings.downloadOption.1=Open in hetzelfde venster
|
settings.downloadOption.1=Open in hetzelfde venster
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Kleurtype
|
|||||||
pdfToImage.color=Kleur
|
pdfToImage.color=Kleur
|
||||||
pdfToImage.grey=Grijstinten
|
pdfToImage.grey=Grijstinten
|
||||||
pdfToImage.blackwhite=Zwart en wit (kan data verliezen!)
|
pdfToImage.blackwhite=Zwart en wit (kan data verliezen!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Omzetten
|
pdfToImage.submit=Omzetten
|
||||||
pdfToImage.info=Python is niet geïnstalleerd. Vereist voor WebP-conversie.
|
pdfToImage.info=Python is niet geïnstalleerd. Vereist voor WebP-conversie.
|
||||||
pdfToImage.placeholder=(bijv. 1,2,8 of 4,7,12-16 of 2n-1)
|
pdfToImage.placeholder=(bijv. 1,2,8 of 4,7,12-16 of 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Populært
|
|||||||
settings.title=Innstillinger
|
settings.title=Innstillinger
|
||||||
settings.update=Oppdatering tilgjengelig
|
settings.update=Oppdatering tilgjengelig
|
||||||
settings.updateAvailable={0} er den nåværende installerte versjonen. En ny versjon ({1}) er tilgjengelig.
|
settings.updateAvailable={0} er den nåværende installerte versjonen. En ny versjon ({1}) er tilgjengelig.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=App Versjon:
|
settings.appVersion=App Versjon:
|
||||||
settings.downloadOption.title=Velg nedlastingsalternativ (For enkeltfil ikke-zip nedlastinger):
|
settings.downloadOption.title=Velg nedlastingsalternativ (For enkeltfil ikke-zip nedlastinger):
|
||||||
settings.downloadOption.1=Åpne i samme vindu
|
settings.downloadOption.1=Åpne i samme vindu
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Farge type
|
|||||||
pdfToImage.color=Farge
|
pdfToImage.color=Farge
|
||||||
pdfToImage.grey=Gråtone
|
pdfToImage.grey=Gråtone
|
||||||
pdfToImage.blackwhite=Svart-hvitt (kan miste data!)
|
pdfToImage.blackwhite=Svart-hvitt (kan miste data!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Konverter
|
pdfToImage.submit=Konverter
|
||||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
pdfToImage.placeholder=(f.eks. 1,2,8 eller 4,7,12-16 eller 2n-1)
|
pdfToImage.placeholder=(f.eks. 1,2,8 eller 4,7,12-16 eller 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popularne
|
|||||||
settings.title=Ustawienia
|
settings.title=Ustawienia
|
||||||
settings.update=Dostępna aktualizacja
|
settings.update=Dostępna aktualizacja
|
||||||
settings.updateAvailable=Wersja {0} jest obecenia zainstalowana, dostępna jest nowa wersja ({1}).
|
settings.updateAvailable=Wersja {0} jest obecenia zainstalowana, dostępna jest nowa wersja ({1}).
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Wersja aplikacji:
|
settings.appVersion=Wersja aplikacji:
|
||||||
settings.downloadOption.title=Wybierz opcję pobierania (w przypadku pobierania pojedynczych plików innych niż ZIP):
|
settings.downloadOption.title=Wybierz opcję pobierania (w przypadku pobierania pojedynczych plików innych niż ZIP):
|
||||||
settings.downloadOption.1=Otwórz w tym samym oknie
|
settings.downloadOption.1=Otwórz w tym samym oknie
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Rodzaj koloru
|
|||||||
pdfToImage.color=Kolor
|
pdfToImage.color=Kolor
|
||||||
pdfToImage.grey=Odcień szarości
|
pdfToImage.grey=Odcień szarości
|
||||||
pdfToImage.blackwhite=Czarno-biały (może spowodować utratę danych!)
|
pdfToImage.blackwhite=Czarno-biały (może spowodować utratę danych!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Konwertuj
|
pdfToImage.submit=Konwertuj
|
||||||
pdfToImage.info=Python nie został zainstalowany. Jest wymagany do konwersji WebP.
|
pdfToImage.info=Python nie został zainstalowany. Jest wymagany do konwersji WebP.
|
||||||
pdfToImage.placeholder=(przykład 1,2,8 lub 2n-1)
|
pdfToImage.placeholder=(przykład 1,2,8 lub 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Populares
|
|||||||
settings.title=Configurações
|
settings.title=Configurações
|
||||||
settings.update=Atualização disponível
|
settings.update=Atualização disponível
|
||||||
settings.updateAvailable={0} é a versão atualmente instalada. Uma nova versão ({1}) está disponível.
|
settings.updateAvailable={0} é a versão atualmente instalada. Uma nova versão ({1}) está disponível.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Versão do Aplicativo:
|
settings.appVersion=Versão do Aplicativo:
|
||||||
settings.downloadOption.title=Escolha a opção de download (para download de arquivo único, não compactados):
|
settings.downloadOption.title=Escolha a opção de download (para download de arquivo único, não compactados):
|
||||||
settings.downloadOption.1=Abrir na mesma janela
|
settings.downloadOption.1=Abrir na mesma janela
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Cor de saída:
|
|||||||
pdfToImage.color=Colorido
|
pdfToImage.color=Colorido
|
||||||
pdfToImage.grey=Escala de Cinza
|
pdfToImage.grey=Escala de Cinza
|
||||||
pdfToImage.blackwhite=Preto e Branco (pode perder informações!)
|
pdfToImage.blackwhite=Preto e Branco (pode perder informações!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Converter
|
pdfToImage.submit=Converter
|
||||||
pdfToImage.info=Python não está instalado. Necessário para conversão WebP.
|
pdfToImage.info=Python não está instalado. Necessário para conversão WebP.
|
||||||
pdfToImage.placeholder=(por exemplo 1,2,8 ou 4,7,12-16 ou 2n-1)
|
pdfToImage.placeholder=(por exemplo 1,2,8 ou 4,7,12-16 ou 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popular
|
|||||||
settings.title=Definições
|
settings.title=Definições
|
||||||
settings.update=Atualização disponível
|
settings.update=Atualização disponível
|
||||||
settings.updateAvailable={0} é a versão atual instalada. Uma nova versão ({1}) está disponível.
|
settings.updateAvailable={0} é a versão atual instalada. Uma nova versão ({1}) está disponível.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Versão da Aplicação:
|
settings.appVersion=Versão da Aplicação:
|
||||||
settings.downloadOption.title=Escolha a opção de download (Para downloads de ficheiro único não zipado):
|
settings.downloadOption.title=Escolha a opção de download (Para downloads de ficheiro único não zipado):
|
||||||
settings.downloadOption.1=Abrir na mesma janela
|
settings.downloadOption.1=Abrir na mesma janela
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Tipo de cor
|
|||||||
pdfToImage.color=Cor
|
pdfToImage.color=Cor
|
||||||
pdfToImage.grey=Escala de Cinza
|
pdfToImage.grey=Escala de Cinza
|
||||||
pdfToImage.blackwhite=Preto e Branco (Pode perder dados!)
|
pdfToImage.blackwhite=Preto e Branco (Pode perder dados!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Converter
|
pdfToImage.submit=Converter
|
||||||
pdfToImage.info=Python não está instalado. Necessário para conversão WebP.
|
pdfToImage.info=Python não está instalado. Necessário para conversão WebP.
|
||||||
pdfToImage.placeholder=(ex. 1,2,8 ou 4,7,12-16 ou 2n-1)
|
pdfToImage.placeholder=(ex. 1,2,8 ou 4,7,12-16 ou 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popular
|
|||||||
settings.title=Setări
|
settings.title=Setări
|
||||||
settings.update=Actualizare disponibilă
|
settings.update=Actualizare disponibilă
|
||||||
settings.updateAvailable={0} este versiunea instalată curent. O nouă versiune ({1}) este disponibilă.
|
settings.updateAvailable={0} este versiunea instalată curent. O nouă versiune ({1}) este disponibilă.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Versiune aplicație:
|
settings.appVersion=Versiune aplicație:
|
||||||
settings.downloadOption.title=Alege opțiunea de descărcare (pentru descărcarea unui singur fișier non-zip):
|
settings.downloadOption.title=Alege opțiunea de descărcare (pentru descărcarea unui singur fișier non-zip):
|
||||||
settings.downloadOption.1=Deschide în aceeași fereastră
|
settings.downloadOption.1=Deschide în aceeași fereastră
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Tip culoare
|
|||||||
pdfToImage.color=Culoare
|
pdfToImage.color=Culoare
|
||||||
pdfToImage.grey=Scală de gri
|
pdfToImage.grey=Scală de gri
|
||||||
pdfToImage.blackwhite=Alb și negru (Poate pierde date!)
|
pdfToImage.blackwhite=Alb și negru (Poate pierde date!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Convertește
|
pdfToImage.submit=Convertește
|
||||||
pdfToImage.info=Python nu este instalat. Necesar pentru conversia WebP.
|
pdfToImage.info=Python nu este instalat. Necesar pentru conversia WebP.
|
||||||
pdfToImage.placeholder=(ex. 1,2,8 sau 4,7,12-16 sau 2n-1)
|
pdfToImage.placeholder=(ex. 1,2,8 sau 4,7,12-16 sau 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Популярное
|
|||||||
settings.title=Настройки
|
settings.title=Настройки
|
||||||
settings.update=Доступно обновление
|
settings.update=Доступно обновление
|
||||||
settings.updateAvailable=Текущая установленная версия - {0}. Доступна новая версия ({1}).
|
settings.updateAvailable=Текущая установленная версия - {0}. Доступна новая версия ({1}).
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Версия приложения:
|
settings.appVersion=Версия приложения:
|
||||||
settings.downloadOption.title=Выберите вариант загрузки (для одиночных файлов без архивации):
|
settings.downloadOption.title=Выберите вариант загрузки (для одиночных файлов без архивации):
|
||||||
settings.downloadOption.1=Открыть в том же окне
|
settings.downloadOption.1=Открыть в том же окне
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Тип цвета
|
|||||||
pdfToImage.color=Цветной
|
pdfToImage.color=Цветной
|
||||||
pdfToImage.grey=Оттенки серого
|
pdfToImage.grey=Оттенки серого
|
||||||
pdfToImage.blackwhite=Черно-белый (возможна потеря данных!)
|
pdfToImage.blackwhite=Черно-белый (возможна потеря данных!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Преобразовать
|
pdfToImage.submit=Преобразовать
|
||||||
pdfToImage.info=Python не установлен. Требуется для конвертации в WebP.
|
pdfToImage.info=Python не установлен. Требуется для конвертации в WebP.
|
||||||
pdfToImage.placeholder=(например, 1,2,8 или 4,7,12-16 или 2n-1)
|
pdfToImage.placeholder=(например, 1,2,8 или 4,7,12-16 или 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Заменить существующие з
|
|||||||
editTableOfContents.editorTitle=Редактор закладок
|
editTableOfContents.editorTitle=Редактор закладок
|
||||||
editTableOfContents.editorDesc=Добавьте и упорядочьте закладки ниже. Нажмите «+», чтобы добавить дочерние закладки.
|
editTableOfContents.editorDesc=Добавьте и упорядочьте закладки ниже. Нажмите «+», чтобы добавить дочерние закладки.
|
||||||
editTableOfContents.addBookmark=Добавить новую закладку
|
editTableOfContents.addBookmark=Добавить новую закладку
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=Этот инструмент позволяет вам добавлять или редактировать оглавление (закладки) в PDF-документе.
|
editTableOfContents.desc.1=Этот инструмент позволяет вам добавлять или редактировать оглавление (закладки) в PDF-документе.
|
||||||
editTableOfContents.desc.2=Вы можете создать иерархическую структуру, добавив дочерние закладки к родительским.
|
editTableOfContents.desc.2=Вы можете создать иерархическую структуру, добавив дочерние закладки к родительским.
|
||||||
editTableOfContents.desc.3=Для каждой закладки требуется название и номер целевой страницы.
|
editTableOfContents.desc.3=Для каждой закладки требуется название и номер целевой страницы.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popular
|
|||||||
settings.title=Nastavenia
|
settings.title=Nastavenia
|
||||||
settings.update=Dostupná aktualizácia
|
settings.update=Dostupná aktualizácia
|
||||||
settings.updateAvailable={0} je aktuálne nainštalovaná verzia. Nová verzia ({1}) je dostupná.
|
settings.updateAvailable={0} je aktuálne nainštalovaná verzia. Nová verzia ({1}) je dostupná.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Verzia aplikácie:
|
settings.appVersion=Verzia aplikácie:
|
||||||
settings.downloadOption.title=Vyberte možnosť sťahovania (Pre jednotlivé neskomprimované súbory):
|
settings.downloadOption.title=Vyberte možnosť sťahovania (Pre jednotlivé neskomprimované súbory):
|
||||||
settings.downloadOption.1=Otvoriť v rovnakom okne
|
settings.downloadOption.1=Otvoriť v rovnakom okne
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Typ farby
|
|||||||
pdfToImage.color=Farba
|
pdfToImage.color=Farba
|
||||||
pdfToImage.grey=Odtiene šedej
|
pdfToImage.grey=Odtiene šedej
|
||||||
pdfToImage.blackwhite=Čierno-biele (Môže stratiť údaje!)
|
pdfToImage.blackwhite=Čierno-biele (Môže stratiť údaje!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Konvertovať
|
pdfToImage.submit=Konvertovať
|
||||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
pdfToImage.placeholder=(napr. 1,2,8 alebo 4,7,12-16 alebo 2n-1)
|
pdfToImage.placeholder=(napr. 1,2,8 alebo 4,7,12-16 alebo 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Priljubljeno
|
|||||||
settings.title=Nastavitve
|
settings.title=Nastavitve
|
||||||
settings.update=Na voljo je posodobitev
|
settings.update=Na voljo je posodobitev
|
||||||
settings.updateAvailable={0} je trenutno nameščena različica. Na voljo je nova različica ({1}).
|
settings.updateAvailable={0} je trenutno nameščena različica. Na voljo je nova različica ({1}).
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Različica aplikacije:
|
settings.appVersion=Različica aplikacije:
|
||||||
settings.downloadOption.title=Izberi možnost prenosa (za prenose ene datoteke brez zip):
|
settings.downloadOption.title=Izberi možnost prenosa (za prenose ene datoteke brez zip):
|
||||||
settings.downloadOption.1=Odpri v istem oknu
|
settings.downloadOption.1=Odpri v istem oknu
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Vrsta barve
|
|||||||
pdfToImage.color=Barva
|
pdfToImage.color=Barva
|
||||||
pdfToImage.grey=Sivine
|
pdfToImage.grey=Sivine
|
||||||
pdfToImage.blackwhite=Črno-belo (Lahko izgubite podatke!)
|
pdfToImage.blackwhite=Črno-belo (Lahko izgubite podatke!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Pretvori
|
pdfToImage.submit=Pretvori
|
||||||
pdfToImage.info=Python ni nameščen. Zahtevano za pretvorbo WebP.
|
pdfToImage.info=Python ni nameščen. Zahtevano za pretvorbo WebP.
|
||||||
pdfToImage.placeholder=(npr. 1,2,8 ali 4,7,12-16 ali 2n-1)
|
pdfToImage.placeholder=(npr. 1,2,8 ali 4,7,12-16 ali 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popularno
|
|||||||
settings.title=Podešavanja
|
settings.title=Podešavanja
|
||||||
settings.update=Dostupno ažuriranje
|
settings.update=Dostupno ažuriranje
|
||||||
settings.updateAvailable={0} je trenutno instalirana verzija. Nova verzija ({1}) je dostupna.
|
settings.updateAvailable={0} je trenutno instalirana verzija. Nova verzija ({1}) je dostupna.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Verzija aplikacije:
|
settings.appVersion=Verzija aplikacije:
|
||||||
settings.downloadOption.title=Odaberi opciju preuzimanja (Za preuzimanje pojedinačnih fajlova bez zip formata):
|
settings.downloadOption.title=Odaberi opciju preuzimanja (Za preuzimanje pojedinačnih fajlova bez zip formata):
|
||||||
settings.downloadOption.1=Otvori u istom prozoru
|
settings.downloadOption.1=Otvori u istom prozoru
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Režim boja:
|
|||||||
pdfToImage.color=Kolor
|
pdfToImage.color=Kolor
|
||||||
pdfToImage.grey=Monohromatski
|
pdfToImage.grey=Monohromatski
|
||||||
pdfToImage.blackwhite=Crno-belo (Može izgubiti detalje!)
|
pdfToImage.blackwhite=Crno-belo (Može izgubiti detalje!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Konvertuj
|
pdfToImage.submit=Konvertuj
|
||||||
pdfToImage.info=Python nije instaliran. Neophodan je za WebP konverziju.
|
pdfToImage.info=Python nije instaliran. Neophodan je za WebP konverziju.
|
||||||
pdfToImage.placeholder=(npr. 1,2,8 ili 4,7,12-16 ili 2n-1)
|
pdfToImage.placeholder=(npr. 1,2,8 ili 4,7,12-16 ili 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Zameni postojeće obeleživače (isključi d
|
|||||||
editTableOfContents.editorTitle=Editor obeleživača
|
editTableOfContents.editorTitle=Editor obeleživača
|
||||||
editTableOfContents.editorDesc=Dodaj i rasporedi obeleživače ispod. Klikni + za dodavanje podređenih obeleživača.
|
editTableOfContents.editorDesc=Dodaj i rasporedi obeleživače ispod. Klikni + za dodavanje podređenih obeleživača.
|
||||||
editTableOfContents.addBookmark=Dodaj novi obeleživač
|
editTableOfContents.addBookmark=Dodaj novi obeleživač
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=Ovaj alat omogućava dodavanje ili izmenu sadržaja (obeleživača) u PDF dokumentu.
|
editTableOfContents.desc.1=Ovaj alat omogućava dodavanje ili izmenu sadržaja (obeleživača) u PDF dokumentu.
|
||||||
editTableOfContents.desc.2=Moguće je kreirati hijerarhijsku strukturu dodavanjem podređenih obeleživača nadređenim obeleživačima.
|
editTableOfContents.desc.2=Moguće je kreirati hijerarhijsku strukturu dodavanjem podređenih obeleživača nadređenim obeleživačima.
|
||||||
editTableOfContents.desc.3=Svaki obeleživač zahteva naslov i broj ciljne strane.
|
editTableOfContents.desc.3=Svaki obeleživač zahteva naslov i broj ciljne strane.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Populära
|
|||||||
settings.title=Inställningar
|
settings.title=Inställningar
|
||||||
settings.update=Uppdatering tillgänglig
|
settings.update=Uppdatering tillgänglig
|
||||||
settings.updateAvailable={0} är den aktuella installerade versionen. En ny version ({1}) finns tillgänglig.
|
settings.updateAvailable={0} är den aktuella installerade versionen. En ny version ({1}) finns tillgänglig.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Appversion:
|
settings.appVersion=Appversion:
|
||||||
settings.downloadOption.title=Välj nedladdningsalternativ (för nedladdning av en fil utan zip):
|
settings.downloadOption.title=Välj nedladdningsalternativ (för nedladdning av en fil utan zip):
|
||||||
settings.downloadOption.1=Öppnas i samma fönster
|
settings.downloadOption.1=Öppnas i samma fönster
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Färgtyp
|
|||||||
pdfToImage.color=Färg
|
pdfToImage.color=Färg
|
||||||
pdfToImage.grey=Gråskala
|
pdfToImage.grey=Gråskala
|
||||||
pdfToImage.blackwhite=Svartvitt (kan förlora data!)
|
pdfToImage.blackwhite=Svartvitt (kan förlora data!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Konvertera
|
pdfToImage.submit=Konvertera
|
||||||
pdfToImage.info=Python är inte installerat. Krävs för WebP-konvertering.
|
pdfToImage.info=Python är inte installerat. Krävs för WebP-konvertering.
|
||||||
pdfToImage.placeholder=(t.ex. 1,2,8 eller 4,7,12-16 eller 2n-1)
|
pdfToImage.placeholder=(t.ex. 1,2,8 eller 4,7,12-16 eller 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=ยอดนิยม
|
|||||||
settings.title=การตั้งค่า
|
settings.title=การตั้งค่า
|
||||||
settings.update=มีการอัปเดต
|
settings.update=มีการอัปเดต
|
||||||
settings.updateAvailable={0} คือเวอร์ชันที่ติดตั้งในปัจจุบัน มีเวอร์ชันใหม่ ({1}) พร้อมให้บริการ
|
settings.updateAvailable={0} คือเวอร์ชันที่ติดตั้งในปัจจุบัน มีเวอร์ชันใหม่ ({1}) พร้อมให้บริการ
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=เวอร์ชันแอป:
|
settings.appVersion=เวอร์ชันแอป:
|
||||||
settings.downloadOption.title=เลือกตัวเลือกการดาวน์โหลด (สำหรับการดาวน์โหลดไฟล์เดียวที่ไม่ใช่ zip):
|
settings.downloadOption.title=เลือกตัวเลือกการดาวน์โหลด (สำหรับการดาวน์โหลดไฟล์เดียวที่ไม่ใช่ zip):
|
||||||
settings.downloadOption.1=เปิดในหน้าต่างเดียวกัน
|
settings.downloadOption.1=เปิดในหน้าต่างเดียวกัน
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=ประเภทสี
|
|||||||
pdfToImage.color=สี
|
pdfToImage.color=สี
|
||||||
pdfToImage.grey=ระดับสีเทา
|
pdfToImage.grey=ระดับสีเทา
|
||||||
pdfToImage.blackwhite=ขาวดำ (อาจสูญเสียข้อมูล!)
|
pdfToImage.blackwhite=ขาวดำ (อาจสูญเสียข้อมูล!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=แปลง
|
pdfToImage.submit=แปลง
|
||||||
pdfToImage.info=Python ไม่มีการติดตั้ง จำเป็นสำหรับการแปลง WebP
|
pdfToImage.info=Python ไม่มีการติดตั้ง จำเป็นสำหรับการแปลง WebP
|
||||||
pdfToImage.placeholder=(เช่น 1,2,8 หรือ 4,7,12-16 หรือ 2n-1)
|
pdfToImage.placeholder=(เช่น 1,2,8 หรือ 4,7,12-16 หรือ 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popular
|
|||||||
settings.title=Ayarlar
|
settings.title=Ayarlar
|
||||||
settings.update=Güncelleme mevcut
|
settings.update=Güncelleme mevcut
|
||||||
settings.updateAvailable={0} mevcut kurulu sürümdür. Yeni bir sürüm ({1}) mevcuttur.
|
settings.updateAvailable={0} mevcut kurulu sürümdür. Yeni bir sürüm ({1}) mevcuttur.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Uygulama Sürümü:
|
settings.appVersion=Uygulama Sürümü:
|
||||||
settings.downloadOption.title=İndirme seçeneği seçin (Zip olmayan tek dosya indirmeler için):
|
settings.downloadOption.title=İndirme seçeneği seçin (Zip olmayan tek dosya indirmeler için):
|
||||||
settings.downloadOption.1=Aynı pencerede aç
|
settings.downloadOption.1=Aynı pencerede aç
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Renk türü
|
|||||||
pdfToImage.color=Renk
|
pdfToImage.color=Renk
|
||||||
pdfToImage.grey=Gri tonlama
|
pdfToImage.grey=Gri tonlama
|
||||||
pdfToImage.blackwhite=Siyah ve Beyaz (Veri kaybolabilir!)
|
pdfToImage.blackwhite=Siyah ve Beyaz (Veri kaybolabilir!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Dönüştür
|
pdfToImage.submit=Dönüştür
|
||||||
pdfToImage.info=Python kurulu değil. WebP dönüşümü için gereklidir.
|
pdfToImage.info=Python kurulu değil. WebP dönüşümü için gereklidir.
|
||||||
pdfToImage.placeholder=(örneğin 1,2,8 veya 4,7,12-16 ya da 2n-1)
|
pdfToImage.placeholder=(örneğin 1,2,8 veya 4,7,12-16 ya da 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Mevcut yer işaretlerini değiştir (var ola
|
|||||||
editTableOfContents.editorTitle=Yer İşareti Düzenleyici
|
editTableOfContents.editorTitle=Yer İşareti Düzenleyici
|
||||||
editTableOfContents.editorDesc=Aşağıdan yer işaretleri ekleyin ve düzenleyin. Alt yer işareti eklemek için + simgesine tıklayın.
|
editTableOfContents.editorDesc=Aşağıdan yer işaretleri ekleyin ve düzenleyin. Alt yer işareti eklemek için + simgesine tıklayın.
|
||||||
editTableOfContents.addBookmark=Yeni Yer İşareti Ekle
|
editTableOfContents.addBookmark=Yeni Yer İşareti Ekle
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=Bu araç, bir PDF belgesine içindekiler tablosu (yer işaretleri) eklemenizi veya mevcut olanları düzenlemenizi sağlar.
|
editTableOfContents.desc.1=Bu araç, bir PDF belgesine içindekiler tablosu (yer işaretleri) eklemenizi veya mevcut olanları düzenlemenizi sağlar.
|
||||||
editTableOfContents.desc.2=Alt yer işaretleri ekleyerek hiyerarşik bir yapı oluşturabilirsiniz.
|
editTableOfContents.desc.2=Alt yer işaretleri ekleyerek hiyerarşik bir yapı oluşturabilirsiniz.
|
||||||
editTableOfContents.desc.3=Her yer işareti bir başlık ve hedef sayfa numarası gerektirir.
|
editTableOfContents.desc.3=Her yer işareti bir başlık ve hedef sayfa numarası gerektirir.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Популярне
|
|||||||
settings.title=Налаштування
|
settings.title=Налаштування
|
||||||
settings.update=Доступне оновлення
|
settings.update=Доступне оновлення
|
||||||
settings.updateAvailable=Зараз встановлена версія {0}. Нова версія ({1}) доступна.
|
settings.updateAvailable=Зараз встановлена версія {0}. Нова версія ({1}) доступна.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Версія додатку:
|
settings.appVersion=Версія додатку:
|
||||||
settings.downloadOption.title=Виберіть варіант завантаження (для завантаження одного файлу без zip):
|
settings.downloadOption.title=Виберіть варіант завантаження (для завантаження одного файлу без zip):
|
||||||
settings.downloadOption.1=Відкрити в тому ж вікні
|
settings.downloadOption.1=Відкрити в тому ж вікні
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Тип кольору
|
|||||||
pdfToImage.color=Колір
|
pdfToImage.color=Колір
|
||||||
pdfToImage.grey=Відтінки сірого
|
pdfToImage.grey=Відтінки сірого
|
||||||
pdfToImage.blackwhite=Чорно-білий (може втратити дані!)
|
pdfToImage.blackwhite=Чорно-білий (може втратити дані!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Конвертувати
|
pdfToImage.submit=Конвертувати
|
||||||
pdfToImage.info=Python не встановлено. Необхідно для конвертації WebP.
|
pdfToImage.info=Python не встановлено. Необхідно для конвертації WebP.
|
||||||
pdfToImage.placeholder=(наприклад 1,2,8 або 4,7,12-16 або 2n-1)
|
pdfToImage.placeholder=(наприклад 1,2,8 або 4,7,12-16 або 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=Popular
|
|||||||
settings.title=Cài đặt
|
settings.title=Cài đặt
|
||||||
settings.update=Có bản cập nhật
|
settings.update=Có bản cập nhật
|
||||||
settings.updateAvailable={0} là phiên bản hiện tại đã cài đặt. Một phiên bản mới ({1}) đã có sẵn.
|
settings.updateAvailable={0} là phiên bản hiện tại đã cài đặt. Một phiên bản mới ({1}) đã có sẵn.
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=Phiên bản ứng dụng:
|
settings.appVersion=Phiên bản ứng dụng:
|
||||||
settings.downloadOption.title=Chọn tùy chọn tải xuống (Đối với tải xuống tệp đơn không nén):
|
settings.downloadOption.title=Chọn tùy chọn tải xuống (Đối với tải xuống tệp đơn không nén):
|
||||||
settings.downloadOption.1=Mở trong cùng cửa sổ
|
settings.downloadOption.1=Mở trong cùng cửa sổ
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=Loại màu
|
|||||||
pdfToImage.color=Màu
|
pdfToImage.color=Màu
|
||||||
pdfToImage.grey=Thang độ xám
|
pdfToImage.grey=Thang độ xám
|
||||||
pdfToImage.blackwhite=Đen trắng (Có thể mất dữ liệu!)
|
pdfToImage.blackwhite=Đen trắng (Có thể mất dữ liệu!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=Chuyển đổi
|
pdfToImage.submit=Chuyển đổi
|
||||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
pdfToImage.placeholder=(ví dụ: 1,2,8 hoặc 4,7,12-16 hoặc 2n-1)
|
pdfToImage.placeholder=(ví dụ: 1,2,8 hoặc 4,7,12-16 hoặc 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to appen
|
|||||||
editTableOfContents.editorTitle=Bookmark Editor
|
editTableOfContents.editorTitle=Bookmark Editor
|
||||||
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks.
|
||||||
editTableOfContents.addBookmark=Add New Bookmark
|
editTableOfContents.addBookmark=Add New Bookmark
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document.
|
||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=热门
|
|||||||
settings.title=设置
|
settings.title=设置
|
||||||
settings.update=有可用的更新
|
settings.update=有可用的更新
|
||||||
settings.updateAvailable=当前版本为 {0},新版本 ({1}) 可用。
|
settings.updateAvailable=当前版本为 {0},新版本 ({1}) 可用。
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=应用程序版本:
|
settings.appVersion=应用程序版本:
|
||||||
settings.downloadOption.title=选择下载选项(单个文件非压缩文件):
|
settings.downloadOption.title=选择下载选项(单个文件非压缩文件):
|
||||||
settings.downloadOption.1=在同一窗口打开
|
settings.downloadOption.1=在同一窗口打开
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=颜色类型
|
|||||||
pdfToImage.color=颜色
|
pdfToImage.color=颜色
|
||||||
pdfToImage.grey=灰度
|
pdfToImage.grey=灰度
|
||||||
pdfToImage.blackwhite=黑白(可能会丢失数据!)。
|
pdfToImage.blackwhite=黑白(可能会丢失数据!)。
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=转换
|
pdfToImage.submit=转换
|
||||||
pdfToImage.info=WebP 转换需要安装 Python
|
pdfToImage.info=WebP 转换需要安装 Python
|
||||||
pdfToImage.placeholder=(例如:1,2,8 或 4,7,12-16 或 2n-1)
|
pdfToImage.placeholder=(例如:1,2,8 或 4,7,12-16 或 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=替换现有书签(取消勾选则追加
|
|||||||
editTableOfContents.editorTitle=书签编辑器
|
editTableOfContents.editorTitle=书签编辑器
|
||||||
editTableOfContents.editorDesc=在下方添加并排列书签,点击 + 可添加子书签
|
editTableOfContents.editorDesc=在下方添加并排列书签,点击 + 可添加子书签
|
||||||
editTableOfContents.addBookmark=添加书签
|
editTableOfContents.addBookmark=添加书签
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=此工具可用于在 PDF 文档中添加或编辑目录(书签)
|
editTableOfContents.desc.1=此工具可用于在 PDF 文档中添加或编辑目录(书签)
|
||||||
editTableOfContents.desc.2=您可以通过为父书签添加子书签来构建层级结构
|
editTableOfContents.desc.2=您可以通过为父书签添加子书签来构建层级结构
|
||||||
editTableOfContents.desc.3=每个书签需填写标题和目标页码
|
editTableOfContents.desc.3=每个书签需填写标题和目标页码
|
||||||
|
@ -366,6 +366,38 @@ navbar.sections.popular=熱門功能
|
|||||||
settings.title=設定
|
settings.title=設定
|
||||||
settings.update=有更新可用
|
settings.update=有更新可用
|
||||||
settings.updateAvailable=目前安裝的版本是 {0}。有新版本({1})可供使用。
|
settings.updateAvailable=目前安裝的版本是 {0}。有新版本({1})可供使用。
|
||||||
|
|
||||||
|
# Update modal and notification strings
|
||||||
|
update.urgentUpdateAvailable=🚨 Update Available
|
||||||
|
update.updateAvailable=Update Available
|
||||||
|
update.modalTitle=Update Available
|
||||||
|
update.current=Current
|
||||||
|
update.latest=Latest
|
||||||
|
update.latestStable=Latest Stable
|
||||||
|
update.priority=Priority
|
||||||
|
update.recommendedAction=Recommended Action
|
||||||
|
update.breakingChangesDetected=⚠️ Breaking Changes Detected
|
||||||
|
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
|
||||||
|
update.migrationGuides=Migration Guides:
|
||||||
|
update.viewGuide=View Guide
|
||||||
|
update.loadingDetailedInfo=Loading detailed version information...
|
||||||
|
update.close=Close
|
||||||
|
update.viewAllReleases=View All Releases
|
||||||
|
update.downloadLatest=Download Latest
|
||||||
|
update.availableUpdates=Available Updates:
|
||||||
|
update.unableToLoadDetails=Unable to load detailed version information.
|
||||||
|
update.version=Version
|
||||||
|
|
||||||
|
# Update priority levels
|
||||||
|
update.priority.urgent=URGENT
|
||||||
|
update.priority.normal=NORMAL
|
||||||
|
update.priority.minor=MINOR
|
||||||
|
update.priority.low=LOW
|
||||||
|
|
||||||
|
# Breaking changes text
|
||||||
|
update.breakingChanges=Breaking Changes:
|
||||||
|
update.breakingChangesDefault=This version contains breaking changes
|
||||||
|
update.migrationGuide=Migration Guide
|
||||||
settings.appVersion=應用程式版本:
|
settings.appVersion=應用程式版本:
|
||||||
settings.downloadOption.title=選擇下載選項(適用於單一檔案非壓縮下載):
|
settings.downloadOption.title=選擇下載選項(適用於單一檔案非壓縮下載):
|
||||||
settings.downloadOption.1=在同一視窗中開啟
|
settings.downloadOption.1=在同一視窗中開啟
|
||||||
@ -1399,6 +1431,7 @@ pdfToImage.colorType=顏色類型
|
|||||||
pdfToImage.color=顏色
|
pdfToImage.color=顏色
|
||||||
pdfToImage.grey=灰度
|
pdfToImage.grey=灰度
|
||||||
pdfToImage.blackwhite=黑白(可能會遺失資料!)
|
pdfToImage.blackwhite=黑白(可能會遺失資料!)
|
||||||
|
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||||
pdfToImage.submit=轉換
|
pdfToImage.submit=轉換
|
||||||
pdfToImage.info=尚未安裝 Python。需要安裝 Python 才能進行 WebP 轉換。
|
pdfToImage.info=尚未安裝 Python。需要安裝 Python 才能進行 WebP 轉換。
|
||||||
pdfToImage.placeholder=(例如 1,2,8 或 4,7,12-16 或 2n-1)
|
pdfToImage.placeholder=(例如 1,2,8 或 4,7,12-16 或 2n-1)
|
||||||
@ -1856,6 +1889,12 @@ editTableOfContents.replaceExisting=取代現有書籤 (取消勾選以附加到
|
|||||||
editTableOfContents.editorTitle=書籤編輯器
|
editTableOfContents.editorTitle=書籤編輯器
|
||||||
editTableOfContents.editorDesc=在下方新增和排列書籤。點選 + 新增子書籤。
|
editTableOfContents.editorDesc=在下方新增和排列書籤。點選 + 新增子書籤。
|
||||||
editTableOfContents.addBookmark=新增書籤
|
editTableOfContents.addBookmark=新增書籤
|
||||||
|
editTableOfContents.importBookmarksDefault=Import
|
||||||
|
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
|
||||||
|
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
|
||||||
|
editTableOfContents.exportBookmarksDefault=Export
|
||||||
|
editTableOfContents.exportBookmarksAsJson=Download as JSON
|
||||||
|
editTableOfContents.exportBookmarksAsText=Copy as text
|
||||||
editTableOfContents.desc.1=此工具可讓您在 PDF 文件中新增或編輯目錄 (書籤)。
|
editTableOfContents.desc.1=此工具可讓您在 PDF 文件中新增或編輯目錄 (書籤)。
|
||||||
editTableOfContents.desc.2=您可以透過將子書籤新增至父書籤來建立階層式結構。
|
editTableOfContents.desc.2=您可以透過將子書籤新增至父書籤來建立階層式結構。
|
||||||
editTableOfContents.desc.3=每個書籤都需要標題和目標頁碼。
|
editTableOfContents.desc.3=每個書籤都需要標題和目標頁碼。
|
||||||
|
@ -108,6 +108,7 @@ system:
|
|||||||
enableAnalytics: null # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
|
enableAnalytics: null # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
|
||||||
enableUrlToPDF: false # Set to 'true' to enable URL to PDF, INTERNAL ONLY, known security issues, should not be used externally
|
enableUrlToPDF: false # Set to 'true' to enable URL to PDF, INTERNAL ONLY, known security issues, should not be used externally
|
||||||
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
|
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
|
||||||
|
maxDPI: 500 # Maximum allowed DPI for PDF to image conversion
|
||||||
html:
|
html:
|
||||||
urlSecurity:
|
urlSecurity:
|
||||||
enabled: true # Enable URL security restrictions for HTML processing
|
enabled: true # Enable URL security restrictions for HTML processing
|
||||||
|
@ -336,6 +336,12 @@
|
|||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "com.twelvemonkeys.imageio:imageio-psd",
|
||||||
|
"moduleVersion": "3.12.0",
|
||||||
|
"moduleLicense": "The BSD License",
|
||||||
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.imageio:imageio-tiff",
|
"moduleName": "com.twelvemonkeys.imageio:imageio-tiff",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.12.0",
|
||||||
@ -623,21 +629,21 @@
|
|||||||
{
|
{
|
||||||
"moduleName": "io.swagger.core.v3:swagger-annotations-jakarta",
|
"moduleName": "io.swagger.core.v3:swagger-annotations-jakarta",
|
||||||
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-annotations",
|
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-annotations",
|
||||||
"moduleVersion": "2.2.34",
|
"moduleVersion": "2.2.35",
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
"moduleLicense": "Apache License, Version 2.0",
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "io.swagger.core.v3:swagger-core-jakarta",
|
"moduleName": "io.swagger.core.v3:swagger-core-jakarta",
|
||||||
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-core",
|
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-core",
|
||||||
"moduleVersion": "2.2.34",
|
"moduleVersion": "2.2.35",
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
"moduleLicense": "Apache License, Version 2.0",
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "io.swagger.core.v3:swagger-models-jakarta",
|
"moduleName": "io.swagger.core.v3:swagger-models-jakarta",
|
||||||
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-models",
|
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-models",
|
||||||
"moduleVersion": "2.2.34",
|
"moduleVersion": "2.2.35",
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
"moduleLicense": "Apache License, Version 2.0",
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||||
},
|
},
|
||||||
@ -980,13 +986,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "org.commonmark:commonmark",
|
"moduleName": "org.commonmark:commonmark",
|
||||||
"moduleVersion": "0.25.0",
|
"moduleVersion": "0.25.1",
|
||||||
"moduleLicense": "BSD-2-Clause",
|
"moduleLicense": "BSD-2-Clause",
|
||||||
"moduleLicenseUrl": "https://opensource.org/licenses/BSD-2-Clause"
|
"moduleLicenseUrl": "https://opensource.org/licenses/BSD-2-Clause"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "org.commonmark:commonmark-ext-gfm-tables",
|
"moduleName": "org.commonmark:commonmark-ext-gfm-tables",
|
||||||
"moduleVersion": "0.25.0",
|
"moduleVersion": "0.25.1",
|
||||||
"moduleLicense": "BSD-2-Clause",
|
"moduleLicense": "BSD-2-Clause",
|
||||||
"moduleLicenseUrl": "https://opensource.org/licenses/BSD-2-Clause"
|
"moduleLicenseUrl": "https://opensource.org/licenses/BSD-2-Clause"
|
||||||
},
|
},
|
||||||
|
@ -156,7 +156,7 @@
|
|||||||
.bookmark-actions {
|
.bookmark-actions {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Collapse/expand icons */
|
/* Collapse/expand icons */
|
||||||
@ -274,3 +274,25 @@
|
|||||||
--bg-empty: var(--md-sys-color-surface-container-low, #24282e);
|
--bg-empty: var(--md-sys-color-surface-container-low, #24282e);
|
||||||
--border-empty: var(--md-sys-color-outline, #495057);
|
--border-empty: var(--md-sys-color-outline, #495057);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.success-flash {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-flash::after {
|
||||||
|
content: "✓";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: white;
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeOut 1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut {
|
||||||
|
0% { opacity: 1; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
@ -16,21 +16,96 @@ function compareVersions(version1, version2) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLatestReleaseVersion() {
|
function getDownloadUrl() {
|
||||||
const url = "https://api.github.com/repos/Stirling-Tools/Stirling-PDF/releases/latest";
|
// Only show download for non-Docker installations
|
||||||
|
if (machineType === 'Docker' || machineType === 'Kubernetes') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = 'https://files.stirlingpdf.com/';
|
||||||
|
|
||||||
|
// Determine file based on machine type and security
|
||||||
|
if (machineType === 'Server-jar') {
|
||||||
|
return baseUrl + (activeSecurity ? 'Stirling-PDF-with-login.jar' : 'Stirling-PDF.jar');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client installations
|
||||||
|
if (machineType.startsWith('Client-')) {
|
||||||
|
const os = machineType.replace('Client-', ''); // win, mac, unix
|
||||||
|
const type = activeSecurity ? '-server-security' : '-server';
|
||||||
|
|
||||||
|
if (os === 'unix') {
|
||||||
|
return baseUrl + os + type + '.jar';
|
||||||
|
} else if (os === 'win') {
|
||||||
|
return baseUrl + os + '-installer.exe';
|
||||||
|
} else if (os === 'mac') {
|
||||||
|
return baseUrl + os + '-installer.dmg';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get translated priority text
|
||||||
|
function getTranslatedPriority(priority) {
|
||||||
|
switch(priority?.toLowerCase()) {
|
||||||
|
case 'urgent': return updatePriorityUrgent;
|
||||||
|
case 'normal': return updatePriorityNormal;
|
||||||
|
case 'minor': return updatePriorityMinor;
|
||||||
|
case 'low': return updatePriorityLow;
|
||||||
|
default: return priority?.toUpperCase() || 'NORMAL';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUpdateSummary() {
|
||||||
|
// Map Java License enum to API types
|
||||||
|
let type = 'normal';
|
||||||
|
if (licenseType === 'PRO') {
|
||||||
|
type = 'pro';
|
||||||
|
} else if (licenseType === 'ENTERPRISE') {
|
||||||
|
type = 'enterprise';
|
||||||
|
}
|
||||||
|
const url = `https://supabase.stirling.com/functions/v1/updates?from=${currentVersion}&type=${type}&login=${activeSecurity}&summary=true`;
|
||||||
|
console.log("Fetching update summary from:", url);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
console.log("Response status:", response.status);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data.tag_name ? data.tag_name.substring(1) : "";
|
return data;
|
||||||
} else {
|
} else {
|
||||||
// If the status is not 200, try to get the version from build.gradle
|
console.error("Failed to fetch update summary from Supabase:", response.status);
|
||||||
return await getCurrentVersionFromBypass();
|
return null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch latest version from GitHub:", error);
|
console.error("Failed to fetch update summary from Supabase:", error);
|
||||||
// If an error occurs, try to get the version from build.gradle
|
return null;
|
||||||
return await getCurrentVersionFromBypass();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFullUpdateInfo() {
|
||||||
|
// Map Java License enum to API types
|
||||||
|
let type = 'normal';
|
||||||
|
if (licenseType === 'PRO') {
|
||||||
|
type = 'pro';
|
||||||
|
} else if (licenseType === 'ENTERPRISE') {
|
||||||
|
type = 'enterprise';
|
||||||
|
}
|
||||||
|
const url = `https://supabase.stirling.com/functions/v1/updates?from=${currentVersion}&type=${type}&login=${activeSecurity}&summary=false`;
|
||||||
|
console.log("Fetching full update info from:", url);
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
console.log("Full update response status:", response.status);
|
||||||
|
if (response.status === 200) {
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch full update info from Supabase:", response.status);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch full update info from Supabase:", error);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +135,7 @@ async function checkForUpdate() {
|
|||||||
var updateLinkLegacy = document.getElementById("update-link-legacy") || null;
|
var updateLinkLegacy = document.getElementById("update-link-legacy") || null;
|
||||||
if (updateBtn !== null) {
|
if (updateBtn !== null) {
|
||||||
updateBtn.style.display = "none";
|
updateBtn.style.display = "none";
|
||||||
|
updateBtn.classList.remove("btn-danger", "btn-warning", "btn-outline-primary");
|
||||||
}
|
}
|
||||||
if (updateLink !== null) {
|
if (updateLink !== null) {
|
||||||
updateLink.style.display = "none";
|
updateLink.style.display = "none";
|
||||||
@ -71,19 +147,47 @@ async function checkForUpdate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const latestVersion = await getLatestReleaseVersion();
|
const updateSummary = await getUpdateSummary();
|
||||||
console.log("latestVersion=" + latestVersion);
|
if (!updateSummary) {
|
||||||
|
console.log("No update summary available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("updateSummary=", updateSummary);
|
||||||
console.log("currentVersion=" + currentVersion);
|
console.log("currentVersion=" + currentVersion);
|
||||||
console.log("compareVersions(latestVersion, currentVersion) > 0)=" + compareVersions(latestVersion, currentVersion));
|
console.log("latestVersion=" + updateSummary.latest_version);
|
||||||
if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
|
|
||||||
|
if (updateSummary.latest_version && compareVersions(updateSummary.latest_version, currentVersion) > 0) {
|
||||||
|
const priority = updateSummary.max_priority || 'normal';
|
||||||
|
|
||||||
if (updateBtn != null) {
|
if (updateBtn != null) {
|
||||||
document.getElementById("update-btn").style.display = "block";
|
// Style button based on priority
|
||||||
|
if (priority === 'urgent') {
|
||||||
|
updateBtn.classList.add("btn-danger");
|
||||||
|
updateBtn.innerHTML = urgentUpdateAvailable;
|
||||||
|
} else if (priority === 'normal') {
|
||||||
|
updateBtn.classList.add("btn-warning");
|
||||||
|
updateBtn.innerHTML = updateAvailableText;
|
||||||
|
} else {
|
||||||
|
updateBtn.classList.add("btn-outline-primary");
|
||||||
|
updateBtn.innerHTML = updateAvailableText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store summary for initial display
|
||||||
|
updateBtn.setAttribute('data-update-summary', JSON.stringify(updateSummary));
|
||||||
|
updateBtn.style.display = "block";
|
||||||
|
|
||||||
|
// Add click handler for update details modal
|
||||||
|
updateBtn.onclick = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
showUpdateModal();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (updateLink !== null) {
|
if (updateLink !== null) {
|
||||||
document.getElementById("update-link").style.display = "flex";
|
document.getElementById("update-link").style.display = "flex";
|
||||||
}
|
}
|
||||||
if (updateLinkLegacy !== null) {
|
if (updateLinkLegacy !== null) {
|
||||||
document.getElementById("app-update").innerHTML = updateAvailable.replace("{0}", '<b>' + currentVersion + '</b>').replace("{1}", '<b>' + latestVersion + '</b>');
|
document.getElementById("app-update").innerHTML = updateAvailable.replace("{0}", '<b>' + currentVersion + '</b>').replace("{1}", '<b>' + updateSummary.latest_version + '</b>');
|
||||||
if (updateLinkLegacy.classList.contains("visually-hidden")) {
|
if (updateLinkLegacy.classList.contains("visually-hidden")) {
|
||||||
updateLinkLegacy.classList.remove("visually-hidden");
|
updateLinkLegacy.classList.remove("visually-hidden");
|
||||||
}
|
}
|
||||||
@ -99,6 +203,188 @@ async function checkForUpdate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function showUpdateModal() {
|
||||||
|
// Close settings modal if open
|
||||||
|
const settingsModal = bootstrap.Modal.getInstance(document.getElementById('settingsModal'));
|
||||||
|
if (settingsModal) {
|
||||||
|
settingsModal.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get summary data from button
|
||||||
|
const updateBtn = document.getElementById("update-btn");
|
||||||
|
const summaryData = JSON.parse(updateBtn.getAttribute('data-update-summary'));
|
||||||
|
|
||||||
|
// Utility function to escape HTML special characters
|
||||||
|
function escapeHtml(str) {
|
||||||
|
if (typeof str !== 'string') return str;
|
||||||
|
return str
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
.replace(/\//g, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create initial modal with loading state
|
||||||
|
const initialModalHtml = `
|
||||||
|
<div class="modal fade" id="updateModal" tabindex="-1" role="dialog" aria-labelledby="updateModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable" role="document" style="max-height: 80vh;">
|
||||||
|
<div class="modal-content" style="max-height: 80vh;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="updateModalLabel">${updateModalTitle}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
|
||||||
|
<span class="material-symbols-rounded">close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="updateModalBody" style="max-height: 60vh; overflow-y: auto;">
|
||||||
|
<div class="update-summary mb-4">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="${summaryData.latest_stable_version ? 'col-4' : 'col-6'} text-center">
|
||||||
|
<small class="text-muted">${updateCurrent}</small><br>
|
||||||
|
<strong>${escapeHtml(currentVersion)}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="${summaryData.latest_stable_version ? 'col-4' : 'col-6'} text-center">
|
||||||
|
<small class="text-muted">${updateLatest}</small><br>
|
||||||
|
<strong class="text-primary">${escapeHtml(summaryData.latest_version)}</strong>
|
||||||
|
</div>
|
||||||
|
${summaryData.latest_stable_version ? `
|
||||||
|
<div class="col-4 text-center">
|
||||||
|
<small class="text-muted">${updateLatestStable}</small><br>
|
||||||
|
<strong class="text-success">${escapeHtml(summaryData.latest_stable_version)}</strong>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="alert ${summaryData.max_priority === 'urgent' ? 'alert-danger' : 'alert-warning'}" role="alert">
|
||||||
|
<strong>${updatePriority}:</strong> ${getTranslatedPriority(summaryData.max_priority)}
|
||||||
|
${summaryData.recommended_action ? `<br><strong>${updateRecommendedAction}:</strong> ${escapeHtml(summaryData.recommended_action)}` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${summaryData.any_breaking ? `
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<h6><strong>${updateBreakingChangesDetected}</strong></h6>
|
||||||
|
<p>${updateBreakingChangesMessage}</p>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
${summaryData.migration_guides && summaryData.migration_guides.length > 0 ? `
|
||||||
|
<div class="migration-guides mb-4">
|
||||||
|
<h6>${updateMigrationGuides}</h6>
|
||||||
|
<ul class="list-group">
|
||||||
|
${summaryData.migration_guides.map(guide => `
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<strong>${updateVersion} ${escapeHtml(guide.version)}:</strong> ${escapeHtml(guide.notes)}
|
||||||
|
</div>
|
||||||
|
<a href="${escapeHtml(guide.url)}" target="_blank" class="btn btn-sm btn-outline-primary">${updateViewGuide}</a>
|
||||||
|
</li>
|
||||||
|
`).join('')}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="spinner-border text-primary" role="status" id="loadingSpinner">
|
||||||
|
<span class="visually-hidden">${updateLoadingDetailedInfo}</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2">${updateLoadingDetailedInfo}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">${updateClose}</button>
|
||||||
|
<a href="https://github.com/Stirling-Tools/Stirling-PDF/releases" target="_blank" class="btn btn-outline-primary">${updateViewAllReleases}</a>
|
||||||
|
${getDownloadUrl() ? `<a href="${escapeHtml(getDownloadUrl())}" class="btn btn-success" target="_blank">${updateDownloadLatest}</a>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Remove existing modal if present
|
||||||
|
const existingModal = document.getElementById('updateModal');
|
||||||
|
if (existingModal) {
|
||||||
|
existingModal.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add modal to body
|
||||||
|
document.body.insertAdjacentHTML('beforeend', initialModalHtml);
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('updateModal'));
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
// Fetch full update info
|
||||||
|
const fullUpdateInfo = await getFullUpdateInfo();
|
||||||
|
|
||||||
|
// Update modal with full information
|
||||||
|
const modalBody = document.getElementById('updateModalBody');
|
||||||
|
if (fullUpdateInfo && fullUpdateInfo.new_versions) {
|
||||||
|
const storedMode = localStorage.getItem("dark-mode");
|
||||||
|
const isDarkMode = storedMode === "on" ||
|
||||||
|
(storedMode === null && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||||
|
const darkClasses = isDarkMode ? {
|
||||||
|
accordionItem: 'bg-dark border-secondary text-light',
|
||||||
|
accordionButton: 'bg-dark text-light border-secondary',
|
||||||
|
accordionBody: 'bg-dark text-light'
|
||||||
|
} : {
|
||||||
|
accordionItem: '',
|
||||||
|
accordionButton: '',
|
||||||
|
accordionBody: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailedVersionsHtml = `
|
||||||
|
<div class="detailed-versions mt-4">
|
||||||
|
<h6>${updateAvailableUpdates}</h6>
|
||||||
|
<div class="accordion" id="versionsAccordion">
|
||||||
|
${fullUpdateInfo.new_versions.map((version, index) => `
|
||||||
|
<div class="accordion-item" style="border-color: var(--md-sys-color-outline);">
|
||||||
|
<h2 class="accordion-header" id="heading${index}">
|
||||||
|
<button class="accordion-button ${index === 0 ? '' : 'collapsed'}" style="color: var(--md-sys-color-on-surface); background-color:
|
||||||
|
var(--md-sys-color-surface);" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapse${index}" aria-expanded="${index === 0 ? 'true' : 'false'}" aria-controls="collapse${index}">
|
||||||
|
<div class="d-flex justify-content-between w-100 me-3">
|
||||||
|
<span><strong>${updateVersion} ${version.version}</strong></span>
|
||||||
|
<span class="badge ${version.priority === 'urgent' ? 'bg-danger' : version.priority === 'normal' ? 'bg-warning' : 'bg-secondary'}">${getTranslatedPriority(version.priority)}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapse${index}" class="accordion-collapse collapse ${index === 0 ? 'show' : ''}"
|
||||||
|
aria-labelledby="heading${index}" data-bs-parent="#versionsAccordion">
|
||||||
|
<div class="accordion-body" style="color: var(--md-sys-color-on-surface); background-color:
|
||||||
|
var(--md-sys-color-surface-bright);">
|
||||||
|
<h6>${version.announcement.title}</h6>
|
||||||
|
<p>${version.announcement.message}</p>
|
||||||
|
${version.compatibility.breaking_changes ? `
|
||||||
|
<div class="alert alert-warning alert-sm" role="alert">
|
||||||
|
<small><strong>⚠️ ${updateBreakingChanges}</strong> ${version.compatibility.breaking_description || updateBreakingChangesDefault}</small>
|
||||||
|
${version.compatibility.migration_guide_url ? `<br><a href="${version.compatibility.migration_guide_url}" target="_blank" class="btn btn-sm btn-outline-warning mt-1">${updateMigrationGuide}</a>` : ''}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Remove loading spinner and add detailed info
|
||||||
|
const spinner = document.getElementById('loadingSpinner');
|
||||||
|
if (spinner) {
|
||||||
|
spinner.parentElement.remove();
|
||||||
|
}
|
||||||
|
modalBody.insertAdjacentHTML('beforeend', detailedVersionsHtml);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Remove loading spinner if failed to load
|
||||||
|
const spinner = document.getElementById('loadingSpinner');
|
||||||
|
if (spinner) {
|
||||||
|
spinner.parentElement.innerHTML = `<p class="text-muted">${updateUnableToLoadDetails}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", (event) => {
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
checkForUpdate();
|
checkForUpdate();
|
||||||
});
|
});
|
||||||
|
@ -1,88 +1,117 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const bookmarksContainer = document.getElementById('bookmarks-container');
|
const bookmarksContainer = document.getElementById("bookmarks-container");
|
||||||
const addBookmarkBtn = document.getElementById('addBookmarkBtn');
|
const errorMessageContainer = document.getElementById("error-message-container");
|
||||||
const bookmarkDataInput = document.getElementById('bookmarkData');
|
const addBookmarkBtn = document.getElementById("addBookmarkBtn");
|
||||||
|
const bookmarkDataInput = document.getElementById("bookmarkData");
|
||||||
let bookmarks = [];
|
let bookmarks = [];
|
||||||
let counter = 0; // Used for generating unique IDs
|
let counter = 0; // Used for generating unique IDs
|
||||||
|
|
||||||
// Add event listener to the file input to extract existing bookmarks
|
// callback function on file input change to extract bookmarks from PDF
|
||||||
document.getElementById('fileInput-input').addEventListener('change', async function(e) {
|
async function getBookmarkDataFromPdf(event) {
|
||||||
if (!e.target.files || e.target.files.length === 0) {
|
if (!event.target.files || event.target.files.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset bookmarks initially
|
|
||||||
bookmarks = [];
|
|
||||||
updateBookmarksUI();
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', e.target.files[0]);
|
formData.append("file", event.target.files[0]);
|
||||||
|
|
||||||
// Show loading indicator
|
|
||||||
showLoadingIndicator();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call the API to extract bookmarks using fetchWithCsrf for CSRF protection
|
// Call the API to extract bookmarks using fetchWithCsrf for CSRF protection
|
||||||
const response = await fetchWithCsrf('/api/v1/general/extract-bookmarks', {
|
const response = await fetchWithCsrf("/api/v1/general/extract-bookmarks", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
body: formData
|
body: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to extract bookmarks: ${response.status} ${response.statusText}`);
|
throw new Error(`Failed to fetch API: ${response.status} ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractedBookmarks = await response.json();
|
const extractedBookmarks = await response.json();
|
||||||
|
return extractedBookmarks;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("Error extracting bookmark data:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// callback function on file input change to extract bookmarks from JSON
|
||||||
|
async function getBookmarkDataFromJson(event) {
|
||||||
|
if (!event.target.files || event.target.files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = event.target.files[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileText = await file.text();
|
||||||
|
const jsonData = JSON.parse(fileText);
|
||||||
|
return jsonData;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error extracting bookmark data: error while reading or parsing JSON file: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// display new bookmark data given by a callback function that loads or fetches the data
|
||||||
|
async function loadBookmarks(getBookmarkDataCallback) {
|
||||||
|
// reset bookmarks
|
||||||
|
bookmarks = [];
|
||||||
|
updateBookmarksUI();
|
||||||
|
showLoadingIndicator();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get new bookmarks from the callback
|
||||||
|
const newBookmarks = await getBookmarkDataCallback();
|
||||||
|
|
||||||
// Convert extracted bookmarks to our format with IDs
|
// Convert extracted bookmarks to our format with IDs
|
||||||
if (extractedBookmarks && extractedBookmarks.length > 0) {
|
if (newBookmarks && newBookmarks.length > 0) {
|
||||||
bookmarks = extractedBookmarks.map(convertExtractedBookmark);
|
bookmarks = newBookmarks.map(convertExtractedBookmark);
|
||||||
} else {
|
|
||||||
showEmptyState();
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Show error message
|
bookmarks = [];
|
||||||
showErrorMessage('Failed to extract bookmarks. You can still create new ones.');
|
throw new Error(`Error loading bookmarks: ${error}`);
|
||||||
|
|
||||||
// Add a default bookmark if no bookmarks and error
|
|
||||||
if (bookmarks.length === 0) {
|
|
||||||
showEmptyState();
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
// Remove loading indicator
|
|
||||||
removeLoadingIndicator();
|
removeLoadingIndicator();
|
||||||
|
|
||||||
// Update the UI
|
|
||||||
updateBookmarksUI();
|
updateBookmarksUI();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listener to the file input to extract existing bookmarks
|
||||||
|
document.getElementById("fileInput-input").addEventListener("change", async function (event) {
|
||||||
|
try {
|
||||||
|
await loadBookmarks(async function () {
|
||||||
|
return getBookmarkDataFromPdf(event);
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
showErrorMessage("Failed to extract bookmarks. You can still create new ones.");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function showLoadingIndicator() {
|
function showLoadingIndicator() {
|
||||||
const loadingEl = document.createElement('div');
|
const loadingEl = document.createElement("div");
|
||||||
loadingEl.className = 'alert alert-info';
|
loadingEl.className = "alert alert-info";
|
||||||
loadingEl.textContent = 'Loading bookmarks from PDF...';
|
loadingEl.textContent = "Loading bookmarks from PDF...";
|
||||||
loadingEl.id = 'loading-bookmarks';
|
loadingEl.id = "loading-bookmarks";
|
||||||
bookmarksContainer.innerHTML = '';
|
errorMessageContainer.innerHTML = "";
|
||||||
|
bookmarksContainer.innerHTML = "";
|
||||||
bookmarksContainer.appendChild(loadingEl);
|
bookmarksContainer.appendChild(loadingEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeLoadingIndicator() {
|
function removeLoadingIndicator() {
|
||||||
const loadingEl = document.getElementById('loading-bookmarks');
|
const loadingEl = document.getElementById("loading-bookmarks");
|
||||||
if (loadingEl) {
|
if (loadingEl) {
|
||||||
loadingEl.remove();
|
loadingEl.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showErrorMessage(message) {
|
function showErrorMessage(message) {
|
||||||
const errorEl = document.createElement('div');
|
const errorEl = document.createElement("div");
|
||||||
errorEl.className = 'alert alert-danger';
|
errorEl.className = "alert alert-danger";
|
||||||
errorEl.textContent = message;
|
errorEl.textContent = message;
|
||||||
bookmarksContainer.appendChild(errorEl);
|
errorMessageContainer.appendChild(errorEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showEmptyState() {
|
function showEmptyState() {
|
||||||
const emptyStateEl = document.createElement('div');
|
const emptyStateEl = document.createElement("div");
|
||||||
emptyStateEl.className = 'empty-bookmarks mb-3';
|
emptyStateEl.className = "empty-bookmarks mb-3";
|
||||||
emptyStateEl.innerHTML = `
|
emptyStateEl.innerHTML = `
|
||||||
<span class="material-symbols-rounded mb-2" style="font-size: 48px;">bookmark_add</span>
|
<span class="material-symbols-rounded mb-2" style="font-size: 48px;">bookmark_add</span>
|
||||||
<h5>No bookmarks found</h5>
|
<h5>No bookmarks found</h5>
|
||||||
@ -93,8 +122,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Add event listener to the "Add First Bookmark" button
|
// Add event listener to the "Add First Bookmark" button
|
||||||
emptyStateEl.querySelector('.btn-add-first-bookmark').addEventListener('click', function() {
|
emptyStateEl.querySelector(".btn-add-first-bookmark").addEventListener("click", function () {
|
||||||
addBookmark(null, 'New Bookmark', 1);
|
addBookmark(null, "New Bookmark", 1);
|
||||||
emptyStateEl.remove();
|
emptyStateEl.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -106,15 +135,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
counter++;
|
counter++;
|
||||||
const result = {
|
const result = {
|
||||||
id: Date.now() + counter, // Generate a unique ID
|
id: Date.now() + counter, // Generate a unique ID
|
||||||
title: bookmark.title || 'Untitled Bookmark',
|
title: bookmark.title || "Untitled Bookmark",
|
||||||
pageNumber: bookmark.pageNumber || 1,
|
pageNumber: bookmark.pageNumber || 1,
|
||||||
children: [],
|
children: [],
|
||||||
expanded: false // All bookmarks start collapsed for better visibility
|
expanded: false, // All bookmarks start collapsed for better visibility
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert children recursively
|
// Convert children recursively
|
||||||
if (bookmark.children && bookmark.children.length > 0) {
|
if (bookmark.children && bookmark.children.length > 0) {
|
||||||
result.children = bookmark.children.map(child => {
|
result.children = bookmark.children.map((child) => {
|
||||||
return convertExtractedBookmark(child);
|
return convertExtractedBookmark(child);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -123,24 +152,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add bookmark button click handler
|
// Add bookmark button click handler
|
||||||
addBookmarkBtn.addEventListener('click', function(e) {
|
addBookmarkBtn.addEventListener("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addBookmark();
|
addBookmark();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add form submit handler to update JSON data
|
// Add form submit handler to update JSON data
|
||||||
document.getElementById('editTocForm').addEventListener('submit', function() {
|
document.getElementById("editTocForm").addEventListener("submit", function () {
|
||||||
updateBookmarkData();
|
updateBookmarkData();
|
||||||
});
|
});
|
||||||
|
|
||||||
function addBookmark(parent = null, title = '', pageNumber = 1) {
|
function addBookmark(parent = null, title = "", pageNumber = 1) {
|
||||||
counter++;
|
counter++;
|
||||||
const newBookmark = {
|
const newBookmark = {
|
||||||
id: Date.now() + counter,
|
id: Date.now() + counter,
|
||||||
title: title || 'New Bookmark',
|
title: title || "New Bookmark",
|
||||||
pageNumber: pageNumber || 1,
|
pageNumber: pageNumber || 1,
|
||||||
children: [],
|
children: [],
|
||||||
expanded: false // New bookmarks start collapsed
|
expanded: false, // New bookmarks start collapsed
|
||||||
};
|
};
|
||||||
|
|
||||||
if (parent === null) {
|
if (parent === null) {
|
||||||
@ -162,13 +191,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const newElement = document.querySelector(`[data-id="${newBookmark.id}"]`);
|
const newElement = document.querySelector(`[data-id="${newBookmark.id}"]`);
|
||||||
if (newElement) {
|
if (newElement) {
|
||||||
const titleInput = newElement.querySelector('.bookmark-title');
|
const titleInput = newElement.querySelector(".bookmark-title");
|
||||||
if (titleInput) {
|
if (titleInput) {
|
||||||
titleInput.focus();
|
titleInput.focus();
|
||||||
titleInput.select();
|
titleInput.select();
|
||||||
}
|
}
|
||||||
// Scroll to the new element
|
// Scroll to the new element
|
||||||
newElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
newElement.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
}
|
}
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
@ -203,7 +232,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
function removeBookmark(id) {
|
function removeBookmark(id) {
|
||||||
// Remove from top level
|
// Remove from top level
|
||||||
const index = bookmarks.findIndex(b => b.id === id);
|
const index = bookmarks.findIndex((b) => b.id === id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
bookmarks.splice(index, 1);
|
bookmarks.splice(index, 1);
|
||||||
updateBookmarksUI();
|
updateBookmarksUI();
|
||||||
@ -213,7 +242,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Remove from children
|
// Remove from children
|
||||||
function removeFromChildren(bookmarkArray, id) {
|
function removeFromChildren(bookmarkArray, id) {
|
||||||
for (const bookmark of bookmarkArray) {
|
for (const bookmark of bookmarkArray) {
|
||||||
const childIndex = bookmark.children.findIndex(b => b.id === id);
|
const childIndex = bookmark.children.findIndex((b) => b.id === id);
|
||||||
if (childIndex !== -1) {
|
if (childIndex !== -1) {
|
||||||
bookmark.children.splice(childIndex, 1);
|
bookmark.children.splice(childIndex, 1);
|
||||||
return true;
|
return true;
|
||||||
@ -253,7 +282,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return {
|
return {
|
||||||
title: bookmark.title,
|
title: bookmark.title,
|
||||||
pageNumber: bookmark.pageNumber,
|
pageNumber: bookmark.pageNumber,
|
||||||
children: bookmark.children.map(cleanBookmark)
|
children: bookmark.children.map(cleanBookmark),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,22 +292,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only clear the container if there are no error messages or loading indicators
|
// Only clear the container if there are no error messages or loading indicators
|
||||||
if (!document.querySelector('#bookmarks-container .alert')) {
|
if (!document.querySelector("#bookmarks-container .alert")) {
|
||||||
bookmarksContainer.innerHTML = '';
|
bookmarksContainer.innerHTML = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there are bookmarks to display
|
// Check if there are bookmarks to display
|
||||||
if (bookmarks.length === 0 && !document.querySelector('.empty-bookmarks')) {
|
if (bookmarks.length === 0 && !document.querySelector(".empty-bookmarks")) {
|
||||||
showEmptyState();
|
showEmptyState();
|
||||||
} else {
|
} else {
|
||||||
// Remove empty state if it exists and there are bookmarks
|
// Remove empty state if it exists and there are bookmarks
|
||||||
const emptyState = document.querySelector('.empty-bookmarks');
|
const emptyState = document.querySelector(".empty-bookmarks");
|
||||||
if (emptyState && bookmarks.length > 0) {
|
if (emptyState && bookmarks.length > 0) {
|
||||||
emptyState.remove();
|
emptyState.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create bookmark elements
|
// Create bookmark elements
|
||||||
bookmarks.forEach(bookmark => {
|
bookmarks.forEach((bookmark) => {
|
||||||
const bookmarkElement = createBookmarkElement(bookmark);
|
const bookmarkElement = createBookmarkElement(bookmark);
|
||||||
bookmarksContainer.appendChild(bookmarkElement);
|
bookmarksContainer.appendChild(bookmarkElement);
|
||||||
});
|
});
|
||||||
@ -287,15 +316,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
updateBookmarkData();
|
updateBookmarkData();
|
||||||
|
|
||||||
// Initialize tooltips for dynamically added elements
|
// Initialize tooltips for dynamically added elements
|
||||||
if (typeof $ !== 'undefined') {
|
if (typeof $ !== "undefined") {
|
||||||
$('[data-bs-toggle="tooltip"]').tooltip();
|
$('[data-bs-toggle="tooltip"]').tooltip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the main bookmark element with collapsible interface
|
// Create the main bookmark element with collapsible interface
|
||||||
function createBookmarkElement(bookmark, level = 0) {
|
function createBookmarkElement(bookmark, level = 0) {
|
||||||
const bookmarkEl = document.createElement('div');
|
const bookmarkEl = document.createElement("div");
|
||||||
bookmarkEl.className = 'bookmark-item';
|
bookmarkEl.className = "bookmark-item";
|
||||||
bookmarkEl.dataset.id = bookmark.id;
|
bookmarkEl.dataset.id = bookmark.id;
|
||||||
bookmarkEl.dataset.level = level;
|
bookmarkEl.dataset.level = level;
|
||||||
|
|
||||||
@ -304,10 +333,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
bookmarkEl.appendChild(header);
|
bookmarkEl.appendChild(header);
|
||||||
|
|
||||||
// Create the content (collapsible part)
|
// Create the content (collapsible part)
|
||||||
const content = document.createElement('div');
|
const content = document.createElement("div");
|
||||||
content.className = 'bookmark-content';
|
content.className = "bookmark-content";
|
||||||
if (!bookmark.expanded) {
|
if (!bookmark.expanded) {
|
||||||
content.style.display = 'none';
|
content.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main input row
|
// Main input row
|
||||||
@ -328,48 +357,48 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Create the header that's always visible
|
// Create the header that's always visible
|
||||||
function createBookmarkHeader(bookmark, level) {
|
function createBookmarkHeader(bookmark, level) {
|
||||||
const header = document.createElement('div');
|
const header = document.createElement("div");
|
||||||
header.className = 'bookmark-header';
|
header.className = "bookmark-header";
|
||||||
if (!bookmark.expanded) {
|
if (!bookmark.expanded) {
|
||||||
header.classList.add('collapsed');
|
header.classList.add("collapsed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Left side of header with expand/collapse and info
|
// Left side of header with expand/collapse and info
|
||||||
const headerLeft = document.createElement('div');
|
const headerLeft = document.createElement("div");
|
||||||
headerLeft.className = 'd-flex align-items-center';
|
headerLeft.className = "d-flex align-items-center";
|
||||||
|
|
||||||
// Toggle expand/collapse icon with child count
|
// Toggle expand/collapse icon with child count
|
||||||
const toggleContainer = document.createElement('div');
|
const toggleContainer = document.createElement("div");
|
||||||
toggleContainer.className = 'd-flex align-items-center';
|
toggleContainer.className = "d-flex align-items-center";
|
||||||
toggleContainer.style.marginRight = '8px';
|
toggleContainer.style.marginRight = "8px";
|
||||||
|
|
||||||
// Only show toggle if has children
|
// Only show toggle if has children
|
||||||
if (bookmark.children && bookmark.children.length > 0) {
|
if (bookmark.children && bookmark.children.length > 0) {
|
||||||
// Create toggle icon
|
// Create toggle icon
|
||||||
const toggleIcon = document.createElement('span');
|
const toggleIcon = document.createElement("span");
|
||||||
toggleIcon.className = 'material-symbols-rounded toggle-icon me-1';
|
toggleIcon.className = "material-symbols-rounded toggle-icon me-1";
|
||||||
toggleIcon.textContent = 'expand_more';
|
toggleIcon.textContent = "expand_more";
|
||||||
toggleIcon.style.cursor = 'pointer';
|
toggleIcon.style.cursor = "pointer";
|
||||||
toggleContainer.appendChild(toggleIcon);
|
toggleContainer.appendChild(toggleIcon);
|
||||||
|
|
||||||
// Add child count indicator
|
// Add child count indicator
|
||||||
const childCount = document.createElement('span');
|
const childCount = document.createElement("span");
|
||||||
childCount.className = 'badge rounded-pill';
|
childCount.className = "badge rounded-pill";
|
||||||
// Use theme-appropriate badge color
|
// Use theme-appropriate badge color
|
||||||
const isDarkMode = document.documentElement.getAttribute('data-bs-theme') === 'dark';
|
const isDarkMode = document.documentElement.getAttribute("data-bs-theme") === "dark";
|
||||||
childCount.classList.add(isDarkMode ? 'bg-info' : 'bg-secondary');
|
childCount.classList.add(isDarkMode ? "bg-info" : "bg-secondary");
|
||||||
childCount.style.fontSize = '0.7rem';
|
childCount.style.fontSize = "0.7rem";
|
||||||
childCount.style.padding = '0.2em 0.5em';
|
childCount.style.padding = "0.2em 0.5em";
|
||||||
childCount.textContent = bookmark.children.length;
|
childCount.textContent = bookmark.children.length;
|
||||||
childCount.setAttribute('data-bs-toggle', 'tooltip');
|
childCount.setAttribute("data-bs-toggle", "tooltip");
|
||||||
childCount.setAttribute('data-bs-placement', 'top');
|
childCount.setAttribute("data-bs-placement", "top");
|
||||||
childCount.title = `${bookmark.children.length} child bookmark${bookmark.children.length > 1 ? 's' : ''}`;
|
childCount.title = `${bookmark.children.length} child bookmark${bookmark.children.length > 1 ? "s" : ""}`;
|
||||||
toggleContainer.appendChild(childCount);
|
toggleContainer.appendChild(childCount);
|
||||||
} else {
|
} else {
|
||||||
// Add spacer if no children
|
// Add spacer if no children
|
||||||
const spacer = document.createElement('span');
|
const spacer = document.createElement("span");
|
||||||
spacer.style.width = '24px';
|
spacer.style.width = "24px";
|
||||||
spacer.style.display = 'inline-block';
|
spacer.style.display = "inline-block";
|
||||||
toggleContainer.appendChild(spacer);
|
toggleContainer.appendChild(spacer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,65 +407,68 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Level indicator for nested items
|
// Level indicator for nested items
|
||||||
if (level > 0) {
|
if (level > 0) {
|
||||||
// Add relationship indicator visual line
|
// Add relationship indicator visual line
|
||||||
const relationshipIndicator = document.createElement('div');
|
const relationshipIndicator = document.createElement("div");
|
||||||
relationshipIndicator.className = 'bookmark-relationship-indicator';
|
relationshipIndicator.className = "bookmark-relationship-indicator";
|
||||||
|
|
||||||
const line = document.createElement('div');
|
const line = document.createElement("div");
|
||||||
line.className = 'relationship-line';
|
line.className = "relationship-line";
|
||||||
relationshipIndicator.appendChild(line);
|
relationshipIndicator.appendChild(line);
|
||||||
|
|
||||||
const arrow = document.createElement('div');
|
const arrow = document.createElement("div");
|
||||||
arrow.className = 'relationship-arrow';
|
arrow.className = "relationship-arrow";
|
||||||
relationshipIndicator.appendChild(arrow);
|
relationshipIndicator.appendChild(arrow);
|
||||||
|
|
||||||
header.appendChild(relationshipIndicator);
|
header.appendChild(relationshipIndicator);
|
||||||
|
|
||||||
// Text indicator
|
// Text indicator
|
||||||
const levelIndicator = document.createElement('span');
|
const levelIndicator = document.createElement("span");
|
||||||
levelIndicator.className = 'bookmark-level-indicator';
|
levelIndicator.className = "bookmark-level-indicator";
|
||||||
levelIndicator.textContent = `Child`;
|
levelIndicator.textContent = `Child`;
|
||||||
headerLeft.appendChild(levelIndicator);
|
headerLeft.appendChild(levelIndicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title preview
|
// Title preview
|
||||||
const titlePreview = document.createElement('span');
|
const titlePreview = document.createElement("span");
|
||||||
titlePreview.className = 'bookmark-title-preview';
|
titlePreview.className = "bookmark-title-preview";
|
||||||
titlePreview.textContent = bookmark.title;
|
titlePreview.textContent = bookmark.title;
|
||||||
headerLeft.appendChild(titlePreview);
|
headerLeft.appendChild(titlePreview);
|
||||||
|
|
||||||
// Page number preview
|
// Page number preview
|
||||||
const pagePreview = document.createElement('span');
|
const pagePreview = document.createElement("span");
|
||||||
pagePreview.className = 'bookmark-page-preview';
|
pagePreview.className = "bookmark-page-preview";
|
||||||
pagePreview.textContent = `Page ${bookmark.pageNumber}`;
|
pagePreview.textContent = `Page ${bookmark.pageNumber}`;
|
||||||
headerLeft.appendChild(pagePreview);
|
headerLeft.appendChild(pagePreview);
|
||||||
|
|
||||||
// Right side of header with action buttons
|
// Right side of header with action buttons
|
||||||
const headerRight = document.createElement('div');
|
const headerRight = document.createElement("div");
|
||||||
headerRight.className = 'bookmark-actions-header';
|
headerRight.className = "bookmark-actions-header";
|
||||||
|
|
||||||
// Quick add buttons with clear visual distinction - using Stirling-PDF's tooltip system
|
// Quick add buttons with clear visual distinction - using Stirling-PDF's tooltip system
|
||||||
const quickAddChildButton = createButton('subdirectory_arrow_right', 'btn-add-child', 'Add child bookmark', function(e) {
|
const quickAddChildButton = createButton("subdirectory_arrow_right", "btn-add-child", "Add child bookmark", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
addBookmark(bookmark.id);
|
addBookmark(bookmark.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
const quickAddSiblingButton = createButton('add', 'btn-add-sibling', 'Add sibling bookmark', function(e) {
|
const quickAddSiblingButton = createButton("add", "btn-add-sibling", "Add sibling bookmark", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// Find parent of current bookmark
|
// Find parent of current bookmark
|
||||||
const parentId = findParentBookmark(bookmarks, bookmark.id);
|
const parentId = findParentBookmark(bookmarks, bookmark.id);
|
||||||
addBookmark(parentId, '', bookmark.pageNumber); // Same level as current bookmark
|
addBookmark(parentId, "", bookmark.pageNumber); // Same level as current bookmark
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quick remove button
|
// Quick remove button
|
||||||
const quickRemoveButton = createButton('delete', 'btn-outline-danger', 'Remove bookmark', function(e) {
|
const quickRemoveButton = createButton("delete", "btn-outline-danger", "Remove bookmark", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (confirm('Are you sure you want to remove this bookmark' +
|
if (
|
||||||
(bookmark.children.length > 0 ? ' and all its children?' : '?'))) {
|
confirm(
|
||||||
|
"Are you sure you want to remove this bookmark" + (bookmark.children.length > 0 ? " and all its children?" : "?")
|
||||||
|
)
|
||||||
|
) {
|
||||||
removeBookmark(bookmark.id);
|
removeBookmark(bookmark.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -450,9 +482,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
header.appendChild(headerRight);
|
header.appendChild(headerRight);
|
||||||
|
|
||||||
// Add click handler for expansion toggle
|
// Add click handler for expansion toggle
|
||||||
header.addEventListener('click', function(e) {
|
header.addEventListener("click", function (e) {
|
||||||
// Only toggle if not clicking on buttons
|
// Only toggle if not clicking on buttons
|
||||||
if (!e.target.closest('button')) {
|
if (!e.target.closest("button")) {
|
||||||
toggleBookmarkExpanded(bookmark.id);
|
toggleBookmarkExpanded(bookmark.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -461,8 +493,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createInputRow(bookmark) {
|
function createInputRow(bookmark) {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement("div");
|
||||||
row.className = 'row';
|
row.className = "row";
|
||||||
|
|
||||||
// Title input
|
// Title input
|
||||||
row.appendChild(createTitleInputElement(bookmark));
|
row.appendChild(createTitleInputElement(bookmark));
|
||||||
@ -474,26 +506,26 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createTitleInputElement(bookmark) {
|
function createTitleInputElement(bookmark) {
|
||||||
const titleCol = document.createElement('div');
|
const titleCol = document.createElement("div");
|
||||||
titleCol.className = 'col-md-8';
|
titleCol.className = "col-md-8";
|
||||||
|
|
||||||
const titleGroup = document.createElement('div');
|
const titleGroup = document.createElement("div");
|
||||||
titleGroup.className = 'mb-3';
|
titleGroup.className = "mb-3";
|
||||||
|
|
||||||
const titleLabel = document.createElement('label');
|
const titleLabel = document.createElement("label");
|
||||||
titleLabel.textContent = 'Title';
|
titleLabel.textContent = "Title";
|
||||||
titleLabel.className = 'form-label';
|
titleLabel.className = "form-label";
|
||||||
|
|
||||||
const titleInput = document.createElement('input');
|
const titleInput = document.createElement("input");
|
||||||
titleInput.type = 'text';
|
titleInput.type = "text";
|
||||||
titleInput.className = 'form-control bookmark-title';
|
titleInput.className = "form-control bookmark-title";
|
||||||
titleInput.value = bookmark.title;
|
titleInput.value = bookmark.title;
|
||||||
titleInput.addEventListener('input', function() {
|
titleInput.addEventListener("input", function () {
|
||||||
bookmark.title = this.value;
|
bookmark.title = this.value;
|
||||||
updateBookmarkData();
|
updateBookmarkData();
|
||||||
|
|
||||||
// Also update the preview in the header
|
// Also update the preview in the header
|
||||||
const header = titleInput.closest('.bookmark-item').querySelector('.bookmark-title-preview');
|
const header = titleInput.closest(".bookmark-item").querySelector(".bookmark-title-preview");
|
||||||
if (header) {
|
if (header) {
|
||||||
header.textContent = this.value;
|
header.textContent = this.value;
|
||||||
}
|
}
|
||||||
@ -507,27 +539,27 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createPageInputElement(bookmark) {
|
function createPageInputElement(bookmark) {
|
||||||
const pageCol = document.createElement('div');
|
const pageCol = document.createElement("div");
|
||||||
pageCol.className = 'col-md-4';
|
pageCol.className = "col-md-4";
|
||||||
|
|
||||||
const pageGroup = document.createElement('div');
|
const pageGroup = document.createElement("div");
|
||||||
pageGroup.className = 'mb-3';
|
pageGroup.className = "mb-3";
|
||||||
|
|
||||||
const pageLabel = document.createElement('label');
|
const pageLabel = document.createElement("label");
|
||||||
pageLabel.textContent = 'Page';
|
pageLabel.textContent = "Page";
|
||||||
pageLabel.className = 'form-label';
|
pageLabel.className = "form-label";
|
||||||
|
|
||||||
const pageInput = document.createElement('input');
|
const pageInput = document.createElement("input");
|
||||||
pageInput.type = 'number';
|
pageInput.type = "number";
|
||||||
pageInput.className = 'form-control bookmark-page';
|
pageInput.className = "form-control bookmark-page";
|
||||||
pageInput.value = bookmark.pageNumber;
|
pageInput.value = bookmark.pageNumber;
|
||||||
pageInput.min = 1;
|
pageInput.min = 1;
|
||||||
pageInput.addEventListener('input', function() {
|
pageInput.addEventListener("input", function () {
|
||||||
bookmark.pageNumber = parseInt(this.value) || 1;
|
bookmark.pageNumber = parseInt(this.value) || 1;
|
||||||
updateBookmarkData();
|
updateBookmarkData();
|
||||||
|
|
||||||
// Also update the preview in the header
|
// Also update the preview in the header
|
||||||
const header = pageInput.closest('.bookmark-item').querySelector('.bookmark-page-preview');
|
const header = pageInput.closest(".bookmark-item").querySelector(".bookmark-page-preview");
|
||||||
if (header) {
|
if (header) {
|
||||||
header.textContent = `Page ${bookmark.pageNumber}`;
|
header.textContent = `Page ${bookmark.pageNumber}`;
|
||||||
}
|
}
|
||||||
@ -541,25 +573,25 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createButton(icon, className, title, clickHandler) {
|
function createButton(icon, className, title, clickHandler) {
|
||||||
const button = document.createElement('button');
|
const button = document.createElement("button");
|
||||||
button.type = 'button';
|
button.type = "button";
|
||||||
button.className = `btn ${className} btn-bookmark-action`;
|
button.className = `btn ${className} btn-bookmark-action`;
|
||||||
button.innerHTML = `<span class="material-symbols-rounded">${icon}</span>`;
|
button.innerHTML = `<span class="material-symbols-rounded">${icon}</span>`;
|
||||||
|
|
||||||
// Use Bootstrap tooltips
|
// Use Bootstrap tooltips
|
||||||
button.setAttribute('data-bs-toggle', 'tooltip');
|
button.setAttribute("data-bs-toggle", "tooltip");
|
||||||
button.setAttribute('data-bs-placement', 'top');
|
button.setAttribute("data-bs-placement", "top");
|
||||||
button.title = title;
|
button.title = title;
|
||||||
|
|
||||||
button.addEventListener('click', clickHandler);
|
button.addEventListener("click", clickHandler);
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createChildrenContainer(bookmark, level) {
|
function createChildrenContainer(bookmark, level) {
|
||||||
const childrenContainer = document.createElement('div');
|
const childrenContainer = document.createElement("div");
|
||||||
childrenContainer.className = 'bookmark-children';
|
childrenContainer.className = "bookmark-children";
|
||||||
|
|
||||||
bookmark.children.forEach(child => {
|
bookmark.children.forEach((child) => {
|
||||||
childrenContainer.appendChild(createBookmarkElement(child, level + 1));
|
childrenContainer.appendChild(createBookmarkElement(child, level + 1));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -568,24 +600,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Update the add bookmark button appearance with clear visual cue
|
// Update the add bookmark button appearance with clear visual cue
|
||||||
addBookmarkBtn.innerHTML = '<span class="material-symbols-rounded">add</span> Add Top-level Bookmark';
|
addBookmarkBtn.innerHTML = '<span class="material-symbols-rounded">add</span> Add Top-level Bookmark';
|
||||||
addBookmarkBtn.className = 'btn btn-primary btn-add-bookmark top-level';
|
addBookmarkBtn.className = "btn btn-primary btn-add-bookmark top-level";
|
||||||
|
|
||||||
// Use Bootstrap tooltips
|
// Use Bootstrap tooltips
|
||||||
addBookmarkBtn.setAttribute('data-bs-toggle', 'tooltip');
|
addBookmarkBtn.setAttribute("data-bs-toggle", "tooltip");
|
||||||
addBookmarkBtn.setAttribute('data-bs-placement', 'top');
|
addBookmarkBtn.setAttribute("data-bs-placement", "top");
|
||||||
addBookmarkBtn.title = 'Add a new top-level bookmark';
|
addBookmarkBtn.title = "Add a new top-level bookmark";
|
||||||
|
|
||||||
// Add icon to empty state button as well
|
// Add icon to empty state button as well
|
||||||
const updateEmptyStateButton = function () {
|
const updateEmptyStateButton = function () {
|
||||||
const emptyStateBtn = document.querySelector('.btn-add-first-bookmark');
|
const emptyStateBtn = document.querySelector(".btn-add-first-bookmark");
|
||||||
if (emptyStateBtn) {
|
if (emptyStateBtn) {
|
||||||
emptyStateBtn.innerHTML = '<span class="material-symbols-rounded">add</span> Add First Bookmark';
|
emptyStateBtn.innerHTML = '<span class="material-symbols-rounded">add</span> Add First Bookmark';
|
||||||
emptyStateBtn.setAttribute('data-bs-toggle', 'tooltip');
|
emptyStateBtn.setAttribute("data-bs-toggle", "tooltip");
|
||||||
emptyStateBtn.setAttribute('data-bs-placement', 'top');
|
emptyStateBtn.setAttribute("data-bs-placement", "top");
|
||||||
emptyStateBtn.title = 'Add first bookmark';
|
emptyStateBtn.title = "Add first bookmark";
|
||||||
|
|
||||||
// Initialize tooltips for the empty state button
|
// Initialize tooltips for the empty state button
|
||||||
if (typeof $ !== 'undefined') {
|
if (typeof $ !== "undefined") {
|
||||||
$('[data-bs-toggle="tooltip"]').tooltip();
|
$('[data-bs-toggle="tooltip"]').tooltip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -597,14 +629,147 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
updateEmptyStateButton();
|
updateEmptyStateButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add bookmarks Import/Export functionality
|
||||||
|
|
||||||
|
// Import/Export button references
|
||||||
|
const importDefaultBtn = document.getElementById("importDefaultBtn");
|
||||||
|
const exportDefaultBtn = document.getElementById("exportDefaultBtn");
|
||||||
|
const importUploadJsonFileInput = document.getElementById("importUploadJsonFileInput");
|
||||||
|
const importPasteFromClipboardBtn = document.getElementById("importPasteFromClipboardBtn");
|
||||||
|
const exportDownloadJsonFileBtn = document.getElementById("exportDownloadJsonFileBtn");
|
||||||
|
const exportCopyToClipboardBtn = document.getElementById("exportCopyToClipboardBtn");
|
||||||
|
|
||||||
|
// display import/export from/to clipboard buttons if supported
|
||||||
|
if (navigator.clipboard && navigator.clipboard.readText) {
|
||||||
|
importPasteFromClipboardBtn.parentElement.classList.remove("d-none");
|
||||||
|
}
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
exportCopyToClipboardBtn.parentElement.classList.remove("d-none");
|
||||||
|
}
|
||||||
|
|
||||||
|
function flashButtonSuccess(button) {
|
||||||
|
const originalClass = button.className;
|
||||||
|
|
||||||
|
button.classList.remove("btn-outline-primary");
|
||||||
|
button.classList.add("btn-success", "success-flash");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.className = originalClass;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import handlers
|
||||||
|
async function handleJsonFileInputChange(event) {
|
||||||
|
try {
|
||||||
|
await loadBookmarks(async function () {
|
||||||
|
return getBookmarkDataFromJson(event);
|
||||||
|
});
|
||||||
|
flashButtonSuccess(importDefaultBtn);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to import bookmarks from JSON file: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importBookmarksFromClipboard() {
|
||||||
|
console.log("Importing bookmarks from clipboard...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await loadBookmarks(async function () {
|
||||||
|
const clipboardText = await navigator.clipboard.readText();
|
||||||
|
if (!clipboardText) return [];
|
||||||
|
|
||||||
|
return JSON.parse(clipboardText);
|
||||||
|
});
|
||||||
|
flashButtonSuccess(importDefaultBtn);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to import bookmarks from clipboard: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleBookmarksPasteFromClipboard(event) {
|
||||||
|
// do not override normal paste behavior on input fields
|
||||||
|
if (event.target.tagName.toLowerCase() === "input") return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await loadBookmarks(async function () {
|
||||||
|
const clipboardText = event.clipboardData?.getData("text/plain");
|
||||||
|
if (!clipboardText) return [];
|
||||||
|
|
||||||
|
return JSON.parse(clipboardText);
|
||||||
|
});
|
||||||
|
flashButtonSuccess(importDefaultBtn);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to import bookmarks from clipboard (ctrl-v): ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export handlers
|
||||||
|
async function exportBookmarksToJson() {
|
||||||
|
console.log("Exporting bookmarks to JSON...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const bookmarkData = bookmarkDataInput.value;
|
||||||
|
const blob = new Blob([bookmarkData], { type: "application/json" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = "bookmarks.json";
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
flashButtonSuccess(exportDefaultBtn);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to export bookmarks to JSON: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportBookmarksToClipboard() {
|
||||||
|
const bookmarkData = bookmarkDataInput.value;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(bookmarkData);
|
||||||
|
flashButtonSuccess(exportDefaultBtn);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to export bookmarks to clipboard: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleBookmarksCopyToClipboard(event) {
|
||||||
|
// do not override normal copy behavior on input fields
|
||||||
|
if (event.target.tagName.toLowerCase() === "input") return;
|
||||||
|
|
||||||
|
const bookmarkData = bookmarkDataInput.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
event.clipboardData.setData("text/plain", bookmarkData);
|
||||||
|
event.preventDefault();
|
||||||
|
flashButtonSuccess(exportDefaultBtn);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to export bookmarks to clipboard (ctrl-c): ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register event listeners for import/export functions
|
||||||
|
importUploadJsonFileInput.addEventListener("change", handleJsonFileInputChange);
|
||||||
|
importPasteFromClipboardBtn.addEventListener("click", importBookmarksFromClipboard);
|
||||||
|
exportDownloadJsonFileBtn.addEventListener("click", exportBookmarksToJson);
|
||||||
|
exportCopyToClipboardBtn.addEventListener("click", exportBookmarksToClipboard);
|
||||||
|
document.body.addEventListener("copy", handleBookmarksCopyToClipboard);
|
||||||
|
document.body.addEventListener("paste", handleBookmarksPasteFromClipboard);
|
||||||
|
// set default actions
|
||||||
|
// importDefaultBtn is already handled by being a label for the file input
|
||||||
|
exportDefaultBtn.addEventListener("click", exportBookmarksToJson);
|
||||||
|
|
||||||
// Listen for theme changes to update badge colors
|
// Listen for theme changes to update badge colors
|
||||||
const observer = new MutationObserver(function (mutations) {
|
const observer = new MutationObserver(function (mutations) {
|
||||||
mutations.forEach(function (mutation) {
|
mutations.forEach(function (mutation) {
|
||||||
if (mutation.attributeName === 'data-bs-theme') {
|
if (mutation.attributeName === "data-bs-theme") {
|
||||||
const isDarkMode = document.documentElement.getAttribute('data-bs-theme') === 'dark';
|
const isDarkMode = document.documentElement.getAttribute("data-bs-theme") === "dark";
|
||||||
document.querySelectorAll('.badge').forEach(badge => {
|
document.querySelectorAll(".badge").forEach((badge) => {
|
||||||
badge.classList.remove('bg-secondary', 'bg-info');
|
badge.classList.remove("bg-secondary", "bg-info");
|
||||||
badge.classList.add(isDarkMode ? 'bg-info' : 'bg-secondary');
|
badge.classList.add(isDarkMode ? "bg-info" : "bg-secondary");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -613,26 +778,26 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
observer.observe(document.documentElement, { attributes: true });
|
observer.observe(document.documentElement, { attributes: true });
|
||||||
|
|
||||||
// Add visual enhancement to clearly show the top-level/child relationship
|
// Add visual enhancement to clearly show the top-level/child relationship
|
||||||
document.addEventListener('mouseover', function(e) {
|
document.addEventListener("mouseover", function (e) {
|
||||||
// When hovering over add buttons, highlight their relationship targets
|
// When hovering over add buttons, highlight their relationship targets
|
||||||
const button = e.target.closest('.btn-add-child, .btn-add-sibling');
|
const button = e.target.closest(".btn-add-child, .btn-add-sibling");
|
||||||
if (button) {
|
if (button) {
|
||||||
if (button.classList.contains('btn-add-child')) {
|
if (button.classList.contains("btn-add-child")) {
|
||||||
// Highlight parent-child relationship
|
// Highlight parent-child relationship
|
||||||
const bookmarkItem = button.closest('.bookmark-item');
|
const bookmarkItem = button.closest(".bookmark-item");
|
||||||
if (bookmarkItem) {
|
if (bookmarkItem) {
|
||||||
bookmarkItem.style.boxShadow = '0 0 0 2px var(--btn-add-child-border, #198754)';
|
bookmarkItem.style.boxShadow = "0 0 0 2px var(--btn-add-child-border, #198754)";
|
||||||
}
|
}
|
||||||
} else if (button.classList.contains('btn-add-sibling')) {
|
} else if (button.classList.contains("btn-add-sibling")) {
|
||||||
// Highlight sibling relationship
|
// Highlight sibling relationship
|
||||||
const bookmarkItem = button.closest('.bookmark-item');
|
const bookmarkItem = button.closest(".bookmark-item");
|
||||||
if (bookmarkItem) {
|
if (bookmarkItem) {
|
||||||
// Find siblings
|
// Find siblings
|
||||||
const parent = bookmarkItem.parentElement;
|
const parent = bookmarkItem.parentElement;
|
||||||
const siblings = parent.querySelectorAll(':scope > .bookmark-item');
|
const siblings = parent.querySelectorAll(":scope > .bookmark-item");
|
||||||
siblings.forEach(sibling => {
|
siblings.forEach((sibling) => {
|
||||||
if (sibling !== bookmarkItem) {
|
if (sibling !== bookmarkItem) {
|
||||||
sibling.style.boxShadow = '0 0 0 2px var(--btn-add-sibling-border, #0d6efd)';
|
sibling.style.boxShadow = "0 0 0 2px var(--btn-add-sibling-border, #0d6efd)";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -640,13 +805,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('mouseout', function(e) {
|
document.addEventListener("mouseout", function (e) {
|
||||||
// Remove highlights when not hovering
|
// Remove highlights when not hovering
|
||||||
const button = e.target.closest('.btn-add-child, .btn-add-sibling');
|
const button = e.target.closest(".btn-add-child, .btn-add-sibling");
|
||||||
if (button) {
|
if (button) {
|
||||||
// Remove all highlights
|
// Remove all highlights
|
||||||
document.querySelectorAll('.bookmark-item').forEach(item => {
|
document.querySelectorAll(".bookmark-item").forEach((item) => {
|
||||||
item.style.boxShadow = '';
|
item.style.boxShadow = "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<form id="imageToPDFForm" method="post" enctype="multipart/form-data"
|
<form id="imageToPDFForm" method="post" enctype="multipart/form-data"
|
||||||
th:action="@{'/api/v1/convert/img/pdf'}">
|
th:action="@{'/api/v1/convert/img/pdf'}">
|
||||||
<div
|
<div
|
||||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='image/*', inputText=#{imgPrompt})}">
|
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='image/*,.psd', inputText=#{imgPrompt})}">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="fitOption" th:text="#{imageToPDF.selectLabel}">Fit Options</label>
|
<label for="fitOption" th:text="#{imageToPDF.selectLabel}">Fit Options</label>
|
||||||
|
@ -4,6 +4,21 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{pdfToImage.title}, header=#{pdfToImage.header})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{pdfToImage.title}, header=#{pdfToImage.header})}"></th:block>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const maxDPI = /*[[${maxDPI}]]*/ 500; // Maximum DPI for PDF to image conversion
|
||||||
|
const maxDPILabelRaw = /*[[#{pdfToImage.dpi}]]*/ "DPI (The server limit is {0} dpi)";
|
||||||
|
const maxDPILabel = maxDPILabelRaw.replace("{0}", maxDPI); // Replace with actual value from properties
|
||||||
|
const dpilabel = document.querySelector('label[for="dpi"]');
|
||||||
|
if (dpilabel) {
|
||||||
|
dpilabel.textContent = maxDPILabel;
|
||||||
|
}
|
||||||
|
const maxDPIInput = document.getElementById("dpi");
|
||||||
|
if (maxDPIInput) {
|
||||||
|
maxDPIInput.setAttribute("max", maxDPI); // Set the maximum value for DPI input
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
|
<html th:lang="${#locale.language}"
|
||||||
|
th:dir="#{language.direction}"
|
||||||
|
th:data-language="${#locale.toString()}"
|
||||||
xmlns:th="https://www.thymeleaf.org">
|
xmlns:th="https://www.thymeleaf.org">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{editTableOfContents.title}, header=#{editTableOfContents.header})}">
|
<th:block
|
||||||
|
th:insert="~{fragments/common :: head(title=#{editTableOfContents.title}, header=#{editTableOfContents.header})}">
|
||||||
</th:block>
|
</th:block>
|
||||||
<link rel="stylesheet" th:href="@{'/css/edit-table-of-contents.css'}">
|
<link rel="stylesheet"
|
||||||
|
th:href="@{'/css/edit-table-of-contents.css'}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -18,49 +22,139 @@
|
|||||||
<div class="col-md-8 bg-card">
|
<div class="col-md-8 bg-card">
|
||||||
<div class="tool-header">
|
<div class="tool-header">
|
||||||
<span class="material-symbols-rounded tool-header-icon edit">bookmark_add</span>
|
<span class="material-symbols-rounded tool-header-icon edit">bookmark_add</span>
|
||||||
<span class="tool-header-text" th:text="#{editTableOfContents.header}"></span>
|
<span class="tool-header-text"
|
||||||
|
th:text="#{editTableOfContents.header}"></span>
|
||||||
</div>
|
</div>
|
||||||
<form th:action="@{'/api/v1/general/edit-table-of-contents'}" method="post" enctype="multipart/form-data" id="editTocForm">
|
<form th:action="@{'/api/v1/general/edit-table-of-contents'}"
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
id="editTocForm">
|
||||||
<div
|
<div
|
||||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
|
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 form-check">
|
<div class="mb-3 form-check">
|
||||||
<input type="checkbox" class="form-check-input" id="replaceExisting" name="replaceExisting" checked>
|
<input type="checkbox"
|
||||||
<label class="form-check-label" for="replaceExisting"
|
class="form-check-input"
|
||||||
|
id="replaceExisting"
|
||||||
|
name="replaceExisting"
|
||||||
|
checked>
|
||||||
|
<label class="form-check-label"
|
||||||
|
for="replaceExisting"
|
||||||
th:text="#{editTableOfContents.replaceExisting}"></label>
|
th:text="#{editTableOfContents.replaceExisting}"></label>
|
||||||
<input type="hidden" name="replaceExisting" value="false" />
|
<input type="hidden"
|
||||||
|
name="replaceExisting"
|
||||||
|
value="false" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bookmark-editor">
|
<div class="bookmark-editor">
|
||||||
<h5 th:text="#{editTableOfContents.editorTitle}"></h5>
|
<h5 th:text="#{editTableOfContents.editorTitle}"></h5>
|
||||||
<p th:text="#{editTableOfContents.editorDesc}"></p>
|
<p th:text="#{editTableOfContents.editorDesc}"></p>
|
||||||
|
|
||||||
|
<div id="error-message-container">
|
||||||
|
<!-- Error messages will be added here dynamically -->
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="bookmarks-container">
|
<div id="bookmarks-container">
|
||||||
<!-- Bookmarks will be added here dynamically -->
|
<!-- Bookmarks will be added here dynamically -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bookmark-actions">
|
<div class="bookmark-actions">
|
||||||
<button type="button" id="addBookmarkBtn" class="btn btn-outline-primary" th:text="#{editTableOfContents.addBookmark}"></button>
|
<button type="button"
|
||||||
|
id="addBookmarkBtn"
|
||||||
|
class="btn btn-outline-primary"
|
||||||
|
th:text="#{editTableOfContents.addBookmark}"></button>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap justify-content-end gap-2">
|
||||||
|
<!-- Import Split Button -->
|
||||||
|
<div class="btn-group">
|
||||||
|
<label
|
||||||
|
id="importDefaultBtn"
|
||||||
|
for="importUploadJsonFileInput"
|
||||||
|
class="btn btn-outline-primary"
|
||||||
|
style="border-top-left-radius: 1.25rem !important; border-bottom-left-radius: 1.25rem !important;"
|
||||||
|
th:text="#{editTableOfContents.importBookmarksDefault}">
|
||||||
|
</label>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-primary dropdown-toggle dropdown-toggle-split"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">
|
||||||
|
<span class="visually-hidden">Toggle Import Options</span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><label class="dropdown-item"
|
||||||
|
id="importUploadJsonFileBtn"
|
||||||
|
for="importUploadJsonFileInput"
|
||||||
|
style="cursor: pointer;"
|
||||||
|
th:text="#{editTableOfContents.importBookmarksFromJsonFile}"></label></li>
|
||||||
|
<li class="d-none"><a class="dropdown-item"
|
||||||
|
href="#bookmarks-container"
|
||||||
|
id="importPasteFromClipboardBtn"
|
||||||
|
th:text="#{editTableOfContents.importBookmarksFromClipboard}"></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Export Split Button -->
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button"
|
||||||
|
id="exportDefaultBtn"
|
||||||
|
class="btn btn-outline-primary"
|
||||||
|
th:text="#{editTableOfContents.exportBookmarksDefault}"></button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-primary dropdown-toggle dropdown-toggle-split"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">
|
||||||
|
<span class="visually-hidden">Toggle Export Options</span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item"
|
||||||
|
href="#bookmarks-container"
|
||||||
|
id="exportDownloadJsonFileBtn"
|
||||||
|
th:text="#{editTableOfContents.exportBookmarksAsJson}"></a></li>
|
||||||
|
<li class="d-none"><a class="dropdown-item"
|
||||||
|
href="#bookmarks-container"
|
||||||
|
id="exportCopyToClipboardBtn"
|
||||||
|
th:text="#{editTableOfContents.exportBookmarksAsText}"></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Hidden field to store JSON data -->
|
<!-- Hidden field to store JSON data -->
|
||||||
<input type="hidden" id="bookmarkData" name="bookmarkData" value="[]">
|
<input type="hidden"
|
||||||
|
id="bookmarkData"
|
||||||
|
name="bookmarkData"
|
||||||
|
value="[]">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-outline-primary" data-bs-toggle="collapse" href="#info" role="button"
|
<a class="btn btn-outline-primary"
|
||||||
aria-expanded="false" aria-controls="info" th:text="#{info}"></a>
|
data-bs-toggle="collapse"
|
||||||
|
href="#info"
|
||||||
|
role="button"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="info"
|
||||||
|
th:text="#{info}"></a>
|
||||||
</p>
|
</p>
|
||||||
<div class="collapse" id="info">
|
<div class="collapse"
|
||||||
|
id="info">
|
||||||
<p th:text="#{editTableOfContents.desc.1}"></p>
|
<p th:text="#{editTableOfContents.desc.1}"></p>
|
||||||
<p th:text="#{editTableOfContents.desc.2}"></p>
|
<p th:text="#{editTableOfContents.desc.2}"></p>
|
||||||
<p th:text="#{editTableOfContents.desc.3}"></p>
|
<p th:text="#{editTableOfContents.desc.3}"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{editTableOfContents.submit}"></button>
|
<button type="submit"
|
||||||
|
id="submitBtn"
|
||||||
|
class="btn btn-primary"
|
||||||
|
th:text="#{editTableOfContents.submit}"></button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- Hidden file input for JSON import (outside of form)-->
|
||||||
|
<input type="file"
|
||||||
|
id="importUploadJsonFileInput"
|
||||||
|
accept="application/json"
|
||||||
|
style="display: none;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,9 +11,44 @@
|
|||||||
</script>
|
</script>
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
const currentVersion = /*[[${@appVersion}]]*/ '';
|
const currentVersion = /*[[${@appVersion}]]*/ '';
|
||||||
|
const licenseType = /*[[${@license}]]*/ '';
|
||||||
|
const machineType = /*[[${@machineType}]]*/ '';
|
||||||
|
const activeSecurity = /*[[${@activeSecurity}]]*/ false;
|
||||||
const noFavourites = /*[[#{noFavourites}]]*/ '';
|
const noFavourites = /*[[#{noFavourites}]]*/ '';
|
||||||
console.log(noFavourites);
|
console.log(noFavourites);
|
||||||
const updateAvailable = /*[[#{settings.updateAvailable}]]*/ '';
|
const updateAvailable = /*[[#{settings.updateAvailable}]]*/ '';
|
||||||
|
|
||||||
|
// Update notification i18n constants
|
||||||
|
const urgentUpdateAvailable = /*[[#{update.urgentUpdateAvailable}]]*/ '🚨 Update Available';
|
||||||
|
const updateAvailableText = /*[[#{update.updateAvailable}]]*/ 'Update Available';
|
||||||
|
const updateModalTitle = /*[[#{update.modalTitle}]]*/ 'Update Available';
|
||||||
|
const updateCurrent = /*[[#{update.current}]]*/ 'Current';
|
||||||
|
const updateLatest = /*[[#{update.latest}]]*/ 'Latest';
|
||||||
|
const updateLatestStable = /*[[#{update.latestStable}]]*/ 'Latest Stable';
|
||||||
|
const updatePriority = /*[[#{update.priority}]]*/ 'Priority';
|
||||||
|
const updateRecommendedAction = /*[[#{update.recommendedAction}]]*/ 'Recommended Action';
|
||||||
|
const updateBreakingChangesDetected = /*[[#{update.breakingChangesDetected}]]*/ '⚠️ Breaking Changes Detected';
|
||||||
|
const updateBreakingChangesMessage = /*[[#{update.breakingChangesMessage}]]*/ 'This update contains breaking changes. Please review the migration guides below.';
|
||||||
|
const updateMigrationGuides = /*[[#{update.migrationGuides}]]*/ 'Migration Guides:';
|
||||||
|
const updateViewGuide = /*[[#{update.viewGuide}]]*/ 'View Guide';
|
||||||
|
const updateLoadingDetailedInfo = /*[[#{update.loadingDetailedInfo}]]*/ 'Loading detailed version information...';
|
||||||
|
const updateClose = /*[[#{update.close}]]*/ 'Close';
|
||||||
|
const updateViewAllReleases = /*[[#{update.viewAllReleases}]]*/ 'View All Releases';
|
||||||
|
const updateDownloadLatest = /*[[#{update.downloadLatest}]]*/ 'Download Latest';
|
||||||
|
const updateAvailableUpdates = /*[[#{update.availableUpdates}]]*/ 'Available Updates:';
|
||||||
|
const updateUnableToLoadDetails = /*[[#{update.unableToLoadDetails}]]*/ 'Unable to load detailed version information.';
|
||||||
|
const updateVersion = /*[[#{update.version}]]*/ 'Version';
|
||||||
|
|
||||||
|
// Update priority levels
|
||||||
|
const updatePriorityUrgent = /*[[#{update.priority.urgent}]]*/ 'URGENT';
|
||||||
|
const updatePriorityNormal = /*[[#{update.priority.normal}]]*/ 'NORMAL';
|
||||||
|
const updatePriorityMinor = /*[[#{update.priority.minor}]]*/ 'MINOR';
|
||||||
|
const updatePriorityLow = /*[[#{update.priority.low}]]*/ 'LOW';
|
||||||
|
|
||||||
|
// Breaking changes text
|
||||||
|
const updateBreakingChanges = /*[[#{update.breakingChanges}]]*/ 'Breaking Changes:';
|
||||||
|
const updateBreakingChangesDefault = /*[[#{update.breakingChangesDefault}]]*/ 'This version contains breaking changes';
|
||||||
|
const updateMigrationGuide = /*[[#{update.migrationGuide}]]*/ 'Migration Guide';
|
||||||
</script>
|
</script>
|
||||||
<script th:src="@{'/js/homecard.js'}"></script>
|
<script th:src="@{'/js/homecard.js'}"></script>
|
||||||
<script th:src="@{'/js/githubVersion.js'}"></script>
|
<script th:src="@{'/js/githubVersion.js'}"></script>
|
||||||
|
@ -413,9 +413,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
|
|
||||||
/*<![CDATA[*/
|
/*<![CDATA[*/
|
||||||
|
@ -208,7 +208,6 @@
|
|||||||
color: gold;
|
color: gold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
/*<![CDATA[*/
|
/*<![CDATA[*/
|
||||||
window.analyticsPromptBoolean = /*[[${@analyticsPrompt}]]*/ false;
|
window.analyticsPromptBoolean = /*[[${@analyticsPrompt}]]*/ false;
|
||||||
|
@ -98,7 +98,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||||
</div>
|
</div>
|
||||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
// Show/hide advanced settings
|
// Show/hide advanced settings
|
||||||
document.getElementById('advancedSettingsToggle').addEventListener('change', function() {
|
document.getElementById('advancedSettingsToggle').addEventListener('change', function() {
|
||||||
|
@ -39,7 +39,6 @@
|
|||||||
<!-- Button to download the JSON -->
|
<!-- Button to download the JSON -->
|
||||||
<a href="#" id="downloadJS" class="btn btn-primary mt-3" style="display: none;" th:text="#{showJS.downloadJS}">Download JSON</a>
|
<a href="#" id="downloadJS" class="btn btn-primary mt-3" style="display: none;" th:text="#{showJS.downloadJS}">Download JSON</a>
|
||||||
</div>
|
</div>
|
||||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
document.querySelector('#pdfInfoForm').addEventListener('submit', function(event){
|
document.querySelector('#pdfInfoForm').addEventListener('submit', function(event){
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -192,7 +192,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
|
||||||
<script th:src="@{'/js/pipeline.js'}"></script>
|
<script th:src="@{'/js/pipeline.js'}"></script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,7 +106,6 @@
|
|||||||
<!-- Button to download the JSON -->
|
<!-- Button to download the JSON -->
|
||||||
<a href="#" id="downloadJson" class="btn btn-primary mt-3" style="display: none;" th:text="#{getPdfInfo.downloadJson}">Download JSON</a>
|
<a href="#" id="downloadJson" class="btn btn-primary mt-3" style="display: none;" th:text="#{getPdfInfo.downloadJson}">Download JSON</a>
|
||||||
</div>
|
</div>
|
||||||
<script th:src="@{'/js/fetch-utils.js'}"></script>
|
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
// Pre-load message translations
|
// Pre-load message translations
|
||||||
const getPdfInfoSummary = /*[[#{getPdfInfo.summary}]]*/ "PDF Summary";
|
const getPdfInfoSummary = /*[[#{getPdfInfo.summary}]]*/ "PDF Summary";
|
||||||
|
62
build.gradle
62
build.gradle
@ -30,6 +30,7 @@ ext {
|
|||||||
openSamlVersion = "4.3.2"
|
openSamlVersion = "4.3.2"
|
||||||
commonmarkVersion = "0.25.1"
|
commonmarkVersion = "0.25.1"
|
||||||
googleJavaFormatVersion = "1.28.0"
|
googleJavaFormatVersion = "1.28.0"
|
||||||
|
junitPlatformVersion = "1.12.2"
|
||||||
tempJrePath = null
|
tempJrePath = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ repositories {
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
version = '1.1.1'
|
version = '1.2.0'
|
||||||
|
|
||||||
configurations.configureEach {
|
configurations.configureEach {
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
@ -65,28 +66,11 @@ allprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('writeVersion') {
|
tasks.register('writeVersion', WriteProperties) {
|
||||||
def propsFile = file("$projectDir/app/common/src/main/resources/version.properties")
|
outputFile = layout.projectDirectory.file('app/common/src/main/resources/version.properties')
|
||||||
def propsDir = propsFile.parentFile
|
println "Writing version.properties to ${outputFile.path}"
|
||||||
|
comment = "${new Date()}"
|
||||||
doLast {
|
property 'version', project.provider { project.version.toString() }
|
||||||
if (propsDir.exists()) {
|
|
||||||
if (propsFile.exists()) {
|
|
||||||
println "File exists: $propsFile"
|
|
||||||
} else {
|
|
||||||
println "$propsFile does not exist. Creating file."
|
|
||||||
propsFile.createNewFile()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println "Creating directory: $propsDir"
|
|
||||||
propsDir.mkdirs()
|
|
||||||
propsFile.createNewFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
def props = new Properties()
|
|
||||||
props.setProperty("version", version)
|
|
||||||
props.store(propsFile.newWriter(), null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('createExe') {
|
tasks.named('createExe') {
|
||||||
@ -99,6 +83,7 @@ subprojects {
|
|||||||
apply plugin: 'com.diffplug.spotless'
|
apply plugin: 'com.diffplug.spotless'
|
||||||
apply plugin: 'org.springframework.boot'
|
apply plugin: 'org.springframework.boot'
|
||||||
apply plugin: 'io.spring.dependency-management'
|
apply plugin: 'io.spring.dependency-management'
|
||||||
|
apply plugin: 'jacoco'
|
||||||
|
|
||||||
java {
|
java {
|
||||||
// 17 is lowest but we support and recommend 21
|
// 17 is lowest but we support and recommend 21
|
||||||
@ -142,7 +127,10 @@ subprojects {
|
|||||||
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
|
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2'
|
testRuntimeOnly "org.junit.platform:junit-platform-launcher:$junitPlatformVersion"
|
||||||
|
|
||||||
|
testImplementation platform("com.squareup.okhttp3:okhttp-bom:5.1.0")
|
||||||
|
testImplementation "com.squareup.okhttp3:mockwebserver"
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
tasks.withType(JavaCompile).configureEach {
|
||||||
@ -156,6 +144,27 @@ subprojects {
|
|||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
finalizedBy jacocoTestReport
|
||||||
|
}
|
||||||
|
|
||||||
|
jacocoTestReport {
|
||||||
|
dependsOn test
|
||||||
|
reports {
|
||||||
|
xml.required.set(true)
|
||||||
|
csv.required.set(false)
|
||||||
|
html.required.set(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jacocoTestCoverageVerification {
|
||||||
|
dependsOn jacocoTestReport
|
||||||
|
violationRules {
|
||||||
|
rule {
|
||||||
|
limit {
|
||||||
|
minimum = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named("processResources") {
|
tasks.named("processResources") {
|
||||||
@ -573,7 +582,10 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2'
|
testRuntimeOnly "org.junit.platform:junit-platform-launcher:$junitPlatformVersion"
|
||||||
|
|
||||||
|
testImplementation platform("com.squareup.okhttp3:okhttp-bom:5.1.0")
|
||||||
|
testImplementation "com.squareup.okhttp3:mockwebserver"
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named("test") {
|
tasks.named("test") {
|
||||||
|
@ -295,6 +295,7 @@ Stirling-PDF can be customized through environment variables or a `settings.yml`
|
|||||||
- Security settings
|
- Security settings
|
||||||
- UI customization
|
- UI customization
|
||||||
- Endpoint management
|
- Endpoint management
|
||||||
|
- Maximum DPI for PDF to image conversion (`system.maxDPI`)
|
||||||
|
|
||||||
When using Docker, pass environment variables using the `-e` flag or in your `docker-compose.yml` file.
|
When using Docker, pass environment variables using the `-e` flag or in your `docker-compose.yml` file.
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
|
||||||
# Enables build caching to reuse outputs from previous builds for faster execution
|
# Enables build caching to reuse outputs from previous builds for faster execution
|
||||||
# org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
|
||||||
org.gradle.build-scan=true
|
org.gradle.build-scan=true
|
||||||
|
# org.gradle.configuration-cache=true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user