diff --git a/.github/workflows/PR-Demo-Comment.yml b/.github/workflows/PR-Demo-Comment.yml
index 0ad568899..524c69ea6 100644
--- a/.github/workflows/PR-Demo-Comment.yml
+++ b/.github/workflows/PR-Demo-Comment.yml
@@ -4,9 +4,15 @@ on:
issue_comment:
types: [created]
+permissions:
+ contents: read
+
jobs:
check-comment:
runs-on: ubuntu-latest
+ permissions:
+ pull-requests: read
+ issues: read
if: |
github.event.issue.pull_request &&
(
@@ -68,6 +74,9 @@ jobs:
deploy-pr:
needs: check-comment
runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ issues: write
steps:
- name: Harden Runner
@@ -98,7 +107,9 @@ jobs:
- name: Get version number
id: versionNumber
- run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
+ run: |
+ VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
+ echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
diff --git a/.github/workflows/PR-Demo-cleanup.yml b/.github/workflows/PR-Demo-cleanup.yml
index 6ed7bea88..57b6750fb 100644
--- a/.github/workflows/PR-Demo-cleanup.yml
+++ b/.github/workflows/PR-Demo-cleanup.yml
@@ -4,7 +4,8 @@ on:
pull_request:
types: [opened, synchronize, reopened, closed]
-permissions: read-all
+permissions:
+ contents: read
env:
SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets
diff --git a/.github/workflows/auto-labeler.yml b/.github/workflows/auto-labeler.yml
index 3495f7ae0..501d9863e 100644
--- a/.github/workflows/auto-labeler.yml
+++ b/.github/workflows/auto-labeler.yml
@@ -3,7 +3,8 @@ on:
pull_request_target:
types: [opened, synchronize]
-permissions: read-all
+permissions:
+ contents: read
jobs:
labeler:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b5a0784bb..c7e987251 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,13 +6,15 @@ on:
pull_request:
branches: ["main"]
-permissions: read-all
+permissions:
+ contents: read
jobs:
build:
runs-on: ubuntu-latest
permissions:
+ actions: read
security-events: write
strategy:
@@ -44,7 +46,7 @@ jobs:
run: ./gradlew clean build
env:
DOCKER_ENABLE_SECURITY: true
-
+
docker-compose-tests:
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
# (github.event_name == 'pull_request' &&
diff --git a/.github/workflows/check_properties.yml b/.github/workflows/check_properties.yml
index 5fc4dce95..b45da71fb 100644
--- a/.github/workflows/check_properties.yml
+++ b/.github/workflows/check_properties.yml
@@ -8,12 +8,14 @@ on:
permissions:
contents: read # Allow read access to repository content
- issues: write # Allow posting comments on issues/PRs
jobs:
check-files:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
+ permissions:
+ issues: write # Allow posting comments on issues/PRs
+ pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
@@ -28,61 +30,141 @@ jobs:
with:
python-version: "3.x"
+ - name: Get PR data
+ id: get-pr-data
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ with:
+ script: |
+ const prNumber = context.payload.pull_request.number;
+ const repoOwner = context.payload.repository.owner.login;
+ const repoName = context.payload.repository.name;
+ const branch = context.payload.pull_request.head.ref;
+
+ console.log(`PR Number: ${prNumber}`);
+ console.log(`Repo Owner: ${repoOwner}`);
+ console.log(`Repo Name: ${repoName}`);
+ console.log(`Branch: ${branch}`);
+
+ core.setOutput("pr_number", prNumber);
+ core.setOutput("repo_owner", repoOwner);
+ core.setOutput("repo_name", repoName);
+ core.setOutput("branch", branch);
+ continue-on-error: true
+
- name: Fetch PR changed files
id: fetch-pr-changes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Fetching PR changed files..."
-
- gh repo set-default ${{ github.event.pull_request.head.repo.full_name }} # Set the fork repository as default
-
- # Fetch the list of changed files in the PR
echo "Getting list of changed files from PR..."
- gh pr view ${{ github.event.pull_request.number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]+\.properties$' > changed_files.txt # Filter only matching property files
+ gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]+\.properties$' > changed_files.txt # Filter only matching property files
- - name: Determine reference file
+ - name: Determine reference file test
id: determine-file
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- echo "Determining reference file..."
- REPO_OWNER=$(gh pr view ${{ github.event.pull_request.number }} --json author -q '.author.login') # Get PR author's username
- REPO_NAME=$(gh pr view ${{ github.event.pull_request.number }} --json headRepository -q '.headRepository.name') # Get PR repository name
- BRANCH=$(gh pr view ${{ github.event.pull_request.number }} --json headRefName -q '.headRefName') # Get PR branch name
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ with:
+ script: |
+ const fs = require("fs");
+ const path = require("path");
- mkdir -p pr-branch # Create a directory for PR files
+ const prNumber = ${{ steps.get-pr-data.outputs.pr_number }};
+ const repoOwner = "${{ steps.get-pr-data.outputs.repo_owner }}";
+ const repoName = "${{ steps.get-pr-data.outputs.repo_name }}";
- # Download the content of each changed file
- while IFS= read -r file; do
- mkdir -p "pr-branch/$(dirname "$file")" # Create directories for files
- gh api repos/$REPO_OWNER/$REPO_NAME/contents/$file?ref=$BRANCH --jq '.content' | base64 -d > "pr-branch/src/main/resources/$(basename "$file")" # Save decoded file content
- done < changed_files.txt
+ const prRepoOwner = "${{ github.event.pull_request.head.repo.owner.login }}";
+ const prRepoName = "${{ github.event.pull_request.head.repo.name }}";
+ const branch = "${{ steps.get-pr-data.outputs.branch }}";
- # Generate a list of files without the "pr-branch/" prefix
- find pr-branch/ -type f | awk -F'pr-branch/' '{print $2}' > file_list.txt
+ console.log(`Determining reference file for PR #${prNumber}`);
- mapfile -t FILES_LIST < file_list.txt # Read the file list into an array
- FILES_LIST_STR="${FILES_LIST[*]}" # Join array into a space-separated string
- echo "FILES_LIST=${FILES_LIST_STR}" >> $GITHUB_ENV # Export the file list to the environment
- echo "Changed files: ${FILES_LIST_STR}"
+ // Validate inputs
+ const validateInput = (input, regex, name) => {
+ if (!regex.test(input)) {
+ throw new Error(`Invalid ${name}: ${input}`);
+ }
+ };
- cat file_list.txt # Display the file list
+ validateInput(repoOwner, /^[a-zA-Z0-9_-]+$/, "repository owner");
+ validateInput(repoName, /^[a-zA-Z0-9._-]+$/, "repository name");
+ validateInput(branch, /^[a-zA-Z0-9._/-]+$/, "branch name");
- # Determine which reference file to use
- if grep -Fxq "src/main/resources/messages_en_GB.properties" changed_files.txt; then
- echo "Using PR branch reference file"
- REFERENCE_FILE="pr-branch-messages_en_GB.properties"
- gh api repos/$REPO_OWNER/$REPO_NAME/contents/src/main/resources/messages_en_GB.properties?ref=${{ github.event.pull_request.head.ref }} \
- --jq '.content' | base64 -d > $REFERENCE_FILE # Save PR branch reference file
- else
- echo "Using main branch reference file"
- REFERENCE_FILE="main-branch-messages_en_GB.properties"
- gh api repos/Ludy87/test_java/contents/src/main/resources/messages_en_GB.properties?ref=main \
- --jq '.content' | base64 -d > $REFERENCE_FILE # Save main branch reference file
- fi
+ // Get the list of changed files in the PR
+ const { data: files } = await github.rest.pulls.listFiles({
+ owner: repoOwner,
+ repo: repoName,
+ pull_number: prNumber,
+ });
- echo "REFERENCE_FILE=$REFERENCE_FILE" >> $GITHUB_ENV # Export reference file path to the environment
+ // Filter for relevant files based on the PR changes
+ const changedFiles = files
+ .map(file => file.filename)
+ .filter(file => /^src\/main\/resources\/messages_[a-zA-Z_]+\.properties$/.test(file));
+
+ console.log("Changed files:", changedFiles);
+
+ // Create a temporary directory for PR files
+ const tempDir = "pr-branch";
+ if (!fs.existsSync(tempDir)) {
+ fs.mkdirSync(tempDir, { recursive: true });
+ }
+
+ // Download and save each changed file
+ for (const file of changedFiles) {
+ const { data: fileContent } = await github.rest.repos.getContent({
+ owner: prRepoOwner,
+ repo: prRepoName,
+ path: file,
+ ref: branch,
+ });
+
+ const content = Buffer.from(fileContent.content, "base64").toString("utf-8");
+ const filePath = path.join(tempDir, file);
+ const dirPath = path.dirname(filePath);
+
+ if (!fs.existsSync(dirPath)) {
+ fs.mkdirSync(dirPath, { recursive: true });
+ }
+
+ fs.writeFileSync(filePath, content);
+ console.log(`Saved file: ${filePath}`);
+ }
+
+ // Output the list of changed files for further processing
+ const fileList = changedFiles.join(" ");
+ core.exportVariable("FILES_LIST", fileList);
+ console.log("Files saved and listed in FILES_LIST.");
+
+ // Determine reference file
+ let referenceFilePath;
+ if (changedFiles.includes("src/main/resources/messages_en_GB.properties")) {
+ console.log("Using PR branch reference file.");
+ const { data: fileContent } = await github.rest.repos.getContent({
+ owner: prRepoOwner,
+ repo: prRepoName,
+ path: "src/main/resources/messages_en_GB.properties",
+ ref: branch,
+ });
+
+ referenceFilePath = "pr-branch-messages_en_GB.properties";
+ const content = Buffer.from(fileContent.content, "base64").toString("utf-8");
+ fs.writeFileSync(referenceFilePath, content);
+ } else {
+ console.log("Using main branch reference file.");
+ const { data: fileContent } = await github.rest.repos.getContent({
+ owner: repoOwner,
+ repo: repoName,
+ path: "src/main/resources/messages_en_GB.properties",
+ ref: "main",
+ });
+
+ referenceFilePath = "main-branch-messages_en_GB.properties";
+ const content = Buffer.from(fileContent.content, "base64").toString("utf-8");
+ fs.writeFileSync(referenceFilePath, content);
+ }
+
+ console.log(`Reference file path: ${referenceFilePath}`);
+ core.exportVariable("REFERENCE_FILE", referenceFilePath);
- name: Run Python script to check files
id: run-check
@@ -92,7 +174,8 @@ jobs:
--actor ${{ github.event.pull_request.user.login }} \
--reference-file "${REFERENCE_FILE}" \
--branch "pr-branch" \
- --files "${FILES_LIST[@]}" > result.txt || true
+ --files "${FILES_LIST[@]}" > result.txt
+ continue-on-error: true # Continue the job even if this step fails
- name: Capture output
id: capture-output
@@ -124,13 +207,13 @@ jobs:
script: |
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
- const prNumber = context.issue.number;
+ const issueNumber = context.issue.number;
// Find existing comment
const comments = await github.rest.issues.listComments({
owner: repoOwner,
repo: repoName,
- issue_number: prNumber
+ issue_number: issueNumber
});
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
@@ -152,7 +235,7 @@ jobs:
await github.rest.issues.createComment({
owner: repoOwner,
repo: repoName,
- issue_number: prNumber,
+ issue_number: issueNumber,
body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n`
});
console.log("Created new comment.");
diff --git a/.github/workflows/licenses-update.yml b/.github/workflows/licenses-update.yml
index 409735c08..8a7d07c34 100644
--- a/.github/workflows/licenses-update.yml
+++ b/.github/workflows/licenses-update.yml
@@ -7,7 +7,8 @@ on:
paths:
- "build.gradle"
-permissions: read-all
+permissions:
+ contents: read
jobs:
generate-license-report:
@@ -52,7 +53,7 @@ jobs:
- name: Create Pull Request
id: cpr
if: env.CHANGES_DETECTED == 'true'
- uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
+ uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update 3rd Party Licenses"
diff --git a/.github/workflows/manage-label.yml b/.github/workflows/manage-label.yml
index 05367ee8c..6f765438b 100644
--- a/.github/workflows/manage-label.yml
+++ b/.github/workflows/manage-label.yml
@@ -4,7 +4,8 @@ on:
schedule:
- cron: "30 20 * * *"
-permissions: read-all
+permissions:
+ contents: read
jobs:
labeler:
diff --git a/.github/workflows/multiOSReleases.yml b/.github/workflows/multiOSReleases.yml
index e445dc2b9..d2f0f87a9 100644
--- a/.github/workflows/multiOSReleases.yml
+++ b/.github/workflows/multiOSReleases.yml
@@ -5,7 +5,8 @@ on:
release:
types: [created]
-permissions: read-all
+permissions:
+ contents: read
jobs:
build-installers:
@@ -42,7 +43,7 @@ jobs:
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
- gradle-version: 8.7
+ gradle-version: 8.12
# Install Windows dependencies
- name: Install WiX Toolset
diff --git a/.github/workflows/pre_commit.yml b/.github/workflows/pre_commit.yml
index 842257d73..df4e5a0f8 100644
--- a/.github/workflows/pre_commit.yml
+++ b/.github/workflows/pre_commit.yml
@@ -4,7 +4,8 @@ on:
push:
branches: [main]
-permissions: read-all
+permissions:
+ contents: read
jobs:
update:
@@ -19,7 +20,7 @@ jobs:
with:
fetch-depth: 0
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: 3.12
- name: Run Pre-Commit Hooks
@@ -35,7 +36,7 @@ jobs:
git diff --staged --quiet || git commit -m ":file_folder: pre-commit
> Made via .github/workflows/pre_commit.yml" || echo "pre-commit: no changes"
- name: Create Pull Request
- uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
+ uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "ci: 🤖 format everything with pre-commit"
diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml
index dea71c096..80f3021c1 100644
--- a/.github/workflows/push-docker.yml
+++ b/.github/workflows/push-docker.yml
@@ -9,14 +9,13 @@ on:
permissions:
contents: read
- packages: write
- id-token: write
jobs:
push:
runs-on: ubuntu-latest
permissions:
packages: write
+ id-token: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
@@ -33,7 +32,7 @@ jobs:
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
- gradle-version: 8.7
+ gradle-version: 8.12
- name: Run Gradle Command
run: ./gradlew clean build
@@ -42,7 +41,7 @@ jobs:
- name: Install cosign
if: github.ref == 'refs/heads/master'
- uses: sigstore/cosign-installer@v3.7.0
+ uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
with:
cosign-release: 'v2.4.1'
diff --git a/.github/workflows/releaseArtifacts.yml b/.github/workflows/releaseArtifacts.yml
index 21f2fb147..235cb4058 100644
--- a/.github/workflows/releaseArtifacts.yml
+++ b/.github/workflows/releaseArtifacts.yml
@@ -5,7 +5,8 @@ on:
release:
types: [created]
-permissions: read-all
+permissions:
+ contents: read
jobs:
push:
@@ -37,7 +38,7 @@ jobs:
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
- gradle-version: 8.7
+ gradle-version: 8.12
- name: Generate jar (With Security=${{ matrix.enable_security }})
run: ./gradlew clean createExe
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 3746016ce..f8631b55b 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -5,7 +5,8 @@ on:
- cron: "30 0 * * *"
workflow_dispatch:
-permissions: read-all
+permissions:
+ contents: read
jobs:
stale:
diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml
index 502208684..6478aeb18 100644
--- a/.github/workflows/swagger.yml
+++ b/.github/workflows/swagger.yml
@@ -6,7 +6,8 @@ on:
branches:
- master
-permissions: read-all
+permissions:
+ contents: read
jobs:
push:
diff --git a/.github/workflows/sync_files.yml b/.github/workflows/sync_files.yml
index fbbb56abe..e27f1b395 100644
--- a/.github/workflows/sync_files.yml
+++ b/.github/workflows/sync_files.yml
@@ -9,7 +9,8 @@ on:
- "src/main/resources/messages_*.properties"
- "scripts/ignore_translation.toml"
-permissions: read-all
+permissions:
+ contents: read
jobs:
sync-readme:
@@ -42,7 +43,7 @@ jobs:
git diff --staged --quiet || git commit -m ":memo: Sync README
> Made via sync_files.yml" || echo "no changes"
- name: Create Pull Request
- uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
+ uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update files
diff --git a/.github/workflows/update-translations.yml b/.github/workflows/update-translations.yml
index c6b408c3b..8c1a82d63 100644
--- a/.github/workflows/update-translations.yml
+++ b/.github/workflows/update-translations.yml
@@ -6,7 +6,8 @@ on:
paths:
- "src/main/resources/messages_en_GB.properties"
-permissions: read-all
+permissions:
+ contents: read
jobs:
update-translations-main:
@@ -50,7 +51,7 @@ jobs:
- name: Create Pull Request
id: cpr
if: env.CHANGES_DETECTED == 'true'
- uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
+ uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update translation files"
diff --git a/Dockerfile.fat b/Dockerfile.fat
index 1ee201763..b36677b9b 100644
--- a/Dockerfile.fat
+++ b/Dockerfile.fat
@@ -1,5 +1,5 @@
# Build the application
-FROM gradle:8.11-jdk17 AS build
+FROM gradle:8.12-jdk17 AS build
# Set the working directory
WORKDIR /app
diff --git a/README.md b/README.md
index 785a4e2f2..96bf8f60a 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,9 @@
[](https://hub.docker.com/r/frooodle/s-pdf)
[](https://discord.gg/HYmhKj45pU)
[](https://github.com/Stirling-Tools/Stirling-PDF/)
+[](https://scorecard.dev/viewer/?uri=github.com/Stirling-Tools/Stirling-PDF)
[](https://github.com/Stirling-Tools/stirling-pdf)
-
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
@@ -116,43 +116,43 @@ Stirling-PDF currently supports 38 languages!
| Language | Progress |
| -------------------------------------------- | -------------------------------------- |
-| Arabic (العربية) (ar_AR) |  |
+| Arabic (العربية) (ar_AR) |  |
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
| Basque (Euskara) (eu_ES) |  |
| Bulgarian (Български) (bg_BG) |  |
| Catalan (Català) (ca_CA) |  |
-| Croatian (Hrvatski) (hr_HR) |  |
+| Croatian (Hrvatski) (hr_HR) |  |
| Czech (Česky) (cs_CZ) |  |
| Danish (Dansk) (da_DK) |  |
-| Dutch (Nederlands) (nl_NL) |  |
+| Dutch (Nederlands) (nl_NL) |  |
| English (English) (en_GB) |  |
| English (US) (en_US) |  |
| French (Français) (fr_FR) |  |
-| German (Deutsch) (de_DE) |  |
+| German (Deutsch) (de_DE) |  |
| Greek (Ελληνικά) (el_GR) |  |
| Hindi (हिंदी) (hi_IN) |  |
-| Hungarian (Magyar) (hu_HU) |  |
+| Hungarian (Magyar) (hu_HU) |  |
| Indonesian (Bahasa Indonesia) (id_ID) |  |
| Irish (Gaeilge) (ga_IE) |  |
| Italian (Italiano) (it_IT) |  |
| Japanese (日本語) (ja_JP) |  |
-| Korean (한국어) (ko_KR) |  |
+| Korean (한국어) (ko_KR) |  |
| Norwegian (Norsk) (no_NB) |  |
-| Persian (فارسی) (fa_IR) |  |
-| Polish (Polski) (pl_PL) |  |
+| Persian (فارسی) (fa_IR) |  |
+| Polish (Polski) (pl_PL) |  |
| Portuguese (Português) (pt_PT) |  |
| Portuguese Brazilian (Português) (pt_BR) |  |
| Romanian (Română) (ro_RO) |  |
| Russian (Русский) (ru_RU) |  |
-| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
+| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
| Simplified Chinese (简体中文) (zh_CN) |  |
-| Slovakian (Slovensky) (sk_SK) |  |
-| Spanish (Español) (es_ES) |  |
+| Slovakian (Slovensky) (sk_SK) |  |
+| Spanish (Español) (es_ES) |  |
| Swedish (Svenska) (sv_SE) |  |
-| Thai (ไทย) (th_TH) |  |
+| Thai (ไทย) (th_TH) |  |
| Traditional Chinese (繁體中文) (zh_TW) |  |
| Turkish (Türkçe) (tr_TR) |  |
-| Ukrainian (Українська) (uk_UA) |  |
+| Ukrainian (Українська) (uk_UA) |  |
| Vietnamese (Tiếng Việt) (vi_VN) |  |
@@ -169,4 +169,4 @@ Join our community:
- [Translation Guide (How to add custom languages)](HowToAddNewLanguage.md)
- [Issue Tracker](https://github.com/Stirling-Tools/Stirling-PDF/issues)
- [Discord Community](https://discord.gg/HYmhKj45pU)
-- [Developer Guide](DeveloperGuide.md)
\ No newline at end of file
+- [Developer Guide](DeveloperGuide.md)
diff --git a/build.gradle b/build.gradle
index 15fab3bfd..6772dcb67 100644
--- a/build.gradle
+++ b/build.gradle
@@ -258,7 +258,7 @@ spotless {
// rules=['unused-dependency']
// }
tasks.wrapper {
- gradleVersion = "8.7"
+ gradleVersion = "8.12"
}
//tasks.withType(JavaCompile) {
// options.compilerArgs << "-Xlint:deprecation"
@@ -299,7 +299,6 @@ dependencies {
implementation "org.springframework.session:spring-session-core:$springBootVersion"
- implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
// Don't upgrade h2database
runtimeOnly "com.h2database:h2:2.3.232"
constraints {
@@ -308,7 +307,7 @@ dependencies {
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
}
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
-// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
+ // implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
implementation 'com.coveo:saml-client:5.0.0'
diff --git a/cucumber/features/general.feature b/cucumber/features/general.feature
index 4255c89e7..3ac610669 100644
--- a/cucumber/features/general.feature
+++ b/cucumber/features/general.feature
@@ -1,7 +1,7 @@
@general
Feature: API Validation
-
+
@split-pdf-by-sections @positive
Scenario Outline: split-pdf-by-sections with different parameters
Given I generate a PDF file as "fileInput"
@@ -66,7 +66,7 @@ Feature: API Validation
| pageNumbers | file_count |
| 1,3,5-9 | 8 |
| all | 20 |
- | 2n+1 | 11 |
+ | 2n+1 | 10 |
| 3n | 7 |
@@ -106,9 +106,9 @@ Feature: API Validation
And the response ZIP should contain 2 files
And the response file should have size greater than 0
And the response status code should be 200
-
+
Examples:
- | format |
- | png |
+ | format |
+ | png |
| gif |
- | jpeg |
+ | jpeg |
diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java
index 93dff07bb..be425590e 100644
--- a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java
+++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java
@@ -22,22 +22,31 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
+import stirling.software.SPDF.model.ApplicationProperties;
+import stirling.software.SPDF.model.ApplicationProperties.Security;
+import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
+import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
import stirling.software.SPDF.model.User;
+@Slf4j
@Component
public class UserAuthenticationFilter extends OncePerRequestFilter {
+ private final ApplicationProperties applicationProperties;
private final UserService userService;
private final SessionPersistentRegistry sessionPersistentRegistry;
private final boolean loginEnabledValue;
public UserAuthenticationFilter(
+ @Lazy ApplicationProperties applicationProperties,
@Lazy UserService userService,
SessionPersistentRegistry sessionPersistentRegistry,
@Qualifier("loginEnabled") boolean loginEnabledValue) {
+ this.applicationProperties = applicationProperties;
this.userService = userService;
this.sessionPersistentRegistry = sessionPersistentRegistry;
this.loginEnabledValue = loginEnabledValue;
@@ -121,33 +130,67 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
// Check if the authenticated user is disabled and invalidate their session if so
if (authentication != null && authentication.isAuthenticated()) {
+
+ Security securityProp = applicationProperties.getSecurity();
+ LoginMethod loginMethod = LoginMethod.UNKNOWN;
+
+ boolean blockRegistration = false;
+
+ // Extract username and determine the login method
Object principal = authentication.getPrincipal();
String username = null;
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
+ loginMethod = LoginMethod.USERDETAILS;
} else if (principal instanceof OAuth2User) {
username = ((OAuth2User) principal).getName();
+ loginMethod = LoginMethod.OAUTH2USER;
+ OAUTH2 oAuth = securityProp.getOauth2();
+ blockRegistration = oAuth != null && oAuth.getBlockRegistration();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
+ loginMethod = LoginMethod.SAML2USER;
+ SAML2 saml2 = securityProp.getSaml2();
+ blockRegistration = saml2 != null && saml2.getBlockRegistration();
} else if (principal instanceof String) {
username = (String) principal;
+ loginMethod = LoginMethod.STRINGUSER;
}
+ // Retrieve all active sessions for the user
List sessionsInformations =
sessionPersistentRegistry.getAllSessions(principal, false);
+ // Check if the user exists, is disabled, or needs session invalidation
if (username != null) {
+ log.debug("Validating user: {}", username);
boolean isUserExists = userService.usernameExistsIgnoreCase(username);
boolean isUserDisabled = userService.isUserDisabled(username);
+ boolean notSsoLogin =
+ !loginMethod.equals(LoginMethod.OAUTH2USER)
+ && !loginMethod.equals(LoginMethod.SAML2USER);
+
+ // Block user registration if not allowed by configuration
+ if (blockRegistration && !isUserExists) {
+ log.warn("Blocked registration for OAuth2/SAML user: {}", username);
+ response.sendRedirect(
+ request.getContextPath() + "/logout?oauth2_admin_blocked_user=true");
+ return;
+ }
+
+ // Expire sessions and logout if the user does not exist or is disabled
if (!isUserExists || isUserDisabled) {
+ log.info(
+ "Invalidating session for disabled or non-existent user: {}", username);
for (SessionInformation sessionsInformation : sessionsInformations) {
sessionsInformation.expireNow();
sessionPersistentRegistry.expireSession(sessionsInformation.getSessionId());
}
}
- if (!isUserExists) {
+ // Redirect to logout if credentials are invalid
+ if (!isUserExists && notSsoLogin) {
response.sendRedirect(request.getContextPath() + "/logout?badcredentials=true");
return;
}
@@ -161,6 +204,25 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
}
+ private enum LoginMethod {
+ USERDETAILS("UserDetails"),
+ OAUTH2USER("OAuth2User"),
+ STRINGUSER("StringUser"),
+ UNKNOWN("Unknown"),
+ SAML2USER("Saml2User");
+
+ private String method;
+
+ LoginMethod(String method) {
+ this.method = method;
+ }
+
+ @Override
+ public String toString() {
+ return method;
+ }
+ }
+
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String uri = request.getRequestURI();
diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java
index 81b46166b..31c39c1fc 100644
--- a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java
+++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java
@@ -163,6 +163,9 @@ public class AccountWebController {
case "invalid_destination":
erroroauth = "login.invalid_destination";
break;
+ case "relying_party_registration_not_found":
+ erroroauth = "login.relyingPartyRegistrationNotFound";
+ break;
// Valid InResponseTo was not available from the validation context, unable to
// evaluate
case "invalid_in_response_to":
diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java
index 4334f51de..13d19145a 100644
--- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java
+++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java
@@ -13,6 +13,8 @@ import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.simpleyaml.configuration.file.YamlFile;
import org.simpleyaml.configuration.file.YamlFileWrapper;
@@ -221,32 +223,51 @@ public class GeneralUtils {
throw new IllegalArgumentException("Invalid expression");
}
- int n = 0;
- while (true) {
+ for (int n = 1; n <= maxValue; n++) {
// Replace 'n' with the current value of n, correctly handling numbers before
// 'n'
- String sanitizedExpression = insertMultiplicationBeforeN(expression, n);
+ String sanitizedExpression = sanitizeNFunction(expression, n);
Double result = evaluator.evaluate(sanitizedExpression);
// Check if the result is null or not within bounds
- if (result == null || result <= 0 || result.intValue() > maxValue) {
- if (n != 0) break;
- } else {
+ if (result == null)
+ break;
+
+ if (result.intValue() > 0 && result.intValue() <= maxValue)
results.add(result.intValue());
- }
- n++;
}
return results;
}
+ private static String sanitizeNFunction(String expression, int nValue) {
+ String sanitizedExpression = expression.replace(" ", "");
+ String multiplyByOpeningRoundBracketPattern = "([0-9n)])\\("; // example: n(n-1), 9(n-1), (n-1)(n-2)
+ sanitizedExpression = sanitizedExpression.replaceAll(multiplyByOpeningRoundBracketPattern, "$1*(");
+
+ String multiplyByClosingRoundBracketPattern = "\\)([0-9n)])"; // example: (n-1)n, (n-1)9, (n-1)(n-2)
+ sanitizedExpression = sanitizedExpression.replaceAll(multiplyByClosingRoundBracketPattern, ")*$1");
+
+ sanitizedExpression = insertMultiplicationBeforeN(sanitizedExpression, nValue);
+ return sanitizedExpression;
+ }
+
private static String insertMultiplicationBeforeN(String expression, int nValue) {
// Insert multiplication between a number and 'n' (e.g., "4n" becomes "4*n")
String withMultiplication = expression.replaceAll("(\\d)n", "$1*n");
+ withMultiplication = formatConsecutiveNsForNFunction(withMultiplication);
// Now replace 'n' with its current value
return withMultiplication.replace("n", String.valueOf(nValue));
}
+ private static String formatConsecutiveNsForNFunction(String expression) {
+ String text = expression;
+ while (text.matches(".*n{2,}.*")) {
+ text = text.replaceAll("(? handlePart(String part, int totalPages, int offset) {
List partResult = new ArrayList<>();
diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties
index b12a12ed6..67b08084a 100644
--- a/src/main/resources/messages_ar_AR.properties
+++ b/src/main/resources/messages_ar_AR.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=طلب غير صالح
login.oauth2AccessDenied=تم رفض الوصول
login.oauth2InvalidTokenResponse=استجابة الرمز المميز غير صالحة
login.oauth2InvalidIdToken=رمز الهوية غير صالح
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=تم تعطيل المستخدم، تم حظر تسجيل الدخول حاليًا باستخدام اسم المستخدم هذا. يرجى الاتصال بالمسؤول.
login.alreadyLoggedIn=لقد تسجل دخولًا إلى
login.alreadyLoggedIn2=أجهزة أخرى. يرجى تسجيل الخروج من الأجهزة وحاول مرة أخرى.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=إصلاح
repair.header=إصلاح ملفات PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=تقطيع ملف PDF
fileChooser.click=انقر هنا
fileChooser.or=أو
fileChooser.dragAndDrop=قم بسحب الملفات وإفلاتها
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=قم بسحب المفات وإفلاتها هنا
#release notes
diff --git a/src/main/resources/messages_az_AZ.properties b/src/main/resources/messages_az_AZ.properties
index 9a4cae6a9..471eafaa6 100644
--- a/src/main/resources/messages_az_AZ.properties
+++ b/src/main/resources/messages_az_AZ.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Etibarsız Sorğu
login.oauth2AccessDenied=Giriş rədd edildi
login.oauth2InvalidTokenResponse=Etibarsız Token Cavabı
login.oauth2InvalidIdToken=Etibarsız Id Token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=İstifadəçi deaktivləşdirilmişdir, bu istifadəçi adı ilə giriş hal-hazırda bloklanmışdır. Zəhmət olmasa, administratorla əlaqə saxlayın.
login.alreadyLoggedIn=Siz artıq daxil olmusunuz
login.alreadyLoggedIn2=cihazlar. Zəhmət olmasa, cihazlardan çıxış edin və yenidən cəhd edin.
@@ -830,7 +831,7 @@ sign.first=İlk səhifə
sign.last=Son səhifə
sign.next=Növbəti səhifə
sign.previous=Əvvəlki səhifə
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Bərpa Et
repair.header=PDFləri Bərpa Et
@@ -1284,6 +1285,8 @@ splitByChapters.submit=PDF-i Ayır
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_bg_BG.properties b/src/main/resources/messages_bg_BG.properties
index fb4796e56..57c0e8a36 100644
--- a/src/main/resources/messages_bg_BG.properties
+++ b/src/main/resources/messages_bg_BG.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Невалидна заявка
login.oauth2AccessDenied=Отказан достъп
login.oauth2InvalidTokenResponse=Невалиден отговор на токена
login.oauth2InvalidIdToken=Невалиден токен за идентификатор
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Потребителят е деактивиран, влизането в момента е блокирано с това потребителско име. Моля, свържете се с администратора.
login.alreadyLoggedIn=Вече сте влезли в
login.alreadyLoggedIn2=устройства. Моля, излезте от устройствата и опитайте отново.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Поправи
repair.header=Поправи PDF-и
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Разделяне на PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties
index 96e429a32..3257a1551 100644
--- a/src/main/resources/messages_ca_CA.properties
+++ b/src/main/resources/messages_ca_CA.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Sol·licitud no vàlida
login.oauth2AccessDenied=Accés denegat
login.oauth2InvalidTokenResponse=Resposta de token no vàlida
login.oauth2InvalidIdToken=ID Token no vàlid
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=L'usuari està desactivat, l'inici de sessió està actualment bloquejat amb aquest nom d'usuari. Si us plau, contacta amb l'administrador.
login.alreadyLoggedIn=Ja has iniciat sessió a
login.alreadyLoggedIn2=dispositius. Si us plau, tanca la sessió en els dispositius i torna-ho a intentar.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Reparar
repair.header=Repara els PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Divideix PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_cs_CZ.properties b/src/main/resources/messages_cs_CZ.properties
index 553ec40e8..bc793279b 100644
--- a/src/main/resources/messages_cs_CZ.properties
+++ b/src/main/resources/messages_cs_CZ.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Neplatný požadavek
login.oauth2AccessDenied=Přístup zazobán
login.oauth2InvalidTokenResponse=Neplatná odpověď tokenu
login.oauth2InvalidIdToken=Neplatný identifikační token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Uživatel je deaktivován, přihlášení aktuálně pro tuto uživatelskou jmena je zakázáno. Kontaktujte správce.
login.alreadyLoggedIn=Jste již přihlášeni na
login.alreadyLoggedIn2=zariadení. Odhlašujte se z těchto zařízení a zkuste to znovu.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Opravit
repair.header=Opravit PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Podělit se PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_da_DK.properties b/src/main/resources/messages_da_DK.properties
index 9ab36d586..71fea7630 100644
--- a/src/main/resources/messages_da_DK.properties
+++ b/src/main/resources/messages_da_DK.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Ugyldig Anmodning
login.oauth2AccessDenied=Adgang Nægtet
login.oauth2InvalidTokenResponse=Ugyldigt Token Svar
login.oauth2InvalidIdToken=Ugyldigt Id Token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Bruger er deaktiveret, login er i øjeblikket blokeret med dette brugernavn. Kontakt venligst administratoren.
login.alreadyLoggedIn=Du er allerede logget ind på
login.alreadyLoggedIn2=enheder. Log ud af disse enheder og prøv igen.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Reparér
repair.header=Reparér PDF'er
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Splitter PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties
index 3681c2212..910649566 100644
--- a/src/main/resources/messages_de_DE.properties
+++ b/src/main/resources/messages_de_DE.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=ungültige Anfrage
login.oauth2AccessDenied=Zugriff abgelehnt
login.oauth2InvalidTokenResponse=Ungültige Token-Antwort
login.oauth2InvalidIdToken=Ungültiges ID-Token
+login.relyingPartyRegistrationNotFound=Keine Relying-Party-Registrierung gefunden
login.userIsDisabled=Benutzer ist deaktiviert, die Anmeldung ist mit diesem Benutzernamen derzeit gesperrt. Bitte wenden Sie sich an den Administrator.
login.alreadyLoggedIn=Sie sind bereits an
login.alreadyLoggedIn2=Geräten angemeldet. Bitte melden Sie sich dort ab und versuchen es dann erneut.
@@ -830,7 +831,7 @@ sign.first=Erste Seite
sign.last=Letzte Seite
sign.next=Nächste Seite
sign.previous=Vorherige Seite
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Reparieren
repair.header=PDFs reparieren
@@ -1284,6 +1285,8 @@ splitByChapters.submit=PDF teilen
fileChooser.click=Klicken
fileChooser.or=oder
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Datei(en) hierhin Ziehen & Fallenlassen
#release notes
diff --git a/src/main/resources/messages_el_GR.properties b/src/main/resources/messages_el_GR.properties
index cefb83e16..99cc37e92 100644
--- a/src/main/resources/messages_el_GR.properties
+++ b/src/main/resources/messages_el_GR.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Αρχαίως απαιτήσεις πληροφορ
login.oauth2AccessDenied=Πρόσβαση ορισμένες τοποθετήσεις
login.oauth2InvalidTokenResponse=Απάντηση άρκετο ρόταμα
login.oauth2InvalidIdToken=Λιδός όρκου μη αρκετός
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Ο χρήστης είναι δευτεροβαθμιακά διακωμένος, το σύστημα άλλαξε τον καθώς βρεθεί. Παρακαλείστε τους αρχηγούς να επιβεβαιώσουν τη συμπεριφορά χρήστη.
login.alreadyLoggedIn=Είστε ήδη σύνδεδες σε
login.alreadyLoggedIn2=κατοχόι. Παρακαλώ δυσκέντρωση τους και προσπαθήστε ξανά.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Επιδιόρθωση
repair.header=Επιδιόρθωση PDFs
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Διαλύστε το PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties
index 73e56c9d0..42069459d 100644
--- a/src/main/resources/messages_en_GB.properties
+++ b/src/main/resources/messages_en_GB.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Repair
repair.header=Repair PDFs
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties
index 99095e195..6c90e559a 100644
--- a/src/main/resources/messages_en_US.properties
+++ b/src/main/resources/messages_en_US.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Repair
repair.header=Repair PDFs
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties
index d21c2e601..7401569e1 100644
--- a/src/main/resources/messages_es_ES.properties
+++ b/src/main/resources/messages_es_ES.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Solicitud no válida
login.oauth2AccessDenied=Acceso denegado
login.oauth2InvalidTokenResponse=Respuesta de token no válida
login.oauth2InvalidIdToken=Token de identificación no válido
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=El usuario está desactivado, actualmente el acceso está bloqueado para ese nombre de usuario. Por favor, póngase en contacto con el administrador.
login.alreadyLoggedIn=Ya has iniciado sesión en
login.alreadyLoggedIn2=dispositivos. Cierra sesión en los dispositivos y vuelve a intentarlo.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Reparar
repair.header=Reparar archivos PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Dividir PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties
index f5d65e4f2..44df5362d 100644
--- a/src/main/resources/messages_eu_ES.properties
+++ b/src/main/resources/messages_eu_ES.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Konpondu
repair.header=Konpondu PDF fitxategiak
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_fa_IR.properties b/src/main/resources/messages_fa_IR.properties
index ce7781dab..0be077386 100644
--- a/src/main/resources/messages_fa_IR.properties
+++ b/src/main/resources/messages_fa_IR.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=درخواست نامعتبر
login.oauth2AccessDenied=دسترسی ممنوع
login.oauth2InvalidTokenResponse=پاسخ توکن نامعتبر است
login.oauth2InvalidIdToken=توکن شناسه نامعتبر است
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=کاربر غیرفعال شده است، ورود با این نام کاربری در حال حاضر مسدود است. لطفاً با مدیر تماس بگیرید.
login.alreadyLoggedIn=شما قبلاً وارد شدهاید در
login.alreadyLoggedIn2=دستگاهها. لطفاً از دستگاهها خارج شده و دوباره تلاش کنید.
@@ -830,7 +831,7 @@ sign.first=صفحه اول
sign.last=صفحه آخر
sign.next=صفحه بعدی
sign.previous=صفحه قبلی
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=تعمیر
repair.header=تعمیر PDFها
@@ -1284,6 +1285,8 @@ splitByChapters.submit=تقسیم PDF
fileChooser.click=کلیک کنید
fileChooser.or=یا
fileChooser.dragAndDrop=بکشید و رها کنید
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
#release notes
diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties
index 2a7d2f876..36c92b5e3 100644
--- a/src/main/resources/messages_fr_FR.properties
+++ b/src/main/resources/messages_fr_FR.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Requête invalide
login.oauth2AccessDenied=Accès refusé
login.oauth2InvalidTokenResponse=Réponse contenant le jeton est invalide
login.oauth2InvalidIdToken=Jeton d'identification invalide
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=L'utilisateur est désactivé, la connexion est actuellement bloquée avec ce nom d'utilisateur. Veuillez contacter l'administrateur.
login.alreadyLoggedIn=Vous êtes déjà connecté sur
login.alreadyLoggedIn2=appareils. Veuillez vous déconnecter des appareils et réessayer.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Réparer
repair.header=Réparer
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Diviser le PDF
fileChooser.click=Cliquez
fileChooser.or=ou
fileChooser.dragAndDrop=Glisser & Déposer
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Glisser & Déposer le(s) fichier(s) ici
#release notes
diff --git a/src/main/resources/messages_ga_IE.properties b/src/main/resources/messages_ga_IE.properties
index 42ef0095a..c9cb7355f 100644
--- a/src/main/resources/messages_ga_IE.properties
+++ b/src/main/resources/messages_ga_IE.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Iarratas Neamhbhailí
login.oauth2AccessDenied=Rochtain Diúltaithe
login.oauth2InvalidTokenResponse=Freagra Comhartha Neamhbhailí
login.oauth2InvalidIdToken=Comhartha Aitheantais Neamhbhailí
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Deisiúchán
repair.header=PDF a dheisiú
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_hi_IN.properties b/src/main/resources/messages_hi_IN.properties
index 581f04b13..994d2e800 100644
--- a/src/main/resources/messages_hi_IN.properties
+++ b/src/main/resources/messages_hi_IN.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=गलत याचना
login.oauth2AccessDenied=इनपुट उम्मीदवार डिसकार
login.oauth2InvalidTokenResponse=अमान्तरित सिक्वेंस जवाब कैसे है
login.oauth2InvalidIdToken=गलत इड टोकन
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=उपयोगकर्ता डिसबाल, यह वर्षा सभी उपयोगकर्ता जूझे वर्षाकरण बारा मिल गई है। कृपया संबवादक से संपर्क करें.
login.alreadyLoggedIn=आप पहले से ही
login.alreadyLoggedIn2=सुनिश्चित करने वाले डिवाइस्स पर लॉग-इन हैं। कृपया इन डिवाइस से लॉगआउट करें और पुनः प्रयास करें
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=मरम्मत
repair.header=पीडीएफ़ मरम्मत करें
@@ -1284,6 +1285,8 @@ splitByChapters.submit=PDF विभाजित
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_hr_HR.properties b/src/main/resources/messages_hr_HR.properties
index efd24b4c1..2e2157488 100644
--- a/src/main/resources/messages_hr_HR.properties
+++ b/src/main/resources/messages_hr_HR.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Neispravan zahtjev
login.oauth2AccessDenied=Pristup odbijen
login.oauth2InvalidTokenResponse=Nevažeći odgovor tokena
login.oauth2InvalidIdToken=Nevažeći ID token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Korisnik je deaktiviran, prijava sa ovim korisničkim imenom je trenutno zakazana. Molimo Vas da kontaktirate administratorske osobe.
login.alreadyLoggedIn=Već ste se prijavili na
login.alreadyLoggedIn2=ure. Odjavite se s ure i pokušajte ponovo.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Popravi
repair.header=Popravi PDF datoteku
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Podijeli PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_hu_HU.properties b/src/main/resources/messages_hu_HU.properties
index c847e6469..a1bcc7ae7 100644
--- a/src/main/resources/messages_hu_HU.properties
+++ b/src/main/resources/messages_hu_HU.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Érvénytelen kérelem
login.oauth2AccessDenied=Hozzáférés megtagadva
login.oauth2InvalidTokenResponse=Érvénytelen token-válasz
login.oauth2InvalidIdToken=Érvénytelen azonosító token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=A felhasználó deaktivált, a bejelentkezés jelenleg megszakítva ezzel a felhasználónévvel. Kérjen segítséget a rendszergazdától.
login.alreadyLoggedIn=Már be van jelentkezve az
login.alreadyLoggedIn2=eszközökre. Kijelentkezzen ezekből a eszközökből, majd próbálja újra bejelentkezni.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Javítás
repair.header=PDF-ek javítása
@@ -1284,6 +1285,8 @@ splitByChapters.submit=PDF osztás
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_id_ID.properties b/src/main/resources/messages_id_ID.properties
index d7fd7bda3..f61147f04 100644
--- a/src/main/resources/messages_id_ID.properties
+++ b/src/main/resources/messages_id_ID.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Permintaan Tidak Valid
login.oauth2AccessDenied=Akses Ditolak
login.oauth2InvalidTokenResponse=Respons Token Tidak Valid
login.oauth2InvalidIdToken=Token ID Tidak Valid
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Pengguna dinonaktifkan, login saat ini diblokir dengan nama pengguna ini. Silakan hubungi administrator.
login.alreadyLoggedIn=Anda sudah login ke
login.alreadyLoggedIn2=perangkat. Silakan keluar dari perangkat dan coba lagi.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Perbaiki
repair.header=Perbaiki PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Pecah PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties
index e5a500b8b..6f2602e25 100644
--- a/src/main/resources/messages_it_IT.properties
+++ b/src/main/resources/messages_it_IT.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Richiesta non valida
login.oauth2AccessDenied=Accesso negato
login.oauth2InvalidTokenResponse=Risposta token non valida
login.oauth2InvalidIdToken=Id Token non valido
+login.relyingPartyRegistrationNotFound=Nessuna registrazione di parte affidabile trovata
login.userIsDisabled=L'utente è disattivato, l'accesso è attualmente bloccato con questo nome utente. Si prega di contattare l'amministratore.
login.alreadyLoggedIn=Hai già effettuato l'accesso a
login.alreadyLoggedIn2=dispositivi. Esci dai dispositivi e riprova.
@@ -830,7 +831,7 @@ sign.first=Prima pagina
sign.last=Ultima pagina
sign.next=Prossima pagina
sign.previous=Pagina precedente
-
+sign.maintainRatio=Attiva il mantenimento delle proporzioni
#repair
repair.title=Ripara
repair.header=Ripara PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Dividi PDF
fileChooser.click=Clicca
fileChooser.or=o
fileChooser.dragAndDrop=Trascina & Rilascia
+fileChooser.dragAndDropPDF=Trascina & rilascia il file PDF
+fileChooser.dragAndDropImage=Trascina & rilascia il file immagine
fileChooser.hoveredDragAndDrop=Trascina & rilascia i file qui
#release notes
diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties
index e5bef7b48..16afc859b 100644
--- a/src/main/resources/messages_ja_JP.properties
+++ b/src/main/resources/messages_ja_JP.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=無効なリクエスト
login.oauth2AccessDenied=アクセス拒否
login.oauth2InvalidTokenResponse=無効なトークン応答
login.oauth2InvalidIdToken=無効なIDトークン
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
login.alreadyLoggedIn=すでにログインしています
login.alreadyLoggedIn2=デバイスからログアウトしてもう一度お試しください。
@@ -830,7 +831,7 @@ sign.first=最初のページ
sign.last=最後のページ
sign.next=次のページ
sign.previous=前のページ
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=修復
repair.header=PDFを修復
@@ -1284,6 +1285,8 @@ splitByChapters.submit=PDFを分割
fileChooser.click=クリック
fileChooser.or=または
fileChooser.dragAndDrop=ドラッグ&ドロップ
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ
#release notes
diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties
index 5ad547ecb..83c48e470 100644
--- a/src/main/resources/messages_ko_KR.properties
+++ b/src/main/resources/messages_ko_KR.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=복구
repair.header=PDF 복구
@@ -1284,6 +1285,8 @@ splitByChapters.submit=PDF 분할
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_nl_NL.properties b/src/main/resources/messages_nl_NL.properties
index 11885d9d4..76b57ef75 100644
--- a/src/main/resources/messages_nl_NL.properties
+++ b/src/main/resources/messages_nl_NL.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Ongeldig verzoek
login.oauth2AccessDenied=Toegang geweigerd
login.oauth2InvalidTokenResponse=Ongeldige tokenreactie
login.oauth2InvalidIdToken=Ongeldige ID token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=De gebruiker is gedesactiveerd, inloggen is momenteel geblokkeerd voor deze gebruikersnaam. Neem contact op met de beheerder.
login.alreadyLoggedIn=U zit reeds ingelogd bij
login.alreadyLoggedIn2=apparaten. U moet u a.u.b. uitloggen van de apparaten en opnieuw proberen.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Repareren
repair.header=PDF's repareren
@@ -1284,6 +1285,8 @@ splitByChapters.submit=PDF splitsen
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_no_NB.properties b/src/main/resources/messages_no_NB.properties
index f7e41b53e..d6fb339df 100644
--- a/src/main/resources/messages_no_NB.properties
+++ b/src/main/resources/messages_no_NB.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Ugyldig forespørsel
login.oauth2AccessDenied=Tilgang nektet
login.oauth2InvalidTokenResponse=Ugyldig tokenrespons
login.oauth2InvalidIdToken=Ugyldig Id Token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Reparer
repair.header=Reparer PDF-er
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties
index 1b2f9c8e1..ee5672123 100644
--- a/src/main/resources/messages_pl_PL.properties
+++ b/src/main/resources/messages_pl_PL.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Nieprawidłowe żądanie
login.oauth2AccessDenied=Brak dostępu
login.oauth2InvalidTokenResponse=Nieprawidłowa odpowiedź na token
login.oauth2InvalidIdToken=Nieprawidłowa wartość tokenu
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Użytkownik jest nieaktywny, logowanie przy użyciu tej nazwy użytkownika jest obecnie zablokowane. Prosimy o kontakt z administratorem.
login.alreadyLoggedIn=Jesteś już zalogowany na
login.alreadyLoggedIn2=urządzeniach. Wyloguj się z tych urządzeń i spróbuj ponownie.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Napraw
repair.header=Napraw dokument(y) PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Podziel PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties
index f5184d4a1..b5b1656c1 100644
--- a/src/main/resources/messages_pt_BR.properties
+++ b/src/main/resources/messages_pt_BR.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Requisição Inválida
login.oauth2AccessDenied=Acesso Negado
login.oauth2InvalidTokenResponse=Resposta de Token Inválida
login.oauth2InvalidIdToken=Id de Token Inválido
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=O usuário está desativado, o login está atualmente bloqueado com este nome de usuário. Entre em contato com o administrador.
login.alreadyLoggedIn=Você já está conectado em
login.alreadyLoggedIn2=aparelhos. Por favor saia dos aparelhos e tente novamente.
@@ -830,7 +831,7 @@ sign.first=Primeira página
sign.last=Última página
sign.next=Próxima página
sign.previous=Página anterior
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Reparar
repair.header=Reparar
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Dividir
fileChooser.click=Clique
fileChooser.or=ou
fileChooser.dragAndDrop=Arraste & Solte
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Arraste & Solte arquivo(s) aqui
#release notes
diff --git a/src/main/resources/messages_pt_PT.properties b/src/main/resources/messages_pt_PT.properties
index 86457f790..0fe76bb7a 100644
--- a/src/main/resources/messages_pt_PT.properties
+++ b/src/main/resources/messages_pt_PT.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Requisito inválido
login.oauth2AccessDenied=Acesso negado
login.oauth2InvalidTokenResponse=Resposta de token inválida
login.oauth2InvalidIdToken=Token de identificação inválido
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=O utilizador foi desativado, o login está atualmente bloqueado com esta conta. Por favor, contacte o administrador.
login.alreadyLoggedIn=Já está logado em
login.alreadyLoggedIn2=dispositivos. Por favor, faça logout nos dispositivos e tente novamente.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Reparar
repair.header=Reparar PDFs
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Dividir o PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties
index 0e8b32266..f3688a73d 100644
--- a/src/main/resources/messages_ro_RO.properties
+++ b/src/main/resources/messages_ro_RO.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Cerere Invalidă
login.oauth2AccessDenied=Acces Refuzat
login.oauth2InvalidTokenResponse=Răspuns Invalid la Token
login.oauth2InvalidIdToken=Token de Id Invalid
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Utilizatorul este dezactivat, conectarea este în prezent blocată cu acest nume de utilizator. Te rugăm să contactezi administratorul.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Repară
repair.header=Repară documente PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties
index 6bd9331dc..6a5b1569a 100644
--- a/src/main/resources/messages_ru_RU.properties
+++ b/src/main/resources/messages_ru_RU.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Неверный запрос
login.oauth2AccessDenied=Доступ запрещен
login.oauth2InvalidTokenResponse=Недействительный ответ токена
login.oauth2InvalidIdToken=Недействительный идентификационный токен
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Пользователь деактивирован, вход с данным именем пользователя заблокирован. Пожалуйста, обратитесь к администратору.
login.alreadyLoggedIn=Вы уже вошли в
login.alreadyLoggedIn2=устройства. Выполните выход из устройств и попробуйте снова.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Ремонт
repair.header=Ремонт PDF ов
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Разделить PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_sk_SK.properties b/src/main/resources/messages_sk_SK.properties
index 66da42147..aed436354 100644
--- a/src/main/resources/messages_sk_SK.properties
+++ b/src/main/resources/messages_sk_SK.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Opraviť
repair.header=Opraviť PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_sr_LATN_RS.properties b/src/main/resources/messages_sr_LATN_RS.properties
index a60195457..4c17fd28a 100644
--- a/src/main/resources/messages_sr_LATN_RS.properties
+++ b/src/main/resources/messages_sr_LATN_RS.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Popravi
repair.header=Popravi PDF fajlove
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties
index ec26aaa1f..78d6b6466 100644
--- a/src/main/resources/messages_sv_SE.properties
+++ b/src/main/resources/messages_sv_SE.properties
@@ -250,7 +250,7 @@ database.fileNullOrEmpty=Filen får inte vara null eller tom
database.failedImportFile=Misslyckades med att importera fil
session.expired=Din session har löpt ut. Uppdatera sidan och försök igen.
-session.refreshPage=Refresh Page
+session.refreshPage=Uppdatera sida
#############
# HOME-PAGE #
@@ -519,9 +519,9 @@ home.validateSignature.desc=Verify digital signatures and certificates in PDF do
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
#replace-invert-color
-replace-color.title=Replace-Invert-Color
+replace-color.title=Ersätt-Invertera-Färg
replace-color.header=Ersätt-Invertera färg på PDF
-home.replaceColorPdf.title=Replace and Invert Color
+home.replaceColorPdf.title=Ersätt och Invertera färg
home.replaceColorPdf.desc=Ersätt färg för text och bakgrund i PDF och invertera hela färgen på PDF för att minska filstorlek
replaceColorPdf.tags=Ersätt Färg, Sidåtgärder, Bakomliggande, Serversid
replace-color.selectText.1=Ersätt eller Invertera färgalternativ
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Ogiltig begäran
login.oauth2AccessDenied=Åtkomst nekad
login.oauth2InvalidTokenResponse=Ogiltigt token-svar
login.oauth2InvalidIdToken=Ogiltigt Id-token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Användaren är inaktiverad, inloggning är för närvarande blockerad med detta användarnamn. Kontakta administratören.
login.alreadyLoggedIn=Du är redan inloggad på
login.alreadyLoggedIn2=enheter. Logga ut från enheterna och försök igen.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Reparera
repair.header=Reparera PDF-filer
@@ -968,17 +969,17 @@ multiTool.undo=Undo
multiTool.redo=Redo
#decrypt
-decrypt.passwordPrompt=This file is password-protected. Please enter the password:
-decrypt.cancelled=Operation cancelled for PDF: {0}
-decrypt.noPassword=No password provided for encrypted PDF: {0}
-decrypt.invalidPassword=Please try again with the correct password.
-decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
-decrypt.unexpectedError=There was an error processing the file. Please try again.
-decrypt.serverError=Server error while decrypting: {0}
-decrypt.success=File decrypted successfully.
+decrypt.passwordPrompt=Denna fil är lösenordsskyddad. Fyll i lösenord:
+decrypt.cancelled=Operation misslyckades för PDF: {0}
+decrypt.noPassword=Inget lösenord angivet för krypterad PDF: {0}
+decrypt.invalidPassword=Försök igen med korrekt lösenord.
+decrypt.invalidPasswordHeader=Felaktigt lösenord eller osupportad kryptering för PDF: {0}
+decrypt.unexpectedError=Det uppstod ett fel vid processering av filen. Vänligen försök igen.
+decrypt.serverError=Serverfel vid avkryptering: {0}
+decrypt.success=Fil avkrypterad.
#multiTool-advert
-multiTool-advert.message=This feature is also available in our multi-tool page . Check it out for enhanced page-by-page UI and additional features!
+multiTool-advert.message=Denna funktion finns också tillgänglig i vår multi-tool page . Spana in den för bättre sida-för-sida anpassning och ytterligare funktioner!
#view pdf
viewPdf.title=Visa PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Dela upp PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_th_TH.properties b/src/main/resources/messages_th_TH.properties
index ccf3fdd5d..33711c130 100644
--- a/src/main/resources/messages_th_TH.properties
+++ b/src/main/resources/messages_th_TH.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=คำขอไม่ถูกต้อง
login.oauth2AccessDenied=การเข้าถึงถูกปฏิเสธ
login.oauth2InvalidTokenResponse=การตอบกลับโทเค็นไม่ถูกต้อง
login.oauth2InvalidIdToken=โทเค็น Id ไม่ถูกต้อง
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=ผู้ใช้งานถูกระงับการใช้งาน ไม่สามารถเข้าสู่ระบบด้วยชื่อผู้ใช้นี้ได้ กรุณาติดต่อผู้ดูแลระบบ
login.alreadyLoggedIn=คุณได้เข้าสู่ระบบใน
login.alreadyLoggedIn2=อุปกรณ์แล้ว กรุณาออกจากระบบจากอุปกรณ์ที่ใช้งานอยู่แล้ว จากนั้นลองใหม่อีกครั้ง
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=ซ่อมแซม
repair.header=ซ่อมแซม PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=แบ่งไฟล์ PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_tr_TR.properties b/src/main/resources/messages_tr_TR.properties
index 5b546bc37..7c5ca7b67 100644
--- a/src/main/resources/messages_tr_TR.properties
+++ b/src/main/resources/messages_tr_TR.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Geçersiz İstek
login.oauth2AccessDenied=Erişim Reddedildi
login.oauth2InvalidTokenResponse=Geçersiz Belirteç Yanıtı
login.oauth2InvalidIdToken=Geçersiz Kimlik Belirteci
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Kullanıcı devre dışı bırakıldı, şu anda bu kullanıcı adıyla giriş engellendi. Lütfen yöneticiyle iletişime geçin.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Onar
repair.header=PDF'leri Onar
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_uk_UA.properties b/src/main/resources/messages_uk_UA.properties
index fcab3ea15..a43bb399f 100644
--- a/src/main/resources/messages_uk_UA.properties
+++ b/src/main/resources/messages_uk_UA.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Недійсний запит
login.oauth2AccessDenied=Доступ заблоковано
login.oauth2InvalidTokenResponse=Недійсна відповідь з токеном
login.oauth2InvalidIdToken=Недійсний Id токен
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Ремонт
repair.header=Ремонт PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_vi_VN.properties b/src/main/resources/messages_vi_VN.properties
index 2200c7eba..ae1e47f03 100644
--- a/src/main/resources/messages_vi_VN.properties
+++ b/src/main/resources/messages_vi_VN.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=Yêu cầu không hợp lệ
login.oauth2AccessDenied=Truy cập bị từ chối
login.oauth2InvalidTokenResponse=Phản hồi token không hợp lệ
login.oauth2InvalidIdToken=Id Token không hợp lệ
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
login.alreadyLoggedIn=You are already logged in to
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
@@ -830,7 +831,7 @@ sign.first=First page
sign.last=Last page
sign.next=Next page
sign.previous=Previous page
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=Sửa chữa
repair.header=Sửa chữa PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click
fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes
diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties
index 1653fa11e..921faf0cf 100644
--- a/src/main/resources/messages_zh_CN.properties
+++ b/src/main/resources/messages_zh_CN.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=无效请求
login.oauth2AccessDenied=拒绝访问
login.oauth2InvalidTokenResponse=无效的 Token 响应
login.oauth2InvalidIdToken=无效的 Token
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=用户被禁用,登录已被阻止。请联系管理员。
login.alreadyLoggedIn=您已经登录到了
login.alreadyLoggedIn2=设备,请注销设备后重试。
@@ -830,7 +831,7 @@ sign.first=首页
sign.last=末页
sign.next=下一页
sign.previous=上一页
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=修复
repair.header=修复 PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=拆分 PDF
fileChooser.click=单击
fileChooser.or=或
fileChooser.dragAndDrop=拖放文件
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=拖放文件到此处
#release notes
diff --git a/src/main/resources/messages_zh_TW.properties b/src/main/resources/messages_zh_TW.properties
index acfe2c312..cf6dfb9d4 100644
--- a/src/main/resources/messages_zh_TW.properties
+++ b/src/main/resources/messages_zh_TW.properties
@@ -561,6 +561,7 @@ login.oauth2invalidRequest=無效的回應
login.oauth2AccessDenied=存取被拒
login.oauth2InvalidTokenResponse=無效的權杖回應
login.oauth2InvalidIdToken=無效的識別權杖
+login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=使用者已停用,目前此使用者無法登入。請聯絡系統管理員。
login.alreadyLoggedIn=您已經登入了
login.alreadyLoggedIn2=個裝置。請登出其他裝置後再試一次。
@@ -830,7 +831,7 @@ sign.first=第一頁
sign.last=最後一頁
sign.next=下一頁
sign.previous=上一頁
-
+sign.maintainRatio=Toggle maintain aspect ratio
#repair
repair.title=修復
repair.header=修復 PDF
@@ -1284,6 +1285,8 @@ splitByChapters.submit=分割 PDF
fileChooser.click=點選
fileChooser.or=或
fileChooser.dragAndDrop=拖放檔案
+fileChooser.dragAndDropPDF=Drag & Drop PDF file
+fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=將檔案拖放至此
#release notes
diff --git a/src/main/resources/static/css/add-image.css b/src/main/resources/static/css/add-image.css
index 5a735b425..f51da024f 100644
--- a/src/main/resources/static/css/add-image.css
+++ b/src/main/resources/static/css/add-image.css
@@ -2,22 +2,32 @@
position: relative;
margin: 20px 0;
}
+
#pdf-canvas {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
width: 100%;
}
+
.draggable-buttons-box {
- position: absolute;
+ position: relative;
top: 0;
padding: 10px;
- width: 100%;
+ width: calc(100% + 4.4rem);
display: flex;
gap: 5px;
+ z-index: 5;
+ margin-left: -2.2rem;
}
-.draggable-buttons-box > button {
- z-index: 10;
+
+.draggable-buttons-box>button {
+ z-index: 4;
background-color: rgba(13, 110, 253, 0.1);
+ flex: 1 1 auto;
+ min-width: 2.5rem;
+ max-width: 4rem;
}
+
+
.draggable-canvas {
border: 1px solid red;
position: absolute;
@@ -26,3 +36,20 @@
top: 0px;
left: 0;
}
+
+.input-with-icon {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+}
+
+.input-with-icon .icon {
+ position: absolute;
+ left: 0.5rem;
+ pointer-events: none;
+ color: #aaa;
+}
+
+.input-with-icon input {
+ padding-left: 2.2rem;
+}
diff --git a/src/main/resources/static/css/fileSelect.css b/src/main/resources/static/css/fileSelect.css
index 3f96814f9..afb0b075f 100644
--- a/src/main/resources/static/css/fileSelect.css
+++ b/src/main/resources/static/css/fileSelect.css
@@ -118,6 +118,7 @@
row-gap: 1px;
height: 60px;
width: 60px;
+ top:4px;
}
.file-icon {
@@ -165,8 +166,8 @@
height: 15px;
width: 15px;
- right: 10px;
- top: -5px;
+ right: 0px;
+ top: -17px;
}
.remove-selected-file * {
@@ -219,3 +220,54 @@
border-radius: 1rem;
border: 1px solid rgb(105, 116, 134, 0.5);
}
+
+.draggable-image-overlay{
+ position: absolute;
+ background: rgba(0, 0, 0, 0.7);
+ display: none;
+ z-index: 10;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ font-size: 16px;
+ font-weight: bold;
+ pointer-events: none;
+ left:0;
+ top:0;
+ height:100%;
+ width:100%;
+ border-radius: 1rem;
+}
+
+.small-file-container:hover .drag-icon {
+ display: flex;
+}
+
+.drag-icon {
+ display: none;
+ position: absolute;
+ top: 5px;
+ width: 20px;
+ height: 20px;
+ background: rgba(0, 0, 0, 0.5);
+ color: white;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ font-size: 14px;
+ pointer-events: none;
+ z-index: 1;
+}
+
+#imagePreviewModal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: none;
+ justify-content: center;
+ align-items: center;
+ z-index: 9999;
+}
diff --git a/src/main/resources/static/css/sign.css b/src/main/resources/static/css/sign.css
index 772fee6d0..6de4efcd6 100644
--- a/src/main/resources/static/css/sign.css
+++ b/src/main/resources/static/css/sign.css
@@ -22,18 +22,38 @@ select#font-select option {
}
.draggable-buttons-box {
- position: absolute;
+ position: relative;
top: 0;
padding: 10px;
- width: 100%;
+ width: calc(100% + 4.4rem);
display: flex;
gap: 5px;
z-index: 5;
+ margin-left: -2.2rem;
}
.draggable-buttons-box>button {
z-index: 4;
background-color: rgba(13, 110, 253, 0.1);
+ flex: 1 1 auto;
+ min-width: 2.5rem;
+ max-width: 4rem;
+}
+
+
+.rotation-handle {
+ width: 20px;
+ height: 20px;
+ border: 2px solid #3498db;
+ background-color: rgba(52, 152, 219, 0.1);
+ color: white;
+ border-radius: 50%;
+ text-align: center;
+ line-height: 20px;
+ position: absolute;
+ cursor: grab;
+ top: -30px;
+ left: calc(50% - 10px);
}
.draggable-canvas {
@@ -113,3 +133,33 @@ select#font-select option {
text-align: right;
padding: 0.5rem 1rem;
}
+
+.input-with-icon {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+}
+
+.input-with-icon .icon {
+ position: absolute;
+ left: 0.5rem;
+ pointer-events: none;
+ color: #aaa;
+}
+
+.input-with-icon input {
+ padding-left: 2.2rem;
+}
+
+.small-file-container-saved {
+ padding-top: 1px;
+ position: relative;
+ row-gap: 1px;
+ height: 60px;
+ width: 60px;
+ top: 4px;
+}
+
+.small-file-container-saved:hover .drag-icon {
+ display: flex;
+}
diff --git a/src/main/resources/static/js/draggable-utils.js b/src/main/resources/static/js/draggable-utils.js
index aa0c15a03..b6fa73b5f 100644
--- a/src/main/resources/static/js/draggable-utils.js
+++ b/src/main/resources/static/js/draggable-utils.js
@@ -7,75 +7,137 @@ const DraggableUtils = {
elementAllPages: [],
documentsMap: new Map(),
lastInteracted: null,
-
+ padding: 15,
+ maintainRatioEnabled: true,
init() {
interact('.draggable-canvas')
.draggable({
listeners: {
+ start(event) {
+ const target = event.target;
+ x = parseFloat(target.getAttribute('data-bs-x'));
+ y = parseFloat(target.getAttribute('data-bs-y'));
+ },
move: (event) => {
const target = event.target;
- const x = (parseFloat(target.getAttribute('data-bs-x')) || 0) + event.dx;
- const y = (parseFloat(target.getAttribute('data-bs-y')) || 0) + event.dy;
+ // Retrieve position attributes
+ let x = parseFloat(target.getAttribute('data-bs-x')) || 0;
+ let y = parseFloat(target.getAttribute('data-bs-y')) || 0;
+ const angle = parseFloat(target.getAttribute('data-angle')) || 0;
+
+ // Update position based on drag movement
+ x += event.dx;
+ y += event.dy;
+
+ // Apply translation to the parent container (bounding box)
target.style.transform = `translate(${x}px, ${y}px)`;
+
+ // Preserve rotation on the inner canvas
+ const canvas = target.querySelector('.display-canvas');
+
+ const canvasWidth = parseFloat(canvas.style.width);
+ const canvasHeight = parseFloat(canvas.style.height);
+
+ const cosAngle = Math.abs(Math.cos(angle));
+ const sinAngle = Math.abs(Math.sin(angle));
+
+ const rotatedWidth = canvasWidth * cosAngle + canvasHeight * sinAngle;
+ const rotatedHeight = canvasWidth * sinAngle + canvasHeight * cosAngle;
+
+ const offsetX = (rotatedWidth - canvasWidth) / 2;
+ const offsetY = (rotatedHeight - canvasHeight) / 2;
+
+ canvas.style.transform = `translate(${offsetX}px, ${offsetY}px) rotate(${angle}rad)`;
+
+ // Update attributes for persistence
target.setAttribute('data-bs-x', x);
target.setAttribute('data-bs-y', y);
- this.onInteraction(target);
- //update the last interacted element
- this.lastInteracted = event.target;
+ // Set the last interacted element
+ this.lastInteracted = target;
},
},
})
.resizable({
- edges: {left: true, right: true, bottom: true, top: true},
+ edges: { left: true, right: true, bottom: true, top: true },
listeners: {
+ start: (event) => {
+ const target = event.target;
+ x = parseFloat(target.getAttribute('data-bs-x')) || 0;
+ y = parseFloat(target.getAttribute('data-bs-y')) || 0;
+ },
move: (event) => {
- var target = event.target;
- var x = parseFloat(target.getAttribute('data-bs-x')) || 0;
- var y = parseFloat(target.getAttribute('data-bs-y')) || 0;
+ const target = event.target;
- // check if control key is pressed
- if (event.ctrlKey) {
- const aspectRatio = target.offsetWidth / target.offsetHeight;
- // preserve aspect ratio
- let width = event.rect.width;
- let height = event.rect.height;
+ const MAX_CHANGE = 60;
- if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) {
- height = width / aspectRatio;
- } else {
- width = height * aspectRatio;
+ let width = event.rect.width - 2 * this.padding;
+ let height = event.rect.height - 2 * this.padding;
+
+ const canvas = target.querySelector('.display-canvas');
+ if (canvas) {
+ const originalWidth = parseFloat(canvas.style.width) || canvas.width;
+ const originalHeight = parseFloat(canvas.style.height) || canvas.height;
+ const angle = parseFloat(target.getAttribute('data-angle')) || 0;
+
+ const aspectRatio = originalWidth / originalHeight;
+
+ if (!event.ctrlKey && this.maintainRatioEnabled) {
+ if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) {
+ height = width / aspectRatio;
+ } else {
+ width = height * aspectRatio;
+ }
}
- event.rect.width = width;
- event.rect.height = height;
+ const widthChange = width - originalWidth;
+ const heightChange = height - originalHeight;
+
+ if (Math.abs(widthChange) > MAX_CHANGE || Math.abs(heightChange) > MAX_CHANGE) {
+ const scale = MAX_CHANGE / Math.max(Math.abs(widthChange), Math.abs(heightChange));
+ width = originalWidth + widthChange * scale;
+ height = originalHeight + heightChange * scale;
+ }
+
+ const cosAngle = Math.abs(Math.cos(angle));
+ const sinAngle = Math.abs(Math.sin(angle));
+ const boundingWidth = width * cosAngle + height * sinAngle;
+ const boundingHeight = width * sinAngle + height * cosAngle;
+
+ if (event.edges.left) {
+ const dx = event.deltaRect.left;
+ x += dx;
+ }
+ if (event.edges.top) {
+ const dy = event.deltaRect.top;
+ y += dy;
+ }
+
+ target.style.transform = `translate(${x}px, ${y}px)`;
+ target.style.width = `${boundingWidth + 2 * this.padding}px`;
+ target.style.height = `${boundingHeight + 2 * this.padding}px`;
+
+ canvas.style.width = `${width}px`;
+ canvas.style.height = `${height}px`;
+ canvas.style.transform = `translate(${(boundingWidth - width) / 2}px, ${(boundingHeight - height) / 2
+ }px) rotate(${angle}rad)`;
+
+ target.setAttribute('data-bs-x', x);
+ target.setAttribute('data-bs-y', y);
+
+ this.lastInteracted = target;
}
-
- target.style.width = event.rect.width + 'px';
- target.style.height = event.rect.height + 'px';
-
- // translate when resizing from top or left edges
- x += event.deltaRect.left;
- y += event.deltaRect.top;
-
- target.style.transform = 'translate(' + x + 'px,' + y + 'px)';
-
- target.setAttribute('data-bs-x', x);
- target.setAttribute('data-bs-y', y);
- target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height);
-
- this.onInteraction(target);
},
},
-
modifiers: [
interact.modifiers.restrictSize({
- min: {width: 5, height: 5},
+ min: { width: 50, height: 50 },
}),
],
inertia: true,
});
+
//Arrow key Support for Add-Image and Sign pages
if (window.location.pathname.endsWith('sign') || window.location.pathname.endsWith('add-image')) {
window.addEventListener('keydown', (event) => {
@@ -117,7 +179,8 @@ const DraggableUtils = {
}
// Update position
- target.style.transform = `translate(${x}px, ${y}px)`;
+ const angle = parseFloat(target.getAttribute('data-angle')) || 0;
+ target.style.transform = `translate(${x}px, ${y}px) rotate(${angle}rad)`;
target.setAttribute('data-bs-x', x);
target.setAttribute('data-bs-y', y);
@@ -125,72 +188,97 @@ const DraggableUtils = {
});
}
},
-
onInteraction(target) {
- this.boxDragContainer.appendChild(target);
- },
-
- createDraggableCanvas() {
- const createdCanvas = document.createElement('canvas');
- createdCanvas.id = `draggable-canvas-${this.nextId++}`;
- createdCanvas.classList.add('draggable-canvas');
-
- const x = 0;
- const y = 20;
- createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
- createdCanvas.setAttribute('data-bs-x', x);
- createdCanvas.setAttribute('data-bs-y', y);
-
- //Click element in order to enable arrow keys
- createdCanvas.addEventListener('click', () => {
- this.lastInteracted = createdCanvas;
- });
-
- createdCanvas.onclick = (e) => this.onInteraction(e.target);
-
- this.boxDragContainer.appendChild(createdCanvas);
-
- //Enable Arrow keys directly after the element is created
- this.lastInteracted = createdCanvas;
-
- return createdCanvas;
+ this.lastInteracted = target;
+ // this.boxDragContainer.appendChild(target);
+ // target.appendChild(target.querySelector(".display-canvas"));
},
createDraggableCanvasFromUrl(dataUrl) {
return new Promise((resolve) => {
- var myImage = new Image();
+ const canvasContainer = document.createElement('div');
+ const createdCanvas = document.createElement('canvas'); // Inner canvas
+ const padding = this.padding;
+
+ canvasContainer.id = `draggable-canvas-${this.nextId++}`;
+ canvasContainer.classList.add('draggable-canvas');
+ createdCanvas.classList.add('display-canvas');
+
+ canvasContainer.style.position = 'absolute';
+ canvasContainer.style.padding = `${padding}px`;
+ canvasContainer.style.overflow = 'hidden';
+
+ let x = 0,
+ y = 30,
+ angle = 0;
+ canvasContainer.style.transform = `translate(${x}px, ${y}px)`;
+ canvasContainer.setAttribute('data-bs-x', x);
+ canvasContainer.setAttribute('data-bs-y', y);
+ canvasContainer.setAttribute('data-angle', angle);
+
+ canvasContainer.addEventListener('click', () => {
+ this.lastInteracted = canvasContainer;
+ this.showRotationControls(canvasContainer);
+ });
+ canvasContainer.appendChild(createdCanvas);
+ this.boxDragContainer.appendChild(canvasContainer);
+
+ const myImage = new Image();
myImage.src = dataUrl;
myImage.onload = () => {
- var createdCanvas = this.createDraggableCanvas();
+ const context = createdCanvas.getContext('2d');
createdCanvas.width = myImage.width;
createdCanvas.height = myImage.height;
const imgAspect = myImage.width / myImage.height;
- const pdfAspect = this.boxDragContainer.offsetWidth / this.boxDragContainer.offsetHeight;
+ const containerWidth = this.boxDragContainer.offsetWidth;
+ const containerHeight = this.boxDragContainer.offsetHeight;
- var scaleMultiplier;
- if (imgAspect > pdfAspect) {
- scaleMultiplier = this.boxDragContainer.offsetWidth / myImage.width;
- } else {
- scaleMultiplier = this.boxDragContainer.offsetHeight / myImage.height;
- }
+ let scaleMultiplier = Math.min(containerWidth / myImage.width, containerHeight / myImage.height);
+ const scaleFactor = 0.5;
- var newWidth = createdCanvas.width;
- var newHeight = createdCanvas.height;
- if (scaleMultiplier < 1) {
- newWidth = newWidth * scaleMultiplier;
- newHeight = newHeight * scaleMultiplier;
- }
+ const newWidth = myImage.width * scaleMultiplier * scaleFactor;
+ const newHeight = myImage.height * scaleMultiplier * scaleFactor;
- createdCanvas.style.width = newWidth + 'px';
- createdCanvas.style.height = newHeight + 'px';
+ // Calculate initial bounding box size
+ const cosAngle = Math.abs(Math.cos(angle));
+ const sinAngle = Math.abs(Math.sin(angle));
+ const boundingWidth = newWidth * cosAngle + newHeight * sinAngle;
+ const boundingHeight = newWidth * sinAngle + newHeight * cosAngle;
- var myContext = createdCanvas.getContext('2d');
- myContext.drawImage(myImage, 0, 0);
- resolve(createdCanvas);
+ createdCanvas.style.width = `${newWidth}px`;
+ createdCanvas.style.height = `${newHeight}px`;
+
+ canvasContainer.style.width = `${boundingWidth + 2 * padding}px`;
+ canvasContainer.style.height = `${boundingHeight + 2 * padding}px`;
+
+ context.imageSmoothingEnabled = true;
+ context.imageSmoothingQuality = 'high';
+ context.drawImage(myImage, 0, 0, myImage.width, myImage.height);
+ this.showRotationControls(canvasContainer);
+ this.lastInteracted = canvasContainer;
+
+ resolve(canvasContainer);
+ };
+
+ myImage.onerror = () => {
+ console.error('Failed to load the image.');
+ resolve(null);
};
});
},
+ toggleMaintainRatio() {
+ this.maintainRatioEnabled = !this.maintainRatioEnabled;
+ const button = document.getElementById('ratioToggleBtn');
+ if (this.maintainRatioEnabled) {
+ button.classList.remove('btn-danger');
+ button.classList.add('btn-outline-secondary');
+ } else {
+ button.classList.remove('btn-outline-secondary');
+ button.classList.add('btn-danger');
+ }
+ },
+
deleteAllDraggableCanvases() {
this.boxDragContainer.querySelectorAll('.draggable-canvas').forEach((el) => el.remove());
},
@@ -266,9 +354,61 @@ const DraggableUtils = {
}
},
getLastInteracted() {
- return this.boxDragContainer.querySelector('.draggable-canvas:last-of-type');
+ return this.lastInteracted;
},
+ showRotationControls(element) {
+ const rotationControls = document.getElementById('rotation-controls');
+ const rotationInput = document.getElementById('rotation-input');
+ rotationControls.style.display = 'flex';
+ rotationInput.value = Math.round((parseFloat(element.getAttribute('data-angle')) * 180) / Math.PI);
+ rotationInput.addEventListener('input', this.handleRotationInputChange);
+ },
+ hideRotationControls() {
+ const rotationControls = document.getElementById('rotation-controls');
+ const rotationInput = document.getElementById('rotation-input');
+ rotationControls.style.display = 'none';
+ rotationInput.addEventListener('input', this.handleRotationInputChange);
+ },
+ applyRotationToElement(element, degrees) {
+ const radians = degrees * (Math.PI / 180); // Convert degrees to radians
+ // Get current position
+ const x = parseFloat(element.getAttribute('data-bs-x')) || 0;
+ const y = parseFloat(element.getAttribute('data-bs-y')) || 0;
+
+ // Get the inner canvas (image)
+ const canvas = element.querySelector('.display-canvas');
+ if (canvas) {
+ const originalWidth = parseFloat(canvas.style.width);
+ const originalHeight = parseFloat(canvas.style.height);
+ const padding = this.padding; // Access the padding value
+
+ // Calculate rotated bounding box dimensions
+ const cosAngle = Math.abs(Math.cos(radians));
+ const sinAngle = Math.abs(Math.sin(radians));
+ const boundingWidth = originalWidth * cosAngle + originalHeight * sinAngle + 2 * padding;
+ const boundingHeight = originalWidth * sinAngle + originalHeight * cosAngle + 2 * padding;
+
+ // Update parent container to fit the rotated bounding box
+ element.style.width = `${boundingWidth}px`;
+ element.style.height = `${boundingHeight}px`;
+
+ // Center the canvas within the bounding box, accounting for padding
+ const offsetX = (boundingWidth - originalWidth) / 2 - padding;
+ const offsetY = (boundingHeight - originalHeight) / 2 - padding;
+
+ canvas.style.transform = `translate(${offsetX}px, ${offsetY}px) rotate(${radians}rad)`;
+ }
+
+ // Keep the bounding box positioned properly
+ element.style.transform = `translate(${x}px, ${y}px)`;
+ element.setAttribute('data-angle', radians);
+ },
+ handleRotationInputChange() {
+ const rotationInput = document.getElementById('rotation-input');
+ const degrees = parseFloat(rotationInput.value) || 0;
+ DraggableUtils.applyRotationToElement(DraggableUtils.lastInteracted, degrees);
+ },
storePageContents() {
var pagesMap = this.documentsMap.get(this.pdfDoc);
if (!pagesMap) {
@@ -325,7 +465,7 @@ const DraggableUtils = {
// render the page onto the canvas
var renderContext = {
canvasContext: this.pdfCanvas.getContext('2d'),
- viewport: page.getViewport({scale: 1}),
+ viewport: page.getViewport({ scale: 1 }),
};
await page.render(renderContext).promise;
@@ -352,8 +492,6 @@ const DraggableUtils = {
this.loadPageContents();
}
},
-
- parseTransform(element) {},
async getOverlayedPdfDocument() {
const pdfBytes = await this.pdfDoc.getData();
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
@@ -367,7 +505,6 @@ const DraggableUtils = {
if (pageIdx.includes('offset')) {
continue;
}
- console.log(typeof pageIdx);
const page = pdfDocModified.getPage(parseInt(pageIdx));
let draggablesData = pagesMap[pageIdx];
@@ -376,45 +513,61 @@ const DraggableUtils = {
const offsetHeight = pagesMap[pageIdx + '-offsetHeight'];
for (const draggableData of draggablesData) {
- // embed the draggable canvas
- const draggableElement = draggableData.element;
+ // Embed the draggable canvas
+ const draggableElement = draggableData.element.querySelector('.display-canvas');
const response = await fetch(draggableElement.toDataURL());
const draggableImgBytes = await response.arrayBuffer();
const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes);
- // calculate the position in the pdf document
- const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, '');
- const transformComponents = tansform.split(',');
+ // Extract transformation data
+ const transform = draggableData.element.style.transform || '';
+ const translateRegex = /translate\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/;
+
+ const translateMatch = transform.match(translateRegex);
+
+ const translateX = translateMatch ? parseFloat(translateMatch[1]) : 0;
+ const translateY = translateMatch ? parseFloat(translateMatch[2]) : 0;
+
+ const childTransform = draggableElement.style.transform || '';
+ const childTranslateMatch = childTransform.match(translateRegex);
+
+ const childOffsetX = childTranslateMatch ? parseFloat(childTranslateMatch[1]) : 0;
+ const childOffsetY = childTranslateMatch ? parseFloat(childTranslateMatch[2]) : 0;
+
+ const rotateAngle = parseFloat(draggableData.element.getAttribute('data-angle')) || 0;
+
const draggablePositionPixels = {
- x: parseFloat(transformComponents[0]),
- y: parseFloat(transformComponents[1]),
- width: draggableData.offsetWidth,
- height: draggableData.offsetHeight,
+ x: translateX + childOffsetX + this.padding + 2,
+ y: translateY + childOffsetY + this.padding + 2,
+ width: parseFloat(draggableElement.style.width),
+ height: parseFloat(draggableElement.style.height),
+ angle: rotateAngle, // Store rotation
};
- //Auxiliary variables
+ // Auxiliary variables
let widthAdjusted = page.getWidth();
let heightAdjusted = page.getHeight();
const rotation = page.getRotation();
- //Normalizing angle
+ // Normalize page rotation angle
let normalizedAngle = rotation.angle % 360;
if (normalizedAngle < 0) {
normalizedAngle += 360;
}
- //Changing the page dimension if the angle is 90 or 270
+ // Adjust page dimensions for rotated pages
if (normalizedAngle === 90 || normalizedAngle === 270) {
- let widthTemp = widthAdjusted;
- widthAdjusted = heightAdjusted;
- heightAdjusted = widthTemp;
+ [widthAdjusted, heightAdjusted] = [heightAdjusted, widthAdjusted];
}
+
const draggablePositionRelative = {
x: draggablePositionPixels.x / offsetWidth,
y: draggablePositionPixels.y / offsetHeight,
width: draggablePositionPixels.width / offsetWidth,
height: draggablePositionPixels.height / offsetHeight,
+ angle: draggablePositionPixels.angle,
};
+
const draggablePositionPdf = {
x: draggablePositionRelative.x * widthAdjusted,
y: draggablePositionRelative.y * heightAdjusted,
@@ -422,11 +575,13 @@ const DraggableUtils = {
height: draggablePositionRelative.height * heightAdjusted,
};
- //Defining the image if the page has a 0-degree angle
+ // Calculate position based on normalized page rotation
let x = draggablePositionPdf.x;
let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height;
- //Defining the image position if it is at other angles
+ let originx = x + draggablePositionPdf.width / 2;
+ let originy = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height / 2;
+
if (normalizedAngle === 90) {
x = draggablePositionPdf.y + draggablePositionPdf.height;
y = draggablePositionPdf.x;
@@ -437,17 +592,32 @@ const DraggableUtils = {
x = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height;
y = widthAdjusted - draggablePositionPdf.x;
}
-
- // draw the image
+ // let angle = draggablePositionPixels.angle % 360;
+ // if (angle < 0) angle += 360; // Normalize to positive angle
+ const radians = -draggablePositionPixels.angle; // Convert angle to radians
+ page.pushOperators(
+ PDFLib.pushGraphicsState(),
+ PDFLib.concatTransformationMatrix(1, 0, 0, 1, originx, originy),
+ PDFLib.concatTransformationMatrix(
+ Math.cos(radians),
+ Math.sin(radians),
+ -Math.sin(radians),
+ Math.cos(radians),
+ 0,
+ 0
+ ),
+ PDFLib.concatTransformationMatrix(1, 0, 0, 1, -1 * originx, -1 * originy)
+ );
page.drawImage(pdfImageObject, {
x: x,
y: y,
width: draggablePositionPdf.width,
height: draggablePositionPdf.height,
- rotate: rotation,
});
+ page.pushOperators(PDFLib.popGraphicsState());
}
}
+
this.loadPageContents();
return pdfDocModified;
},
diff --git a/src/main/resources/static/js/fileInput.js b/src/main/resources/static/js/fileInput.js
index 63485c606..b2316f4ff 100644
--- a/src/main/resources/static/js/fileInput.js
+++ b/src/main/resources/static/js/fileInput.js
@@ -9,6 +9,7 @@ if (!isScriptExecuted) {
document.querySelectorAll('.custom-file-chooser').forEach(setupFileInput);
});
}
+let hasDroppedImage = false;
function setupFileInput(chooser) {
const elementId = chooser.getAttribute('data-bs-element-id');
@@ -18,6 +19,11 @@ function setupFileInput(chooser) {
let inputContainer = document.getElementById(inputContainerId);
+ if (inputContainer.id === 'pdf-upload-input-container') {
+ inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.dragAndDropPDF;
+ } else if (inputContainer.id === 'image-upload-input-container') {
+ inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.dragAndDropImage;
+ }
let allFiles = [];
let overlay;
let dragCounter = 0;
@@ -141,12 +147,17 @@ function setupFileInput(chooser) {
files.forEach((file) => dataTransfer.items.add(file));
return dataTransfer;
}
-
function handleFileInputChange(inputElement) {
const files = allFiles;
showOrHideSelectedFilesContainer(files);
- const filesInfo = files.map((f) => ({name: f.name, size: f.size, uniqueId: f.uniqueId}));
+ const filesInfo = files.map((f) => ({
+ name: f.name,
+ size: f.size,
+ uniqueId: f.uniqueId,
+ type: f.type,
+ url: URL.createObjectURL(f),
+ }));
const selectedFilesContainer = $(inputContainer).siblings('.selected-files');
selectedFilesContainer.empty();
@@ -157,30 +168,111 @@ function setupFileInput(chooser) {
$(fileContainer).addClass(fileContainerClasses);
$(fileContainer).attr('id', info.uniqueId);
- let fileIconContainer = createFileIconContainer(info);
+ let fileIconContainer = document.createElement('div');
+ const isDragAndDropEnabled =
+ window.location.pathname.includes('add-image') || window.location.pathname.includes('sign');
+ if (info.type.startsWith('image/')) {
+ let imgPreview = document.createElement('img');
+ imgPreview.src = info.url;
+ imgPreview.alt = 'Preview';
+ imgPreview.style.width = '50px';
+ imgPreview.style.height = '50px';
+ imgPreview.style.objectFit = 'cover';
+ $(fileIconContainer).append(imgPreview);
+
+ if (isDragAndDropEnabled) {
+ let dragIcon = document.createElement('div');
+ dragIcon.classList.add('drag-icon');
+ dragIcon.innerHTML =
+ ' ';
+ fileContainer.appendChild(dragIcon);
+
+ $(fileContainer).attr('draggable', 'true');
+ $(fileContainer).on('dragstart', (e) => {
+ e.originalEvent.dataTransfer.setData('fileUrl', info.url);
+ e.originalEvent.dataTransfer.setData('uniqueId', info.uniqueId);
+ e.originalEvent.dataTransfer.setDragImage(imgPreview, imgPreview.width / 2, imgPreview.height / 2);
+ });
+ enableImagePreviewOnClick(fileIconContainer);
+ } else {
+ $(fileContainer).removeAttr('draggable');
+ }
+ } else {
+ fileIconContainer = createFileIconContainer(info);
+ }
let fileInfoContainer = createFileInfoContainer(info);
- let removeBtn = document.createElement('div');
- removeBtn.classList.add('remove-selected-file');
+ if (!isDragAndDropEnabled) {
+ let removeBtn = document.createElement('div');
+ removeBtn.classList.add('remove-selected-file');
- let removeBtnIconHTML = ` `;
- $(removeBtn).append(removeBtnIconHTML);
- $(removeBtn).attr('data-file-id', info.uniqueId).click(removeFileListener);
-
- $(fileContainer).append(fileIconContainer);
- $(fileContainer).append(fileInfoContainer);
- $(fileContainer).append(removeBtn);
+ let removeBtnIconHTML = ` `;
+ $(removeBtn).append(removeBtnIconHTML);
+ $(removeBtn).attr('data-file-id', info.uniqueId).click(removeFileListener);
+ $(fileContainer).append(removeBtn);
+ }
+ $(fileContainer).append(fileIconContainer, fileInfoContainer);
selectedFilesContainer.append(fileContainer);
});
+ const pageContainers = $('#box-drag-container');
+ pageContainers.off('dragover').on('dragover', (e) => {
+ e.preventDefault();
+ });
- showOrHideSelectedFilesContainer(filesInfo);
+ pageContainers.off('drop').on('drop', (e) => {
+ e.preventDefault();
+ const fileUrl = e.originalEvent.dataTransfer.getData('fileUrl');
+
+ if (fileUrl) {
+ const existingImages = $(e.target).find(`img[src="${fileUrl}"]`);
+ if (existingImages.length === 0) {
+ DraggableUtils.createDraggableCanvasFromUrl(fileUrl);
+ }
+ }
+ const overlayElement = chooser.querySelector('.drag-drop-overlay');
+ if (overlayElement) {
+ overlayElement.style.display = 'none';
+ }
+ hasDroppedImage = true;
+ });
+
+ showOrHideSelectedFilesContainer(files);
}
function showOrHideSelectedFilesContainer(files) {
- if (files && files.length > 0) chooser.style.setProperty('--selected-files-display', 'flex');
- else chooser.style.setProperty('--selected-files-display', 'none');
+ if (files && files.length > 0) {
+ chooser.style.setProperty('--selected-files-display', 'flex');
+ } else {
+ chooser.style.setProperty('--selected-files-display', 'none');
+ }
+ const isDragAndDropEnabled =
+ (window.location.pathname.includes('add-image') || window.location.pathname.includes('sign')) &&
+ files.some((file) => file.type.startsWith('image/'));
+
+ if (!isDragAndDropEnabled) return;
+
+ const selectedFilesContainer = chooser.querySelector('.selected-files');
+
+ let overlayElement = chooser.querySelector('.drag-drop-overlay');
+ if (!overlayElement) {
+ selectedFilesContainer.style.position = 'relative';
+ overlayElement = document.createElement('div');
+ overlayElement.classList.add('draggable-image-overlay');
+
+ overlayElement.innerHTML = 'Drag images to add them to the page';
+ selectedFilesContainer.appendChild(overlayElement);
+ }
+ if (hasDroppedImage) overlayElement.style.display = files && files.length > 0 ? 'flex' : 'none';
+
+ selectedFilesContainer.addEventListener('mouseenter', () => {
+ overlayElement.style.display = 'none';
+ });
+
+ selectedFilesContainer.addEventListener('mouseleave', () => {
+ if (!hasDroppedImage) overlayElement.style.display = files && files.length > 0 ? 'flex' : 'none';
+ });
}
function removeFileListener(e) {
@@ -235,4 +327,52 @@ function setupFileInput(chooser) {
removeFileById(fileId, inputElement);
showOrHideSelectedFilesContainer(allFiles);
});
+ function enableImagePreviewOnClick(container) {
+ const imagePreviewModal = document.getElementById('imagePreviewModal') || createImagePreviewModal();
+
+ container.querySelectorAll('img').forEach((img) => {
+ if (!img.hasPreviewListener) {
+ img.addEventListener('mouseup', function () {
+ const imgElement = imagePreviewModal.querySelector('img');
+ imgElement.src = this.src;
+ imagePreviewModal.style.display = 'flex';
+ });
+ img.hasPreviewListener = true;
+ }
+ });
+
+ function createImagePreviewModal() {
+ const modal = document.createElement('div');
+ modal.id = 'imagePreviewModal';
+ modal.style.position = 'fixed';
+ modal.style.top = '0';
+ modal.style.left = '0';
+ modal.style.width = '100vw';
+ modal.style.height = '100vh';
+ modal.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
+ modal.style.display = 'none';
+ modal.style.justifyContent = 'center';
+ modal.style.alignItems = 'center';
+ modal.style.zIndex = '2000';
+
+ const imgElement = document.createElement('img');
+ imgElement.style.maxWidth = '90%';
+ imgElement.style.maxHeight = '90%';
+
+ modal.appendChild(imgElement);
+ document.body.appendChild(modal);
+
+ modal.addEventListener('click', () => {
+ modal.style.display = 'none';
+ });
+
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape' && modal.style.display === 'flex') {
+ modal.style.display = 'none';
+ }
+ });
+
+ return modal;
+ }
+ }
}
diff --git a/src/main/resources/static/js/pages/add-image.js b/src/main/resources/static/js/pages/add-image.js
index 5899b53f9..2bafd86ec 100644
--- a/src/main/resources/static/js/pages/add-image.js
+++ b/src/main/resources/static/js/pages/add-image.js
@@ -1,23 +1,38 @@
+window.goToFirstOrLastPage = goToFirstOrLastPage;
+
document.getElementById('download-pdf').addEventListener('click', async () => {
- const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
- const modifiedPdfBytes = await modifiedPdf.save();
- const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
- const link = document.createElement('a');
- link.href = URL.createObjectURL(blob);
- link.download = originalFileName + '_addedImage.pdf';
- link.click();
+ const downloadButton = document.getElementById('download-pdf');
+ const originalContent = downloadButton.innerHTML;
+
+ downloadButton.disabled = true;
+ downloadButton.innerHTML = `
+
+ `;
+
+ try {
+ const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
+ const modifiedPdfBytes = await modifiedPdf.save();
+ const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
+ const link = document.createElement('a');
+ link.href = URL.createObjectURL(blob);
+ link.download = originalFileName + '_addedImage.pdf';
+ link.click();
+ } finally {
+ downloadButton.disabled = false;
+ downloadButton.innerHTML = originalContent;
+ }
});
let originalFileName = '';
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
const fileInput = event.target;
fileInput.addEventListener('file-input-change', async (e) => {
- const {allFiles} = e.detail;
+ const { allFiles } = e.detail;
if (allFiles && allFiles.length > 0) {
const file = allFiles[0];
originalFileName = file.name.replace(/\.[^/.]+$/, '');
const pdfData = await file.arrayBuffer();
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
- const pdfDoc = await pdfjsLib.getDocument({data: pdfData}).promise;
+ const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
await DraggableUtils.renderPage(pdfDoc, 0);
document.querySelectorAll('.show-on-file-selected').forEach((el) => {
@@ -30,6 +45,11 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.show-on-file-selected').forEach((el) => {
el.style.cssText = 'display:none !important';
});
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Delete') {
+ DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted());
+ }
+ });
});
const imageUpload = document.querySelector('input[name=image-upload]');
@@ -45,3 +65,12 @@ imageUpload.addEventListener('change', (e) => {
};
}
});
+
+async function goToFirstOrLastPage(page) {
+ if (page) {
+ const lastPage = DraggableUtils.pdfDoc.numPages;
+ await DraggableUtils.goToPage(lastPage - 1);
+ } else {
+ await DraggableUtils.goToPage(0);
+ }
+}
diff --git a/src/main/resources/static/js/pages/sign.js b/src/main/resources/static/js/pages/sign.js
index 8d45c9697..736ca1cd5 100644
--- a/src/main/resources/static/js/pages/sign.js
+++ b/src/main/resources/static/js/pages/sign.js
@@ -73,6 +73,16 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.show-on-file-selected').forEach((el) => {
el.style.cssText = 'display:none !important';
});
+ document.querySelectorAll('.small-file-container-saved img ').forEach((img) => {
+ img.addEventListener('dragstart', (e) => {
+ e.dataTransfer.setData('fileUrl', img.src);
+ });
+ });
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Delete') {
+ DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted());
+ }
+ });
});
const imageUpload = document.querySelector('input[name=image-upload]');
@@ -203,11 +213,26 @@ async function goToFirstOrLastPage(page) {
}
document.getElementById('download-pdf').addEventListener('click', async () => {
- const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
- const modifiedPdfBytes = await modifiedPdf.save();
- const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
- const link = document.createElement('a');
- link.href = URL.createObjectURL(blob);
- link.download = originalFileName + '_signed.pdf';
- link.click();
+ const downloadButton = document.getElementById('download-pdf');
+ const originalContent = downloadButton.innerHTML;
+
+ downloadButton.disabled = true;
+ downloadButton.innerHTML = `
+
+ `;
+
+ try {
+ const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
+ const modifiedPdfBytes = await modifiedPdf.save();
+ const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
+ const link = document.createElement('a');
+ link.href = URL.createObjectURL(blob);
+ link.download = originalFileName + '_signed.pdf';
+ link.click();
+ } catch (error) {
+ console.error('Error downloading PDF:', error);
+ } finally {
+ downloadButton.disabled = false;
+ downloadButton.innerHTML = originalContent;
+ }
});
diff --git a/src/main/resources/static/js/sign/signature-canvas.js b/src/main/resources/static/js/sign/signature-canvas.js
new file mode 100644
index 000000000..03052d9ce
--- /dev/null
+++ b/src/main/resources/static/js/sign/signature-canvas.js
@@ -0,0 +1,85 @@
+const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
+const signaturePad = new SignaturePad(signaturePadCanvas, {
+ minWidth: 1,
+ maxWidth: 2,
+ penColor: 'black',
+});
+
+function addDraggableFromPad() {
+ if (signaturePad.isEmpty()) return;
+ const startTime = Date.now();
+ const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas);
+ console.log(Date.now() - startTime);
+ DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
+}
+
+function getCroppedCanvasDataUrl(canvas) {
+ let originalCtx = canvas.getContext('2d');
+ let originalWidth = canvas.width;
+ let originalHeight = canvas.height;
+ let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
+
+ let minX = originalWidth + 1,
+ maxX = -1,
+ minY = originalHeight + 1,
+ maxY = -1,
+ x = 0,
+ y = 0,
+ currentPixelColorValueIndex;
+
+ for (y = 0; y < originalHeight; y++) {
+ for (x = 0; x < originalWidth; x++) {
+ currentPixelColorValueIndex = (y * originalWidth + x) * 4;
+ let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
+ if (currentPixelAlphaValue > 0) {
+ if (minX > x) minX = x;
+ if (maxX < x) maxX = x;
+ if (minY > y) minY = y;
+ if (maxY < y) maxY = y;
+ }
+ }
+ }
+
+ let croppedWidth = maxX - minX;
+ let croppedHeight = maxY - minY;
+ if (croppedWidth < 0 || croppedHeight < 0) return null;
+ let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
+
+ let croppedCanvas = document.createElement('canvas'),
+ croppedCtx = croppedCanvas.getContext('2d');
+
+ croppedCanvas.width = croppedWidth;
+ croppedCanvas.height = croppedHeight;
+ croppedCtx.putImageData(cuttedImageData, 0, 0);
+
+ return croppedCanvas.toDataURL();
+}
+
+function isMobile() {
+ const userAgentCheck = /Mobi|Android|iPhone|iPad|iPod|Windows Phone|Opera Mini/i.test(navigator.userAgent);
+ const viewportCheck = window.matchMedia('(max-width: 768px)').matches;
+ return userAgentCheck || viewportCheck;
+}
+
+function getDeviceScalingFactor() {
+ return isMobile() ? 3 : 10;
+}
+
+function resizeCanvas() {
+ const ratio = Math.max(window.devicePixelRatio || 1, 1);
+ const additionalFactor = getDeviceScalingFactor();
+
+ signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
+ signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
+ signaturePadCanvas.getContext('2d').scale(ratio * additionalFactor, ratio * additionalFactor);
+
+ signaturePad.clear();
+}
+
+new IntersectionObserver((entries, observer) => {
+ if (entries.some((entry) => entry.intersectionRatio > 0)) {
+ resizeCanvas();
+ }
+}).observe(signaturePadCanvas);
+
+new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html
index f03e83c5b..1cd908a69 100644
--- a/src/main/resources/templates/fragments/common.html
+++ b/src/main/resources/templates/fragments/common.html
@@ -213,7 +213,10 @@
unexpectedError: '[[#{decrypt.unexpectedError}]]',
serverError: '[[#{decrypt.serverError}]]',
success: '[[#{decrypt.success}]]',
- };
+ };
+ window.fileInput = {
+ dragAndDropPDF : '[[#{fileChooser.dragAndDropPDF}]]',
+ dragAndDropImage : '[[#{fileChooser.dragAndDropImage}]]'};
diff --git a/src/main/resources/templates/misc/add-image.html b/src/main/resources/templates/misc/add-image.html
index 5d644dd3d..51fbc94ef 100644
--- a/src/main/resources/templates/misc/add-image.html
+++ b/src/main/resources/templates/misc/add-image.html
@@ -1,65 +1,123 @@
-
-
+
+
+
-
-
-
+
+
+
-
-